@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / d215d1efca
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 6, 00:45 UTC

[Mypy] Better fixes for the `mypy` issues in `vllm/config` (#37902)

Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Harry Mellor committed Mar 25, 2026 at 13:14 UTC d215d1efca7a18eb2a19007f229bbb070bfbee93
35 files changed +153 -182
benchmarks/benchmark_long_document_qa_throughput.py
+1 -2
@@ -42,7 +42,6 @@ details.
42
43 import random
44 import time
45 -from dataclasses import fields
45
46 from vllm import LLM, SamplingParams
47 from vllm.engine.arg_utils import EngineArgs
@@ -124,7 +123,7 @@ def main(args):
123
124 # Create the LLM engine
125 engine_args = EngineArgs.from_cli_args(args)
127 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
126 + llm = LLM.from_engine_args(engine_args)
127 sampling_params = SamplingParams(temperature=0, max_tokens=args.output_len)
128
129 print("------warm up------")
benchmarks/benchmark_prefix_caching.py
+1 -2
@@ -32,7 +32,6 @@ import dataclasses
32 import json
33 import random
34 import time
35 -from dataclasses import fields
35
36 from transformers import PreTrainedTokenizerBase
37
@@ -197,7 +196,7 @@ def main(args):
196
197 engine_args = EngineArgs.from_cli_args(args)
198
200 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
199 + llm = LLM.from_engine_args(engine_args)
200
201 sampling_params = SamplingParams(
202 temperature=0,
benchmarks/benchmark_prioritization.py
+1 -2
@@ -6,7 +6,6 @@ import argparse
6 import json
7 import random
8 import time
9 -from dataclasses import fields
9
10 from transformers import AutoTokenizer, PreTrainedTokenizerBase
11
@@ -79,7 +78,7 @@ def run_vllm(
78 ) -> float:
79 from vllm import LLM, SamplingParams
80
82 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
81 + llm = LLM.from_engine_args(engine_args)
82
83 assert all(
84 llm.llm_engine.model_config.max_model_len >= (request[1] + request[2])
examples/offline_inference/audio_language.py
+1 -2
@@ -9,7 +9,6 @@ on HuggingFace model repository.
9 """
10
11 import os
12 -from dataclasses import asdict
12 from typing import Any, NamedTuple
13
14 from huggingface_hub import snapshot_download
@@ -633,7 +632,7 @@ def main(args):
632 req_data.engine_args.limit_mm_per_prompt or {}
633 )
634
636 - engine_args = asdict(req_data.engine_args) | {"seed": args.seed}
635 + engine_args = vars(req_data.engine_args) | {"seed": args.seed}
636 if args.tensor_parallel_size is not None:
637 engine_args["tensor_parallel_size"] = args.tensor_parallel_size
638 llm = LLM(**engine_args)
examples/offline_inference/encoder_decoder_multimodal.py
+5 -7
@@ -8,7 +8,6 @@ the explicit/implicit prompt format on enc-dec LMMs for text generation.
8 import os
9 import time
10 from collections.abc import Sequence
11 -from dataclasses import asdict
11 from typing import NamedTuple
12
13 from vllm import LLM, EngineArgs, PromptType, SamplingParams
@@ -91,13 +90,12 @@ def main(args):
90 req_data = model_example_map[model]()
91
92 # Disable other modalities to save memory
93 + engine_args = req_data.engine_args
94 default_limits = {"image": 0, "video": 0, "audio": 0}
95 - req_data.engine_args.limit_mm_per_prompt = default_limits | dict(
96 - req_data.engine_args.limit_mm_per_prompt or {}
97 - )
98 -
99 - engine_args = asdict(req_data.engine_args) | {"seed": args.seed}
100 - llm = LLM(**engine_args)
95 + limit_mm_per_prompt = default_limits | (engine_args.limit_mm_per_prompt or {})
96 + engine_args.limit_mm_per_prompt = limit_mm_per_prompt
97 + engine_args.seed = args.seed
98 + llm = LLM.from_engine_args(engine_args)
99
100 prompts = req_data.prompts
101
examples/offline_inference/load_sharded_state.py
+1 -3
@@ -20,8 +20,6 @@ python load_sharded_state.py \
20 --max-tokens 50
21 """
22
23 -import dataclasses
24 -
23 from vllm import LLM, EngineArgs, SamplingParams
24 from vllm.utils.argparse_utils import FlexibleArgumentParser
25
@@ -64,7 +62,7 @@ def main():
62 print(f"Tensor parallel size: {engine_args.tensor_parallel_size}")
63
64 # Load the model using engine args
67 - llm = LLM(**dataclasses.asdict(engine_args))
65 + llm = LLM.from_engine_args(engine_args)
66
67 # Prepare sampling parameters
68 sampling_params = SamplingParams(
examples/offline_inference/save_sharded_state.py
+1 -2
@@ -21,7 +21,6 @@ llm = LLM(
21 )
22 """
23
24 -import dataclasses
24 import os
25 import shutil
26 from pathlib import Path
@@ -60,7 +59,7 @@ def main(args):
59 if not Path(model_path).is_dir():
60 raise ValueError("model path must be a local directory")
61 # Create LLM instance from arguments
63 - llm = LLM(**dataclasses.asdict(engine_args))
62 + llm = LLM.from_engine_args(engine_args)
63 # Prepare output directory
64 Path(args.output).mkdir(exist_ok=True)
65 # Dump worker states to output directory
examples/offline_inference/vision_language.py
+6 -7
@@ -11,7 +11,6 @@ on HuggingFace model repository.
11 import os
12 import random
13 from contextlib import contextmanager
14 -from dataclasses import asdict
14 from typing import NamedTuple
15
16 from huggingface_hub import snapshot_download
@@ -2434,13 +2433,13 @@ def main(args):
2433 req_data.engine_args.limit_mm_per_prompt or {}
2434 )
2435
2437 - engine_args = asdict(req_data.engine_args) | {
2438 - "seed": args.seed,
2439 - "mm_processor_cache_gb": 0 if args.disable_mm_processor_cache else 4,
2440 - }
2436 + engine_args = req_data.engine_args
2437 + engine_args.seed = args.seed
2438 + mm_processor_cache_gb = 0 if args.disable_mm_processor_cache else 4
2439 + engine_args.mm_processor_cache_gb = mm_processor_cache_gb
2440 if args.tensor_parallel_size is not None:
2442 - engine_args["tensor_parallel_size"] = args.tensor_parallel_size
2443 - llm = LLM(**engine_args)
2441 + engine_args.tensor_parallel_size = args.tensor_parallel_size
2442 + llm = LLM.from_engine_args(engine_args)
2443
2444 # Don't want to check the flag multiple times, so just hijack `prompts`.
2445 prompts = (
examples/offline_inference/vision_language_multi_image.py
+8 -7
@@ -8,7 +8,6 @@ using the chat template defined by the model.
8
9 import os
10 from argparse import Namespace
11 -from dataclasses import asdict
11 from typing import NamedTuple
12
13 from huggingface_hub import snapshot_download
@@ -1481,10 +1480,11 @@ def run_generate(
1480 ):
1481 req_data = model_example_map[model](question, image_urls)
1482
1484 - engine_args = asdict(req_data.engine_args) | {"seed": seed}
1483 + engine_args = req_data.engine_args
1484 + engine_args.seed = seed
1485 if tensor_parallel_size is not None:
1486 - engine_args["tensor_parallel_size"] = tensor_parallel_size
1487 - llm = LLM(**engine_args)
1486 + engine_args.tensor_parallel_size = tensor_parallel_size
1487 + llm = LLM.from_engine_args(engine_args)
1488
1489 sampling_params = SamplingParams(
1490 temperature=0.0, max_tokens=256, stop_token_ids=req_data.stop_token_ids
@@ -1521,10 +1521,11 @@ def run_chat(
1521 req_data.engine_args.limit_mm_per_prompt or {}
1522 )
1523
1524 - engine_args = asdict(req_data.engine_args) | {"seed": seed}
1524 + engine_args = req_data.engine_args
1525 + engine_args.seed = seed
1526 if tensor_parallel_size is not None:
1526 - engine_args["tensor_parallel_size"] = tensor_parallel_size
1527 - llm = LLM(**engine_args)
1527 + engine_args.tensor_parallel_size = tensor_parallel_size
1528 + llm = LLM.from_engine_args(engine_args)
1529
1530 sampling_params = (
1531 SamplingParams(
examples/pooling/embed/vision_embedding_offline.py
+19 -25
@@ -10,12 +10,11 @@ on HuggingFace model repository.
10 """
11
12 import argparse
13 -from dataclasses import asdict
13 from pathlib import Path
14
15 from PIL.Image import Image
16
18 -from vllm import LLM, EngineArgs
17 +from vllm import LLM
18 from vllm.multimodal.utils import fetch_image
19 from vllm.utils.print_utils import print_embeddings
20
@@ -28,14 +27,13 @@ multi_modal_data = {"image": fetch_image(image_url)}
27
28
29 def run_clip(seed: int):
31 - engine_args = EngineArgs(
30 + llm = LLM(
31 model="openai/clip-vit-base-patch32",
32 runner="pooling",
33 limit_mm_per_prompt={"image": 1},
34 + seed=seed,
35 )
36
37 - llm = LLM(**asdict(engine_args) | {"seed": seed})
38 -
37 print("Text embedding output:")
38 outputs = llm.embed(text, use_tqdm=False)
39 print_embeddings(outputs[0].outputs.embedding)
@@ -53,15 +51,14 @@ def run_clip(seed: int):
51
52
53 def run_e5_v(seed: int):
56 - engine_args = EngineArgs(
54 + llm = LLM(
55 model="royokong/e5-v",
56 runner="pooling",
57 max_model_len=4096,
58 limit_mm_per_prompt={"image": 1},
59 + seed=seed,
60 )
61
63 - llm = LLM(**asdict(engine_args) | {"seed": seed})
64 -
62 llama3_template = "<|start_header_id|>user<|end_header_id|>\n\n{}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n \n" # noqa: E501
63
64 print("Text embedding output:")
@@ -108,20 +105,20 @@ def run_qwen3_vl(seed: int):
105
106 multi_modal_data["image"] = post_process_image(multi_modal_data["image"])
107
111 - engine_args = EngineArgs(
112 - model="Qwen/Qwen3-VL-Embedding-2B",
113 - runner="pooling",
114 - max_model_len=8192,
115 - limit_mm_per_prompt={"image": 1},
116 - mm_processor_kwargs={"do_resize": False} if smart_resize is not None else None,
117 - )
108 default_instruction = "Represent the user's input."
109 image_placeholder = "<|vision_start|><|image_pad|><|vision_end|>"
110 prompt_text = f"<|im_start|>system\n{default_instruction}<|im_end|>\n<|im_start|>user\n{text}<|im_end|>\n<|im_start|>assistant\n"
111 prompt_image = f"<|im_start|>system\n{default_instruction}<|im_end|>\n<|im_start|>user\n{image_placeholder}<|im_end|>\n<|im_start|>assistant\n"
112 prompt_image_text = f"<|im_start|>system\n{default_instruction}<|im_end|>\n<|im_start|>user\n{image_placeholder}{text}<|im_end|>\n<|im_start|>assistant\n"
113
124 - llm = LLM(**asdict(engine_args) | {"seed": seed})
114 + llm = LLM(
115 + model="Qwen/Qwen3-VL-Embedding-2B",
116 + runner="pooling",
117 + max_model_len=8192,
118 + limit_mm_per_prompt={"image": 1},
119 + mm_processor_kwargs={"do_resize": False} if smart_resize is not None else None,
120 + seed=seed,
121 + )
122
123 print("Text embedding output:")
124 outputs = llm.embed(prompt_text, use_tqdm=False)
@@ -149,14 +146,13 @@ def run_qwen3_vl(seed: int):
146
147
148 def run_siglip(seed: int):
152 - engine_args = EngineArgs(
149 + llm = LLM(
150 model="google/siglip-base-patch16-224",
151 runner="pooling",
152 limit_mm_per_prompt={"image": 1},
153 + seed=seed,
154 )
155
158 - llm = LLM(**asdict(engine_args) | {"seed": seed})
159 -
156 print("Text embedding output:")
157 outputs = llm.embed(text, use_tqdm=False)
158 print_embeddings(outputs[0].outputs.embedding)
@@ -174,16 +170,15 @@ def run_siglip(seed: int):
170
171
172 def run_vlm2vec_phi3v(seed: int):
177 - engine_args = EngineArgs(
173 + llm = LLM(
174 model="TIGER-Lab/VLM2Vec-Full",
175 runner="pooling",
176 max_model_len=4096,
177 trust_remote_code=True,
178 mm_processor_kwargs={"num_crops": 4},
179 limit_mm_per_prompt={"image": 1},
180 + seed=seed,
181 )
185 -
186 - llm = LLM(**asdict(engine_args) | {"seed": seed})
182 image_token = "<|image_1|>"
183
184 print("Text embedding output:")
@@ -259,7 +254,7 @@ def run_vlm2vec_qwen2vl(seed: int):
254 processor.save_pretrained(merged_path)
255 print("Done!")
256
262 - engine_args = EngineArgs(
257 + llm = LLM(
258 model=merged_path,
259 runner="pooling",
260 max_model_len=4096,
@@ -268,9 +263,8 @@ def run_vlm2vec_qwen2vl(seed: int):
263 "max_pixels": 12845056,
264 },
265 limit_mm_per_prompt={"image": 1},
266 + seed=seed,
267 )
272 -
273 - llm = LLM(**asdict(engine_args) | {"seed": seed})
268 image_token = "<|image_pad|>"
269
270 print("Text embedding output:")
examples/pooling/score/vision_reranker_offline.py
+1 -2
@@ -10,7 +10,6 @@ multimodal documents (text + images/videos).
10
11 from argparse import Namespace
12 from collections.abc import Callable
13 -from dataclasses import asdict
13 from pathlib import Path
14 from typing import NamedTuple
15
@@ -125,7 +124,7 @@ def main(args: Namespace):
124 model_request = model_example_map[args.model_name]()
125 engine_args = model_request.engine_args
126
128 - llm = LLM(**asdict(engine_args))
127 + llm = LLM.from_engine_args(engine_args)
128
129 print("Query: string & Document: string")
130 outputs = llm.score(query, document)
tests/compile/test_config.py
+6 -2
@@ -414,9 +414,12 @@ def test_cudagraph_sizes_post_init(
414 ctx,
415 patch("vllm.config.parallel.cuda_device_count_stateless", return_value=tp_size),
416 ):
417 + kwargs = {}
418 + if cudagraph_capture_sizes is not None:
419 + kwargs["cudagraph_capture_sizes"] = cudagraph_capture_sizes
420 + if max_cudagraph_capture_size is not None:
421 + kwargs["max_cudagraph_capture_size"] = max_cudagraph_capture_size
422 compilation_config = CompilationConfig(
418 - cudagraph_capture_sizes=cudagraph_capture_sizes,
419 - max_cudagraph_capture_size=max_cudagraph_capture_size,
423 pass_config=PassConfig(
424 enable_sp=enable_sp,
425 fuse_norm_quant=True,
@@ -425,6 +428,7 @@ def test_cudagraph_sizes_post_init(
428 sp_min_token_num=512 if enable_sp else None,
429 ),
430 cudagraph_mode=cudagraph_mode,
431 + **kwargs,
432 )
433 engine_args = EngineArgs(
434 model="facebook/opt-125m",
tests/entrypoints/offline_mode/test_offline_mode.py
+1 -4
@@ -2,7 +2,6 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Tests for HF_HUB_OFFLINE mode"""
4
5 -import dataclasses
5 import importlib
6 import sys
7
@@ -12,7 +11,6 @@ import urllib3
11
12 from vllm import LLM
13 from vllm.distributed import cleanup_dist_env_and_memory
15 -from vllm.engine.arg_utils import EngineArgs
14
15 MODEL_CONFIGS = [
16 {
@@ -160,8 +158,7 @@ def test_model_from_huggingface_offline(monkeypatch: pytest.MonkeyPatch):
158 # Need to re-import huggingface_hub
159 # and friends to set up offline mode
160 _re_import_modules()
163 - engine_args = EngineArgs(model="facebook/opt-125m")
164 - LLM(**dataclasses.asdict(engine_args))
161 + LLM(model="facebook/opt-125m")
162 finally:
163 # Reset the environment after the test
164 # NB: Assuming tests are run in online mode
tests/models/multimodal/generation/test_keye.py
+8 -11
@@ -1,6 +1,5 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -from dataclasses import asdict
3 from typing import NamedTuple
4
5 import pytest
@@ -29,14 +28,6 @@ def test_keye_vl(image_assets, question: str):
28 images = [asset.pil_image for asset in image_assets]
29 image_urls = [encode_image_url(image) for image in images]
30
32 - engine_args = EngineArgs(
33 - model=MODEL_NAME,
34 - trust_remote_code=True,
35 - max_model_len=8192,
36 - max_num_seqs=5,
37 - limit_mm_per_prompt={"image": len(image_urls)},
38 - )
39 -
31 placeholders = [{"type": "image", "image": url} for url in image_urls]
32 messages = [
33 {
@@ -54,8 +45,14 @@ def test_keye_vl(image_assets, question: str):
45 messages, tokenize=False, add_generation_prompt=True
46 )
47
57 - engine_args = asdict(engine_args) | {"seed": 42}
58 - llm = LLM(**engine_args)
48 + llm = LLM(
49 + model=MODEL_NAME,
50 + trust_remote_code=True,
51 + max_model_len=8192,
52 + max_num_seqs=5,
53 + limit_mm_per_prompt={"image": len(image_urls)},
54 + seed=42,
55 + )
56
57 sampling_params = SamplingParams(
58 temperature=0.0, max_tokens=256, stop_token_ids=None
tests/models/multimodal/generation/test_vit_backend_functionality.py
+5 -10
@@ -7,13 +7,12 @@ This test validates that each multimodal model can successfully generate outputs
7 using different ViT attention backends. Tests are parametrized by model and backend.
8 """
9
10 -from dataclasses import asdict
10 from typing import Any
11
12 import pytest
13 from transformers import AutoProcessor
14
16 -from vllm import LLM, EngineArgs, SamplingParams
15 +from vllm import LLM, SamplingParams
16 from vllm.multimodal.utils import encode_image_url
17 from vllm.multimodal.video import sample_frames_from_video
18 from vllm.platforms import current_platform
@@ -274,7 +273,7 @@ def run_llm_generate_test(config, mm_encoder_attn_backend, image_assets):
273 limit_mm_per_prompt = config.get("limit_mm_per_prompt", {"image": len(images)})
274
275 # Create engine
277 - engine_args = EngineArgs(
276 + llm = LLM(
277 model=config["model_name"],
278 trust_remote_code=True,
279 max_model_len=config["max_model_len"],
@@ -283,11 +282,9 @@ def run_llm_generate_test(config, mm_encoder_attn_backend, image_assets):
282 mm_encoder_attn_backend=mm_encoder_attn_backend,
283 hf_overrides=dummy_hf_overrides,
284 load_format="dummy",
285 + seed=42,
286 )
287
288 - engine_dict = asdict(engine_args) | {"seed": 42}
289 - llm = LLM(**engine_dict)
290 -
288 # Generate
289 sampling_params = SamplingParams(**config["sampling_params"])
290 outputs = llm.generate(
@@ -318,7 +315,7 @@ def run_llm_chat_test(config, mm_encoder_attn_backend, image_assets):
315 messages = build_dots_ocr_prompt([stop_sign_image], config)
316
317 # Create engine
321 - engine_args = EngineArgs(
318 + llm = LLM(
319 model=config["model_name"],
320 trust_remote_code=True,
321 max_model_len=config["max_model_len"],
@@ -327,11 +324,9 @@ def run_llm_chat_test(config, mm_encoder_attn_backend, image_assets):
324 mm_encoder_attn_backend=mm_encoder_attn_backend,
325 hf_overrides=dummy_hf_overrides,
326 load_format="dummy",
327 + seed=42,
328 )
329
332 - engine_dict = asdict(engine_args) | {"seed": 42}
333 - llm = LLM(**engine_dict)
334 -
330 # Generate using chat
331 sampling_params = SamplingParams(**config["sampling_params"])
332 outputs = llm.chat(messages=messages, sampling_params=sampling_params)
tests/models/multimodal/generation/test_voxtral_realtime.py
+1 -2
@@ -1,7 +1,6 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import contextlib
4 -from dataclasses import asdict
4
5 import pytest
6 import pytest_asyncio
@@ -75,7 +74,7 @@ def tokenizer() -> MistralTokenizer:
74 @pytest.fixture
75 def engine():
76 engine_args = EngineArgs(**ENGINE_CONFIG)
78 - llm = LLM(**asdict(engine_args))
77 + llm = LLM.from_engine_args(engine_args)
78 try:
79 yield llm
80 finally:
tests/v1/kv_connector/unit/test_example_connector.py
+15 -22
@@ -1,12 +1,11 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -from dataclasses import asdict
3 from typing import NamedTuple
4
5 import pytest
6 from PIL import Image
7
9 -from vllm import LLM, EngineArgs, SamplingParams
8 +from vllm import LLM, SamplingParams
9 from vllm.assets.image import ImageAsset
10 from vllm.config import AttentionConfig, KVTransferConfig
11 from vllm.multimodal.utils import encode_image_url
@@ -129,24 +128,6 @@ def test_shared_storage_connector_hashes(tmp_path, attn_backend):
128 # Using tmp_path as the storage path to store KV
129 print(f"KV storage path at: {str(tmp_path)}")
130
132 - # Configure the ExampleConnector
133 - kv_transfer_config = KVTransferConfig(
134 - kv_connector="ExampleConnector",
135 - kv_role="kv_both",
136 - kv_connector_extra_config={"shared_storage_path": str(tmp_path)},
137 - )
138 -
139 - engine_args = EngineArgs(
140 - model=MODEL_NAME,
141 - max_model_len=8192,
142 - max_num_seqs=1,
143 - gpu_memory_utilization=0.4,
144 - attention_config=AttentionConfig(backend=attn_backend),
145 - enforce_eager=True,
146 - kv_transfer_config=kv_transfer_config,
147 - limit_mm_per_prompt={"image": 2},
148 - )
149 -
131 # don't put this import at the top level
132 # it will call torch.accelerator.device_count()
133 from transformers import AutoProcessor
@@ -163,8 +144,20 @@ def test_shared_storage_connector_hashes(tmp_path, attn_backend):
144 assert image_1 != image_2, "The images should not be identical"
145
146 # Create the LLM instance
166 - engine_args = asdict(engine_args)
167 - llm = LLM(**engine_args)
147 + llm = LLM(
148 + model=MODEL_NAME,
149 + max_model_len=8192,
150 + max_num_seqs=1,
151 + gpu_memory_utilization=0.4,
152 + attention_config=AttentionConfig(backend=attn_backend),
153 + enforce_eager=True,
154 + kv_transfer_config=KVTransferConfig(
155 + kv_connector="ExampleConnector",
156 + kv_role="kv_both",
157 + kv_connector_extra_config={"shared_storage_path": str(tmp_path)},
158 + ),
159 + limit_mm_per_prompt={"image": 2},
160 + )
161
162 # Prepare the input cases
163 input_cases = [
vllm/benchmarks/latency.py
+1 -2
@@ -6,7 +6,6 @@ import argparse
6 import json
7 import os
8 import time
9 -from dataclasses import fields
9 from typing import Any
10
11 import numpy as np
@@ -85,7 +84,7 @@ def main(args: argparse.Namespace):
84
85 # NOTE(woosuk): If the request cannot be processed in a single batch,
86 # the engine will automatically process the request in multiple batches.
88 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
87 + llm = LLM.from_engine_args(engine_args)
88 assert llm.llm_engine.model_config.max_model_len >= (
89 args.input_len + args.output_len
90 ), (
vllm/benchmarks/mm_processor.py
+1 -2
@@ -17,7 +17,6 @@ import argparse
17 import json
18 import time
19 from collections import defaultdict
20 -from dataclasses import fields
20 from datetime import datetime
21 from typing import TYPE_CHECKING, Any, Literal
22
@@ -225,7 +224,7 @@ def benchmark_multimodal_processor(
224 args.seed = 0
225
226 engine_args = EngineArgs.from_cli_args(args)
228 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
227 + llm = LLM.from_engine_args(engine_args)
228
229 tokenizer = llm.get_tokenizer()
230 requests = get_requests(args, tokenizer)
vllm/benchmarks/startup.py
+1 -2
@@ -16,7 +16,6 @@ import shutil
16 import tempfile
17 import time
18 from contextlib import contextmanager
19 -from dataclasses import fields
19 from typing import Any
20
21 import numpy as np
@@ -67,7 +66,7 @@ def run_startup_in_subprocess(engine_args, result_queue):
66 # Measure total startup time
67 start_time = time.perf_counter()
68
70 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
69 + llm = LLM.from_engine_args(engine_args)
70
71 total_startup_time = time.perf_counter() - start_time
72
vllm/benchmarks/throughput.py
+2 -3
@@ -8,7 +8,6 @@ import os
8 import random
9 import time
10 import warnings
11 -from dataclasses import fields
11 from typing import Any
12
13 import torch
@@ -53,7 +52,7 @@ def run_vllm(
52 ) -> tuple[float, list[RequestOutput] | None]:
53 from vllm import LLM, SamplingParams
54
56 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
55 + llm = LLM.from_engine_args(engine_args)
56 assert all(
57 llm.llm_engine.model_config.max_model_len
58 >= (request.prompt_len + request.expected_output_len)
@@ -141,7 +140,7 @@ def run_vllm_chat(
140 """
141 from vllm import LLM, SamplingParams
142
144 - llm = LLM(**{f.name: getattr(engine_args, f.name) for f in fields(engine_args)})
143 + llm = LLM.from_engine_args(engine_args)
144
145 assert all(
146 llm.llm_engine.model_config.max_model_len
vllm/config/compilation.py
+15 -20
@@ -116,29 +116,29 @@ class PassConfig:
116 """
117
118 # New flags
119 - fuse_norm_quant: bool | None = Field(default=None)
119 + fuse_norm_quant: bool = None # type: ignore[assignment]
120 """Fuse the custom RMSNorm + quant ops."""
121 - fuse_act_quant: bool | None = Field(default=None)
121 + fuse_act_quant: bool = None # type: ignore[assignment]
122 """Fuse the custom SiluMul + quant ops."""
123 - fuse_attn_quant: bool | None = Field(default=None)
123 + fuse_attn_quant: bool = None # type: ignore[assignment]
124 """Fuse the custom attention + quant ops."""
125 eliminate_noops: bool = Field(default=True)
126 """Eliminate no-op ops."""
127 - enable_sp: bool | None = Field(default=None)
127 + enable_sp: bool = None # type: ignore[assignment]
128 """Enable sequence parallelism. Requires TP>1. Automatically disabled
129 if the model's hidden_size is too small for SP to be beneficial
130 (threshold is device-capability dependent)."""
131 - fuse_gemm_comms: bool | None = Field(default=None)
131 + fuse_gemm_comms: bool = None # type: ignore[assignment]
132 """Enable async TP."""
133 - fuse_allreduce_rms: bool | None = Field(default=None)
133 + fuse_allreduce_rms: bool = None # type: ignore[assignment]
134 """Enable flashinfer allreduce fusion."""
135 enable_qk_norm_rope_fusion: bool = False
136 """Enable fused Q/K RMSNorm + RoPE pass."""
137
138 # ROCm/AITER specific fusions
139 - fuse_act_padding: bool | None = Field(default=None)
139 + fuse_act_padding: bool = None # type: ignore[assignment]
140 """Fuse the custom RMSNorm + padding ops."""
141 - fuse_rope_kvcache: bool | None = Field(default=None)
141 + fuse_rope_kvcache: bool = None # type: ignore[assignment]
142 """Fuse the QK rope + KV cache ops."""
143
144 rope_kvcache_fusion_max_token_num: int = 256
@@ -405,7 +405,7 @@ class CompilationConfig:
405 """
406
407 # Top-level Compilation control
408 - mode: CompilationMode = Field(default=None) # type: ignore[assignment]
408 + mode: CompilationMode = None # type: ignore[assignment]
409 """The compilation approach used for torch.compile-based compilation of the
410 model.
411
@@ -545,7 +545,7 @@ class CompilationConfig:
545 constructor, e.g. `CompilationConfig(inductor_passes={"a": func})`."""
546
547 # CudaGraph compilation
548 - cudagraph_mode: CUDAGraphMode = Field(default=None) # type: ignore[assignment]
548 + cudagraph_mode: CUDAGraphMode = None # type: ignore[assignment]
549 """
550 The mode of the cudagraph:
551
@@ -586,7 +586,7 @@ class CompilationConfig:
586 It means the first several runs will be treated as warmup runs.
587 Only after that, the execution will be recorded, and the recorded
588 cudagraph will be used for subsequent runs."""
589 - cudagraph_capture_sizes: list[int] | None = None
589 + cudagraph_capture_sizes: list[int] = None # type: ignore[assignment]
590 """Sizes to capture cudagraph.
591 - None (default): capture sizes are inferred from vllm config.
592 - list[int]: capture sizes are specified as given."""
@@ -607,7 +607,7 @@ class CompilationConfig:
607 When `enable_lora` is False, this option has no effect.
608 """
609
610 - use_inductor_graph_partition: bool = Field(default=None) # type: ignore[assignment]
610 + use_inductor_graph_partition: bool = None # type: ignore[assignment]
611 """Use inductor graph partition to split the graph at cudagraph_unsafe ops.
612 This partition happens at inductor codegen time after all passes and fusions
613 are finished. It generates a single `call` function which wraps
@@ -630,7 +630,7 @@ class CompilationConfig:
630 pass_config: PassConfig = field(default_factory=PassConfig)
631 """Custom inductor passes, see PassConfig for more details"""
632
633 - max_cudagraph_capture_size: int | None = field(default=None)
633 + max_cudagraph_capture_size: int = None # type: ignore[assignment]
634 """The maximum cudagraph capture size.
635
636 If cudagraph_capture_sizes is specified, this will be set to the largest
@@ -750,7 +750,7 @@ class CompilationConfig:
750 return hash_factors(factors)
751
752 def __repr__(self) -> str:
753 - exclude = {
753 + exclude: dict[str, bool | dict[str, bool]] = {
754 "static_forward_context": True,
755 "enabled_custom_ops": True,
756 "disabled_custom_ops": True,
@@ -770,9 +770,7 @@ class CompilationConfig:
770 exclude["pass_config"] = pass_config_exclude
771
772 config = TypeAdapter(CompilationConfig).dump_python(
773 - self,
774 - exclude=exclude, # type: ignore[arg-type]
775 - exclude_unset=True,
773 + self, exclude=exclude, exclude_unset=True
774 )
775
776 return str(config)
@@ -1023,7 +1021,6 @@ class CompilationConfig:
1021 "Unrecognized size type in compile_sizes, "
1022 f"expect 'cudagraph_capture_sizes', got {x}"
1023 )
1026 - assert self.cudagraph_capture_sizes is not None
1024 computed_compile_sizes.extend(self.cudagraph_capture_sizes)
1025 else:
1026 assert isinstance(x, int)
@@ -1031,7 +1028,6 @@ class CompilationConfig:
1028 self.compile_sizes = computed_compile_sizes # type: ignore
1029
1030 # make sure the sizes are in ascending order
1034 - assert self.cudagraph_capture_sizes is not None
1031 self.cudagraph_capture_sizes.sort()
1032 if self.cudagraph_capture_sizes:
1033 assert self.cudagraph_capture_sizes[-1] == self.max_cudagraph_capture_size
@@ -1123,7 +1119,6 @@ class CompilationConfig:
1119
1120 def set_splitting_ops_for_attn_fusion(self):
1121 assert self.pass_config.fuse_attn_quant
1126 - assert self.cudagraph_mode is not None
1122 if self.splitting_ops is None:
1123 self.splitting_ops = []
1124 if self.cudagraph_mode.has_piecewise_cudagraphs():
vllm/config/device.py
+2 -2
@@ -13,8 +13,8 @@ from vllm.utils.hashing import safe_hash
13 Device = Literal["auto", "cuda", "cpu", "tpu", "xpu"]
14
15
16 -@config(config=ConfigDict(arbitrary_types_allowed=True)) # type: ignore[arg-type,misc]
17 -class DeviceConfig: # type: ignore[misc]
16 +@config(config=ConfigDict(arbitrary_types_allowed=True))
17 +class DeviceConfig:
18 """Configuration for the device to use for vLLM execution."""
19
20 device: SkipValidation[Device | torch.device | None] = "auto"
vllm/config/kernel.py
+2 -2
@@ -4,7 +4,7 @@
4 from collections.abc import Callable
5 from typing import Any, Literal
6
7 -from pydantic import Field, field_validator
7 +from pydantic import field_validator
8
9 from vllm.config.utils import config
10 from vllm.utils.hashing import safe_hash
@@ -26,7 +26,7 @@ MoEBackend = Literal[
26 class KernelConfig:
27 """Configuration for kernel selection and warmup behavior."""
28
29 - enable_flashinfer_autotune: bool | None = Field(default=None)
29 + enable_flashinfer_autotune: bool = None # type: ignore[assignment]
30 """If True, run FlashInfer autotuning during kernel warmup."""
31
32 moe_backend: MoEBackend = "auto"
vllm/config/kv_events.py
+1 -3
@@ -4,8 +4,6 @@
4
5 from typing import Literal
6
7 -from pydantic import Field
8 -
7 from vllm.config.utils import config
8
9
@@ -18,7 +16,7 @@ class KVEventsConfig:
16 Events can be published externally by zmq using the event publisher config.
17 """
18
21 - publisher: Literal["null", "zmq"] | None = Field(default=None)
19 + publisher: Literal["null", "zmq"] = None # type: ignore[assignment]
20 """The publisher to use for publishing kv events. Can be "null", "zmq".
21 """
22
vllm/config/lora.py
+2 -2
@@ -25,8 +25,8 @@ MaxLoRARanks = Literal[1, 8, 16, 32, 64, 128, 256, 320, 512]
25 LoRAExtraVocabSize = Literal[256, 512]
26
27
28 -@config(config=ConfigDict(arbitrary_types_allowed=True)) # type: ignore[arg-type,misc]
29 -class LoRAConfig: # type: ignore[misc]
28 +@config(config=ConfigDict(arbitrary_types_allowed=True))
29 +class LoRAConfig:
30 """Configuration for LoRA."""
31
32 max_lora_rank: MaxLoRARanks = 16
vllm/config/model.py
+6 -6
@@ -102,8 +102,8 @@ AttnTypeStr = Literal[
102 ]
103
104
105 -@config(config=ConfigDict(arbitrary_types_allowed=True)) # type: ignore[arg-type,misc]
106 -class ModelConfig: # type: ignore[misc]
105 +@config(config=ConfigDict(arbitrary_types_allowed=True))
106 +class ModelConfig:
107 """Configuration for the model."""
108
109 model: str = "Qwen/Qwen3-0.6B"
@@ -121,7 +121,7 @@ class ModelConfig: # type: ignore[misc]
121 """Convert the model using adapters defined in
122 [vllm.model_executor.models.adapters][]. The most common use case is to
123 adapt a text generation model to be used for pooling tasks."""
124 - tokenizer: str = Field(default=None) # type: ignore[assignment]
124 + tokenizer: str = None # type: ignore[assignment]
125 """Name or path of the Hugging Face tokenizer to use. If unspecified, model
126 name or path will be used."""
127 tokenizer_mode: TokenizerMode | str = "auto"
@@ -583,7 +583,7 @@ class ModelConfig: # type: ignore[misc]
583 self.dtype,
584 is_pooling_model=self.runner_type == "pooling",
585 revision=self.revision,
586 - config_format=self.config_format, # type: ignore[arg-type]
586 + config_format=self.config_format,
587 )
588
589 self.original_max_model_len = self.max_model_len
@@ -733,7 +733,7 @@ class ModelConfig: # type: ignore[misc]
733
734 @property
735 def architectures(self) -> list[str]:
736 - return self.model_arch_config.architectures # type: ignore[return-value]
736 + return self.model_arch_config.architectures
737
738 @property
739 def architecture(self) -> str:
@@ -1944,7 +1944,7 @@ def _get_and_verify_dtype(
1944 *,
1945 is_pooling_model: bool,
1946 revision: str | None = None,
1947 - config_format: ConfigFormat = "hf",
1947 + config_format: str | ConfigFormat = "hf",
1948 ) -> torch.dtype:
1949 config_dtype = ModelArchConfigConvertorBase.get_torch_dtype(
1950 config, model_id, revision=revision, config_format=config_format
vllm/config/model_arch.py
+1 -1
@@ -16,7 +16,7 @@ class ModelArchitectureConfig:
16 Configuration for model architecture that required by vLLM runtime
17 """
18
19 - architectures: list[str] | None
19 + architectures: list[str]
20 """List of model architecture class names (e.g., ['LlamaForCausalLM']).
21 It can be None upon calling `vllm_config.with_hf_config(config.text_config)`"""
22
vllm/config/parallel.py
+1 -1
@@ -194,7 +194,7 @@ class ParallelConfig:
194 threshold, microbatching will be used. Otherwise, the request will be
195 processed in a single batch."""
196
197 - disable_nccl_for_dp_synchronization: bool | None = Field(default=None)
197 + disable_nccl_for_dp_synchronization: bool | None = None
198 """Forces the dp synchronization logic in vllm/v1/worker/dp_utils.py
199 to use Gloo instead of NCCL for its all reduce.
200
vllm/config/scheduler.py
+3 -3
@@ -52,7 +52,7 @@ class SchedulerConfig:
52 In real usage, this should be set in `EngineArgs.create_engine_config`.
53 """
54
55 - max_num_scheduled_tokens: int | None = Field(default=None)
55 + max_num_scheduled_tokens: int | None = None
56 """Maximum number of tokens that the scheduler may issue in a single iteration.
57
58 This is usually equal to max_num_batched_tokens, but can be smaller in cases
@@ -122,7 +122,7 @@ class SchedulerConfig:
122
123 # scheduler class or path. "vllm.v1.core.sched.scheduler.Scheduler"
124 # (default) or "mod.custom_class".
125 - scheduler_cls: str | type[object] | None = Field(default=None)
125 + scheduler_cls: str | type[object] | None = None
126 """The scheduler class to use. "vllm.v1.core.sched.scheduler.Scheduler" is
127 the default scheduler. Can be a class directly or the path to a class of
128 form "mod.custom_class"."""
@@ -141,7 +141,7 @@ class SchedulerConfig:
141 checking the first chunk. Prevents over-admission and KV cache thrashing
142 with chunked prefill."""
143
144 - async_scheduling: bool | None = Field(default=None)
144 + async_scheduling: bool | None = None
145 """If set to False, disable async scheduling. Async scheduling helps to
146 avoid gaps in GPU utilization, leading to better latency and throughput.
147 """
vllm/config/utils.py
+14 -4
@@ -11,13 +11,13 @@ import os
11 import pathlib
12 import textwrap
13 from collections.abc import Callable, Mapping, Sequence, Set
14 -from dataclasses import MISSING, dataclass, field, fields, is_dataclass
14 +from dataclasses import MISSING, field, fields, is_dataclass
15 from itertools import pairwise
16 -from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast
16 +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast, overload
17
18 import torch
19 from pydantic import ConfigDict
20 -from pydantic.dataclasses import dataclass as pydantic_dataclass
20 +from pydantic.dataclasses import dataclass
21 from pydantic.fields import Field as PydanticField
22 from pydantic.fields import FieldInfo
23 from typing_extensions import dataclass_transform, runtime_checkable
@@ -36,6 +36,16 @@ ConfigType = type[DataclassInstance]
36 ConfigT = TypeVar("ConfigT", bound=DataclassInstance)
37
38
39 +@overload
40 +def config(cls: type[ConfigT]) -> type[ConfigT]: ...
41 +
42 +
43 +@overload
44 +def config(
45 + *, config: ConfigDict | None = None, **kwargs: Any
46 +) -> Callable[[type[ConfigT]], type[ConfigT]]: ...
47 +
48 +
49 @dataclass_transform(field_specifiers=(PydanticField,))
50 def config(
51 cls: type[ConfigT] | None = None,
@@ -59,7 +69,7 @@ def config(
69 merged_config.update(config)
70
71 def decorator(cls: type[ConfigT]) -> type[ConfigT]:
62 - return pydantic_dataclass(cls, config=merged_config, **kwargs) # type: ignore[return-value]
72 + return dataclass(cls, config=merged_config, **kwargs) # type: ignore[return-value]
73
74 # Called with arguments: @config(config=...)
75 if cls is None:
vllm/config/vllm.py
+9 -14
@@ -246,15 +246,15 @@ OPTIMIZATION_LEVEL_TO_CONFIG = {
246 }
247
248
249 -@config(config=ConfigDict(arbitrary_types_allowed=True)) # type: ignore[arg-type,misc]
250 -class VllmConfig: # type: ignore[misc]
249 +@config(config=ConfigDict(arbitrary_types_allowed=True))
250 +class VllmConfig:
251 """Dataclass which contains all vllm-related configuration. This
252 simplifies passing around the distinct configurations in the codebase.
253 """
254
255 # TODO: use default_factory once default constructing ModelConfig doesn't
256 # try to download a model
257 - model_config: ModelConfig = Field(default=None) # type: ignore[assignment]
257 + model_config: ModelConfig = None # type: ignore[assignment]
258 """Model configuration."""
259 cache_config: CacheConfig = Field(default_factory=CacheConfig)
260 """Cache configuration."""
@@ -912,7 +912,8 @@ class VllmConfig: # type: ignore[misc]
912
913 tp_size = self.parallel_config.tensor_parallel_size
914 hidden_size = self.model_config.get_hidden_size()
915 - element_size = self.model_config.dtype.itemsize # type: ignore[union-attr]
915 + assert isinstance(self.model_config.dtype, torch.dtype)
916 + element_size = self.model_config.dtype.itemsize
917 pass_config.sp_min_token_num = get_sequence_parallelism_threshold(
918 hidden_size, tp_size, element_size
919 )
@@ -1246,14 +1247,6 @@ class VllmConfig: # type: ignore[misc]
1247 )
1248 self.compilation_config.debug_dump_path = env_path
1249
1249 - def has_blocked_weights(): # type: ignore[no-redef]
1250 - if self.quant_config is not None:
1251 - if hasattr(self.quant_config, "weight_block_size"):
1252 - return self.quant_config.weight_block_size is not None
1253 - elif hasattr(self.quant_config, "has_blocked_weights"):
1254 - return self.quant_config.has_blocked_weights()
1255 - return False
1256 -
1250 # Enable quant_fp8 CUDA ops (TODO disable in follow up)
1251 # On H100 the CUDA kernel is faster than
1252 # native implementation
@@ -1502,9 +1495,10 @@ class VllmConfig: # type: ignore[misc]
1495 tp_size = self.parallel_config.tensor_parallel_size
1496 max_size = compilation_config.pass_config.flashinfer_max_size(tp_size)
1497 if max_size is not None:
1498 + assert isinstance(self.model_config.dtype, torch.dtype)
1499 max_token_num = max_size // (
1500 self.model_config.get_hidden_size()
1507 - * self.model_config.dtype.itemsize # type: ignore[union-attr]
1501 + * self.model_config.dtype.itemsize
1502 )
1503 if compile_range_end is not None and max_token_num < compile_range_end:
1504 computed_compile_ranges_endpoints.append(max_token_num)
@@ -1527,7 +1521,8 @@ class VllmConfig: # type: ignore[misc]
1521
1522 tp_size = self.parallel_config.tensor_parallel_size
1523 hidden_size = self.model_config.get_hidden_size()
1530 - element_size = self.model_config.dtype.itemsize # type: ignore[union-attr]
1524 + assert isinstance(self.model_config.dtype, torch.dtype)
1525 + element_size = self.model_config.dtype.itemsize
1526 pass_config.sp_min_token_num = get_sequence_parallelism_threshold(
1527 hidden_size, tp_size, element_size
1528 )
vllm/engine/arg_utils.py
+1 -1
@@ -1935,7 +1935,7 @@ class EngineArgs:
1935 )
1936
1937 offload_config = OffloadConfig(
1938 - offload_backend=self.offload_backend, # type: ignore[arg-type]
1938 + offload_backend=self.offload_backend,
1939 uva=UVAOffloadConfig(
1940 cpu_offload_gb=self.cpu_offload_gb,
1941 cpu_offload_params=self.cpu_offload_params,
vllm/entrypoints/llm.py
+5
@@ -409,6 +409,11 @@ class LLM:
409 # Cache for __repr__ to avoid repeated collective_rpc calls
410 self._cached_repr: str | None = None
411
412 + @classmethod
413 + def from_engine_args(cls, engine_args: EngineArgs) -> "LLM":
414 + """Create an LLM instance from EngineArgs."""
415 + return cls(**vars(engine_args))
416 +
417 def get_tokenizer(self) -> TokenizerLike:
418 return self.llm_engine.get_tokenizer()
419
vllm/transformers_utils/model_arch_config_convertor.py
+5 -2
@@ -28,7 +28,10 @@ class ModelArchConfigConvertorBase:
28 self.hf_text_config = hf_text_config
29
30 def get_architectures(self) -> list[str]:
31 - return getattr(self.hf_config, "architectures", [])
31 + # Sometimes we get here from `vllm_config.with_hf_config(text_config)` where
32 + # `text_config` is a sub-config from a multi-modal model. If this is the case,
33 + # the sub-config will not have `architectures` and it will explicitly be `None`
34 + return getattr(self.hf_config, "architectures", None) or []
35
36 def get_num_hidden_layers(self) -> int:
37 return getattr(self.hf_text_config, "num_hidden_layers", 0)
@@ -128,7 +131,7 @@ class ModelArchConfigConvertorBase:
131 hf_config: PretrainedConfig,
132 model_id: str,
133 revision: str | None,
131 - config_format: ConfigFormat,
134 + config_format: str | ConfigFormat,
135 ):
136 # NOTE: getattr(config, "dtype", torch.float32) is not correct
137 # because config.dtype can be None.