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,