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 15, 11:00 UTC
Improve configs - the rest! (#17562)
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Harry Mellor committed
May 9, 2025 at 23:18 UTC
4b2ed7926a1d93d4189ac112209f2e34cd80846a
14 files changed
+461
-345
tests/compile/test_full_graph.py
+1
-4
@@ -9,7 +9,7 @@ import torch
9
10
from tests.quantization.utils import is_quant_method_supported
11
from vllm import LLM, SamplingParams
12
-from vllm.config import CompilationConfig, CompilationLevel
12
+from vllm.config import CompilationConfig, CompilationLevel, PassConfig
13
from vllm.platforms import current_platform
14
15
from ..utils import create_new_process_for_each_test
@@ -95,9 +95,6 @@ def test_full_graph(
95
run_model(optimization_level, model, model_kwargs)
96
97
98
-PassConfig = CompilationConfig.PassConfig
99
-
100
-
98
# TODO(luka) add other supported compilation config scenarios here
99
@pytest.mark.parametrize(
100
"compilation_config, model_info",
tests/compile/test_functionalization.py
+3
-4
@@ -11,7 +11,7 @@ from vllm.compilation.fusion import (FUSED_OPS, FusionPass, QuantKey,
11
kFp8DynamicTokenSym, kFp8StaticTensorSym)
12
from vllm.compilation.fx_utils import find_auto_fn, find_auto_fn_maybe, is_func
13
from vllm.compilation.noop_elimination import NoOpEliminationPass
14
-from vllm.config import CompilationConfig, VllmConfig
14
+from vllm.config import CompilationConfig, PassConfig, VllmConfig
15
16
from .backend import TestBackend
17
@@ -53,9 +53,8 @@ def test_fix_functionalization(model: str, quant_key: QuantKey,
53
torch.set_default_device("cuda")
54
55
vllm_config = VllmConfig()
56
- vllm_config.compilation_config = CompilationConfig(pass_config= \
57
- CompilationConfig.PassConfig(enable_fusion=do_fusion,
58
- enable_noop=True))
56
+ vllm_config.compilation_config = CompilationConfig(
57
+ pass_config=PassConfig(enable_fusion=do_fusion, enable_noop=True))
58
noop_pass = NoOpEliminationPass(vllm_config)
59
fusion_pass = FusionPass.instance(vllm_config)
60
act_quant_fusion_pass = ActivationQuantFusionPass(vllm_config)
tests/compile/test_fusion.py
+3
-3
@@ -9,7 +9,8 @@ from vllm.compilation.fusion import (FUSED_OPS, QUANT_OPS, FusedRMSQuantKey,
9
FusionPass, QuantKey)
10
from vllm.compilation.fx_utils import find_auto_fn, find_auto_fn_maybe
11
from vllm.compilation.noop_elimination import NoOpEliminationPass
12
-from vllm.config import CompilationConfig, CompilationLevel, VllmConfig
12
+from vllm.config import (CompilationConfig, CompilationLevel, PassConfig,
13
+ VllmConfig)
14
from vllm.model_executor.layers.layernorm import RMSNorm
15
from vllm.model_executor.layers.quantization.utils.w8a8_utils import (
16
CUTLASS_FP8_SUPPORTED, Fp8LinearOp, maybe_create_device_identity)
@@ -78,8 +79,7 @@ def test_fusion_rmsnorm_quant(dtype, hidden_size, num_tokens, eps, static,
79
vllm_config = VllmConfig(compilation_config=CompilationConfig(
80
level=CompilationLevel.PIECEWISE, custom_ops=["+rms_norm"]))
81
vllm_config.compilation_config.pass_config = \
81
- CompilationConfig.PassConfig(enable_fusion=True,
82
- enable_noop=True)
82
+ PassConfig(enable_fusion=True, enable_noop=True)
83
with vllm.config.set_current_vllm_config(vllm_config):
84
# Reshape pass is needed for the fusion pass to work
85
noop_pass = NoOpEliminationPass(vllm_config)
tests/compile/test_sequence_parallelism.py
+3
-4
@@ -10,7 +10,7 @@ from vllm.compilation.fx_utils import (find_auto_fn, find_auto_fn_maybe,
10
find_specified_fn_maybe, is_func)
11
from vllm.compilation.sequence_parallelism import SequenceParallelismPass
12
from vllm.config import (CompilationConfig, DeviceConfig, ModelConfig,
13
- VllmConfig)
13
+ PassConfig, VllmConfig)
14
from vllm.distributed import tensor_model_parallel_all_reduce
15
from vllm.distributed.parallel_state import (init_distributed_environment,
16
initialize_model_parallel)
@@ -126,9 +126,8 @@ def sequence_parallelism_pass_on_test_model(local_rank: int, world_size: int,
126
127
# configure vllm config for SequenceParallelismPass
128
vllm_config = VllmConfig()
129
- vllm_config.compilation_config = CompilationConfig(
130
- pass_config=CompilationConfig.PassConfig(
131
- enable_sequence_parallelism=True, ), )
129
+ vllm_config.compilation_config = CompilationConfig(pass_config=PassConfig(
130
+ enable_sequence_parallelism=True))
131
vllm_config.device_config = DeviceConfig(device=torch.device("cuda"))
132
133
# this is a fake model name to construct the model config
tests/compile/test_silu_mul_quant_fusion.py
+2
-3
@@ -6,7 +6,7 @@ import vllm.envs as envs
6
from vllm._custom_ops import scaled_fp8_quant
7
from vllm.compilation.activation_quant_fusion import ActivationQuantFusionPass
8
from vllm.compilation.fx_utils import find_auto_fn, find_auto_fn_maybe
9
-from vllm.config import CompilationConfig, VllmConfig
9
+from vllm.config import CompilationConfig, PassConfig, VllmConfig
10
from vllm.model_executor.layers.activation import SiluAndMul
11
12
from .backend import TestBackend
@@ -36,8 +36,7 @@ def test_fusion_silu_and_mul_quant(num_tokens, hidden_size):
36
# Reshape pass is needed for the fusion pass to work
37
config = VllmConfig()
38
config.compilation_config = CompilationConfig(
39
- pass_config=CompilationConfig.PassConfig(enable_fusion=True,
40
- enable_reshape=True))
39
+ pass_config=PassConfig(enable_fusion=True, enable_reshape=True))
40
fusion_pass = ActivationQuantFusionPass(config)
41
42
backend = TestBackend(fusion_pass)
tests/distributed/test_sequence_parallel.py
+2
-2
@@ -206,7 +206,7 @@ def _compare_sp(
206
'compile_sizes': [4, 8],
207
'splitting_ops': [],
208
'pass_config': {
209
- 'enable_sequence_parallism': sp_enabled,
209
+ 'enable_sequence_parallelism': sp_enabled,
210
'enable_noop': True,
211
'enable_fusion': True,
212
},
@@ -223,7 +223,7 @@ def _compare_sp(
223
"--distributed-executor-backend",
224
distributed_backend,
225
"--compilation_config",
226
- str(compilation_config),
226
+ json.dumps(compilation_config),
227
]
228
229
tp_env = {
tests/engine/test_arg_utils.py
+61
-15
@@ -8,21 +8,18 @@ from typing import Literal, Optional
8
9
import pytest
10
11
-from vllm.config import config
11
+from vllm.config import CompilationConfig, config
12
from vllm.engine.arg_utils import (EngineArgs, contains_type, get_kwargs,
13
get_type, is_not_builtin, is_type,
14
literal_to_kwargs, nullable_kvs,
15
- optional_type)
15
+ optional_type, parse_type)
16
from vllm.utils import FlexibleArgumentParser
17
18
19
@pytest.mark.parametrize(("type", "value", "expected"), [
20
(int, "42", 42),
21
- (int, "None", None),
21
(float, "3.14", 3.14),
23
- (float, "None", None),
22
(str, "Hello World!", "Hello World!"),
25
- (str, "None", None),
23
(json.loads, '{"foo":1,"bar":2}', {
24
"foo": 1,
25
"bar": 2
@@ -31,15 +28,20 @@ from vllm.utils import FlexibleArgumentParser
28
"foo": 1,
29
"bar": 2
30
}),
34
- (json.loads, "None", None),
31
])
36
-def test_optional_type(type, value, expected):
37
- optional_type_func = optional_type(type)
32
+def test_parse_type(type, value, expected):
33
+ parse_type_func = parse_type(type)
34
context = nullcontext()
35
if value == "foo=1,bar=2":
36
context = pytest.warns(DeprecationWarning)
37
with context:
42
- assert optional_type_func(value) == expected
38
+ assert parse_type_func(value) == expected
39
+
40
+
41
+def test_optional_type():
42
+ optional_type_func = optional_type(int)
43
+ assert optional_type_func("None") is None
44
+ assert optional_type_func("42") == 42
45
46
47
@pytest.mark.parametrize(("type_hint", "type", "expected"), [
@@ -89,7 +91,40 @@ def test_literal_to_kwargs(type_hints, expected):
91
92
@config
93
@dataclass
92
-class DummyConfigClass:
94
+class NestedConfig:
95
+ field: int = 1
96
+ """field"""
97
+
98
+
99
+@config
100
+@dataclass
101
+class FromCliConfig1:
102
+ field: int = 1
103
+ """field"""
104
+
105
+ @classmethod
106
+ def from_cli(cls, cli_value: str):
107
+ inst = cls(**json.loads(cli_value))
108
+ inst.field += 1
109
+ return inst
110
+
111
+
112
+@config
113
+@dataclass
114
+class FromCliConfig2:
115
+ field: int = 1
116
+ """field"""
117
+
118
+ @classmethod
119
+ def from_cli(cls, cli_value: str):
120
+ inst = cls(**json.loads(cli_value))
121
+ inst.field += 2
122
+ return inst
123
+
124
+
125
+@config
126
+@dataclass
127
+class DummyConfig:
128
regular_bool: bool = True
129
"""Regular bool with default True"""
130
optional_bool: Optional[bool] = None
@@ -108,18 +143,24 @@ class DummyConfigClass:
143
"""Literal of literals with default 1"""
144
json_tip: dict = field(default_factory=dict)
145
"""Dict which will be JSON in CLI"""
146
+ nested_config: NestedConfig = field(default_factory=NestedConfig)
147
+ """Nested config"""
148
+ from_cli_config1: FromCliConfig1 = field(default_factory=FromCliConfig1)
149
+ """Config with from_cli method"""
150
+ from_cli_config2: FromCliConfig2 = field(default_factory=FromCliConfig2)
151
+ """Different config with from_cli method"""
152
153
154
@pytest.mark.parametrize(("type_hint", "expected"), [
155
(int, False),
115
- (DummyConfigClass, True),
156
+ (DummyConfig, True),
157
])
158
def test_is_not_builtin(type_hint, expected):
159
assert is_not_builtin(type_hint) == expected
160
161
162
def test_get_kwargs():
122
- kwargs = get_kwargs(DummyConfigClass)
163
+ kwargs = get_kwargs(DummyConfig)
164
print(kwargs)
165
166
# bools should not have their type set
@@ -142,6 +183,11 @@ def test_get_kwargs():
183
# dict should have json tip in help
184
json_tip = "\n\nShould be a valid JSON string."
185
assert kwargs["json_tip"]["help"].endswith(json_tip)
186
+ # nested config should should construct the nested config
187
+ assert kwargs["nested_config"]["type"]('{"field": 2}') == NestedConfig(2)
188
+ # from_cli configs should be constructed with the correct method
189
+ assert kwargs["from_cli_config1"]["type"]('{"field": 2}').field == 3
190
+ assert kwargs["from_cli_config2"]["type"]('{"field": 2}').field == 4
191
192
193
@pytest.mark.parametrize(("arg", "expected"), [
@@ -177,7 +223,7 @@ def test_compilation_config():
223
224
# default value
225
args = parser.parse_args([])
180
- assert args.compilation_config is None
226
+ assert args.compilation_config == CompilationConfig()
227
228
# set to O3
229
args = parser.parse_args(["-O3"])
@@ -194,7 +240,7 @@ def test_compilation_config():
240
# set to string form of a dict
241
args = parser.parse_args([
242
"--compilation-config",
197
- "{'level': 3, 'cudagraph_capture_sizes': [1, 2, 4, 8]}",
243
+ '{"level": 3, "cudagraph_capture_sizes": [1, 2, 4, 8]}',
244
])
245
assert (args.compilation_config.level == 3 and
246
args.compilation_config.cudagraph_capture_sizes == [1, 2, 4, 8])
@@ -202,7 +248,7 @@ def test_compilation_config():
248
# set to string form of a dict
249
args = parser.parse_args([
250
"--compilation-config="
205
- "{'level': 3, 'cudagraph_capture_sizes': [1, 2, 4, 8]}",
251
+ '{"level": 3, "cudagraph_capture_sizes": [1, 2, 4, 8]}',
252
])
253
assert (args.compilation_config.level == 3 and
254
args.compilation_config.cudagraph_capture_sizes == [1, 2, 4, 8])
vllm/compilation/vllm_inductor_pass.py
+2
-5
@@ -4,7 +4,7 @@ import time
4
5
import torch
6
7
-from vllm.config import CompilationConfig, VllmConfig
7
+from vllm.config import PassConfig, VllmConfig
8
# yapf: disable
9
from vllm.distributed import get_tensor_model_parallel_rank as get_tp_rank
10
from vllm.distributed import (
@@ -56,10 +56,7 @@ class VllmInductorPass(InductorPass):
56
57
class PrinterInductorPass(VllmInductorPass):
58
59
- def __init__(self,
60
- name: str,
61
- config: CompilationConfig.PassConfig,
62
- always=False):
59
+ def __init__(self, name: str, config: PassConfig, always=False):
60
super().__init__(config)
61
self.name = name
62
self.always = always
vllm/config.py
+303
-220
@@ -11,8 +11,8 @@ import textwrap
11
import warnings
12
from collections import Counter
13
from contextlib import contextmanager
14
-from dataclasses import (MISSING, dataclass, field, fields, is_dataclass,
15
- replace)
14
+from dataclasses import (MISSING, Field, asdict, dataclass, field, fields,
15
+ is_dataclass, replace)
16
from functools import cached_property
17
from importlib.util import find_spec
18
from pathlib import Path
@@ -20,7 +20,6 @@ from typing import (TYPE_CHECKING, Any, Callable, ClassVar, Literal, Optional,
20
Protocol, TypeVar, Union, cast, get_args, get_origin)
21
22
import torch
23
-from pydantic import BaseModel, Field, PrivateAttr
23
from torch.distributed import ProcessGroup, ReduceOp
24
from transformers import PretrainedConfig
25
from typing_extensions import deprecated
@@ -57,7 +56,7 @@ if TYPE_CHECKING:
56
57
ConfigType = type[DataclassInstance]
58
else:
60
- QuantizationConfig = None
59
+ QuantizationConfig = Any
60
ConfigType = type
61
62
logger = init_logger(__name__)
@@ -169,6 +168,12 @@ def config(cls: ConfigT) -> ConfigT:
168
"""
169
A decorator that ensures all fields in a dataclass have default values
170
and that each field has a docstring.
171
+
172
+ If a `ConfigT` is used as a CLI argument itself, the default value provided
173
+ by `get_kwargs` will be the result parsing a JSON string as the kwargs
174
+ (i.e. `ConfigT(**json.loads(cli_arg))`). However, if a particular `ConfigT`
175
+ requires custom construction from CLI (i.e. `CompilationConfig`), it can
176
+ have a `from_cli` method, which will be called instead.
177
"""
178
if not is_dataclass(cls):
179
raise TypeError("The decorated class must be a dataclass.")
@@ -202,7 +207,7 @@ def get_field(cls: ConfigType, name: str) -> Field:
207
cls_fields = {f.name: f for f in fields(cls)}
208
if name not in cls_fields:
209
raise ValueError(f"Field '{name}' not found in {cls.__name__}.")
205
- named_field: Field = cls_fields.get(name)
210
+ named_field: Field = cls_fields[name]
211
if (default_factory := named_field.default_factory) is not MISSING:
212
return field(default_factory=default_factory)
213
if (default := named_field.default) is not MISSING:
@@ -211,6 +216,10 @@ def get_field(cls: ConfigType, name: str) -> Field:
216
f"{cls.__name__}.{name} must have a default value or default factory.")
217
218
219
+def is_init_field(cls: ConfigType, name: str) -> bool:
220
+ return next(f for f in fields(cls) if f.name == name).init
221
+
222
+
223
TokenizerMode = Literal["auto", "slow", "mistral", "custom"]
224
ModelDType = Literal["auto", "half", "float16", "bfloat16", "float", "float32"]
225
@@ -2007,13 +2016,13 @@ class SchedulerConfig:
2016
def __post_init__(self) -> None:
2017
if self.max_model_len is None:
2018
self.max_model_len = 8192
2010
- logger.warning(
2019
+ logger.warning_once(
2020
"max_model_len was is not set. Defaulting to arbitrary value "
2021
"of %d.", self.max_model_len)
2022
2023
if self.max_num_seqs is None:
2024
self.max_num_seqs = 128
2016
- logger.warning(
2025
+ logger.warning_once(
2026
"max_num_seqs was is not set. Defaulting to arbitrary value "
2027
"of %d.", self.max_num_seqs)
2028
@@ -2840,8 +2849,8 @@ class PromptAdapterConfig:
2849
class MultiModalConfig:
2850
"""Controls the behavior of multimodal models."""
2851
2843
- limit_per_prompt: dict[str, int] = get_field(ModelConfig,
2844
- "limit_mm_per_prompt")
2852
+ limit_per_prompt: dict[str, int] = \
2853
+ cast(dict[str, int], get_field(ModelConfig, "limit_mm_per_prompt"))
2854
"""
2855
The maximum number of input items allowed per prompt for each modality.
2856
Defaults to 1 (V0) or 999 (V1) for each modality.
@@ -3415,41 +3424,49 @@ class ObservabilityConfig:
3424
self.collect_detailed_traces[0].split(","))
3425
3426
3418
-class KVTransferConfig(BaseModel):
3427
+KVProducer = Literal["kv_producer", "kv_both"]
3428
+KVConsumer = Literal["kv_consumer", "kv_both"]
3429
+KVRole = Literal[KVProducer, KVConsumer]
3430
+
3431
+
3432
+@config
3433
+@dataclass
3434
+class KVTransferConfig:
3435
"""Configuration for distributed KV cache transfer."""
3436
3421
- # The KV connector for vLLM to transmit KV caches between vLLM instances.
3437
kv_connector: Optional[str] = None
3438
+ """The KV connector for vLLM to transmit KV caches between vLLM instances.
3439
+ """
3440
3424
- # The device used by kv connector to buffer the KV cache.
3425
- # Currently only support 'cuda'.
3441
kv_buffer_device: Optional[str] = "cuda"
3442
+ """The device used by kv connector to buffer the KV cache.
3443
+ Currently only support 'cuda'."""
3444
3428
- # The buffer size for TorchDistributedConnector. Measured in number of
3429
- # bytes. Recommended value: 1e9 (about 1GB).
3445
kv_buffer_size: float = 1e9
3446
+ """The buffer size for TorchDistributedConnector. Measured in number of
3447
+ bytes. Recommended value: 1e9 (about 1GB)."""
3448
3432
- # Whether this vLLM instance produces, consumes KV cache, or both. Choices
3433
- # are 'kv_producer', 'kv_consumer', and 'both'.
3434
- kv_role: Optional[str] = None
3449
+ kv_role: Optional[KVRole] = None
3450
+ """Whether this vLLM instance produces, consumes KV cache, or both. Choices
3451
+ are 'kv_producer', 'kv_consumer', and 'both'."""
3452
3436
- # The rank of this vLLM instance in the KV cache transfer. Typical value:
3437
- # 0 for prefill instance, 1 for decode instance.
3438
- # Currently only 1P1D is supported.
3453
kv_rank: Optional[int] = None
3454
+ """The rank of this vLLM instance in the KV cache transfer. Typical value:
3455
+ 0 for prefill instance, 1 for decode instance.
3456
+ Currently only 1P1D is supported."""
3457
3441
- # The number of parallel instances for KV cache transfer. For
3442
- # PyNcclConnector, this should be 2.
3458
kv_parallel_size: int = 1
3459
+ """The number of parallel instances for KV cache transfer. For
3460
+ PyNcclConnector, this should be 2."""
3461
3445
- # The KV connector ip, used to build distributed connection
3462
kv_ip: str = "127.0.0.1"
3463
+ """The KV connector ip, used to build distributed connection."""
3464
3448
- # The KV connector port, used to build distributed connection
3465
kv_port: int = 14579
3466
+ """The KV connector port, used to build distributed connection."""
3467
3451
- # any extra config that the connector may need
3452
- kv_connector_extra_config: dict[str, Any] = {}
3468
+ kv_connector_extra_config: dict[str, Any] = field(default_factory=dict)
3469
+ """any extra config that the connector may need."""
3470
3471
def compute_hash(self) -> str:
3472
"""
@@ -3470,46 +3487,37 @@ class KVTransferConfig(BaseModel):
3487
usedforsecurity=False).hexdigest()
3488
return hash_str
3489
3473
- @classmethod
3474
- def from_cli(cls, cli_value: str) -> "KVTransferConfig":
3475
- """Parse the CLI value for the kv cache transfer config."""
3476
- return KVTransferConfig.model_validate_json(cli_value)
3477
-
3478
- def model_post_init(self, __context: Any) -> None:
3479
-
3480
- if self.kv_role is not None and self.kv_role not in [
3481
- "kv_producer", "kv_consumer", "kv_both"
3482
- ]:
3483
- raise ValueError(
3484
- f"Unsupported kv_role: {self.kv_role}. "
3485
- f"Supported roles are `kv_producer`, `kv_consumer`, "
3486
- f"and `kv_both`")
3490
+ def __post_init__(self) -> None:
3491
+ if self.kv_role is not None and self.kv_role not in get_args(KVRole):
3492
+ raise ValueError(f"Unsupported kv_role: {self.kv_role}. "
3493
+ f"Supported roles are {get_args(KVRole)}")
3494
3495
if self.kv_connector is not None and self.kv_role is None:
3496
raise ValueError("Please specify kv_disagg_role when kv_connector "
3490
- "is set, supported roles are `kv_producer`, "
3491
- "`kv_consumer`, and `kv_both`")
3497
+ f"is set, supported roles are {get_args(KVRole)}")
3498
3499
@property
3500
def is_kv_transfer_instance(self) -> bool:
3501
return self.kv_connector is not None and \
3496
- self.kv_role in ["kv_producer", "kv_consumer", "kv_both"]
3502
+ self.kv_role in get_args(KVRole)
3503
3504
@property
3505
def is_kv_producer(self) -> bool:
3506
return self.kv_connector is not None and \
3501
- self.kv_role in ["kv_producer", "kv_both"]
3507
+ self.kv_role in get_args(KVProducer)
3508
3509
@property
3510
def is_kv_consumer(self) -> bool:
3511
return self.kv_connector is not None and \
3506
- self.kv_role in ["kv_consumer", "kv_both"]
3512
+ self.kv_role in get_args(KVConsumer)
3513
3514
def get_from_extra_config(self, key, default) -> Any:
3515
return self.kv_connector_extra_config.get(key, default)
3516
3517
3512
-class KVEventsConfig(BaseModel):
3518
+@config
3519
+@dataclass
3520
+class KVEventsConfig:
3521
"""Configuration for KV event publishing."""
3522
3523
enable_kv_cache_events: bool = False
@@ -3548,11 +3556,6 @@ class KVEventsConfig(BaseModel):
3556
this topic to receive events.
3557
"""
3558
3551
- @classmethod
3552
- def from_cli(cls, cli_value: str) -> "KVEventsConfig":
3553
- """Parse the CLI value for the event publisher config."""
3554
- return KVEventsConfig.model_validate_json(cli_value)
3555
-
3559
3560
class CompilationLevel:
3561
# constants for the levels of the compilation process
@@ -3562,80 +3565,72 @@ class CompilationLevel:
3565
PIECEWISE = 3
3566
3567
3565
-class CompilationConfig(BaseModel):
3566
- """
3567
- Configuration for compilation.
3568
- It has three parts:
3568
+@config
3569
+@dataclass
3570
+class PassConfig:
3571
+ """Configuration for custom Inductor passes.
3572
+
3573
+ This is separate from general `CompilationConfig` so that inductor passes
3574
+ don't all have access to full configuration - that would create a cycle as
3575
+ the `PassManager` is set as a property of config."""
3576
+
3577
+ dump_graph_stages: list[str] = field(default_factory=list)
3578
+ """List of stages for which we want to dump the graph. Each pass defines
3579
+ its own stages (before, after, maybe in-between)."""
3580
+ dump_graph_dir: Path = Path(".")
3581
+ """Directory to dump the graphs."""
3582
+ # TODO(luka) better pass enabling system.
3583
+ enable_fusion: bool = True
3584
+ """Whether to enable the custom fusion pass."""
3585
+ enable_noop: bool = True
3586
+ """Whether to enable the custom no-op elimination pass."""
3587
+ enable_sequence_parallelism: bool = False
3588
+ """Whether to enable sequence parallelism."""
3589
+
3590
+ def uuid(self):
3591
+ """
3592
+ Produces a hash unique to the pass configuration.
3593
+ Any new fields that affect compilation should be added to the hash.
3594
+ Do not include dump_graph_* in the hash - they don't affect
3595
+ compilation.
3596
+ """
3597
+ include = {
3598
+ "enable_fusion", "enable_noop", "enable_sequence_parallelism"
3599
+ }
3600
+ dict_ = {k: v for k, v in asdict(self).items() if k in include}
3601
+ return InductorPass.hash_dict(dict_)
3602
+
3603
+ def __post_init__(self) -> None:
3604
+ if not self.enable_noop and self.enable_fusion:
3605
+ logger.warning_once(
3606
+ "Fusion enabled but reshape elimination disabled. "
3607
+ "RMSNorm + quant (fp8) fusion might not work")
3608
+
3609
+
3610
+@config
3611
+@dataclass
3612
+class CompilationConfig:
3613
+ """Configuration for compilation. It has three parts:
3614
+
3615
- Top-level Compilation control:
3570
- - level: the level of compilation.
3571
- - 0: no compilation.
3572
- - 1: dynamo as is.
3573
- - 2: dynamo once.
3574
- - 3: piecewise compilation.
3575
- - debug_dump_path: the path to dump the debug information.
3576
- - cache_dir: the directory to store the compiled graph, to
3577
- accelerate Inductor compilation. By default, it will use
3578
- model-related information to generate a cache directory.
3579
- - backend: the backend for compilation. It needs to be a string.
3580
- - "" (empty string): use the default backend.
3581
- - "eager"/"openxla"/...: use the specified backend registered in PyTorch.
3582
- - "full.module.name": a qualified name which can be used to import the backend function.
3583
- We use string to avoid serialization issues when using compilation in a distributed setting.
3584
- When the compilation level is 1 or 2, the backend is used for the compilation directly (it sees the whole graph).
3585
- When the compilation level is 3, the backend is used for the piecewise compilation (it sees a part of the graph).
3586
- - custom_ops: fine-grained control over which custom ops to enable/disable.
3587
- Use 'all' to enable all, 'none' to disable all.
3588
- Also specify a list of custom op names to enable (prefixed with a '+'),
3589
- or disable (prefixed with a '-').
3590
- Examples:
3591
- - 'all,-op1' to enable all except op1
3592
- - 'none,+op1,+op2' to enable only op1 and op2
3593
- By default, all custom ops are enabled when running without Inductor
3594
- and disabled when running with Inductor (compile_level >= Inductor).
3595
- - splitting_ops: a list of ops to split the full graph into subgraphs, used in piecewise compilation.
3616
+ - {attr}`level`
3617
+ - {attr}`debug_dump_path`
3618
+ - {attr}`cache_dir`
3619
+ - {attr}`backend`
3620
+ - {attr}`custom_ops`
3621
+ - {attr}`splitting_ops`
3622
- CudaGraph capture:
3597
- - use_cudagraph: whether to use cudagraph inside compilation.
3598
- - False: cudagraph inside compilation is not used.
3599
- - True: cudagraph inside compilation is used. It requires
3600
- that all input buffers have fixed addresses, and all
3601
- splitting ops write their outputs to input buffers.
3602
- Note that this is orthogonal to the cudagraph capture logic
3603
- outside of compilation.
3604
- TODO: move outside cudagraph logic into compilation.
3605
- torch.compile will handle cudagraph capture logic in the future.
3606
- - cudagraph_capture_sizes: sizes to capture cudagraph.
3607
- - None (default): capture sizes are inferred from vllm config.
3608
- - list[int]: capture sizes are specified as given.
3609
- - cudagraph_num_of_warmups: number of warmup runs for cudagraph.
3610
- It means the first several runs will be treated as warmup runs.
3611
- Only after that, the execution will be recorded, and the recorded
3612
- cudagraph will be used for subsequent runs.
3613
- - cudagraph_copy_inputs: whether to copy input tensors for
3614
- cudagraph. If the caller can guarantee that the same input buffers
3615
- are always used, it can set this to False. Otherwise, it should
3616
- set this to True, and the compiler will copy the input to an
3617
- internally managed buffer. Default is False.
3618
- - full_cuda_graph: whether to use a full cuda graph for the entire forward
3619
- pass rather than splitting certain operations such as attention into subgraphs.
3620
- Thus this flag cannot be used together with splitting_ops. This may provide
3621
- performance benefits for smaller models.
3623
+ - {attr}`use_cudagraph`
3624
+ - {attr}`cudagraph_capture_sizes`
3625
+ - {attr}`cudagraph_num_of_warmups`
3626
+ - {attr}`cudagraph_copy_inputs`
3627
+ - {attr}`full_cuda_graph`
3628
- Inductor compilation:
3623
- - use_inductor: whether to use inductor compilation.
3624
- - False: inductor compilation is not used. graph runs in eager.
3625
- - True: inductor compilation is used. one graph for symbolic shape
3626
- is compiled. In addition, compile for compile_sizes,
3627
- using configurations in inductor_compile_config.
3628
- - compile_sizes: sizes to compile for inductor. In addition
3629
- to integers, it also supports "cudagraph_capture_sizes" to
3630
- specify the sizes for cudagraph capture.
3631
- - inductor_compile_config: additional configurations for inductor.
3632
- - None: use default configurations.
3633
- - inductor_passes: additional passes for inductor. It is a dictionary
3634
- from pass name to pass function qualified name. We use function
3635
- name because the config uses json format. If we pass the config
3636
- from Python, functions can also be passed directly via Python object
3637
- constructor, e.g. `CompilationConfig(inductor_passes={"a": func})`
3638
- - custom inductor passes: see PassConfig for more details
3629
+ - {attr}`use_inductor`
3630
+ - {attr}`compile_sizes`
3631
+ - {attr}`inductor_compile_config`
3632
+ - {attr}`inductor_passes`
3633
+ - custom inductor passes
3634
3635
Why we have different sizes for cudagraph and inductor:
3636
- cudagraph: a cudagraph captured for a specific size can only be used
@@ -3646,83 +3641,135 @@ class CompilationConfig(BaseModel):
3641
static shapes. However, we find the general shape compilation is
3642
sufficient for most cases. It might be beneficial to compile for
3643
certain small batchsizes, where inductor is good at optimizing.
3649
- """ # noqa
3644
+ """
3645
+ # Top-level Compilation control
3646
level: int = 0
3647
+ """The level of compilation:
3648
+
3649
+ - 0: no compilation.
3650
+ - 1: dynamo as is.
3651
+ - 2: dynamo once.
3652
+ - 3: piecewise compilation."""
3653
debug_dump_path: str = ""
3654
+ """The path to dump the debug information."""
3655
cache_dir: str = ""
3656
+ """The directory to store the compiled graph, to accelerate Inductor
3657
+ compilation. By default, it will use model-related information to generate
3658
+ a cache directory."""
3659
backend: str = ""
3654
- custom_ops: list[str] = Field(default_factory=list)
3655
- splitting_ops: list[str] = Field(default=None) # type: ignore
3656
-
3660
+ """The backend for compilation. It needs to be a string:
3661
+
3662
+ - "" (empty string): use the default backend.
3663
+ - "eager"/"openxla"/...: use the specified backend registered in PyTorch.
3664
+ - "full.module.name": a qualified name which can be used to import the
3665
+
3666
+ backend function.
3667
+ We use string to avoid serialization issues when using compilation in a
3668
+ distributed setting. When the compilation level is 1 or 2, the backend is
3669
+ used for the compilation directly (it sees the whole graph). When the
3670
+ compilation level is 3, the backend is used for the piecewise compilation
3671
+ (it sees a part of the graph)."""
3672
+ custom_ops: list[str] = field(default_factory=list)
3673
+ """Fine-grained control over which custom ops to enable/disable. Use 'all'
3674
+ to enable all, 'none' to disable all. Also specify a list of custom op
3675
+ names to enable (prefixed with a '+'), or disable (prefixed with a '-').
3676
+ Examples:
3677
+
3678
+ - 'all,-op1' to enable all except op1
3679
+ - 'none,+op1,+op2' to enable only op1 and op2
3680
+
3681
+ By default, all custom ops are enabled when running without Inductor and
3682
+ disabled when running with Inductor (compile_level >= Inductor)."""
3683
+ splitting_ops: list[str] = field(default_factory=list)
3684
+ """A list of ops to split the full graph into subgraphs, used in piecewise
3685
+ compilation."""
3686
+
3687
+ # Inductor capture
3688
use_inductor: bool = True
3658
- compile_sizes: Optional[list[Union[int, str]]] = Field(default=None)
3659
- inductor_compile_config: dict = Field(default_factory=dict)
3660
- inductor_passes: dict[str, str] = Field(default_factory=dict)
3661
-
3689
+ """Whether to use inductor compilation:
3690
+
3691
+ - False: inductor compilation is not used. graph runs in eager.
3692
+ - True: inductor compilation is used. one graph for symbolic shape
3693
+ is compiled. In addition, compile for compile_sizes,
3694
+ using configurations in inductor_compile_config."""
3695
+ compile_sizes: Optional[list[Union[int, str]]] = None
3696
+ """Sizes to compile for inductor. In addition
3697
+ to integers, it also supports "cudagraph_capture_sizes" to
3698
+ specify the sizes for cudagraph capture."""
3699
+ inductor_compile_config: dict = field(default_factory=dict)
3700
+ """Additional configurations for inductor.
3701
+ - None: use default configurations."""
3702
+ inductor_passes: dict[str, str] = field(default_factory=dict)
3703
+ """Additional passes for inductor. It is a dictionary
3704
+ from pass name to pass function qualified name. We use function
3705
+ name because the config uses JSON format. If we pass the config
3706
+ from Python, functions can also be passed directly via Python object
3707
+ constructor, e.g. `CompilationConfig(inductor_passes={"a": func})`."""
3708
+
3709
+ # CudaGraph compilation
3710
use_cudagraph: bool = False
3711
+ """Whether to use cudagraph inside compilation.
3712
+ - False: cudagraph inside compilation is not used.
3713
+ - True: cudagraph inside compilation is used. It requires
3714
+ that all input buffers have fixed addresses, and all
3715
+ splitting ops write their outputs to input buffers.
3716
+ Note that this is orthogonal to the cudagraph capture logic
3717
+ outside of compilation.
3718
+ TODO: move outside cudagraph logic into compilation.
3719
+ torch.compile will handle cudagraph capture logic in the future."""
3720
cudagraph_num_of_warmups: int = 0
3721
+ """Number of warmup runs for cudagraph.
3722
+ It means the first several runs will be treated as warmup runs.
3723
+ Only after that, the execution will be recorded, and the recorded
3724
+ cudagraph will be used for subsequent runs."""
3725
cudagraph_capture_sizes: Optional[list[int]] = None
3726
+ """Sizes to capture cudagraph.
3727
+ - None (default): capture sizes are inferred from vllm config.
3728
+ - list[int]: capture sizes are specified as given."""
3729
cudagraph_copy_inputs: bool = False
3730
+ """Whether to copy input tensors for
3731
+ cudagraph. If the caller can guarantee that the same input buffers
3732
+ are always used, it can set this to False. Otherwise, it should
3733
+ set this to True, and the compiler will copy the input to an
3734
+ internally managed buffer. Default is False."""
3735
full_cuda_graph: bool = False
3667
-
3668
- class PassConfig(BaseModel):
3669
- """
3670
- Configuration for custom Inductor passes.
3671
- This is separate from general CompilationConfig so that inductor passes
3672
- don't all have access to full configuration - that would create a cycle
3673
- as the PassManager is set as a property of config.
3674
- - dump_graph_stages: list of stages for which we want to dump the graph.
3675
- Each pass defines its own stages (before, after, maybe in-between).
3676
- - dump_graph_dir: directory to dump the graphs. Default is .
3677
- - enable_fusion: whether to enable the custom fusion pass.
3678
- - enable_noop: whether to enable the custom no-op elimination pass.
3679
- TODO(luka) better pass enabling system.
3680
- - enable_sequence_parallelism: whether to enable sequence parallelism.
3681
- """
3682
- dump_graph_stages: list[str] = Field(default_factory=list)
3683
- dump_graph_dir: Path = Field(default=Path("."))
3684
- enable_fusion: bool = True
3685
- enable_noop: bool = True
3686
- enable_sequence_parallelism: bool = False
3687
-
3688
- def uuid(self):
3689
- """
3690
- Produces a hash unique to the pass configuration.
3691
- Any new fields that affect compilation should be added to the hash.
3692
- Do not include dump_graph_* in the hash - they don't affect
3693
- compilation.
3694
- """
3695
- dict_ = self.model_dump(include={"enable_fusion", "enable_noop", \
3696
- "enable_sequence_parallelism"})
3697
- return InductorPass.hash_dict(dict_)
3698
-
3699
- def model_post_init(self, __context: Any) -> None:
3700
- if not self.enable_noop and self.enable_fusion:
3701
- logger.warning_once(
3702
- "Fusion enabled but reshape elimination disabled. "
3703
- "RMSNorm + quant (fp8) fusion might not work")
3704
-
3705
- pass_config: PassConfig = Field(default_factory=PassConfig)
3706
-
3707
- # not configurable, computed after init
3708
- max_capture_size: int = PrivateAttr
3709
- local_cache_dir: str = PrivateAttr # local cache dir for each rank
3710
- # optimization:
3711
- # Intuitively, bs_to_padded_graph_size should be dict[int, int].
3712
- # since we know all keys are in a range [0, max_capture_size],
3713
- # we can optimize it to list[int] for better lookup performance.
3714
- bs_to_padded_graph_size: list[int] = PrivateAttr
3736
+ """whether to use a full cuda graph for the entire forward pass rather than
3737
+ splitting certain operations such as attention into subgraphs. Thus this
3738
+ flag cannot be used together with splitting_ops. This may provide
3739
+ performance benefits for smaller models."""
3740
+
3741
+ pass_config: PassConfig = field(default_factory=PassConfig)
3742
+ """Custom inductor passes, see PassConfig for more details"""
3743
+
3744
+ max_capture_size: int = field(default=None, init=False) # type: ignore
3745
+ """not configurable, computed after init"""
3746
+ local_cache_dir: str = field(default=None, init=False) # type: ignore
3747
+ """local cache dir for each rank"""
3748
+ bs_to_padded_graph_size: list[int] = field(
3749
+ default=None, # type: ignore
3750
+ init=False)
3751
+ """optimization:
3752
+ Intuitively, bs_to_padded_graph_size should be dict[int, int].
3753
+ since we know all keys are in a range [0, max_capture_size],
3754
+ we can optimize it to list[int] for better lookup performance."""
3755
3756
# keep track of enabled and disabled custom ops
3717
- enabled_custom_ops: Counter[str] = PrivateAttr
3718
- disabled_custom_ops: Counter[str] = PrivateAttr
3719
- traced_files: set[str] = PrivateAttr
3720
- compilation_time: float = PrivateAttr
3721
-
3722
- # Per-model forward context
3723
- # Map from layer name to layer objects that need to be accessed outside
3724
- # model code, e.g., Attention, FusedMOE when dp_size>1.
3725
- static_forward_context: dict[str, Any] = PrivateAttr
3757
+ enabled_custom_ops: Counter[str] = field(default_factory=Counter,
3758
+ init=False)
3759
+ """custom ops that are enabled"""
3760
+ disabled_custom_ops: Counter[str] = field(default_factory=Counter,
3761
+ init=False)
3762
+ """custom ops that are disabled"""
3763
+ traced_files: set[str] = field(default_factory=set, init=False)
3764
+ """files that are traced for compilation"""
3765
+ compilation_time: float = field(default=0.0, init=False)
3766
+ """time taken for compilation"""
3767
+
3768
+ static_forward_context: dict[str, Any] = field(default_factory=dict,
3769
+ init=False)
3770
+ """Per-model forward context
3771
+ Map from layer name to layer objects that need to be accessed outside
3772
+ model code, e.g., Attention, FusedMOE when dp_size>1."""
3773
3774
def compute_hash(self) -> str:
3775
"""
@@ -3757,7 +3804,17 @@ class CompilationConfig(BaseModel):
3804
"pass_config",
3805
"traced_files",
3806
}
3760
- return self.model_dump_json(exclude=exclude, exclude_unset=True)
3807
+ include = dict()
3808
+ for k, v in asdict(self).items():
3809
+ if k in exclude:
3810
+ continue
3811
+ f = get_field(CompilationConfig, k)
3812
+ if (d := f.default) is not MISSING and d == v:
3813
+ continue
3814
+ if (df := f.default_factory) is not MISSING and df() == v:
3815
+ continue
3816
+ include[k] = v
3817
+ return json.dumps(include)
3818
3819
__str__ = __repr__
3820
@@ -3766,12 +3823,9 @@ class CompilationConfig(BaseModel):
3823
"""Parse the CLI value for the compilation config."""
3824
if cli_value in ["0", "1", "2", "3"]:
3825
return cls(level=int(cli_value))
3769
- # do not use `eval`, it is dangerous and can execute arbitrary code
3770
- dict_value = ast.literal_eval(cli_value)
3771
- return CompilationConfig.model_validate(dict_value)
3772
-
3773
- def model_post_init(self, __context: Any) -> None:
3826
+ return cls(**json.loads(cli_value))
3827
3828
+ def __post_init__(self) -> None:
3829
count_none = self.custom_ops.count("none")
3830
count_all = self.custom_ops.count("all")
3831
assert count_none + count_all <= 1, "Can only specify 'none' or 'all'"
@@ -3789,9 +3843,6 @@ class CompilationConfig(BaseModel):
3843
if KEY not in self.inductor_compile_config:
3844
self.inductor_compile_config[KEY] = False
3845
3792
- if self.splitting_ops is None:
3793
- self.splitting_ops = []
3794
-
3846
for k, v in self.inductor_passes.items():
3847
if not isinstance(v, str):
3848
assert callable(v), (
@@ -3808,11 +3859,8 @@ class CompilationConfig(BaseModel):
3859
self.inductor_compile_config[k] = func if isinstance(
3860
func, InductorPass) else CallableInductorPass(func)
3861
3811
- self.enabled_custom_ops = Counter()
3812
- self.disabled_custom_ops = Counter()
3813
- self.traced_files = set()
3814
- self.static_forward_context = {}
3815
- self.compilation_time = 0.0
3862
+ if isinstance(self.pass_config, dict):
3863
+ self.pass_config = PassConfig(**self.pass_config)
3864
3865
def init_backend(self, vllm_config: "VllmConfig") -> Union[str, Callable]:
3866
if self.level == CompilationLevel.NO_COMPILATION:
@@ -3899,39 +3947,67 @@ class CompilationConfig(BaseModel):
3947
]
3948
3949
3950
+@config
3951
@dataclass
3952
class VllmConfig:
3953
"""Dataclass which contains all vllm-related configuration. This
3954
simplifies passing around the distinct configurations in the codebase.
3955
"""
3956
3908
- model_config: ModelConfig = field(default=None, init=True) # type: ignore
3909
- cache_config: CacheConfig = field(default=None, init=True) # type: ignore
3910
- parallel_config: ParallelConfig = field(default_factory=ParallelConfig,
3911
- init=True)
3912
- scheduler_config: SchedulerConfig = field(default_factory=SchedulerConfig,
3913
- init=True)
3914
- device_config: DeviceConfig = field(default=None,
3915
- init=True) # type: ignore
3916
- load_config: LoadConfig = field(default=None, init=True) # type: ignore
3957
+ model_config: ModelConfig = field(default_factory=ModelConfig)
3958
+ """Model configuration."""
3959
+ cache_config: CacheConfig = field(default_factory=CacheConfig)
3960
+ """Cache configuration."""
3961
+ parallel_config: ParallelConfig = field(default_factory=ParallelConfig)
3962
+ """Parallel configuration."""
3963
+ scheduler_config: SchedulerConfig = field(default_factory=SchedulerConfig)
3964
+ """Scheduler configuration."""
3965
+ device_config: DeviceConfig = field(default_factory=DeviceConfig)
3966
+ """Device configuration."""
3967
+ load_config: LoadConfig = field(default_factory=LoadConfig)
3968
+ """Load configuration."""
3969
lora_config: Optional[LoRAConfig] = None
3918
- speculative_config: SpeculativeConfig = field(default=None,
3919
- init=True) # type: ignore
3970
+ """LoRA configuration."""
3971
+ speculative_config: Optional[SpeculativeConfig] = None
3972
+ """Speculative decoding configuration."""
3973
decoding_config: Optional[DecodingConfig] = None
3974
+ """Decoding configuration."""
3975
observability_config: Optional[ObservabilityConfig] = None
3976
+ """Observability configuration."""
3977
prompt_adapter_config: Optional[PromptAdapterConfig] = None
3978
+ """Prompt adapter configuration."""
3979
quant_config: Optional[QuantizationConfig] = None
3924
- compilation_config: CompilationConfig = field(default=None,
3925
- init=True) # type: ignore
3926
- kv_transfer_config: KVTransferConfig = field(default=None,
3927
- init=True) # type: ignore
3980
+ """Quantization configuration."""
3981
+ compilation_config: CompilationConfig = field(
3982
+ default_factory=CompilationConfig)
3983
+ """`torch.compile` configuration for the model.
3984
+
3985
+ When it is a number (0, 1, 2, 3), it will be interpreted as the
3986
+ optimization level.
3987
+
3988
+ NOTE: level 0 is the default level without any optimization. level 1 and 2
3989
+ are for internal testing only. level 3 is the recommended level for
3990
+ production.
3991
+
3992
+ Following the convention of traditional compilers, using `-O` without space
3993
+ is also supported. `-O3` is equivalent to `-O 3`.
3994
+
3995
+ You can specify the full compilation config like so:
3996
+ `{"level": 3, "cudagraph_capture_sizes": [1, 2, 4, 8]}`
3997
+ """
3998
+ kv_transfer_config: Optional[KVTransferConfig] = None
3999
+ """The configurations for distributed KV cache transfer."""
4000
kv_events_config: Optional[KVEventsConfig] = None
4001
+ """The configurations for event publishing."""
4002
# some opaque config, only used to provide additional information
4003
# for the hash computation, mainly used for testing, debugging or out of
4004
# tree config registration.
3932
- additional_config: SupportsHash = field(default=None,
3933
- init=True) # type: ignore
4005
+ additional_config: Union[dict, SupportsHash] = field(default_factory=dict)
4006
+ """Additional config for specified platform. Different platforms may
4007
+ support different configs. Make sure the configs are valid for the platform
4008
+ you are using. Contents must be hashable."""
4009
instance_id: str = ""
4010
+ """The ID of the vLLM instance."""
4011
4012
def compute_hash(self) -> str:
4013
"""
@@ -4012,7 +4088,14 @@ class VllmConfig:
4088
else:
4089
vllm_factors.append("None")
4090
if self.additional_config:
4015
- vllm_factors.append(self.additional_config.compute_hash())
4091
+ if isinstance(additional_config := self.additional_config, dict):
4092
+ additional_config_hash = hashlib.md5(
4093
+ json.dumps(additional_config, sort_keys=True).encode(),
4094
+ usedforsecurity=False,
4095
+ ).hexdigest()
4096
+ else:
4097
+ additional_config_hash = additional_config.compute_hash()
4098
+ vllm_factors.append(additional_config_hash)
4099
else:
4100
vllm_factors.append("None")
4101
factors.append(vllm_factors)
vllm/distributed/kv_events.py
+2
-1
@@ -5,6 +5,7 @@ import threading
5
import time
6
from abc import ABC, abstractmethod
7
from collections import deque
8
+from dataclasses import asdict
9
from itertools import count
10
from queue import Queue
11
from typing import Any, Callable, Optional, Union
@@ -284,7 +285,7 @@ class EventPublisherFactory:
285
if not config:
286
return NullEventPublisher()
287
287
- config_dict = config.model_dump()
288
+ config_dict = asdict(config)
289
290
kind = config_dict.pop("publisher", "null")
291
config_dict.pop("enable_kv_cache_events")
vllm/engine/arg_utils.py
+56
-75
@@ -7,10 +7,10 @@ import json
7
import re
8
import threading
9
import warnings
10
-from dataclasses import MISSING, dataclass, fields
10
+from dataclasses import MISSING, dataclass, fields, is_dataclass
11
from itertools import permutations
12
-from typing import (Any, Callable, Dict, List, Literal, Optional, Type,
13
- TypeVar, Union, cast, get_args, get_origin)
12
+from typing import (Annotated, Any, Callable, Dict, List, Literal, Optional,
13
+ Type, TypeVar, Union, cast, get_args, get_origin)
14
15
import torch
16
from typing_extensions import TypeIs, deprecated
@@ -36,7 +36,8 @@ from vllm.reasoning import ReasoningParserManager
36
from vllm.test_utils import MODEL_WEIGHTS_S3_BUCKET, MODELS_ON_S3
37
from vllm.transformers_utils.utils import check_gguf_file
38
from vllm.usage.usage_lib import UsageContext
39
-from vllm.utils import FlexibleArgumentParser, GiB_bytes, is_in_ray_actor
39
+from vllm.utils import (FlexibleArgumentParser, GiB_bytes, is_in_doc_build,
40
+ is_in_ray_actor)
41
42
# yapf: enable
43
@@ -48,12 +49,9 @@ TypeHint = Union[type[Any], object]
49
TypeHintT = Union[type[T], object]
50
51
51
-def optional_type(
52
- return_type: Callable[[str], T]) -> Callable[[str], Optional[T]]:
52
+def parse_type(return_type: Callable[[str], T]) -> Callable[[str], T]:
53
54
- def _optional_type(val: str) -> Optional[T]:
55
- if val == "" or val == "None":
56
- return None
54
+ def _parse_type(val: str) -> T:
55
try:
56
if return_type is json.loads and not re.match("^{.*}$", val):
57
return cast(T, nullable_kvs(val))
@@ -62,14 +60,24 @@ def optional_type(
60
raise argparse.ArgumentTypeError(
61
f"Value {val} cannot be converted to {return_type}.") from e
62
63
+ return _parse_type
64
+
65
+
66
+def optional_type(
67
+ return_type: Callable[[str], T]) -> Callable[[str], Optional[T]]:
68
+
69
+ def _optional_type(val: str) -> Optional[T]:
70
+ if val == "" or val == "None":
71
+ return None
72
+ return parse_type(return_type)(val)
73
+
74
return _optional_type
75
76
77
def union_dict_and_str(val: str) -> Optional[Union[str, dict[str, str]]]:
78
if not re.match("^{.*}$", val):
79
return str(val)
71
- else:
72
- return optional_type(json.loads)(val)
80
+ return optional_type(json.loads)(val)
81
82
83
@deprecated(
@@ -144,10 +152,25 @@ def get_kwargs(cls: ConfigType) -> dict[str, Any]:
152
cls_docs = get_attr_docs(cls)
153
kwargs = {}
154
for field in fields(cls):
155
+ # Get the set of possible types for the field
156
+ type_hints: set[TypeHint] = set()
157
+ if get_origin(field.type) in {Union, Annotated}:
158
+ type_hints.update(get_args(field.type))
159
+ else:
160
+ type_hints.add(field.type)
161
+
162
+ # If the field is a dataclass, we can use the model_validate_json
163
+ generator = (th for th in type_hints if is_dataclass(th))
164
+ dataclass_cls = next(generator, None)
165
+
166
# Get the default value of the field
148
- default = field.default
149
- if field.default_factory is not MISSING:
150
- default = field.default_factory()
167
+ if field.default is not MISSING:
168
+ default = field.default
169
+ elif field.default_factory is not MISSING:
170
+ if is_dataclass(field.default_factory) and is_in_doc_build():
171
+ default = {}
172
+ else:
173
+ default = field.default_factory()
174
175
# Get the help text for the field
176
name = field.name
@@ -158,16 +181,17 @@ def get_kwargs(cls: ConfigType) -> dict[str, Any]:
181
# Initialise the kwargs dictionary for the field
182
kwargs[name] = {"default": default, "help": help}
183
161
- # Get the set of possible types for the field
162
- type_hints: set[TypeHint] = set()
163
- if get_origin(field.type) is Union:
164
- type_hints.update(get_args(field.type))
165
- else:
166
- type_hints.add(field.type)
167
-
184
# Set other kwargs based on the type hints
185
json_tip = "\n\nShould be a valid JSON string."
170
- if contains_type(type_hints, bool):
186
+ if dataclass_cls is not None:
187
+ dataclass_init = lambda x, f=dataclass_cls: f(**json.loads(x))
188
+ # Special case for configs with a from_cli method
189
+ if hasattr(dataclass_cls, "from_cli"):
190
+ from_cli = dataclass_cls.from_cli
191
+ dataclass_init = lambda x, f=from_cli: f(x)
192
+ kwargs[name]["type"] = dataclass_init
193
+ kwargs[name]["help"] += json_tip
194
+ elif contains_type(type_hints, bool):
195
# Creates --no-<name> and --<name> flags
196
kwargs[name]["action"] = argparse.BooleanOptionalAction
197
elif contains_type(type_hints, Literal):
@@ -202,7 +226,7 @@ def get_kwargs(cls: ConfigType) -> dict[str, Any]:
226
kwargs[name]["type"] = union_dict_and_str
227
elif contains_type(type_hints, dict):
228
# Dict arguments will always be optional
205
- kwargs[name]["type"] = optional_type(json.loads)
229
+ kwargs[name]["type"] = parse_type(json.loads)
230
kwargs[name]["help"] += json_tip
231
elif (contains_type(type_hints, str)
232
or any(is_not_builtin(th) for th in type_hints)):
@@ -771,63 +795,20 @@ class EngineArgs:
795
scheduler_group.add_argument("--scheduler-cls",
796
**scheduler_kwargs["scheduler_cls"])
797
774
- # Compilation arguments
775
- # compilation_kwargs = get_kwargs(CompilationConfig)
776
- compilation_group = parser.add_argument_group(
777
- title="CompilationConfig",
778
- description=CompilationConfig.__doc__,
779
- )
780
- compilation_group.add_argument(
781
- "--compilation-config",
782
- "-O",
783
- type=CompilationConfig.from_cli,
784
- default=None,
785
- help="torch.compile configuration for the model. "
786
- "When it is a number (0, 1, 2, 3), it will be "
787
- "interpreted as the optimization level.\n"
788
- "NOTE: level 0 is the default level without "
789
- "any optimization. level 1 and 2 are for internal "
790
- "testing only. level 3 is the recommended level "
791
- "for production.\n"
792
- "To specify the full compilation config, "
793
- "use a JSON string, e.g. ``{\"level\": 3, "
794
- "\"cudagraph_capture_sizes\": [1, 2, 4, 8]}``\n"
795
- "Following the convention of traditional "
796
- "compilers, using ``-O`` without space is also "
797
- "supported. ``-O3`` is equivalent to ``-O 3``.")
798
-
799
- # KVTransfer arguments
800
- # kv_transfer_kwargs = get_kwargs(KVTransferConfig)
801
- kv_transfer_group = parser.add_argument_group(
802
- title="KVTransferConfig",
803
- description=KVTransferConfig.__doc__,
804
- )
805
- kv_transfer_group.add_argument(
806
- "--kv-transfer-config",
807
- type=KVTransferConfig.from_cli,
808
- default=None,
809
- help="The configurations for distributed KV cache "
810
- "transfer. Should be a JSON string.")
811
- kv_transfer_group.add_argument(
812
- '--kv-events-config',
813
- type=KVEventsConfig.from_cli,
814
- default=None,
815
- help='The configurations for event publishing.')
816
-
798
# vLLM arguments
818
- # vllm_kwargs = get_kwargs(VllmConfig)
799
+ vllm_kwargs = get_kwargs(VllmConfig)
800
vllm_group = parser.add_argument_group(
801
title="VllmConfig",
802
description=VllmConfig.__doc__,
803
)
823
- vllm_group.add_argument(
824
- "--additional-config",
825
- type=json.loads,
826
- default=None,
827
- help="Additional config for specified platform in JSON format. "
828
- "Different platforms may support different configs. Make sure the "
829
- "configs are valid for the platform you are using. The input format"
830
- " is like '{\"config_key\":\"config_value\"}'")
804
+ vllm_group.add_argument("--kv-transfer-config",
805
+ **vllm_kwargs["kv_transfer_config"])
806
+ vllm_group.add_argument('--kv-events-config',
807
+ **vllm_kwargs["kv_events_config"])
808
+ vllm_group.add_argument("--compilation-config", "-O",
809
+ **vllm_kwargs["compilation_config"])
810
+ vllm_group.add_argument("--additional-config",
811
+ **vllm_kwargs["additional_config"])
812
813
# Other arguments
814
parser.add_argument('--use-v2-block-manager',
vllm/entrypoints/llm.py
+9
-4
@@ -13,7 +13,8 @@ from typing_extensions import TypeVar, deprecated
13
14
from vllm.beam_search import (BeamSearchInstance, BeamSearchOutput,
15
BeamSearchSequence, get_beam_search_score)
16
-from vllm.config import CompilationConfig, ModelDType, TokenizerMode
16
+from vllm.config import (CompilationConfig, ModelDType, TokenizerMode,
17
+ is_init_field)
18
from vllm.engine.arg_utils import (EngineArgs, HfOverrides, PoolerConfig,
19
TaskOption)
20
from vllm.engine.llm_engine import LLMEngine
@@ -204,9 +205,13 @@ class LLM:
205
kwargs["worker_cls"] = cloudpickle.dumps(worker_cls)
206
207
if compilation_config is not None:
207
- if isinstance(compilation_config, (int, dict)):
208
- compilation_config_instance = CompilationConfig.from_cli(
209
- str(compilation_config))
208
+ if isinstance(compilation_config, int):
209
+ compilation_config_instance = CompilationConfig(
210
+ level=compilation_config)
211
+ elif isinstance(compilation_config, dict):
212
+ predicate = lambda x: is_init_field(CompilationConfig, x[0])
213
+ compilation_config_instance = CompilationConfig(
214
+ **dict(filter(predicate, compilation_config.items())))
215
else:
216
compilation_config_instance = compilation_config
217
else:
vllm/platforms/tpu.py
+6
-5
@@ -1,6 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
3
-from typing import TYPE_CHECKING, Optional, Tuple, Union
3
+from typing import TYPE_CHECKING, Optional, Tuple, Union, cast
4
5
import torch
6
from tpu_info import device
@@ -13,9 +13,10 @@ from vllm.sampling_params import SamplingParams, SamplingType
13
from .interface import Platform, PlatformEnum, _Backend
14
15
if TYPE_CHECKING:
16
- from vllm.config import ModelConfig, VllmConfig
16
+ from vllm.config import BlockSize, ModelConfig, VllmConfig
17
from vllm.pooling_params import PoolingParams
18
else:
19
+ BlockSize = None
20
ModelConfig = None
21
VllmConfig = None
22
PoolingParams = None
@@ -94,7 +95,7 @@ class TpuPlatform(Platform):
95
cache_config = vllm_config.cache_config
96
# For v0, the default block size is 16.
97
if cache_config and cache_config.block_size is None:
97
- cache_config.block_size = 16
98
+ cache_config.block_size = cast(BlockSize, 16)
99
compilation_config = vllm_config.compilation_config
100
101
# TPU only supports DYNAMO_ONCE compilation level
@@ -118,7 +119,7 @@ class TpuPlatform(Platform):
119
from vllm.v1.attention.backends.pallas import (
120
PallasAttentionBackend)
121
cache_config.block_size = PallasAttentionBackend.get_page_size(
121
- vllm_config)
122
+ vllm_config) # type: ignore[assignment]
123
min_page_size = PallasAttentionBackend.get_min_page_size(
124
vllm_config)
125
if min_page_size > cache_config.block_size:
@@ -128,7 +129,7 @@ class TpuPlatform(Platform):
129
cache_config.block_size,
130
min_page_size,
131
)
131
- cache_config.block_size = min_page_size
132
+ cache_config.block_size = min_page_size # type: ignore[assignment]
133
134
parallel_config = vllm_config.parallel_config
135
scheduler_config = vllm_config.scheduler_config
vllm/utils.py
+8
@@ -1820,6 +1820,14 @@ def get_cuda_view_from_cpu_tensor(cpu_tensor: torch.Tensor) -> torch.Tensor:
1820
return torch.ops._C.get_cuda_view_from_cpu_tensor(cpu_tensor)
1821
1822
1823
+def is_in_doc_build() -> bool:
1824
+ try:
1825
+ from sphinx.ext.autodoc.mock import _MockModule
1826
+ return isinstance(zmq, _MockModule)
1827
+ except ModuleNotFoundError:
1828
+ return False
1829
+
1830
+
1831
def import_from_path(module_name: str, file_path: Union[str, os.PathLike]):
1832
"""
1833
Import a Python file according to its file path.