@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 5e8862e9e0
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 13, 23:15 UTC

[Feature] Pydantic validation for scheduler.py and structured_outputs.py (#26519)

Signed-off-by: Vinay Damodaran <vrdn@hey.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Vinay R Damodaran committed Oct 31, 2025 at 11:05 UTC 5e8862e9e0f5c81d81a1ee46248cc281edc42596
4 files changed +39 -35
vllm/config/scheduler.py
+31 -31
@@ -2,10 +2,11 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import hashlib
5 -from dataclasses import InitVar, field
5 +from collections.abc import Callable
6 +from dataclasses import InitVar
7 from typing import Any, Literal
8
8 -from pydantic import SkipValidation, model_validator
9 +from pydantic import Field, field_validator, model_validator
10 from pydantic.dataclasses import dataclass
11 from typing_extensions import Self
12
@@ -31,28 +32,28 @@ class SchedulerConfig:
32 runner_type: RunnerType = "generate"
33 """The runner type to launch for the model."""
34
34 - max_num_batched_tokens: SkipValidation[int] = None # type: ignore
35 + max_num_batched_tokens: int = Field(default=None, ge=1)
36 """Maximum number of tokens to be processed in a single iteration.
37
38 This config has no static default. If left unspecified by the user, it will
39 be set in `EngineArgs.create_engine_config` based on the usage context."""
40
40 - max_num_seqs: SkipValidation[int] = None # type: ignore
41 + max_num_seqs: int = Field(default=None, ge=1)
42 """Maximum number of sequences to be processed in a single iteration.
43
44 This config has no static default. If left unspecified by the user, it will
45 be set in `EngineArgs.create_engine_config` based on the usage context."""
46
46 - max_model_len: SkipValidation[int] = None # type: ignore
47 + max_model_len: int = Field(default=None, ge=1)
48 """Maximum length of a sequence (including prompt and generated text). This
49 is primarily set in `ModelConfig` and that value should be manually
50 duplicated here."""
51
51 - max_num_partial_prefills: int = 1
52 + max_num_partial_prefills: int = Field(default=1, ge=1)
53 """For chunked prefill, the maximum number of sequences that can be
54 partially prefilled concurrently."""
55
55 - max_long_partial_prefills: int = 1
56 + max_long_partial_prefills: int = Field(default=1, ge=1)
57 """For chunked prefill, the maximum number of prompts longer than
58 long_prefill_token_threshold that will be prefilled concurrently. Setting
59 this less than max_num_partial_prefills will allow shorter prompts to jump
@@ -62,7 +63,7 @@ class SchedulerConfig:
63 """For chunked prefill, a request is considered long if the prompt is
64 longer than this number of tokens."""
65
65 - num_lookahead_slots: int = 0
66 + num_lookahead_slots: int = Field(default=0, ge=0)
67 """The number of slots to allocate per sequence per
68 step, beyond the known token ids. This is used in speculative
69 decoding to store KV activations of tokens which may or may not be
@@ -71,7 +72,7 @@ class SchedulerConfig:
72 NOTE: This will be replaced by speculative config in the future; it is
73 present to enable correctness tests until then."""
74
74 - enable_chunked_prefill: SkipValidation[bool] = None # type: ignore
75 + enable_chunked_prefill: bool = Field(default=None)
76 """If True, prefill requests can be chunked based
77 on the remaining max_num_batched_tokens."""
78
@@ -86,14 +87,14 @@ class SchedulerConfig:
87 """
88
89 # TODO (ywang96): Make this configurable.
89 - max_num_encoder_input_tokens: int = field(init=False)
90 + max_num_encoder_input_tokens: int = Field(init=False)
91 """Multimodal encoder compute budget, only used in V1.
92
93 NOTE: This is not currently configurable. It will be overridden by
94 max_num_batched_tokens in case max multimodal embedding size is larger."""
95
96 # TODO (ywang96): Make this configurable.
96 - encoder_cache_size: int = field(init=False)
97 + encoder_cache_size: int = Field(init=False)
98 """Multimodal encoder cache size, only used in V1.
99
100 NOTE: This is not currently configurable. It will be overridden by
@@ -106,7 +107,7 @@ class SchedulerConfig:
107 - "priority" means requests are handled based on given priority (lower
108 value means earlier handling) and time of arrival deciding any ties)."""
109
109 - chunked_prefill_enabled: bool = field(init=False)
110 + chunked_prefill_enabled: bool = Field(init=False)
111 """True if chunked prefill is enabled."""
112
113 disable_chunked_mm_input: bool = False
@@ -155,6 +156,20 @@ class SchedulerConfig:
156 hash_str = hashlib.md5(str(factors).encode(), usedforsecurity=False).hexdigest()
157 return hash_str
158
159 + @field_validator(
160 + "max_num_batched_tokens",
161 + "max_num_seqs",
162 + "max_model_len",
163 + "enable_chunked_prefill",
164 + mode="wrap",
165 + )
166 + @classmethod
167 + def _skip_none_validation(cls, value: Any, handler: Callable) -> Any:
168 + """Skip validation if the value is `None` when initialisation is delayed."""
169 + if value is None:
170 + return value
171 + return handler(value)
172 +
173 def __post_init__(self, is_encoder_decoder: bool) -> None:
174 if self.max_model_len is None:
175 self.max_model_len = 8192
@@ -260,19 +275,7 @@ class SchedulerConfig:
275 self.max_num_seqs * self.max_model_len,
276 )
277
263 - if self.num_lookahead_slots < 0:
264 - raise ValueError(
265 - "num_lookahead_slots "
266 - f"({self.num_lookahead_slots}) must be greater than or "
267 - "equal to 0."
268 - )
269 -
270 - if self.max_num_partial_prefills < 1:
271 - raise ValueError(
272 - f"max_num_partial_prefills ({self.max_num_partial_prefills}) "
273 - "must be greater than or equal to 1."
274 - )
275 - elif self.max_num_partial_prefills > 1:
278 + if self.max_num_partial_prefills > 1:
279 if not self.chunked_prefill_enabled:
280 raise ValueError(
281 "Chunked prefill must be enabled to set "
@@ -286,13 +289,10 @@ class SchedulerConfig:
289 f"than the max_model_len ({self.max_model_len})."
290 )
291
289 - if (self.max_long_partial_prefills < 1) or (
290 - self.max_long_partial_prefills > self.max_num_partial_prefills
291 - ):
292 + if self.max_long_partial_prefills > self.max_num_partial_prefills:
293 raise ValueError(
293 - f"max_long_partial_prefills ({self.max_long_partial_prefills}) "
294 - "must be greater than or equal to 1 and less than or equal to "
295 - f"max_num_partial_prefills ({self.max_num_partial_prefills})."
294 + f"{self.max_long_partial_prefills=} must be less than or equal to "
295 + f"{self.max_num_partial_prefills=}."
296 )
297
298 return self
vllm/config/structured_outputs.py
+5 -2
@@ -2,8 +2,9 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import hashlib
5 -from typing import Any, Literal
5 +from typing import Any, Literal, Self
6
7 +from pydantic import model_validator
8 from pydantic.dataclasses import dataclass
9
10 from vllm.config.utils import config
@@ -56,7 +57,8 @@ class StructuredOutputsConfig:
57 hash_str = hashlib.md5(str(factors).encode(), usedforsecurity=False).hexdigest()
58 return hash_str
59
59 - def __post_init__(self):
60 + @model_validator(mode="after")
61 + def _validate_structured_output_config(self) -> Self:
62 if self.disable_any_whitespace and self.backend not in ("xgrammar", "guidance"):
63 raise ValueError(
64 "disable_any_whitespace is only supported for "
@@ -67,3 +69,4 @@ class StructuredOutputsConfig:
69 "disable_additional_properties is only supported "
70 "for the guidance backend."
71 )
72 + return self
vllm/engine/arg_utils.py
+1 -1
@@ -1807,7 +1807,7 @@ class EngineArgs:
1807 incremental_prefill_supported = (
1808 pooling_type is not None
1809 and pooling_type.lower() == "last"
1810 - and is_causal
1810 + and bool(is_causal)
1811 )
1812
1813 action = "Enabling" if incremental_prefill_supported else "Disabling"
vllm/entrypoints/openai/tool_parsers/minimax_m2_tool_parser.py
+2 -1
@@ -2,11 +2,12 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import json
5 -import re
5 import uuid
6 from collections.abc import Sequence
7 from typing import Any
8
9 +import regex as re
10 +
11 from vllm.entrypoints.openai.protocol import (
12 ChatCompletionRequest,
13 DeltaFunctionCall,