@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c01f6e525f
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 00:30 UTC

[CI] Fix mypy for `vllm/v1/core` and `vllm/v1/engine` (#27108)

Signed-off-by: yewentao256 <zhyanwentao@126.com>

Wentao Ye committed Oct 30, 2025 at 07:32 UTC c01f6e525f457133cfb00127a89c09e5247e563c
12 files changed +91 -61
tools/pre_commit/mypy.py
+13 -1
@@ -36,12 +36,15 @@ FILES = [
36 "vllm/transformers_utils",
37 "vllm/triton_utils",
38 "vllm/usage",
39 + "vllm/v1/core",
40 + "vllm/v1/engine",
41 ]
42
43 # After fixing errors resulting from changing follow_imports
44 # from "skip" to "silent", move the following directories to FILES
45 SEPARATE_GROUPS = [
46 "tests",
47 + # v0 related
48 "vllm/attention",
49 "vllm/compilation",
50 "vllm/engine",
@@ -50,7 +53,16 @@ SEPARATE_GROUPS = [
53 "vllm/model_executor",
54 "vllm/plugins",
55 "vllm/worker",
53 - "vllm/v1",
56 + # v1 related
57 + "vllm/v1/attention",
58 + "vllm/v1/executor",
59 + "vllm/v1/kv_offload",
60 + "vllm/v1/metrics",
61 + "vllm/v1/pool",
62 + "vllm/v1/sample",
63 + "vllm/v1/spec_decode",
64 + "vllm/v1/structured_output",
65 + "vllm/v1/worker",
66 ]
67
68 # TODO(woosuk): Include the code from Megatron and HuggingFace.
vllm/config/vllm.py
+4 -5
@@ -84,7 +84,9 @@ class VllmConfig:
84 default_factory=StructuredOutputsConfig
85 )
86 """Structured outputs configuration."""
87 - observability_config: ObservabilityConfig | None = None
87 + observability_config: ObservabilityConfig = Field(
88 + default_factory=ObservabilityConfig
89 + )
90 """Observability configuration."""
91 quant_config: QuantizationConfig | None = None
92 """Quantization configuration."""
@@ -170,10 +172,7 @@ class VllmConfig:
172 vllm_factors.append(self.structured_outputs_config.compute_hash())
173 else:
174 vllm_factors.append("None")
173 - if self.observability_config:
174 - vllm_factors.append(self.observability_config.compute_hash())
175 - else:
176 - vllm_factors.append("None")
175 + vllm_factors.append(self.observability_config.compute_hash())
176 if self.quant_config:
177 pass # should be captured by model_config.quantization
178 if self.compilation_config:
vllm/engine/protocol.py
+1
@@ -77,6 +77,7 @@ class EngineClient(ABC):
77 lora_request: LoRARequest | None = None,
78 trace_headers: Mapping[str, str] | None = None,
79 priority: int = 0,
80 + truncate_prompt_tokens: int | None = None,
81 tokenization_kwargs: dict[str, Any] | None = None,
82 ) -> AsyncGenerator[PoolingRequestOutput, None]:
83 """Generate outputs for a request from a pooling model."""
vllm/v1/core/sched/scheduler.py
+9 -7
@@ -167,7 +167,7 @@ class Scheduler(SchedulerInterface):
167 self.kv_cache_manager = KVCacheManager(
168 kv_cache_config=kv_cache_config,
169 max_model_len=self.max_model_len,
170 - enable_caching=self.cache_config.enable_prefix_caching,
170 + enable_caching=bool(self.cache_config.enable_prefix_caching),
171 use_eagle=self.use_eagle,
172 log_stats=self.log_stats,
173 enable_kv_cache_events=self.enable_kv_cache_events,
@@ -407,13 +407,13 @@ class Scheduler(SchedulerInterface):
407
408 # Get externally-cached tokens if using a KVConnector.
409 if self.connector is not None:
410 - num_external_computed_tokens, load_kv_async = (
410 + ext_tokens, load_kv_async = (
411 self.connector.get_num_new_matched_tokens(
412 request, num_new_local_computed_tokens
413 )
414 )
415
416 - if num_external_computed_tokens is None:
416 + if ext_tokens is None:
417 # The request cannot be scheduled because
418 # the KVConnector couldn't determine
419 # the number of matched tokens.
@@ -421,6 +421,8 @@ class Scheduler(SchedulerInterface):
421 skipped_waiting_requests.prepend_request(request)
422 continue
423
424 + num_external_computed_tokens = ext_tokens
425 +
426 # Total computed tokens (local + external).
427 num_computed_tokens = (
428 num_new_local_computed_tokens + num_external_computed_tokens
@@ -905,13 +907,13 @@ class Scheduler(SchedulerInterface):
907
908 outputs: dict[int, list[EngineCoreOutput]] = defaultdict(list)
909 spec_decoding_stats: SpecDecodingStats | None = None
908 - kv_connector_stats = (
910 + kv_connector_stats: KVConnectorStats | None = (
911 kv_connector_output.kv_connector_stats if kv_connector_output else None
912 )
913 if kv_connector_stats and self.connector:
912 - stats = self.connector.get_kv_connector_stats()
913 - if stats:
914 - kv_connector_stats = kv_connector_stats.aggregate(stats)
914 + kv_stats = self.connector.get_kv_connector_stats()
915 + if kv_stats:
916 + kv_connector_stats = kv_connector_stats.aggregate(kv_stats)
917
918 failed_kv_load_req_ids = None
919 if kv_connector_output and kv_connector_output.invalid_block_ids:
vllm/v1/engine/async_llm.py
+13 -8
@@ -6,7 +6,7 @@ import socket
6 import time
7 from collections.abc import AsyncGenerator, Iterable, Mapping
8 from copy import copy
9 -from typing import Any
9 +from typing import Any, cast
10
11 import numpy as np
12 import torch
@@ -131,10 +131,9 @@ class AsyncLLM(EngineClient):
131 self.output_processor = OutputProcessor(
132 self.tokenizer, log_stats=self.log_stats
133 )
134 - if self.observability_config.otlp_traces_endpoint is not None:
135 - tracer = init_tracer(
136 - "vllm.llm_engine", self.observability_config.otlp_traces_endpoint
137 - )
134 + endpoint = self.observability_config.otlp_traces_endpoint
135 + if endpoint is not None:
136 + tracer = init_tracer("vllm.llm_engine", endpoint)
137 self.output_processor.tracer = tracer
138
139 # EngineCore (starts the engine in background process).
@@ -266,7 +265,9 @@ class AsyncLLM(EngineClient):
265 if engine_core := getattr(self, "engine_core", None):
266 engine_core.shutdown()
267
269 - cancel_task_threadsafe(getattr(self, "output_handler", None))
268 + handler = getattr(self, "output_handler", None)
269 + if handler is not None:
270 + cancel_task_threadsafe(handler)
271
272 async def get_supported_tasks(self) -> tuple[SupportedTask, ...]:
273 return await self.engine_core.get_supported_tasks_async()
@@ -314,7 +315,10 @@ class AsyncLLM(EngineClient):
315 priority,
316 data_parallel_rank,
317 )
317 - prompt_text = prompt if isinstance(prompt, str) else prompt.get("prompt")
318 + if isinstance(prompt, str):
319 + prompt_text = prompt
320 + elif isinstance(prompt, Mapping):
321 + prompt_text = cast(str | None, prompt.get("prompt"))
322
323 if is_pooling or params.n == 1:
324 await self._add_request(request, prompt_text, None, 0, queue)
@@ -436,6 +440,7 @@ class AsyncLLM(EngineClient):
440 # Note: both OutputProcessor and EngineCore handle their
441 # own request cleanup based on finished.
442 finished = out.finished
443 + assert isinstance(out, RequestOutput)
444 yield out
445
446 # If the request is disconnected by the client, generate()
@@ -653,7 +658,7 @@ class AsyncLLM(EngineClient):
658 return self.tokenizer
659
660 async def is_tracing_enabled(self) -> bool:
656 - return self.observability_config.otlp_traces_endpoint is not None
661 + return self.observability_config.otlp_traces_endpoint is not None # type: ignore
662
663 async def do_log_stats(self) -> None:
664 if self.logger_manager:
vllm/v1/engine/core.py
+1
@@ -1075,6 +1075,7 @@ class DPEngineCoreProc(EngineCoreProc):
1075 local_dp_rank = vllm_config.parallel_config.data_parallel_rank_local
1076
1077 assert dp_size > 1
1078 + assert local_dp_rank is not None
1079 assert 0 <= local_dp_rank <= dp_rank < dp_size
1080
1081 if vllm_config.kv_transfer_config is not None:
vllm/v1/engine/core_client.py
+7 -7
@@ -385,10 +385,11 @@ class BackgroundResources:
385 with contextlib.suppress(Exception):
386 task.cancel()
387
388 - if in_loop(loop):
389 - close_sockets_and_tasks()
390 - elif loop and not loop.is_closed():
391 - loop.call_soon_threadsafe(close_sockets_and_tasks)
388 + if loop is not None:
389 + if in_loop(loop):
390 + close_sockets_and_tasks()
391 + elif not loop.is_closed():
392 + loop.call_soon_threadsafe(close_sockets_and_tasks)
393 else:
394 # Loop has been closed, try to clean up directly.
395 del tasks
@@ -1044,6 +1045,7 @@ class DPAsyncMPClient(AsyncMPClient):
1045 return
1046
1047 assert self.stats_update_address is not None
1048 + stats_addr: str = self.stats_update_address
1049 assert len(self.engine_ranks_managed) > 0
1050 # NOTE: running and waiting counts are all global from
1051 # the Coordinator include all global EngineCores. This
@@ -1054,9 +1056,7 @@ class DPAsyncMPClient(AsyncMPClient):
1056
1057 async def run_engine_stats_update_task():
1058 with (
1057 - make_zmq_socket(
1058 - self.ctx, self.stats_update_address, zmq.XSUB, linger=0
1059 - ) as socket,
1059 + make_zmq_socket(self.ctx, stats_addr, zmq.XSUB, linger=0) as socket,
1060 make_zmq_socket(
1061 self.ctx, self.first_req_sock_addr, zmq.PAIR, bind=False, linger=0
1062 ) as first_req_rcv_socket,
vllm/v1/engine/detokenizer.py
+10 -3
@@ -69,14 +69,21 @@ class BaseIncrementalDetokenizer(IncrementalDetokenizer, ABC):
69 # Stop strings
70 params = request.sampling_params
71 assert params is not None
72 - self.stop = stop = params.stop
72 + stop_list: list[str]
73 + if params.stop is None:
74 + stop_list = []
75 + elif isinstance(params.stop, str):
76 + stop_list = [params.stop]
77 + else:
78 + stop_list = params.stop
79 + self.stop = stop_list
80 self.min_tokens = params.min_tokens
81 self.include_stop_str_in_output = params.include_stop_str_in_output
82
83 # Number of chars to hold back when stop strings are to be excluded
84 # from streamed output.
78 - if stop and not self.include_stop_str_in_output:
79 - self.stop_buffer_length = max(len(s) for s in stop) - 1
85 + if self.stop and not self.include_stop_str_in_output:
86 + self.stop_buffer_length = max(len(s) for s in self.stop) - 1
87 else:
88 self.stop_buffer_length = 0
89 self._last_output_text_offset: int = 0
vllm/v1/engine/llm_engine.py
+9 -7
@@ -4,7 +4,7 @@
4 import time
5 from collections.abc import Callable, Mapping
6 from copy import copy
7 -from typing import Any
7 +from typing import Any, cast
8
9 import torch.nn as nn
10 from typing_extensions import TypeVar
@@ -112,10 +112,9 @@ class LLMEngine:
112 self.output_processor = OutputProcessor(
113 self.tokenizer, log_stats=self.log_stats
114 )
115 - if self.observability_config.otlp_traces_endpoint is not None:
116 - tracer = init_tracer(
117 - "vllm.llm_engine", self.observability_config.otlp_traces_endpoint
118 - )
115 + endpoint = self.observability_config.otlp_traces_endpoint
116 + if endpoint is not None:
117 + tracer = init_tracer("vllm.llm_engine", endpoint)
118 self.output_processor.tracer = tracer
119
120 # EngineCore (gets EngineCoreRequests and gives EngineCoreOutputs)
@@ -259,7 +258,10 @@ class LLMEngine:
258 trace_headers,
259 priority,
260 )
262 - prompt_text = prompt if isinstance(prompt, str) else prompt.get("prompt")
261 + if isinstance(prompt, str):
262 + prompt_text = prompt
263 + elif isinstance(prompt, Mapping):
264 + prompt_text = cast(str | None, prompt.get("prompt"))
265
266 n = params.n if isinstance(params, SamplingParams) else 1
267
@@ -285,7 +287,7 @@ class LLMEngine:
287 # Add the request to EngineCore.
288 self.engine_core.add_request(child_request)
289
288 - def step(self) -> list[RequestOutput] | list[PoolingRequestOutput]:
290 + def step(self) -> list[RequestOutput | PoolingRequestOutput]:
291 if self.should_execute_dummy_batch:
292 self.should_execute_dummy_batch = False
293 self.engine_core.execute_dummy_batch()
vllm/v1/engine/output_processor.py
+8 -2
@@ -44,10 +44,16 @@ class RequestOutputCollector:
44 if self.output is None or isinstance(output, Exception):
45 self.output = output
46 self.ready.set()
47 - elif isinstance(self.output, (RequestOutput, PoolingRequestOutput)):
47 + elif isinstance(self.output, RequestOutput) and isinstance(
48 + output, RequestOutput
49 + ):
50 # This ensures that request outputs with different request indexes
51 # (if n > 1) do not override each other.
52 self.output.add(output, aggregate=self.aggregate)
53 + elif isinstance(self.output, PoolingRequestOutput) and isinstance(
54 + output, PoolingRequestOutput
55 + ):
56 + self.output = output
57
58 async def get(self) -> RequestOutput | PoolingRequestOutput:
59 """Get operation blocks on put event."""
@@ -408,7 +414,7 @@ class OutputProcessor:
414 within the loop below.
415 """
416
411 - request_outputs: list[RequestOutput] | list[PoolingRequestOutput] = []
417 + request_outputs: list[RequestOutput | PoolingRequestOutput] = []
418 reqs_to_abort: list[str] = []
419 for engine_core_output in engine_core_outputs:
420 req_id = engine_core_output.request_id
vllm/v1/engine/parallel_sampling.py
+2 -2
@@ -2,7 +2,7 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from copy import copy
5 -from typing import Optional
5 +from typing import Optional, cast
6
7 from vllm.outputs import CompletionOutput
8 from vllm.sampling_params import RequestOutputKind, SamplingParams
@@ -37,7 +37,7 @@ class ParentRequest:
37
38 self.child_requests = set()
39 self.output_aggregator = (
40 - [None] * sampling_params.n
40 + [cast(CompletionOutput, None)] * sampling_params.n
41 if (sampling_params.output_kind == RequestOutputKind.FINAL_ONLY)
42 else []
43 )
vllm/v1/engine/processor.py
+14 -19
@@ -3,7 +3,7 @@
3
4 import time
5 from collections.abc import Mapping
6 -from typing import Any, Literal
6 +from typing import Any, Literal, cast
7
8 from vllm.config import VllmConfig
9 from vllm.inputs import ProcessorInputs, PromptType, SingletonInputs
@@ -208,9 +208,9 @@ class Processor:
208 enc = prompt.get("encoder_prompt")
209 dec = prompt.get("decoder_prompt")
210 if enc is not None:
211 - _validate_single_prompt(enc)
211 + _validate_single_prompt(cast(dict | str, enc))
212 if dec is not None:
213 - _validate_single_prompt(dec)
213 + _validate_single_prompt(cast(dict | str, dec))
214 else:
215 _validate_single_prompt(prompt) # type: ignore[arg-type]
216
@@ -332,7 +332,7 @@ class Processor:
332 if not mm_data:
333 return None
334
335 - mm_uuids: MultiModalUUIDDict = {}
335 + mm_uuids: dict[str, list[str | None] | str] = {}
336 for modality, data in mm_data.items():
337 n = len(data) if isinstance(data, list) else 1
338 mm_uuids[modality] = [f"{request_id}-{modality}-{i}" for i in range(n)]
@@ -384,7 +384,9 @@ class Processor:
384 # if provided.
385 self._validate_multi_modal_uuids(prompt)
386 if isinstance(prompt, dict):
387 - mm_uuids = prompt.get("multi_modal_uuids")
387 + mm_uuids = cast(
388 + MultiModalUUIDDict | None, prompt.get("multi_modal_uuids")
389 + )
390 else:
391 mm_uuids = None
392
@@ -410,20 +412,13 @@ class Processor:
412 encoder_inputs, decoder_inputs = split_enc_dec_inputs(processed_inputs)
413 self._validate_model_inputs(encoder_inputs, decoder_inputs)
414
413 - # Mypy does not always properly infer the types of some elements of
414 - # discriminated unions of TypedDicts, because of how it handles
415 - # inheritance of TypedDict. If we explicitly extract the items we want
416 - # we can avoid type errors from using `dict.get` later in the method.
417 - prompt_token_ids = (
418 - decoder_inputs["prompt_token_ids"]
419 - if decoder_inputs["type"] != "embeds"
420 - else None
421 - )
422 - prompt_embeds = (
423 - decoder_inputs["prompt_embeds"]
424 - if decoder_inputs["type"] == "embeds"
425 - else None
426 - )
415 + # Mypy can be conservative for TypedDict unions; normalize access.
416 + if decoder_inputs["type"] == "embeds":
417 + prompt_token_ids = None
418 + prompt_embeds = decoder_inputs["prompt_embeds"]
419 + else:
420 + prompt_token_ids = decoder_inputs["prompt_token_ids"]
421 + prompt_embeds = None
422
423 sampling_params = None
424 pooling_params = None