Files
vLLM/tests/models/transformers/test_layer_registry_e2e.py
T

114 lines
4.1 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""End-to-end check of the Transformers backend's hw-agnostic layers.
Serves a tiny Llama through `vllm_runner` with and without
`VLLM_USE_HW_AGNOSTIC` and checks the layer providers and logprobs. Needs a
real engine, so it runs in the GPU job. The CPU-only unit tests for the
resolution logic (`layers._resolve`) are in `test_layer_registry.py`.
"""
import pytest
import torch
@pytest.fixture(scope="module")
def tiny_llama_path(tmp_path_factory):
"""A randomly-initialized microscopic Llama saved to disk (with an ungated
tokenizer) so vLLM can load it like any local checkpoint."""
from transformers import AutoTokenizer, LlamaConfig, LlamaForCausalLM
tokenizer = AutoTokenizer.from_pretrained("hf-internal-testing/llama-tokenizer")
config = LlamaConfig(
vocab_size=tokenizer.vocab_size,
hidden_size=128,
intermediate_size=256,
num_hidden_layers=2,
num_attention_heads=4,
rms_norm_eps=1e-6,
hidden_act="silu",
)
torch.manual_seed(0)
model = LlamaForCausalLM(config)
path = tmp_path_factory.mktemp("tiny_llama")
model.save_pretrained(path)
tokenizer.save_pretrained(path)
return str(path)
# Registered names of the layers the backend can
# currently route to hw-agnostic implementations.
_COVERED_LAYERS = ("rms_norm", "silu_and_mul")
def _layer_providers(model) -> dict[str, str]:
"""Map each covered layer type present in the model to the provider its
implementation came from (``hw_agnostic`` or ``vllm``).
"""
def provider_of(module) -> str | None:
for cls in type(module).__mro__:
if "hw_agnostic.layers" in cls.__module__:
return "hw_agnostic"
if ".model_executor.layers." in cls.__module__:
return "vllm"
return None
providers: dict[str, str] = {}
for module in model.modules():
name = getattr(module, "name", None)
if (
name in _COVERED_LAYERS
and name not in providers
and (prov := provider_of(module)) is not None
):
providers[name] = prov
return providers
def _serve(vllm_runner, model_path, prompts):
"""Serve the model through the backend; return (layer_providers, logprobs)."""
with vllm_runner(
model_path,
model_impl="transformers",
max_model_len=64,
enforce_eager=True,
gpu_memory_utilization=0.3,
) as runner:
assert runner.llm.llm_engine.model_config.using_transformers_backend()
providers = runner.apply_model(_layer_providers)[0]
outputs = runner.generate_greedy_logprobs(
prompts, max_tokens=32, num_logprobs=5
)
return providers, outputs
def test_hw_agnostic_matches_vllm_end_to_end(monkeypatch, vllm_runner, tiny_llama_path):
"""Serving the tiny model with hw-agnostic layers matches the vLLM baseline."""
# spawn: `layers` resolves its symbols at import time, so the worker must
# re-import it with VLLM_USE_HW_AGNOSTIC already set (fork would inherit
# the parent's already-resolved module).
monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
# apply_model pickles the introspection function.
monkeypatch.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1")
from ..utils import check_logprobs_close
prompts = ["The capital of France is", "vLLM is"]
monkeypatch.setenv("VLLM_USE_HW_AGNOSTIC", "0")
vllm_providers, vllm_outputs = _serve(vllm_runner, tiny_llama_path, prompts)
# Both replaceable layers present in a Llama block must be vLLM's here.
assert vllm_providers == {"rms_norm": "vllm", "silu_and_mul": "vllm"}
monkeypatch.setenv("VLLM_USE_HW_AGNOSTIC", "1")
hw_providers, hw_outputs = _serve(vllm_runner, tiny_llama_path, prompts)
assert hw_providers == {"rms_norm": "hw_agnostic", "silu_and_mul": "hw_agnostic"}
check_logprobs_close(
outputs_0_lst=vllm_outputs,
outputs_1_lst=hw_outputs,
name_0="vllm",
name_1="hw_agnostic",
)