[MyPy][2/N] Fix mypy errors in small tests/ dirs (#51043)

Signed-off-by: Martin Hickey <martin.hickey@ie.ibm.com>
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-09-29 21:27:45 +01:00
1 parent a96ee5935d
commit 5dd693122c
71 files changed
+491 -301

No files matched your search

@@ -285,7 +285,7 @@ def test_async_tp_pass_requires_full_graph_compilation():
]
async_tp_pass = object.__new__(AsyncTPPass)
async_tp_pass.compilation_config = vllm_config.compilation_config
async_tp_pass.compilation_config = vllm_config.compilation_config # type: ignore[assignment]
with pytest.raises(
AssertionError, match="AsyncTPPass requires full-graph compilation"
@@ -111,7 +111,7 @@ def test_fused_ar_workspace_hidden_dim(
target_hidden: int, draft_hidden: int | None, expected: int
):
config = _make_workspace_hidden_config(target_hidden, draft_hidden)
assert _fused_ar_workspace_hidden_dim(config) == expected
assert _fused_ar_workspace_hidden_dim(config) == expected # type: ignore[arg-type]
def test_fused_ar_workspace_hidden_dim_spec_without_draft():
@@ -120,7 +120,7 @@ def test_fused_ar_workspace_hidden_dim_spec_without_draft():
model_config=SimpleNamespace(get_hidden_size=lambda: 2048),
speculative_config=SimpleNamespace(draft_model_config=None),
)
assert _fused_ar_workspace_hidden_dim(config) == 2048
assert _fused_ar_workspace_hidden_dim(config) == 2048 # type: ignore[arg-type]
class TestAllReduceRMSNormModel(torch.nn.Module):
@@ -222,7 +222,7 @@ def test_sequence_parallelism_pass_requires_full_graph_compilation():
]
sequence_parallelism_pass = object.__new__(SequenceParallelismPass)
sequence_parallelism_pass.compilation_config = vllm_config.compilation_config
sequence_parallelism_pass.compilation_config = vllm_config.compilation_config # type: ignore[assignment]
sequence_parallelism_pass.min_token_num = 1
with pytest.raises(
+3 -1
View File
@@ -33,6 +33,7 @@ from vllm.config import (
VllmConfig,
set_current_vllm_config,
)
from vllm.config.cache import CacheDType
from vllm.forward_context import get_forward_context, set_forward_context
from vllm.model_executor.layers.attention import Attention
from vllm.model_executor.layers.quantization.utils.quant_utils import (
@@ -198,6 +199,7 @@ class TestAttentionAiterFp8StaticQuantPatternModel(
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fp8_linear.kernel.quant_fp8.use_aiter = True
assert self.fp8_linear.input_scale is not None
self.fp8_linear.input_scale.fill_(0.125)
@@ -307,7 +309,7 @@ def test_attention_quant_pattern(
head_size: int,
batch_size: int,
dtype: torch.dtype,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
custom_ops: str,
model_name: str,
model_class: type[AttentionQuantPatternModel],
@@ -24,6 +24,7 @@ from vllm.config import (
PassConfig,
VllmConfig,
)
from vllm.config.cache import CacheDType
from vllm.forward_context import get_forward_context, set_forward_context
from vllm.model_executor.layers.attention import MLAAttention
from vllm.model_executor.layers.linear import ColumnParallelLinear
@@ -254,7 +255,7 @@ def test_mla_rope_kvcache_cat_fusion(
block_size: int,
is_neox: bool,
dtype: torch.dtype,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
monkeypatch: pytest.MonkeyPatch,
):
torch.set_default_device("cuda")
@@ -39,6 +39,7 @@ from vllm.config import (
PassConfig,
VllmConfig,
)
from vllm.config.cache import CacheDType
from vllm.forward_context import get_forward_context, set_forward_context
from vllm.model_executor.layers.attention import Attention
from vllm.model_executor.layers.layernorm import RMSNorm
@@ -342,7 +343,7 @@ def _run_qk_norm_rope_kvcache_fusion_test(
use_shuffle_kv_layout: str,
kv_layout: KVCacheLayout,
dtype: torch.dtype,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
rms_norm_eps: float,
custom_op: str,
monkeypatch: pytest.MonkeyPatch,
@@ -611,7 +612,7 @@ def test_qk_norm_rope_kvcache_fusion(
kv_layout: KVCacheLayout,
block_size: int,
dtype: torch.dtype,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
rms_norm_eps: float,
custom_op: str,
monkeypatch: pytest.MonkeyPatch,
@@ -663,7 +664,7 @@ def test_qk_norm_mrope_kvcache_fusion(
mrope_interleaved: bool,
is_neox: bool,
kv_layout: KVCacheLayout,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
monkeypatch: pytest.MonkeyPatch,
):
_run_qk_norm_rope_kvcache_fusion_test(
@@ -30,6 +30,7 @@ from vllm.config import (
PassConfig,
VllmConfig,
)
from vllm.config.cache import CacheDType
from vllm.config.utils import Range
from vllm.forward_context import get_forward_context, set_forward_context
from vllm.model_executor.layers.attention import Attention
@@ -324,7 +325,7 @@ def test_rope_kvcache_fusion(
block_size: int,
is_neox: bool,
dtype: torch.dtype,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
monkeypatch: pytest.MonkeyPatch,
):
torch.set_default_device("cuda")
@@ -468,7 +469,7 @@ def test_rope_static_qquant_kvcache_fusion(
block_size: int,
is_neox: bool,
dtype: torch.dtype,
kv_cache_dtype: str,
kv_cache_dtype: CacheDType,
monkeypatch: pytest.MonkeyPatch,
):
torch.set_default_device("cuda")
@@ -60,7 +60,7 @@ class TestSiluMulFp8QuantModel(torch.nn.Module):
def __init__(
self,
hidden_size: int,
force_kernel: FP8ScaledMMLinearKernel,
force_kernel: type[FP8ScaledMMLinearKernel],
dtype: torch.dtype,
**kwargs,
):
@@ -282,7 +282,7 @@ def test_fusion_silu_and_mul_quant(
],
enable_silu_mul_custom_op: bool,
enable_quant_fp8_custom_op: bool,
force_kernel: FP8ScaledMMLinearKernel | None,
force_kernel: type[FP8ScaledMMLinearKernel] | None,
monkeypatch: pytest.MonkeyPatch,
):
if model_class is TestSiluMulNvfp4QuantModel and not is_nvfp4_supported():
@@ -330,7 +330,10 @@ def test_fusion_silu_and_mul_quant(
passes = [NoOpEliminationPass(config), *fusion_passes, PostCleanupPass(config)]
backend = TestBackend(*passes)
model = model_class(
hidden_size=hidden_size, force_kernel=force_kernel, x=x, dtype=dtype
hidden_size=hidden_size,
force_kernel=force_kernel, # type: ignore[arg-type]
x=x,
dtype=dtype,
)
# First dimension dynamic
@@ -84,7 +84,7 @@ def test_manual_fusion_fp8_static_with_linear(
num_tokens: int,
hidden_size: int,
dtype: torch.dtype,
force_kernel: FP8ScaledMMLinearKernel,
force_kernel: type[FP8ScaledMMLinearKernel],
):
"""Test manual fusion with real FP8 linear layer (kFp8StaticTensorSym).
@@ -307,7 +307,8 @@ def test_external_process_monitoring(api_server_args):
def run_with_exception_capture():
try:
wait_for_completion_or_failure(
api_server_manager=manager, coordinator=mock_coordinator
api_server_manager=manager,
coordinator=mock_coordinator, # type: ignore[arg-type]
)
except Exception as e:
result["exception"] = e
@@ -376,7 +377,7 @@ def test_gather_actual_addresses_end_to_end():
manager = APIServerProcessManager(
listen_address=f"tcp://{host}:0",
sock=sock,
args="test_args",
args="test_args", # type: ignore[arg-type]
num_servers=num_servers,
input_addresses=placeholder_inputs,
output_addresses=placeholder_outputs,
@@ -423,7 +424,7 @@ def test_gather_actual_addresses_child_crash_before_report():
manager = APIServerProcessManager(
listen_address=f"tcp://{host}:0",
sock=sock,
args="test_args",
args="test_args", # type: ignore[arg-type]
num_servers=num_servers,
input_addresses=placeholder_inputs,
output_addresses=placeholder_outputs,
@@ -108,8 +108,8 @@ async def test_unload_lora_adapter_success():
response = await serving_models.load_lora_adapter(request)
assert len(serving_models.lora_requests) == 1
request = UnloadLoRAAdapterRequest(lora_name="adapter1")
response = await serving_models.unload_lora_adapter(request)
unload_request = UnloadLoRAAdapterRequest(lora_name="adapter1")
response = await serving_models.unload_lora_adapter(unload_request)
assert response == LORA_UNLOADING_SUCCESS_MESSAGE.format(lora_name="adapter1")
assert len(serving_models.lora_requests) == 0
@@ -84,7 +84,10 @@ def task_routes(request, monkeypatch) -> tuple[str, list[tuple[str, list[str]]]]
# Register routers for this specific task (development mode already enabled).
register_api_routers(
args, app, supported_tasks=(task,), model_config=MockModelConfig()
args,
app,
supported_tasks=(task,),
model_config=MockModelConfig(), # type: ignore[arg-type]
)
routes = get_all_http_routes(app)
@@ -19,6 +19,7 @@ from vllm.entrypoints.serve.tokenize.api_router import attach_router
from vllm.entrypoints.serve.tokenize.protocol import (
TokenizeChatRequest,
TokenizeCompletionRequest,
TokenizeResponse,
)
from vllm.entrypoints.serve.tokenize.serving import ServingTokenization
from vllm.exceptions import VLLMNotFoundError, VLLMValidationError
@@ -95,7 +96,7 @@ async def test_tokenize_chat_skips_mm_cache_for_renderer_only_path():
mock_engine.renderer = MagicMock()
serving = _build_serving_tokenization(mock_engine)
serving.online_renderer.preprocess_chat = AsyncMock(
serving.online_renderer.preprocess_chat = AsyncMock( # type: ignore[method-assign]
return_value=(
[{"role": "user", "content": "Test"}],
[{"prompt_token_ids": [1, 2, 3]}],
@@ -109,7 +110,7 @@ async def test_tokenize_chat_skips_mm_cache_for_renderer_only_path():
response = await serving.create_tokenize(request, MagicMock(headers={}))
assert response.tokens == [1, 2, 3]
assert isinstance(response, TokenizeResponse) and response.tokens == [1, 2, 3]
assert (
serving.online_renderer.preprocess_chat.call_args.kwargs["skip_mm_cache"]
is True
@@ -125,7 +126,7 @@ async def test_tokenize_completion_skips_mm_cache_for_renderer_only_path():
mock_engine.renderer = MagicMock()
serving = _build_serving_tokenization(mock_engine)
serving.online_renderer.preprocess_completion = AsyncMock(
serving.online_renderer.preprocess_completion = AsyncMock( # type: ignore[method-assign]
return_value=[{"prompt_token_ids": [1, 2, 3]}]
)
@@ -136,7 +137,7 @@ async def test_tokenize_completion_skips_mm_cache_for_renderer_only_path():
response = await serving.create_tokenize(request, MagicMock(headers={}))
assert response.tokens == [1, 2, 3]
assert isinstance(response, TokenizeResponse) and response.tokens == [1, 2, 3]
assert (
serving.online_renderer.preprocess_completion.call_args.kwargs["skip_mm_cache"]
is True
@@ -101,13 +101,13 @@ async def test_tokenize_chat(
if continue_final:
conversation.append({"role": "assistant", "content": "Sure,"})
prompt = tokenizer.apply_chat_template(
prompt = tokenizer.apply_chat_template( # type: ignore[call-arg]
add_generation_prompt=add_generation,
continue_final_message=continue_final,
conversation=conversation,
tokenize=False,
)
tokens = tokenizer.encode(prompt, add_special_tokens=add_special)
tokens = tokenizer.encode(prompt, add_special_tokens=add_special) # type: ignore[arg-type]
response = requests.post(
server.url_for("tokenize"),
@@ -169,14 +169,14 @@ async def test_tokenize_chat_with_tools(
if continue_final:
conversation.append({"role": "assistant", "content": "Sure,"})
prompt = tokenizer.apply_chat_template(
prompt = tokenizer.apply_chat_template( # type: ignore[call-arg]
add_generation_prompt=add_generation,
continue_final_message=continue_final,
conversation=conversation,
tools=tools,
tokenize=False,
)
tokens = tokenizer.encode(prompt, add_special_tokens=add_special)
tokens = tokenizer.encode(prompt, add_special_tokens=add_special) # type: ignore[arg-type]
response = requests.post(
server.url_for("tokenize"),
@@ -18,7 +18,7 @@ def _cfg(tp=1, pp=1, dp=1, ep=False, digest="a3b21f94deadbeef"):
enable_expert_parallel=ep,
)
)
c.compute_hash = lambda: digest # type: ignore[attr-defined]
c.compute_hash = lambda: digest
return c
@@ -59,6 +59,7 @@ def test_get_respects_set_default():
fp.set_default_fingerprint_mode("hash")
hashed = fp.get_system_fingerprint(cfg)
assert hashed is not None
assert hashed != full
assert "tp8" not in hashed
@@ -72,5 +73,7 @@ def test_get_respects_set_default():
def test_compute_hash_failure_does_not_raise():
cfg = _cfg()
cfg.compute_hash = lambda: (_ for _ in ()).throw(RuntimeError("boom"))
assert fp.build_system_fingerprint(cfg, "full").endswith("-nohash")
assert fp.build_system_fingerprint(cfg, "hash").endswith("-nohash")
full_fp = fp.build_system_fingerprint(cfg, "full")
assert full_fp is not None and full_fp.endswith("-nohash")
hash_fp = fp.build_system_fingerprint(cfg, "hash")
assert hash_fp is not None and hash_fp.endswith("-nohash")
+18 -16
View File
@@ -17,6 +17,7 @@ from vllm.config import ModelConfig
from vllm.entrypoints.chat_utils import (
MEDIA_CONNECTOR_REGISTRY,
AsyncMultiModalItemTracker,
ChatCompletionMessageParam,
ConversationMessage,
_load_embeds_dict,
_parse_metadata_array,
@@ -215,7 +216,7 @@ def video_url():
@pytest.fixture(scope="module")
def audio_url():
audio = AudioAsset("mary_had_lamb")
return encode_audio_url(*audio.audio_and_sample_rate)
return encode_audio_url(*audio.audio_and_sample_rate) # type: ignore[arg-type]
def _assert_mm_data_is_image_input(
@@ -769,7 +770,9 @@ async def test_text_only_chat_does_not_initialize_media_connector(
):
load_connector = MagicMock()
monkeypatch.setattr(MEDIA_CONNECTOR_REGISTRY, "load", load_connector)
messages = [{"role": "user", "content": "Who are you?"}]
messages: list[ChatCompletionMessageParam] = [
{"role": "user", "content": "Who are you?"}
]
parse_chat_messages(
messages,
@@ -2457,7 +2460,7 @@ def test_parse_chat_messages_image_vision_chunk(
kimi_k2_5_model_config,
image_url,
):
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2493,7 +2496,7 @@ def test_parse_chat_messages_video_vision_chunk(
kimi_k2_5_model_config,
video_url,
):
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2530,7 +2533,7 @@ def test_parse_chat_messages_image_vision_chunk_with_uuid(
image_url,
):
image_uuid = "image_123"
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2568,7 +2571,7 @@ def test_parse_chat_messages_video_vision_chunk_with_uuid(
video_url,
):
video_uuid = "video_456"
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2606,7 +2609,7 @@ def test_parse_chat_messages_mixed_vision_chunk(
image_url,
video_url,
):
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2655,7 +2658,7 @@ def test_parse_chat_messages_mixed_vision_chunk_with_uuid(
):
image_uuid = "image_123"
video_uuid = "video_456"
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2707,7 +2710,7 @@ async def test_parse_chat_messages_mixed_vision_chunk_async(
image_url,
video_url,
):
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2757,7 +2760,7 @@ async def test_parse_chat_messages_mixed_vision_chunk_with_uuid_async(
):
image_uuid = "image_123"
video_uuid = "video_456"
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2808,7 +2811,7 @@ async def test_parse_chat_messages_image_vision_chunk_async(
kimi_k2_5_model_config,
image_url,
):
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2845,7 +2848,7 @@ async def test_parse_chat_messages_video_vision_chunk_async(
kimi_k2_5_model_config,
video_url,
):
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2883,7 +2886,7 @@ async def test_parse_chat_messages_image_vision_chunk_with_uuid_async(
image_url,
):
image_uuid = "image_123"
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2922,7 +2925,7 @@ async def test_parse_chat_messages_video_vision_chunk_with_uuid_async(
video_url,
):
video_uuid = "video_456"
messages = [
messages: list[ChatCompletionMessageParam] = [
{
"role": "user",
"content": [
@@ -2997,8 +3000,7 @@ async def test_resolve_items_runs_modalities_concurrently_and_preserves_order():
finally:
active_fetches -= 1
tracker = AsyncMultiModalItemTracker(MagicMock())
tracker._model_config.is_multimodal_model = True
tracker = AsyncMultiModalItemTracker(MagicMock(is_multimodal_model=True))
tracker.__dict__["mm_processor"] = MagicMock()
tracker._items_by_modality["video"] = [
lambda: _fetch("video-0", 0.02),
+3 -2
View File
@@ -37,7 +37,8 @@ from vllm.utils.flashinfer import (
)
from vllm.utils.import_utils import has_deep_gemm
if current_platform.get_device_capability() < (9, 0):
capability = current_platform.get_device_capability()
if capability is None or capability < (9, 0):
pytest.skip("FP8 Triton requires CUDA 9.0 or higher", allow_module_level=True)
vllm_config = VllmConfig()
@@ -258,7 +259,7 @@ def test_w8a8_block_fp8_torch_scaled_mm_matmul():
stub = BlockWiseTorchFP8ScaledMMLinearKernel.__new__(
BlockWiseTorchFP8ScaledMMLinearKernel
)
stub.config = types.SimpleNamespace(out_dtype=out_dtype)
stub.config = types.SimpleNamespace(out_dtype=out_dtype) # type: ignore[assignment]
out = stub.apply_block_scaled_mm(
A_fp8_cuda.cuda(), B_fp8.cuda(), As_cuda.cuda(), Bs.cuda()
)
@@ -62,7 +62,8 @@ PER_TOKEN_GROUP_SHAPE = (1, -1)
PER_OUT_CH_GROUP_SHAPE = (-1, 1)
capability = current_platform.get_device_capability()
capability = capability[0] * 10 + capability[1]
# Fall back to 0 so import doesn't fail on unknown capability
capability_int = (capability[0] * 10 + capability[1]) if capability is not None else 0
def rand_int8(shape: tuple, device: str = "cuda"):
@@ -639,7 +640,7 @@ def test_cutlass_cuda_graph(per_act_token: bool, per_out_ch: bool):
def test_cutlass_support_opcheck():
opcheck(torch.ops._C.cutlass_scaled_mm_supports_fp8, (capability,))
opcheck(torch.ops._C.cutlass_scaled_mm_supports_fp8, (capability_int,))
@pytest.mark.parametrize("num_experts", [8, 64])
@@ -29,7 +29,8 @@ if not current_platform.is_cuda():
# `is_quant_method_supported` conflates kernels with quantization methods
# an assumption which is breaking down as quantizations methods can have
# have kernels and some kernels support multiple quantization methods.
IS_SUPPORTED_BY_GPU = current_platform.get_device_capability()[0] >= 9
capability = current_platform.get_device_capability()
IS_SUPPORTED_BY_GPU = capability is not None and capability[0] >= 9
MNK_SHAPES = [
(1, 128, 128),
@@ -207,7 +208,7 @@ def mm_test_helper(
a=tensors.a,
b_q=tensors.w_q,
b_group_scales=tensors.w_g_s,
b_group_size=group_size,
b_group_size=group_size, # type: ignore[arg-type]
b_channel_scales=tensors.w_ch_s,
a_token_scales=tensors.w_tok_s,
)
@@ -17,8 +17,9 @@ from vllm.platforms import current_platform
from vllm.scalar_type import ScalarType, scalar_types
from vllm.utils.torch_utils import set_random_seed
capability = current_platform.get_device_capability()
IS_SUPPORTED_BY_GPU = (
current_platform.is_cuda() and current_platform.get_device_capability()[0] >= 9
current_platform.is_cuda() and capability is not None and capability[0] >= 9
)
+6 -2
View File
@@ -12,6 +12,7 @@ from tests.kernels.quant_utils import (
)
from tests.kernels.utils import opcheck
from vllm.model_executor.layers.quantization.utils.quant_utils import (
GroupShape,
scaled_quantize,
)
from vllm.platforms import current_platform
@@ -208,7 +209,10 @@ def test_static_fp8_quant_group_2d(
x = torch.rand(num_tokens, hidden_size, dtype=dtype, device="cuda")
ref_out, scale = scaled_quantize(
x, group_shape, current_platform.fp8_dtype(), compute_dtype=torch.float32
x,
GroupShape(*group_shape),
current_platform.fp8_dtype(),
compute_dtype=torch.float32,
)
ops_out, ops_scale = ops.scaled_fp8_quant(x, scale=scale, group_shape=group_shape)
@@ -236,7 +240,7 @@ def test_static_fp8_quant_1d_scale(
x = torch.rand(num_tokens, hidden_size, dtype=dtype, device="cuda")
ref_out, scale_2d = scaled_quantize(
x, group_shape, FP8_DTYPE, compute_dtype=torch.float32
x, GroupShape(*group_shape), FP8_DTYPE, compute_dtype=torch.float32
)
# Flatten scale to 1D for testing 1D scale path
@@ -38,7 +38,8 @@ CUDA_DEVICES = [
# `is_quant_method_supported` conflates kernels with quantization methods
# an assumption which is breaking down as quantizations methods can have
# have kernels and some kernels support multiple quantization methods.
IS_SUPPORTED_BY_GPU = current_platform.get_device_capability()[0] >= 9
capability = current_platform.get_device_capability()
IS_SUPPORTED_BY_GPU = capability is not None and capability[0] >= 9
MNK_SHAPES = [
(1, 128, 128),
@@ -305,7 +306,7 @@ def test_machete_all_schedules(shape, types: TypeConfig):
if types.group_scale_type is None:
group_sizes = [None]
else:
group_sizes = query_machete_supported_group_sizes(types.act_type)
group_sizes = list(query_machete_supported_group_sizes(types.act_type))
for group_size in group_sizes:
if not group_size_valid(shape, group_size):
@@ -334,7 +335,7 @@ def test_machete_heuristic(shape, types: TypeConfig):
if types.group_scale_type is None:
group_sizes = [None]
else:
group_sizes = query_machete_supported_group_sizes(types.act_type)
group_sizes = list(query_machete_supported_group_sizes(types.act_type))
for group_size in group_sizes:
if not group_size_valid(shape, group_size):
@@ -342,7 +342,7 @@ def marlin_generate_valid_test_cases():
[quant_test_config["b_type"]],
quant_test_config.get("c_type", f16_types),
quant_test_config["group_blocks"],
)
) # type: ignore[call-overload]
for sub_case in inner_combinations:
if (
@@ -11,6 +11,7 @@ import pytest
import torch
from vllm import _custom_ops as ops
from vllm.model_executor.layers.linear import LinearBase
from vllm.model_executor.layers.quantization.utils.marlin_utils import (
GPTQ_MARLIN_TILE,
apply_gptq_marlin_linear,
@@ -524,13 +525,15 @@ def test_check_marlin_supports_layer_allow_tile_padding():
)
# Tile-misaligned but group-aligned: rejected strictly, allowed w/ padding
layer = _FakeLinear(4640, 512, input_size=2048)
layer: LinearBase = _FakeLinear( # type: ignore[assignment]
4640, 512, input_size=2048
)
assert not check_marlin_supports_layer(layer, 128)
assert check_marlin_supports_layer(layer, 128, allow_tile_padding=True)
assert check_marlin_supports_layer(layer, -1, allow_tile_padding=True)
# A group straddling the TP shard cannot be fixed by padding
layer = _FakeLinear(4608, 4672, input_size=18688)
layer = _FakeLinear(4608, 4672, input_size=18688) # type: ignore[assignment]
assert not check_marlin_supports_layer(layer, 128, allow_tile_padding=True)
@@ -206,7 +206,7 @@ def test_nvfp4_emulation_support_check_rejects_bias_and_lora(
in_dtype=torch.bfloat16,
device="cuda",
routing_method=RoutingMethodType.TopK,
**config_kwargs,
**config_kwargs, # type: ignore[arg-type]
)
supported, reason = Nvfp4QuantizationEmulationTritonExperts.is_supported_config(
@@ -35,7 +35,7 @@ pytestmark = pytest.mark.cpu_test
def test_auto_linear_backend_is_unchanged_without_overrides():
kernels = [object]
kernels: list[type] = [object]
quantizations = (
"fp8_block_w8a8",
"fp8_w8a8",
@@ -17,6 +17,7 @@ import vllm.config as vllm_config_module
from vllm.config import VllmConfig, set_current_vllm_config
from vllm.model_executor.layers.fused_moe import utils as fused_moe_utils
from vllm.model_executor.layers.fused_moe.layer import determine_expert_counts
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
from vllm.model_executor.layers.quantization.utils.config_utils import (
@@ -140,10 +141,15 @@ def get_deepseek_v4_quark_config(exclude: list[str]) -> dict[str, Any]:
return quantization_config
def _stub_quant_config() -> QuantizationConfig:
return cast(QuantizationConfig, object())
def get_fse_test_model_config(
model_type: str,
quantization_config: dict[str, Any],
) -> tuple[object, type[nn.Module]]:
config: object
if model_type == "minimax_m3":
config = MiniMaxM3TextConfig(
hidden_size=128,
@@ -301,7 +307,7 @@ def test_resolve_layer_fused_shared_expert_skips_compatibility_when_disabled(
)
assert not fused_moe_utils.resolve_layer_fused_shared_expert(
object(), "model.layers.0.mlp"
_stub_quant_config(), "model.layers.0.mlp"
)
@@ -316,7 +322,7 @@ def test_resolve_layer_fused_shared_expert_normalizes_unavailable_aiter(
assert (
fused_moe_utils.resolve_layer_fused_shared_expert(
object(), "model.layers.0.mlp"
_stub_quant_config(), "model.layers.0.mlp"
)
is False
)
@@ -325,7 +331,7 @@ def test_resolve_layer_fused_shared_expert_normalizes_unavailable_aiter(
def test_resolve_layer_fused_shared_expert_passes_module_prefixes(
monkeypatch: pytest.MonkeyPatch,
) -> None:
quant_config = object()
quant_config = _stub_quant_config()
monkeypatch.setattr(
fused_moe_utils.rocm_aiter_ops,
"is_fusion_moe_shared_experts_enabled",
@@ -374,7 +380,7 @@ def test_resolve_layer_fused_shared_expert_rejects_incompatible_quantization(
)
assert not fused_moe_utils.resolve_layer_fused_shared_expert(
object(), "model.layers.0.mlp"
_stub_quant_config(), "model.layers.0.mlp"
)
assert "shared experts are excluded" in caplog.text
@@ -407,7 +413,7 @@ def test_deepseek_v4_shared_expert_fse_uses_mtp_quantization_config_prefix(
)
compatible, reason = is_shared_expert_quant_fse_compatible(
DeepseekV4Config(),
DeepseekV4Config(), # type: ignore[arg-type]
"model.layers.2.ffn.experts",
"model.layers.2.ffn.shared_experts",
)
@@ -818,7 +824,7 @@ def test_models_fse_init(
model_type, quantization_config
)
vllm_config = VllmConfig()
vllm_config.model_config = SimpleNamespace(
vllm_config.model_config = SimpleNamespace( # type: ignore[assignment]
hf_config=config,
hf_text_config=config,
dtype=torch.bfloat16,
@@ -887,7 +893,7 @@ def test_models_fse_init(
DeepSeekV4MTP,
)
vllm_config.speculative_config = SimpleNamespace(
vllm_config.speculative_config = SimpleNamespace( # type: ignore[assignment]
draft_model_config=SimpleNamespace(hf_config=config),
method="mtp",
parallel_drafting=False,
@@ -1214,7 +1220,7 @@ def test_quark_packed_layer_config_must_match_global_config() -> None:
def test_non_quark_shared_expert_fse_is_incompatible() -> None:
compatible, reason = is_shared_expert_quant_fse_compatible(
object(),
_stub_quant_config(),
"model.layers.0.mlp.experts",
"model.layers.0.mlp.shared_experts",
)
@@ -46,7 +46,7 @@ def test_sparse_attention_refreshes_batch_state_inside_eager_segment(
with pytest.raises(BatchStateRefreshed):
DeepseekV32Attention._sparse_indexer_and_attn(
layer,
layer, # type: ignore[arg-type]
torch.empty(1, dtype=torch.long),
torch.empty(1, 1),
torch.empty(1, 1, 1),
@@ -80,7 +80,7 @@ def make_indexer_metadata(
num_decode_tokens=num_decode_tokens,
num_prefills=num_prefills,
num_prefill_tokens=num_prefill_tokens,
prefill=SimpleNamespace(chunks=[]) if num_prefills else None,
prefill=SimpleNamespace(chunks=[]) if num_prefills else None, # type: ignore[arg-type]
)
@@ -129,7 +129,7 @@ def test_short_prefill_updates_k_cache_before_scoring_decision(
slot_mapping=slot_mapping,
)
if indexer_metadata.num_decodes:
indexer_metadata.decode = object()
indexer_metadata.decode = object() # type: ignore[assignment]
mla_metadata = make_mla_metadata(
use_dense_mha=batch_kind != "force_mqa",
num_decode_tokens=mla_num_decode_tokens,
@@ -307,7 +307,7 @@ def test_deepseek_v32_dispatches_selected_mha(
output = torch.empty(2, 2)
DeepseekV32Attention._sparse_indexer_and_attn(
layer,
layer, # type: ignore[arg-type]
torch.arange(2),
torch.empty(2, 2),
q_nope,
@@ -224,11 +224,11 @@ class TestResolveClassifierActFn:
model_config = SimpleNamespace(
hf_config=SimpleNamespace(num_labels=3, problem_type="")
)
result = resolve_classifier_act_fn(model_config, act_fn=None)
result = resolve_classifier_act_fn(model_config, act_fn=None) # type: ignore[arg-type]
assert isinstance(result, PoolerClassify)
assert result.num_labels == 3
def test_passes_through_provided_act_fn(self):
custom = PoolerIdentity()
result = resolve_classifier_act_fn(None, act_fn=custom)
result = resolve_classifier_act_fn(None, act_fn=custom) # type: ignore[arg-type]
assert result is custom
@@ -15,6 +15,7 @@ from vllm.model_executor.layers.pooler.tokwise.heads import (
TokenEmbeddingPoolerHead,
)
from vllm.pooling_params import PoolingParams
from vllm.tasks import PoolingTask
from vllm.v1.pool.metadata import PoolingMetadata, PoolingStates
_HIDDEN = 16
@@ -24,7 +25,7 @@ _BATCH = 3
def _make_params(
n: int,
*,
task: str = "embed",
task: PoolingTask = "embed",
dimensions: int | None = None,
use_activation: bool | None = None,
) -> list[PoolingParams]:
@@ -320,6 +321,7 @@ class TestTokenEmbeddingPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_embed")
out = head.forward_chunk(x, param)
assert out is not None
assert out.dtype == torch.float16
def test_projector(self):
@@ -328,6 +330,7 @@ class TestTokenEmbeddingPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_embed")
out = head.forward_chunk(x, param)
assert out is not None
assert out.shape == (5, 8)
assert torch.allclose(out, proj(x))
@@ -336,6 +339,7 @@ class TestTokenEmbeddingPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_embed", dimensions=4)
out = head.forward_chunk(x, param)
assert out is not None
assert out.shape == (5, 4)
assert torch.equal(out, x[..., :4])
@@ -360,6 +364,7 @@ class TestTokenEmbeddingPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_embed", dimensions=4)
out = head.forward_chunk(x, param)
assert out is not None
assert out.shape == (5, 4)
assert torch.equal(out, proj(x)[..., :4])
@@ -368,6 +373,7 @@ class TestTokenEmbeddingPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_embed", dimensions=4, use_activation=True)
out = head.forward_chunk(x, param)
assert out is not None
assert out.shape == (5, 4)
norms = torch.linalg.norm(out, dim=-1)
assert torch.allclose(norms, torch.ones(5), atol=1e-5)
@@ -416,6 +422,7 @@ class TestTokenClassifierPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_classify")
out = head.forward_chunk(x, param)
assert out is not None
assert out.dtype == torch.float16
def test_classifier(self):
@@ -424,6 +431,7 @@ class TestTokenClassifierPoolerHead:
x = torch.randn(5, _HIDDEN)
param = PoolingParams(task="token_classify")
out = head.forward_chunk(x, param)
assert out is not None
assert out.shape == (5, 3)
assert torch.allclose(out, clf(x))
@@ -26,6 +26,7 @@ from vllm.model_executor.layers.pooler.tokwise.methods import (
get_tok_pooling_method,
)
from vllm.pooling_params import PoolingParams
from vllm.tasks import PoolingTask
from vllm.v1.pool.metadata import PoolingCursor, PoolingMetadata, PoolingStates
_CPU = torch.device("cpu")
@@ -66,7 +67,7 @@ def _make_pooling_cursor(
def _make_metadata(
prompt_lens: list[int],
*,
tasks: list[str] | None = None,
tasks: list[PoolingTask] | None = None,
token_ids: list[list[int]] | None = None,
pooling_params: list[PoolingParams] | None = None,
num_scheduled_tokens: list[int] | None = None,
@@ -319,7 +320,7 @@ def test_dispatch_seq_cls_honors_token_pooling_type(tok_pooling_type):
),
)
classifier = torch.nn.Linear(4, 3)
with set_current_vllm_config(config):
with set_current_vllm_config(config): # type: ignore[arg-type]
pooler = DispatchPooler.for_seq_cls(pooler_config, classifier=classifier)
token_ids = [[2, 99, 3, 99], [4, 99], [5, 6]]
@@ -114,7 +114,8 @@ async def test_bge_m3_sparse_plugin_online(
response = ret.json()
# Verify the request response is in the correct format
assert (parsed_response := IOProcessorResponse(**response).data)
parsed_response: object = IOProcessorResponse(**response).data
assert parsed_response
# Verify the output is formatted as expected for this plugin
assert _get_attr_or_val(parsed_response, "data")
@@ -79,7 +79,7 @@ def _post_pooling(server: RemoteOpenAIServer, data: dict):
ret = requests.post(server.url_for("pooling"), json=request_payload)
ret.raise_for_status()
response = ret.json()
parsed_response = IOProcessorResponse(**response).data
parsed_response: object = IOProcessorResponse(**response).data
assert parsed_response
return parsed_response
+2 -2
View File
@@ -172,7 +172,7 @@ async def test_init_state_is_noop_without_phase_a(monkeypatch: pytest.MonkeyPatc
monkeypatch.setenv("VLLM_PLUGINS", "dummy_admin_endpoint_plugin")
state = State()
await init_endpoint_plugins_state(_FakeEngineClient(), state, _build_args())
await init_endpoint_plugins_state(_FakeEngineClient(), state, _build_args()) # type: ignore[arg-type]
assert not hasattr(state, "dummy_engine_client")
@@ -223,7 +223,7 @@ async def test_endpoint_plugin_end_to_end(monkeypatch: pytest.MonkeyPatch):
)
fake_engine_client = _FakeEngineClient(rpc_result=["cfg-a", "cfg-b"])
await init_endpoint_plugins_state(fake_engine_client, app.state, args)
await init_endpoint_plugins_state(fake_engine_client, app.state, args) # type: ignore[arg-type]
assert app.state.dummy_engine_client is fake_engine_client
@@ -92,7 +92,7 @@ def test_oot_registration_multimodal(
)
first_token = llm.get_tokenizer().decode(0)
outputs = llm.generate(prompts, sampling_params)
outputs = llm.generate(prompts, sampling_params) # type: ignore[arg-type]
for output in outputs:
generated_text = output.outputs[0].text
@@ -23,7 +23,8 @@ def test_stat_logger_plugin_is_discovered(monkeypatch: pytest.MonkeyPatch):
# instantiate and confirm the right type
vllm_config = VllmConfig()
instance = factories[0](vllm_config)
assert isinstance(instance, DummyStatLogger)
# The `is` check above narrows DummyStatLogger to a Callable
assert isinstance(instance, DummyStatLogger) # type: ignore[arg-type]
def test_no_plugins_loaded_if_env_empty(monkeypatch: pytest.MonkeyPatch):
@@ -66,10 +67,11 @@ async def test_stat_logger_plugin_integration_with_engine(
engine = AsyncLLM.from_engine_args(engine_args=engine_args)
assert engine.logger_manager is not None
assert len(engine.logger_manager.stat_loggers) == 2
assert len(engine.logger_manager.stat_loggers[0].per_engine_stat_loggers) == 1
assert len(engine.logger_manager.stat_loggers[0].per_engine_stat_loggers) == 1 # type: ignore[attr-defined]
assert isinstance(
engine.logger_manager.stat_loggers[0].per_engine_stat_loggers[0],
engine.logger_manager.stat_loggers[0].per_engine_stat_loggers[0], # type: ignore[attr-defined]
DummyStatLogger,
)
@@ -95,7 +95,8 @@ async def test_prithvi_mae_plugin_online(
response = ret.json()
# verify the request response is in the correct format
assert (parsed_response := IOProcessorResponse(**response))
parsed_response: IOProcessorResponse = IOProcessorResponse(**response)
assert parsed_response
# verify the output is formatted as expected for this plugin
plugin_data = parsed_response.data
+6 -4
View File
@@ -16,6 +16,7 @@ from transformers import AutoModelForSeq2SeqLM
from vllm import CompletionOutput, RequestOutput
from vllm.assets.audio import AudioAsset
from vllm.entrypoints.llm import LLM
from vllm.inputs import TokensInput
from vllm.logprobs import Logprob, SampleLogprobs
from vllm.platforms import current_platform
from vllm.sampling_params import BeamSearchParams, StructuredOutputsParams
@@ -107,11 +108,12 @@ def test_beam_search_abort_returns_partial_outputs_and_continues_other_prompts(
llm.renderer = Mock(get_tokenizer=Mock(return_value=tokenizer))
monkeypatch.setattr(llm, "_preprocess_cmpl", lambda prompts: prompts)
monkeypatch.setattr(llm, "_render_and_run_requests", run_requests)
prompts = [
# Engine inputs, since _preprocess_cmpl is patched to the identity
prompts: list[TokensInput] = [
{"type": "token", "prompt_token_ids": [token]} for token in [prompt_token, 2]
]
params = BeamSearchParams(beam_width=2, max_tokens=3)
outputs = llm.beam_search(prompts, params)
outputs = llm.beam_search(prompts, params) # type: ignore[arg-type]
aborted, normal = outputs
expected_tokens = (
@@ -324,7 +326,7 @@ def test_beam_search_structured_output(
llm = LLM(
model=model,
dtype=dtype,
dtype=dtype, # type: ignore[arg-type]
max_model_len=512,
structured_outputs_config=dict(
backend="xgrammar",
@@ -343,7 +345,7 @@ def test_beam_search_structured_output(
"Generate a JSON object for a person with name and age:",
]
outputs = llm.beam_search(prompts, params)
outputs = llm.beam_search(prompts, params) # type: ignore[arg-type]
assert len(outputs) == len(prompts)
for output in outputs:
+10 -5
View File
@@ -5,6 +5,7 @@ import pytest
from vllm import CompletionOutput, RequestOutput
from vllm.entrypoints.generate.beam_search.online import BeamSearchOnlineMixin
from vllm.inputs import TokensInput
from vllm.logprobs import Logprob, SampleLogprobs
from vllm.sampling_params import BeamSearchParams
@@ -56,13 +57,13 @@ class _EngineClient:
class _Serving(BeamSearchOnlineMixin):
renderer = _Renderer()
engine_client = _EngineClient()
renderer = _Renderer() # type: ignore[assignment]
engine_client = _EngineClient() # type: ignore[assignment]
@pytest.mark.asyncio
async def test_beam_search_handles_extra_logprob_candidates() -> None:
prompt = {
prompt: TokensInput = {
"type": "token",
"prompt": "prompt",
"prompt_token_ids": [1],
@@ -90,7 +91,7 @@ async def test_beam_search_handles_extra_logprob_candidates() -> None:
async def test_beam_search_respects_skip_special_tokens(
skip_special_tokens: bool, expected_text: str
) -> None:
prompt = {
prompt: TokensInput = {
"type": "token",
"prompt": "prompt",
"prompt_token_ids": [1],
@@ -140,7 +141,11 @@ async def test_beam_search_abort_returns_partial_outputs(
serving = _Serving()
monkeypatch.setattr(serving.engine_client, "generate", generate)
prompt = {"type": "token", "prompt": "prompt", "prompt_token_ids": [prompt_token]}
prompt: TokensInput = {
"type": "token",
"prompt": "prompt",
"prompt_token_ids": [prompt_token],
}
outputs = [
output
async for output in serving.beam_search(
+9 -4
View File
@@ -8,11 +8,12 @@ Run `pytest tests/samplers/test_no_bad_words.py`.
from transformers import AutoTokenizer
from vllm import LLM, SamplingParams
from tests.conftest import VllmRunner
from vllm import SamplingParams
def _generate(
llm: LLM,
llm: VllmRunner,
prompt: str,
num_prompt_tokens: int,
temperature: float = 0,
@@ -56,7 +57,9 @@ class TestOneTokenBadWord:
output_token_ids = self._generate(llm, bad_words=[self.TARGET_TOKEN])
assert self.target_token_id not in output_token_ids
def _generate(self, llm: LLM, bad_words: list[str] | None = None) -> list[int]:
def _generate(
self, llm: VllmRunner, bad_words: list[str] | None = None
) -> list[int]:
return _generate(
llm=llm,
prompt=self.PROMPT,
@@ -151,7 +154,9 @@ class TestTwoTokenBadWord:
self.neighbour_token_id2 in output_token_ids
)
def _generate(self, llm: LLM, bad_words: list[str] | None = None) -> list[int]:
def _generate(
self, llm: VllmRunner, bad_words: list[str] | None = None
) -> list[int]:
return _generate(
llm=llm,
prompt=self.PROMPT,
+23 -5
View File
@@ -2,7 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from collections.abc import Generator
from typing import Any
from typing import Any, cast
import pytest
from transformers import AutoTokenizer, PythonBackend, TokenizersBackend
@@ -10,6 +10,7 @@ from transformers import AutoTokenizer, PythonBackend, TokenizersBackend
from vllm.sampling_params import SamplingParams
from vllm.tokenizers.detokenizer_utils import convert_ids_list_to_tokens
from vllm.tokenizers.mistral import MistralTokenizer
from vllm.tokenizers.protocol import TokenizerLike
from vllm.v1.engine import EngineCoreRequest
from vllm.v1.engine.detokenizer import (
FastIncrementalDetokenizer,
@@ -281,9 +282,26 @@ class _MockTokenizer:
return "".join(self._decoded[tid] for tid in ids)
def _mock_tokenizer(
raw_tokens: dict[int, str],
decoded_tokens: dict[int, str],
pre_tokenizer_type: str = "Metaspace",
replacement: str | None = "▁",
) -> TokenizerLike:
return cast(
TokenizerLike,
_MockTokenizer(
raw_tokens=raw_tokens,
decoded_tokens=decoded_tokens,
pre_tokenizer_type=pre_tokenizer_type,
replacement=replacement,
),
)
def test_sentencepiece_leading_space_preserved():
"""▁true and true must produce distinct strings."""
tok = _MockTokenizer(
tok = _mock_tokenizer(
raw_tokens={0: "▁true", 1: "true", 2: "▁false", 3: "false"},
decoded_tokens={0: "true", 1: "true", 2: "false", 3: "false"},
)
@@ -297,7 +315,7 @@ def test_sentencepiece_leading_space_preserved():
def test_whitespace_run_tokens_stay_distinct():
"""▁, ▁▁, ▁▁▁ must produce different-length space strings."""
tok = _MockTokenizer(
tok = _mock_tokenizer(
raw_tokens={0: "▁", 1: "▁▁", 2: "▁▁▁"},
decoded_tokens={0: "", 1: " ", 2: " "},
)
@@ -307,7 +325,7 @@ def test_whitespace_run_tokens_stay_distinct():
def test_bpe_leading_space_already_preserved():
"""GPT-2 BPE: Ġtrue already decodes to ' true', no fix needed."""
tok = _MockTokenizer(
tok = _mock_tokenizer(
raw_tokens={0: "Ġtrue", 1: "true"},
decoded_tokens={0: " true", 1: "true"},
pre_tokenizer_type="ByteLevel",
@@ -319,7 +337,7 @@ def test_bpe_leading_space_already_preserved():
def test_logprobs_count_stable_across_k():
"""logprobs=4 and logprobs=10 must return 4 and 10 entries."""
tok = _MockTokenizer(
tok = _mock_tokenizer(
raw_tokens={
0: "▁true",
1: "a",
+2 -2
View File
@@ -61,7 +61,7 @@ def test_thread_pool_tokenizer_pickle(model_id: str):
unpickled_tokenizer = pickle.loads(pickle.dumps(pooled_tokenizer))
assert unpickled_tokenizer is not None
assert isinstance(unpickled_tokenizer, ThreadSafeHFTokenizerMixin)
assert unpickled_tokenizer.encode("prompt") == reference_tokenizer.encode("prompt")
assert unpickled_tokenizer.encode("prompt") == reference_tokenizer.encode("prompt") # type: ignore[attr-defined]
# Idempotence: wrapping an already-pooled tokenizer returns it unchanged.
assert maybe_make_thread_pool(pooled_tokenizer) is pooled_tokenizer
assert maybe_make_thread_pool(pooled_tokenizer) is pooled_tokenizer # type: ignore[type-var]
+26 -12
View File
@@ -10,6 +10,7 @@ from mistral_common.exceptions import InvalidMessageStructureException
from mistral_common.guidance.grammar_factory import GrammarFactory
from mistral_common.tokens.tokenizers.base import SpecialTokenPolicy, SpecialTokens
from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
from vllm.exceptions import VLLMValidationError
from vllm.tokenizers.mistral import (
@@ -21,7 +22,9 @@ from vllm.tokenizers.mistral import (
def test_validate_apply_chat_template_args():
# add_generation_prompt with assistant last message → error
messages = [{"role": "assistant", "content": "Hello"}]
messages: list[ChatCompletionMessageParam] = [
{"role": "assistant", "content": "Hello"}
]
with pytest.raises(ValueError):
_validate_apply_chat_template_args(messages, add_generation_prompt=True)
@@ -32,7 +35,9 @@ def test_validate_apply_chat_template_args():
# both add_generation_prompt and continue_final_message → error
with pytest.raises(ValueError):
_validate_apply_chat_template_args(
messages, add_generation_prompt=True, continue_final_message=True
messages,
add_generation_prompt=True,
continue_final_message=True,
)
# continue_final_message with assistant last message → ok
@@ -835,7 +840,9 @@ class TestMistralTokenizer:
)
def test_apply_chat_template_error(self, mistral_tokenizer: MistralTokenizer):
messages = [{"role": "user", "content": "Hello world !"}]
messages: list[ChatCompletionMessageParam] = [
{"role": "user", "content": "Hello world !"}
]
with pytest.raises(ValueError):
mistral_tokenizer.apply_chat_template(
@@ -968,7 +975,7 @@ class TestMistralTokenizer:
mistral_tokenizer: MistralTokenizer,
):
assert mistral_tokenizer.batch_decode(
[[]],
[[]], # type: ignore[arg-type]
) == [""]
def test_convert_tokens_to_string(self, mistral_tokenizer: MistralTokenizer):
@@ -2167,13 +2174,15 @@ class TestMistralTokenizer:
def test_apply_chat_template_tool_optional_fields(
self,
mistral_tokenizer: MistralTokenizer,
messages: list[dict[str, Any]],
messages: list[ChatCompletionMessageParam],
tools: list[dict[str, Any]],
tekken_expected_substrings: list[str],
spm_expected_substrings: list[str],
) -> None:
output = mistral_tokenizer.apply_chat_template(
messages, tools=tools, add_generation_prompt=True
messages,
tools=tools,
add_generation_prompt=True,
)
decoded = mistral_tokenizer.tokenizer.decode(output, SpecialTokenPolicy.KEEP)
@@ -2188,7 +2197,7 @@ class TestMistralTokenizer:
def test_apply_chat_template_tools_not_mutated(
self, mistral_tokenizer: MistralTokenizer
) -> None:
messages: list[dict[str, Any]] = [
messages: list[ChatCompletionMessageParam] = [
{"role": "user", "content": "Hello"},
]
tools: list[dict[str, Any]] = [
@@ -2209,7 +2218,9 @@ class TestMistralTokenizer:
original_tools = copy.deepcopy(tools)
mistral_tokenizer.apply_chat_template(
messages, tools=tools, add_generation_prompt=True
messages,
tools=tools,
add_generation_prompt=True,
)
assert tools == original_tools
@@ -2235,7 +2246,8 @@ class TestMistralTokenizer:
]
output = mistral_tokenizer.apply_chat_template(
messages, add_generation_prompt=True
messages, # type: ignore[arg-type]
add_generation_prompt=True,
)
decoded = mistral_tokenizer.tokenizer.decode(output, SpecialTokenPolicy.KEEP)
@@ -2310,6 +2322,7 @@ def v15_mistral_tokenizer() -> MistralTokenizer:
return MistralTokenizer.from_pretrained("mistralai/Mistral-Small-4-119B-2603")
except Exception:
pytest.skip("v15 tokenizer unavailable")
raise AssertionError("unreachable")
@pytest.fixture(scope="module")
@@ -2319,6 +2332,7 @@ def v13_mistral_tokenizer() -> MistralTokenizer:
return MistralTokenizer.from_pretrained("mistralai/Magistral-Small-2509")
except Exception:
pytest.skip("v13 tokenizer unavailable")
raise AssertionError("unreachable")
def test_v15_apply_chat_template_passes_reasoning_effort_high(
@@ -2338,7 +2352,7 @@ def test_v15_apply_chat_template_passes_reasoning_effort_high(
)
v15_mistral_tokenizer.apply_chat_template(
messages=_PASSTHROUGH_MESSAGES,
messages=_PASSTHROUGH_MESSAGES, # type: ignore[arg-type]
reasoning_effort="high",
)
@@ -2361,7 +2375,7 @@ def test_v15_apply_chat_template_passes_reasoning_effort_none_by_default(
fake_apply_chat_template,
)
v15_mistral_tokenizer.apply_chat_template(messages=_PASSTHROUGH_MESSAGES)
v15_mistral_tokenizer.apply_chat_template(messages=_PASSTHROUGH_MESSAGES) # type: ignore[arg-type]
assert "reasoning_effort" in captured_kwargs[-1]
assert captured_kwargs[-1]["reasoning_effort"] is None
@@ -2384,7 +2398,7 @@ def test_pre_v15_apply_chat_template_omits_reasoning_effort(
)
v13_mistral_tokenizer.apply_chat_template(
messages=_PASSTHROUGH_MESSAGES,
messages=_PASSTHROUGH_MESSAGES, # type: ignore[arg-type]
reasoning_effort="high",
)
+1 -1
View File
@@ -205,7 +205,7 @@ def test_cached_tokenizer_from_config_registers_local_config(tmp_path: Path):
side_effect=lambda tokenizer: tokenizer,
),
):
tokenizer = cached_tokenizer_from_config(model_config)
tokenizer = cached_tokenizer_from_config(model_config) # type: ignore[arg-type]
assert tokenizer.is_fast is True
finally:
@@ -148,7 +148,7 @@ def test_tool_parser_adjust_request_builds_valid_response_text_config() -> None:
description string that the previous two-step construction injected.
"""
parser = ToolParser.__new__(ToolParser)
parser.model_tokenizer = None
parser.model_tokenizer = None # type: ignore[assignment]
request = _build_responses_request(tool_choice="required")
ToolParser.adjust_request(parser, request)
+28 -21
View File
@@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import json
from typing import cast
import pytest
@@ -15,6 +16,7 @@ from vllm.exceptions import VLLMValidationError
from vllm.parser.kimi_k3 import KimiK3Parser
from vllm.parser.parser_manager import ParserManager
from vllm.reasoning.kimi_k3_reasoning_parser import KimiK3ReasoningParser
from vllm.tokenizers.protocol import TokenizerLike
from vllm.tool_parsers.kimi_k3_tool_parser import KimiK3ToolParser
OPEN = "<|open|>"
@@ -37,6 +39,10 @@ class DummyTokenizer:
return [ord(ch) for ch in text]
def _dummy_tokenizer() -> TokenizerLike:
return cast(TokenizerLike, DummyTokenizer())
class KimiK3DelegatingParser(KimiK3Parser):
reasoning_parser_cls = KimiK3ReasoningParser
tool_parser_cls = KimiK3ToolParser
@@ -124,7 +130,7 @@ def _tools(*calls: str) -> str:
def test_extract_tool_calls_with_response_and_typed_arguments():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
output = _response("answer") + _tools(
_call(
@@ -150,7 +156,7 @@ def test_extract_tool_calls_with_response_and_typed_arguments():
def test_delegating_parser_preserves_tool_calls_after_reasoning():
parser = KimiK3DelegatingParser(DummyTokenizer())
parser = KimiK3DelegatingParser(_dummy_tokenizer())
output = (
f"{THINK_OPEN}step{THINK_CLOSE}"
+ _response("answer")
@@ -172,7 +178,7 @@ def test_delegating_parser_preserves_tool_calls_after_reasoning():
def test_delegating_parser_required_tool_choice_uses_xtml_parser():
parser = KimiK3DelegatingParser(DummyTokenizer())
parser = KimiK3DelegatingParser(_dummy_tokenizer())
request = _request().model_copy(update={"tool_choice": "required"})
output = (
f"{THINK_OPEN}step{THINK_CLOSE}"
@@ -195,7 +201,7 @@ def test_delegating_parser_required_tool_choice_uses_xtml_parser():
def test_delegating_parser_named_tool_choice_uses_xtml_parser():
parser = KimiK3DelegatingParser(DummyTokenizer())
parser = KimiK3DelegatingParser(_dummy_tokenizer())
output = (
f"{THINK_OPEN}step{THINK_CLOSE}"
+ _response("")
@@ -218,7 +224,7 @@ def test_delegating_parser_named_tool_choice_uses_xtml_parser():
def test_delegating_parser_auto_no_call_strips_consumed_response_prefix():
parser = KimiK3DelegatingParser(
DummyTokenizer(), chat_template_kwargs={"thinking": False}
_dummy_tokenizer(), chat_template_kwargs={"thinking": False}
)
request = _request().model_copy(
update={"chat_template_kwargs": {"thinking": False}}
@@ -237,7 +243,7 @@ def test_delegating_parser_auto_no_call_strips_consumed_response_prefix():
def test_delegating_parser_required_call_strips_consumed_response_prefix():
parser = KimiK3DelegatingParser(
DummyTokenizer(), chat_template_kwargs={"thinking": False}
_dummy_tokenizer(), chat_template_kwargs={"thinking": False}
)
request = _request().model_copy(
update={
@@ -263,7 +269,7 @@ def test_delegating_parser_required_call_strips_consumed_response_prefix():
def test_delegating_parser_truncated_tools_do_not_leak_xtml():
parser = KimiK3DelegatingParser(
DummyTokenizer(), chat_template_kwargs={"thinking": False}
_dummy_tokenizer(), chat_template_kwargs={"thinking": False}
)
request = _request().model_copy(
update={
@@ -284,7 +290,7 @@ def test_delegating_parser_truncated_tools_do_not_leak_xtml():
def test_extract_tool_calls_unescapes_attributes():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
output = _tools(_call("a&amp;b&quot;c", 1, _arg("k&amp;q", "string", "v")))
extracted = parser.extract_tool_calls(output, _request())
@@ -295,7 +301,7 @@ def test_extract_tool_calls_unescapes_attributes():
def test_extract_tool_calls_allows_less_than_in_attributes():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
output = _tools(_call("calc<beta", 1, _arg("foo<bar", "string", "raw")))
extracted = parser.extract_tool_calls(output, _request())
@@ -306,7 +312,7 @@ def test_extract_tool_calls_allows_less_than_in_attributes():
def test_extract_content_from_whitespace_degraded_markers():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
extracted = parser.extract_tool_calls(
f"{OPEN} response {SEP}answer{CLOSE} response {SEP}",
@@ -318,7 +324,7 @@ def test_extract_content_from_whitespace_degraded_markers():
def test_streaming_split_markers_do_not_leak():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
request = _request()
previous_text = ""
previous_ids: list[int] = []
@@ -362,21 +368,22 @@ def test_streaming_split_markers_do_not_leak():
assert OPEN not in content
assert SEP not in content
assert len(tool_deltas) == 1
assert tool_deltas[0].function is not None
assert tool_deltas[0].function.name == "calc"
assert json.loads(tool_deltas[0].function.arguments) == {"x": 1}
assert json.loads(tool_deltas[0].function.arguments) == {"x": 1} # type: ignore[arg-type]
def test_tool_call_ids_are_unique_across_messages():
output = _tools(_call("calc", 1))
first = KimiK3ToolParser(DummyTokenizer()).extract_tool_calls(output, _request())
second = KimiK3ToolParser(DummyTokenizer()).extract_tool_calls(output, _request())
first = KimiK3ToolParser(_dummy_tokenizer()).extract_tool_calls(output, _request())
second = KimiK3ToolParser(_dummy_tokenizer()).extract_tool_calls(output, _request())
assert first.tool_calls[0].id != second.tool_calls[0].id
def test_streaming_consumed_response_prefix_no_call_keeps_content():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
request = _request()
previous_text = ""
previous_ids: list[int] = []
@@ -406,7 +413,7 @@ def test_streaming_consumed_response_prefix_no_call_keeps_content():
def test_delegating_parser_tool_choice_none_strips_xtml_and_suppresses_calls():
parser = KimiK3DelegatingParser(
DummyTokenizer(), chat_template_kwargs={"thinking": False}
_dummy_tokenizer(), chat_template_kwargs={"thinking": False}
)
request = _request().model_copy(
update={
@@ -443,7 +450,7 @@ def test_delegating_parser_tool_choice_none_strips_xtml_and_suppresses_calls():
def test_adjust_request_keeps_xtml_markers_contiguous():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
request = _request()
adjusted = parser.adjust_request(request)
@@ -455,7 +462,7 @@ def test_adjust_request_keeps_xtml_markers_contiguous():
def test_adjust_request_required_uses_xtml_parser_not_json_guidance():
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
request = _request().model_copy(update={"tool_choice": "required"})
adjusted = parser.adjust_request(request)
@@ -476,7 +483,7 @@ def test_adjust_request_required_uses_xtml_parser_not_json_guidance():
],
)
def test_adjust_request_rejects_named_tool_choice(tool_request):
parser = KimiK3ToolParser(DummyTokenizer())
parser = KimiK3ToolParser(_dummy_tokenizer())
with pytest.raises(VLLMValidationError) as exc_info:
parser.adjust_request(tool_request)
@@ -512,7 +519,7 @@ def test_responses_chat_params_keeps_template_tool_choice_when_api_auto():
def test_responses_required_tool_choice_uses_xtml_parser():
parser = KimiK3DelegatingParser(
DummyTokenizer(), chat_template_kwargs={"thinking": False}
_dummy_tokenizer(), chat_template_kwargs={"thinking": False}
)
request = _responses_request(tool_choice="required").model_copy(
update={"chat_template_kwargs": {"thinking": False}}
@@ -538,7 +545,7 @@ def test_responses_required_tool_choice_uses_xtml_parser():
def test_responses_named_tool_choice_uses_xtml_parser():
parser = KimiK3DelegatingParser(
DummyTokenizer(), chat_template_kwargs={"thinking": False}
_dummy_tokenizer(), chat_template_kwargs={"thinking": False}
)
request = _responses_request(
tool_choice={"type": "function", "name": "calc"}
+36 -20
View File
@@ -24,9 +24,11 @@ a real tokenizer live in ``test_muse_glimmer_parse_delta.py``.
import json
from types import SimpleNamespace
from typing import cast
import pytest
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
from vllm.reasoning.muse_glimmer_reasoning_parser import MuseGlimmerReasoningParser
from vllm.tool_parsers.muse_glimmer_tool_parser import MuseGlimmerToolParser
@@ -61,10 +63,21 @@ class _FakeReq:
tools = None
def _req(*names):
def _fake_req() -> ChatCompletionRequest:
return cast(ChatCompletionRequest, _FakeReq())
# The parsers accept a missing request at runtime.
_NO_REQ = cast(ChatCompletionRequest, None)
def _req(*names: str) -> ChatCompletionRequest:
"""A request with ``names`` registered as tools."""
return SimpleNamespace(
tools=[SimpleNamespace(function=SimpleNamespace(name=n)) for n in names]
return cast(
ChatCompletionRequest,
SimpleNamespace(
tools=[SimpleNamespace(function=SimpleNamespace(name=n)) for n in names]
),
)
@@ -90,7 +103,7 @@ def test_single_tool_call_after_reasoning():
'<atem:parameter name="units">celsius</atem:parameter>\n'
"</atem:invoke>\n</atem:function_calls><|eot|>"
)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, None)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, _NO_REQ)
assert out.tools_called and len(out.tool_calls) == 1
assert out.tool_calls[0].function.name == "weather.get"
assert json.loads(out.tool_calls[0].function.arguments) == {
@@ -112,7 +125,7 @@ def test_parallel_calls_across_eom_boundaries():
'<atem:parameter name="b">4</atem:parameter>\n'
"</atem:invoke>\n</atem:function_calls><|eot|>"
)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, None)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, _NO_REQ)
assert out.tools_called and len(out.tool_calls) == 2, len(out.tool_calls)
assert [t.function.name for t in out.tool_calls] == ["math.add", "math.mul"]
# JSON-typed values decode to ints
@@ -127,14 +140,14 @@ def test_echoed_invoke_in_reasoning_is_not_parsed():
"but I will not.<|eom|>"
"<|start|>assistant to=user<|message|>The answer is 42.<|eot|>"
)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, None)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, _NO_REQ)
assert not out.tools_called, "channel scoping failed -- echoed invoke parsed!"
assert out.content == "The answer is 42.", repr(out.content)
def test_plain_answer_yields_no_tool_calls():
out = MuseGlimmerToolParser.extract_tool_calls(
T, "to=user<|message|>Just a plain answer.<|eot|>", None
T, "to=user<|message|>Just a plain answer.<|eot|>", _NO_REQ
)
assert not out.tools_called
@@ -147,7 +160,7 @@ def test_json_object_array_and_bool_params_decode():
'<atem:parameter name="flag">true</atem:parameter>\n'
"</atem:invoke>\n</atem:function_calls><|eot|>"
)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, None)
out = MuseGlimmerToolParser.extract_tool_calls(T, raw, _NO_REQ)
assert json.loads(out.tool_calls[0].function.arguments) == {
"payload": {"nested": [1, 2, 3]},
"flag": True,
@@ -166,10 +179,10 @@ def test_reasoning_to_toolcall_handoff():
'<atem:parameter name="city">Paris</atem:parameter>\n'
"</atem:invoke>\n</atem:function_calls>"
)
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(R, raw, None)
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(R, raw, _NO_REQ)
assert reasoning == "Let me call the tool.", repr(reasoning)
assert content is not None and "<atem:invoke" in content, repr(content)
out = MuseGlimmerToolParser.extract_tool_calls(T, content, None)
out = MuseGlimmerToolParser.extract_tool_calls(T, content, _NO_REQ)
assert out.tools_called and len(out.tool_calls) == 1
assert out.tool_calls[0].function.name == "weather.get"
@@ -179,15 +192,17 @@ def test_reasoning_then_user_answer():
" to=self<|message|>thinking<|eom|>"
"<|start|>assistant to=user<|message|>The answer is 42.<|eot|>"
)
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(R, raw, None)
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(R, raw, _NO_REQ)
assert reasoning == "thinking", repr(reasoning)
assert content == "The answer is 42.", repr(content)
assert not MuseGlimmerToolParser.extract_tool_calls(T, content, None).tools_called
assert not MuseGlimmerToolParser.extract_tool_calls(
T, content, _NO_REQ
).tools_called
def test_plain_content_without_framing_passes_through():
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(
R, "Just a direct answer.", None
R, "Just a direct answer.", _NO_REQ
)
assert reasoning is None and content == "Just a direct answer.", (
reasoning,
@@ -207,9 +222,10 @@ def test_reasoning_then_parallel_calls():
'<atem:parameter name="a">3</atem:parameter>\n</atem:invoke>\n'
"</atem:function_calls><|eot|>"
)
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(R, raw, None)
reasoning, content = MuseGlimmerReasoningParser.extract_reasoning(R, raw, _NO_REQ)
assert reasoning == "need two calls", repr(reasoning)
out = MuseGlimmerToolParser.extract_tool_calls(T, content, None)
assert content is not None
out = MuseGlimmerToolParser.extract_tool_calls(T, content, _NO_REQ)
assert [t.function.name for t in out.tool_calls] == ["math.add", "math.mul"], (
out.tool_calls
)
@@ -231,7 +247,7 @@ def _stream(raw: str, chunk: int):
R, prev, cur, delta, [], [], []
)
if dm is not None:
if getattr(dm, "reasoning", None):
if dm.reasoning:
reasoning.append(dm.reasoning)
content_delta = getattr(dm, "content", None)
# Tool-channel content is an internal handoff to the tool parser,
@@ -239,7 +255,7 @@ def _stream(raw: str, chunk: int):
if content_delta and "<atem:function_calls>" not in content_delta:
content.append(content_delta)
dt = MuseGlimmerToolParser.extract_tool_calls_streaming(
T, prev, cur, delta, [], [], [], _FakeReq()
T, prev, cur, delta, [], [], [], _fake_req()
)
if dt is not None and dt.tool_calls:
toolcalls.extend(dt.tool_calls)
@@ -319,11 +335,11 @@ def test_streaming_reasoning_then_content():
def test_truncated_cot_no_toolcall_nonstreaming():
out = MuseGlimmerToolParser.extract_tool_calls(T, RAW_TRUNCATED, _FakeReq())
out = MuseGlimmerToolParser.extract_tool_calls(T, RAW_TRUNCATED, _fake_req())
assert not out.tools_called and out.tool_calls == []
# partial reasoning must still be recovered by the reasoning parser
reasoning, _ = MuseGlimmerReasoningParser.extract_reasoning(
R, RAW_TRUNCATED, _FakeReq()
R, RAW_TRUNCATED, _fake_req()
)
assert reasoning and "Maybe I should call" in reasoning, repr(reasoning)
@@ -374,7 +390,7 @@ def test_trailing_segment_ambiguous_left_alone():
def test_no_registered_tools_passthrough():
out = MuseGlimmerToolParser.extract_tool_calls(
T, _call("get_weather.get_weather"), None
T, _call("get_weather.get_weather"), _NO_REQ
)
assert out.tool_calls[0].function.name == "get_weather.get_weather"
@@ -203,6 +203,7 @@ def test_input_image_detail_defaults_or_preserves(detail: str | None) -> None:
request = ResponsesRequest.model_validate({"input": [item]})
assert isinstance(request.input, list)
assert request.input[0]["content"][0]["detail"] == (
"auto" if detail is None else detail
)
+5 -6
View File
@@ -306,18 +306,17 @@ def _collect_required_tool_streaming_json(output_json: str, delta_len: int) -> s
combined_messages = "["
for message in messages:
if message.tool_calls[0].function.name:
fn = message.tool_calls[0].function
assert fn is not None
if fn.name:
if len(combined_messages) > 1:
combined_messages += "},"
combined_messages += (
'{"name": "'
+ message.tool_calls[0].function.name
+ '", "parameters": '
+ message.tool_calls[0].function.arguments
'{"name": "' + fn.name + '", "parameters": ' + (fn.arguments or "")
)
else:
combined_messages += message.tool_calls[0].function.arguments
combined_messages += fn.arguments or ""
combined_messages += "}]"
return combined_messages
+9 -9
View File
@@ -476,32 +476,32 @@ def test_compilation_config_mode_validator():
"""Test that CompilationConfig.mode field validator converts strings to integers."""
from vllm.config.compilation import CompilationConfig, CompilationMode
config = CompilationConfig(mode=0)
config = CompilationConfig(mode=0) # type: ignore[arg-type]
assert config.mode == CompilationMode.NONE
config = CompilationConfig(mode=3)
config = CompilationConfig(mode=3) # type: ignore[arg-type]
assert config.mode == CompilationMode.VLLM_COMPILE
config = CompilationConfig(mode="NONE")
config = CompilationConfig(mode="NONE") # type: ignore[arg-type]
assert config.mode == CompilationMode.NONE
config = CompilationConfig(mode="STOCK_TORCH_COMPILE")
config = CompilationConfig(mode="STOCK_TORCH_COMPILE") # type: ignore[arg-type]
assert config.mode == CompilationMode.STOCK_TORCH_COMPILE
config = CompilationConfig(mode="DYNAMO_TRACE_ONCE")
config = CompilationConfig(mode="DYNAMO_TRACE_ONCE") # type: ignore[arg-type]
assert config.mode == CompilationMode.DYNAMO_TRACE_ONCE
config = CompilationConfig(mode="VLLM_COMPILE")
config = CompilationConfig(mode="VLLM_COMPILE") # type: ignore[arg-type]
assert config.mode == CompilationMode.VLLM_COMPILE
config = CompilationConfig(mode="none")
config = CompilationConfig(mode="none") # type: ignore[arg-type]
assert config.mode == CompilationMode.NONE
config = CompilationConfig(mode="vllm_compile")
config = CompilationConfig(mode="vllm_compile") # type: ignore[arg-type]
assert config.mode == CompilationMode.VLLM_COMPILE
with pytest.raises(ValidationError, match="Invalid compilation mode"):
CompilationConfig(mode="INVALID_MODE")
CompilationConfig(mode="INVALID_MODE") # type: ignore[arg-type]
def test_flat_product():
+1 -1
View File
@@ -128,7 +128,7 @@ def test_lru_cache():
def test_lru_cache_put_if_fits():
cache = LRUCache(10, getsizeof=lambda x: x)
cache = LRUCache[str, int](10, getsizeof=lambda x: x)
assert cache.put_if_fits("ok", 4) is True
assert cache["ok"] == 4
+2 -2
View File
@@ -206,8 +206,8 @@ def test_make_zmq_socket_ipv6():
def test_make_zmq_path():
assert make_zmq_path("tcp", "127.0.0.1", "5555") == "tcp://127.0.0.1:5555"
assert make_zmq_path("tcp", "::1", "5555") == "tcp://[::1]:5555"
assert make_zmq_path("tcp", "127.0.0.1", 5555) == "tcp://127.0.0.1:5555"
assert make_zmq_path("tcp", "::1", 5555) == "tcp://[::1]:5555"
def test_get_tcp_uri():
@@ -257,7 +257,7 @@ class TestCudagraphDispatcher:
def test_get_capture_descs_empty_when_not_initialized(self):
"""Test that get_capture_descs returns empty list when keys not initialized."""
comp_config = CompilationConfig(
cudagraph_mode="FULL",
cudagraph_mode=CUDAGraphMode.FULL,
mode=CompilationMode.NONE,
cudagraph_capture_sizes=[1, 8],
)
@@ -419,7 +419,7 @@ def _run_and_monitor_call(
def test_capture_replay_bypass_logic():
comp_config = CompilationConfig(
mode=CompilationMode.VLLM_COMPILE,
cudagraph_mode="FULL",
cudagraph_mode=CUDAGraphMode.FULL,
cudagraph_capture_sizes=[1, 2],
)
vllm_config = _create_vllm_config(comp_config)
@@ -489,7 +489,7 @@ def test_nested_wrappers():
"""Tests a scenario with a PIECEWISE wrapper inside a FULL one."""
comp_config = CompilationConfig(
mode=CompilationMode.VLLM_COMPILE,
cudagraph_mode="FULL",
cudagraph_mode=CUDAGraphMode.FULL,
cudagraph_capture_sizes=[1],
)
vllm_config = _create_vllm_config(comp_config)
+5 -4
View File
@@ -38,7 +38,7 @@ def _reset_graph_pool_id():
def _create_vllm_config() -> MagicMock:
compilation_config = CompilationConfig(
cudagraph_mode="FULL",
cudagraph_mode=CUDAGraphMode.FULL,
cudagraph_capture_sizes=[4],
)
compilation_config.max_cudagraph_capture_size = 4
@@ -134,7 +134,7 @@ def test_piecewise_capture_uses_pcp_dummy_slot_mappings():
device=torch.device("cpu"),
max_num_reqs=num_reqs,
max_num_tokens=num_tokens,
block_tables=pcp_block_tables,
block_tables=pcp_block_tables, # type: ignore[arg-type]
)
block_tables = MagicMock()
@@ -224,7 +224,7 @@ def test_speculator_capture_preserves_decode_query_bounds(
)
manager = SpeculatorCudaGraphManager.__new__(SpeculatorCudaGraphManager)
manager.max_num_reqs = num_reqs
manager.vllm_config = SimpleNamespace(
manager.vllm_config = SimpleNamespace( # type: ignore[assignment]
parallel_config=SimpleNamespace(data_parallel_size=1)
)
buffers = InputBuffers(num_reqs, num_tokens, torch.device("cpu"))
@@ -264,7 +264,7 @@ def _create_decode_vllm_config(
use_kda_recoverssm: bool = False,
) -> MagicMock:
compilation_config = CompilationConfig(
cudagraph_mode="FULL_AND_PIECEWISE",
cudagraph_mode=CUDAGraphMode.FULL_AND_PIECEWISE,
cudagraph_capture_sizes=capture_sizes,
)
compilation_config.max_cudagraph_capture_size = capture_sizes[-1]
@@ -473,6 +473,7 @@ def test_dynamic_spec_decode_shared_token_count_stays_reachable(monkeypatch):
assert any(len(descs) > 1 for descs in by_num_tokens.values())
for desc in full_descs:
assert desc.num_reqs is not None
assert desc in manager._candidates[(desc.num_tokens, 0)], desc
assert (
manager.dispatch(
+57 -7
View File
@@ -14,7 +14,7 @@ Test organization:
from collections.abc import Hashable
from types import SimpleNamespace
from typing import Any
from typing import Any, cast
from unittest.mock import MagicMock, patch
import pytest
@@ -100,6 +100,50 @@ class _MockModel(SupportsEncoderCudaGraph):
def get_encoder_cudagraph_budget_range(self, vllm_config):
return (self._min_budget, self._max_budget)
def get_max_frames_per_video(self) -> int:
return 0
def get_encoder_cudagraph_item_specs(
self, mm_kwargs: dict[str, Any]
) -> list[EncoderItemSpec]:
return []
def prepare_encoder_cudagraph_capture_inputs(
self,
token_budget: int,
max_batch_size: int,
max_frames_per_batch: int,
device: torch.device,
dtype: torch.dtype,
path: str = "default",
axis_keys: tuple[Hashable, ...] | None = None,
) -> EncoderCudaGraphCaptureInputs:
return EncoderCudaGraphCaptureInputs(values={})
def prepare_encoder_cudagraph_replay_buffers(
self,
mm_kwargs: dict[str, Any],
max_batch_size: int,
max_frames_per_batch: int,
path: str = "default",
) -> EncoderCudaGraphReplayBuffers:
return EncoderCudaGraphReplayBuffers(values={})
def encoder_cudagraph_forward(
self, inputs: dict[str, torch.Tensor], path: str = "default"
) -> torch.Tensor:
return torch.zeros(0)
def encoder_eager_forward(
self, mm_kwargs: dict[str, Any], path: str = "default"
) -> torch.Tensor:
return torch.zeros(0)
def select_encoder_cudagraph_items(
self, mm_kwargs: dict[str, Any], indices: list[int]
) -> dict[str, Any]:
return {}
def _make_manager_with_budgets(budgets: list[int]) -> EncoderCudaGraphManager:
"""Create a minimal EncoderCudaGraphManager with only token_budgets set.
@@ -594,6 +638,7 @@ def test_eonly_capture_preserves_outputs_across_replay_and_fallback(profile_only
ECConnectorBase,
ECConnectorMetadata,
)
from vllm.v1.core.sched.output import SchedulerOutput
from vllm.v1.worker.gpu.ec_connector import ActiveECConnector
from vllm.v1.worker.gpu.mm.encoder_runner import EncoderRunner
from vllm.v1.worker.mm_encoder_model_runner import MMEncoderModelRunner
@@ -607,7 +652,7 @@ def test_eonly_capture_preserves_outputs_across_replay_and_fallback(profile_only
encoder.device = device
encoder.cudagraph_manager = manager
runner = object.__new__(MMEncoderModelRunner)
runner.model_state = SimpleNamespace(encoder_runner=encoder)
runner.model_state = SimpleNamespace(encoder_runner=encoder) # type: ignore[assignment]
# No decoder manager is installed: capture must be encoder-only.
with patch(
"vllm.v1.worker.mm_encoder_model_runner.lock_workspace", wraps=lock_workspace
@@ -628,9 +673,12 @@ def test_eonly_capture_preserves_outputs_across_replay_and_fallback(profile_only
with patch(
"vllm.v1.worker.gpu.ec_connector.get_ec_transfer", return_value=connector
):
ec = ActiveECConnector(SimpleNamespace(), cache)
scheduled = SimpleNamespace(
ec_connector_metadata=ECConnectorMetadata(), finished_req_ids=frozenset()
ec = ActiveECConnector(SimpleNamespace(), cache) # type: ignore[arg-type]
scheduled = cast(
SchedulerOutput,
SimpleNamespace(
ec_connector_metadata=ECConnectorMetadata(), finished_req_ids=frozenset()
),
)
saved_outputs: list[tuple[torch.Tensor, torch.Tensor]] = []
# Mixed sizes exercise packing order; 64 is the boundary, 81 falls back.
@@ -643,6 +691,7 @@ def test_eonly_capture_preserves_outputs_across_replay_and_fallback(profile_only
outputs = manager.execute(inputs)
assert outputs is not None
cache[str(len(cache))] = outputs[0]
assert ec_output is not None
assert ec_output.finished_sending is None
assert outputs is not None
for actual, eager in zip(outputs, expected):
@@ -656,6 +705,7 @@ def test_eonly_capture_preserves_outputs_across_replay_and_fallback(profile_only
connector.get_finished.return_value = (set(cache), None)
with ec.maybe_get_output(scheduled) as ec_output:
pass
assert ec_output is not None
assert ec_output.finished_sending == set(cache)
@@ -666,7 +716,7 @@ def test_eonly_without_encoder_graph_skips_capture():
encoder = object.__new__(EncoderRunner)
encoder.cudagraph_manager = None
runner = object.__new__(MMEncoderModelRunner)
runner.model_state = SimpleNamespace(encoder_runner=encoder)
runner.model_state = SimpleNamespace(encoder_runner=encoder) # type: ignore[assignment]
assert runner.capture_model() == 0
@@ -973,7 +1023,7 @@ class TestInitInvariantValidation:
vllm_config = _MockVllmConfig(token_budgets, max_mm_items)
model = _MockModel(min_budget, max_budget)
return EncoderCudaGraphManager(
vllm_config=vllm_config,
vllm_config=vllm_config, # type: ignore[arg-type]
device=torch.device("cpu"),
dtype=torch.float32,
model=model,
+5 -12
View File
@@ -155,7 +155,7 @@ def test_v1_generation_is_deterministic_across_batch_sizes_with_needle(
# Ensure engines are shutdown to free GPU/VRAM across test sessions
if llm is not None:
with contextlib.suppress(Exception):
llm.shutdown()
llm.llm_engine.engine_core.shutdown()
@skip_unsupported
@@ -456,7 +456,7 @@ def test_logprobs_bitwise_batch_invariance_ragged_chunked_prefill(backend):
)
finally:
with contextlib.suppress(Exception):
llm.shutdown()
llm.llm_engine.engine_core.shutdown()
@skip_unsupported
@@ -505,7 +505,7 @@ def test_simple_generation(backend):
finally:
with contextlib.suppress(Exception):
llm.shutdown()
llm.llm_engine.engine_core.shutdown()
@skip_unsupported
@@ -815,15 +815,8 @@ def test_decode_logprobs_match_prefill_logprobs(
if token_idx == 0:
prefix_prompt = prompt
else:
# Use the partial output text up to this token
# We'll need to construct this from the full output
prefix_output = decode_output.outputs[0]
# Get the text for tokens 0 to token_idx-1
# Unfortunately, we don't have per-token text, so we'll use
# a different approach: run prefill with prompt + tokens[0:token_idx]
# Actually, we need to get the actual text. Let's use a workaround:
# Run a generation with max_tokens = token_idx to get that prefix
# No per-token text, so regenerate with max_tokens = token_idx
# to get the prefix text
prefix_sp = SamplingParams(
temperature=0.0,
max_tokens=token_idx,
@@ -12,6 +12,7 @@ from PIL import Image
from utils import _extract_step_logprobs, skip_if_not_cuda
from vllm import LLM, SamplingParams
from vllm.inputs import TextPrompt
VLM_TEST_MODEL = os.getenv("VLLM_VLM_TEST_MODEL", "Qwen/Qwen3-VL-2B-Instruct")
@@ -30,8 +31,8 @@ def _make_image(seed: int, size: int) -> Image.Image:
return Image.fromarray(rng.integers(0, 255, (size, size, 3), dtype=np.uint8))
def _make_inputs(input_type: str, num_reqs: int) -> list[dict]:
inputs = []
def _make_inputs(input_type: str, num_reqs: int) -> list[TextPrompt]:
inputs: list[TextPrompt] = []
for i in range(num_reqs):
if input_type == "image":
prompt = IMAGE_PROMPT
@@ -54,7 +55,7 @@ def _make_inputs(input_type: str, num_reqs: int) -> list[dict]:
return inputs
def _assert_batch_invariant(llm: LLM, inputs: list[dict], sampling) -> None:
def _assert_batch_invariant(llm: LLM, inputs: list[TextPrompt], sampling) -> None:
bs1 = [
_extract_step_logprobs(llm.generate([inp], sampling, use_tqdm=False)[0])
for inp in inputs
@@ -134,4 +135,4 @@ def _run_vlm_batch_invariance(
_assert_batch_invariant(llm, inputs, sampling)
finally:
with contextlib.suppress(Exception):
llm.shutdown()
llm.llm_engine.engine_core.shutdown()
@@ -221,13 +221,15 @@ def _make_cutlass_fp4_moe_batch_invariant_case(
in_dtype=dtype,
activation=activation,
)
prepare_finalize = maybe_make_prepare_finalize(
moe=moe_config,
quant_config=quant_config,
allow_new_interface=True,
use_monolithic=False,
)
assert prepare_finalize is not None
kernel = mk.FusedMoEKernel(
maybe_make_prepare_finalize(
moe=moe_config,
quant_config=quant_config,
allow_new_interface=True,
use_monolithic=False,
),
prepare_finalize,
CutlassExpertsFp4(
moe_config=moe_config,
quant_config=quant_config,
@@ -97,4 +97,4 @@ def test_dense_nvfp4_generation_is_deterministic_across_batch_sizes_e2e(backend)
finally:
if llm is not None:
with contextlib.suppress(Exception):
llm.shutdown()
llm.llm_engine.engine_core.shutdown()
@@ -524,6 +524,7 @@ def test_rms_norm_batch_invariance(dtype):
batch[4] = row[0]
out_batch = rms_norm_batch_invariant(batch, weight, eps=eps)
assert isinstance(out_single, torch.Tensor) and isinstance(out_batch, torch.Tensor)
assert torch.equal(out_single[0], out_batch[4]), (
"rms_norm output for a row differs when batch context changes"
)
@@ -547,6 +548,7 @@ if __name__ == "__main__":
# Batch-invariant implementation
triton_output = rms_norm_batch_invariant(input_tensor, weight, eps=eps)
assert isinstance(triton_output, torch.Tensor)
# Compare
max_diff = (triton_output - reference_output).abs().max().item()
+5 -5
View File
@@ -85,12 +85,12 @@ def test_multiproc_executor_worker_termination_timeout(
monkeypatch.setattr(multiproc_executor_module.time, "sleep", clock.sleep)
executor = MultiprocExecutor.__new__(MultiprocExecutor)
proc = _FakeProcess(clock, exits_at=exits_at)
executor._ensure_worker_termination([proc])
executor._ensure_worker_termination([proc]) # type: ignore[list-item]
assert proc.terminate_called is expected_terminate
class CustomMultiprocExecutor(MultiprocExecutor):
def collective_rpc(
def collective_rpc( # type: ignore[override]
self,
method: str | Callable,
timeout: float | None = None,
@@ -98,7 +98,7 @@ class CustomMultiprocExecutor(MultiprocExecutor):
kwargs: dict | None = None,
non_block: bool = False,
unique_reply_rank: int | None = None,
kv_output_aggregator: KVOutputAggregator = None,
kv_output_aggregator: KVOutputAggregator | None = None,
ec_output_aggregator: ECOutputAggregator | None = None,
) -> Any | list[Any] | Future[Any | list[Any]]:
# Drop marker to show that this was run
@@ -126,7 +126,7 @@ def test_custom_executor_type_checking():
model=MODEL,
gpu_memory_utilization=0.2,
max_model_len=8192,
distributed_executor_backend=Mock,
distributed_executor_backend=Mock, # type: ignore[arg-type]
)
LLMEngine.from_engine_args(engine_args)
with pytest.raises(ValueError):
@@ -134,7 +134,7 @@ def test_custom_executor_type_checking():
model=MODEL,
gpu_memory_utilization=0.2,
max_model_len=8192,
distributed_executor_backend=Mock,
distributed_executor_backend=Mock, # type: ignore[arg-type]
)
AsyncLLM.from_engine_args(engine_args)
@@ -55,6 +55,7 @@ def _flush_gpu_cache(llm: LLM, sampling_params: SamplingParams, seed: int = 0):
block_size = cache_config.block_size
# Use 1.2x GPU capacity to give the lazy cursor enough scheduling steps
# to walk past all target blocks near the tail of the free queue.
assert num_gpu_blocks is not None
total_tokens_needed = int(num_gpu_blocks * block_size * 1.5)
# Use token-id prompts so each filler is unique (no prefix sharing).
+5 -1
View File
@@ -260,6 +260,7 @@ def test_block_stored_per_group_metadata_full_attention() -> None:
)
assert ev.block_hashes == [expected_hash_0]
assert ev.parent_block_hash is None
assert req.prompt_token_ids is not None
assert ev.token_ids == req.prompt_token_ids[0:BLOCK_SIZE]
# Second block's parent_block_hash equals the first block's hash.
@@ -296,7 +297,7 @@ def test_eager_store_lora_metadata() -> None:
f"expected lora_name='test-lora', got {ev.lora_name!r}"
)
assert ev.extra_keys is not None, "expected extra_keys for lora request"
assert any("test-lora" in keys for keys in ev.extra_keys), (
assert any(keys is not None and "test-lora" in keys for keys in ev.extra_keys), (
f"expected 'test-lora' in extra_keys, got {ev.extra_keys}"
)
assert len(ev.token_ids) == BLOCK_SIZE
@@ -372,6 +373,7 @@ def test_finished_eager_store_emits_all_storage_events() -> None:
secondary_event_hash = maybe_convert_block_hash(get_block_hash(secondary_hash))
by_hash = {event.block_hashes[0]: event for event in stored}
assert set(by_hash) == {*primary_event_hashes, secondary_event_hash}
assert req.prompt_token_ids is not None
assert (
by_hash[primary_event_hashes[0]].token_ids == req.prompt_token_ids[:BLOCK_SIZE]
)
@@ -451,6 +453,7 @@ def test_secondary_hash_block_stored_metadata() -> None:
by_hash = {event.block_hashes[0]: event for event in stored}
assert set(by_hash) == {primary_event_hash, secondary_event_hash}
assert by_hash[primary_event_hash].block_size == BLOCK_SIZE
assert req.prompt_token_ids is not None
assert by_hash[primary_event_hash].token_ids == req.prompt_token_ids[:BLOCK_SIZE]
assert by_hash[secondary_event_hash].block_size == BLOCK_SIZE // 2
assert (
@@ -752,6 +755,7 @@ def test_mamba_align_skips_positional_event_metadata() -> None:
stored = [e for e in events if isinstance(e, BlockStored)]
assert len(stored) == 2
assert {ev.group_idx for ev in stored} == {0}
assert req.prompt_token_ids is not None
for idx, ev in enumerate(stored):
assert ev.block_size == vbs
assert ev.token_ids == req.prompt_token_ids[idx * vbs : (idx + 1) * vbs]
+5 -4
View File
@@ -19,6 +19,7 @@ from vllm.config import (
SchedulerConfig,
VllmConfig,
)
from vllm.config.cache import MambaCacheMode
from vllm.config.kv_events import KVEventsConfig
from vllm.distributed.kv_transfer.kv_connector.v1.simple_cpu_offload_connector import (
SimpleCPUOffloadConnector,
@@ -1243,7 +1244,7 @@ def test_preemption_no_cpu_block_leak() -> None:
block_hasher=req._block_hasher,
)
hit_tokens, is_async = sched.get_num_new_matched_tokens(req2, num_computed_tokens=0)
assert hit_tokens > 0
assert hit_tokens is not None and hit_tokens > 0
gpu_blocks2 = fix.gpu_block_pool.get_new_blocks(num_blocks)
kv_blocks2 = KVCacheBlocks(blocks=(gpu_blocks2,))
@@ -1338,7 +1339,7 @@ def test_inflight_finish_deferred_cleanup() -> None:
block_hasher=req._block_hasher,
)
hit_tokens, _ = sched.get_num_new_matched_tokens(req2, num_computed_tokens=0)
assert hit_tokens > 0
assert hit_tokens is not None and hit_tokens > 0
gpu_blocks2 = fix.gpu_block_pool.get_new_blocks(num_blocks)
kv_blocks2 = KVCacheBlocks(blocks=(gpu_blocks2,))
@@ -1896,7 +1897,7 @@ def test_reset_pending_loads() -> None:
block_hasher=req._block_hasher,
)
hit_tokens, is_async = sched.get_num_new_matched_tokens(req2, num_computed_tokens=0)
assert hit_tokens > 0
assert hit_tokens is not None and hit_tokens > 0
gpu_blocks2 = gpu_pool.get_new_blocks(num_blocks)
kv_blocks2 = KVCacheBlocks(blocks=(gpu_blocks2,))
@@ -2246,7 +2247,7 @@ def _make_hybrid_attention_mamba_scheduler(
hash_block_size: int | None = None,
dcp_world_size: int = 4,
lazy: bool = False,
mamba_cache_mode: str = "align",
mamba_cache_mode: MambaCacheMode = "align",
enable_kv_cache_events: bool = False,
) -> SchedulerFixture:
"""Build a scheduler for one attention group plus one Mamba group."""
+25 -18
View File
@@ -36,6 +36,7 @@ from vllm.v1.core.kv_cache_utils import (
)
from vllm.v1.kv_cache_interface import (
FullAttentionSpec,
KVCacheConfig,
KVCacheGroupSpec,
KVCacheLayout,
KVCacheSpec,
@@ -50,7 +51,10 @@ from vllm.v1.simple_kv_offload.cuda_mem_ops import (
build_params,
)
from vllm.v1.simple_kv_offload.disk_backend import DiskBackend
from vllm.v1.simple_kv_offload.metadata import SimpleCPUOffloadMetadata
from vllm.v1.simple_kv_offload.metadata import (
SimpleCPUOffloadMetadata,
SimpleCPUOffloadWorkerMetadata,
)
from vllm.v1.simple_kv_offload.worker import SimpleCPUOffloadWorker
from vllm.v1.worker.gpu.kv_connector import ActiveKVConnector
from vllm.v1.worker.kv_connector_model_runner_mixin import KVConnectorModelRunnerMixin
@@ -64,6 +68,12 @@ ITERS = 30
SLEEP_CYCLES = 50_000_000
def _make_worker(
kv_cache_config: KVCacheConfig | None, cpu_capacity_bytes: int
) -> SimpleCPUOffloadWorker:
return SimpleCPUOffloadWorker(None, kv_cache_config, cpu_capacity_bytes) # type: ignore[arg-type]
def _make_backend() -> tuple[DmaCopyBackend, torch.Tensor, torch.Tensor]:
gpu = {"k": torch.zeros((NUM_BLOCKS, BLOCK_BYTES), dtype=torch.int8, device="cuda")}
cpu = {
@@ -87,7 +97,7 @@ def _make_backend() -> tuple[DmaCopyBackend, torch.Tensor, torch.Tensor]:
def test_no_forward_step_completes_cpu_store(monkeypatch, use_v2):
"""A finished request's final store must drain without another model step."""
backend, gpu, cpu = _make_backend()
worker = SimpleCPUOffloadWorker(None, None, cpu_capacity_bytes=0)
worker = _make_worker(None, 0)
worker._backend = backend
connector = SimpleCPUOffloadConnector.__new__(SimpleCPUOffloadConnector)
connector.worker_handler = worker
@@ -105,13 +115,13 @@ def test_no_forward_step_completes_cpu_store(monkeypatch, use_v2):
active = ActiveKVConnector.__new__(ActiveKVConnector)
active.kv_connector = connector
active._disabled = False
active._pending_load_start = False
active._pending_load_kwargs = None
module = "vllm.v1.worker.gpu.kv_connector"
monkeypatch.setattr(f"{module}.is_forward_context_available", lambda: True)
monkeypatch.setattr(f"{module}.get_forward_context", lambda: None)
result = active.no_forward(output)
else:
result = KVConnectorModelRunnerMixin.kv_connector_no_forward(output, None)
result = KVConnectorModelRunnerMixin.kv_connector_no_forward(output, None) # type: ignore[arg-type]
completion = (
result.kv_connector_output.kv_connector_worker_meta
if result.kv_connector_output is not None
@@ -123,6 +133,7 @@ def test_no_forward_step_completes_cpu_store(monkeypatch, use_v2):
completion = connector.build_connector_worker_meta()
time.sleep(0.001)
assert completion is not None, "CPU store never completed on the empty step"
assert isinstance(completion, SimpleCPUOffloadWorkerMetadata)
assert completion.completed_store_events == {0: 1}
assert torch.equal(cpu[0], gpu[0].cpu())
finally:
@@ -132,7 +143,7 @@ def test_no_forward_step_completes_cpu_store(monkeypatch, use_v2):
def test_deferred_store_waits_for_draft_forward(monkeypatch):
"""Target completion must not submit the store before draft finalization."""
backend, gpu, cpu = _make_backend()
worker = SimpleCPUOffloadWorker(None, None, cpu_capacity_bytes=0)
worker = _make_worker(None, 0)
worker._backend = backend
connector = SimpleCPUOffloadConnector.__new__(SimpleCPUOffloadConnector)
connector.worker_handler = worker
@@ -267,11 +278,12 @@ class _RecordingBackend:
def test_transfer_hooks_pass_wait_event_for_store_only():
"""wait_for_save gates stores on a compute-done event; start_load_kv does not."""
worker = SimpleCPUOffloadWorker(
vllm_config=None, kv_cache_config=None, cpu_capacity_bytes=0
worker = _make_worker(
kv_cache_config=None,
cpu_capacity_bytes=0,
)
recording = _RecordingBackend()
worker._backend = recording
worker._backend = recording # type: ignore[assignment]
worker._connector_metadata = SimpleCPUOffloadMetadata(
load_event=0,
load_gpu_blocks=[0],
@@ -332,8 +344,7 @@ def test_register_shared_kv_cache_storage(monkeypatch, layout: KVCacheLayout):
)
],
)
worker = SimpleCPUOffloadWorker(
vllm_config=None,
worker = _make_worker(
kv_cache_config=cache_config,
cpu_capacity_bytes=raw.nbytes,
)
@@ -366,8 +377,7 @@ def test_register_kv_cache_storage_with_trailing_padding(monkeypatch):
cache_bytes = num_blocks * block_bytes
raw = torch.zeros(4096, dtype=torch.int8, device="cuda")
cache = raw[:cache_bytes].view(num_blocks, block_bytes)
worker = SimpleCPUOffloadWorker(
vllm_config=None,
worker = _make_worker(
kv_cache_config=MagicMock(
num_blocks=num_blocks,
kv_cache_tensors=[
@@ -412,8 +422,7 @@ def test_register_separate_kv_head_groups(monkeypatch):
)
caches = dense_kv_cache_views(raw, spec, num_blocks, num_layers, layout)
layer_names = [f"layer.{i}" for i in range(num_layers)]
worker = SimpleCPUOffloadWorker(
vllm_config=None,
worker = _make_worker(
kv_cache_config=MagicMock(
num_blocks=num_blocks,
kv_cache_tensors=[
@@ -509,8 +518,7 @@ def test_register_mixed_page_sizes_in_one_cache_group(monkeypatch):
assert len(set(pages)) > 1, "the mixed page sizes are what this test covers"
kv_caches = allocate_kv_cache(kv_cache_config, torch.device("cuda"), layout)
worker = SimpleCPUOffloadWorker(
vllm_config=None,
worker = _make_worker(
kv_cache_config=kv_cache_config,
cpu_capacity_bytes=sum(pages) * num_blocks,
)
@@ -567,8 +575,7 @@ def test_register_mixed_page_sizes_odd_block_counts(monkeypatch, rank_blocks):
assert kv_cache_config.num_blocks == rank_blocks
kv_caches = allocate_kv_cache(kv_cache_config, torch.device("cuda"), layout)
worker = SimpleCPUOffloadWorker(
vllm_config=None,
worker = _make_worker(
kv_cache_config=kv_cache_config,
cpu_capacity_bytes=sum(pages) * rank_blocks,
)
@@ -91,7 +91,7 @@ def test_grammar_bitmask_with_specdec():
json='{"type": "object"}',
),
)
sampling_params.structured_outputs._backend = "guidance"
sampling_params.structured_outputs._backend = "guidance" # type: ignore[union-attr]
sampling_params.update_from_generation_config({}, tokenizer.eos_token_id)
my_req_id = f"my_req_id_{i}"
@@ -107,19 +107,23 @@ def test_grammar_bitmask_with_specdec():
def grammar_bitmask(req: Request, tokens: list[int]) -> None:
structured_output_manager.grammar_bitmask(
requests={req.request_id: req},
structured_output_request_ids={req.request_id: 0},
structured_output_request_ids=[req.request_id],
scheduled_spec_decode_tokens={req.request_id: tokens},
)
# At this point, we rolled-back, so should not be terminated
assert not req.structured_output_request.grammar.is_terminated()
assert req.structured_output_request is not None
grammar = req.structured_output_request.grammar
assert grammar is not None and not isinstance(grammar, Exception)
assert not grammar.is_terminated()
# The grammar might not yet be compiled, so we wait for it
assert request.structured_output_request is not None
while not request.structured_output_request._check_grammar_completion():
continue
assert request.structured_output_request.grammar.accept_tokens(
request.request_id, prompt[:i]
)
grammar = request.structured_output_request.grammar
assert grammar is not None and not isinstance(grammar, Exception)
assert grammar.accept_tokens(request.request_id, prompt[:i])
grammar_bitmask(request, prompt[i:] + [tokenizer.eos_token_id])
grammar_bitmask(
@@ -155,7 +159,7 @@ def test_grammar_init_async_and_sync(async_grammar):
json='{"type": "object"}',
),
)
sampling_params.structured_outputs._backend = "guidance"
sampling_params.structured_outputs._backend = "guidance" # type: ignore[union-attr]
sampling_params.update_from_generation_config({}, tokenizer.eos_token_id)
request = Request(
@@ -169,6 +173,7 @@ def test_grammar_init_async_and_sync(async_grammar):
# Check the internal _grammar type immediately after init
# Before _check_grammar_completion is called, async mode should have a Future
assert request.structured_output_request is not None
raw_grammar = request.structured_output_request._grammar
if async_grammar:
assert isinstance(raw_grammar, Future), (
@@ -191,7 +196,7 @@ def test_grammar_init_async_and_sync(async_grammar):
# Verify grammar is properly initialized and functional
grammar = request.structured_output_request.grammar
assert grammar is not None
assert grammar is not None and not isinstance(grammar, Exception)
assert not grammar.is_terminated()
# Verify the grammar can accept valid tokens
@@ -13,6 +13,7 @@ from mistral_common.protocol.instruct.tool_calls import Tool, ToolChoiceEnum
from transformers import AutoTokenizer
from vllm.config import StructuredOutputsConfig, VllmConfig
from vllm.config.structured_outputs import StructuredOutputsBackend
from vllm.exceptions import VLLMValidationError
from vllm.sampling_params import SamplingParams, StructuredOutputsParams
from vllm.tokenizers.mistral import MistralTokenizer
@@ -41,13 +42,15 @@ EBNF_GRAMMAR = 'root ::= "id=" [0-9]+'
def _validate(
grammar: str, backend: str = "xgrammar", tokenizer: object | None = None
grammar: str,
backend: StructuredOutputsBackend = "xgrammar",
tokenizer: object | None = None,
) -> StructuredOutputsParams:
params = SamplingParams(structured_outputs=StructuredOutputsParams(grammar=grammar))
params._validate_structured_outputs(
_StubModelConfig(),
_StubModelConfig(), # type: ignore[arg-type]
StructuredOutputsConfig(backend=backend),
tokenizer=tokenizer or object(),
tokenizer=tokenizer or object(), # type: ignore[arg-type]
)
assert params.structured_outputs is not None
return params.structured_outputs
@@ -51,7 +51,7 @@ def make_vllm_config() -> VllmConfig:
config = VllmConfig(
structured_outputs_config=StructuredOutputsConfig(backend="xgrammar"),
)
config.model_config = SimpleNamespace(
config.model_config = SimpleNamespace( # type: ignore[assignment]
hf_config=SimpleNamespace(model_type="plamo3")
)
return config
@@ -73,7 +73,7 @@ def test_plamo3_python_tokenizer_compiles_and_accepts_structural_tag():
tokenizer = Plamo3Tokenizer(["<eos>", *call_tokens])
backend = XgrammarBackend(
make_vllm_config(),
tokenizer=tokenizer,
tokenizer=tokenizer, # type: ignore[arg-type]
vocab_size=len(tokenizer.get_vocab()),
)
tools = [
@@ -107,7 +107,7 @@ def test_plamo3_grammar_matches_decoded_bytes(value):
tokenizer = Plamo3Tokenizer(list(dict.fromkeys(["<eos>", value, *pieces])))
backend = XgrammarBackend(
make_vllm_config(),
tokenizer=tokenizer,
tokenizer=tokenizer, # type: ignore[arg-type]
vocab_size=len(tokenizer.get_vocab()),
)
for tokens in ([value], pieces):
@@ -10,9 +10,11 @@ from transformers import AutoTokenizer
from vllm.config import DeviceConfig, StructuredOutputsConfig, VllmConfig
from vllm.config.model import ModelConfig
from vllm.config.speculative import SpeculativeConfig
from vllm.config.structured_outputs import StructuredOutputsBackend
from vllm.sampling_params import SamplingParams, StructuredOutputsParams
from vllm.v1.request import Request
from vllm.v1.structured_output import StructuredOutputManager
from vllm.v1.structured_output.backend_outlines import OutlinesGrammar
TOKENIZER = "gpt2"
THINK_END = "\n" # reasoning-end marker (single GPT-2 token)
@@ -84,7 +86,7 @@ def _wait_for_grammar(request: Request) -> None:
def _build_harness(
tokenizer,
backend: str,
backend: StructuredOutputsBackend,
prefix: str = "",
use_reasoner: bool = True,
reasoning_ended: bool | None = None,
@@ -106,13 +108,13 @@ def _build_harness(
)
manager = StructuredOutputManager(vllm_config)
if use_reasoner:
manager.reasoner_cls = MockReasoner
manager.reasoner_cls = MockReasoner # type: ignore[assignment]
sampling_params = SamplingParams(
structured_outputs=structured_outputs
or StructuredOutputsParams(json=JSON_SCHEMA)
)
sampling_params.structured_outputs._backend = backend
sampling_params.structured_outputs._backend = backend # type: ignore[union-attr]
sampling_params.update_from_generation_config({}, tokenizer.eos_token_id)
prompt_ids = tokenizer.encode(prefix) if prefix else []
@@ -130,9 +132,10 @@ def _build_harness(
structured_req = request.structured_output_request
assert request.prompt_token_ids is not None
assert structured_req is not None
assert structured_req.grammar is not None
grammar = structured_req.grammar
assert grammar is not None and not isinstance(grammar, Exception)
if prompt_ids:
assert structured_req.grammar.accept_tokens(
assert grammar.accept_tokens(
request.request_id,
prompt_ids,
)
@@ -173,7 +176,7 @@ def _run_real_flow(
structured_req = request.structured_output_request
assert structured_req is not None
grammar = structured_req.grammar
assert grammar is not None
assert grammar is not None and not isinstance(grammar, Exception)
assert not grammar.is_terminated()
# `bitmask[i]` is the grammar state after the first `i` scheduled tokens.
# These tests commit the validated prefix, so the matching post-accept
@@ -335,7 +338,7 @@ FLOW_CASES = [
@pytest.mark.parametrize("case", FLOW_CASES)
def test_real_flow(
tokenizer,
backend: str,
backend: StructuredOutputsBackend,
case: FlowCase,
):
if backend == "guidance" and case.xfail_guidance:
@@ -379,7 +382,7 @@ def test_real_flow(
)
def test_initial_constraint_activation(
tokenizer,
backend: str,
backend: StructuredOutputsBackend,
use_reasoner: bool,
reasoning_ended: bool | None,
enable_in_reasoning: bool,
@@ -431,7 +434,7 @@ def test_initial_constraint_activation(
)
def test_regex_flow(
tokenizer,
backend: str,
backend: StructuredOutputsBackend,
raw_drafts: tuple[str, ...],
expected_validated: tuple[str, ...] | None,
expected_row_pattern: str,
@@ -459,7 +462,9 @@ def test_regex_flow(
@pytest.mark.parametrize("backend", REGEX_BACKENDS)
def test_rejected_draft_keeps_later_rows_constrained(tokenizer, backend: str):
def test_rejected_draft_keeps_later_rows_constrained(
tokenizer, backend: StructuredOutputsBackend
):
"""A draft the grammar rejects during bitmask fill must not unconstrain
the remaining rows or the bonus row."""
manager, request = _build_harness(
@@ -492,7 +497,7 @@ def test_outlines_termination(tokenizer):
structured_req = request.structured_output_request
assert structured_req is not None
grammar = structured_req.grammar
assert grammar is not None
assert isinstance(grammar, OutlinesGrammar)
one, eos = _to_token_ids(tokenizer, ("1", EOS))
assert not grammar.accept_tokens(request.request_id, [eos])
+11 -10
View File
@@ -35,7 +35,7 @@ def test_structured_outputs_rejected_for_diffusion_models():
)
with pytest.raises(VLLMValidationError, match="not yet supported for diffusion"):
params._validate_structured_outputs(
_StubModelConfig(is_diffusion=True),
_StubModelConfig(is_diffusion=True), # type: ignore[arg-type]
StructuredOutputsConfig(),
tokenizer=None,
)
@@ -45,7 +45,7 @@ def test_plain_request_allowed_for_diffusion_models():
"""Requests without structured outputs are unaffected by the guard."""
params = SamplingParams()
params._validate_structured_outputs(
_StubModelConfig(is_diffusion=True),
_StubModelConfig(is_diffusion=True), # type: ignore[arg-type]
StructuredOutputsConfig(),
tokenizer=None,
)
@@ -71,9 +71,9 @@ def test_degenerate_structured_outputs_rejected(structured_outputs, match):
params = SamplingParams(structured_outputs=structured_outputs)
with pytest.raises(VLLMValidationError, match=match):
params._validate_structured_outputs(
_StubModelConfig(is_diffusion=False),
_StubModelConfig(is_diffusion=False), # type: ignore[arg-type]
StructuredOutputsConfig(),
tokenizer=object(),
tokenizer=object(), # type: ignore[arg-type]
)
@@ -97,9 +97,9 @@ def test_regex_with_nul_byte_rejected(regex):
# (which would otherwise catch the error and fall back to another backend).
with pytest.raises(VLLMValidationError, match="NUL"):
params._validate_structured_outputs(
_StubModelConfig(is_diffusion=False),
_StubModelConfig(is_diffusion=False), # type: ignore[arg-type]
StructuredOutputsConfig(),
tokenizer=object(),
tokenizer=object(), # type: ignore[arg-type]
)
# The xgrammar backend also rejects it directly (defense in depth), before
@@ -133,9 +133,9 @@ def test_unsupported_grammar_is_a_client_error(backend, structured_outputs):
params = SamplingParams(structured_outputs=structured_outputs)
with pytest.raises(VLLMClientError):
params._validate_structured_outputs(
_StubModelConfig(is_diffusion=False),
_StubModelConfig(is_diffusion=False), # type: ignore[arg-type]
StructuredOutputsConfig(backend=backend),
tokenizer=object(),
tokenizer=object(), # type: ignore[arg-type]
)
@@ -180,8 +180,9 @@ def test_auto_backend_falls_back_on_unsupported_schema(schema, expected_backend)
"""`auto` falls back on rejection, so it must catch what the validators raise."""
params = SamplingParams(structured_outputs=StructuredOutputsParams(json=schema))
params._validate_structured_outputs(
_StubModelConfig(is_diffusion=False),
_StubModelConfig(is_diffusion=False), # type: ignore[arg-type]
StructuredOutputsConfig(backend="auto"),
tokenizer=object(),
tokenizer=object(), # type: ignore[arg-type]
)
assert params.structured_outputs is not None
assert params.structured_outputs._backend == expected_backend
+15 -17
View File
@@ -31,24 +31,37 @@ SILENT_GROUPS = [
"tests/compile/correctness_e2e",
"tests/compile/fullgraph",
"tests/compile/fusions_e2e",
"tests/compile/passes",
"tests/config",
"tests/entrypoints/generate",
"tests/entrypoints/serve",
"tests/entrypoints/tool_parsers",
"tests/entrypoints/unit_tests",
"tests/entrypoints/weight_transfer",
"tests/kernels/core",
"tests/kernels/mamba",
"tests/kernels/quantization",
"tests/model_executor/layers",
"tests/models/language",
"tests/models/quantization",
"tests/plugins/bge_m3_sparse_plugin",
"tests/plugins/prithvi_io_processor_plugin",
"tests/plugins/vllm_add_dummy_platform",
"tests/plugins/vllm_add_dummy_stat_logger",
"tests/plugins_tests/gguf",
"tests/plugins_tests/lora_resolvers",
"tests/plugins_tests",
"tests/samplers",
"tests/spec_decode",
"tests/tokenizers_",
"tests/tool_use",
"tests/transformers_utils",
"tests/utils_",
"tests/v1/cudagraph",
"tests/v1/determinism",
"tests/v1/distributed",
"tests/v1/executor",
"tests/v1/shutdown",
"tests/v1/simple_kv_offload",
"tests/v1/structured_output",
]
# After fixing errors resulting from changing follow_imports
@@ -57,57 +70,42 @@ SEPARATE_GROUPS = [
"tests",
"tests/benchmarks",
"tests/compile",
"tests/compile/passes",
"tests/distributed",
"tests/entrypoints/anthropic",
"tests/entrypoints/llm",
"tests/entrypoints/multimodal",
"tests/entrypoints/openai",
"tests/entrypoints/pooling",
"tests/entrypoints/serve",
"tests/entrypoints/speech_to_text",
"tests/entrypoints/unit_tests",
"tests/kernels",
"tests/kernels/attention",
"tests/kernels/helion",
"tests/kernels/moe",
"tests/kernels/quantization",
"tests/lora",
"tests/model_executor",
"tests/model_executor/layers",
"tests/model_executor/model_loader",
"tests/models",
"tests/models/test_initialization.py",
"tests/models/multimodal",
"tests/multimodal",
"tests/parser",
"tests/plugins_tests",
"tests/quantization",
"tests/reasoning",
"tests/renderers",
"tests/samplers",
"tests/tokenizers_",
"tests/tool_parsers",
"tests/tool_use",
"tests/utils_",
"tests/v1",
"tests/v1/attention",
"tests/v1/core",
"tests/v1/cudagraph",
"tests/v1/determinism",
"tests/v1/e2e",
"tests/v1/ec_connector",
"tests/v1/engine",
"tests/v1/executor",
"tests/v1/kv_connector",
"tests/v1/kv_offload",
"tests/v1/logits_processors",
"tests/v1/metrics",
"tests/v1/sample",
"tests/v1/simple_kv_offload",
"tests/v1/spec_decode",
"tests/v1/streaming_input",
"tests/v1/structured_output",
"tests/v1/worker",
]