main
py 224 lines 7.38 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import warnings
5
6 import pytest
7 import torch.cuda
8
9 from vllm.model_executor.models import (
10 is_pooling_model,
11 is_text_generation_model,
12 supports_multimodal,
13 )
14 from vllm.model_executor.models.adapters import (
15 as_embedding_model,
16 as_seq_cls_model,
17 )
18 from vllm.model_executor.models.registry import (
19 _MULTIMODAL_MODELS,
20 _SPECULATIVE_DECODING_MODELS,
21 _TEXT_GENERATION_MODELS,
22 ModelRegistry,
23 _LazyRegisteredModel,
24 )
25 from vllm.platforms import current_platform
26
27 from ..utils import create_new_process_for_each_test
28 from .registry import HF_EXAMPLE_MODELS
29
30
31 @pytest.mark.parametrize("model_arch", ModelRegistry.get_supported_archs())
32 def test_registry_imports(model_arch):
33 # Skip if transformers version is incompatible
34 model_info = HF_EXAMPLE_MODELS.get_hf_info(model_arch)
35 model_info.check_transformers_version(
36 on_fail="skip",
37 check_max_version=False,
38 check_version_reason="vllm",
39 )
40
41 if model_arch == "Terratorch":
42 import importlib.util
43
44 if importlib.util.find_spec("terratorch") is None:
45 pytest.skip(
46 "terratorch is not installed; "
47 "temporarily skipped while PyPI has `lightning` quarantined "
48 "(see #41376)"
49 )
50
51 # DSpark draft model is supported on CUDA and ROCm; stubbed to None on XPU.
52 if model_arch == "DSparkDraftModel" and not (
53 current_platform.is_cuda() or current_platform.is_rocm()
54 ):
55 pytest.skip("DSparkDraftModel is only supported on CUDA and ROCm")
56
57 if model_arch in ("Dots3NoteForCausalLM", "Dots3NoteMTPModel") and not (
58 current_platform.is_cuda()
59 ):
60 pytest.skip("Dots3 NOTE is only supported on CUDA")
61
62 if model_arch in ("HYV4ForCausalLM", "HYV4MTPModel") and not (
63 current_platform.is_cuda() or current_platform.is_rocm()
64 ):
65 pytest.skip("HY V4 is only supported on CUDA and ROCm")
66
67 if (
68 model_arch == "DeepseekV4ForConditionalGeneration"
69 and not current_platform.is_cuda_alike()
70 ):
71 pytest.skip("Deepseek V4 vision is only supported on CUDA and ROCm")
72
73 # Ensure all model classes can be imported successfully
74 model_cls = ModelRegistry._try_load_model_cls(model_arch)
75 assert model_cls is not None
76
77 if model_arch in _SPECULATIVE_DECODING_MODELS:
78 return # Ignore these models which do not have a unified format
79
80 if model_arch in _TEXT_GENERATION_MODELS or model_arch in _MULTIMODAL_MODELS:
81 assert is_text_generation_model(model_cls)
82
83 # All vLLM models should be convertible to a pooling model
84 assert is_pooling_model(as_seq_cls_model(model_cls))
85 assert is_pooling_model(as_embedding_model(model_cls))
86
87 if model_arch in _MULTIMODAL_MODELS:
88 assert supports_multimodal(model_cls)
89
90
91 @pytest.mark.parametrize(
92 "model_arch,is_mm,score_type",
93 [
94 ("LlamaForCausalLM", False, "bi-encoder"),
95 ("LlavaForConditionalGeneration", True, "bi-encoder"),
96 ("DeepseekV41ForCausalLM", True, "bi-encoder"),
97 ("BertForSequenceClassification", False, "cross-encoder"),
98 ("RobertaForSequenceClassification", False, "cross-encoder"),
99 ("XLMRobertaForSequenceClassification", False, "cross-encoder"),
100 ("GteNewModel", False, "bi-encoder"),
101 ("GteNewForSequenceClassification", False, "cross-encoder"),
102 ("HF_ColBERT", False, "late-interaction"),
103 ],
104 )
105 def test_registry_model_property(model_arch, is_mm, score_type):
106 model_info = ModelRegistry._try_inspect_model_cls(model_arch)
107 assert model_info is not None
108
109 assert model_info.supports_multimodal is is_mm
110 assert model_info.score_type == score_type
111
112
113 @pytest.mark.parametrize(
114 "model_arch,is_pp",
115 [
116 # TODO(woosuk): Re-enable this once the MLP Speculator is supported
117 # in V1.
118 # ("MLPSpeculatorPreTrainedModel", False),
119 ("DeepseekV2ForCausalLM", True),
120 ("Qwen2VLForConditionalGeneration", True),
121 ],
122 )
123 def test_registry_is_pp(model_arch, is_pp):
124 model_info = ModelRegistry._try_inspect_model_cls(model_arch)
125 assert model_info is not None
126
127 assert model_info.supports_pp is is_pp
128
129
130 @create_new_process_for_each_test()
131 @pytest.mark.parametrize(
132 "model_arch",
133 ["LlavaForConditionalGeneration", "Qwen2VLForConditionalGeneration"],
134 )
135 def test_registry_inspect_does_not_init_cuda(model_arch):
136 """Inspecting a model must not import it: these architectures initialize
137 CUDA at import time, so the inspection path has to stay out-of-process."""
138 if not current_platform.is_cuda_alike():
139 pytest.skip("Requires a CUDA-like platform")
140
141 assert ModelRegistry._try_inspect_model_cls(model_arch) is not None
142 assert not torch.cuda.is_initialized()
143
144 ModelRegistry._try_load_model_cls(model_arch)
145 if not torch.cuda.is_initialized():
146 warnings.warn(
147 "This model no longer initializes CUDA on import. "
148 "Please test using a different one.",
149 stacklevel=2,
150 )
151
152
153 @pytest.mark.parametrize(
154 "model_arch,supported",
155 [
156 # ReplaySSM is opt-in per model.
157 ("NemotronHForCausalLM", True),
158 ("KimiLinearForCausalLM", not current_platform.is_rocm()),
159 ("KimiK3ForConditionalGeneration", not current_platform.is_rocm()),
160 ("Mamba2ForCausalLM", False),
161 ("Zamba2ForCausalLM", False),
162 ],
163 )
164 def test_registry_supports_replayssm(model_arch, supported):
165 model_info = ModelRegistry._try_inspect_model_cls(model_arch)
166 assert model_info is not None
167 assert model_info.supports_replayssm is supported
168
169
170 def test_lazy_modelinfo_package_hash_includes_submodules(tmp_path):
171 package_dir = tmp_path / "model_package"
172 package_dir.mkdir()
173 init_file = package_dir / "__init__.py"
174 init_file.write_text("from .model import Model\n", encoding="utf-8")
175 model_file = package_dir / "model.py"
176 model_file.write_text("class Model: pass\n", encoding="utf-8")
177
178 first_hash = _LazyRegisteredModel._get_modelinfo_module_hash(init_file)
179
180 model_file.write_text("class Model:\n supports_pp = True\n", encoding="utf-8")
181 second_hash = _LazyRegisteredModel._get_modelinfo_module_hash(init_file)
182
183 assert first_hash != second_hash
184
185
186 def test_lazy_modelinfo_package_attempts_cache_load(monkeypatch):
187 cached_model_info = object()
188 loaded_hashes = []
189
190 def fake_load_cache(self, module_hash):
191 loaded_hashes.append(module_hash)
192 return cached_model_info
193
194 monkeypatch.setattr(
195 _LazyRegisteredModel,
196 "_load_modelinfo_from_cache",
197 fake_load_cache,
198 )
199 monkeypatch.setattr(
200 "vllm.model_executor.models.registry._run_in_subprocess",
201 lambda _: pytest.fail("Package-backed model should use the cache path"),
202 )
203
204 registered_model = _LazyRegisteredModel(
205 module_name="vllm.model_executor.models.transformers",
206 class_name="TransformersForCausalLM",
207 )
208
209 result = registered_model.inspect_model_cls()
210
211 assert result is cached_model_info
212 assert len(loaded_hashes) == 1
213 assert loaded_hashes[0]
214
215
216 def test_hf_registry_coverage():
217 untested_archs = (
218 ModelRegistry.get_supported_archs() - HF_EXAMPLE_MODELS.get_supported_archs()
219 )
220
221 assert not untested_archs, (
222 "Please add the following architectures to "
223 f"`tests/models/registry.py`: {untested_archs}"
224 )