[MyPy][3/N] Fix MyPy errors in test groups (part 3) (#55939)
Signed-off-by: Taimys <ffy2281693949@gmail.com> Signed-off-by: Luyi Xiao <81631911+luyixiao95@users.noreply.github.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Luyi Xiao <81631911+luyixiao95@users.noreply.github.com> Co-authored-by: Taimys <ffy2281693949@gmail.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Taimys committed
Sep 30, 2026 at 19:06 UTC
5225f1bacb907e3d7ec76e7f356c598468d229ec
118 files changed
+948
-605
tests/benchmarks/sweep/test_param_sweep.py
+8
-8
@@ -116,7 +116,7 @@ class TestParameterSweep:
116
117
def test_from_records_list(self):
118
"""Test creating ParameterSweep from a list of records."""
119
- records = [
119
+ records: list[dict[str, object]] = [
120
{"max_tokens": 100, "temperature": 0.7},
121
{"max_tokens": 200, "temperature": 0.9},
122
]
@@ -127,7 +127,7 @@ class TestParameterSweep:
127
128
def test_read_from_dict(self):
129
"""Test creating ParameterSweep from a dict format."""
130
- data = {
130
+ data: dict[str, dict[str, object]] = {
131
"experiment1": {"max_tokens": 100, "temperature": 0.7},
132
"experiment2": {"max_tokens": 200, "temperature": 0.9},
133
}
@@ -149,7 +149,7 @@ class TestParameterSweep:
149
150
def test_read_json_list_format(self):
151
"""Test reading JSON file with list format."""
152
- records = [
152
+ records: list[dict[str, object]] = [
153
{"max_tokens": 100, "temperature": 0.7},
154
{"max_tokens": 200, "temperature": 0.9},
155
]
@@ -168,7 +168,7 @@ class TestParameterSweep:
168
169
def test_read_json_dict_format(self):
170
"""Test reading JSON file with dict format."""
171
- data = {
171
+ data: dict[str, dict[str, object]] = {
172
"experiment1": {"max_tokens": 100, "temperature": 0.7},
173
"experiment2": {"max_tokens": 200, "temperature": 0.9},
174
}
@@ -190,7 +190,7 @@ class TestParameterSweep:
190
def test_unique_benchmark_names_validation(self):
191
"""Test that duplicate _benchmark_name values raise an error."""
192
# Test with duplicate names in list format
193
- records = [
193
+ records: list[dict[str, object]] = [
194
{"_benchmark_name": "exp1", "max_tokens": 100},
195
{"_benchmark_name": "exp1", "max_tokens": 200},
196
]
@@ -200,7 +200,7 @@ class TestParameterSweep:
200
201
def test_unique_benchmark_names_multiple_duplicates(self):
202
"""Test validation with multiple duplicate names."""
203
- records = [
203
+ records: list[dict[str, object]] = [
204
{"_benchmark_name": "exp1", "max_tokens": 100},
205
{"_benchmark_name": "exp1", "max_tokens": 200},
206
{"_benchmark_name": "exp2", "max_tokens": 300},
@@ -212,7 +212,7 @@ class TestParameterSweep:
212
213
def test_no_benchmark_names_allowed(self):
214
"""Test that records without _benchmark_name are allowed."""
215
- records = [
215
+ records: list[dict[str, object]] = [
216
{"max_tokens": 100, "temperature": 0.7},
217
{"max_tokens": 200, "temperature": 0.9},
218
]
@@ -221,7 +221,7 @@ class TestParameterSweep:
221
222
def test_mixed_benchmark_names_allowed(self):
223
"""Test that mixing records with and without _benchmark_name is allowed."""
224
- records = [
224
+ records: list[dict[str, object]] = [
225
{"_benchmark_name": "exp1", "max_tokens": 100},
226
{"max_tokens": 200, "temperature": 0.9},
227
]
tests/benchmarks/test_audio_dataset.py
+10
-5
@@ -13,6 +13,7 @@ import soundfile as sf
13
import vllm.benchmarks.datasets.datasets as datasets_module
14
import vllm.benchmarks.lib.endpoint_request_func as request_func_module
15
from vllm.benchmarks.lib.endpoint_request_func import RequestFuncInput
16
+from vllm.tokenizers import TokenizerLike
17
18
pytestmark = pytest.mark.skip_global_cleanup
19
@@ -44,6 +45,10 @@ class _CohereNameOnlyTokenizer(_Tokenizer):
45
super().__init__("cohere/some-local-checkpoint")
46
47
48
+def _as_tokenizer(tokenizer: _Tokenizer) -> TokenizerLike:
49
+ return cast(TokenizerLike, tokenizer)
50
+
51
+
52
def _write_wav(path: Path, duration_s: float = 0.1, sample_rate: int = 16_000) -> None:
53
num_samples = int(duration_s * sample_rate)
54
sf.write(path, np.zeros(num_samples, dtype=np.float32), sample_rate)
@@ -109,7 +114,7 @@ def test_asr_dataset_sample_handles_local_audio_paths(tmp_path: Path) -> None:
114
]
115
116
samples = dataset.sample(
112
- tokenizer=_Tokenizer(),
117
+ tokenizer=_as_tokenizer(_Tokenizer()),
118
num_requests=1,
119
output_len=32,
120
asr_min_audio_len_sec=0.0,
@@ -146,7 +151,7 @@ def test_asr_dataset_sample_handles_embedded_audio_bytes(
151
]
152
153
samples = dataset.sample(
149
- tokenizer=_Tokenizer(),
154
+ tokenizer=_as_tokenizer(_Tokenizer()),
155
num_requests=1,
156
output_len=32,
157
asr_min_audio_len_sec=0.0,
@@ -240,7 +245,7 @@ def _make_asr_dataset(tmp_path: Path) -> datasets_module.ASRDataset:
245
def test_asr_dataset_cohere_class_name_gets_decoder_prompt(tmp_path: Path) -> None:
246
dataset = _make_asr_dataset(tmp_path)
247
samples = dataset.sample(
243
- tokenizer=CohereAsrTokenizer(),
248
+ tokenizer=_as_tokenizer(CohereAsrTokenizer()),
249
num_requests=1,
250
output_len=32,
251
asr_min_audio_len_sec=0.0,
@@ -255,7 +260,7 @@ def test_asr_dataset_cohere_name_or_path_fallback_gets_decoder_prompt(
260
) -> None:
261
dataset = _make_asr_dataset(tmp_path)
262
samples = dataset.sample(
258
- tokenizer=_CohereNameOnlyTokenizer(),
263
+ tokenizer=_as_tokenizer(_CohereNameOnlyTokenizer()),
264
num_requests=1,
265
output_len=32,
266
asr_min_audio_len_sec=0.0,
@@ -268,7 +273,7 @@ def test_asr_dataset_cohere_name_or_path_fallback_gets_decoder_prompt(
273
def test_asr_dataset_unknown_tokenizer_gets_empty_prompt(tmp_path: Path) -> None:
274
dataset = _make_asr_dataset(tmp_path)
275
samples = dataset.sample(
271
- tokenizer=_Tokenizer(name_or_path="some-other/asr-model"),
276
+ tokenizer=_as_tokenizer(_Tokenizer(name_or_path="some-other/asr-model")),
277
num_requests=1,
278
output_len=32,
279
asr_min_audio_len_sec=0.0,
tests/benchmarks/test_bfcl_dataset.py
+2
-2
@@ -271,7 +271,7 @@ def test_bfcl_prompt_len_includes_tools(tmp_path: Path) -> None:
271
args.num_prompts = 1
272
273
with _patch_hf_api(fake_download):
274
- samples = get_samples(args, fake)
274
+ samples = get_samples(args, fake) # type: ignore[arg-type]
275
276
assert len(samples) == 1
277
assert captured["tools"] is not None, (
@@ -311,7 +311,7 @@ def test_bfcl_prompt_len_falls_back_when_tokenizer_rejects_tools(
311
args.num_prompts = 1
312
313
with _patch_hf_api(fake_download):
314
- samples = get_samples(args, _LegacyTokenizer())
314
+ samples = get_samples(args, _LegacyTokenizer()) # type: ignore[arg-type]
315
316
assert len(samples) == 1
317
assert samples[0].prompt_len > 0
tests/benchmarks/test_custom_dataset_chat_template_kwargs.py
+2
-2
@@ -58,7 +58,7 @@ def test_chat_template_kwargs_forwarded(tmp_path: Path) -> None:
58
_write_one(jsonl)
59
60
tok = _RecordingTokenizer()
61
- get_samples(_args(str(jsonl), {"thinking": True}), tok)
61
+ get_samples(_args(str(jsonl), {"thinking": True}), tok) # type: ignore[arg-type]
62
63
assert tok.captured_kwargs == {"thinking": True}
64
@@ -70,6 +70,6 @@ def test_chat_template_kwargs_default_is_noop(tmp_path: Path) -> None:
70
_write_one(jsonl)
71
72
tok = _RecordingTokenizer()
73
- get_samples(_args(str(jsonl), None), tok)
73
+ get_samples(_args(str(jsonl), None), tok) # type: ignore[arg-type]
74
75
assert tok.captured_kwargs == {}
tests/benchmarks/test_custom_image_dataset.py
+29
-15
@@ -4,7 +4,7 @@ import json
4
from argparse import Namespace
5
from io import BytesIO
6
from pathlib import Path
7
-from typing import Any
7
+from typing import Any, cast
8
9
import pybase64 as base64
10
import pytest
@@ -17,6 +17,7 @@ from vllm.benchmarks.lib.endpoint_request_func import (
17
_get_chat_content,
18
_get_chat_messages,
19
)
20
+from vllm.tokenizers import TokenizerLike
21
22
pytestmark = pytest.mark.skip_global_cleanup
23
@@ -31,6 +32,10 @@ class _Tokenizer:
32
return _TokenizedPrompt(prompt)
33
34
35
+def _tokenizer() -> TokenizerLike:
36
+ return cast(TokenizerLike, _Tokenizer())
37
+
38
+
39
def _write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None:
40
with path.open("w") as f:
41
for row in rows:
@@ -92,7 +97,7 @@ def test_get_samples_custom_image_cli_path_supports_multi_image_and_content(
97
],
98
)
99
95
- samples = get_samples(_args_for_custom_image(jsonl), _Tokenizer())
100
+ samples = get_samples(_args_for_custom_image(jsonl), _tokenizer())
101
102
assert len(samples) == 2
103
assert samples[0].request_id == "req-0"
@@ -106,6 +111,7 @@ def test_get_samples_custom_image_cli_path_supports_multi_image_and_content(
111
assert samples[1].multi_modal_data is None
112
assert isinstance(samples[1].prompt, list)
113
assert samples[1].prompt[0] == {"type": "text", "text": "Now compare "}
114
+ assert isinstance(samples[1].prompt[1], dict)
115
assert samples[1].prompt[1]["image_url"]["url"] == f"file://{image_c}"
116
117
@@ -126,7 +132,7 @@ def test_custom_image_dataset_uses_all_image_files(tmp_path: Path) -> None:
132
133
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
134
samples = dataset.sample(
129
- tokenizer=_Tokenizer(),
135
+ tokenizer=_tokenizer(),
136
num_requests=1,
137
output_len=32,
138
)
@@ -171,7 +177,7 @@ def test_custom_image_dataset_preserves_interleaved_content_order(
177
178
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
179
samples = dataset.sample(
174
- tokenizer=_Tokenizer(),
180
+ tokenizer=_tokenizer(),
181
num_requests=1,
182
output_len=32,
183
)
@@ -181,14 +187,18 @@ def test_custom_image_dataset_preserves_interleaved_content_order(
187
assert sample.multi_modal_data is None
188
assert sample.prompt_len == 2
189
assert isinstance(sample.prompt, list)
184
- assert [part["type"] for part in sample.prompt] == [
190
+ parts = []
191
+ for part in sample.prompt:
192
+ assert isinstance(part, dict)
193
+ parts.append(part)
194
+ assert [part["type"] for part in parts] == [
195
"text",
196
"image_url",
197
"text",
198
"image_url",
199
]
190
- assert sample.prompt[1]["image_url"]["url"] == f"file://{image_a}"
191
- assert sample.prompt[3]["image_url"] == {
200
+ assert parts[1]["image_url"]["url"] == f"file://{image_a}"
201
+ assert parts[3]["image_url"] == {
202
"url": f"file://{image_b}",
203
"detail": "low",
204
}
@@ -223,7 +233,7 @@ def test_custom_image_dataset_wraps_interleaved_content_for_multimodal_chat(
233
234
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
235
samples = dataset.sample(
226
- tokenizer=_Tokenizer(),
236
+ tokenizer=_tokenizer(),
237
num_requests=1,
238
output_len=32,
239
enable_multimodal_chat=True,
@@ -289,7 +299,7 @@ def test_custom_image_dataset_encodes_image_media_when_requested(
299
300
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
301
samples = dataset.sample(
292
- tokenizer=_Tokenizer(),
302
+ tokenizer=_tokenizer(),
303
num_requests=1,
304
output_len=32,
305
ensure_client_side_data=True,
@@ -335,7 +345,7 @@ def test_custom_image_dataset_encodes_interleaved_image_media(
345
346
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
347
samples = dataset.sample(
338
- tokenizer=_Tokenizer(),
348
+ tokenizer=_tokenizer(),
349
num_requests=1,
350
output_len=32,
351
ensure_client_side_data=True,
@@ -343,9 +353,13 @@ def test_custom_image_dataset_encodes_interleaved_image_media(
353
354
sample = samples[0]
355
assert isinstance(sample.prompt, list)
346
- _assert_png_data_url(sample.prompt[1]["image_url"]["url"])
347
- _assert_png_data_url(sample.prompt[2]["image_url"]["url"])
348
- assert sample.prompt[2]["image_url"]["detail"] == "low"
356
+ parts = []
357
+ for part in sample.prompt:
358
+ assert isinstance(part, dict)
359
+ parts.append(part)
360
+ _assert_png_data_url(parts[1]["image_url"]["url"])
361
+ _assert_png_data_url(parts[2]["image_url"]["url"])
362
+ assert parts[2]["image_url"]["detail"] == "low"
363
364
365
@pytest.mark.benchmark
@@ -363,7 +377,7 @@ def test_custom_image_dataset_rejects_invalid_image_media(
377
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
378
with pytest.raises(ValueError, match="Invalid image URL"):
379
dataset.sample(
366
- tokenizer=_Tokenizer(),
380
+ tokenizer=_tokenizer(),
381
num_requests=1,
382
output_len=32,
383
ensure_client_side_data=True,
@@ -380,7 +394,7 @@ def test_custom_image_dataset_rejects_invalid_content_part(
394
dataset = CustomImageDataset(dataset_path=str(jsonl), disable_shuffle=True)
395
with pytest.raises(ValueError, match="type 'text', 'image', or 'image_url'"):
396
dataset.sample(
383
- tokenizer=_Tokenizer(),
397
+ tokenizer=_tokenizer(),
398
num_requests=1,
399
output_len=32,
400
)
tests/benchmarks/test_random_dataset.py
+2
@@ -37,6 +37,7 @@ def random_dataset_params() -> Params:
37
38
def _fingerprint_sample(req: SampleRequest) -> tuple[str, int, int]:
39
"""Project a SampleRequest into a comparable tuple."""
40
+ assert isinstance(req.prompt, str)
41
return (req.prompt, req.prompt_len, req.expected_output_len)
42
43
@@ -153,6 +154,7 @@ def _mm_fingerprint_sample(
154
- count of multimodal items
155
- per-item type and URL prefix (e.g., 'data:image/jpeg;base64,')
156
"""
157
+ assert isinstance(req.prompt, str)
158
items = req.multi_modal_data or []
159
item_prefixes: list[str] = []
160
for it in items:
tests/benchmarks/test_sampling_params.py
+4
-4
@@ -19,7 +19,7 @@ class _FakeTokenizer(TokenizerLike):
19
# -- Properties required by TokenizerLike --
20
21
@classmethod
22
- def from_pretrained(cls, path_or_repo_id, *a, **kw): # type: ignore[override]
22
+ def from_pretrained(cls, path_or_repo_id, *a, **kw):
23
return cls()
24
25
@property
@@ -80,16 +80,16 @@ class _FakeTokenizer(TokenizerLike):
80
def apply_chat_template(self, messages, **kw): # type: ignore[override]
81
raise NotImplementedError
82
83
- def convert_tokens_to_ids(self, tokens): # type: ignore[override]
83
+ def convert_tokens_to_ids(self, tokens):
84
raise NotImplementedError
85
86
def convert_tokens_to_string(self, tokens: list[str]) -> str:
87
raise NotImplementedError
88
89
- def decode(self, ids, skip_special_tokens: bool = False) -> str: # type: ignore[override]
89
+ def decode(self, ids, skip_special_tokens: bool = False) -> str:
90
raise NotImplementedError
91
92
- def convert_ids_to_tokens( # type: ignore[override]
92
+ def convert_ids_to_tokens(
93
self, ids, skip_special_tokens: bool = False
94
) -> list[str]:
95
raise NotImplementedError
tests/benchmarks/test_throughput_cli.py
+3
-2
@@ -9,6 +9,7 @@ serve-side dataset coverage.
9
"""
10
11
import subprocess
12
+from argparse import Namespace
13
from types import SimpleNamespace
14
15
import pytest
@@ -289,7 +290,7 @@ def test_to_serve_args_supports_mm_processor_flags() -> None:
290
explicit 0 instead of coercing it to None (which later crashed
291
RandomMultiModalDataset.get_prefix on ``None <= 0``).
292
"""
292
- args = SimpleNamespace(
293
+ args = Namespace(
294
random_input_len=32,
295
random_output_len=1,
296
random_prefix_len=0,
@@ -306,7 +307,7 @@ def test_to_serve_args_supports_mm_processor_flags() -> None:
307
308
def test_to_serve_args_keeps_legacy_flag_fallback() -> None:
309
"""Callers that only set the legacy flags still get them mapped."""
309
- args = SimpleNamespace(
310
+ args = Namespace(
311
input_len=256,
312
output_len=8,
313
prefix_len=16,
tests/entrypoints/anthropic/test_anthropic_messages_conversion.py
+14
-6
@@ -355,9 +355,11 @@ class TestToolResultContent:
355
if m["role"] == "user" and isinstance(m.get("content"), list)
356
]
357
assert len(follow_up) == 1
358
- assert follow_up[0]["content"][0]["image_url"]["url"] == (
359
- "data:image/jpeg;base64,QUFB"
360
- )
358
+ content = follow_up[0]["content"]
359
+ assert isinstance(content, list)
360
+ image = content[0]
361
+ assert image["type"] == "image_url"
362
+ assert image["image_url"]["url"] == "data:image/jpeg;base64,QUFB"
363
364
def test_tool_result_with_multiple_images(self):
365
request = self._make_tool_result_request(
@@ -387,7 +389,12 @@ class TestToolResultContent:
389
if m["role"] == "user" and isinstance(m.get("content"), list)
390
]
391
assert len(follow_up) == 1
390
- urls = [p["image_url"]["url"] for p in follow_up[0]["content"]]
392
+ content = follow_up[0]["content"]
393
+ assert isinstance(content, list)
394
+ urls = []
395
+ for part in content:
396
+ assert part["type"] == "image_url"
397
+ urls.append(part["image_url"]["url"])
398
assert urls == [
399
"data:image/png;base64,IMG1",
400
"https://example.com/img2.jpg",
@@ -584,7 +591,8 @@ class TestThinkingBlockConversion:
591
asst = asst_msgs[0]
592
593
assert asst.get("reasoning") == "I need to call the calculator."
587
- tool_calls = list(asst.get("tool_calls", []))
594
+ tool_calls = asst.get("tool_calls")
595
+ assert tool_calls is not None
596
assert len(tool_calls) == 1
597
assert tool_calls[0]["function"]["name"] == "calculator"
598
# No text content alongside reasoning + tool_use.
@@ -1439,7 +1447,7 @@ class TestDetectMergeInlineSystem:
1447
model_config=None,
1448
)
1449
assert (
1442
- AnthropicServingMessages._should_merge_inline_system(online_renderer)
1450
+ AnthropicServingMessages._should_merge_inline_system(online_renderer) # type: ignore[arg-type]
1451
is expected
1452
)
1453
assert mock_logger.warning_once.called is expected
tests/entrypoints/multimodal/llm/test_mm_processor_kwargs.py
+69
-41
@@ -7,22 +7,26 @@ from unittest.mock import Mock
7
import pytest
8
9
from vllm import LLM, SamplingParams
10
+from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
11
+from vllm.inputs import TextPrompt
12
+from vllm.outputs import RequestOutput
13
14
15
def _make_mock_llm() -> LLM:
16
llm = object.__new__(LLM)
14
- llm.model_config = SimpleNamespace(
17
+ llm.model_config = SimpleNamespace( # type: ignore[assignment]
18
runner_type="generate", enable_prompt_embeds=False
19
)
20
return llm
21
22
20
-def test_generate_forwards_mm_processor_kwargs() -> None:
23
+def test_generate_forwards_mm_processor_kwargs(monkeypatch: pytest.MonkeyPatch) -> None:
24
llm = _make_mock_llm()
25
mm_processor_kwargs = {"num_crops": 4}
26
sampling_params = SamplingParams(max_tokens=1)
27
25
- llm._run_completion = Mock(return_value=["ok"])
28
+ _run_completion = Mock(return_value=["ok"])
29
+ monkeypatch.setattr(llm, "_run_completion", _run_completion)
30
31
outputs = llm.generate(
32
"prompt",
@@ -31,17 +35,18 @@ def test_generate_forwards_mm_processor_kwargs() -> None:
35
)
36
37
assert outputs == ["ok"]
34
- assert llm._run_completion.call_args.kwargs["mm_processor_kwargs"] == (
38
+ assert _run_completion.call_args.kwargs["mm_processor_kwargs"] == (
39
mm_processor_kwargs
40
)
41
42
39
-def test_enqueue_forwards_mm_processor_kwargs() -> None:
43
+def test_enqueue_forwards_mm_processor_kwargs(monkeypatch: pytest.MonkeyPatch) -> None:
44
llm = _make_mock_llm()
45
mm_processor_kwargs = {"do_resize": False}
46
sampling_params = SamplingParams(max_tokens=1)
47
44
- llm._add_completion_requests = Mock(return_value=["req-0"])
48
+ _add_completion_requests = Mock(return_value=["req-0"])
49
+ monkeypatch.setattr(llm, "_add_completion_requests", _add_completion_requests)
50
51
request_ids = llm.enqueue(
52
"prompt",
@@ -51,18 +56,19 @@ def test_enqueue_forwards_mm_processor_kwargs() -> None:
56
)
57
58
assert request_ids == ["req-0"]
54
- assert llm._add_completion_requests.call_args.kwargs["mm_processor_kwargs"] == (
59
+ assert _add_completion_requests.call_args.kwargs["mm_processor_kwargs"] == (
60
mm_processor_kwargs
61
)
62
63
59
-def test_chat_forwards_mm_processor_kwargs() -> None:
64
+def test_chat_forwards_mm_processor_kwargs(monkeypatch: pytest.MonkeyPatch) -> None:
65
llm = _make_mock_llm()
66
mm_processor_kwargs = {"do_pan_and_scan": True}
67
sampling_params = SamplingParams(max_tokens=1)
63
- messages = [{"role": "user", "content": "hello"}]
68
+ messages: list[ChatCompletionMessageParam] = [{"role": "user", "content": "hello"}]
69
65
- llm._run_chat = Mock(return_value=["ok"])
70
+ _run_chat = Mock(return_value=["ok"])
71
+ monkeypatch.setattr(llm, "_run_chat", _run_chat)
72
73
outputs = llm.chat(
74
messages,
@@ -71,18 +77,19 @@ def test_chat_forwards_mm_processor_kwargs() -> None:
77
)
78
79
assert outputs == ["ok"]
74
- assert llm._run_chat.call_args.kwargs["mm_processor_kwargs"] == (
75
- mm_processor_kwargs
76
- )
80
+ assert _run_chat.call_args.kwargs["mm_processor_kwargs"] == mm_processor_kwargs
81
82
79
-def test_enqueue_chat_forwards_mm_processor_kwargs() -> None:
83
+def test_enqueue_chat_forwards_mm_processor_kwargs(
84
+ monkeypatch: pytest.MonkeyPatch,
85
+) -> None:
86
llm = _make_mock_llm()
87
mm_processor_kwargs = {"do_pan_and_scan": True}
88
sampling_params = SamplingParams(max_tokens=1)
83
- messages = [{"role": "user", "content": "hello"}]
89
+ messages: list[ChatCompletionMessageParam] = [{"role": "user", "content": "hello"}]
90
85
- llm._add_chat_requests = Mock(return_value=["req-0"])
91
+ _add_chat_requests = Mock(return_value=["req-0"])
92
+ monkeypatch.setattr(llm, "_add_chat_requests", _add_chat_requests)
93
94
request_ids = llm.enqueue_chat(
95
messages,
@@ -92,67 +99,79 @@ def test_enqueue_chat_forwards_mm_processor_kwargs() -> None:
99
)
100
101
assert request_ids == ["req-0"]
95
- assert llm._add_chat_requests.call_args.kwargs["mm_processor_kwargs"] == (
102
+ assert _add_chat_requests.call_args.kwargs["mm_processor_kwargs"] == (
103
mm_processor_kwargs
104
)
105
106
100
-def test_run_chat_forwards_mm_processor_kwargs() -> None:
107
+def test_run_chat_forwards_mm_processor_kwargs(monkeypatch: pytest.MonkeyPatch) -> None:
108
llm = _make_mock_llm()
109
mm_processor_kwargs = {"num_crops": 8}
110
sampling_params = SamplingParams(max_tokens=1)
104
- messages = [{"role": "user", "content": "hello"}]
111
+ messages: list[ChatCompletionMessageParam] = [{"role": "user", "content": "hello"}]
112
sentinel_output = ["done"]
113
107
- llm._add_chat_requests = Mock()
108
- llm._run_engine = Mock(return_value=sentinel_output)
114
+ _add_chat_requests = Mock()
115
+ monkeypatch.setattr(llm, "_add_chat_requests", _add_chat_requests)
116
+ _run_engine = Mock(return_value=sentinel_output)
117
+ monkeypatch.setattr(llm, "_run_engine", _run_engine)
118
119
outputs = llm._run_chat(
120
messages=messages,
121
params=sampling_params,
113
- output_type=object,
122
+ output_type=RequestOutput,
123
use_tqdm=False,
124
mm_processor_kwargs=mm_processor_kwargs,
125
)
126
127
assert outputs == sentinel_output
119
- assert llm._add_chat_requests.call_args.kwargs["mm_processor_kwargs"] == (
128
+ assert _add_chat_requests.call_args.kwargs["mm_processor_kwargs"] == (
129
mm_processor_kwargs
130
)
131
132
124
-def test_run_completion_forwards_mm_processor_kwargs() -> None:
133
+def test_run_completion_forwards_mm_processor_kwargs(
134
+ monkeypatch: pytest.MonkeyPatch,
135
+) -> None:
136
llm = _make_mock_llm()
137
mm_processor_kwargs = {"min_pixels": 4 * 28 * 28}
138
sampling_params = SamplingParams(max_tokens=1)
139
sentinel_output = ["done"]
140
130
- llm._add_completion_requests = Mock()
131
- llm._run_engine = Mock(return_value=sentinel_output)
141
+ _add_completion_requests = Mock()
142
+ monkeypatch.setattr(llm, "_add_completion_requests", _add_completion_requests)
143
+ _run_engine = Mock(return_value=sentinel_output)
144
+ monkeypatch.setattr(llm, "_run_engine", _run_engine)
145
146
outputs = llm._run_completion(
147
prompts=["prompt"],
148
params=sampling_params,
136
- output_type=object,
149
+ output_type=RequestOutput,
150
use_tqdm=False,
151
mm_processor_kwargs=mm_processor_kwargs,
152
)
153
154
assert outputs == sentinel_output
142
- assert llm._add_completion_requests.call_args.kwargs["mm_processor_kwargs"] == (
155
+ assert _add_completion_requests.call_args.kwargs["mm_processor_kwargs"] == (
156
mm_processor_kwargs
157
)
158
159
147
-def test_add_completion_requests_forwards_mm_processor_kwargs() -> None:
160
+def test_add_completion_requests_forwards_mm_processor_kwargs(
161
+ monkeypatch: pytest.MonkeyPatch,
162
+) -> None:
163
llm = _make_mock_llm()
164
mm_processor_kwargs = {"max_dynamic_patch": 4}
165
sampling_params = SamplingParams(max_tokens=1)
166
152
- llm._params_to_seq = Mock(return_value=[sampling_params])
153
- llm._lora_request_to_seq = Mock(return_value=[None])
154
- llm._priority_to_seq = Mock(return_value=[0])
155
- llm._preprocess_cmpl_one = Mock(return_value={"prompt_token_ids": [1]})
167
+ _params_to_seq = Mock(return_value=[sampling_params])
168
+ monkeypatch.setattr(llm, "_params_to_seq", _params_to_seq)
169
+ _lora_request_to_seq = Mock(return_value=[None])
170
+ monkeypatch.setattr(llm, "_lora_request_to_seq", _lora_request_to_seq)
171
+ _priority_to_seq = Mock(return_value=[0])
172
+ monkeypatch.setattr(llm, "_priority_to_seq", _priority_to_seq)
173
+ _preprocess_cmpl_one = Mock(return_value={"prompt_token_ids": [1]})
174
+ monkeypatch.setattr(llm, "_preprocess_cmpl_one", _preprocess_cmpl_one)
175
176
captured_prompts = []
177
@@ -160,7 +179,8 @@ def test_add_completion_requests_forwards_mm_processor_kwargs() -> None:
179
captured_prompts.extend(prompts)
180
return ["req-0"]
181
163
- llm._render_and_add_requests = Mock(side_effect=fake_render_and_add_requests)
182
+ _render_and_add_requests = Mock(side_effect=fake_render_and_add_requests)
183
+ monkeypatch.setattr(llm, "_render_and_add_requests", _render_and_add_requests)
184
185
request_ids = llm._add_completion_requests(
186
prompts=["prompt"],
@@ -170,7 +190,7 @@ def test_add_completion_requests_forwards_mm_processor_kwargs() -> None:
190
)
191
192
assert request_ids == ["req-0"]
173
- llm._preprocess_cmpl_one.assert_called_once_with(
193
+ _preprocess_cmpl_one.assert_called_once_with(
194
"prompt",
195
None,
196
mm_processor_kwargs=mm_processor_kwargs,
@@ -183,7 +203,7 @@ def test_preprocess_cmpl_applies_mm_processor_kwargs_to_renderer(
203
) -> None:
204
llm = _make_mock_llm()
205
mm_processor_kwargs = {"num_crops": 8}
186
- prompt = {"prompt": "<image>", "multi_modal_data": {"image": object()}}
206
+ prompt: TextPrompt = {"prompt": "<image>", "multi_modal_data": {"image": object()}}
207
208
renderer = Mock()
209
renderer.default_cmpl_tok_params = Mock()
@@ -213,7 +233,7 @@ def test_preprocess_cmpl_keeps_prompt_mm_processor_kwargs_when_no_override(
233
monkeypatch: pytest.MonkeyPatch,
234
) -> None:
235
llm = _make_mock_llm()
216
- prompt = {
236
+ prompt: TextPrompt = {
237
"prompt": "<image>",
238
"multi_modal_data": {"image": object()},
239
"mm_processor_kwargs": {"num_crops": 2},
@@ -243,7 +263,9 @@ def test_preprocess_cmpl_keeps_prompt_mm_processor_kwargs_when_no_override(
263
def test_preprocess_chat_applies_mm_processor_kwargs_to_renderer() -> None:
264
llm = _make_mock_llm()
265
mm_processor_kwargs = {"num_crops": 8}
246
- messages = [[{"role": "user", "content": "Describe this image."}]]
266
+ messages: list[list[ChatCompletionMessageParam]] = [
267
+ [{"role": "user", "content": "Describe this image."}]
268
+ ]
269
270
renderer = Mock()
271
renderer.tokenizer = object()
@@ -269,7 +291,9 @@ def test_preprocess_chat_applies_mm_processor_kwargs_to_renderer() -> None:
291
292
def test_preprocess_chat_omits_mm_processor_kwargs_when_no_override() -> None:
293
llm = _make_mock_llm()
272
- messages = [[{"role": "user", "content": "Describe this image."}]]
294
+ messages: list[list[ChatCompletionMessageParam]] = [
295
+ [{"role": "user", "content": "Describe this image."}]
296
+ ]
297
298
renderer = Mock()
299
renderer.tokenizer = object()
@@ -291,7 +315,9 @@ def test_preprocess_chat_omits_mm_processor_kwargs_when_no_override() -> None:
315
def test_preprocess_chat_defaults_add_special_tokens_to_false() -> None:
316
# Matches `ChatCompletionRequest.add_special_tokens` on the server.
317
llm = _make_mock_llm()
294
- messages = [[{"role": "user", "content": "hi"}]]
318
+ messages: list[list[ChatCompletionMessageParam]] = [
319
+ [{"role": "user", "content": "hi"}]
320
+ ]
321
322
renderer = Mock()
323
renderer.tokenizer = object()
@@ -309,7 +335,9 @@ def test_preprocess_chat_defaults_add_special_tokens_to_false() -> None:
335
336
def test_preprocess_chat_tokenization_kwargs_override_add_special_tokens() -> None:
337
llm = _make_mock_llm()
312
- messages = [[{"role": "user", "content": "hi"}]]
338
+ messages: list[list[ChatCompletionMessageParam]] = [
339
+ [{"role": "user", "content": "hi"}]
340
+ ]
341
342
renderer = Mock()
343
renderer.tokenizer = object()
tests/entrypoints/multimodal/openai/chat_completion/test_audio.py
+10
-8
@@ -47,18 +47,20 @@ async def client(server):
47
48
@pytest.fixture(scope="session")
49
def base64_encoded_audio() -> dict[str, str]:
50
- return {
51
- audio_url: encode_audio_base64(*fetch_audio(audio_url))
52
- for audio_url in TEST_AUDIO_URLS
53
- }
50
+ encoded = {}
51
+ for audio_url in TEST_AUDIO_URLS:
52
+ audio, sample_rate = fetch_audio(audio_url)
53
+ encoded[audio_url] = encode_audio_base64(audio, int(sample_rate))
54
+ return encoded
55
56
57
@pytest.fixture(scope="session")
58
def url_encoded_audio() -> dict[str, str]:
58
- return {
59
- audio_url: encode_audio_url(*fetch_audio(audio_url))
60
- for audio_url in TEST_AUDIO_URLS
61
- }
59
+ encoded = {}
60
+ for audio_url in TEST_AUDIO_URLS:
61
+ audio, sample_rate = fetch_audio(audio_url)
62
+ encoded[audio_url] = encode_audio_url(audio, int(sample_rate))
63
+ return encoded
64
65
66
def dummy_messages_from_audio_url(
tests/entrypoints/pooling/classify/test_offline.py
+3
-1
@@ -7,6 +7,7 @@ import torch
7
8
from tests.models.utils import softmax
9
from vllm import LLM, ClassificationRequestOutput, PoolingParams
10
+from vllm.inputs import PromptType
11
from vllm.tasks import PoolingTask
12
13
MODEL_NAME = "jason9693/Qwen2.5-1.5B-apeach"
@@ -53,7 +54,8 @@ def test_token_ids_prompts(llm: LLM):
54
55
@pytest.mark.skip_global_cleanup
56
def test_list_prompts(llm: LLM):
56
- outputs = llm.classify([prompt, prompt_token_ids], use_tqdm=False)
57
+ prompts: list[PromptType] = [prompt, prompt_token_ids]
58
+ outputs = llm.classify(prompts, use_tqdm=False)
59
assert len(outputs) == 2
60
for i in range(len(outputs)):
61
assert isinstance(outputs[i], ClassificationRequestOutput)
tests/entrypoints/pooling/embed/test_io_processor.py
+10
-9
@@ -21,6 +21,7 @@ from vllm.entrypoints.pooling.typing import (
21
PoolingEngineInput,
22
PoolingServeContext,
23
)
24
+from vllm.inputs import tokens_input
25
from vllm.outputs import PoolingOutput, PoolingRequestOutput
26
27
@@ -28,7 +29,7 @@ class TestEmbeddingRequestParsing:
29
"""Unit tests for OpenAI embedding request parsing."""
30
31
def test_input_messages_parses_as_chat_request(self):
31
- request = TypeAdapter(EmbeddingRequest).validate_python(
32
+ request: EmbeddingRequest = TypeAdapter(EmbeddingRequest).validate_python(
33
{
34
"model": "test",
35
"input": [{"role": "user", "content": "hello"}],
@@ -42,7 +43,7 @@ class TestEmbeddingRequestParsing:
43
assert request.chat_template_kwargs == {"instruction": "Represent the query: "}
44
45
def test_batched_input_messages_parses_as_batch_chat_input_request(self):
45
- request = TypeAdapter(EmbeddingRequest).validate_python(
46
+ request: EmbeddingRequest = TypeAdapter(EmbeddingRequest).validate_python(
47
{
48
"model": "test",
49
"input": [
@@ -65,7 +66,7 @@ class TestEmbeddingRequestParsing:
66
assert request.chat_template_kwargs == {"instruction": "Represent the query: "}
67
68
def test_token_ids_still_parse_as_completion_request(self):
68
- request = TypeAdapter(EmbeddingRequest).validate_python(
69
+ request: EmbeddingRequest = TypeAdapter(EmbeddingRequest).validate_python(
70
{
71
"model": "test",
72
"input": [[1, 2, 3], [4, 5]],
@@ -76,7 +77,7 @@ class TestEmbeddingRequestParsing:
77
assert request.input == [[1, 2, 3], [4, 5]]
78
79
def test_messages_still_parses_as_chat_request(self):
79
- request = TypeAdapter(EmbeddingRequest).validate_python(
80
+ request: EmbeddingRequest = TypeAdapter(EmbeddingRequest).validate_python(
81
{
82
"model": "test",
83
"messages": [{"role": "user", "content": "hello"}],
@@ -89,7 +90,7 @@ class TestEmbeddingRequestParsing:
90
assert request.chat_template_kwargs == {"instruction": "Represent the query: "}
91
92
def test_batched_messages_parses_as_batch_chat_request(self):
92
- request = TypeAdapter(EmbeddingRequest).validate_python(
93
+ request: EmbeddingRequest = TypeAdapter(EmbeddingRequest).validate_python(
94
{
95
"model": "test",
96
"messages": [
@@ -410,13 +411,13 @@ class TestChunkedEmbeddingProcessing:
411
@classmethod
412
def _make_handler(cls):
413
handler = object.__new__(EmbedIOProcessor)
413
- handler.model_config = cls._FakeModelConfig()
414
+ handler.model_config = cls._FakeModelConfig() # type: ignore[assignment]
415
handler.enable_chunked_processing = True
416
return handler
417
418
@staticmethod
419
def _make_context() -> PoolingServeContext[EmbeddingCompletionRequest]:
419
- request = TypeAdapter(EmbeddingRequest).validate_python(
420
+ request: EmbeddingRequest = TypeAdapter(EmbeddingRequest).validate_python(
421
{
422
"model": "test",
423
"input": [[0, 1, 2, 3, 4], [10, 11]],
@@ -431,13 +432,13 @@ class TestChunkedEmbeddingProcessing:
432
request_id="embd-client-prompt-999-chunk-888",
433
engine_inputs=[
434
PoolingEngineInput(
434
- prompts={"prompt_token_ids": [0, 1, 2, 3, 4]},
435
+ prompts=tokens_input(prompt_token_ids=[0, 1, 2, 3, 4]),
436
params=pooling_params,
437
lora_requests=None,
438
priorities=0,
439
),
440
PoolingEngineInput(
440
- prompts={"prompt_token_ids": [10, 11]},
441
+ prompts=tokens_input(prompt_token_ids=[10, 11]),
442
params=pooling_params,
443
lora_requests=None,
444
priorities=0,
tests/entrypoints/pooling/embed/test_offline.py
+3
-1
@@ -7,6 +7,7 @@ import torch
7
import torch.nn.functional as F
8
9
from vllm import LLM, EmbeddingRequestOutput, PoolingParams
10
+from vllm.inputs import PromptType
11
from vllm.tasks import PoolingTask
12
13
MODEL_NAME = "intfloat/multilingual-e5-small"
@@ -54,7 +55,8 @@ def test_token_ids_prompts(llm: LLM):
55
56
@pytest.mark.skip_global_cleanup
57
def test_list_prompts(llm: LLM):
57
- outputs = llm.embed([prompt, prompt_token_ids], use_tqdm=False)
58
+ prompts: list[PromptType] = [prompt, prompt_token_ids]
59
+ outputs = llm.embed(prompts, use_tqdm=False)
60
assert len(outputs) == 2
61
for i in range(len(outputs)):
62
assert isinstance(outputs[i], EmbeddingRequestOutput)
tests/entrypoints/pooling/embed/test_online.py
+13
-9
@@ -12,9 +12,13 @@ import requests
12
import torch
13
import torch.nn.functional as F
14
15
-from tests.models.language.pooling.embed_utils import run_embedding_correctness_test
15
+from tests.models.language.pooling.embed_utils import (
16
+ float_embeddings,
17
+ run_embedding_correctness_test,
18
+)
19
from tests.models.utils import check_embeddings_close
20
from tests.utils import RemoteOpenAIServer
21
+from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
22
from vllm.entrypoints.pooling.embed.protocol import EmbeddingResponse
23
from vllm.entrypoints.pooling.pooling.protocol import PoolingResponse
24
from vllm.entrypoints.pooling.utils import (
@@ -145,7 +149,7 @@ async def test_completion_request(
149
assert embeddings.usage.prompt_tokens == len(input_tokens)
150
assert embeddings.usage.total_tokens == len(input_tokens)
151
148
- vllm_outputs = [d.embedding for d in embeddings.data]
152
+ vllm_outputs = float_embeddings(embeddings)
153
run_embedding_correctness_test(hf_model, [input_text], vllm_outputs)
154
155
# test input: list[int]
@@ -165,7 +169,7 @@ async def test_completion_request(
169
assert embeddings.usage.prompt_tokens == len(input_tokens)
170
assert embeddings.usage.total_tokens == len(input_tokens)
171
168
- vllm_outputs = [d.embedding for d in embeddings.data]
172
+ vllm_outputs = float_embeddings(embeddings)
173
run_embedding_correctness_test(hf_model, [input_text], vllm_outputs)
174
175
@@ -194,7 +198,7 @@ async def test_completion_request_batched(
198
assert embeddings.usage.prompt_tokens == len(input_tokens) * N
199
assert embeddings.usage.total_tokens == len(input_tokens) * N
200
197
- vllm_outputs = [d.embedding for d in embeddings.data]
201
+ vllm_outputs = float_embeddings(embeddings)
202
run_embedding_correctness_test(hf_model, input_texts, vllm_outputs)
203
204
# test list[list[int]]
@@ -214,7 +218,7 @@ async def test_completion_request_batched(
218
assert embeddings.usage.prompt_tokens == len(input_tokens) * N
219
assert embeddings.usage.total_tokens == len(input_tokens) * N
220
217
- vllm_outputs = [d.embedding for d in embeddings.data]
221
+ vllm_outputs = float_embeddings(embeddings)
222
run_embedding_correctness_test(hf_model, input_texts, vllm_outputs)
223
224
@@ -331,7 +335,7 @@ async def test_padding(client: openai.AsyncOpenAI, model_name: str):
335
async def test_chat_request(
336
server: RemoteOpenAIServer, client: openai.AsyncOpenAI, model_name: str
337
):
334
- messages = [
338
+ messages: list[ChatCompletionMessageParam] = [
339
{
340
"role": "user",
341
"content": "The cat sat on the mat.",
@@ -382,8 +386,8 @@ async def test_chat_request(
386
assert chat_embeddings.created <= completion_embeddings.created
387
# Use tolerance-based comparison for embeddings
388
check_embeddings_close(
385
- embeddings_0_lst=[d.embedding for d in chat_embeddings.data],
386
- embeddings_1_lst=[d.embedding for d in completion_embeddings.data],
389
+ embeddings_0_lst=float_embeddings(chat_embeddings),
390
+ embeddings_1_lst=float_embeddings(completion_embeddings),
391
name_0="chat",
392
name_1="completion",
393
)
@@ -488,7 +492,7 @@ async def test_invocations_completion_request(
492
493
@pytest.mark.asyncio
494
async def test_invocations_chat_request(server: RemoteOpenAIServer):
491
- messages = [
495
+ messages: list[ChatCompletionMessageParam] = [
496
{
497
"role": "user",
498
"content": "The cat sat on the mat.",
tests/entrypoints/pooling/embed/test_online_dimensions.py
+5
-2
@@ -6,7 +6,10 @@ import openai
6
import pytest
7
8
from tests.conftest import HfRunner
9
-from tests.models.language.pooling.embed_utils import run_embedding_correctness_test
9
+from tests.models.language.pooling.embed_utils import (
10
+ float_embeddings,
11
+ run_embedding_correctness_test,
12
+)
13
from tests.models.utils import EmbedModelInfo
14
from tests.utils import ROCM_EXTRA_ARGS, RemoteOpenAIServer
15
from vllm.entrypoints.pooling.embed.protocol import EmbeddingResponse
@@ -95,7 +98,7 @@ async def test_matryoshka(
98
if dimensions is not None:
99
assert len(embeddings.data[0].embedding) == dimensions
100
98
- vllm_outputs = [d.embedding for d in embeddings.data]
101
+ vllm_outputs = float_embeddings(embeddings)
102
run_embedding_correctness_test(hf_model, prompts, vllm_outputs, dimensions)
103
104
if model_info.is_matryoshka:
tests/entrypoints/pooling/embed/test_online_long_text.py
+4
@@ -274,6 +274,7 @@ async def test_long_text_embedding_1500_chars(
274
275
# Verify embedding vector validity
276
embedding_vector = embeddings.data[0].embedding
277
+ assert isinstance(embedding_vector, list)
278
assert all(isinstance(x, float) for x in embedding_vector), (
279
"Embedding vector should contain floats"
280
)
@@ -322,6 +323,7 @@ async def test_long_text_embedding_2500_chars(
323
324
# Verify embedding vector validity
325
embedding_vector = embeddings.data[0].embedding
326
+ assert isinstance(embedding_vector, list)
327
assert all(isinstance(x, float) for x in embedding_vector), (
328
"Embedding vector should contain floats"
329
)
@@ -409,6 +411,7 @@ async def test_chunked_vs_normal_consistency(
411
412
# 验证embedding向量的有效性
413
embedding_vector = embeddings.data[0].embedding
414
+ assert isinstance(embedding_vector, list)
415
assert all(isinstance(x, float) for x in embedding_vector)
416
assert not all(x == 0 for x in embedding_vector)
417
@@ -438,6 +441,7 @@ async def test_chunked_processing_response_format(
441
442
# Verify embedding vector properties
443
embedding_vector = embeddings.data[0].embedding
444
+ assert isinstance(embedding_vector, list)
445
import math
446
447
vector_norm = math.sqrt(sum(x * x for x in embedding_vector))
tests/entrypoints/pooling/reward/test_token_reward_online.py
+3
-2
@@ -11,6 +11,7 @@ import torch
11
12
from tests.models.utils import check_embeddings_close
13
from tests.utils import RemoteOpenAIServer
14
+from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
15
from vllm.entrypoints.pooling.pooling.protocol import PoolingResponse
16
from vllm.entrypoints.pooling.utils import (
17
MetadataItem,
@@ -137,7 +138,7 @@ def test_completion_request_batched(server: RemoteOpenAIServer, model_name: str)
138
@pytest.mark.asyncio
139
@pytest.mark.parametrize("model_name", [MODEL_NAME])
140
async def test_chat_request(server: RemoteOpenAIServer, model_name: str):
140
- messages = [
141
+ messages: list[ChatCompletionMessageParam] = [
142
{
143
"role": "user",
144
"content": "The cat sat on the mat.",
@@ -524,7 +525,7 @@ async def test_invocations_chat_request(server: RemoteOpenAIServer):
525
526
@pytest.mark.asyncio
527
async def test_invocations_conversation_chat_request(server: RemoteOpenAIServer):
527
- messages = [
528
+ messages: list[ChatCompletionMessageParam] = [
529
{
530
"role": "user",
531
"content": "The cat sat on the mat.",
tests/entrypoints/pooling/scoring/test_io_processor_unit.py
+7
-3
@@ -47,7 +47,7 @@ def llm_reranker_processor() -> CrossEncoderIOProcessor:
47
)
48
49
processor = CrossEncoderIOProcessor.__new__(CrossEncoderIOProcessor)
50
- processor.model_config = SimpleNamespace(enable_prompt_embeds=False)
50
+ processor.model_config = SimpleNamespace(enable_prompt_embeds=False) # type: ignore[assignment]
51
processor.tokenizer = TokenizersBackend(
52
tokenizer_object=backend,
53
unk_token="[UNK]",
@@ -95,12 +95,16 @@ def test_token_type_ids_stay_aligned_with_a_truncated_padded_prompt():
95
truncation_side="left",
96
)
97
98
- prompt = tok_params.apply_post_tokenization(tokenizer, prompt)
98
+ processed_prompt = tok_params.apply_post_tokenization(
99
+ tokenizer, # type: ignore[arg-type]
100
+ prompt,
101
+ )
102
+ assert "prompt_token_ids" in processed_prompt
103
token_type_ids = _apply_post_tokenization_to_token_type_ids(
104
tokenizer, tok_params, token_type_ids
105
)
106
103
- prompt_token_ids = prompt["prompt_token_ids"]
107
+ prompt_token_ids = processed_prompt["prompt_token_ids"]
108
assert len(token_type_ids) == len(prompt_token_ids)
109
110
# Keeping the last 40 tokens drops the first 10 query tokens, so 10 query
tests/entrypoints/pooling/scoring/test_jina_ranking_io_processor_unit.py
+15
-7
@@ -30,12 +30,18 @@ def test_online_forwards_truncate_prompt_tokens_to_proxy(monkeypatch):
30
silently disables truncate_prompt_tokens for Jina rerank/score.
31
"""
32
proc = JinaRankingIOProcessor.__new__(JinaRankingIOProcessor)
33
- proc.valid_inputs_online = MagicMock(
34
- return_value=ScoringData(data_1=["query"], data_2=["doc"])
33
+ monkeypatch.setattr(
34
+ proc,
35
+ "valid_inputs_online",
36
+ MagicMock(return_value=ScoringData(data_1=["query"], data_2=["doc"])),
37
+ )
38
+ monkeypatch.setattr(proc, "_get_token_limits", MagicMock(return_value=(0, 0)))
39
+ monkeypatch.setattr(
40
+ proc, "ensure_str", MagicMock(side_effect=lambda data: list(data))
41
+ )
42
+ monkeypatch.setattr(
43
+ proc, "format_docs_prompts_func", MagicMock(return_value="formatted prompt")
44
)
36
- proc._get_token_limits = MagicMock(return_value=(0, 0))
37
- proc.ensure_str = MagicMock(side_effect=lambda data: list(data))
38
- proc.format_docs_prompts_func = MagicMock(return_value="formatted prompt")
45
46
captured: dict[str, object] = {}
47
@@ -71,8 +77,10 @@ def offline_processor_and_context():
77
backend.pre_tokenizer = pre_tokenizers.WhitespaceSplit()
78
proc = JinaRankingIOProcessor.__new__(JinaRankingIOProcessor)
79
proc.tokenizer = TokenizersBackend(tokenizer_object=backend)
74
- proc.model_config = SimpleNamespace(max_model_len=1024, is_encoder_decoder=False)
75
- proc.renderer = SimpleNamespace(
80
+ proc.model_config = SimpleNamespace( # type: ignore[assignment]
81
+ max_model_len=1024, is_encoder_decoder=False
82
+ )
83
+ proc.renderer = SimpleNamespace( # type: ignore[assignment]
84
default_cmpl_tok_params=TokenizeParams(max_total_tokens=1024)
85
)
86
ctx = OfflineScoringInputsContext(
tests/entrypoints/pooling/scoring/test_late_interaction_serving.py
+20
-9
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, Mock
6
import pytest
7
8
from vllm.entrypoints.pooling.scoring.serving import ServingScores
9
-from vllm.entrypoints.pooling.typing import PoolingServeContext
9
+from vllm.entrypoints.pooling.typing import PoolingEngineInput, PoolingServeContext
10
from vllm.inputs import tokens_input
11
from vllm.pooling_params import PoolingParams
12
@@ -21,8 +21,18 @@ def _make_context() -> PoolingServeContext:
21
priorities=None,
22
prompt_extras=None,
23
engine_inputs=[
24
- tokens_input(prompt_token_ids=[1]),
25
- tokens_input(prompt_token_ids=[2]),
24
+ PoolingEngineInput(
25
+ prompts=tokens_input(prompt_token_ids=[1]),
26
+ params=PoolingParams(task="token_embed"),
27
+ lora_requests=None,
28
+ priorities=0,
29
+ ),
30
+ PoolingEngineInput(
31
+ prompts=tokens_input(prompt_token_ids=[2]),
32
+ params=PoolingParams(task="token_embed"),
33
+ lora_requests=None,
34
+ priorities=0,
35
+ ),
36
],
37
n_queries=1,
38
)
@@ -39,10 +49,13 @@ def _query_key(context: PoolingServeContext) -> str:
49
50
51
@pytest.mark.asyncio
42
-async def test_colliding_request_ids_use_distinct_query_cache_keys():
52
+async def test_colliding_request_ids_use_distinct_query_cache_keys(
53
+ monkeypatch: pytest.MonkeyPatch,
54
+):
55
serving = object.__new__(ServingScores)
44
- serving._prepare_generators = AsyncMock()
45
- serving._collect_batch = AsyncMock()
56
+ prepare_generators = AsyncMock()
57
+ monkeypatch.setattr(serving, "_prepare_generators", prepare_generators)
58
+ monkeypatch.setattr(serving, "_collect_batch", AsyncMock())
59
60
first = _make_context()
61
second = _make_context()
@@ -50,9 +63,7 @@ async def test_colliding_request_ids_use_distinct_query_cache_keys():
63
await serving._flash_late_interaction_encode_queries(context)
64
await serving._flash_late_interaction_encode_docs(context)
65
53
- prepared_contexts = [
54
- call.args[0] for call in serving._prepare_generators.await_args_list
55
- ]
66
+ prepared_contexts = [call.args[0] for call in prepare_generators.await_args_list]
67
first_query_key = _query_key(prepared_contexts[0])
68
first_doc_key = _query_key(prepared_contexts[1])
69
second_query_key = _query_key(prepared_contexts[2])
tests/entrypoints/pooling/test_io_processor.py
+2
-2
@@ -53,8 +53,8 @@ def test_rejects_mismatched_lora_requests(processor: PoolingIOProcessor):
53
54
55
def test_rejects_conflicting_pooling_task(processor: PoolingIOProcessor):
56
- processor.model_config = SimpleNamespace(is_encoder_decoder=False)
57
- processor.renderer = SimpleNamespace(
56
+ processor.model_config = SimpleNamespace(is_encoder_decoder=False) # type: ignore[assignment]
57
+ processor.renderer = SimpleNamespace( # type: ignore[assignment] # Tokenization defaults only.
58
default_cmpl_tok_params=TokenizeParams(max_total_tokens=None)
59
)
60
ctx = OfflineEncodeInputsContext(
tests/entrypoints/pooling/token_classify/test_online.py
+1
@@ -45,6 +45,7 @@ async def test_pooling_token_classify(server: RemoteOpenAIServer, model_name: st
45
poolings = PoolingResponse.model_validate(response.json())
46
assert len(poolings.data) == 1
47
assert len(poolings.data[0].data) == 8
48
+ assert isinstance(poolings.data[0].data[0], list)
49
assert len(poolings.data[0].data[0]) == 2
50
51
tests/entrypoints/pooling/token_embed/test_online.py
+1
@@ -71,6 +71,7 @@ async def test_pooling_token_embed(server: RemoteOpenAIServer, model_name: str):
71
assert poolings.id == f"pooling-{request_id}"
72
assert len(poolings.data) == 1
73
assert len(poolings.data[0].data) == len(input_tokens)
74
+ assert isinstance(poolings.data[0].data[0], list)
75
assert len(poolings.data[0].data[0]) == 384
76
77
tests/kernels/attention/test_attention_selector.py
+12
-8
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
from contextlib import contextmanager
5
+from typing import Literal
6
from unittest.mock import MagicMock, patch
7
8
import pytest
@@ -14,6 +15,7 @@ from vllm.config import (
15
VllmConfig,
16
set_current_vllm_config,
17
)
18
+from vllm.config.cache import CacheDType
19
from vllm.platforms import current_platform
20
from vllm.platforms.cpu import CpuPlatform
21
from vllm.platforms.interface import DeviceCapability
@@ -21,12 +23,12 @@ from vllm.platforms.interface import DeviceCapability
23
if current_platform.is_cuda():
24
from vllm.platforms.cuda import CudaPlatform
25
else:
24
- CudaPlatform = None
26
+ CudaPlatform = None # type: ignore[assignment] # Unavailable platform import.
27
28
if current_platform.is_rocm():
29
from vllm.platforms.rocm import RocmPlatform
30
else:
29
- RocmPlatform = None
31
+ RocmPlatform = None # type: ignore[misc, assignment] # Unavailable platform import.
32
33
from vllm.v1.attention.backend import AttentionType
34
from vllm.v1.attention.backends.registry import AttentionBackendEnum
@@ -354,14 +356,14 @@ def test_invalid_backend():
356
def test_auto_backend_string(auto_value: str):
357
"""Test that 'auto' string value triggers automatic backend selection."""
358
# Using "auto" should result in backend=None (automatic selection)
357
- attention_config = AttentionConfig(backend=auto_value)
359
+ attention_config = AttentionConfig(backend=auto_value) # type: ignore[arg-type]
360
assert attention_config.backend is None
361
362
363
def test_auto_backend_selection_behavior():
364
"""Test that 'auto' backend behaves same as None (automatic selection)."""
365
# Create config with explicit "auto"
364
- auto_config = AttentionConfig(backend="auto")
366
+ auto_config = AttentionConfig(backend="auto") # type: ignore[arg-type]
367
368
# Create config with None (default)
369
none_config = AttentionConfig(backend=None)
@@ -406,7 +408,7 @@ def test_auto_backend_selection_behavior():
408
reason="Attention backend FA3 is not supported on ROCm. This test can't succeed.",
409
)
410
def test_per_head_quant_scales_backend_selection(
409
- backend_name: str, flash_attn_version: int | None, should_succeed: bool
411
+ backend_name: str, flash_attn_version: Literal[2, 3, 4] | None, should_succeed: bool
412
):
413
"""Test backend selection when use_per_head_quant_scales=True."""
414
# Clear cache to ensure fresh backend selection
@@ -559,7 +561,7 @@ def test_non_causal_autoselect_backend():
561
"int8_per_token_head",
562
],
563
)
562
-def test_flash_attn_rejects_unhandled_kv_cache_dtypes(kv_cache_dtype: str):
564
+def test_flash_attn_rejects_unhandled_kv_cache_dtypes(kv_cache_dtype: CacheDType):
565
"""FlashAttentionBackend must not claim support for kv_cache dtypes
566
that it cannot handle."""
567
from vllm.v1.attention.backends.flash_attn import FlashAttentionBackend
@@ -569,7 +571,7 @@ def test_flash_attn_rejects_unhandled_kv_cache_dtypes(kv_cache_dtype: str):
571
572
@pytest.mark.parametrize("kv_cache_dtype", ["fp8", "fp8_e4m3"])
573
def test_flash_attn_accepts_handled_fp8_variants(
572
- kv_cache_dtype: str, monkeypatch: pytest.MonkeyPatch
574
+ kv_cache_dtype: CacheDType, monkeypatch: pytest.MonkeyPatch
575
):
576
"""FlashAttentionBackend must accept the two fp8 dtypes it can actually
577
handle: 'fp8' (alias for fp8_e4m3fn) and 'fp8_e4m3'."""
@@ -876,7 +878,9 @@ def test_rswa_selection_does_not_reuse_causal_result(blackwell_selection):
878
config = EngineArgs(
879
model="google/gemma-4-31B-it",
880
dtype="bfloat16",
879
- attention_config={"backend": "TRITON_FLASHINFER"},
881
+ attention_config=AttentionConfig(
882
+ backend=AttentionBackendEnum.TRITON_FLASHINFER
883
+ ),
884
).create_engine_config()
885
with set_current_vllm_config(config):
886
assert (
tests/kernels/attention/test_cascade_flash_attn.py
+1
-1
@@ -151,7 +151,7 @@ def test_cascade(
151
max_seqlen_k=max_kv_len,
152
softmax_scale=scale,
153
causal=True,
154
- window_size=window_size,
154
+ window_size=list(window_size),
155
block_table=block_tables,
156
softcap=soft_cap if soft_cap is not None else 0,
157
)
tests/kernels/attention/test_cpu_attn.py
+1
-1
@@ -836,7 +836,7 @@ def varlen_encoder_zentorch_sdpa(
836
key,
837
value,
838
output,
839
- SimpleNamespace(query_start_loc=query_start_loc, causal=False),
839
+ SimpleNamespace(query_start_loc=query_start_loc, causal=False), # type: ignore[arg-type]
840
scale,
841
sliding_window if sliding_window is not None else -1,
842
alibi_slopes,
tests/kernels/attention/test_flash_attn.py
+2
-2
@@ -187,7 +187,7 @@ def test_varlen_with_paged_kv(
187
max_seqlen_k=max_kv_len,
188
softmax_scale=scale,
189
causal=True,
190
- window_size=window_size,
190
+ window_size=list(window_size),
191
block_table=block_tables,
192
softcap=soft_cap if soft_cap is not None else 0,
193
fa_version=fa_version,
@@ -274,7 +274,7 @@ def test_fa4_hd256_paged_call_shape(
274
max_seqlen_k=num_pages * block_size,
275
softmax_scale=scale,
276
causal=True,
277
- window_size=window_size,
277
+ window_size=list(window_size),
278
block_table=block_tables[:, :num_pages],
279
num_splits=1,
280
fa_version=4,
tests/kernels/attention/test_kimi_k3_mla_key_concat_kv_cache.py
+3
-6
@@ -197,11 +197,6 @@ def test_decode_concat_ignores_negative_slots_for_cache(cache_format: str) -> No
197
mixed_cache = initial_cache.clone()
198
reference_cache = _reference_cache(cache_format, inputs, initial_cache, slots)
199
scale_inv = torch.ones(1, device="cuda", dtype=torch.float32)
200
- kwargs = {
201
- "ds_mla": cache_format == "fp8_ds_mla",
202
- "q_scale_inv": scale_inv if cache_format == "fp8" else None,
203
- "cache_scale_inv": scale_inv if cache_format == "fp8" else None,
204
- }
200
201
output = fused_mla_decode_q_concat_kv_cache_insert(
202
inputs["ql_nope"],
@@ -210,7 +205,9 @@ def test_decode_concat_ignores_negative_slots_for_cache(cache_format: str) -> No
205
inputs["k_pe"],
206
mixed_cache,
207
slots,
213
- **kwargs,
208
+ ds_mla=cache_format == "fp8_ds_mla",
209
+ q_scale_inv=scale_inv if cache_format == "fp8" else None,
210
+ cache_scale_inv=scale_inv if cache_format == "fp8" else None,
211
)
212
213
expected = _latent_query(inputs["ql_nope"], inputs["q_pe"])
tests/kernels/attention/test_mha_attn.py
+4
-4
@@ -277,7 +277,7 @@ def test_mha_attn_varlen_forward_flashinfer(
277
# Override vllm config so get_vit_attn_backend returns FLASHINFER (simulates
278
# --mm-encoder-attn-backend=FLASHINFER).
279
vllm_config = get_current_vllm_config()
280
- old_model_config = getattr(vllm_config, "model_config", None)
280
+ old_model_config = vllm_config.model_config
281
minimal_model_config = type(
282
"MinimalModelConfig",
283
(),
@@ -376,7 +376,7 @@ def test_mha_attn_varlen_forward_aiter_fp8(
376
torch.set_default_dtype(dtype)
377
378
vllm_config = get_current_vllm_config()
379
- old_model_config = getattr(vllm_config, "model_config", None)
379
+ old_model_config = vllm_config.model_config
380
minimal_model_config = type(
381
"MinimalModelConfig",
382
(),
@@ -442,7 +442,7 @@ def test_mha_attn_varlen_forward_aiter_fp8(
442
)
443
def test_mha_attn_aiter_fp8_rejects_unsupported_arch(default_vllm_config):
444
vllm_config = get_current_vllm_config()
445
- old_model_config = getattr(vllm_config, "model_config", None)
445
+ old_model_config = vllm_config.model_config
446
vllm_config.model_config = type(
447
"MinimalModelConfig",
448
(),
@@ -468,7 +468,7 @@ def test_mha_attn_aiter_fp8_rejects_unsupported_arch(default_vllm_config):
468
)
469
def test_mha_attn_fp8_rejects_wrong_backend(default_vllm_config):
470
vllm_config = get_current_vllm_config()
471
- old_model_config = getattr(vllm_config, "model_config", None)
471
+ old_model_config = vllm_config.model_config
472
vllm_config.model_config = type(
473
"MinimalModelConfig",
474
(),
tests/kernels/attention/test_minimax_m3.py
+17
-12
@@ -1689,8 +1689,10 @@ def test_aiter_sparse_pa_layout_contract(monkeypatch, layout):
1689
)
1690
1691
backend = sparse_attn_mod.MiniMaxM3SparseBackend
1692
- assert KVCacheLayout.LHBNC in backend.supported_kv_cache_layouts()
1693
- assert KVCacheLayout.LBHNC in backend.supported_kv_cache_layouts()
1692
+ layouts = backend.supported_kv_cache_layouts()
1693
+ assert layouts is not None
1694
+ assert KVCacheLayout.LHBNC in layouts
1695
+ assert KVCacheLayout.LBHNC in layouts
1696
1697
nb, h = 3, 1
1698
cache_dtype = current_platform.fp8_dtype()
@@ -1772,19 +1774,22 @@ def test_aiter_indexer_requires_the_aiter_attend(monkeypatch):
1774
import vllm.models.minimax_m3.amd.indexer_aiter as indexer_aiter_mod
1775
1776
monkeypatch.setattr(indexer_aiter_mod.current_platform, "is_rocm", lambda: True)
1775
- kwargs = dict(
1776
- topk_blocks=TOPK,
1777
- sparse_block_size=BLOCK_SIZE,
1778
- num_index_heads=1,
1779
- index_head_dim=HEAD_DIM,
1780
- indexer_kv_dtype="fp8_e4m3",
1781
- max_model_len=8192,
1782
- )
1777
+
1778
+ def unsupported_reason() -> str | None:
1779
+ return indexer_aiter_mod.aiter_indexer_unsupported_reason(
1780
+ topk_blocks=TOPK,
1781
+ sparse_block_size=BLOCK_SIZE,
1782
+ num_index_heads=1,
1783
+ index_head_dim=HEAD_DIM,
1784
+ # The implementation also accepts this legacy fp8 alias.
1785
+ indexer_kv_dtype="fp8_e4m3", # type: ignore[arg-type]
1786
+ max_model_len=8192,
1787
+ )
1788
1789
monkeypatch.setattr(
1790
indexer_aiter_mod, "_minimax_m3_aiter_sparse_pa_requested", lambda: False
1791
)
1787
- reason = indexer_aiter_mod.aiter_indexer_unsupported_reason(**kwargs)
1792
+ reason = unsupported_reason()
1793
assert reason is not None and "AITER sparse PA attend" in reason
1794
1795
# With the attend asked for, the gate moves on to the kernel-contract
@@ -1792,7 +1797,7 @@ def test_aiter_indexer_requires_the_aiter_attend(monkeypatch):
1797
monkeypatch.setattr(
1798
indexer_aiter_mod, "_minimax_m3_aiter_sparse_pa_requested", lambda: True
1799
)
1795
- reason = indexer_aiter_mod.aiter_indexer_unsupported_reason(**kwargs)
1800
+ reason = unsupported_reason()
1801
assert reason is None or "AITER sparse PA attend" not in reason
1802
1803
tests/kernels/attention/test_minimax_m3_msa_cutlass_sparse_decode.py
+4
-4
@@ -235,14 +235,14 @@ def test_msa_metadata_builder_prepares_cutlass_for_regular_decode(
235
builder.num_q_heads = 64
236
builder.num_kv_heads = 4
237
builder.topk_blocks = TOPK
238
- builder.kv_cache_spec = SimpleNamespace(num_kv_heads=4)
238
+ builder.kv_cache_spec = SimpleNamespace(num_kv_heads=4) # type: ignore[assignment]
239
builder.kv_cache_dtype = "fp8_e4m3"
240
builder.decode_backend = "cutlass"
241
- builder.msa_cutlass_plan_cache = object()
241
+ builder.msa_cutlass_plan_cache = object() # type: ignore[assignment] # Mocked plan-cache sentinel.
242
243
metadata = builder.build(
244
0,
245
- SimpleNamespace(
245
+ SimpleNamespace( # type: ignore[arg-type]
246
seq_lens_cpu_upper_bound=torch.full((batch,), 257, dtype=torch.int32)
247
),
248
)
@@ -377,7 +377,7 @@ def test_query_fp8_stays_valid_when_cutlass_plan_appears_on_replay(
377
with torch.cuda.stream(stream), override_forward_context(forward_context):
378
capture = BreakableCUDAGraphCapture()
379
with capture:
380
- query_fp8 = MiniMaxM3SparseAttention._allocate_query_fp8(attention, qkv)
380
+ query_fp8 = MiniMaxM3SparseAttention._allocate_query_fp8(attention, qkv) # type: ignore[arg-type]
381
if query_fp8 is not None:
382
query_fp8.zero_()
383
run_attention(query_fp8)
tests/kernels/attention/test_rocm_aiter_fa.py
+3
@@ -783,6 +783,9 @@ def test_aiter_fa_shared_kv_matches_reference(
783
32 * BLOCK_SIZE, num_kv_heads, head_size, dtype=torch.bfloat16
784
)
785
value = torch.randn_like(key)
786
+ from vllm.v1.attention.backends.rocm_aiter_fa import AiterFlashAttentionImpl
787
+
788
+ assert isinstance(target.impl, AiterFlashAttentionImpl)
789
target.impl.do_kv_cache_update(
790
target,
791
key,
tests/kernels/attention/test_rocm_aiter_mla_causal_verify_mask.py
+4
-2
@@ -82,6 +82,7 @@ def _run_verify_block():
82
)
83
from vllm.config import SpeculativeConfig
84
from vllm.config.vllm import set_current_vllm_config
85
+ from vllm.v1.attention.backends.mla.rocm_aiter_mla import AiterMLAImpl
86
from vllm.v1.attention.backends.registry import AttentionBackendEnum
87
from vllm.v1.kv_cache_interface import MLAAttentionSpec
88
from vllm.v1.worker.workspace import init_workspace_manager
@@ -106,7 +107,7 @@ def _run_verify_block():
107
vllm_config.speculative_config = SpeculativeConfig(
108
method="ngram", num_speculative_tokens=QLEN - 1
109
)
109
- vllm_config.model_config.get_num_attention_heads = types.MethodType(
110
+ vllm_config.model_config.get_num_attention_heads = types.MethodType( # type: ignore[method-assign]
111
lambda self, parallel_config, arch_config=None: NUM_QUERY_HEADS,
112
vllm_config.model_config,
113
)
@@ -202,7 +203,8 @@ def _run_verify_block():
203
lambda: spy,
204
),
205
):
205
- impl.forward_mqa((q_nope, q_pe), kv_cache, metadata, layer=None)
206
+ assert isinstance(impl, AiterMLAImpl)
207
+ impl.forward_mqa((q_nope, q_pe), kv_cache, metadata, layer=None) # type: ignore[arg-type] # Spy decode path does not read layer.
208
209
return metadata, captured
210
tests/kernels/attention/test_rocm_aiter_mla_fp8_prefill.py
+2
-2
@@ -121,7 +121,7 @@ def _build_prefill_metadata(
121
122
builder = object.__new__(AiterMLAMetadataBuilder)
123
builder.num_heads = num_heads
124
- builder.mla_dims = SimpleNamespace(v_head_dim=V_HEAD_DIM)
124
+ builder.mla_dims = SimpleNamespace(v_head_dim=V_HEAD_DIM) # type: ignore[assignment]
125
builder._init_fp8_prefill_ps_buffers(
126
max_num_reqs=len(seq_lens),
127
max_prefill_qlen=max_q,
@@ -136,7 +136,7 @@ def _build_prefill_metadata(
136
prefill = SimpleNamespace(query_start_loc=qo_indptr, max_query_len=max_q)
137
metadata = SimpleNamespace(prefill=prefill, num_decodes=0)
138
common = SimpleNamespace(query_start_loc_cpu=qo_indptr_cpu)
139
- builder._build_fp8_prefill_ps_metadata(metadata, common)
139
+ builder._build_fp8_prefill_ps_metadata(metadata, common) # type: ignore[arg-type]
140
return metadata, total_q, builder
141
142
tests/kernels/attention/test_rocm_aiter_mla_sink.py
+9
-6
@@ -167,7 +167,10 @@ def test_sparse_mla_sink_matches_ragged_reference(
167
impl.sinks = sinks
168
169
output, lse = impl._forward_mla(
170
- SimpleNamespace(_q_scale=q_scale, _k_scale=kv_scale), q, kv, metadata
170
+ SimpleNamespace(_q_scale=q_scale, _k_scale=kv_scale),
171
+ q,
172
+ kv,
173
+ metadata, # type: ignore[arg-type]
174
)
175
kv_flat = kv_ref[:, 0]
176
references = []
@@ -241,7 +244,7 @@ def test_sparse_mla_sink_rejects_unsupported_aiter_dtypes(
244
SimpleNamespace(_q_scale=None, _k_scale=None),
245
torch.empty(1, 16, Q_HEAD_DIM, dtype=q_dtype),
246
torch.empty(1, 1, Q_HEAD_DIM, dtype=kv_dtype),
244
- metadata,
247
+ metadata, # type: ignore[arg-type]
248
)
249
250
@@ -381,7 +384,7 @@ def test_sparse_mla_sink_matches_dense_attention_with_empty_rows_and_paged_cache
384
paged_kv_indices=torch.tensor(indices, dtype=torch.int32, device="cuda"),
385
paged_kv_indptr=lengths.cumsum(0).to(device="cuda", dtype=torch.int32),
386
)
384
- impl = ROCMAiterMLASparseImpl.__new__(ROCMAiterMLASparseImpl)
387
+ impl = ROCMAiterMLASparseImpl.__new__(ROCMAiterMLASparseImpl) # type: ignore[arg-type]
388
impl.num_heads = num_heads
389
impl.head_size = head_dim
390
impl.kv_lora_rank = value_dim
@@ -438,9 +441,9 @@ def test_sparse_mla_backend_resolves_only_contiguous_layer_layouts(monkeypatch,
441
]
442
if layout == "BLNHC":
443
with pytest.raises(ValueError, match="does not satisfy"):
441
- resolve_kv_cache_layout(config, supported)
444
+ resolve_kv_cache_layout(config, supported) # type: ignore[arg-type]
445
else:
443
- assert resolve_kv_cache_layout(config, supported).name == layout
446
+ assert resolve_kv_cache_layout(config, supported).name == layout # type: ignore[arg-type]
447
448
449
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
@@ -491,7 +494,7 @@ def test_sparse_mla_sink_forward_mqa_preserves_split_query(dtype):
494
actual, _ = impl.forward_mqa(
495
(q[..., :V_HEAD_DIM], q[..., V_HEAD_DIM:]),
496
kv,
494
- metadata,
497
+ metadata, # type: ignore[arg-type]
498
SimpleNamespace(_q_scale=None, _k_scale=None),
499
)
500
references = [
tests/kernels/attention/test_rocm_aiter_mla_sparse_metadata_sync.py
+2
-2
@@ -39,10 +39,10 @@ def _make_builder():
39
topk_tokens = 4
40
41
builder.device = torch.device("cpu")
42
- builder.kv_cache_spec = SimpleNamespace(block_size=1)
42
+ builder.kv_cache_spec = SimpleNamespace(block_size=1) # type: ignore[assignment]
43
builder.model_dtype = torch.bfloat16
44
builder.kv_cache_dtype = "fp8"
45
- builder.mla_dims = SimpleNamespace(kv_lora_rank=512, qk_rope_head_dim=64)
45
+ builder.mla_dims = SimpleNamespace(kv_lora_rank=512, qk_rope_head_dim=64) # type: ignore[assignment]
46
builder.topk_tokens = topk_tokens
47
builder.req_id_per_token_buffer = torch.zeros(
48
max_num_batched_tokens, dtype=torch.int32, device="cpu"
tests/kernels/attention/test_rocm_paged_mxfp4_indexer.py
+4
-2
@@ -227,7 +227,9 @@ def _decode_metadata(case, rows, ratio, query_lens):
227
num_decode_tokens=len(rows),
228
num_prefills=0,
229
num_prefill_tokens=0,
230
- decode=types.SimpleNamespace(block_table=block_table, seq_lens=lens[:, None]),
230
+ decode=types.SimpleNamespace( # type: ignore[arg-type]
231
+ block_table=block_table, seq_lens=lens[:, None]
232
+ ),
233
decode_row_lens=lens,
234
decode_block_ends=(lens + CAND_BLOCK - 1) // CAND_BLOCK,
235
decode_native=native,
@@ -278,7 +280,7 @@ def _prefill_metadata(
280
num_decode_tokens=0,
281
num_prefills=len(case.seq_lens),
282
num_prefill_tokens=len(rows),
281
- prefill=types.SimpleNamespace(chunks=chunks),
283
+ prefill=types.SimpleNamespace(chunks=chunks), # type: ignore[arg-type]
284
prefill_plans=plans,
285
gather_launches=plan_gather_launches(chunks, plans, gather_rows),
286
)
tests/kernels/attention/test_rocm_triton_attn_dsv4.py
+2
-2
@@ -955,7 +955,7 @@ def test_rocm_capture_metadata_sets_adaptive_marker(monkeypatch) -> None:
955
)
956
builder = object.__new__(rocm_mod.DeepseekV4ROCMAiterMLASparseMetadataBuilder)
957
958
- actual = builder.build_for_cudagraph_capture(SimpleNamespace())
958
+ actual = builder.build_for_cudagraph_capture(SimpleNamespace()) # type: ignore[arg-type] # Patched base method ignores metadata.
959
960
assert actual is metadata
961
assert actual.for_cudagraph_capture is _on_gfx950()
@@ -1563,7 +1563,7 @@ def test_dsv4_adaptive_mla_swa_metadata_graph_replay(monkeypatch) -> None:
1563
"sliding_window": window_size,
1564
},
1565
)
1566
- vllm_config.speculative_config = SimpleNamespace(
1566
+ vllm_config.speculative_config = SimpleNamespace( # type: ignore[assignment]
1567
num_speculative_tokens=upper_query_len - 1,
1568
parallel_drafting=False,
1569
enable_adaptive_verification=True,
tests/kernels/attention/test_triton_unified_attention_diffkv.py
+1
@@ -153,6 +153,7 @@ def test_triton_unified_attn_diffkv_vs_reference(
153
else:
154
from vllm.v1.attention.backends.fa_utils import flash_attn_varlen_func
155
156
+ assert fa_version is not None
157
# FA's TMA path needs aligned singleton strides (num_kv_heads == 1).
158
fa_k = canonicalize_singleton_dim_strides(key_cache)
159
fa_v = canonicalize_singleton_dim_strides(value_cache)
tests/kernels/helion/test_config_manager.py
+1
-1
@@ -79,7 +79,7 @@ class TestConfigSet:
79
config_set = ConfigSet("test_kernel")
80
81
with pytest.raises(KeyError, match="platform 'h100' not found"):
82
- config_set.get_config("h100", "nonexistent")
82
+ config_set.get_config("h100", CaseKey({"batch": 32}))
83
84
config_data = {"num_warps": 8, "num_stages": 4}
85
data = {
tests/kernels/helion/test_dynamic_per_token_scaled_fp8_quant.py
+1
-1
@@ -81,7 +81,7 @@ class TestDynamicPerTokenScaledFp8QuantConfigPicker:
81
assert selected_key == CaseKey({"hidden_size": 2048, "num_tokens": 32})
82
83
def test_config_picker_no_configs(self):
84
- config_keys: list[dict] = []
84
+ config_keys: list[CaseKey] = []
85
86
args = _generate_fake_input(16, 4096)
87
selected_key = pick_config(args, config_keys)
tests/kernels/helion/test_fused_qk_norm_rope.py
+1
-1
@@ -127,7 +127,7 @@ class TestFusedQkNormRopeConfigPicker:
127
)
128
129
def test_config_picker_no_configs(self):
130
- config_keys: list[dict] = []
130
+ config_keys: list[CaseKey] = []
131
132
args = _generate_fake_input(16, 4096, 128)
133
selected_key = pick_config(args, config_keys)
tests/kernels/helion/test_pattern_matching.py
+4
-3
@@ -38,6 +38,7 @@ from torch._inductor.pattern_matcher import (
38
)
39
from torch.fx.experimental.proxy_tensor import make_fx
40
41
+from vllm.kernels.helion.case_key import CaseKey
42
from vllm.kernels.helion.config_manager import ConfigManager
43
from vllm.kernels.helion.register import HelionKernelWrapper
44
@@ -45,7 +46,7 @@ from vllm.kernels.helion.register import HelionKernelWrapper
46
@contextlib.contextmanager
47
def _helion_mock_context():
48
configs = {
48
- "default": helion.Config(block_sizes=[64], num_warps=2, num_stages=2),
49
+ CaseKey.default(): helion.Config(block_sizes=[64], num_warps=2, num_stages=2),
50
}
51
mock_config_manager = Mock(spec=ConfigManager)
52
mock_config_manager.get_platform_configs = Mock(return_value=configs)
@@ -88,7 +89,7 @@ class TestMakeFxHop:
89
raw_kernel_func=raw_add_scale,
90
op_name="test_make_fx",
91
fake_impl=lambda *a, **kw: None,
91
- config_picker=lambda args, keys: "default",
92
+ config_picker=lambda args, keys: CaseKey.default(),
93
)
94
95
def fn(x, y):
@@ -145,7 +146,7 @@ class TestMakeFxHop:
146
raw_kernel_func=raw_silu_mul,
147
op_name="test_pm_silu_mul",
148
fake_impl=lambda *a, **kw: None,
148
- config_picker=lambda args, keys: "default",
149
+ config_picker=lambda args, keys: CaseKey.default(),
150
)
151
152
def pattern(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
tests/kernels/helion/test_per_token_group_fp8_quant.py
+1
-1
@@ -107,7 +107,7 @@ class TestPerTokenGroupFp8QuantConfigPicker:
107
)
108
109
def test_config_picker_no_configs(self):
110
- config_keys: list[dict] = []
110
+ config_keys: list[CaseKey] = []
111
112
args = _generate_fake_input(16, 4096, 128)
113
selected_key = pick_config(args, config_keys)
tests/kernels/helion/test_register.py
+1
@@ -397,6 +397,7 @@ class TestHelionKernelWrapper:
397
)
398
399
assert wrapper._disabled is True
400
+ assert wrapper._disabled_reason is not None
401
assert "No configs available" in wrapper._disabled_reason
402
403
def test_disabled_wrapper_raises_on_call(self, sample_kernel):
tests/kernels/helion/test_rms_norm_dynamic_per_token_quant.py
+1
-1
@@ -89,7 +89,7 @@ class TestRmsNormDynamicPerTokenQuantConfigPicker:
89
assert selected_key == CaseKey({"hidden_size": 2048, "num_tokens": 32})
90
91
def test_config_picker_no_configs(self):
92
- config_keys: list[dict] = []
92
+ config_keys: list[CaseKey] = []
93
94
args = _generate_fake_input(16, 4096)
95
selected_key = pick_config(args, config_keys)
tests/kernels/helion/test_rms_norm_per_block_quant.py
+1
-1
@@ -112,7 +112,7 @@ class TestRmsNormPerBlockQuantConfigPicker:
112
)
113
114
def test_config_picker_no_configs(self):
115
- config_keys: list[dict] = []
115
+ config_keys: list[CaseKey] = []
116
117
args = _generate_fake_input(16, 4096, 128)
118
selected_key = pick_config(args, config_keys)
tests/kernels/helion/test_silu_and_mul_per_block_quant.py
+1
-1
@@ -96,7 +96,7 @@ class TestSiluAndMulPerBlockQuantConfigPicker:
96
)
97
98
def test_config_picker_no_configs(self):
99
- config_keys: list[dict] = []
99
+ config_keys: list[CaseKey] = []
100
101
args = _generate_fake_input(16, 4096, 128)
102
selected_key = pick_config(args, config_keys)
tests/kernels/helion/test_silu_mul_fp8.py
+2
-1
@@ -83,7 +83,7 @@ class TestSiluMulFp8ConfigPicker:
83
assert selected_key == CaseKey({"intermediate": 4096, "numtokens": 256})
84
85
def test_config_picker_no_configs(self):
86
- config_keys: list[dict] = []
86
+ config_keys: list[CaseKey] = []
87
88
input_tensor = torch.randn(32, 4096, dtype=torch.bfloat16, device="cuda")
89
scale = torch.tensor([0.5], dtype=torch.float32, device="cuda")
@@ -355,6 +355,7 @@ class TestSiluMulFp8Integration:
355
registered_kernels = get_registered_kernels()
356
kernel_wrapper = registered_kernels["silu_mul_fp8"]
357
fake_impl = kernel_wrapper._fake_impl
358
+ assert fake_impl is not None
359
360
fake_output = fake_impl(input_tensor, scale)
361
tests/models/language/pooling/embed_utils.py
+9
@@ -7,6 +7,15 @@ import pytest
7
8
from tests.conftest import HfRunner
9
from tests.models.utils import EmbedModelInfo, check_embeddings_close, matryoshka_fy
10
+from vllm.entrypoints.pooling.embed.protocol import EmbeddingResponse
11
+
12
+
13
+def float_embeddings(response: EmbeddingResponse) -> list[list[float]]:
14
+ embeddings = []
15
+ for d in response.data:
16
+ assert isinstance(d.embedding, list)
17
+ embeddings.append(d.embedding)
18
+ return embeddings
19
20
21
def run_embedding_correctness_test(
tests/multimodal/media/test_video.py
+9
-9
@@ -53,12 +53,10 @@ def test_video_media_io_kwargs(monkeypatch: pytest.MonkeyPatch):
53
imageio = ImageMediaIO()
54
55
# Verify that different args pass/fail assertions as expected.
56
- videoio = VideoMediaIO(imageio, **{"num_frames": 10, "fps": 1.0})
56
+ videoio = VideoMediaIO(imageio, num_frames=10, fps=1.0)
57
_ = videoio.load_bytes(b"test")
58
59
- videoio = VideoMediaIO(
60
- imageio, **{"num_frames": 10, "fps": 1.0, "not_used": "not_used"}
61
- )
59
+ videoio = VideoMediaIO(imageio, num_frames=10, fps=1.0, not_used="not_used")
60
_ = videoio.load_bytes(b"test")
61
62
with pytest.raises(AssertionError, match="bad num_frames"):
@@ -66,11 +64,11 @@ def test_video_media_io_kwargs(monkeypatch: pytest.MonkeyPatch):
64
_ = videoio.load_bytes(b"test")
65
66
with pytest.raises(AssertionError, match="bad num_frames"):
69
- videoio = VideoMediaIO(imageio, **{"num_frames": 9, "fps": 1.0})
67
+ videoio = VideoMediaIO(imageio, num_frames=9, fps=1.0)
68
_ = videoio.load_bytes(b"test")
69
70
with pytest.raises(AssertionError, match="bad fps"):
73
- videoio = VideoMediaIO(imageio, **{"num_frames": 10, "fps": 2.0})
71
+ videoio = VideoMediaIO(imageio, num_frames=10, fps=2.0)
72
_ = videoio.load_bytes(b"test")
73
74
@@ -86,14 +84,14 @@ def test_opencv_video_io_colorspace(tmp_path, is_color: bool, fourcc: str, ext:
84
image = Image.open(image_path)
85
86
if not is_color:
89
- image_path = f"{tmp_path}/test_grayscale_image.png"
87
+ image_path = Path(tmp_path) / "test_grayscale_image.png"
88
image = image.convert("L")
89
image.save(image_path)
90
# Convert to gray RGB for comparison
91
image = image.convert("RGB")
92
video_path = f"{tmp_path}/test_RGB_video.{ext}"
93
create_video_from_image(
96
- image_path,
94
+ str(image_path),
95
video_path,
96
num_frames=2,
97
is_color=is_color,
@@ -385,7 +383,9 @@ def test_pynvvideocodec_unrelated_error_propagates(
383
384
with pytest.raises(RuntimeError) as exc_info:
385
PyNvVideoCodecVideoBackendMixin.decode_frames_pynvvideocodec(
388
- None, b"video", None
386
+ None,
387
+ b"video",
388
+ None, # type: ignore[arg-type] # Fails before target metadata is used.
389
)
390
391
assert exc_info.value is original_error
tests/multimodal/test_audio.py
+13
-1
@@ -179,7 +179,7 @@ def test_audio_resampler_invalid_method(dummy_audio):
179
# Validated eagerly so a bad method fails at construction, not on the
180
# first audio request.
181
with pytest.raises(ValueError, match="Invalid resampling method"):
182
- AudioResampler(target_sr=22050, method="invalid")
182
+ AudioResampler(target_sr=22050, method="invalid") # type: ignore[arg-type]
183
184
185
def test_audio_resampler_no_target_sr(dummy_audio):
@@ -378,6 +378,7 @@ class TestMultiModalDataParserChannelNormalization:
378
result = parser._parse_audio_data((stereo_audio, 16000))
379
380
# Check that result is mono (1D)
381
+ assert result is not None
382
audio_item = result.get(0)
383
assert audio_item.ndim == 1, f"Expected 1D mono audio, got {audio_item.ndim}D"
384
assert audio_item.shape == (3,), f"Expected shape (3,), got {audio_item.shape}"
@@ -404,6 +405,7 @@ class TestMultiModalDataParserChannelNormalization:
405
result = parser._parse_audio_data((stereo_audio, 16000))
406
407
# Check that result preserves original shape (after resampling)
408
+ assert result is not None
409
audio_item = result.get(0)
410
# When target_channels=None, stereo audio should be preserved
411
assert audio_item.ndim == 2, f"Expected 2D stereo audio, got {audio_item.ndim}D"
@@ -425,6 +427,7 @@ class TestMultiModalDataParserChannelNormalization:
427
result = parser._parse_audio_data((mono_audio, 16000))
428
429
# Check that result is still mono (1D)
430
+ assert result is not None
431
audio_item = result.get(0)
432
assert audio_item.ndim == 1
433
assert audio_item.shape == (16000,)
@@ -446,6 +449,7 @@ class TestMultiModalDataParserChannelNormalization:
449
result = parser._parse_audio_data((surround_audio, 16000))
450
451
# Check that result is stereo (2 channels)
452
+ assert result is not None
453
audio_item = result.get(0)
454
assert audio_item.ndim == 2
455
assert audio_item.shape[0] == 2 # 2 channels
@@ -483,6 +487,7 @@ class TestAudioPipelineE2E:
487
488
# Process audio through the parser
489
result = parser._parse_audio_data((stereo_torchaudio, 16000))
490
+ assert result is not None
491
audio_output = result.get(0)
492
493
# Verify output is mono 1D
@@ -512,6 +517,7 @@ class TestAudioPipelineE2E:
517
518
# Process audio through the parser
519
result = parser._parse_audio_data((stereo_soundfile, 16000))
520
+ assert result is not None
521
audio_output = result.get(0)
522
523
# Verify output is mono 1D
@@ -537,6 +543,7 @@ class TestAudioPipelineE2E:
543
544
# Process audio through the parser
545
result = parser._parse_audio_data((mono_pyav, 16000))
546
+ assert result is not None
547
audio_output = result.get(0)
548
549
# Verify output is still mono 1D
@@ -572,6 +579,7 @@ class TestAudioPipelineE2E:
579
580
# Process audio through the parser
581
result = parser._parse_audio_data((surround_audio, 16000))
582
+ assert result is not None
583
audio_output = result.get(0)
584
585
# Verify output is mono 1D
@@ -603,6 +611,7 @@ class TestAudioPipelineE2E:
611
# Process audio through the parser
612
# Note: Parser expects numpy, so we convert first (simulating real usage)
613
result = parser._parse_audio_data((stereo_torch.numpy(), 16000))
614
+ assert result is not None
615
audio_output = result.get(0)
616
617
# Verify output is mono 1D numpy array
@@ -630,6 +639,7 @@ class TestAudioPipelineE2E:
639
640
# Process audio through the parser
641
result = parser._parse_audio_data((stereo_audio, 16000))
642
+ assert result is not None
643
audio_output = result.get(0)
644
645
# Verify output preserves stereo (2D)
@@ -654,6 +664,7 @@ class TestAudioPipelineE2E:
664
665
# Process audio through the parser
666
result = parser._parse_audio_data((stereo_48k, 48000))
667
+ assert result is not None
668
audio_output = result.get(0)
669
670
# Verify output is mono 1D at target sample rate
@@ -677,6 +688,7 @@ class TestAudioPipelineE2E:
688
)
689
690
result = parser._parse_audio_data((short_stereo, 16000))
691
+ assert result is not None
692
audio_output = result.get(0)
693
694
# Should still produce mono output
tests/multimodal/test_cache.py
+13
-6
@@ -11,7 +11,6 @@ import torch
11
from vllm.config import ModelConfig, ParallelConfig, VllmConfig
12
from vllm.config.multimodal import MultiModalConfig
13
from vllm.multimodal.cache import (
14
- BaseMultiModalProcessorCache,
14
BaseMultiModalReceiverCache,
15
LruKeyReplicatedReceiverCache,
16
LruKeyReplicatedSenderCache,
@@ -145,6 +144,8 @@ def _compare_caches(
144
cache_1_p0 = processor_cache_from_config(config_1)
145
cache_1_p1 = engine_receiver_cache_from_config(config_1)
146
147
+ assert config_0.model_config.multimodal_config is not None
148
+ assert config_1.model_config.multimodal_config is not None
149
cache_size_gb = max(
150
config_0.model_config.multimodal_config.mm_processor_cache_gb,
151
config_1.model_config.multimodal_config.mm_processor_cache_gb,
@@ -280,6 +281,7 @@ def test_oversized_item_is_served_uncached():
281
282
p0 = LruKeyReplicatedSenderCache(model_config) # type: ignore[arg-type]
283
p1 = LruKeyReplicatedReceiverCache(model_config) # type: ignore[arg-type]
284
+ assert item is not None
285
assert p0.get_and_update_item((item, []), "big")[0] is item
286
assert not p0.is_cached_item("big")
287
assert p1.get_and_update_item(item, "big") is item
@@ -318,6 +320,7 @@ def test_mm_cache_miss_raises_and_recovers():
320
assert p0.is_cached_item(mm_hash)
321
# On the next request P0 short-circuits to data=None -- the drift bug when
322
# P1 lacks the item.
323
+ assert item is not None
324
hit = p0.get_and_update_item((item, []), mm_hash)
325
assert hit[0] is None
326
@@ -326,6 +329,7 @@ def test_mm_cache_miss_raises_and_recovers():
329
p0.invalidate(mm_hash)
330
assert not p0.is_cached_item(mm_hash)
331
332
+ assert item is not None
333
resent = p0.get_and_update_item((item, []), mm_hash)
334
assert resent[0] is item # MISS again -> data is resent
335
assert p1.get_and_update_item(item, mm_hash) == item # P1 now caches it
@@ -459,6 +463,7 @@ def test_shm_receiver_handles_prefix_covered_items(monkeypatch):
463
address_item, _ = p0.get_and_update_item((item, []), mm_hash)
464
first = _feature(mm_hash, address_item)
465
p1.get_and_update_features([first])
466
+ assert first.data is not None
467
assert torch.equal(first.data["dummy"].data, item["dummy"].data)
468
469
# Request 2 (identical, fully prefix-covered): the sender hit takes
@@ -476,6 +481,7 @@ def test_shm_receiver_handles_prefix_covered_items(monkeypatch):
481
482
assert covered_uncached.data is None
483
# The address item is resolved to the cached payload.
484
+ assert covered_cached.data is not None
485
assert torch.equal(covered_cached.data["dummy"].data, item["dummy"].data)
486
487
# The hit's writer references were acknowledged by the worker, so the
@@ -488,7 +494,7 @@ def test_shm_receiver_handles_prefix_covered_items(monkeypatch):
494
495
496
def _run_test_cache_eviction_lru(
491
- p0_cache: BaseMultiModalProcessorCache,
497
+ p0_cache: LruKeyReplicatedSenderCache,
498
p1_cache: BaseMultiModalReceiverCache,
499
base_item_size: int,
500
):
@@ -603,7 +609,7 @@ def test_cache_eviction_lru_cache():
609
# image_B is protected from eviction then image_i cannot be added.
610
# This proving normal eviction and reuse behavior.
611
def _run_test_cache_eviction_shm(
606
- p0_cache: BaseMultiModalProcessorCache,
612
+ p0_cache: ShmObjectStoreSenderCache,
613
p1_cache: BaseMultiModalReceiverCache,
614
base_item_size: int,
615
):
@@ -860,9 +866,9 @@ async def test_release_kv_cache_resends_mm_payload(use_async, release_error):
866
867
async def call_release():
868
if use_async:
863
- await AsyncLLM.release_kv_cache_memory(engine)
869
+ await AsyncLLM.release_kv_cache_memory(engine) # type: ignore[arg-type]
870
else:
865
- LLMEngine.release_kv_cache_memory(engine)
871
+ LLMEngine.release_kv_cache_memory(engine) # type: ignore[arg-type]
872
873
if release_error:
874
with pytest.raises(RuntimeError, match=release_error):
@@ -894,9 +900,10 @@ def test_sleep_wake_preserves_mm_cache_consistency():
900
"""Regression for vllm-project/vllm#42995."""
901
from vllm import LLM, SamplingParams
902
from vllm.assets.image import ImageAsset
903
+ from vllm.inputs import TextPrompt
904
905
image = ImageAsset("stop_sign").pil_image
899
- prompt = {
906
+ prompt: TextPrompt = {
907
"prompt": _SLEEP_VISION_PROMPT,
908
"multi_modal_data": {"image": image},
909
}
tests/multimodal/test_hasher.py
+2
@@ -131,6 +131,7 @@ def test_hash_collision_video_num_frames():
131
}
132
video = MediaWithBytes((frames, metadata), source)
133
items = MultiModalDataParser()._parse_video_data([video])
134
+ assert items is not None
135
return items.get_all_items_for_hash()[0]
136
137
hasher = MultiModalHasher
@@ -155,6 +156,7 @@ def test_hash_video_tensor_frames():
156
}
157
video = MediaWithBytes((frames, metadata), source)
158
items = MultiModalDataParser()._parse_video_data([video])
159
+ assert items is not None
160
return items.get_all_items_for_hash()[0]
161
162
np_frames = np.zeros((2, 8, 8, 3), dtype=np.uint8)
tests/multimodal/test_processing.py
+22
-13
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import time
5
+from collections.abc import Sequence
6
from contextlib import nullcontext
7
from copy import deepcopy
8
from types import SimpleNamespace
@@ -868,7 +869,7 @@ def test_limit_mm_per_prompt_apply(model_id, num_images, limit, is_valid):
869
],
870
)
871
def test_budget_caps_prevent_dummy_input_validation_failure(
871
- model_id, user_limit, supported_limit
872
+ model_id, user_limit, supported_limit, monkeypatch
873
):
874
limit_mm_per_prompt = {"image": user_limit}
875
@@ -878,7 +879,9 @@ def test_budget_caps_prevent_dummy_input_validation_failure(
879
)
880
881
processor = MULTIMODAL_REGISTRY.create_processor(model_config)
881
- processor.info.get_supported_mm_limits = lambda: {"image": supported_limit}
882
+ monkeypatch.setattr(
883
+ processor.info, "get_supported_mm_limits", lambda: {"image": supported_limit}
884
+ )
885
886
# This is what budget.py uses to derive mm_counts
887
allowed = processor.info.allowed_mm_limits
@@ -936,7 +939,7 @@ def test_hf_processor_init_kwargs(
939
)
940
941
processor = ctx.get_hf_processor(
939
- DummyProcessor, # type: ignore[arg-type]
942
+ DummyProcessor,
943
**inference_kwargs,
944
)
945
assert processor.a == expected_kwargs["a"]
@@ -967,7 +970,7 @@ def test_hf_processor_call_kwargs(
970
tokenizer=None,
971
)
972
970
- processor = ctx.get_hf_processor(DummyProcessor) # type: ignore[arg-type]
973
+ processor = ctx.get_hf_processor(DummyProcessor)
974
975
result = ctx.call_hf_processor(processor, {}, inference_kwargs)
976
assert result == expected_kwargs
@@ -1278,7 +1281,7 @@ def test_get_supported_mm_processor_kwargs_uses_supported_modalities(
1281
processor: SimpleNamespace,
1282
expected: dict[str, set[str]],
1283
) -> None:
1281
- info = BaseProcessingInfo(SimpleNamespace())
1284
+ info = BaseProcessingInfo(SimpleNamespace()) # type: ignore[arg-type]
1285
info.__dict__["supported_mm_limits"] = supported_mm_limits
1286
monkeypatch.setattr(info, "get_hf_processor", lambda **_: processor)
1287
@@ -1288,7 +1291,7 @@ def test_get_supported_mm_processor_kwargs_uses_supported_modalities(
1291
def test_supported_mm_processor_kwargs_is_cached(
1292
monkeypatch: pytest.MonkeyPatch,
1293
) -> None:
1291
- info = BaseProcessingInfo(SimpleNamespace())
1294
+ info = BaseProcessingInfo(SimpleNamespace()) # type: ignore[arg-type]
1295
expected = {"images_kwargs": {"size"}}
1296
calls = 0
1297
@@ -1541,7 +1544,10 @@ def test_processor_inputs_hashes_scope_kwargs_by_modality():
1544
"video": [np.zeros((2, 8, 8, 3), dtype=np.uint8)],
1545
}
1546
)
1544
- mm_uuid_items = {"image": ["image-uuid"], "video": ["video-uuid"]}
1547
+ mm_uuid_items: dict[str, Sequence[str | None]] = {
1548
+ "image": ["image-uuid"],
1549
+ "video": ["video-uuid"],
1550
+ }
1551
1552
def get_hashes(video_frames: int, image_size: int, video_size: int):
1553
return ProcessorInputs(
@@ -1621,7 +1627,7 @@ def test_processor_inputs_hashes_distinguish_kwargs_shapes(left, right):
1627
[(None, 491520, 491520), (True, 491520, 8192), (True, 128, 128), (False, 128, 128)],
1628
)
1629
def test_dummy_inputs_scheduler_budget(
1624
- chunked_prefill, max_model_len, expected_seq_len
1630
+ chunked_prefill, max_model_len, expected_seq_len, monkeypatch
1631
):
1632
ctx = build_model_context(
1633
"llava-hf/llava-v1.6-mistral-7b-hf",
@@ -1634,11 +1640,9 @@ def test_dummy_inputs_scheduler_budget(
1640
ctx.model_config,
1641
tokenizer=ctx.tokenizer,
1642
)
1637
- processor.apply = lambda *args, **kwargs: {"prompt_token_ids": [7]}
1638
-
1639
- kwargs = {}
1643
+ scheduler_config = None
1644
if chunked_prefill is not None:
1641
- kwargs["scheduler_config"] = SchedulerConfig(
1645
+ scheduler_config = SchedulerConfig(
1646
max_model_len=max_model_len,
1647
is_encoder_decoder=False,
1648
max_num_batched_tokens=8192,
@@ -1646,5 +1650,10 @@ def test_dummy_inputs_scheduler_budget(
1650
enable_chunked_prefill=chunked_prefill,
1651
)
1652
1649
- result = processor.get_dummy_mm_inputs({"image": 1}, **kwargs)
1653
+ monkeypatch.setattr(
1654
+ processor, "apply", lambda *args, **kwargs: {"prompt_token_ids": [7]}
1655
+ )
1656
+ result = processor.get_dummy_mm_inputs(
1657
+ {"image": 1}, scheduler_config=scheduler_config
1658
+ )
1659
assert len(result["prompt_token_ids"]) == expected_seq_len
tests/multimodal/test_registry.py
+1
-1
@@ -24,4 +24,4 @@ def test_create_processor_error_uses_served_model_name():
24
ValueError,
25
match="friendly-model-name is not a multimodal model",
26
):
27
- MULTIMODAL_REGISTRY.create_processor(model_config)
27
+ MULTIMODAL_REGISTRY.create_processor(model_config) # type: ignore[arg-type]
tests/multimodal/test_video.py
+26
-17
@@ -7,6 +7,7 @@ import sys
7
import threading
8
from contextlib import ExitStack, contextmanager
9
from pathlib import Path
10
+from typing import Any
11
12
import numpy as np
13
import numpy.typing as npt
@@ -83,24 +84,28 @@ def _fresh_decoder_pool():
84
@VIDEO_LOADER_REGISTRY.register("test_video_loader_1")
85
class TestVideoLoader1(VideoLoader):
86
@classmethod
86
- def load_bytes(cls, data: bytes, num_frames: int = -1) -> npt.NDArray:
87
- return FAKE_OUTPUT_1
87
+ def load_bytes(
88
+ cls, data: bytes, num_frames: int = -1, **kwargs
89
+ ) -> tuple[npt.NDArray, dict[str, Any]]:
90
+ return FAKE_OUTPUT_1, {}
91
92
93
@VIDEO_LOADER_REGISTRY.register("test_video_loader_2")
94
class TestVideoLoader2(VideoLoader):
95
@classmethod
93
- def load_bytes(cls, data: bytes, num_frames: int = -1) -> npt.NDArray:
94
- return FAKE_OUTPUT_2
96
+ def load_bytes(
97
+ cls, data: bytes, num_frames: int = -1, **kwargs
98
+ ) -> tuple[npt.NDArray, dict[str, Any]]:
99
+ return FAKE_OUTPUT_2, {}
100
101
102
def test_video_loader_registry():
103
custom_loader_1 = VIDEO_LOADER_REGISTRY.load("test_video_loader_1")
99
- output_1 = custom_loader_1.load_bytes(b"test")
104
+ output_1, _ = custom_loader_1.load_bytes(b"test")
105
np.testing.assert_array_equal(output_1, FAKE_OUTPUT_1)
106
107
custom_loader_2 = VIDEO_LOADER_REGISTRY.load("test_video_loader_2")
103
- output_2 = custom_loader_2.load_bytes(b"test")
108
+ output_2, _ = custom_loader_2.load_bytes(b"test")
109
np.testing.assert_array_equal(output_2, FAKE_OUTPUT_2)
110
111
@@ -488,7 +493,7 @@ def test_pynvvideocodec_failed_rebuild_invalidates_decoder_slot():
493
494
old_decoder = FakeDecoder()
495
slot = PyNvVideoCodecDecoderSlot(FakeStream())
491
- slot.decoder = old_decoder
496
+ slot.decoder = old_decoder # type: ignore[assignment] # Fake retained GPU decoder.
497
slot.source_path = "valid.mp4"
498
499
class FakeNvc:
@@ -596,7 +601,9 @@ def test_pynvvideocodec_h200_recovers_after_unsupported_8k():
601
_pynv_decoder_pool.max_slots = old_max
602
603
599
-def test_pynvvideocodec_cross_subclass_shares_single_pool():
604
+def test_pynvvideocodec_cross_subclass_shares_single_pool(
605
+ monkeypatch: pytest.MonkeyPatch,
606
+):
607
"""Regression test for GHSA-j682-9xp5-rrf3.
608
609
Multiple subclasses of PyNvVideoCodecVideoBackendMixin must share the
@@ -623,11 +630,12 @@ def test_pynvvideocodec_cross_subclass_shares_single_pool():
630
with _fresh_decoder_pool() as pool:
631
pool.max_slots = 2
632
626
- orig_create = PyNvVideoCodecVideoBackendMixin._create_decoder_slot
627
- PyNvVideoCodecVideoBackendMixin._create_decoder_slot = classmethod(
628
- fake_create_slot
629
- )
630
- try:
633
+ with monkeypatch.context() as m:
634
+ m.setattr(
635
+ PyNvVideoCodecVideoBackendMixin,
636
+ "_create_decoder_slot",
637
+ classmethod(fake_create_slot),
638
+ )
639
with ExitStack() as stack:
640
stack.enter_context(MixinSubclassA._borrow_decoder_slot())
641
stack.enter_context(MixinSubclassB._borrow_decoder_slot())
@@ -652,8 +660,6 @@ def test_pynvvideocodec_cross_subclass_shares_single_pool():
660
661
assert create_count == 2
662
assert len(pool.slots) == 2
655
- finally:
656
- PyNvVideoCodecVideoBackendMixin._create_decoder_slot = orig_create
663
664
665
@pytest.mark.parametrize("hw_decoders", [0, -1, 1.5, True, "2"])
@@ -662,7 +668,7 @@ def test_pynvvideocodec_rejects_invalid_hw_decoders(hw_decoders: object):
668
VideoBackend.load_bytes(
669
b"fake video",
670
backend=PYNVVIDEOCODEC_VIDEO_BACKEND,
665
- hw_decoders=hw_decoders, # type: ignore[arg-type]
671
+ hw_decoders=hw_decoders,
672
)
673
674
@@ -799,6 +805,7 @@ def test_video_processor_from_model_repo(
805
)
806
807
backend = get_video_loader_backend_for_processor(video_processor)
808
+ assert backend is not None
809
loader = VIDEO_LOADER_REGISTRY.load(backend)
810
assert isinstance(loader, expected_loader_cls), (
811
f"{model_repo!r}: backend={backend!r} loaded "
@@ -1693,7 +1700,9 @@ def test_glm5next_backend_codec_parity(tmp_path, backend):
1700
path = _write_gray_video(tmp_path, total_frames, fps)
1701
# Dense default sampling (gap 5) and a sparse max_frames cap (gap 20).
1702
for max_frames in (None, 6):
1696
- kwargs = {} if max_frames is None else {"max_frames": max_frames}
1703
+ kwargs: dict[str, Any] = (
1704
+ {} if max_frames is None else {"max_frames": max_frames}
1705
+ )
1706
expected = glm_sample_frame_indices(
1707
total_frames, float(fps), 12.0, max_frame_count=max_frames
1708
)
tests/quantization/test_auto_gptq.py
+4
-4
@@ -123,8 +123,8 @@ def test_auto_gptq_moe_creates_zero_initialized_expert_biases():
123
method = object.__new__(AutoGPTQMoEMethod)
124
method.quant_config = AutoGPTQConfig(4, 128, False, True, False, {}, {})
125
method.input_dtype = None
126
- method.experts_cls = None
127
- method.moe = SimpleNamespace(w13_num_shards=2)
126
+ method.experts_cls = None # type: ignore[assignment] # Weight creation does not select experts.
127
+ method.moe = SimpleNamespace(w13_num_shards=2) # type: ignore[assignment]
128
layer = torch.nn.Module()
129
130
method.create_weights(
@@ -170,7 +170,7 @@ def test_routed_experts_loads_per_expert_biases():
170
("w1", torch.tensor([1.0, 2.0, 3.0, 4.0])),
171
("w3", torch.tensor([5.0, 6.0, 7.0, 8.0])),
172
):
173
- assert RoutedExperts.weight_loader(
173
+ assert RoutedExperts.weight_loader( # type: ignore[call-overload]
174
loader,
175
w13_bias,
176
loaded,
@@ -180,7 +180,7 @@ def test_routed_experts_loads_per_expert_biases():
180
return_success=True,
181
)
182
183
- assert RoutedExperts.weight_loader(
183
+ assert RoutedExperts.weight_loader( # type: ignore[call-overload]
184
loader,
185
w2_bias,
186
torch.tensor([9.0, 10.0, 11.0, 12.0]),
tests/quantization/test_auto_round.py
+9
-9
@@ -193,7 +193,7 @@ class DummyFusedMoE:
193
194
195
def make_config(**overrides) -> INCConfig:
196
- kwargs = {
196
+ kwargs: dict[str, Any] = {
197
"weight_bits": 4,
198
"group_size": 128,
199
"sym": True,
@@ -208,7 +208,7 @@ def make_config(**overrides) -> INCConfig:
208
209
210
def make_layer_config(**overrides) -> INCLayerConfig:
211
- kwargs = {
211
+ kwargs: dict[str, Any] = {
212
"bits": 4,
213
"group_size": 128,
214
"sym": True,
@@ -423,7 +423,7 @@ def test_inc_get_quant_method_unquantized_routed_experts_with_model_prefix(
423
424
config = make_config(extra_config={"model.moe_layer": {"bits": 16}})
425
layer = object.__new__(RoutedExperts)
426
- layer.moe_config = None
426
+ layer.moe_config = None # type: ignore[assignment] # No MoE config used by this stub.
427
428
method = config.get_quant_method(layer, "moe_layer")
429
@@ -730,7 +730,7 @@ def test_wna16_cuda_low_bit_moe_routes_to_humming(monkeypatch, bits) -> None:
730
)
731
732
layer = object.__new__(RoutedExperts)
733
- layer.moe_config = DummyMoeConfig()
733
+ layer.moe_config = DummyMoeConfig() # type: ignore[assignment]
734
layer_config = make_layer_config(bits=bits)
735
method = INCWna16Scheme().get_moe_method(
736
make_config(), layer, "model.layers.0.mlp", layer_config
@@ -861,7 +861,7 @@ def test_qwen3_30b_a3b_w4a16_autoround_routes_to_gptq_moe(
861
862
config = make_qwen3_autoround_config("qwen3_30b_a3b_w4a16")
863
layer = object.__new__(RoutedExperts)
864
- layer.moe_config = DummyMoeConfig()
864
+ layer.moe_config = DummyMoeConfig() # type: ignore[assignment]
865
866
method = config.get_quant_method(layer, "model.layers.0.mlp")
867
@@ -890,7 +890,7 @@ def test_qwen3_30b_a3b_mxfp4_autoround_routes_to_mxfp4_moe(
890
891
config = make_qwen3_autoround_config("qwen3_30b_a3b_mxfp4")
892
layer = object.__new__(RoutedExperts)
893
- layer.moe_config = DummyMoeConfig()
893
+ layer.moe_config = DummyMoeConfig() # type: ignore[assignment]
894
895
ignored_method = config.get_quant_method(
896
object.__new__(LinearBase), "model.layers.0.self_attn.q_proj"
@@ -1186,7 +1186,7 @@ def test_wna16_xpu_moe_routes_to_gptq_moe(monkeypatch) -> None:
1186
)
1187
1188
layer = object.__new__(RoutedExperts)
1189
- layer.moe_config = DummyMoeConfig()
1189
+ layer.moe_config = DummyMoeConfig() # type: ignore[assignment]
1190
method = INCWna16Scheme().get_moe_method(
1191
make_config(),
1192
layer,
@@ -1669,7 +1669,7 @@ def test_inc_get_quant_method_unquantized_moe_returns_unquantized(
1669
when extra_config has bits >= 16."""
1670
config = make_config(extra_config={"layer": {"bits": 16}})
1671
layer = object.__new__(RoutedExperts)
1672
- layer.moe_config = None # UnquantizedFusedMoEMethod accepts moe_config
1672
+ layer.moe_config = None # type: ignore[assignment] # No MoE config used by this stub.
1673
1674
class DummyUnquantizedFusedMoEMethod:
1675
def __init__(self, moe_config) -> None:
@@ -2094,7 +2094,7 @@ def _with_w4a8_kernel(monkeypatch) -> None:
2094
2095
def _dispatch(layer_config=None):
2096
return INCWna16Scheme().get_linear_method(
2097
- object(), object(), "layer", layer_config or make_layer_config()
2097
+ make_config(), object(), "layer", layer_config or make_layer_config()
2098
)
2099
2100
tests/quantization/test_cutlass_w4a16.py
+1
@@ -99,6 +99,7 @@ def test_machete_rejects_invalid_config(
99
)
100
can_impl, reason = MacheteLinearKernel.can_implement(config)
101
assert not can_impl
102
+ assert reason is not None
103
assert expected_reason in reason
104
105
tests/quantization/test_fp8.py
+27
-14
@@ -25,6 +25,7 @@ from vllm.model_executor.layers.attention.attention import (
25
set_default_quant_scales,
26
)
27
from vllm.model_executor.layers.fused_moe import FusedMoEFactory
28
+from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
29
from vllm.model_executor.layers.quantization.fp8 import (
30
Fp8Config,
31
Fp8LinearMethod,
@@ -79,17 +80,22 @@ def test_deepseek_v41_mxfp8_scale_loading(
80
for module in (model_module, linear_module):
81
monkeypatch.setattr(module, "get_tensor_model_parallel_world_size", lambda: 2)
82
monkeypatch.setattr(module, "get_tensor_model_parallel_rank", lambda: tp_rank)
82
- tp_args = dict(quant_config=quant_config, bias=False)
83
block = torch.nn.Module()
84
block.attn = torch.nn.Module()
85
- block.attn.wq_b = ColumnParallelLinear(128, 128, **tp_args)
86
- block.attn.fused_wqa_wkv = MergedColumnParallelLinear(128, [128, 64], **tp_args)
85
+ block.attn.wq_b = ColumnParallelLinear(
86
+ 128, 128, quant_config=quant_config, bias=False
87
+ )
88
+ block.attn.fused_wqa_wkv = MergedColumnParallelLinear(
89
+ 128, [128, 64], quant_config=quant_config, bias=False
90
+ )
91
block.ffn = torch.nn.Module()
92
block.ffn.shared_experts = torch.nn.Module()
93
block.ffn.shared_experts.gate_up_proj = MergedColumnParallelLinear(
90
- 128, [128, 128], **tp_args
94
+ 128, [128, 128], quant_config=quant_config, bias=False
95
+ )
96
+ block.ffn.shared_experts.down_proj = RowParallelLinear(
97
+ 128, 128, quant_config=quant_config, bias=False
98
)
92
- block.ffn.shared_experts.down_proj = RowParallelLinear(128, 128, **tp_args)
99
block.ffn.experts = torch.nn.Module()
100
block.ffn.experts.register_parameter(
101
"weight_scale", torch.nn.Parameter(torch.empty(3, 4, dtype=torch.uint8), False)
@@ -111,9 +117,11 @@ def test_deepseek_v41_mxfp8_scale_loading(
117
model.config = SimpleNamespace(num_attention_heads=2)
118
model.quant_config = quant_config
119
model.use_sequence_parallel = False
114
- model.get_expert_mapping = lambda: [
115
- ("experts.weight_scale", "experts.0.w1.weight_scale", 0, "w1")
116
- ]
120
+ monkeypatch.setattr(
121
+ model,
122
+ "get_expert_mapping",
123
+ lambda: [("experts.weight_scale", "experts.0.w1.weight_scale", 0, "w1")],
124
+ )
125
126
checkpoints = [
127
("attn.wq_b", "attn.wq_b", 128, 128, 0, None),
@@ -211,7 +219,10 @@ def test_deepseek_v41_vl_mapper_routes_linear_scales(
219
vllm_config = SimpleNamespace(
220
quant_config=SimpleNamespace(weight_block_size=weight_block_size)
221
)
214
- resolved = model_module._linear_scale_param_name(vllm_config, expert_dtype)
222
+ resolved = model_module._linear_scale_param_name(
223
+ vllm_config, # type: ignore[arg-type]
224
+ expert_dtype,
225
+ )
226
assert resolved == scale_name
227
228
mapper = vl_module._make_deepseek_v4_vl_weights_mapper(expert_dtype, resolved)
@@ -373,7 +384,7 @@ def test_deepgemm_mxfp8_preserves_weight_and_scale_values(
384
@pytest.mark.parametrize("config_source", ["deepseek", "mxfp8"])
385
@pytest.mark.parametrize("num_tokens", [1, 7, 128])
386
def test_mxfp8_bmm_loads_and_projects_grouped_weights(
376
- dist_init, default_vllm_config, prequantized, config_source, num_tokens
387
+ dist_init, default_vllm_config, monkeypatch, prequantized, config_source, num_tokens
388
):
389
"""BMM metadata set after construction selects grouped weight processing."""
390
from vllm.model_executor.kernels.linear.mxfp8.deep_gemm import (
@@ -398,10 +409,11 @@ def test_mxfp8_bmm_loads_and_projects_grouped_weights(
409
pytest.skip("DeepGEMM MXFP8 BMM requires Blackwell")
410
411
default_vllm_config.model_config = SimpleNamespace(dtype=torch.bfloat16)
401
- quant_config = DeepseekV4FP8Config(
412
+ deepseek_config = DeepseekV4FP8Config(
413
is_checkpoint_fp8_serialized=True, weight_block_size=[32, 32]
414
)
404
- quant_config._resolved_expert_dtype = "fp4"
415
+ deepseek_config._resolved_expert_dtype = "fp4"
416
+ quant_config: QuantizationConfig = deepseek_config
417
if config_source == "mxfp8":
418
quant_config = get_quantization_config("mxfp8").from_config(
419
{"quant_method": "mxfp8"}
@@ -432,7 +444,7 @@ def test_mxfp8_bmm_loads_and_projects_grouped_weights(
444
model.config = SimpleNamespace(num_attention_heads=2)
445
model.quant_config = quant_config
446
model.use_sequence_parallel = False
435
- model.get_expert_mapping = lambda: []
447
+ monkeypatch.setattr(model, "get_expert_mapping", lambda: [])
448
model.load_weights(
449
[
450
("layers.0.attn.wo_a.weight", weight),
@@ -443,6 +455,7 @@ def test_mxfp8_bmm_loads_and_projects_grouped_weights(
455
weight.float().reshape(8, 32, 16, 32)
456
* torch.exp2(scales.float() - 127)[:, None, :, None]
457
).reshape(2, 128, 512)
458
+ assert isinstance(linear.quant_method, ModelOptLinearMethod)
459
linear.quant_method.process_weights_after_loading(linear)
460
linear.quant_method.process_weights_after_loading(linear)
461
assert isinstance(linear.quant_method.kernel, DeepGemmMxfp8BmmLinearKernel)
@@ -785,7 +798,7 @@ def test_kv_cache_scale_sync_to_host_copies():
798
set_default_quant_scales(layer, register_buffer=True)
799
layer.kv_cache_dtype = "fp8"
800
788
- method = BaseKVCacheMethod(quant_config=None)
801
+ method = BaseKVCacheMethod(quant_config=None) # type: ignore[arg-type] # Scale synchronization does not use quant_config.
802
method.create_weights(layer)
803
# 0.3 stays != 1.0 even after the fp8_fnuz x2 rescale.
804
checkpoint_scale = torch.tensor(0.3, dtype=torch.float32)
tests/quantization/test_gfx950_moe.py
+2
-1
@@ -8,6 +8,7 @@ These tests run on real hardware — no mocks. Skipped on non-GFX950 platforms.
8
import pytest
9
import torch
10
11
+from vllm.config.kernel import MoEBackend
12
from vllm.model_executor.layers.fused_moe.config import (
13
FusedMoEConfig,
14
FusedMoEParallelConfig,
@@ -53,7 +54,7 @@ def disable_rocm_aiter(monkeypatch: pytest.MonkeyPatch):
54
set_rocm_aiter(monkeypatch, False)
55
56
56
-def _make_w4a4_moe_config(moe_backend: str = "auto") -> FusedMoEConfig:
57
+def _make_w4a4_moe_config(moe_backend: MoEBackend = "auto") -> FusedMoEConfig:
58
from vllm.model_executor.layers.fused_moe.activation import MoEActivation
59
60
return FusedMoEConfig(
tests/quantization/test_int8_moe_oracle.py
+2
-1
@@ -10,6 +10,7 @@ CUDA (SM >= 7.5), ROCm, or XPU — not just gfx950.
10
import pytest
11
import torch
12
13
+from vllm.config.kernel import MoEBackend
14
from vllm.model_executor.layers.fused_moe.config import (
15
FusedMoEConfig,
16
FusedMoEParallelConfig,
@@ -44,7 +45,7 @@ requires_int8_moe = pytest.mark.skipif(
45
DEVICE = current_platform.device_type
46
47
47
-def _make_int8_moe_config(moe_backend: str = "auto") -> FusedMoEConfig:
48
+def _make_int8_moe_config(moe_backend: MoEBackend = "auto") -> FusedMoEConfig:
49
from vllm.model_executor.layers.fused_moe.activation import MoEActivation
50
51
return FusedMoEConfig(
tests/quantization/test_lm_head.py
+2
@@ -15,6 +15,7 @@ from tests.quantization.utils import load_model_without_vllm_runner
15
from vllm.model_executor.layers.linear import ColumnParallelLinear
16
from vllm.model_executor.layers.logits_processor import LogitsProcessor
17
from vllm.model_executor.layers.quantization.auto_gptq import AutoGPTQLinearMethod
18
+from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
19
from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors import ( # noqa: E501
20
CompressedTensorsConfig,
21
)
@@ -80,6 +81,7 @@ def test_quantized_lm_head_matches_linear(
81
dtype=torch.bfloat16, head_dtype=None
82
)
83
default_vllm_config.kernel_config.linear_backend = "humming"
84
+ quant_config: QuantizationConfig
85
if quant_format is None:
86
quant_config = ModelOptNvFp4Config(
87
quant_method=preset,
tests/quantization/test_modelopt.py
+1
@@ -843,6 +843,7 @@ def test_modelopt_mixed_precision_builds_w4a16_sibling_config():
843
}
844
config = m.ModelOptMixedPrecisionConfig.from_config(hf_quant_config)
845
846
+ assert isinstance(config, m.ModelOptMixedPrecisionConfig)
847
assert config.nvfp4_config.quant_method == "NVFP4"
848
assert config.w4a16_nvfp4_config.quant_method == "W4A16_NVFP4"
849
tests/quantization/test_moe_wna16.py
+7
-5
@@ -218,11 +218,13 @@ def test_gptq_nfirst_weights_pass_through_for_triton():
218
219
def test_moe_wna16_setup_forwards_selected_backend(monkeypatch):
220
method = object.__new__(MoeWNA16Method)
221
- method.experts_cls = object
221
+ method.experts_cls = object # type: ignore[assignment] # Opaque mocked-expert sentinel.
222
method.wna16_backend = WNA16MoEBackend.HUMMING
223
- method.moe = object()
223
+ method.moe = object() # type: ignore[assignment] # Opaque mocked-config sentinel.
224
quant_config = object()
225
- method.get_fused_moe_quant_config = lambda layer: quant_config
225
+ monkeypatch.setattr(
226
+ method, "get_fused_moe_quant_config", lambda layer: quant_config
227
+ )
228
layer = SimpleNamespace(_expert_routing_tables=lambda: (None, None, None))
229
captured = {}
230
kernel = object()
@@ -233,7 +235,7 @@ def test_moe_wna16_setup_forwards_selected_backend(monkeypatch):
235
236
monkeypatch.setattr(moe_wna16, "make_wna16_moe_kernel", fake_make_wna16_moe_kernel)
237
236
- method._setup_kernel(layer)
238
+ method._setup_kernel(layer) # type: ignore[arg-type] # Routing-table stub.
239
240
assert method.moe_kernel is kernel
241
assert captured["backend"] == WNA16MoEBackend.HUMMING
@@ -278,7 +280,7 @@ def test_moe_wna16_uses_humming_quant_config(monkeypatch):
280
),
281
)
282
281
- assert method.get_fused_moe_quant_config(layer) is quant_config
283
+ assert method.get_fused_moe_quant_config(layer) is quant_config # type: ignore[arg-type] # Identity sentinel.
284
285
286
@pytest.mark.skipif(
tests/quantization/test_online.py
+15
-14
@@ -16,6 +16,7 @@ from tests.quantization.utils import (
16
_test_online_quant_peak_mem_impl,
17
is_quant_method_supported,
18
load_model_without_vllm_runner,
19
+ quant_config_args,
20
)
21
from vllm import _custom_ops as ops
22
from vllm._aiter_ops import rocm_aiter_ops
@@ -180,7 +181,7 @@ def test_qianfan_online_fp8_keeps_vision_layers_unquantized(
181
config = OnlineQuantizationConfig(args)
182
other_config = OnlineQuantizationConfig(args)
183
model = QianfanOCRForConditionalGeneration.__new__(
183
- QianfanOCRForConditionalGeneration
184
+ QianfanOCRForConditionalGeneration # type: ignore[type-abstract]
185
)
186
model._patch_quant_config(
187
SimpleNamespace(vision_config=SimpleNamespace(num_hidden_layers=2)),
@@ -209,9 +210,9 @@ def test_online_nvfp4_reuses_kernel_when_weights_are_reprocessed(
210
monkeypatch,
211
) -> None:
212
method = object.__new__(Nvfp4OnlineMoEMethod)
212
- method.moe = SimpleNamespace(is_act_and_mul=True)
213
- method.nvfp4_backend = object()
214
- method.experts_cls = object
213
+ method.moe = SimpleNamespace(is_act_and_mul=True) # type: ignore[assignment]
214
+ method.nvfp4_backend = object() # type: ignore[assignment] # Opaque mocked-backend sentinel.
215
+ method.experts_cls = object # type: ignore[assignment] # Opaque mocked-expert sentinel.
216
method.moe_quant_config = None
217
method.moe_kernel = None
218
@@ -239,7 +240,7 @@ def test_online_nvfp4_reuses_kernel_when_weights_are_reprocessed(
240
)
241
make_kernel = Mock(return_value=kernel)
242
get_quant_config = Mock(return_value=quant_config)
242
- method.get_fused_moe_quant_config = get_quant_config
243
+ monkeypatch.setattr(method, "get_fused_moe_quant_config", get_quant_config)
244
245
monkeypatch.setattr(
246
"vllm.model_executor.layers.quantization.online.nvfp4."
@@ -355,7 +356,7 @@ def test_online_prequantized_compatibility(
356
checkpoint_config = checkpoint_config_factory()
357
358
checkpoint_config.online_quantization_config = OnlineQuantizationConfig(
358
- QuantizationConfigArgs(linear="mxfp8")
359
+ quant_config_args(linear="mxfp8")
360
)
361
config = checkpoint_config
362
@@ -408,7 +409,7 @@ def test_online_ignore_keeps_checkpoint_quantization_linear(
409
quant_config = _fully_quantized_quark_config()
410
prefix = "model.layers.0.self_attn.o_proj"
411
quant_config.online_quantization_config = OnlineQuantizationConfig(
411
- QuantizationConfigArgs(linear="mxfp8", ignore=[prefix])
412
+ quant_config_args(linear="mxfp8", ignore=[prefix])
413
)
414
monkeypatch.setattr(
415
quant_config.online_quantization_config,
@@ -437,7 +438,7 @@ def test_online_quantization_rejects_prequantized_moe(
438
prefix = "model.layers.0.mlp.experts"
439
quant_config = _fully_quantized_quark_config()
440
quant_config.online_quantization_config = OnlineQuantizationConfig(
440
- QuantizationConfigArgs(linear="mxfp4", moe="mxfp4")
441
+ quant_config_args(linear="mxfp4", moe="mxfp4")
442
)
443
444
with pytest.raises(ValueError, match="pre-quantized layer"):
@@ -460,7 +461,7 @@ def test_activation_only_override_applies_to_checkpoint_method(
461
model_config = ModelConfig(
462
model=str(tmp_path),
463
quantization="compressed-tensors",
463
- quantization_config=QuantizationConfigArgs(moe={"activation": "mxfp8"}),
464
+ quantization_config=quant_config_args(moe={"activation": "mxfp8"}),
465
hf_overrides={
466
"quantization_config": {
467
"quant_method": "compressed-tensors",
@@ -514,7 +515,7 @@ def test_online_overlay_loads_checkpoint_config_file(tmp_path) -> None:
515
model_config = ModelConfig(
516
model=str(tmp_path),
517
quantization="awq",
517
- quantization_config=QuantizationConfigArgs(linear="mxfp8"),
518
+ quantization_config=quant_config_args(linear="mxfp8"),
519
)
520
521
result = weight_utils.get_quant_config(model_config, LoadConfig())
@@ -574,7 +575,7 @@ def test_online_shorthand_selects_checkpoint_or_online_config(
575
576
def test_log_online_quantization_for_composable_config(monkeypatch) -> None:
577
"""Composable configs log their nested online quantization results."""
577
- online_config = OnlineQuantizationConfig(QuantizationConfigArgs(linear="mxfp8"))
578
+ online_config = OnlineQuantizationConfig(quant_config_args(linear="mxfp8"))
579
online_config.quantized_layers = {
580
"model.layers.0.self_attn.o_proj": ("linear", "mxfp8", None),
581
"model.layers.1.self_attn.o_proj": ("linear", "mxfp8", None),
@@ -1164,7 +1165,7 @@ def test_online_quantization_records_global_config(
1165
default_vllm_config, dist_init
1166
) -> None:
1167
default_vllm_config.model_config = ModelConfig()
1167
- config = OnlineQuantizationConfig(QuantizationConfigArgs(linear="fp8_per_block"))
1168
+ config = OnlineQuantizationConfig(quant_config_args(linear="fp8_per_block"))
1169
prefix = "model.layers.0.self_attn.o_proj"
1170
layer = ColumnParallelLinear(
1171
input_size=1,
@@ -1218,7 +1219,7 @@ def test_online_quantization_targets_reject_unsupported_layer() -> None:
1219
1220
1221
def test_log_online_quantization(default_vllm_config, monkeypatch) -> None:
1221
- config = OnlineQuantizationConfig(QuantizationConfigArgs(linear="fp8_per_tensor"))
1222
+ config = OnlineQuantizationConfig(quant_config_args(linear="fp8_per_tensor"))
1223
config.quantized_layers = {
1224
"model.layers.0.mlp.down_proj": ("linear", "fp8_per_tensor", None),
1225
"model.layers.1.mlp.down_proj": ("linear", "fp8_per_tensor", None),
@@ -1431,7 +1432,7 @@ def test_online_int8_moe_w2_scale_matches_unsharded(monkeypatch) -> None:
1432
layer.w2_weight = torch.nn.Parameter(w2_in, requires_grad=False)
1433
layer.num_experts = layer.local_num_experts = w13.shape[0]
1434
method = SimpleNamespace(moe=SimpleNamespace(tp_size=moe_tp_size))
1434
- Int8OnlineMoEMethod._quantize_weights(method, layer)
1435
+ Int8OnlineMoEMethod._quantize_weights(method, layer) # type: ignore[arg-type]
1436
return layer.w2_weight, layer.w2_scale
1437
1438
full_weight, full_scale = quantize(w2, 1)
tests/quantization/test_online_mxfp4.py
+2
-1
@@ -10,6 +10,7 @@ against the pure-torch reference in `reference_mxfp4.py`.
10
import pytest
11
import torch
12
13
+from vllm.config.kernel import MoEBackend
14
from vllm.config.model import ModelConfig
15
from vllm.model_executor.kernels.linear import _POSSIBLE_MXFP4_KERNELS
16
from vllm.model_executor.kernels.linear.mxfp4.aiter import (
@@ -282,7 +283,7 @@ def test_online_mxfp4_tp_weight_quant_matches_unsharded(tp_size: int):
283
],
284
)
285
def test_online_mxfp4_moe_matches_quark(
285
- moe_backend: str,
286
+ moe_backend: MoEBackend,
287
unpadded_hidden_size: int,
288
unpadded_intermediate_size: int,
289
default_vllm_config,
tests/quantization/test_quantization_config_args.py
+16
-11
@@ -6,6 +6,7 @@ from unittest.mock import Mock
6
7
import pytest
8
9
+from tests.quantization.utils import quant_config_args, quant_spec
10
from vllm.config.quantization import (
11
QUANT_KEY_NAMES,
12
QuantizationConfigArgs,
@@ -30,7 +31,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
31
32
33
def test_quant_spec_resolves_string_to_quant_key():
33
- spec = QuantSpec(weight="mxfp8", activation="fp8_per_token")
34
+ spec = quant_spec(weight="mxfp8", activation="fp8_per_token")
35
assert spec.weight == kMxfp8Dynamic
36
assert spec.activation == kFp8DynamicTokenSym
37
@@ -42,12 +43,12 @@ def test_quant_spec_accepts_quant_key_directly():
43
44
45
def test_quant_spec_string_representation_uses_quantization_name():
45
- assert str(QuantSpec(weight="mxfp4")) == "mxfp4"
46
+ assert str(quant_spec(weight="mxfp4")) == "mxfp4"
47
48
49
def test_quant_spec_rejects_unknown_name():
50
with pytest.raises(ValueError, match="unknown quantization name"):
50
- QuantSpec(weight="not_a_real_format")
51
+ quant_spec(weight="not_a_real_format")
52
53
54
# ---- QuantizationConfigArgs string shorthand on linear/moe ----------------
@@ -55,7 +56,7 @@ def test_quant_spec_rejects_unknown_name():
56
57
def test_args_linear_string_resolves_via_quant_key_names():
58
# A bare QUANT_KEY_NAMES entry desugars to QuantSpec(weight=<key>).
58
- args = QuantizationConfigArgs(linear="fp8_per_block_static")
59
+ args = quant_config_args(linear="fp8_per_block_static")
60
assert args.linear == QuantSpec(weight=kFp8Static128BlockSym)
61
assert args.moe is None
62
@@ -65,7 +66,7 @@ def test_args_moe_string_resolves_via_online_shorthand():
66
# (so `linear: "fp8_per_block"` and `moe: "fp8_per_block"` produce the
67
# same per-layer-kind spec the `--quantization fp8_per_block` shorthand
68
# would).
68
- args = QuantizationConfigArgs(moe="fp8_per_block")
69
+ args = quant_config_args(moe="fp8_per_block")
70
assert args.moe == QuantSpec(weight=kFp8Static128BlockSym)
71
72
@@ -73,11 +74,11 @@ def test_args_string_shorthand_missing_slot_raises():
74
# int8_per_channel_weight_only sets only `moe`; using it on `linear`
75
# has no defined spec and should raise rather than silently no-op.
76
with pytest.raises(ValueError, match="does not define a linear spec"):
76
- QuantizationConfigArgs(linear="int8_per_channel_weight_only")
77
+ quant_config_args(linear="int8_per_channel_weight_only")
78
79
80
def test_args_accepts_dict_form():
80
- args = QuantizationConfigArgs(moe={"activation": "mxfp8"})
81
+ args = quant_config_args(moe={"activation": "mxfp8"})
82
assert args.moe == QuantSpec(weight=None, activation=kMxfp8Dynamic)
83
84
@@ -91,6 +92,7 @@ def test_targets_reject_non_string_keys():
92
93
def test_resolve_shorthand_only_populates_both_slots():
94
args = resolve_quantization_config("fp8_per_block", None)
95
+ assert args is not None
96
assert args.linear == QuantSpec(weight=kFp8Static128BlockSym)
97
assert args.moe == QuantSpec(weight=kFp8Static128BlockSym)
98
@@ -105,6 +107,7 @@ def test_resolve_int8_shorthand_leaves_linear_unset():
107
# int8_per_channel_weight_only is MoE-only; linear stays None so that
108
# OnlineQuantizationConfig leaves Linear layers in full precision.
109
args = resolve_quantization_config("int8_per_channel_weight_only", None)
110
+ assert args is not None
111
assert args.linear is None
112
assert args.moe == QuantSpec(weight=kInt8StaticChannelSym)
113
@@ -113,6 +116,7 @@ def test_resolve_quantization_config_only():
116
# When only `quantization_config` is given (e.g. for an already-quantized
117
# checkpoint that needs an activation override), it's returned as-is.
118
args = resolve_quantization_config(None, {"moe": {"activation": "mxfp8"}})
119
+ assert args is not None
120
assert args.linear is None
121
assert args.moe == QuantSpec(weight=None, activation=kMxfp8Dynamic)
122
@@ -124,13 +128,14 @@ def test_resolve_merges_explicit_over_shorthand():
128
"fp8_per_tensor",
129
{"linear": "fp8_per_block"},
130
)
131
+ assert args is not None
132
assert args.linear == QuantSpec(weight=kFp8Static128BlockSym)
133
assert args.moe == QuantSpec(weight=kFp8StaticTensorSym)
134
135
136
def test_resolve_quantization_config_with_checkpoint_quantization():
137
args = resolve_quantization_config("gptq", {"linear": "fp8_per_block"})
133
- assert args == QuantizationConfigArgs(linear="fp8_per_block")
138
+ assert args == quant_config_args(linear="fp8_per_block")
139
140
141
# ---- QUANT_KEY_NAMES coverage --------------------------------------------
@@ -140,15 +145,15 @@ def test_quant_key_names_round_trip():
145
# Every advertised name should round-trip through QuantSpec without error
146
# and produce the same QuantKey it maps to.
147
for name, expected in QUANT_KEY_NAMES.items():
143
- assert QuantSpec(weight=name).weight == expected, name
144
- assert QuantSpec(activation=name).activation == expected, name
148
+ assert quant_spec(weight=name).weight == expected, name
149
+ assert quant_spec(activation=name).activation == expected, name
150
151
152
def test_static_block_weight_paired_with_dynamic_block_activation():
153
# The block-FP8 shorthand pair: 128x128 static weights + 1x128 dynamic
154
# activations. Pinning this so renames in QUANT_KEY_NAMES don't quietly
155
# rewire the kernel dispatch.
151
- spec = QuantSpec(weight="fp8_per_block_static", activation="fp8_per_block_dynamic")
156
+ spec = quant_spec(weight="fp8_per_block_static", activation="fp8_per_block_dynamic")
157
assert spec.weight == kFp8Static128BlockSym
158
assert spec.activation == kFp8Dynamic128Sym
159
tests/quantization/test_quark.py
+8
-5
@@ -22,7 +22,7 @@ from packaging import version
22
from tests.quantization.utils import load_model_without_vllm_runner
23
from vllm._aiter_ops import is_aiter_found_and_supported, rocm_aiter_ops
24
from vllm.config import VllmConfig, set_current_vllm_config
25
-from vllm.config.cache import CacheConfig
25
+from vllm.config.cache import CacheConfig, CacheDType
26
from vllm.forward_context import set_forward_context
27
from vllm.model_executor import parameter
28
from vllm.model_executor.kernels.linear.scaled_mm.aiter import (
@@ -1081,6 +1081,7 @@ def test_quant_method_dispatch_mxfp8_moe_raises(default_vllm_config):
1081
)
1082
wk, ak, mcls = config.get_quant_method_target("experts", RoutedExperts)
1083
assert mcls is QuarkOCP_MX_MoEMethod
1084
+ assert wk is not None
1085
# The OCP MX MoE constructor should fail loudly for MXFP8.
1086
fake_moe_config = MagicMock()
1087
with pytest.raises(ValueError, match="MXFP8 experts are not supported"):
@@ -1321,7 +1322,6 @@ def test_quark_fp8_ptpc_exposes_kernel_input_quant_key(monkeypatch):
1322
scheme = QuarkW8A8Fp8.__new__(QuarkW8A8Fp8)
1323
scheme.weight_qscheme = "per_channel"
1324
scheme.is_static_input_scheme = False
1324
- scheme.input_qscheme = "per_channel"
1325
scheme.activation_quant_key = kFp8DynamicTokenSym
1326
scheme.weight_quant_key = kFp8StaticChannelSym
1327
scheme.out_dtype = dtype
@@ -1343,7 +1343,7 @@ def test_quark_fp8_ptpc_exposes_kernel_input_quant_key(monkeypatch):
1343
1344
@pytest.mark.parametrize("kv_cache_dtype", ["auto", "fp8"])
1345
def test_quark_fp8_w_per_tensor_a_per_tensor(
1346
- kv_cache_dtype: str, monkeypatch, dist_init, workspace_init
1346
+ kv_cache_dtype: CacheDType, monkeypatch, dist_init, workspace_init
1347
):
1348
model_path = "amd/Llama-3.1-8B-Instruct-FP8-KV-Quark-test"
1349
checkpoint_scales = {}
@@ -2314,7 +2314,7 @@ class TestQuarkInt4Format:
2314
None,
2315
quant_config.quant_config["global_quant_config"]["weight"],
2316
quant_config.pack_method,
2317
- moe_config,
2317
+ moe_config, # type: ignore[arg-type]
2318
)
2319
2320
layer = _FakeLayer(moe_config)
@@ -2325,7 +2325,10 @@ class TestQuarkInt4Format:
2325
torch.zeros(1, 16, 8 // tp_size, dtype=torch.uint8),
2326
requires_grad=False,
2327
)
2328
- loader = method.get_weight_loader(layer, weight_loader=None)
2328
+ loader = method.get_weight_loader(
2329
+ layer, # type: ignore[arg-type]
2330
+ weight_loader=None,
2331
+ )
2332
loader(
2333
param,
2334
raw_zp.clone(),
tests/quantization/test_register_quantization_config.py
+1
-1
@@ -75,7 +75,7 @@ class CustomQuantConfig(QuantizationConfig):
75
76
def get_name(self) -> QuantizationMethods:
77
"""Name of the quantization method."""
78
- return "custom_quant"
78
+ return "custom_quant" # type: ignore[return-value] # Runtime registration extends built-in names.
79
80
def get_supported_act_dtypes(self) -> list[torch.dtype]:
81
"""List of supported activation dtypes."""
tests/quantization/test_torchao.py
+2
@@ -12,6 +12,7 @@ from vllm.config import set_current_vllm_config
12
from vllm.forward_context import set_forward_context
13
from vllm.model_executor.layers.attention import Attention
14
from vllm.model_executor.model_loader import get_model_loader
15
+from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
16
from vllm.platforms import current_platform
17
18
if current_platform.is_rocm():
@@ -126,6 +127,7 @@ def test_online_quant_config_dict_json(vllm_runner, enable_pickle):
127
128
def load_weights(model):
129
model_loader = get_model_loader(load_config)
130
+ assert isinstance(model_loader, DefaultModelLoader)
131
weights_iterator = model_loader.get_all_weights(model_config, model)
132
model.load_weights(weights_iterator)
133
tests/quantization/test_trtllm_nvfp4_hidden_dim_padding.py
+1
-1
@@ -34,7 +34,7 @@ def test_shared_nvfp4_input_scales_have_writable_storage(monkeypatch):
34
35
outputs = prepare_nvfp4_moe_layer_for_fi_or_cutlass(
36
backend=NvFp4MoeBackend.FLASHINFER_CUTLASS,
37
- layer=layer,
37
+ layer=layer, # type: ignore[arg-type]
38
w13=w13,
39
w13_scale=w13_scale,
40
w13_scale_2=weight_scale,
tests/quantization/test_turboquant.py
+1
-1
@@ -300,7 +300,7 @@ class TestTurboQuantKVCacheSpec:
300
301
# The layer builds an unpacked spec; the worker's spec-collection
302
# loop applies TQ slot packing via the backend's customize_spec hook.
303
- spec = Attention.get_kv_cache_spec(layer, vllm_config)
303
+ spec = Attention.get_kv_cache_spec(layer, vllm_config) # type: ignore[arg-type]
304
assert isinstance(spec, FullAttentionSpec)
305
assert spec.kv_quant_mode.is_turboquant
306
assert spec.state_content_bytes is None
tests/quantization/utils.py
+16
-1
@@ -6,18 +6,31 @@ from typing import Any
6
7
import regex as re
8
import torch
9
+from pydantic import TypeAdapter
10
11
from vllm.config import (
12
CompilationConfig,
13
+ CompilationMode,
14
ModelConfig,
15
VllmConfig,
16
set_current_vllm_config,
17
)
18
+from vllm.config.quantization import QuantizationConfigArgs, QuantSpec
19
from vllm.model_executor.layers.quantization import get_quantization_config
20
from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
21
from vllm.platforms import current_platform
22
23
24
+def quant_spec(**kwargs: Any) -> QuantSpec:
25
+ """Build a `QuantSpec` from user-facing names such as `"mxfp8"`."""
26
+ return TypeAdapter(QuantSpec).validate_python(kwargs)
27
+
28
+
29
+def quant_config_args(**kwargs: Any) -> QuantizationConfigArgs:
30
+ """Build `QuantizationConfigArgs` from user-facing shorthand values."""
31
+ return TypeAdapter(QuantizationConfigArgs).validate_python(kwargs)
32
+
33
+
34
def _limit_num_hidden_layers(
35
model: torch.nn.Module, num_hidden_layers: int | None
36
) -> None:
@@ -59,7 +72,9 @@ def load_model_without_vllm_runner(
72
**(model_config_kwargs or {}),
73
)
74
vllm_config_args = dict(vllm_config_kwargs or {})
62
- vllm_config_args.setdefault("compilation_config", CompilationConfig(mode=0))
75
+ vllm_config_args.setdefault(
76
+ "compilation_config", CompilationConfig(mode=CompilationMode.NONE)
77
+ )
78
vllm_config = VllmConfig(model_config=model_config, **vllm_config_args)
79
hf_overrides = (model_config_kwargs or {}).get("hf_overrides") or {}
80
num_hidden_layers = hf_overrides.get("num_hidden_layers")
tests/reasoning/test_base_thinking_reasoning_parser.py
+6
-4
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from tests.reasoning.utils import run_reasoning_extraction
8
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
@@ -39,8 +39,10 @@ REASONING_MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
39
40
41
@pytest.fixture(scope="module")
42
-def test_tokenizer():
43
- tokenizer = AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
42
+def test_tokenizer() -> PreTrainedTokenizerBase:
43
+ tokenizer: PreTrainedTokenizerBase = AutoTokenizer.from_pretrained(
44
+ REASONING_MODEL_NAME
45
+ )
46
# Add custom test tokens
47
test_tokens = ["<test:think>", "</test:think>", "<alt:start>", "<alt:end>"]
48
existing_tokens = set(tokenizer.get_vocab().keys())
@@ -66,7 +68,7 @@ class TestBaseThinkingReasoningParserInit:
68
def test_initialization_with_missing_tokenizer(self):
69
"""Test that initialization fails without tokenizer."""
70
with pytest.raises(ValueError, match="model tokenizer must be passed"):
69
- TestThinkingReasoningParser(None)
71
+ TestThinkingReasoningParser(None) # type: ignore[arg-type]
72
73
def test_initialization_with_missing_tokens(self, test_tokenizer):
74
"""Test that initialization fails when tokens are not in vocabulary."""
tests/reasoning/test_deepseekr1_reasoning_parser.py
+6
-6
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from tests.reasoning.utils import run_reasoning_extraction
8
from vllm.reasoning import ReasoningParser, ReasoningParserManager
@@ -15,7 +15,7 @@ REASONING_MODEL_NAME = "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
15
16
17
@pytest.fixture(scope="module")
18
-def deepseek_r1_qwen_tokenizer():
18
+def deepseek_r1_qwen_tokenizer() -> PreTrainedTokenizerBase:
19
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
20
21
@@ -279,10 +279,10 @@ def test_reasoning(
279
280
# Test extract_content
281
if param_dict["content"] is not None:
282
- content = parser.extract_content_ids(output_ids)
283
- assert content == deepseek_r1_qwen_tokenizer.convert_tokens_to_ids(
282
+ content_ids = parser.extract_content_ids(output_ids)
283
+ assert content_ids == deepseek_r1_qwen_tokenizer.convert_tokens_to_ids(
284
deepseek_r1_qwen_tokenizer.tokenize(param_dict["content"])
285
)
286
else:
287
- content = parser.extract_content_ids(output)
288
- assert content == []
287
+ content_ids = parser.extract_content_ids(output)
288
+ assert content_ids == []
tests/reasoning/test_deepseekv3_reasoning_parser.py
+2
-2
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from vllm.entrypoints.generate.base.protocol import DeltaMessage
8
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
@@ -17,7 +17,7 @@ REASONING_MODEL_NAME = "deepseek-ai/DeepSeek-V3.1"
17
18
19
@pytest.fixture(scope="module")
20
-def tokenizer():
20
+def tokenizer() -> PreTrainedTokenizerBase:
21
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
22
23
tests/reasoning/test_ernie45_reasoning_parser.py
+2
-2
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from tests.reasoning.utils import run_reasoning_extraction
8
from vllm.reasoning import ReasoningParser, ReasoningParserManager
@@ -13,7 +13,7 @@ REASONING_MODEL_NAME = "baidu/ERNIE-4.5-21B-A3B-Thinking"
13
14
15
@pytest.fixture(scope="module")
16
-def ernie45_tokenizer():
16
+def ernie45_tokenizer() -> PreTrainedTokenizerBase:
17
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
18
19
tests/reasoning/test_glm4_moe_reasoning_parser.py
+13
-4
@@ -4,9 +4,11 @@
4
import copy
5
6
import pytest
7
-from transformers import AutoTokenizer
7
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
8
9
from tests.reasoning.utils import run_reasoning_extraction
10
+from vllm.parser.engine.adapters import ParserEngineReasoningAdapter
11
+from vllm.parser.glm47_moe import Glm47MoeParser
12
from vllm.reasoning import ReasoningParser, ReasoningParserManager
13
14
parser_name = "glm45"
@@ -17,7 +19,7 @@ REASONING_MODEL_NAME = "zai-org/GLM-4.7"
19
20
21
@pytest.fixture(scope="module")
20
-def glm45_tokenizer():
22
+def glm45_tokenizer() -> PreTrainedTokenizerBase:
23
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
24
25
@@ -261,13 +263,20 @@ def glm53_style_tokenizer(glm45_tokenizer):
263
return tokenizer
264
265
266
+def _glm_engine(parser: ReasoningParser) -> Glm47MoeParser:
267
+ assert isinstance(parser, ParserEngineReasoningAdapter)
268
+ engine = parser._parser_engine
269
+ assert isinstance(engine, Glm47MoeParser)
270
+ return engine
271
+
272
+
273
@pytest.mark.parametrize(
274
"disable_kwargs", [{"enable_thinking": False}, {"thinking": False}]
275
)
276
def test_glm53_template_forces_reasoning(disable_kwargs: dict, glm53_style_tokenizer):
277
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
278
parser = parser_cls(glm53_style_tokenizer, chat_template_kwargs=disable_kwargs)
270
- assert parser._parser_engine.thinking_enabled
279
+ assert _glm_engine(parser).thinking_enabled
280
281
output = glm53_style_tokenizer.tokenize(GLM53_LEAK["output"])
282
output_tokens: list[str] = [
@@ -298,7 +307,7 @@ def test_glm47_template_honors_thinking_disable(glm45_tokenizer):
307
parser = ReasoningParserManager.get_reasoning_parser(parser_name)(
308
glm45_tokenizer, chat_template_kwargs={"enable_thinking": False}
309
)
301
- assert not parser._parser_engine.thinking_enabled
310
+ assert not _glm_engine(parser).thinking_enabled
311
312
output = glm45_tokenizer.tokenize(GLM53_LEAK["output"])
313
output_tokens: list[str] = [
tests/reasoning/test_granite_thinking_reasoning_parser.py
+22
-21
@@ -1,10 +1,11 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-from typing import TypedDict
4
+from typing import TypedDict, cast
5
6
import pytest
7
import regex as re
8
+from transformers import PreTrainedTokenizerBase
9
10
from tests.reasoning.utils import run_reasoning_extraction
11
from vllm.entrypoints.openai.chat_completion.protocol import (
@@ -52,8 +53,8 @@ class GraniteThinkingTokenizer:
53
54
55
@pytest.fixture
55
-def tokenizer():
56
- return GraniteThinkingTokenizer()
56
+def tokenizer() -> PreTrainedTokenizerBase:
57
+ return cast(PreTrainedTokenizerBase, GraniteThinkingTokenizer())
58
59
60
# ── Basic reasoning extraction (non-streaming + streaming) ───────────
@@ -173,7 +174,7 @@ def tokenizer():
174
],
175
)
176
def test_granite_thinking_reasoning(
176
- tokenizer: GraniteThinkingTokenizer,
177
+ tokenizer: PreTrainedTokenizerBase,
178
streaming: bool,
179
param_dict: ReasoningCase,
180
):
@@ -195,7 +196,7 @@ def test_granite_thinking_reasoning(
196
197
198
def test_granite_thinking_no_content_after_end_token(
198
- tokenizer: GraniteThinkingTokenizer,
199
+ tokenizer: PreTrainedTokenizerBase,
200
):
201
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
202
parser = parser_cls(tokenizer)
@@ -215,7 +216,7 @@ def test_granite_thinking_no_content_after_end_token(
216
217
@pytest.mark.parametrize("streaming", [False, True])
218
def test_granite_thinking_whitespace_only_content(
218
- tokenizer: GraniteThinkingTokenizer,
219
+ tokenizer: PreTrainedTokenizerBase,
220
streaming: bool,
221
):
222
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
@@ -235,7 +236,7 @@ def test_granite_thinking_whitespace_only_content(
236
237
238
def test_granite_thinking_unterminated_think_block(
238
- tokenizer: GraniteThinkingTokenizer,
239
+ tokenizer: PreTrainedTokenizerBase,
240
):
241
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
242
parser = parser_cls(tokenizer)
@@ -260,7 +261,7 @@ def test_granite_thinking_unterminated_think_block(
261
262
263
def test_granite_thinking_disabled_moves_into_content(
263
- tokenizer: GraniteThinkingTokenizer,
264
+ tokenizer: PreTrainedTokenizerBase,
265
):
266
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
267
parser = parser_cls(tokenizer)
@@ -285,7 +286,7 @@ def test_granite_thinking_disabled_moves_into_content(
286
287
288
def test_granite_thinking_disabled_with_leading_newline(
288
- tokenizer: GraniteThinkingTokenizer,
289
+ tokenizer: PreTrainedTokenizerBase,
290
):
291
# With enable_thinking=False, model output goes through the swap
292
# path: content is initially None (all text classified as
@@ -316,7 +317,7 @@ def test_granite_thinking_disabled_with_leading_newline(
317
318
319
def test_granite_thinking_force_nonempty_content_moves_into_content(
319
- tokenizer: GraniteThinkingTokenizer,
320
+ tokenizer: PreTrainedTokenizerBase,
321
):
322
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
323
parser = parser_cls(tokenizer)
@@ -338,7 +339,7 @@ def test_granite_thinking_force_nonempty_content_moves_into_content(
339
340
341
def test_granite_thinking_force_nonempty_no_swap_when_newlines_only(
341
- tokenizer: GraniteThinkingTokenizer,
342
+ tokenizer: PreTrainedTokenizerBase,
343
):
344
# When </think> IS present and content is newlines-only, lstrip
345
# removes them but the swap should NOT fire — content was present,
@@ -363,7 +364,7 @@ def test_granite_thinking_force_nonempty_no_swap_when_newlines_only(
364
365
366
def test_granite_thinking_force_nonempty_swaps_when_content_absent(
366
- tokenizer: GraniteThinkingTokenizer,
367
+ tokenizer: PreTrainedTokenizerBase,
368
):
369
# When </think> IS present but content is truly absent (zero
370
# characters after </think>, e.g. max_tokens cut), swap fires.
@@ -387,7 +388,7 @@ def test_granite_thinking_force_nonempty_swaps_when_content_absent(
388
389
390
def test_granite_thinking_force_nonempty_keeps_real_content(
390
- tokenizer: GraniteThinkingTokenizer,
391
+ tokenizer: PreTrainedTokenizerBase,
392
):
393
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
394
parser = parser_cls(tokenizer)
@@ -412,7 +413,7 @@ def test_granite_thinking_force_nonempty_keeps_real_content(
413
414
415
def test_granite_thinking_keeps_truncated_reasoning(
415
- tokenizer: GraniteThinkingTokenizer,
416
+ tokenizer: PreTrainedTokenizerBase,
417
):
418
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
419
parser = parser_cls(tokenizer)
@@ -472,7 +473,7 @@ def _run_parse_delta(parser, tokenizer, text, request):
473
474
475
def test_granite_thinking_streaming_enable_thinking_false(
475
- tokenizer: GraniteThinkingTokenizer,
476
+ tokenizer: PreTrainedTokenizerBase,
477
):
478
# With enable_thinking=False, the parser (constructed without
479
# kwargs) starts in REASONING state. All text streams as reasoning
@@ -494,7 +495,7 @@ def test_granite_thinking_streaming_enable_thinking_false(
495
496
497
def test_granite_thinking_streaming_strips_leading_newline(
497
- tokenizer: GraniteThinkingTokenizer,
498
+ tokenizer: PreTrainedTokenizerBase,
499
):
500
request = ChatCompletionRequest(
501
model="test-model",
@@ -511,7 +512,7 @@ def test_granite_thinking_streaming_strips_leading_newline(
512
513
514
def test_granite_thinking_streaming_promotes_reasoning_to_content(
514
- tokenizer: GraniteThinkingTokenizer,
515
+ tokenizer: PreTrainedTokenizerBase,
516
):
517
request = ChatCompletionRequest(
518
model="test-model",
@@ -527,7 +528,7 @@ def test_granite_thinking_streaming_promotes_reasoning_to_content(
528
529
530
def test_granite_thinking_streaming_no_promotion_with_real_content(
530
- tokenizer: GraniteThinkingTokenizer,
531
+ tokenizer: PreTrainedTokenizerBase,
532
):
533
request = ChatCompletionRequest(
534
model="test-model",
@@ -548,7 +549,7 @@ def test_granite_thinking_streaming_no_promotion_with_real_content(
549
550
551
def test_granite_thinking_streaming_no_promotion_without_opt_in(
551
- tokenizer: GraniteThinkingTokenizer,
552
+ tokenizer: PreTrainedTokenizerBase,
553
):
554
request = ChatCompletionRequest(model="test-model", messages=[])
555
parser = _make_reasoning_parser(tokenizer)
@@ -564,7 +565,7 @@ def test_granite_thinking_streaming_no_promotion_without_opt_in(
565
566
@pytest.mark.parametrize("streaming", [False, True])
567
def test_granite_thinking_empty_think_block(
567
- tokenizer: GraniteThinkingTokenizer,
568
+ tokenizer: PreTrainedTokenizerBase,
569
streaming: bool,
570
):
571
# <think></think>\nHello — empty reasoning, content after newline.
@@ -592,7 +593,7 @@ def test_granite_thinking_empty_think_block(
593
],
594
)
595
def test_granite_thinking_streaming_chunking_independent(
595
- tokenizer: GraniteThinkingTokenizer,
596
+ tokenizer: PreTrainedTokenizerBase,
597
text: str,
598
expected_reasoning: str,
599
expected_content: str,
tests/reasoning/test_holo2_reasoning_parser.py
+2
-2
@@ -1,7 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import pytest
4
-from transformers import AutoTokenizer
4
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
5
6
from tests.reasoning.utils import run_reasoning_extraction
7
from vllm.reasoning import ReasoningParser, ReasoningParserManager
@@ -15,7 +15,7 @@ REASONING_MODEL_NAME = "HCompany/Holo2-4B"
15
16
17
@pytest.fixture(scope="module")
18
-def tokenizer():
18
+def tokenizer() -> PreTrainedTokenizerBase:
19
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
20
21
tests/reasoning/test_k2_horizon_reasoning_parser.py
+13
-7
@@ -3,6 +3,7 @@
3
4
import string
5
from collections.abc import Sequence
6
+from typing import cast
7
8
import pytest
9
@@ -10,6 +11,7 @@ from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionReque
11
from vllm.parser.parser_manager import ParserManager
12
from vllm.reasoning import ReasoningParserManager
13
from vllm.reasoning.k2_horizon_reasoning_parser import K2HorizonReasoningParser
14
+from vllm.tokenizers import TokenizerLike
15
16
pytestmark = pytest.mark.skip_global_cleanup
17
@@ -59,6 +61,10 @@ class K2Tokenizer:
61
return "".join(inverse_vocab[token_id] for token_id in ids)
62
63
64
+def _k2_tokenizer() -> TokenizerLike:
65
+ return cast(TokenizerLike, K2Tokenizer())
66
+
67
+
68
def _request(**kwargs) -> ChatCompletionRequest:
69
return ChatCompletionRequest(
70
model="k2-horizon",
@@ -70,7 +76,7 @@ def _request(**kwargs) -> ChatCompletionRequest:
76
@pytest.mark.parametrize("effort", ["high", "medium", "low"])
77
def test_reasoning_effort_selects_tokens(effort: str):
78
parser = K2HorizonReasoningParser(
73
- K2Tokenizer(),
79
+ _k2_tokenizer(),
80
chat_template_kwargs={"reasoning_effort": effort},
81
)
82
@@ -78,7 +84,7 @@ def test_reasoning_effort_selects_tokens(effort: str):
84
85
86
def test_reasoning_effort_precedence_and_default():
81
- tokenizer = K2Tokenizer()
87
+ tokenizer = _k2_tokenizer()
88
request = _request(
89
reasoning_effort="medium",
90
chat_template_kwargs={"reasoning_effort": "low"},
@@ -103,13 +109,13 @@ def test_reasoning_effort_precedence_and_default():
109
def test_invalid_reasoning_effort_raises(effort: str | None):
110
with pytest.raises(ValueError, match="Unsupported reasoning_effort"):
111
K2HorizonReasoningParser(
106
- K2Tokenizer(),
112
+ _k2_tokenizer(),
113
chat_template_kwargs={"reasoning_effort": effort},
114
)
115
116
117
def test_non_streaming_explicit_and_implicit_boundaries():
112
- parser = K2HorizonReasoningParser(K2Tokenizer())
118
+ parser = K2HorizonReasoningParser(_k2_tokenizer())
119
request = _request()
120
121
assert parser.extract_reasoning("<ifm|think>plan</ifm|think>answer", request) == (
@@ -126,7 +132,7 @@ def test_non_streaming_explicit_and_implicit_boundaries():
132
133
@pytest.mark.parametrize("reasoning", ["plan", " ", "\n", " \n"])
134
def test_character_split_reasoning_stream(reasoning: str):
129
- parser = K2HorizonReasoningParser(K2Tokenizer())
135
+ parser = K2HorizonReasoningParser(_k2_tokenizer())
136
reasoning_parts: list[str] = []
137
content_parts: list[str] = []
138
@@ -150,7 +156,7 @@ def test_character_split_reasoning_stream(reasoning: str):
156
157
158
def test_latest_boundary_controls_reasoning_state():
153
- tokenizer = K2Tokenizer()
159
+ tokenizer = _k2_tokenizer()
160
parser = K2HorizonReasoningParser(tokenizer)
161
vocab = tokenizer.get_vocab()
162
@@ -159,7 +165,7 @@ def test_latest_boundary_controls_reasoning_state():
165
166
167
def test_composed_streaming_reasoning_to_tool_handoff():
162
- tokenizer = K2Tokenizer()
168
+ tokenizer = _k2_tokenizer()
169
tools = [
170
{
171
"type": "function",
tests/reasoning/test_kimi_k2_reasoning_parser.py
+13
-4
@@ -6,6 +6,7 @@ import pytest
6
7
from vllm.entrypoints.generate.base.protocol import DeltaMessage
8
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
9
+from vllm.parser.kimi_k2 import KimiK2Parser
10
from vllm.reasoning.kimi_k2_reasoning_parser import KimiK2ReasoningParser
11
from vllm.tokenizers import get_tokenizer
12
@@ -65,9 +66,13 @@ def test_extract_reasoning_tool_section_ends_reasoning(kimi_k2_tokenizer):
66
def test_streaming_reasoning_then_content(kimi_k2_tokenizer):
67
"""Token-by-token streaming: reasoning tokens then content after </think>."""
68
parser = KimiK2ReasoningParser(kimi_k2_tokenizer)
69
+ parser_engine = parser._parser_engine
70
+ assert isinstance(parser_engine, KimiK2Parser)
71
69
- think_id = parser._parser_engine._start_token_id
70
- end_think_id = parser._parser_engine._end_token_id
72
+ think_id = parser_engine._start_token_id
73
+ end_think_id = parser_engine._end_token_id
74
+ assert think_id is not None
75
+ assert end_think_id is not None
76
# Use a real token ID from the tokenizer for regular content
77
regular_id = kimi_k2_tokenizer.encode("hello", add_special_tokens=False)[0]
78
@@ -123,9 +128,13 @@ def test_streaming_reasoning_then_content(kimi_k2_tokenizer):
128
def test_streaming_tool_section_ends_reasoning(kimi_k2_tokenizer):
129
"""<|tool_calls_section_begin|> in delta ends reasoning during streaming."""
130
parser = KimiK2ReasoningParser(kimi_k2_tokenizer)
131
+ parser_engine = parser._parser_engine
132
+ assert isinstance(parser_engine, KimiK2Parser)
133
127
- think_id = parser._parser_engine._start_token_id
128
- tool_begin_id = parser._parser_engine._tool_section_start_token_id
134
+ think_id = parser_engine._start_token_id
135
+ tool_begin_id = parser_engine._tool_section_start_token_id
136
+ assert think_id is not None
137
+ assert tool_begin_id is not None
138
regular_id = kimi_k2_tokenizer.encode("hello", add_special_tokens=False)[0]
139
140
# Tool section token arrives — should transition from reasoning to content
tests/reasoning/test_kimi_k3_reasoning_parser.py
+15
-4
@@ -1,6 +1,8 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
+from typing import cast
5
+
6
import pytest
7
8
from vllm.entrypoints.generate.base.protocol import DeltaMessage
@@ -10,6 +12,7 @@ from vllm.entrypoints.openai.chat_completion.protocol import (
12
from vllm.parser.kimi_k3 import KimiK3Parser
13
from vllm.parser.parser_manager import ParserManager
14
from vllm.reasoning.kimi_k3_reasoning_parser import KimiK3ReasoningParser
15
+from vllm.tokenizers import TokenizerLike
16
17
pytestmark = pytest.mark.skip_global_cleanup
18
@@ -25,10 +28,13 @@ RESPONSE_OPEN_IDS = [ord(ch) for ch in RESPONSE_OPEN]
28
29
30
class DummyTokenizer:
31
+ def __len__(self) -> int:
32
+ return 1
33
+
34
def get_vocab(self) -> dict[str, int]:
35
return {}
36
31
- def encode(self, text: str, add_special_tokens: bool = False) -> list[int]:
37
+ def encode(self, text: str, *args, **kwargs) -> list[int]:
38
if text == THINK_OPEN:
39
return [1, 2, 3]
40
if text == THINK_CLOSE:
@@ -36,6 +42,10 @@ class DummyTokenizer:
42
return [ord(ch) for ch in text]
43
44
45
+def _dummy_tokenizer() -> TokenizerLike:
46
+ return cast(TokenizerLike, DummyTokenizer())
47
+
48
+
49
class ReasoningOnlyParser(KimiK3Parser):
50
reasoning_parser_cls = KimiK3ReasoningParser
51
@@ -84,7 +94,7 @@ def test_extract_reasoning_with_generation_prefix_consumed():
94
95
96
def test_delegating_parser_strips_response_wrapper_without_tool_parser():
87
- parser = ReasoningOnlyParser(DummyTokenizer())
97
+ parser = ReasoningOnlyParser(_dummy_tokenizer())
98
request = ChatCompletionRequest(model="test-model", messages=[])
99
100
reasoning, content, tool_calls = parser.parse(
@@ -165,7 +175,7 @@ def test_count_reasoning_tokens_is_zero_when_thinking_disabled():
175
176
177
def test_count_reasoning_tokens_through_delegating_parser():
168
- parser = ReasoningOnlyParser(DummyTokenizer())
178
+ parser = ReasoningOnlyParser(_dummy_tokenizer())
179
180
assert parser.count_reasoning_tokens([*OPEN_IDS, 9, *CLOSE_IDS, 11]) == 1
181
@@ -253,7 +263,8 @@ def test_thinking_disabled_streams_content():
263
264
def test_delegating_parser_thinking_false_streams_response_content():
265
parser = ReasoningOnlyParser(
256
- DummyTokenizer(), chat_template_kwargs={"thinking": False}
266
+ _dummy_tokenizer(),
267
+ chat_template_kwargs={"thinking": False},
268
)
269
request = ChatCompletionRequest(
270
model="test-model",
tests/reasoning/test_minimax_m2_append_reasoning_parser.py
+2
-2
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from tests.reasoning.utils import run_reasoning_extraction
8
from vllm.reasoning import ReasoningParser, ReasoningParserManager
@@ -15,7 +15,7 @@ REASONING_MODEL_NAME = "MiniMaxAI/MiniMax-M2"
15
16
17
@pytest.fixture(scope="module")
18
-def minimax_m2_tokenizer():
18
+def minimax_m2_tokenizer() -> PreTrainedTokenizerBase:
19
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
20
21
tests/reasoning/test_minimax_m2_reasoning_parser.py
+6
-6
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from tests.reasoning.utils import run_reasoning_extraction
8
from vllm.reasoning import ReasoningParser, ReasoningParserManager
@@ -15,7 +15,7 @@ REASONING_MODEL_NAME = "MiniMaxAI/MiniMax-M2"
15
16
17
@pytest.fixture(scope="module")
18
-def minimax_m2_tokenizer():
18
+def minimax_m2_tokenizer() -> PreTrainedTokenizerBase:
19
return AutoTokenizer.from_pretrained(REASONING_MODEL_NAME)
20
21
@@ -195,10 +195,10 @@ def test_reasoning(
195
196
# Test extract_content
197
if param_dict["content"] is not None:
198
- content = parser.extract_content_ids(output_ids)
199
- assert content == minimax_m2_tokenizer.convert_tokens_to_ids(
198
+ content_ids = parser.extract_content_ids(output_ids)
199
+ assert content_ids == minimax_m2_tokenizer.convert_tokens_to_ids(
200
minimax_m2_tokenizer.tokenize(param_dict["content"])
201
)
202
else:
203
- content = parser.extract_content_ids(output)
204
- assert content == []
203
+ content_ids = parser.extract_content_ids(output)
204
+ assert content_ids == []
tests/reasoning/test_nemotron_v3_reasoning_parser.py
+12
-11
@@ -1,10 +1,11 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-from typing import TypedDict
4
+from typing import TypedDict, cast
5
6
import pytest
7
import regex as re
8
+from transformers import PreTrainedTokenizerBase
9
10
from tests.reasoning.utils import run_reasoning_extraction
11
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
@@ -48,8 +49,8 @@ class FakeNemotronTokenizer:
49
50
51
@pytest.fixture
51
-def tokenizer():
52
- return FakeNemotronTokenizer()
52
+def tokenizer() -> PreTrainedTokenizerBase:
53
+ return cast(PreTrainedTokenizerBase, FakeNemotronTokenizer())
54
55
56
@pytest.mark.parametrize(
@@ -94,7 +95,7 @@ def tokenizer():
95
],
96
)
97
def test_nemotron_v3_reasoning(
97
- tokenizer: FakeNemotronTokenizer,
98
+ tokenizer: PreTrainedTokenizerBase,
99
streaming: bool,
100
param_dict: ReasoningCase,
101
):
@@ -113,7 +114,7 @@ def test_nemotron_v3_reasoning(
114
115
116
def test_nemotron_v3_without_thinking_moves_into_content(
116
- tokenizer: FakeNemotronTokenizer,
117
+ tokenizer: PreTrainedTokenizerBase,
118
):
119
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
120
parser = parser_cls(tokenizer)
@@ -137,7 +138,7 @@ def test_nemotron_v3_without_thinking_moves_into_content(
138
139
140
def test_nemotron_v3_force_nonempty_content_moves_into_content(
140
- tokenizer: FakeNemotronTokenizer,
141
+ tokenizer: PreTrainedTokenizerBase,
142
):
143
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
144
parser = parser_cls(tokenizer)
@@ -159,7 +160,7 @@ def test_nemotron_v3_force_nonempty_content_moves_into_content(
160
161
162
def test_nemotron_v3_force_nonempty_keeps_real_content(
162
- tokenizer: FakeNemotronTokenizer,
163
+ tokenizer: PreTrainedTokenizerBase,
164
):
165
# When real content follows the closing tag nothing is promoted: the
166
# content after </think> is returned as-is and reasoning stays separate.
@@ -183,7 +184,7 @@ def test_nemotron_v3_force_nonempty_keeps_real_content(
184
185
186
def test_nemotron_v3_with_thinking_keeps_truncated_reasoning(
186
- tokenizer: FakeNemotronTokenizer,
187
+ tokenizer: PreTrainedTokenizerBase,
188
):
189
parser_cls = ReasoningParserManager.get_reasoning_parser(parser_name)
190
parser = parser_cls(tokenizer)
@@ -242,7 +243,7 @@ def _run_parse_delta(parser, tokenizer, text, request):
243
244
245
def test_nemotron_v3_streaming_promotes_reasoning_to_content(
245
- tokenizer: FakeNemotronTokenizer,
246
+ tokenizer: PreTrainedTokenizerBase,
247
):
248
# Model never closes <think>: reasoning streams normally AND is duplicated
249
# into content on the terminal delta.
@@ -260,7 +261,7 @@ def test_nemotron_v3_streaming_promotes_reasoning_to_content(
261
262
263
def test_nemotron_v3_streaming_no_promotion_with_real_content(
263
- tokenizer: FakeNemotronTokenizer,
264
+ tokenizer: PreTrainedTokenizerBase,
265
):
266
request = ChatCompletionRequest(
267
model="test-model",
@@ -279,7 +280,7 @@ def test_nemotron_v3_streaming_no_promotion_with_real_content(
280
281
282
def test_nemotron_v3_streaming_no_promotion_without_opt_in(
282
- tokenizer: FakeNemotronTokenizer,
283
+ tokenizer: PreTrainedTokenizerBase,
284
):
285
# Without enable_thinking=False / force_nonempty_content the fallback must
286
# stay disabled: the response stays reasoning-only, content empty.
tests/reasoning/test_qwen3_reasoning_parser.py
+2
-2
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import pytest
5
-from transformers import AutoTokenizer
5
+from transformers import AutoTokenizer, PreTrainedTokenizerBase
6
7
from tests.reasoning.utils import (
8
StreamingReasoningReconstructor,
@@ -24,7 +24,7 @@ REASONING_MODEL_NAMES = [
24
25
26
@pytest.fixture(scope="module", params=REASONING_MODEL_NAMES)
27
-def qwen3_tokenizer(request):
27
+def qwen3_tokenizer(request) -> PreTrainedTokenizerBase:
28
return AutoTokenizer.from_pretrained(request.param)
29
30
tests/reasoning/test_step3p5_reasoning_parser.py
+8
-8
@@ -4,7 +4,8 @@
4
import pytest
5
6
from tests.reasoning.utils import run_reasoning_extraction
7
-from vllm.reasoning import ReasoningParser, ReasoningParserManager
7
+from vllm.reasoning import ReasoningParserManager
8
+from vllm.reasoning.step3p5_reasoning_parser import Step3p5ReasoningParser
9
from vllm.tokenizers import get_tokenizer
10
11
parser_name = "step3p5"
@@ -283,9 +284,8 @@ def test_reasoning(
284
output_tokens: list[str] = [
285
step3p5_tokenizer.convert_tokens_to_string([token]) for token in output
286
]
286
- parser: ReasoningParser = ReasoningParserManager.get_reasoning_parser(parser_name)(
287
- step3p5_tokenizer
288
- )
287
+ parser = ReasoningParserManager.get_reasoning_parser(parser_name)(step3p5_tokenizer)
288
+ assert isinstance(parser, Step3p5ReasoningParser)
289
290
reasoning, content = run_reasoning_extraction(
291
parser, output_tokens, streaming=streaming
@@ -317,7 +317,7 @@ def test_reasoning(
317
318
# Test extract_content
319
if param_dict["content"] is not None:
320
- content = parser.extract_content_ids(output_ids)
320
+ content_ids = parser.extract_content_ids(output_ids)
321
# Fixed expected token ids for specific test cases
322
test_id = (
323
request.node.callspec.id if hasattr(request.node, "callspec") else None
@@ -332,7 +332,7 @@ def test_reasoning(
332
expected_content_ids = step3p5_tokenizer.convert_tokens_to_ids(
333
step3p5_tokenizer.tokenize(param_dict["content"])
334
)
335
- assert content == expected_content_ids
335
+ assert content_ids == expected_content_ids
336
else:
337
- content = parser.extract_content_ids(output_ids)
338
- assert content == []
337
+ content_ids = parser.extract_content_ids(output_ids)
338
+ assert content_ids == []
tests/reasoning/utils.py
+7
-2
@@ -1,6 +1,10 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
+from typing import cast
5
+
6
+from transformers import PreTrainedTokenizerBase
7
+
8
from vllm.entrypoints.generate.base.protocol import DeltaMessage
9
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
10
from vllm.reasoning import ReasoningParser
@@ -102,10 +106,11 @@ def run_reasoning_extraction_streaming(
106
reconstructor = StreamingReasoningReconstructor()
107
previous_text = ""
108
previous_tokens: list[int] = []
109
+ tokenizer = cast(PreTrainedTokenizerBase, reasoning_parser.model_tokenizer)
110
for delta in model_deltas:
111
token_delta = [
107
- reasoning_parser.vocab.get(token)
108
- for token in reasoning_parser.model_tokenizer.tokenize(delta)
112
+ reasoning_parser.vocab[token]
113
+ for token in tokenizer.tokenize(delta)
114
if token in reasoning_parser.vocab
115
]
116
current_text = previous_text + delta
tests/v1/logits_processors/test_correctness.py
+54
-71
@@ -3,7 +3,7 @@
3
4
import random
5
from collections.abc import Callable
6
-from typing import NamedTuple, TypeAlias
6
+from typing import NamedTuple, NoReturn, TypeAlias
7
8
import numpy as np
9
import pytest
@@ -13,6 +13,7 @@ from tests.utils import create_new_process_for_each_test
13
from tests.v1.sample.utils import (
14
LogitsprocsTestFakes,
15
create_fake_logits,
16
+ create_mock_reasoning_config,
17
create_penalty_tensor,
18
create_prompt_tokens_tensor,
19
fake_apply_logitsprocs,
@@ -103,14 +104,6 @@ class LogitsProcsRequestParams:
104
return f"MyClass({summ})"
105
106
106
-class MockReasoningConfig:
107
- """Minimal reasoning config for ``ThinkingBudgetStateHolder`` tests."""
108
-
109
- reasoning_start_token_ids = [THINK_START_TOKEN_ID]
110
- reasoning_end_token_ids = [THINK_END_TOKEN_ID]
111
- enabled = True
112
-
113
-
107
def _generate_fake_sampling_metadata(
108
num_output_tokens: int,
109
batch_size: int,
@@ -131,7 +124,9 @@ def _generate_fake_sampling_metadata(
124
)
125
126
vllm_config = VllmConfig()
134
- vllm_config.reasoning_config = MockReasoningConfig()
127
+ vllm_config.reasoning_config = create_mock_reasoning_config(
128
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
129
+ )
130
131
logitsprocs = build_logitsprocs(
132
vllm_config=vllm_config,
@@ -242,7 +237,7 @@ def _raise_error_invalid(
237
request_params: LogitsProcsRequestParams,
238
step_idx: int,
239
err_cls: type[Exception] = ValueError,
245
-) -> None:
240
+) -> NoReturn:
241
raise err_cls(
242
f"Validation failed for step={step_idx}, "
243
f"batch_index={batch_index}, "
@@ -268,6 +263,7 @@ def _logit_bias_validate(
263
) -> None:
264
"""Validate logit bias logitproc applied correctly."""
265
logit_bias = request_params.params.logit_bias
266
+ assert logit_bias is not None
267
logits_old = test_fakes.logits[persistent_batch[batch_index].workload_index].cpu()
268
logits_new = logits_new[batch_index].cpu()
269
for token_id in range(VOCAB_SIZE):
@@ -368,9 +364,7 @@ def _min_tokens_validate(
364
ref_num_out_tokens = len(request_params.out_tokens)
365
min_reached = ref_num_out_tokens >= MIN_TOKENS_LEN_THRESHOLD
366
ref_all_stop_token_ids = request_params.params.all_stop_token_ids
371
- mt_lp: MinTokensLogitsProcessor = next(
372
- test_fakes.get_logitsprocs_by_cls(MinTokensLogitsProcessor)
373
- )
367
+ mt_lp = next(test_fakes.get_logitsprocs_by_cls(MinTokensLogitsProcessor))
368
assert isinstance(mt_lp, MinTokensLogitsProcessor)
369
min_tok = mt_lp.min_toks.get(batch_index, None)
370
@@ -689,7 +683,7 @@ class LogitsprocTestHelpers(NamedTuple):
683
gen_request_fxn: Callable | None = None
684
685
692
-logitsprocs_test_mapping = {
686
+logitsprocs_test_mapping: dict[LogitprocType, LogitsprocTestHelpers] = {
687
STR_NO_LOGITPROC: LogitsprocTestHelpers(eval_fxn=_none_validate),
688
LogitBiasLogitsProcessor: LogitsprocTestHelpers(
689
gen_request_fxn=_logit_bias_params, eval_fxn=_logit_bias_validate
@@ -706,22 +700,22 @@ logitsprocs_test_mapping = {
700
}
701
702
709
-def _get_test_cases() -> list[list[str]]:
703
+def _get_test_cases() -> list[list[LogitprocType]]:
704
"""Each test case is a set of logitsprocs."""
711
- logitsprocs_types = list(logitsprocs_test_mapping.keys())
705
+ logitsprocs_types: list[LogitprocType] = list(logitsprocs_test_mapping.keys())
706
707
# Isolate thinking-budget handling from other processors to avoid cross-talk.
708
thinking_id: LogitprocType = STR_THINKING_BUDGET
715
- other_processors = [
709
+ other_processors: list[LogitprocType] = [
710
p for p in logitsprocs_types if p != STR_NO_LOGITPROC and p != thinking_id
711
]
712
719
- return (
720
- [[STR_NO_LOGITPROC]]
721
- + [[logitproc_type, STR_NO_LOGITPROC] for logitproc_type in other_processors]
722
- + [other_processors]
723
- + [[thinking_id]]
713
+ test_cases: list[list[LogitprocType]] = [[STR_NO_LOGITPROC]]
714
+ test_cases.extend(
715
+ [logitproc_type, STR_NO_LOGITPROC] for logitproc_type in other_processors
716
)
717
+ test_cases.extend([other_processors, [thinking_id]])
718
+ return test_cases
719
720
721
def _generate_fake_step_update(
@@ -774,6 +768,7 @@ def _generate_fake_step_update(
768
for add_req_params in workload_params[wdx : (wdx + num_step_add_replace)]:
769
# Replace as many removed requests as possible with added requests
770
add_remove_idx = batch_update_builder.pop_removed()
771
+ assert add_remove_idx is not None
772
batch_update_builder.added.append(
773
(
774
add_remove_idx,
@@ -976,13 +971,6 @@ def test_logitsprocs(
971
step_idx += 1
972
973
979
-class MockReasoningNoEndTokens:
980
- """Reasoning config with no end token ids (disables enforcement in holder)."""
981
-
982
- reasoning_start_token_ids = [THINK_START_TOKEN_ID]
983
- reasoning_end_token_ids: list[int] = []
984
-
985
-
974
def test_maybe_create_thinking_budget_holder_without_reasoning():
975
cfg = VllmConfig()
976
assert cfg.reasoning_config is None
@@ -1000,7 +988,9 @@ def test_maybe_create_thinking_budget_holder_without_reasoning():
988
989
def test_thinking_budget_holder_has_tracked_after_sync_add():
990
vc = VllmConfig()
1003
- vc.reasoning_config = MockReasoningConfig()
991
+ vc.reasoning_config = create_mock_reasoning_config(
992
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
993
+ )
994
h = ThinkingBudgetStateHolder(
995
vc.reasoning_config,
996
vc.scheduler_config.max_num_seqs,
@@ -1030,7 +1020,9 @@ def test_thinking_budget_holder_has_tracked_after_sync_add():
1020
1021
def test_thinking_budget_holder_sync_remove_clears_state():
1022
vc = VllmConfig()
1033
- vc.reasoning_config = MockReasoningConfig()
1023
+ vc.reasoning_config = create_mock_reasoning_config(
1024
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
1025
+ )
1026
h = ThinkingBudgetStateHolder(
1027
vc.reasoning_config,
1028
vc.scheduler_config.max_num_seqs,
@@ -1060,7 +1052,9 @@ def test_thinking_budget_holder_sync_remove_clears_state():
1052
1053
def test_thinking_budget_holder_sync_add_without_budget_drops_row():
1054
vc = VllmConfig()
1063
- vc.reasoning_config = MockReasoningConfig()
1055
+ vc.reasoning_config = create_mock_reasoning_config(
1056
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
1057
+ )
1058
h = ThinkingBudgetStateHolder(
1059
vc.reasoning_config,
1060
vc.scheduler_config.max_num_seqs,
@@ -1081,7 +1075,9 @@ def test_thinking_budget_holder_sync_add_without_budget_drops_row():
1075
1076
def test_thinking_budget_holder_swap_exchanges_state():
1077
vc = VllmConfig()
1084
- vc.reasoning_config = MockReasoningConfig()
1078
+ vc.reasoning_config = create_mock_reasoning_config(
1079
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
1080
+ )
1081
h = ThinkingBudgetStateHolder(
1082
vc.reasoning_config,
1083
vc.scheduler_config.max_num_seqs,
@@ -1125,7 +1121,9 @@ def test_thinking_budget_holder_swap_exchanges_state():
1121
1122
def test_thinking_budget_holder_unidirectional_move():
1123
vc = VllmConfig()
1128
- vc.reasoning_config = MockReasoningConfig()
1124
+ vc.reasoning_config = create_mock_reasoning_config(
1125
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
1126
+ )
1127
h = ThinkingBudgetStateHolder(
1128
vc.reasoning_config,
1129
vc.scheduler_config.max_num_seqs,
@@ -1163,7 +1161,9 @@ def test_thinking_budget_holder_unidirectional_move():
1161
1162
def test_thinking_budget_holder_update_state_repeat_indices_last_row_wins():
1163
vc = VllmConfig()
1166
- vc.reasoning_config = MockReasoningConfig()
1164
+ vc.reasoning_config = create_mock_reasoning_config(
1165
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
1166
+ )
1167
h = ThinkingBudgetStateHolder(
1168
vc.reasoning_config,
1169
vc.scheduler_config.max_num_seqs,
@@ -1197,7 +1197,7 @@ def test_thinking_budget_holder_update_state_repeat_indices_last_row_wins():
1197
1198
def test_thinking_budget_holder_spec_mode_tensor_layout():
1199
h = ThinkingBudgetStateHolder(
1200
- MockReasoningConfig(),
1200
+ create_mock_reasoning_config([THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]),
1201
8,
1202
2,
1203
torch.device("cpu"),
@@ -1209,7 +1209,7 @@ def test_thinking_budget_holder_spec_mode_tensor_layout():
1209
1210
def test_thinking_budget_holder_empty_end_tokens_disables_row():
1211
vc = VllmConfig()
1212
- vc.reasoning_config = MockReasoningNoEndTokens()
1212
+ vc.reasoning_config = create_mock_reasoning_config([THINK_START_TOKEN_ID], [])
1213
h = ThinkingBudgetStateHolder(
1214
vc.reasoning_config,
1215
vc.scheduler_config.max_num_seqs,
@@ -1253,7 +1253,9 @@ def test_thinking_budget_enforced_without_penalties():
1253
passing an empty list (the pre-fix behavior) prevents budget enforcement.
1254
"""
1255
vc = VllmConfig()
1256
- vc.reasoning_config = MockReasoningConfig()
1256
+ vc.reasoning_config = create_mock_reasoning_config(
1257
+ [THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]
1258
+ )
1259
budget = 3 # allow 3 thinking tokens
1260
1261
h = ThinkingBudgetStateHolder(
@@ -1346,7 +1348,11 @@ def test_thinking_budget_long_thinking_section_end_marker_found_at_correct_index
1348
"""Test thinking budget enforced for a long thinking run,
1349
then a natural end marker."""
1350
h = ThinkingBudgetStateHolder(
1349
- MockReasoningConfig(), 8, 0, torch.device("cpu"), False
1351
+ create_mock_reasoning_config([THINK_START_TOKEN_ID], [THINK_END_TOKEN_ID]),
1352
+ 8,
1353
+ 0,
1354
+ torch.device("cpu"),
1355
+ False,
1356
)
1357
h.sync_batch(
1358
BatchUpdate(
@@ -1356,8 +1362,8 @@ def test_thinking_budget_long_thinking_section_end_marker_found_at_correct_index
1362
moved=(),
1363
)
1364
)
1359
- start = MockReasoningConfig.reasoning_start_token_ids
1360
- end = MockReasoningConfig.reasoning_end_token_ids
1365
+ start = [THINK_START_TOKEN_ID]
1366
+ end = [THINK_END_TOKEN_ID]
1367
1368
out: list[int] = list(start)
1369
h.update_state([out], None, None)
@@ -1396,13 +1402,9 @@ class TestThinkingBudgetReentry:
1402
1403
@staticmethod
1404
def _make_holder(end_token_ids: list[int]) -> ThinkingBudgetStateHolder:
1399
- class FakeReasoningConfig:
1400
- reasoning_start_token_ids = [TestThinkingBudgetReentry.THINK_START]
1401
- reasoning_end_token_ids: list[int] = []
1402
- enabled = True
1403
-
1404
- cfg = FakeReasoningConfig()
1405
- cfg.reasoning_end_token_ids = end_token_ids
1405
+ cfg = create_mock_reasoning_config(
1406
+ [TestThinkingBudgetReentry.THINK_START], end_token_ids
1407
+ )
1408
return ThinkingBudgetStateHolder(
1409
reasoning_config=cfg,
1410
max_num_seqs=8,
@@ -1526,21 +1528,12 @@ class TestThinkingBudgetNaturalEndReentry:
1528
1529
@staticmethod
1530
def _make_holder(end_token_ids):
1529
- from dataclasses import dataclass
1530
-
1531
from vllm.v1.sample.thinking_budget_state import (
1532
ThinkingBudgetStateHolder,
1533
)
1534
1535
- @dataclass
1536
- class FakeReasoningConfig:
1537
- reasoning_start_token_ids: list[int]
1538
- reasoning_end_token_ids: list[int]
1539
- enabled: bool = True
1540
-
1541
- cfg = FakeReasoningConfig(
1542
- reasoning_start_token_ids=[TestThinkingBudgetNaturalEndReentry.THINK_START],
1543
- reasoning_end_token_ids=end_token_ids,
1535
+ cfg = create_mock_reasoning_config(
1536
+ [TestThinkingBudgetNaturalEndReentry.THINK_START], end_token_ids
1537
)
1538
return ThinkingBudgetStateHolder(
1539
reasoning_config=cfg,
@@ -1830,21 +1823,11 @@ class TestThinkingBudgetNaturalEndReentry:
1823
natural </think> exit, continue_thinking is cleared and Block 2
1824
enforcement works correctly.
1825
"""
1833
- from dataclasses import dataclass
1826
from unittest.mock import MagicMock
1827
1828
from vllm.v1.sample.thinking_budget_state import ThinkingBudgetStateHolder
1829
1838
- @dataclass
1839
- class FakeReasoningConfig:
1840
- reasoning_start_token_ids: list[int]
1841
- reasoning_end_token_ids: list[int]
1842
- enabled: bool = True
1843
-
1844
- cfg = FakeReasoningConfig(
1845
- reasoning_start_token_ids=[self.THINK_START],
1846
- reasoning_end_token_ids=self.THINK_END_SINGLE,
1847
- )
1830
+ cfg = create_mock_reasoning_config([self.THINK_START], self.THINK_END_SINGLE)
1831
holder = ThinkingBudgetStateHolder(
1832
reasoning_config=cfg,
1833
max_num_seqs=8,
tests/v1/logits_processors/utils.py
+10
-6
@@ -57,15 +57,20 @@ prompts = [
57
class DummyLogitsProcessor(LogitsProcessor):
58
"""Fake logit processor to support unit testing and examples."""
59
60
- @classmethod
61
- def validate_params(cls, params: SamplingParams):
62
- target_token: int | None = params.extra_args and params.extra_args.get(
63
- "target_token"
60
+ @staticmethod
61
+ def _get_target_token(params: SamplingParams) -> int | None:
62
+ target_token = (
63
+ params.extra_args.get(DUMMY_LOGITPROC_ARG) if params.extra_args else None
64
)
65
if target_token is not None and not isinstance(target_token, int):
66
raise VLLMValidationError(
67
f"target_token value {target_token} {type(target_token)} is not int"
68
)
69
+ return target_token
70
+
71
+ @classmethod
72
+ def validate_params(cls, params: SamplingParams):
73
+ cls._get_target_token(params)
74
75
def __init__(
76
self, vllm_config: "VllmConfig", device: torch.device, is_pin_memory: bool
@@ -78,8 +83,7 @@ class DummyLogitsProcessor(LogitsProcessor):
83
84
def update_state(self, batch_update: BatchUpdate | None):
85
def extract_extra_arg(params: SamplingParams) -> int | None:
81
- self.validate_params(params)
82
- return params.extra_args and params.extra_args.get("target_token")
86
+ return self._get_target_token(params)
87
88
process_dict_updates(
89
self.req_info,
tests/v1/sample/test_diffusion_gemma_reads.py
+1
@@ -341,6 +341,7 @@ def test_batch_allowed_needs_one_shared_set():
341
assert states.batch_allowed([]) is None
342
343
shared = states.batch_allowed([0])
344
+ assert shared is not None
345
assert shared.tolist() == [1, 2, 3]
346
assert shared.dtype == torch.int64
347
assert states.batch_allowed([0, 0]) is shared
tests/v1/sample/test_diffusion_sampler_stats.py
+3
@@ -34,6 +34,8 @@ def test_stats_match_reference(vocab: int, dtype: torch.dtype):
34
)
35
assert torch.equal(argmax, ref_argmax)
36
torch.testing.assert_close(entropy, ref_entropy, atol=2e-3, rtol=1e-3)
37
+ assert probs is not None
38
+ assert ref_probs is not None
39
torch.testing.assert_close(probs.float(), ref_probs.float(), atol=5e-3, rtol=2e-2)
40
assert probs.dtype == torch.bfloat16
41
assert probs.shape == logits.shape
@@ -100,6 +102,7 @@ def test_masked_logits_keep_a_finite_entropy():
102
assert entropy[0].item() < 1e-6 and entropy[1].item() < 1e-6
103
assert abs(entropy[2].item() - math.log(2.0)) < 1e-4
104
assert torch.equal(argmax[[0, 1, 3]], torch.tensor([3, 3, 3], device="cuda"))
105
+ assert probs is not None
106
assert probs[0, 3].item() == 1.0 and probs[0].sum().item() == 1.0
107
_, _, ref_entropy, _ = sample_row_stats_reference(logits, temps, 1, None)
108
assert torch.isfinite(ref_entropy).all()
tests/v1/sample/test_dry.py
+6
-3
@@ -340,7 +340,7 @@ def _make_v2_state(
340
device=device,
341
all_token_ids=SimpleNamespace(gpu=all_tokens),
342
)
343
- state = DryState(_make_vllm_config(), req_states)
343
+ state = DryState(_make_vllm_config(), req_states) # type: ignore[arg-type]
344
if tokenizer is not None:
345
# Re-resolve the default set now that a tokenizer is installed.
346
state._tokenizer = tokenizer
@@ -446,7 +446,7 @@ def test_spec_decode_is_refused_at_construction():
446
)
447
config = _make_vllm_config(speculative_config=SimpleNamespace())
448
with pytest.raises(ValueError, match="speculative decoding"):
449
- DryState(config, req_states)
449
+ DryState(config, req_states) # type: ignore[arg-type]
450
451
452
def test_v2_draft_expanded_logits_raise():
@@ -917,7 +917,10 @@ def test_loads_through_the_loader_by_fqcn():
917
total_len=None,
918
)
919
procs = loader.build_custom_logits_processors(
920
- _make_vllm_config(), req_states, False, [DRY_FQCN]
920
+ _make_vllm_config(),
921
+ req_states, # type: ignore[arg-type]
922
+ False,
923
+ [DRY_FQCN],
924
)
925
assert [type(p) for p in procs] == [DryState]
926
tests/v1/sample/test_head_dtype.py
+21
-7
@@ -16,6 +16,7 @@ from vllm.model_executor.layers.logits_processor import LogitsProcessor
16
from vllm.model_executor.layers.vocab_parallel_embedding import (
17
ParallelLMHead,
18
UnquantizedEmbeddingMethod,
19
+ VocabParallelEmbedding,
20
)
21
22
@@ -32,10 +33,17 @@ class _FakeLmHead:
33
self.tp_size = 1
34
35
36
+def _fake_lm_head(
37
+ weight: torch.Tensor, quantized: bool = False, shard_indices: object | None = None
38
+) -> VocabParallelEmbedding:
39
+ # The dtype tests only need the projection fields, not distributed setup.
40
+ return _FakeLmHead(weight, quantized, shard_indices) # type: ignore[return-value]
41
+
42
+
43
def _build_processor(vocab_size: int) -> LogitsProcessor:
44
lp = LogitsProcessor(vocab_size)
45
# The TP gather is orthogonal to the dtype behavior under test.
38
- lp._gather_logits = lambda logits: logits
46
+ lp._gather_logits = lambda logits: logits # type: ignore[method-assign]
47
return lp
48
49
@@ -47,8 +55,9 @@ def test_fp32_head_runs_projection_in_fp32(default_vllm_config):
55
hidden_states = torch.randn(num_tokens, hidden_size, dtype=torch.bfloat16)
56
weight = torch.randn(vocab_size, hidden_size, dtype=torch.bfloat16)
57
50
- logits = lp._get_logits(hidden_states, _FakeLmHead(weight), None)
58
+ logits = lp._get_logits(hidden_states, _fake_lm_head(weight), None)
59
60
+ assert logits is not None
61
assert logits.dtype == torch.float32
62
assert torch.isfinite(logits).all()
63
expected = torch.nn.functional.linear(hidden_states.float(), weight.float())
@@ -66,8 +75,9 @@ def test_non_fp32_head_dtype_uses_cast_path(default_vllm_config):
75
hidden_states = torch.randn(4, hidden_size, dtype=torch.bfloat16)
76
weight = torch.randn(vocab_size, hidden_size, dtype=torch.bfloat16)
77
69
- logits = lp._get_logits(hidden_states, _FakeLmHead(weight), None)
78
+ logits = lp._get_logits(hidden_states, _fake_lm_head(weight), None)
79
80
+ assert logits is not None
81
assert logits.dtype == torch.float16
82
expected = torch.nn.functional.linear(hidden_states.half(), weight.half())
83
torch.testing.assert_close(logits, expected)
@@ -82,7 +92,7 @@ def test_head_dtype_equal_to_model_dtype_uses_quant_method(default_vllm_config):
92
93
hidden_states = torch.randn(4, hidden_size, dtype=torch.bfloat16)
94
weight = torch.randn(vocab_size, hidden_size, dtype=torch.bfloat16)
85
- lm_head = _FakeLmHead(weight)
95
+ lm_head = _fake_lm_head(weight)
96
97
with mock.patch.object(
98
lm_head.quant_method,
@@ -94,6 +104,7 @@ def test_head_dtype_equal_to_model_dtype_uses_quant_method(default_vllm_config):
104
logits = lp._get_logits(hidden_states, lm_head, None)
105
106
apply_mock.assert_called_once()
107
+ assert logits is not None
108
assert logits.dtype == torch.bfloat16
109
110
@@ -120,9 +131,10 @@ def test_fp32_head_uses_mm_fast_path_on_device(default_vllm_config):
131
with mock.patch(
132
"vllm.model_executor.layers.logits_processor.F.linear"
133
) as linear_mock:
123
- logits = lp._get_logits(hidden_states, _FakeLmHead(weight), None)
134
+ logits = lp._get_logits(hidden_states, _fake_lm_head(weight), None)
135
136
linear_mock.assert_not_called()
137
+ assert logits is not None
138
assert logits.dtype == torch.float32
139
expected = torch.nn.functional.linear(hidden_states.float(), weight.float())
140
torch.testing.assert_close(logits, expected)
@@ -131,7 +143,7 @@ def test_fp32_head_uses_mm_fast_path_on_device(default_vllm_config):
143
def test_fp32_head_rejects_quantized_lm_head(default_vllm_config):
144
lp = _build_processor(64)
145
lp.head_dtype = torch.float32
134
- lm_head = _FakeLmHead(torch.randn(64, 16, dtype=torch.bfloat16), quantized=True)
146
+ lm_head = _fake_lm_head(torch.randn(64, 16, dtype=torch.bfloat16), quantized=True)
147
148
with pytest.raises(ValueError, match="unquantized"):
149
lp._get_logits(torch.randn(4, 16, dtype=torch.bfloat16), lm_head, None)
@@ -197,7 +209,7 @@ def test_get_top_tokens_honors_head_dtype(default_vllm_config):
209
210
hidden_states = torch.randn(4, hidden_size, dtype=torch.bfloat16)
211
weight = torch.randn(vocab_size, hidden_size, dtype=torch.bfloat16)
200
- lm_head = _FakeLmHead(
212
+ lm_head = _fake_lm_head(
213
weight,
214
shard_indices=types.SimpleNamespace(
215
num_org_vocab_padding=0, org_vocab_start_index=0
@@ -236,7 +248,9 @@ def test_fp32_head_e2e_no_nan():
248
249
for output in outputs:
250
for completion in output.outputs:
251
+ assert completion.logprobs is not None
252
for token_id, position in zip(completion.token_ids, completion.logprobs):
253
+ assert position is not None
254
# The sampled token survived filtering, so its logprob is finite.
255
assert math.isfinite(position[token_id].logprob)
256
# No returned logprob is NaN.
tests/v1/sample/test_logprobs.py
+33
-17
@@ -24,6 +24,7 @@ from vllm import SamplingParams
24
from vllm.config.model import LogprobsMode
25
from vllm.distributed import cleanup_dist_env_and_memory
26
from vllm.exceptions import VLLMValidationError
27
+from vllm.logprobs import Logprob
28
from vllm.platforms import current_platform
29
from vllm.v1.engine.input_processor import InputProcessor
30
@@ -145,7 +146,7 @@ def _repeat_logprob_config(
146
def _run_and_validate(
147
vllm_model: VllmRunner,
148
test_prompts: list[str],
148
- vllm_sampling_params: SamplingParams,
149
+ vllm_sampling_params: SamplingParams | list[SamplingParams],
150
hf_logprobs: list[list[torch.Tensor]],
151
hf_outputs: list[tuple[list[int], str]],
152
logprob_prompt_logprob_list: BatchLogprobsSpecType,
@@ -163,11 +164,12 @@ def _run_and_validate(
164
# Extract request-level (prompt)logprobs config
165
num_top_logprobs, num_top_prompt_logprobs = logprob_prompt_logprob
166
167
+ assert vllm_result.prompt_token_ids is not None
168
# Test whether sampled token output is consistent between vLLM and HF
169
# vLLM prompt+completion should match HF output
170
if temperature == 0.0:
171
assert (
170
- vllm_result.prompt_token_ids + vllm_result.outputs[0].token_ids
172
+ vllm_result.prompt_token_ids + list(vllm_result.outputs[0].token_ids)
173
== hf_output[0]
174
)
175
else:
@@ -191,7 +193,9 @@ def _run_and_validate(
193
194
# Confirm that the output token appears among the logprobs
195
assert token_id in logprobs
194
- token_in_topk = logprobs[token_id].rank <= num_top_logprobs
196
+ rank = logprobs[token_id].rank
197
+ assert rank is not None
198
+ token_in_topk = rank <= num_top_logprobs
199
200
# If the output token is not included in the top K
201
# logprob, it can return 1 more data
@@ -207,8 +211,10 @@ def _run_and_validate(
211
212
output_text = vllm_result.outputs[0].text
213
output_string_from_most_likely_tokens_lst: list[str] = []
210
- for top_logprobs in vllm_result.outputs[0].logprobs:
211
- top_logprob = next(iter(top_logprobs.values()))
214
+ for output_logprobs in vllm_result.outputs[0].logprobs:
215
+ assert output_logprobs is not None
216
+ top_logprob = next(iter(output_logprobs.values()))
217
+ assert top_logprob.decoded_token is not None
218
output_string_from_most_likely_tokens_lst.append(
219
top_logprob.decoded_token
220
)
@@ -226,8 +232,9 @@ def _run_and_validate(
232
233
# Compare vLLM sample logprobs to HF
234
vllm_sample_logprobs = vllm_result.outputs[0].logprobs
229
- for i, top_logprobs in enumerate(vllm_sample_logprobs):
230
- for token_id, sample_logprob in top_logprobs.items():
235
+ for i, sample_logprobs in enumerate(vllm_sample_logprobs):
236
+ assert sample_logprobs is not None
237
+ for token_id, sample_logprob in sample_logprobs.items():
238
if temperature == 0.0 or i == 0:
239
logprob = sample_logprob.logprob
240
torch.testing.assert_close(
@@ -271,9 +278,9 @@ def _run_and_validate(
278
279
# Confirm that the prompt token appears among the logprobs
280
assert prompt_token_id in prompt_logprobs
274
- token_in_topk = (
275
- prompt_logprobs[prompt_token_id].rank <= num_top_prompt_logprobs
276
- )
281
+ prompt_rank = prompt_logprobs[prompt_token_id].rank
282
+ assert prompt_rank is not None
283
+ token_in_topk = prompt_rank <= num_top_prompt_logprobs
284
285
# If the prompt token is not included in the top K
286
# logprob, it can return 1 more data
@@ -294,9 +301,10 @@ def _run_and_validate(
301
# 1:.
302
vllm_prompt_logprobs = vllm_result.prompt_logprobs[1:]
303
for i, vllm_prompt_logprob_dict in enumerate(vllm_prompt_logprobs):
297
- for token_id, logprob in vllm_prompt_logprob_dict.items():
304
+ assert vllm_prompt_logprob_dict is not None
305
+ for token_id, prompt_lp in vllm_prompt_logprob_dict.items():
306
torch.testing.assert_close(
299
- logprob.logprob,
307
+ prompt_lp.logprob,
308
hf_logprob[0][i][token_id].item(),
309
atol=2e-2,
310
rtol=2e-2,
@@ -478,12 +486,12 @@ def test_prompt_logprob_token_ids_require_v2_model_runner():
486
params = SamplingParams(prompt_logprob_token_ids=[1, 2])
487
with patch.object(SamplingParams, "verify"):
488
with pytest.raises(VLLMValidationError, match="V2 model runner"):
481
- InputProcessor._validate_params(processor, params, ("generate",))
489
+ InputProcessor._validate_params(processor, params, ("generate",)) # type: ignore[arg-type]
490
processor.vllm_config.use_v2_model_runner = True
483
- InputProcessor._validate_params(processor, params, ("generate",))
491
+ InputProcessor._validate_params(processor, params, ("generate",)) # type: ignore[arg-type]
492
processor.vllm_config.cache_config.kv_sharing_fast_prefill = True
493
with pytest.raises(VLLMValidationError, match="fast-prefill"):
486
- InputProcessor._validate_params(processor, params, ("generate",))
494
+ InputProcessor._validate_params(processor, params, ("generate",)) # type: ignore[arg-type]
495
496
497
def test_none_logprobs(vllm_model, example_prompts):
@@ -607,7 +615,9 @@ def test_logprobs_mode(logprobs_mode: LogprobsMode):
615
total_token_with_logprobs = 0
616
positive_values = 0
617
for output in results[0].outputs:
618
+ assert output.logprobs is not None
619
for logprobs in output.logprobs:
620
+ assert logprobs is not None
621
for token_id in logprobs:
622
logprob = logprobs[token_id]
623
if logprobs_mode in ("raw_logprobs", "processed_logprobs"):
@@ -648,6 +658,7 @@ def test_prompt_logprobs_mode():
658
)
659
assert results[0].prompt_logprobs is not None
660
assert results[0].prompt_logprobs[1] is not None
661
+ assert results[0].prompt_token_ids is not None
662
tok_id = results[0].prompt_token_ids[1]
663
values[mode] = results[0].prompt_logprobs[1][tok_id].logprob
664
finally:
@@ -1228,10 +1239,12 @@ def test_spec_decode_logprobs(
1239
[prompt, prompt], [sampling_params, penalty_sampling_params]
1240
)
1241
# Collect logprobs outputs from reference LLM.
1231
- ref_logprobs = []
1242
+ ref_logprobs: list[Logprob] = []
1243
for results in ref_results:
1244
for output in results.outputs:
1245
+ assert output.logprobs is not None
1246
for logprobs in output.logprobs:
1247
+ assert logprobs is not None
1248
ref_logprobs.extend(logprobs.values())
1249
del ref_llm
1250
torch.accelerator.empty_cache()
@@ -1249,10 +1262,12 @@ def test_spec_decode_logprobs(
1262
[prompt, prompt], [sampling_params, penalty_sampling_params]
1263
)
1264
# Collect logprobs outputs from spec decode LLM.
1252
- spec_logprobs = []
1265
+ spec_logprobs: list[Logprob] = []
1266
for results in spec_results:
1267
for output in results.outputs:
1268
+ assert output.logprobs is not None
1269
for logprobs in output.logprobs:
1270
+ assert logprobs is not None
1271
spec_logprobs.extend(logprobs.values())
1272
del spec_llm
1273
torch.accelerator.empty_cache()
@@ -1326,6 +1341,7 @@ def test_prompt_logprobs_with_chunking_and_preemption():
1341
"Unexpected number of prompt logprob positions"
1342
)
1343
1344
+ assert sampling_params.prompt_logprobs is not None
1345
# Each position should have the requested number of logprobs
1346
for pos, logprobs_dict in enumerate(prompt_logprobs):
1347
if logprobs_dict is not None: # First token may be None
tests/v1/sample/test_sampler.py
+1
@@ -333,6 +333,7 @@ def test_sampler_repetition_penalty(
333
for batch_idx in range(batch_size):
334
non_penalized_token_id = logits[batch_idx].argmax().item()
335
penalized_token_id = logits[batch_idx].argmin().item()
336
+ assert sampling_metadata.prompt_token_ids is not None
337
prompt_tokens = sampling_metadata.prompt_token_ids[batch_idx][:].tolist()
338
output_tokens = sampling_metadata.output_token_ids[batch_idx]
339
if repetition_penalty > 1.0:
tests/v1/sample/test_thinking_budget_state.py
+2
-6
@@ -4,6 +4,7 @@
4
5
import torch
6
7
+from tests.v1.sample.utils import create_mock_reasoning_config
8
from vllm.sampling_params import SamplingParams
9
from vllm.v1.sample.logits_processor.interface import (
10
BatchUpdate,
@@ -12,14 +13,9 @@ from vllm.v1.sample.logits_processor.interface import (
13
from vllm.v1.sample.thinking_budget_state import ThinkingBudgetStateHolder
14
15
15
-class _MockReasoningConfig:
16
- reasoning_start_token_ids = [151667]
17
- reasoning_end_token_ids = [151668]
18
-
19
-
16
def _make_holder() -> ThinkingBudgetStateHolder:
17
return ThinkingBudgetStateHolder(
22
- _MockReasoningConfig(),
18
+ create_mock_reasoning_config([151667], [151668]),
19
8,
20
0,
21
torch.device("cpu"),
tests/v1/sample/test_trace_replay_params.py
+2
-1
@@ -104,7 +104,8 @@ def test_trace_decode_token_ids_rejects_speculative_decoding():
104
params = SamplingParams(trace_decode_token_ids=[1])
105
with pytest.raises(ValueError, match="not supported with speculative decoding"):
106
params._validate_trace_replay(
107
- _make_model_config(vocab_size=100), speculative_config=object()
107
+ _make_model_config(vocab_size=100),
108
+ speculative_config=object(), # type: ignore[arg-type] # Only non-None presence is tested.
109
)
110
111
tests/v1/sample/utils.py
+17
-1
@@ -4,16 +4,28 @@
4
from collections.abc import Iterator
5
from enum import Enum
6
from typing import NamedTuple
7
+from unittest.mock import MagicMock
8
9
import regex as re
10
import torch
11
12
from vllm import CompletionOutput
13
+from vllm.config import ReasoningConfig
14
from vllm.utils.torch_utils import make_tensor_with_pad
15
from vllm.v1.sample.logits_processor import BatchUpdate, LogitsProcessor
16
from vllm.v1.sample.metadata import SamplingMetadata
17
18
19
+def create_mock_reasoning_config(
20
+ start_token_ids: list[int], end_token_ids: list[int]
21
+) -> ReasoningConfig:
22
+ return MagicMock(
23
+ spec=ReasoningConfig,
24
+ reasoning_start_token_ids=start_token_ids,
25
+ reasoning_end_token_ids=end_token_ids,
26
+ )
27
+
28
+
29
class BatchLogprobsComposition(Enum):
30
"""Types of logprobs configs to include in test batch."""
31
@@ -141,7 +153,11 @@ def compute_correct_cumulative_logprob(completion_output: CompletionOutput) -> f
153
token_ids = completion_output.token_ids
154
logprobs = completion_output.logprobs
155
assert logprobs is not None
144
- return sum([lp[tok_id].logprob for tok_id, lp in zip(token_ids, logprobs)])
156
+ values: list[float] = []
157
+ for tok_id, lp in zip(token_ids, logprobs):
158
+ assert lp is not None
159
+ values.append(lp[tok_id].logprob)
160
+ return sum(values)
161
162
163
def create_fake_logits(batch_size: int, vocab_size: int) -> torch.Tensor:
tests/v1/spec_decode/test_adaptive_verification.py
+16
-8
@@ -25,9 +25,9 @@ def make_manager(
25
num_reqs, num_steps = confidences.shape
26
manager = AdaptiveVerificationManager.__new__(AdaptiveVerificationManager)
27
manager.num_speculative_steps = num_steps
28
- manager._stale_confidences = [SimpleNamespace(np=confidences)]
28
+ manager._stale_confidences = [SimpleNamespace(np=confidences)] # type: ignore[list-item] # CPU-only confidence buffer.
29
manager._stale_idx = 0
30
- manager.req_states = SimpleNamespace(
30
+ manager.req_states = SimpleNamespace( # type: ignore[assignment] # CPU-only request fixture.
31
req_id_to_index={"low": 0, "high": 1},
32
num_computed_tokens_np=np.ones(num_reqs, dtype=np.int32),
33
prefill_len=SimpleNamespace(np=np.ones(num_reqs, dtype=np.int32)),
@@ -153,11 +153,11 @@ def test_manager_checks_target_varlen_cudagraph_bound(
153
manager = maybe_create_adaptive_verification_manager(
154
enable_adaptive_verification=True,
155
attn_groups=groups,
156
- req_states=SimpleNamespace(num_speculative_steps=7),
156
+ req_states=SimpleNamespace(num_speculative_steps=7), # type: ignore[arg-type]
157
query_start_loc=object(),
158
num_bonus_tokens=1,
159
max_total_logits=1,
160
- vllm_config=None,
160
+ vllm_config=None, # type: ignore[arg-type]
161
target_layer_names={"target"},
162
additional_attn_cg_support=additional,
163
)
@@ -176,6 +176,7 @@ def test_budget_stops_where_marginal_drafts_stop_paying_for_themselves():
176
{"low": 3, "high": 3},
177
{"low": [1, 2], "high": [3, 4]},
178
)
179
+ assert manager._batch_budget is not None
180
valid_drafts, num_non_draft_tokens, draft_budget = manager._batch_budget
181
182
assert draft_budget == 1
@@ -183,14 +184,18 @@ def test_budget_stops_where_marginal_drafts_stop_paying_for_themselves():
184
assert num_non_draft_tokens == {"low": 1, "high": 1}
185
186
186
-def test_profiled_batches_seed_cost_curves_via_consumer():
187
+def test_profiled_batches_seed_cost_curves_via_consumer(monkeypatch):
188
manager = AdaptiveVerificationManager.__new__(AdaptiveVerificationManager)
188
- manager.req_states = SimpleNamespace(max_num_batched_tokens=4096, max_num_reqs=64)
189
+ manager.req_states = SimpleNamespace( # type: ignore[assignment] # CPU-only request fixture.
190
+ max_num_batched_tokens=4096, max_num_reqs=64
191
+ )
192
manager.num_speculative_steps = 7
193
manager.num_bonus_tokens = 1
194
curves: dict[str, list[tuple[int, float]]] = {}
192
- manager.set_cost_curves = lambda draft, verify: curves.update(
193
- draft=draft, verify=verify
195
+ monkeypatch.setattr(
196
+ manager,
197
+ "set_cost_curves",
198
+ lambda draft, verify: curves.update(draft=draft, verify=verify),
199
)
200
201
timings = [
@@ -258,6 +263,7 @@ def test_budget_caps_at_one_rejection_sampler_chunk():
263
{"low": 3, "high": 3},
264
{"low": [1, 2], "high": [3, 4]},
265
)
266
+ assert manager._batch_budget is not None
267
_, _, draft_budget = manager._batch_budget
268
assert draft_budget <= 1
269
@@ -285,6 +291,7 @@ def test_zero_budget_rebuilds_cpu_cu_num_logits():
291
{"low": 3, "high": 3, "prefill": 40},
292
{"low": [1, 2], "high": [3, 4]},
293
)
294
+ assert manager._batch_budget is not None
295
_, _, draft_budget = manager._batch_budget
296
assert draft_budget == 0
297
@@ -323,6 +330,7 @@ def test_zero_budget_keeps_one_grammar_row_per_scheduled_draft():
330
manager.get_num_tokens(
331
{"low": 3, "high": 3, "prefill": 40}, scheduled_spec_decode_tokens
332
)
333
+ assert manager._batch_budget is not None
334
assert manager._batch_budget[2] == 0
335
336
req_ids = ["low", "high", "prefill"]
tests/v1/spec_decode/test_dflash2.py
+10
-8
@@ -98,7 +98,7 @@ def test_draft_quant_exclusions_include_global_layer_indices():
98
]
99
)
100
101
- _add_global_draft_layer_exclusions(quant_config, 88, 5)
101
+ _add_global_draft_layer_exclusions(quant_config, 88, 5) # type: ignore[arg-type]
102
103
assert "layers.88.mlp_conv*" in quant_config.exclude_modules
104
assert "*layers.92.self_attn.q_proj" in quant_config.exclude_modules
@@ -171,7 +171,7 @@ def test_selector_leaves_greedy_drafting_without_proposal_logits(monkeypatch):
171
allocating one here would claim a proposal the walk never sampled from.
172
"""
173
_stub_base(monkeypatch, None)
174
- speculator = DFlash2Speculator(None, torch.device("cpu"))
174
+ speculator = DFlash2Speculator(None, torch.device("cpu")) # type: ignore[arg-type] # Base initialization is stubbed.
175
176
assert speculator.draft_logits is None
177
@@ -183,7 +183,7 @@ def test_selector_asks_for_fp32_proposal_logits():
183
candidate row often enough that the walk and the rejection sampler checking it
184
would no longer read the same distribution.
185
"""
186
- dtype, fill = DFlash2Speculator.draft_logits_spec(None, None)
186
+ dtype, fill = DFlash2Speculator.draft_logits_spec(None, None) # type: ignore[arg-type] # Constant spec ignores self/config.
187
188
assert dtype is torch.float32
189
assert fill == float("-inf")
@@ -309,9 +309,9 @@ def test_candidate_model_decoder_layer_cls(monkeypatch, variant):
309
vllm_config.compilation_config = mock_current_vllm_config.compilation_config
310
311
# 3. Instantiate the model under meta device to avoid parameter allocation issues
312
- with set_current_vllm_config(mock_current_vllm_config), torch.device("meta"):
312
+ with set_current_vllm_config(mock_current_vllm_config), torch.device("meta"): # type: ignore[arg-type]
313
model_cls = DFlash2Qwen3Model if variant == "dflash2" else LiLiCorr
314
- model = model_cls(vllm_config=vllm_config)
314
+ model = model_cls(vllm_config=vllm_config) # type: ignore[arg-type]
315
316
# 4. Assert that the layers are DFlash2Qwen3DecoderLayer (the subclass)
317
assert len(model.layers) == 2
@@ -350,7 +350,7 @@ def test_conv_projections_use_draft_quant_config(monkeypatch):
350
quant_method="W4A16_NVFP4", is_checkpoint_nvfp4_serialized=True
351
)
352
layer = DFlash2Qwen3DecoderLayer(
353
- SimpleNamespace(
353
+ SimpleNamespace( # type: ignore[arg-type]
354
speculative_config=SimpleNamespace(num_speculative_tokens=7),
355
model_config=SimpleNamespace(dtype=torch.bfloat16),
356
),
@@ -434,13 +434,15 @@ def test_context_kv_uses_quantized_projection_fallback(monkeypatch):
434
for projection in projections
435
]
436
qwen3_dflash.DFlashQwen3Model._build_context_kv_buffers(
437
- model, layers_attn, has_bias=False
437
+ model, # type: ignore[arg-type]
438
+ layers_attn,
439
+ has_bias=False,
440
)
441
assert model._fused_kv_weight is None
442
assert all(not hasattr(projection, "weight") for projection in projections)
443
444
actual_k, actual_v = qwen3_dflash.DFlashQwen3Model._project_context_kv(
443
- model,
445
+ model, # type: ignore[arg-type]
446
context_states,
447
num_ctx=2,
448
num_layers=2,
tests/v1/spec_decode/test_dflash_lookahead.py
+4
-4
@@ -141,9 +141,9 @@ def test_dflash_drafter_window_reserves_bonus_token():
141
speculative_config=_dflash_speculative_config(NUM_SPECULATIVE_TOKENS),
142
)
143
# window = 4, so 96 fits (96 + 4 == 100) but 97 does not (97 + 4 == 101)
144
- assert input_fits_in_drafter(dflash_runner, SimpleNamespace(max_seq_len=96))
145
- assert not input_fits_in_drafter(dflash_runner, SimpleNamespace(max_seq_len=97))
146
- assert not input_fits_in_drafter(dflash_runner, None) # no metadata
144
+ assert input_fits_in_drafter(dflash_runner, SimpleNamespace(max_seq_len=96)) # type: ignore[arg-type]
145
+ assert not input_fits_in_drafter(dflash_runner, SimpleNamespace(max_seq_len=97)) # type: ignore[arg-type]
146
+ assert not input_fits_in_drafter(dflash_runner, None) # type: ignore[arg-type] # No metadata.
147
148
# Other drafters don't reserve the bonus token, so 97 fits (97 + 3 == 100).
149
plain_runner = SimpleNamespace(
@@ -151,4 +151,4 @@ def test_dflash_drafter_window_reserves_bonus_token():
151
effective_drafter_max_model_len=100,
152
speculative_config=SimpleNamespace(use_dflash=lambda: False),
153
)
154
- assert input_fits_in_drafter(plain_runner, SimpleNamespace(max_seq_len=97))
154
+ assert input_fits_in_drafter(plain_runner, SimpleNamespace(max_seq_len=97)) # type: ignore[arg-type]
tests/v1/spec_decode/test_dsv4_mtp_local_argmax.py
+4
-1
@@ -56,6 +56,7 @@ def _make_predictor(vocab_size: int) -> DeepSeekV4MultiTokenPredictor:
56
):
57
shared_head = SharedHead(config=config, prefix="shared_head")
58
59
+ assert shared_head.head is not None
60
generator = torch.Generator().manual_seed(7)
61
with torch.no_grad():
62
shared_head.head.weight.normal_(0.0, 0.02, generator=generator)
@@ -157,7 +158,9 @@ def test_wrapper_forwards_spec_step_idx():
158
159
hidden_states = torch.empty(0)
160
wrapper = object.__new__(DeepSeekV4MTP)
160
- wrapper.model = SimpleNamespace(get_top_tokens=lambda *args: args)
161
+ wrapper.model = SimpleNamespace( # type: ignore[assignment]
162
+ get_top_tokens=lambda *args: args
163
+ )
164
165
assert DeepSeekV4MTP.get_top_tokens(wrapper, hidden_states, 2) == (hidden_states, 2)
166
assert DeepSeekV4MTP.get_top_tokens(wrapper, hidden_states) == (hidden_states, 0)
tests/v1/spec_decode/test_dynamic_sd_cug.py
+4
-4
@@ -25,7 +25,7 @@ def _create_vllm_config_for_dsd(
25
max_num_seqs: int,
26
max_spec_tokens: int,
27
*,
28
- cudagraph_mode: str = "FULL_AND_PIECEWISE",
28
+ cudagraph_mode: CUDAGraphMode = CUDAGraphMode.FULL_AND_PIECEWISE,
29
use_dynamic_sd: bool = True,
30
num_spec_per_batch_size: list[tuple[int, int, int]] | None = None,
31
) -> MagicMock:
@@ -199,7 +199,7 @@ def test_dynamic_sd_non_uniform_batch_falls_back_to_piecewise(monkeypatch):
199
vllm_config = _create_vllm_config_for_dsd(
200
max_num_seqs=512,
201
max_spec_tokens=max_spec_tokens,
202
- cudagraph_mode="FULL_AND_PIECEWISE",
202
+ cudagraph_mode=CUDAGraphMode.FULL_AND_PIECEWISE,
203
use_dynamic_sd=True,
204
)
205
manager = gpu_cudagraph_utils.CudaGraphManager(
@@ -316,7 +316,7 @@ def test_basic_sd_does_not_capture_shorter_full_decode_shapes(monkeypatch):
316
vllm_config = _create_vllm_config_for_dsd(
317
max_num_seqs=max_num_seqs,
318
max_spec_tokens=max_spec_tokens,
319
- cudagraph_mode="FULL_AND_PIECEWISE",
319
+ cudagraph_mode=CUDAGraphMode.FULL_AND_PIECEWISE,
320
use_dynamic_sd=False,
321
)
322
manager = gpu_cudagraph_utils.CudaGraphManager(
@@ -382,7 +382,7 @@ def test_dynamic_sd_only_captures_scheduled_query_lengths(monkeypatch):
382
vllm_config = _create_vllm_config_for_dsd(
383
max_num_seqs=max_num_seqs,
384
max_spec_tokens=max_spec_tokens,
385
- cudagraph_mode="FULL_AND_PIECEWISE",
385
+ cudagraph_mode=CUDAGraphMode.FULL_AND_PIECEWISE,
386
use_dynamic_sd=True,
387
num_spec_per_batch_size=num_spec_per_batch_size,
388
)
tests/v1/spec_decode/test_eagle.py
+17
-11
@@ -1,6 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
+from typing import Literal
5
from unittest import mock
6
7
import numpy as np
@@ -31,6 +32,7 @@ from vllm.v1.attention.backends.registry import AttentionBackendEnum
32
from vllm.v1.spec_decode.dflash import DFlashProposer
33
from vllm.v1.spec_decode.draft_model import DraftModelProposer
34
from vllm.v1.spec_decode.eagle import EagleProposer
35
+from vllm.v1.spec_decode.llm_base_proposer import SpecDecodeBaseProposer
36
from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
37
from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
38
@@ -46,13 +48,13 @@ DEVICE_TYPE = current_platform.device_type
48
49
50
def _create_proposer(
49
- method: str,
51
+ method: Literal["eagle", "eagle3", "draft_model", "dflash"],
52
num_speculative_tokens: int,
53
attention_backend: str | None = None,
54
parallel_drafting: bool = False,
53
- rejection_sample_method: str = "standard",
54
- draft_sample_method: str = "greedy",
55
-) -> EagleProposer:
55
+ rejection_sample_method: Literal["standard", "synthetic", "block"] = "standard",
56
+ draft_sample_method: Literal["greedy", "probabilistic"] = "greedy",
57
+) -> SpecDecodeBaseProposer:
58
# Method-dependent setup
59
if method == "eagle":
60
target_model_dir = model_dir
@@ -102,9 +104,14 @@ def _create_proposer(
104
max_model_len=model_config.max_model_len,
105
is_encoder_decoder=model_config.is_encoder_decoder,
106
),
105
- attention_config=AttentionConfig(backend=attention_backend),
107
+ attention_config=AttentionConfig(
108
+ backend=AttentionBackendEnum[attention_backend]
109
+ if attention_backend
110
+ else None
111
+ ),
112
)
113
114
+ proposer: SpecDecodeBaseProposer
115
if method == "dflash":
116
proposer = DFlashProposer(vllm_config=vllm_config, device=device)
117
elif "eagle" in method:
@@ -135,7 +142,7 @@ def test_prepare_next_token_ids():
142
)
143
144
mock_num_scheduled_tokens = {req_id: 0 for req_id in req_ids}
138
- mock_requests = {}
145
+ mock_requests: dict[str, CachedRequestState] = {}
146
for req_id in req_ids:
147
mock_request = mock.MagicMock(spec=CachedRequestState)
148
# Each request will have a backup next token id of 10, 20, 30, 40
@@ -948,13 +955,12 @@ def test_propose(method, attn_backend, num_speculative_tokens, monkeypatch):
955
956
attn_metadata_builder = attn_metadata_builder_cls(
957
kv_cache_spec=create_standard_kv_cache_spec(proposer.vllm_config),
951
- layer_names=proposer._draft_attn_layer_names,
958
+ layer_names=sorted(proposer._draft_attn_layer_names),
959
vllm_config=proposer.vllm_config,
960
device=device,
961
)
962
956
- # Mock runner and draft_attn_groups for attention metadata building
957
- proposer.runner = mock.MagicMock()
963
+ # Mock draft_attn_groups for attention metadata building.
964
mock_attn_group = mock.MagicMock()
965
mock_attn_group.get_metadata_builder.return_value = attn_metadata_builder
966
mock_attn_group.layer_names = list(proposer._draft_attn_layer_names)
@@ -1054,11 +1060,10 @@ def test_propose_stores_probabilistic_draft_probs(attn_backend, monkeypatch):
1060
)
1061
attn_metadata_builder = attn_metadata_builder_cls(
1062
kv_cache_spec=create_standard_kv_cache_spec(proposer.vllm_config),
1057
- layer_names=proposer._draft_attn_layer_names,
1063
+ layer_names=sorted(proposer._draft_attn_layer_names),
1064
vllm_config=proposer.vllm_config,
1065
device=device,
1066
)
1061
- proposer.runner = mock.MagicMock()
1067
mock_attn_group = mock.MagicMock()
1068
mock_attn_group.get_metadata_builder.return_value = attn_metadata_builder
1069
mock_attn_group.layer_names = list(proposer._draft_attn_layer_names)
@@ -1133,6 +1138,7 @@ def test_set_inputs_first_pass_dflash():
1138
1139
num_speculative_tokens = 3
1140
proposer = _create_proposer("dflash", num_speculative_tokens)
1141
+ assert isinstance(proposer, DFlashProposer)
1142
mask_token_id = proposer.parallel_drafting_token_id
1143
1144
# Setup batch with 3 requests
tests/v1/spec_decode/test_eagle_draft_attn_metadata.py
+1
-1
@@ -93,7 +93,7 @@ def _run_build(
93
94
with patch.object(base_speculator, "build_attn_metadata", fake_build_attn_metadata):
95
EagleSpeculator._build_uniform_attn_metadata(
96
- fake, # type: ignore[arg-type]
96
+ fake,
97
batch_desc=batch_desc,
98
num_reqs=num_reqs,
99
num_query_per_req=num_query_per_req,
tests/v1/spec_decode/test_extract_hidden_states.py
+5
-5
@@ -51,7 +51,7 @@ def _create_proposer(
51
target_parallel_config=ParallelConfig(),
52
method="extract_hidden_states",
53
num_speculative_tokens=num_speculative_tokens,
54
- draft_model_config={
54
+ draft_model_config={ # type: ignore[arg-type]
55
"hf_config": {
56
"eagle_aux_hidden_state_layer_ids": layer_ids,
57
}
@@ -103,7 +103,7 @@ def test_proposer_initialization_missing_layer_ids():
103
target_parallel_config=ParallelConfig(),
104
method="extract_hidden_states",
105
num_speculative_tokens=1,
106
- draft_model_config={
106
+ draft_model_config={ # type: ignore[arg-type]
107
"hf_config": {} # Missing eagle_aux_hidden_state_layer_ids
108
},
109
)
@@ -146,7 +146,7 @@ def test_prepare_next_token_ids_padded():
146
mock_input_batch.vocab_size = 100
147
mock_input_batch.num_tokens_no_spec = np.array([5] * num_requests)
148
149
- mock_requests = {}
149
+ mock_requests: dict[str, CachedRequestState] = {}
150
for req_id in req_ids:
151
mock_request = mock.MagicMock(spec=CachedRequestState)
152
# Each request will have a backup next token id of 10, 20, 30, 40
@@ -363,7 +363,7 @@ def test_extract_hidden_states_text_only_config_regression():
363
target_parallel_config=ParallelConfig(),
364
method="extract_hidden_states",
365
num_speculative_tokens=1,
366
- draft_model_config={
366
+ draft_model_config={ # type: ignore[arg-type]
367
"hf_config": {
368
"eagle_aux_hidden_state_layer_ids": [1, 2, 3, 4],
369
}
@@ -450,7 +450,7 @@ def test_extract_hidden_states_speculative_config_vlm():
450
target_parallel_config=ParallelConfig(),
451
method="extract_hidden_states",
452
num_speculative_tokens=1,
453
- draft_model_config={
453
+ draft_model_config={ # type: ignore[arg-type]
454
"hf_config": {
455
"eagle_aux_hidden_state_layer_ids": [1, 2],
456
}
tests/v1/spec_decode/test_lilicorr.py
+38
-20
@@ -11,6 +11,7 @@ from torch import nn
11
12
from vllm.config import (
13
CompilationConfig,
14
+ CompilationMode,
15
DeviceConfig,
16
LoadConfig,
17
VllmConfig,
@@ -122,7 +123,7 @@ def test_lilicorr_matches_exported_head(head_width, slots, dtype):
123
set_current_vllm_config(
124
VllmConfig(
125
device_config=DeviceConfig("cuda"),
125
- compilation_config=CompilationConfig(mode=0),
126
+ compilation_config=CompilationConfig(mode=CompilationMode.NONE),
127
)
128
),
129
):
@@ -183,23 +184,23 @@ def test_runtime_length_keeps_trained_checkpoint_geometry(monkeypatch):
184
with set_current_vllm_config(
185
VllmConfig(
186
device_config=DeviceConfig("cpu"),
186
- compilation_config=CompilationConfig(mode=0),
187
+ compilation_config=CompilationConfig(mode=CompilationMode.NONE),
188
)
189
):
190
for slots in (1, 3):
191
spec.num_speculative_tokens = slots
192
head = LiLiCorr(
192
- vllm_config=SimpleNamespace(speculative_config=spec)
193
+ vllm_config=SimpleNamespace(speculative_config=spec) # type: ignore[arg-type]
194
).lilicorr
195
assert head.slot_embedding.shape == (1, 1, 3, 1, 8)
196
assert head.relative_slot_bias.shape == (2, 7)
197
spec.draft_model_config.hf_config.dflash_config["lilicorr_enabled"] = False
198
with pytest.raises(ValueError, match="lilicorr_enabled"):
198
- LiLiCorr(vllm_config=SimpleNamespace(speculative_config=spec))
199
+ LiLiCorr(vllm_config=SimpleNamespace(speculative_config=spec)) # type: ignore[arg-type]
200
spec.draft_model_config.hf_config.dflash_config["lilicorr_enabled"] = True
201
spec.num_speculative_tokens = 4
202
with pytest.raises(ValueError, match="trained block_size"):
202
- LiLiCorr(vllm_config=SimpleNamespace(speculative_config=spec))
203
+ LiLiCorr(vllm_config=SimpleNamespace(speculative_config=spec)) # type: ignore[arg-type]
204
205
206
@pytest.mark.parametrize("tp_size", [1, 2])
@@ -268,7 +269,11 @@ def test_candidates_use_global_partition_and_exclude_padding(monkeypatch, tp_siz
269
)
270
monkeypatch.setattr(logits_processor, "_topk", lambda x, k: x.topk(k, dim=-1))
271
ids, values = logits_processor.LogitsProcessor.get_top_k_tokens(
271
- fake, lm_head, torch.empty(2, 1), 2, return_log_probs=True
272
+ fake, # type: ignore[arg-type]
273
+ lm_head, # type: ignore[arg-type]
274
+ torch.empty(2, 1),
275
+ 2,
276
+ return_log_probs=True,
277
)
278
expected = torch.cat(valid_shards, -1).log_softmax(-1).topk(2)
279
torch.testing.assert_close(ids, expected.indices)
@@ -286,13 +291,13 @@ def test_context_anchor_is_last_committed_normalized_feature():
291
anchor_valid=torch.ones(4, dtype=torch.bool),
292
)
293
batch = SimpleNamespace(num_reqs=2, query_start_loc=torch.tensor([0, 2, 6]))
289
- LiLiCorrSpeculator.prepare_context_anchor(spec, batch, torch.tensor([0, 2]))
294
+ LiLiCorrSpeculator.prepare_context_anchor(spec, batch, torch.tensor([0, 2])) # type: ignore[arg-type]
295
torch.testing.assert_close(spec.anchor_hidden[:2], norm(hidden[[1, 3]]))
296
assert spec.anchor_valid.tolist() == [True, True, False, False]
297
assert not spec.anchor_hidden[2:].any()
298
# A smaller reordered batch must not retain an old request's anchor.
299
batch = SimpleNamespace(num_reqs=1, query_start_loc=torch.tensor([0, 3]))
295
- LiLiCorrSpeculator.prepare_context_anchor(spec, batch, torch.tensor([1]))
300
+ LiLiCorrSpeculator.prepare_context_anchor(spec, batch, torch.tensor([1])) # type: ignore[arg-type]
301
torch.testing.assert_close(spec.anchor_hidden[0], norm(hidden[1]))
302
assert not spec.anchor_hidden[1:].any()
303
@@ -316,7 +321,7 @@ def test_candidate_generation_routes_scores_and_adaptive_inputs(monkeypatch, lil
321
322
monkeypatch.setattr(DFlashSpeculator, "__init__", init_base)
323
cls = LiLiCorrSpeculator if lilicorr else DFlash2Speculator
319
- spec = cls(None, torch.device("cpu"))
324
+ spec = cls(None, torch.device("cpu")) # type: ignore[arg-type]
325
hidden = torch.arange(36).float().view(9, 4)
326
spec._run_model = lambda *args: hidden
327
spec.sample_indices = torch.tensor([1, 2, 4, 5, 7, 8])
@@ -329,7 +334,7 @@ def test_candidate_generation_routes_scores_and_adaptive_inputs(monkeypatch, lil
334
spec.draft_logits = None
335
spec.use_fp64_gumbel = False
336
spec.enable_adaptive_verification = True
332
- spec.input_buffers = SimpleNamespace(input_ids=torch.arange(9))
337
+ spec.input_buffers = SimpleNamespace(input_ids=torch.arange(9)) # type: ignore[assignment]
338
candidates = torch.arange(24).view(6, 4)
339
log_probs = candidates.float().log_softmax(-1)
340
scores = torch.randn(3, 2, 4, 4)
@@ -349,22 +354,35 @@ def test_candidate_generation_routes_scores_and_adaptive_inputs(monkeypatch, lil
354
compute_candidates=lambda h: (candidates, log_probs),
355
model=SimpleNamespace(lilicorr=score, candidate_selector=score),
356
)
352
- spec.target_embeddings = lambda ids: ids.float().unsqueeze(-1).expand(*ids.shape, 4)
357
if lilicorr:
358
+ assert isinstance(spec, LiLiCorrSpeculator)
359
+ monkeypatch.setattr(
360
+ spec,
361
+ "target_embeddings",
362
+ lambda ids: ids.float().unsqueeze(-1).expand(*ids.shape, 4),
363
+ raising=False,
364
+ )
365
spec.anchor_hidden.copy_(torch.arange(12).view(3, 4))
366
spec.anchor_valid[:2] = True
356
- spec.candidate_sampler.sample = sample
357
- spec._maybe_predict_acceptance = lambda *args: adaptive_inputs.extend(args)
367
+ monkeypatch.setattr(spec.candidate_sampler, "sample", sample)
368
+ monkeypatch.setattr(
369
+ spec, "_maybe_predict_acceptance", lambda *args: adaptive_inputs.extend(args)
370
+ )
371
spec._generate_draft(3, 9, None, None, None)
372
373
expected_ids = candidates.view(3, 2, 4)
361
- expected_first = spec.target_embeddings(expected_ids) if lilicorr else expected_ids
374
+ expected_first = (
375
+ spec.target_embeddings(expected_ids)
376
+ if isinstance(spec, LiLiCorrSpeculator)
377
+ else expected_ids
378
+ )
379
torch.testing.assert_close(scoring_inputs[0], expected_first)
380
torch.testing.assert_close(scoring_inputs[1], log_probs.view(3, 2, 4))
381
torch.testing.assert_close(
382
scoring_inputs[2], hidden[spec.sample_indices].view(3, 2, 4)
383
)
384
if lilicorr:
385
+ assert isinstance(spec, LiLiCorrSpeculator)
386
torch.testing.assert_close(scoring_inputs[3], spec.anchor_hidden)
387
torch.testing.assert_close(scoring_inputs[4], spec.anchor_valid)
388
else:
@@ -472,7 +490,7 @@ def test_checkpoint_coverage_rejects_incomplete_or_wrong_heads(
490
):
491
from vllm.model_executor.models.lilicorr import LiLiCorr, LiLiCorrForCausalLM
492
475
- wrapper = LiLiCorrForCausalLM.__new__(LiLiCorrForCausalLM)
493
+ wrapper = LiLiCorrForCausalLM.__new__(LiLiCorrForCausalLM) # type: ignore[type-abstract]
494
nn.Module.__init__(wrapper)
495
wrapper.model = LiLiCorr.__new__(LiLiCorr)
496
nn.Module.__init__(wrapper.model)
@@ -480,7 +498,7 @@ def test_checkpoint_coverage_rejects_incomplete_or_wrong_heads(
498
with set_current_vllm_config(
499
VllmConfig(
500
device_config=DeviceConfig("cpu"),
483
- compilation_config=CompilationConfig(mode=0),
501
+ compilation_config=CompilationConfig(mode=CompilationMode.NONE),
502
)
503
):
504
wrapper.model.lilicorr = LiLiCorrHead(
@@ -615,10 +633,10 @@ def owned_head_model(monkeypatch):
633
with set_current_vllm_config(
634
VllmConfig(
635
device_config=DeviceConfig("cpu"),
618
- compilation_config=CompilationConfig(mode=0),
636
+ compilation_config=CompilationConfig(mode=CompilationMode.NONE),
637
)
638
):
621
- model = LiLiCorrForCausalLM(vllm_config=vllm_config)
639
+ model = LiLiCorrForCausalLM(vllm_config=vllm_config) # type: ignore[arg-type]
640
return model, vllm_config
641
642
return build
@@ -720,7 +738,7 @@ def test_quantized_head_calls_methods_without_reading_packed_weights(monkeypatch
738
with set_current_vllm_config(
739
VllmConfig(
740
device_config=DeviceConfig("cpu"),
723
- compilation_config=CompilationConfig(mode=0),
741
+ compilation_config=CompilationConfig(mode=CompilationMode.NONE),
742
)
743
):
744
head = LiLiCorrHead(
@@ -728,7 +746,7 @@ def test_quantized_head_calls_methods_without_reading_packed_weights(monkeypatch
746
block_size=4,
747
rms_norm_eps=1e-6,
748
config=_config(hidden_size=8),
731
- quant_config=quant_config,
749
+ quant_config=quant_config, # type: ignore[arg-type]
750
prefix="model.lilicorr",
751
)
752
with torch.no_grad():
tests/v1/spec_decode/test_llm_base_proposer.py
+7
-4
@@ -16,6 +16,7 @@ from types import SimpleNamespace
16
import pytest
17
18
import vllm.v1.spec_decode.llm_base_proposer as llm_base_proposer
19
+from vllm.v1.kv_cache_interface import KVCacheConfig
20
from vllm.v1.spec_decode.eagle import EagleProposer
21
22
SCHEDULER_BLOCK_SIZE = 256
@@ -52,7 +53,7 @@ def _make_proposer(
53
monkeypatch.setattr(llm_base_proposer, "AttentionGroup", _FakeAttentionGroup)
54
55
proposer = EagleProposer.__new__(EagleProposer)
55
- proposer.vllm_config = None
56
+ proposer.vllm_config = None # type: ignore[assignment] # Patched backend discovery does not read config.
57
proposer.device = None
58
proposer._draft_attn_layer_names = set(layer_names)
59
proposer.kv_cache_gid = -1
@@ -61,10 +62,10 @@ def _make_proposer(
62
return proposer
63
64
64
-def _make_kv_cache_config(layer_names: set[str]) -> SimpleNamespace:
65
+def _make_kv_cache_config(layer_names: set[str]) -> KVCacheConfig:
66
spec = SimpleNamespace(block_size=SCHEDULER_BLOCK_SIZE)
67
group = SimpleNamespace(layer_names=list(layer_names), kv_cache_spec=spec)
67
- return SimpleNamespace(kv_cache_groups=[group])
68
+ return SimpleNamespace(kv_cache_groups=[group]) # type: ignore[return-value]
69
70
71
def test_block_size_uses_kernel_block_size(monkeypatch: pytest.MonkeyPatch):
@@ -81,7 +82,9 @@ def test_block_size_uses_kernel_block_size(monkeypatch: pytest.MonkeyPatch):
82
assert proposer.block_size == KERNEL_BLOCK_SIZE
83
assert proposer.block_size != SCHEDULER_BLOCK_SIZE
84
# The metadata builder keeps receiving the kernel block size as well.
84
- assert proposer.draft_attn_groups[0].kernel_block_size == KERNEL_BLOCK_SIZE
85
+ group = proposer.draft_attn_groups[0]
86
+ assert isinstance(group, _FakeAttentionGroup)
87
+ assert group.kernel_block_size == KERNEL_BLOCK_SIZE
88
89
90
def test_block_size_falls_back_to_kv_cache_spec(monkeypatch: pytest.MonkeyPatch):
tests/v1/spec_decode/test_llm_base_proposer_sampling.py
+1
-1
@@ -85,7 +85,7 @@ def test_compute_probs_and_sample_next_token_uses_fp64_exponential_race():
85
def test_mtp_model_returns_tuple(architecture: str, expected: bool):
86
proposer = object.__new__(SpecDecodeBaseProposer)
87
proposer.method = "mtp"
88
- proposer.draft_model_config = SimpleNamespace(
88
+ proposer.draft_model_config = SimpleNamespace( # type: ignore[assignment] # Architecture-only config.
89
hf_config=SimpleNamespace(architectures=[architecture])
90
)
91
tests/v1/spec_decode/test_mtp.py
-1
@@ -220,7 +220,6 @@ def test_mtp_propose(num_speculative_tokens, monkeypatch):
220
device=device,
221
)
222
223
- proposer.runner = mock.MagicMock()
223
mock_attn_group = mock.MagicMock()
224
mock_attn_group.get_metadata_builder.return_value = attn_metadata_builder
225
mock_attn_group.layer_names = list(proposer._draft_attn_layer_names)
tests/v1/spec_decode/test_mtp_structured_output.py
+7
-1
@@ -2,6 +2,8 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""Backend and engine-reasoner checks that are not manager-flow tests."""
4
5
+from typing import Literal
6
+
7
import pytest
8
from transformers import AutoTokenizer
9
@@ -17,7 +19,9 @@ TOKENIZER = "gpt2"
19
NUM_SPEC_TOKENS = 4
20
21
20
-def _make_manager_and_request(backend: str, prompt_str: str = '{"a": "b"}'):
22
+def _make_manager_and_request(
23
+ backend: Literal["xgrammar", "guidance"], prompt_str: str = '{"a": "b"}'
24
+):
25
tokenizer = AutoTokenizer.from_pretrained(TOKENIZER)
26
prompt = tokenizer.encode(prompt_str)
27
@@ -33,6 +37,7 @@ def _make_manager_and_request(backend: str, prompt_str: str = '{"a": "b"}'):
37
sampling_params = SamplingParams(
38
structured_outputs=StructuredOutputsParams(json='{"type": "object"}'),
39
)
40
+ assert sampling_params.structured_outputs is not None
41
sampling_params.structured_outputs._backend = backend
42
sampling_params.update_from_generation_config({}, tokenizer.eos_token_id)
43
@@ -43,6 +48,7 @@ def _make_manager_and_request(backend: str, prompt_str: str = '{"a": "b"}'):
48
pooling_params=None,
49
)
50
manager.grammar_init(request)
51
+ assert request.structured_output_request is not None
52
while not request.structured_output_request._check_grammar_completion():
53
continue
54
tests/v1/spec_decode/test_rejection_sampler_utils.py
+4
-6
@@ -168,7 +168,7 @@ def test_watermarked_bonus_uses_target_key():
168
watermarker=GumbelWatermarker(key=42, context_width=2),
169
)
170
expected = GumbelWatermarker(key=42, context_width=2).sample(
171
- target_logits.unsqueeze(0), contexts[1:], lambda _: None
171
+ target_logits.unsqueeze(0), contexts[1:]
172
)
173
174
assert num_sampled.item() == 2
@@ -196,7 +196,7 @@ def test_watermarked_recovery_uses_target_key():
196
watermarker=GumbelWatermarker(key=42, context_width=2),
197
)
198
expected = GumbelWatermarker(key=42, context_width=2).sample(
199
- target_logits.unsqueeze(0), contexts[:1], lambda _: None
199
+ target_logits.unsqueeze(0), contexts[:1]
200
)
201
202
assert num_sampled.item() == 1
@@ -225,9 +225,7 @@ def test_watermarked_recovery_supports_smaller_draft_vocabulary():
225
watermarking=torch.tensor([True], device="cuda"),
226
watermarker=watermarker,
227
)
228
- expected = watermarker.sample(
229
- target_logits[:4].unsqueeze(0), contexts[:1], lambda _: None
230
- )
228
+ expected = watermarker.sample(target_logits[:4].unsqueeze(0), contexts[:1])
229
230
assert num_sampled.item() == 1
231
assert sampled[0, 0] == expected.token_ids[0]
@@ -702,7 +700,7 @@ def test_verify_rejects_unproposed_drafts():
700
first_pos = inputs["pos"].view(num_trials, K + 1)[:, 0]
701
prefill_len = first_pos + 1
702
rejection_sampler = object.__new__(RejectionSampler)
705
- rejection_sampler.sampler = SimpleNamespace(
703
+ rejection_sampler.sampler = SimpleNamespace( # type: ignore[assignment]
704
apply_sampling_params=lambda logits, *args: logits,
705
sampling_states=SimpleNamespace(
706
temperature=SimpleNamespace(gpu=inputs["temperature"]),
tools/pre_commit/mypy.py
+12
-12
@@ -28,27 +28,36 @@ import regex as re
28
# Fixing a directory means moving it from SEPARATE_GROUPS to here. Without that
29
# move the fixes are not enforced because "tests" claims every file below it.
30
SILENT_GROUPS = [
31
+ "tests/benchmarks",
32
"tests/compile/correctness_e2e",
33
"tests/compile/fullgraph",
34
"tests/compile/fusions_e2e",
35
"tests/compile/passes",
36
"tests/config",
37
+ "tests/entrypoints/anthropic",
38
"tests/entrypoints/generate",
39
+ "tests/entrypoints/multimodal",
40
+ "tests/entrypoints/pooling",
41
"tests/entrypoints/serve",
42
"tests/entrypoints/tool_parsers",
43
"tests/entrypoints/unit_tests",
44
"tests/entrypoints/weight_transfer",
45
+ "tests/kernels/attention",
46
"tests/kernels/core",
47
+ "tests/kernels/helion",
48
"tests/kernels/mamba",
49
"tests/kernels/quantization",
50
"tests/model_executor/layers",
51
"tests/models/language",
52
"tests/models/quantization",
53
+ "tests/multimodal",
54
"tests/plugins/bge_m3_sparse_plugin",
55
"tests/plugins/prithvi_io_processor_plugin",
56
"tests/plugins/vllm_add_dummy_platform",
57
"tests/plugins/vllm_add_dummy_stat_logger",
58
"tests/plugins_tests",
59
+ "tests/quantization",
60
+ "tests/reasoning",
61
"tests/samplers",
62
"tests/spec_decode",
63
"tests/tokenizers_",
@@ -59,8 +68,11 @@ SILENT_GROUPS = [
68
"tests/v1/determinism",
69
"tests/v1/distributed",
70
"tests/v1/executor",
71
+ "tests/v1/logits_processors",
72
+ "tests/v1/sample",
73
"tests/v1/shutdown",
74
"tests/v1/simple_kv_offload",
75
+ "tests/v1/spec_decode",
76
"tests/v1/structured_output",
77
]
78
@@ -68,18 +80,12 @@ SILENT_GROUPS = [
80
# from "skip" to "silent", move its directory to SILENT_GROUPS.
81
SEPARATE_GROUPS = [
82
"tests",
71
- "tests/benchmarks",
83
"tests/compile",
84
"tests/distributed",
74
- "tests/entrypoints/anthropic",
85
"tests/entrypoints/llm",
76
- "tests/entrypoints/multimodal",
86
"tests/entrypoints/openai",
78
- "tests/entrypoints/pooling",
87
"tests/entrypoints/speech_to_text",
88
"tests/kernels",
81
- "tests/kernels/attention",
82
- "tests/kernels/helion",
89
"tests/kernels/moe",
90
"tests/lora",
91
"tests/model_executor",
@@ -87,10 +93,7 @@ SEPARATE_GROUPS = [
93
"tests/models",
94
"tests/models/test_initialization.py",
95
"tests/models/multimodal",
90
- "tests/multimodal",
96
"tests/parser",
92
- "tests/quantization",
93
- "tests/reasoning",
97
"tests/renderers",
98
"tests/tool_parsers",
99
"tests/v1",
@@ -101,10 +104,7 @@ SEPARATE_GROUPS = [
104
"tests/v1/engine",
105
"tests/v1/kv_connector",
106
"tests/v1/kv_offload",
104
- "tests/v1/logits_processors",
107
"tests/v1/metrics",
106
- "tests/v1/sample",
107
- "tests/v1/spec_decode",
108
"tests/v1/streaming_input",
109
"tests/v1/worker",
110
]