main
py 3,662 lines 161 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import copy
5 import getpass
6 import json
7 import os
8 import tempfile
9 import threading
10 import time
11 from collections.abc import Iterable
12 from contextlib import contextmanager
13 from dataclasses import is_dataclass
14 from datetime import datetime
15 from enum import IntEnum
16 from functools import lru_cache
17 from pathlib import Path
18 from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_args
19
20 import torch
21 from pydantic import ConfigDict, Field, model_validator
22
23 import vllm.envs as envs
24 from vllm.logger import enable_trace_function_call, init_logger
25 from vllm.transformers_utils.runai_utils import is_runai_obj_uri
26 from vllm.triton_utils import HAS_TRITON
27 from vllm.utils import random_uuid
28 from vllm.utils.hashing import safe_hash
29
30 from .attention import AttentionConfig, HiSparseConfig
31 from .aux_output import AuxOutputConfig
32 from .cache import CacheConfig
33 from .compilation import CompilationConfig, CompilationMode, CUDAGraphMode
34 from .device import DeviceConfig
35 from .diffusion import DiffusionConfig
36 from .ec_manager_config import EncoderCacheManagerConfig
37 from .ec_transfer import ECTransferConfig
38 from .engram import EngramConfig, model_has_engram_layers
39 from .kernel import KernelConfig
40 from .kv_events import KVEventsConfig
41 from .kv_transfer import KVTransferConfig
42 from .load import LoadConfig
43 from .logging import LoggingConfig
44 from .lora import LoRAConfig
45 from .mamba import MambaBackendEnum, MambaConfig
46 from .model import ModelConfig
47 from .observability import ObservabilityConfig
48 from .offload import OffloadConfig
49 from .parallel import ParallelConfig
50 from .profiler import ProfilerConfig
51 from .reasoning import ReasoningConfig
52 from .scheduler import SchedulerConfig
53 from .speculative import EagleModelTypes, NgramGPUTypes, SpeculativeConfig
54 from .structured_outputs import StructuredOutputsConfig
55 from .utils import SupportsHash, config, get_field, replace
56 from .watermarking import WatermarkConfig
57 from .weight_transfer import WeightTransferConfig
58
59 if TYPE_CHECKING:
60 from transformers import PreTrainedConfig
61
62 from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
63 from vllm.v1.kv_cache_interface import KVCacheConfig
64 else:
65 PreTrainedConfig = Any
66
67 QuantizationConfig = Any
68
69 KVCacheConfig = Any
70
71 logger = init_logger(__name__)
72
73 # TODO(rocm): These models are either unsupported by MRV2 or slower with
74 # MRV2 on AMD GPUs.
75 ROCM_DEFAULT_MRV1_ARCHITECTURES = frozenset(
76 {"DeepseekV32ForCausalLM", "DeepseekV4ForCausalLM", "GlmMoeDsaForCausalLM"}
77 )
78
79 DEFAULT_BREAKABLE_CUDAGRAPH_ARCHITECTURES = frozenset(
80 {
81 "DeepseekV32MTPModel",
82 "DeepseekV32ForCausalLM",
83 "DeepseekV4ForCausalLM",
84 "DeepseekV4ForConditionalGeneration",
85 "DeepSeekV4MTPModel",
86 "DeepseekV41ForCausalLM",
87 "Dots3NoteForCausalLM",
88 "Dots3NoteMTPModel",
89 "Glm5NextForCausalLM",
90 "Glm5NextForConditionalGeneration",
91 "Glm5NextMTPModel",
92 "GlmMoeDsaForCausalLM",
93 "HYV4ForCausalLM",
94 "HYV4MTPModel",
95 "InklingForCausalLM",
96 "InklingForConditionalGeneration",
97 "KimiK3ForConditionalGeneration",
98 "KimiK3MTPModel",
99 "KimiLinearForCausalLM",
100 "MiniMaxM3SparseForCausalLM",
101 "MiniMaxM3SparseForConditionalGeneration",
102 "Qwen4ExpForCausalLM",
103 "Qwen4ExpForConditionalGeneration",
104 "Qwen4ExpMTP",
105 }
106 )
107
108
109 @lru_cache
110 def default_breakable_cudagraph_architectures() -> frozenset[str]:
111 """Architectures defaulting to breakable CUDA graphs on this platform."""
112 from vllm.platforms import current_platform
113
114 if current_platform.is_rocm():
115 # Breakable CUDA graphs currently regress performance on ROCm for
116 # models that can use torch.compile piecewise graphs instead. Do not
117 # opt those in by default. Users can still force them with
118 # VLLM_USE_BREAKABLE_CUDAGRAPH=1.
119 #
120 # DeepseekV41ForCausalLM cannot torch.compile, and the ROCm sparse
121 # SWA backend only reports AttentionCGSupport.UNIFORM_BATCH. Default
122 # FULL_AND_PIECEWISE then dies at capture unless breakable CUDA
123 # graphs are on. Enable this architecture so the published AMD
124 # recipe can start.
125 return frozenset({"DeepseekV41ForCausalLM"})
126 return DEFAULT_BREAKABLE_CUDAGRAPH_ARCHITECTURES
127
128
129 class OptimizationLevel(IntEnum):
130 """Optimization level enum."""
131
132 O0 = 0
133 """O0 : No optimization. no compilation, no cudagraphs, no other
134 optimization, just starting up immediately"""
135 O1 = 1
136 """O1: Quick optimizations. Dynamo+Inductor compilation and Piecewise
137 cudagraphs"""
138 O2 = 2
139 """O2: Full optimizations. -O1 as well as Full and Piecewise cudagraphs."""
140 O3 = 3
141 """O3: Currently the same as -O2s."""
142
143
144 PerformanceMode = Literal["balanced", "interactivity", "throughput"]
145
146 IS_QUANTIZED = False
147 IS_DENSE = False
148 # The optimizations that depend on these properties currently set to False
149 # in all cases.
150 # if model_config is not None:
151 # IS_QUANTIZED = lambda c: c.model_config.is_quantized()
152 # IS_DENSE = lambda c: not c.model_config.is_model_moe()
153 # See https://github.com/vllm-project/vllm/issues/25689.
154
155
156 def enable_norm_fusion(cfg: "VllmConfig") -> bool:
157 """Enable if either RMS norm or quant FP8 custom op is active;
158 otherwise Inductor handles fusion."""
159 return (
160 cfg.compilation_config.is_custom_op_enabled("rms_norm")
161 or cfg.compilation_config.is_custom_op_enabled("quant_fp8")
162 or cfg.kernel_config.ir_op_priority.rms_norm[0] != "native"
163 )
164
165
166 def enable_act_fusion(cfg: "VllmConfig") -> bool:
167 """Enable if either SiLU+Mul or quant FP8 custom op is active;
168 otherwise Inductor handles fusion.
169 Also enable for FP4 models as FP4 quant is always custom so Inductor cannot fuse it.
170 """
171 return (
172 cfg.compilation_config.is_custom_op_enabled("silu_and_mul")
173 or cfg.compilation_config.is_custom_op_enabled("quant_fp8")
174 or (cfg.model_config is not None and cfg.model_config.is_nvfp4_quantized())
175 )
176
177
178 def enable_allreduce_rms_fusion(cfg: "VllmConfig") -> bool:
179 """Enable if TP > 1 and Hopper/Blackwell and flashinfer installed."""
180 from vllm.platforms import current_platform
181 from vllm.utils.flashinfer import has_flashinfer
182
183 # The fused all-reduce + RMSNorm path is not batch-invariant
184 if envs.VLLM_BATCH_INVARIANT:
185 return False
186
187 if current_platform.is_rocm():
188 from vllm._aiter_ops import rocm_aiter_ops
189
190 return (
191 rocm_aiter_ops.is_enabled() and cfg.parallel_config.tensor_parallel_size > 1
192 )
193
194 return (
195 cfg.parallel_config.tensor_parallel_size > 1
196 and current_platform.is_cuda()
197 and has_flashinfer()
198 and (
199 current_platform.is_device_capability_family(100)
200 or current_platform.is_device_capability(90)
201 )
202 )
203
204
205 def enable_rope_kvcache_fusion(cfg: "VllmConfig") -> bool:
206 """Enable if rotary embedding custom op is active and
207 use_inductor_graph_partition is enabled.
208 """
209 from vllm._aiter_ops import rocm_aiter_ops
210
211 return (
212 rocm_aiter_ops.is_enabled()
213 and cfg.compilation_config.is_custom_op_enabled("rotary_embedding")
214 and (
215 cfg.compilation_config.use_inductor_graph_partition
216 or not cfg.compilation_config.splitting_ops_contain_kv_cache_update()
217 )
218 )
219
220
221 def enable_rope_kvcache_mla_fusion(cfg: "VllmConfig") -> bool:
222 """Enable if use_inductor_graph_partition is enabled."""
223 return (
224 cfg.compilation_config.use_inductor_graph_partition
225 or not cfg.compilation_config.splitting_ops_contain_kv_cache_update()
226 )
227
228
229 def enable_norm_pad_fusion(cfg: "VllmConfig") -> bool:
230 """Enable if using AITER RMSNorm and hidden size is 2880 i.e. gpt-oss."""
231 return (
232 cfg.kernel_config.ir_op_priority.fused_add_rms_norm[0] == "aiter"
233 and cfg.model_config is not None
234 and cfg.model_config.get_hidden_size() == 2880
235 )
236
237
238 def enable_mla_dual_rms_norm_fusion(cfg: "VllmConfig") -> bool:
239 """Enable MLA dual RMS norm fusion on ROCm with AITER."""
240 from vllm._aiter_ops import rocm_aiter_ops
241
242 return rocm_aiter_ops.is_enabled()
243
244
245 def enable_qk_norm_rope_kvcache(cfg: "VllmConfig") -> bool:
246 """Enable fused QK-norm + RoPE/MRoPE + KV cache update with AITER."""
247 from vllm._aiter_ops import rocm_aiter_ops
248
249 if not rocm_aiter_ops.is_enabled():
250 return False
251 return cfg.compilation_config.is_custom_op_enabled("rotary_embedding")
252
253
254 OPTIMIZATION_LEVEL_00 = {
255 "compilation_config": {
256 "pass_config": {
257 "fuse_norm_quant": False,
258 "fuse_act_quant": False,
259 "fuse_allreduce_rms": False,
260 "fuse_attn_quant": False,
261 "enable_sp": False,
262 "fuse_gemm_comms": False,
263 "fuse_act_padding": False,
264 "fuse_mla_dual_rms_norm": False,
265 "fuse_rope_kvcache": False,
266 "fuse_qk_norm_rope_kvcache": False,
267 "enable_qk_norm_rope_fusion": False,
268 "fuse_rope_kvcache_cat_mla": False,
269 },
270 "cudagraph_mode": CUDAGraphMode.NONE,
271 "use_inductor_graph_partition": False,
272 },
273 "kernel_config": {
274 "enable_flashinfer_autotune": False,
275 },
276 }
277 OPTIMIZATION_LEVEL_01 = {
278 "compilation_config": {
279 "pass_config": {
280 "fuse_norm_quant": enable_norm_fusion,
281 "fuse_act_quant": enable_act_fusion,
282 "fuse_allreduce_rms": False,
283 "fuse_attn_quant": False,
284 "enable_sp": False,
285 "fuse_gemm_comms": False,
286 "fuse_act_padding": enable_norm_pad_fusion,
287 "fuse_mla_dual_rms_norm": enable_mla_dual_rms_norm_fusion,
288 "fuse_rope_kvcache": False,
289 "fuse_qk_norm_rope_kvcache": False,
290 "enable_qk_norm_rope_fusion": False,
291 "fuse_rope_kvcache_cat_mla": False,
292 },
293 "cudagraph_mode": CUDAGraphMode.PIECEWISE,
294 "use_inductor_graph_partition": False,
295 },
296 "kernel_config": {
297 "enable_flashinfer_autotune": True,
298 },
299 }
300 OPTIMIZATION_LEVEL_02 = {
301 "compilation_config": {
302 "pass_config": {
303 "fuse_norm_quant": enable_norm_fusion,
304 "fuse_act_quant": enable_act_fusion,
305 "fuse_allreduce_rms": enable_allreduce_rms_fusion,
306 "fuse_attn_quant": IS_QUANTIZED,
307 "enable_sp": IS_DENSE,
308 "fuse_gemm_comms": IS_DENSE,
309 "fuse_act_padding": enable_norm_pad_fusion,
310 "fuse_mla_dual_rms_norm": enable_mla_dual_rms_norm_fusion,
311 "fuse_rope_kvcache": enable_rope_kvcache_fusion,
312 "fuse_qk_norm_rope_kvcache": enable_qk_norm_rope_kvcache,
313 "enable_qk_norm_rope_fusion": False,
314 "fuse_rope_kvcache_cat_mla": enable_rope_kvcache_mla_fusion,
315 },
316 "cudagraph_mode": CUDAGraphMode.FULL_AND_PIECEWISE,
317 "use_inductor_graph_partition": False,
318 },
319 "kernel_config": {
320 "enable_flashinfer_autotune": True,
321 },
322 }
323 OPTIMIZATION_LEVEL_03 = {
324 "compilation_config": {
325 "pass_config": {
326 "fuse_norm_quant": enable_norm_fusion,
327 "fuse_act_quant": enable_act_fusion,
328 "fuse_allreduce_rms": enable_allreduce_rms_fusion,
329 "fuse_attn_quant": IS_QUANTIZED,
330 "enable_sp": IS_DENSE,
331 "fuse_gemm_comms": IS_DENSE,
332 "fuse_act_padding": enable_norm_pad_fusion,
333 "fuse_mla_dual_rms_norm": enable_mla_dual_rms_norm_fusion,
334 "fuse_rope_kvcache": enable_rope_kvcache_fusion,
335 "fuse_qk_norm_rope_kvcache": enable_qk_norm_rope_kvcache,
336 "enable_qk_norm_rope_fusion": False,
337 "fuse_rope_kvcache_cat_mla": enable_rope_kvcache_mla_fusion,
338 },
339 "cudagraph_mode": CUDAGraphMode.FULL_AND_PIECEWISE,
340 "use_inductor_graph_partition": False,
341 },
342 "kernel_config": {
343 "enable_flashinfer_autotune": True,
344 },
345 }
346
347 OPTIMIZATION_LEVEL_TO_CONFIG = {
348 OptimizationLevel.O0: OPTIMIZATION_LEVEL_00,
349 OptimizationLevel.O1: OPTIMIZATION_LEVEL_01,
350 OptimizationLevel.O2: OPTIMIZATION_LEVEL_02,
351 OptimizationLevel.O3: OPTIMIZATION_LEVEL_03,
352 }
353
354
355 @config(config=ConfigDict(arbitrary_types_allowed=True))
356 class VllmConfig:
357 """Dataclass which contains all vllm-related configuration. This
358 simplifies passing around the distinct configurations in the codebase.
359 """
360
361 # TODO: use default_factory once default constructing ModelConfig doesn't
362 # try to download a model
363 model_config: ModelConfig = None # type: ignore[assignment]
364 """Model configuration."""
365 cache_config: CacheConfig = Field(default_factory=CacheConfig)
366 """Cache configuration."""
367 parallel_config: ParallelConfig = Field(default_factory=ParallelConfig)
368 """Parallel configuration."""
369 scheduler_config: SchedulerConfig = Field(
370 default_factory=SchedulerConfig.default_factory,
371 )
372 """Scheduler configuration."""
373 device_config: DeviceConfig = Field(default_factory=DeviceConfig)
374 """Device configuration."""
375 load_config: LoadConfig = Field(default_factory=LoadConfig)
376 """Load configuration."""
377 offload_config: OffloadConfig = Field(default_factory=OffloadConfig)
378 """Model weight offloading configuration."""
379 attention_config: AttentionConfig = Field(default_factory=AttentionConfig)
380 """Attention configuration."""
381 aux_output_config: AuxOutputConfig = Field(default_factory=AuxOutputConfig)
382 """Execution auxiliary output configuration."""
383 engram_config: EngramConfig | None = None
384 """N-gram embedding storage and sharding settings."""
385 mamba_config: MambaConfig = Field(default_factory=MambaConfig)
386 """Mamba configuration."""
387 kernel_config: KernelConfig = Field(default_factory=KernelConfig)
388 """Kernel configuration."""
389 lora_config: LoRAConfig | None = None
390 """LoRA configuration."""
391 speculative_config: SpeculativeConfig | None = None
392 """Speculative decoding configuration."""
393 watermark_config: WatermarkConfig | None = None
394 """Text watermarking configuration."""
395 diffusion_config: DiffusionConfig | None = None
396 """Diffusion LLM (dLLM) configuration."""
397
398 structured_outputs_config: StructuredOutputsConfig = Field(
399 default_factory=StructuredOutputsConfig
400 )
401 """Structured outputs configuration."""
402 observability_config: ObservabilityConfig = Field(
403 default_factory=ObservabilityConfig
404 )
405 """Observability configuration."""
406 logging_config: LoggingConfig = Field(default_factory=LoggingConfig)
407 """Logging configuration."""
408 quant_config: QuantizationConfig | None = None
409 """Quantization configuration."""
410 compilation_config: CompilationConfig = Field(default_factory=CompilationConfig)
411 """`torch.compile` and cudagraph capture configuration for the model.
412
413 As a shorthand, one can append compilation arguments via
414 -cc.parameter=argument such as `-cc.mode=3` (same as `-cc='{"mode":3}'`).
415
416 You can specify the full compilation config like so:
417 `{"mode": 3, "cudagraph_capture_sizes": [1, 2, 4, 8]}`
418 """
419 profiler_config: ProfilerConfig = Field(default_factory=ProfilerConfig)
420 """Profiling configuration."""
421 kv_transfer_config: KVTransferConfig | None = None
422 """The configurations for distributed KV cache transfer."""
423 kv_events_config: KVEventsConfig | None = None
424 """The configurations for event publishing."""
425 ec_transfer_config: ECTransferConfig | None = None
426 """The configurations for distributed EC cache transfer."""
427 ec_manager_config: EncoderCacheManagerConfig = Field(
428 default_factory=EncoderCacheManagerConfig
429 )
430 """The configurations for custom encoder cache manager."""
431 reasoning_config: ReasoningConfig | None = None
432 """The configurations for reasoning model."""
433 # some opaque config, only used to provide additional information
434 # for the hash computation, mainly used for testing, debugging or out of
435 # tree config registration.
436 additional_config: dict | SupportsHash = Field(default_factory=dict)
437 """Additional config for specified platform. Different platforms may
438 support different configs. Make sure the configs are valid for the platform
439 you are using. Contents must be hashable."""
440 instance_id: str = ""
441 """The ID of the vLLM instance."""
442 optimization_level: OptimizationLevel = OptimizationLevel.O2
443 """The optimization level. These levels trade startup time cost for
444 performance, with -O0 having the best startup time and -O3 having the best
445 performance. -O2 is used by default. See OptimizationLevel for full
446 description."""
447
448 performance_mode: PerformanceMode = "balanced"
449 """Performance mode for runtime behavior, 'balanced' is the default.
450 'interactivity' favors low end-to-end per-request latency at small batch
451 sizes (fine-grained CUDA graphs, latency-oriented kernels).
452 'throughput' favors aggregate tokens/sec at high concurrency (larger CUDA
453 graphs, more aggressive batching, throughput-oriented kernels)."""
454
455 weight_transfer_config: WeightTransferConfig | None = None
456 """The configurations for weight transfer during RL training."""
457
458 shutdown_timeout: int = Field(default=0, ge=0)
459 """Shutdown grace period for in-flight requests. Shutdown will be delayed for
460 up to this amount of time to allow already-running requests to complete. Any
461 remaining requests are aborted once the timeout is reached.
462 """
463
464 def compute_hash(self, include_version: bool = True) -> str:
465 """WARNING: Whenever a new field is added to this config,
466 ensure that it is included in the factors list if
467 it affects the computation graph.
468
469 Provide a hash that uniquely identifies all the configs
470 that affect the structure of the computation
471 graph from input ids/embeddings to the final hidden states,
472 excluding anything before input ids/embeddings and after
473 the final hidden states.
474
475 Args:
476 include_version: Include the vLLM version in the hash.
477
478 """
479 factors: list[Any] = []
480
481 # summarize vllm config
482 vllm_factors: list[Any] = []
483 if include_version:
484 from vllm import __version__
485
486 vllm_factors.append(__version__)
487 if self.model_config:
488 vllm_factors.append(self.model_config.compute_hash())
489 if (
490 self.compilation_config
491 and getattr(self.compilation_config, "compile_mm_encoder", False)
492 and self.model_config.multimodal_config
493 ):
494 vllm_factors.append(self.model_config.multimodal_config.compute_hash())
495 else:
496 vllm_factors.append("None")
497 if self.cache_config:
498 vllm_factors.append(self.cache_config.compute_hash())
499 else:
500 vllm_factors.append("None")
501 if self.parallel_config:
502 vllm_factors.append(self.parallel_config.compute_hash())
503 else:
504 vllm_factors.append("None")
505 if self.scheduler_config:
506 vllm_factors.append(self.scheduler_config.compute_hash())
507 else:
508 vllm_factors.append("None")
509 if self.device_config:
510 vllm_factors.append(self.device_config.compute_hash())
511 else:
512 vllm_factors.append("None")
513 if self.load_config:
514 vllm_factors.append(self.load_config.compute_hash())
515 else:
516 vllm_factors.append("None")
517 if self.offload_config:
518 vllm_factors.append(self.offload_config.compute_hash())
519 else:
520 vllm_factors.append("None")
521 if self.attention_config:
522 vllm_factors.append(self.attention_config.compute_hash())
523 else:
524 vllm_factors.append("None")
525 vllm_factors.append(
526 self.engram_config.compute_hash()
527 if self.engram_config is not None
528 else "None"
529 )
530 if self.lora_config:
531 vllm_factors.append(self.lora_config.compute_hash())
532 else:
533 vllm_factors.append("None")
534 if self.speculative_config:
535 vllm_factors.append(self.speculative_config.compute_hash())
536 else:
537 vllm_factors.append("None")
538 if self.structured_outputs_config:
539 vllm_factors.append(self.structured_outputs_config.compute_hash())
540 if self.profiler_config:
541 vllm_factors.append(self.profiler_config.compute_hash())
542 else:
543 vllm_factors.append("None")
544 vllm_factors.append(self.observability_config.compute_hash())
545 if self.quant_config:
546 pass # should be captured by model_config.quantization
547 if self.compilation_config:
548 vllm_factors.append(self.compilation_config.compute_hash())
549 else:
550 vllm_factors.append("None")
551 if self.kernel_config:
552 vllm_factors.append(self.kernel_config.compute_hash())
553 else:
554 vllm_factors.append(None)
555 if self.kv_transfer_config:
556 vllm_factors.append(self.kv_transfer_config.compute_hash())
557 else:
558 vllm_factors.append("None")
559 if self.ec_transfer_config:
560 vllm_factors.append(self.ec_transfer_config.compute_hash())
561 else:
562 vllm_factors.append("None")
563 vllm_factors.append(self.aux_output_config.compute_hash())
564 if self.additional_config:
565 if isinstance(additional_config := self.additional_config, dict):
566 additional_config_hash = safe_hash(
567 json.dumps(additional_config, sort_keys=True).encode(),
568 usedforsecurity=False,
569 ).hexdigest()
570 else:
571 additional_config_hash = additional_config.compute_hash()
572 vllm_factors.append(additional_config_hash)
573 else:
574 vllm_factors.append("None")
575 factors.append(vllm_factors)
576
577 hash_str = safe_hash(str(factors).encode(), usedforsecurity=False).hexdigest()[
578 :10
579 ]
580 return hash_str
581
582 @property
583 def is_mm_encoder_only(self) -> bool:
584 mm_config = (
585 self.model_config.multimodal_config
586 if self.model_config is not None
587 else None
588 )
589 return bool(mm_config and mm_config.mm_encoder_only)
590
591 @property
592 def max_concurrent_batches(self) -> int:
593 # PP requires PP-size concurrent batches to fill the pipeline.
594 # Async scheduling requires 2 concurrent batches to overlap.
595 pp_size = self.parallel_config.pipeline_parallel_size
596 if self.scheduler_config.async_scheduling:
597 if self.use_v2_model_runner:
598 return pp_size + 1
599 # V1 Model Runner does not fully support async scheduling with PP.
600 if pp_size <= 1:
601 return 2
602 return pp_size
603
604 @property
605 def max_in_flight_tokens(self) -> int:
606 # Upper bound on tokens that are scheduled but not yet settled (freed):
607 # every concurrent batch may hold up to a full `max_num_batched_tokens`.
608 # Recycling-aware KV cache specs (sliding-window, chunked-local) reserve
609 # for this because out-of-window blocks are freed on the processed-token
610 # basis, so in-flight steps transiently keep their blocks.
611 return (
612 self.max_concurrent_batches * self.scheduler_config.max_num_batched_tokens
613 )
614
615 @property
616 def num_speculative_tokens(self) -> int:
617 if (
618 self.speculative_config is not None
619 and self.speculative_config.num_speculative_tokens is not None
620 ):
621 return self.speculative_config.num_speculative_tokens
622 if (
623 self.diffusion_config is not None
624 and self.diffusion_config.canvas_length is not None
625 ):
626 return self.diffusion_config.canvas_length
627 return 0
628
629 @property
630 def num_lookahead_tokens(self) -> int:
631 """KV slots to reserve past the tokens the target model is scheduled for.
632
633 The drafter writes KV for positions beyond the target model's query
634 range, so every component that reserves blocks must add this margin:
635 the scheduler through `allocate_slots`, and the worker warmup, which
636 builds its own `SchedulerOutput`s. Consumers must read this property
637 rather than re-deriving their own per-method lookahead, so the
638 scheduler and warmup cannot drift apart.
639 """
640 speculative_config = self.speculative_config
641 if speculative_config is None:
642 return 0
643 if speculative_config.use_dflash():
644 # DFlash requires an extra lookahead slot since it uses in-fill-style
645 # decoding instead of standard next-token sampling, so it has a query
646 # for the last sampled token plus queries for each draft token.
647 return self.num_speculative_tokens + 1
648 if speculative_config.use_eagle() or speculative_config.uses_draft_model():
649 # DSpark (covered by use_eagle) drafts a block of num_speculative_tokens
650 # query tokens in which the anchor itself is the first prediction
651 # position (no separate bonus query), so it needs exactly
652 # num_speculative_tokens lookahead slots.
653 return self.num_speculative_tokens
654 return 0
655
656 @property
657 def num_prefill_lookahead_tokens(self) -> int:
658 """Prefill tokens past the computed range that the drafter reads.
659
660 Mid-prefill the drafter consumes tokens the target model has not been
661 scheduled for yet, so every component that has to keep them available
662 must apply this margin: the scheduler, which never ends a chunk within
663 it and shifts encoder scheduling by it, and the KV cache manager, which
664 treats the trailing `this - 1` tokens as re-prefillable rather than
665 finalized. Consumers must read this property rather than re-deriving
666 their own per-method lookahead, so those components cannot drift apart.
667 """
668 speculative_config = self.speculative_config
669 if speculative_config is None or not speculative_config.use_eagle():
670 return 0
671 if speculative_config.use_multi_module_mtp():
672 # Each MTP module reads one token further ahead than the one before
673 # it, so the chain needs num_speculative_tokens of runway at a
674 # chunked-prefill boundary.
675 return self.num_speculative_tokens
676 # Eagle-family drafters read only the immediate next token.
677 return 1
678
679 @property
680 def uniform_decode_query_len(self) -> int:
681 """Query length of every request in a uniform decode batch.
682
683 A decode step submits one query for the newly sampled token plus one
684 for each draft token, so the widest uniform decode batch the scheduler
685 can build is `max_num_seqs * uniform_decode_query_len` tokens. Anything
686 that has to cover a decode batch reads this, so the sizing rule cannot
687 drift between the places that apply it.
688
689 This deliberately does not derive from the KV slots a drafter reserves
690 past the target's query range, which is a *reservation* contract rather
691 than a query-length one. The two do not differ by a constant: DFlash
692 reserves `num_speculative_tokens + 1` slots yet still verifies `1 +
693 num_speculative_tokens` queries, while EAGLE reserves
694 `num_speculative_tokens` and verifies the same `1 + n`. Deriving one
695 from the other would under-size EAGLE by a full request width, which is
696 the failure this property exists to prevent.
697 """
698 return 1 + self.num_speculative_tokens
699
700 @property
701 def use_v2_model_runner(self) -> bool:
702 if self.attention_config.hisparse_config is not None:
703 if envs.VLLM_USE_V2_MODEL_RUNNER is False:
704 raise ValueError(
705 "HiSparse requires Model Runner V2; remove "
706 "VLLM_USE_V2_MODEL_RUNNER=0."
707 )
708 return True
709
710 if getattr(self, "watermark_config", None) is not None:
711 if envs.VLLM_USE_V2_MODEL_RUNNER is False:
712 logger.info_once(
713 "Watermarking requires Model Runner V2 and overrides "
714 "VLLM_USE_V2_MODEL_RUNNER=0."
715 )
716 return True
717
718 use_v2_model_runner = envs.VLLM_USE_V2_MODEL_RUNNER
719 if use_v2_model_runner is not None:
720 return use_v2_model_runner
721
722 from vllm.platforms import current_platform
723
724 model_config = self.model_config
725 if model_config is not None and current_platform.is_rocm():
726 architectures = getattr(model_config, "architectures", ())
727 if any(arch in ROCM_DEFAULT_MRV1_ARCHITECTURES for arch in architectures):
728 # This default is a speed preference, not a claim that V1 can
729 # serve the config, so it yields where V1 cannot. It yields by
730 # falling through to the checks below, not by selecting V2.
731 v1_unsupported = self._get_v1_model_runner_unsupported_features()
732 if not v1_unsupported:
733 logger.warning_once(
734 "Defaulting to V1 model runner on ROCm for model "
735 "architectures: %s",
736 ", ".join(architectures),
737 )
738 return False
739 logger.warning_once(
740 "Skipping the ROCm V1 model runner default for %s: V1 does "
741 "not support %s.",
742 ", ".join(architectures),
743 ", ".join(v1_unsupported),
744 )
745
746 if not HAS_TRITON:
747 logger.warning_once(
748 "Model Runner V2 requires Triton; using the V1 model runner instead."
749 )
750 return False
751
752 unsupported = self._get_v2_model_runner_unsupported_features()
753 if unsupported:
754 logger.warning_once(
755 "Model Runner V2 does not yet support %s; using the V1 model "
756 "runner instead.",
757 ", ".join(unsupported),
758 )
759 return False
760
761 return True
762
763 def _is_dflash_candidate_draft(self) -> bool:
764 """Whether the DFlash draft has a candidate head, by the architecture the
765 speculator selects on (v1/worker/gpu/spec_decode/__init__.py)."""
766 spec = self.speculative_config
767 if spec is None or spec.method != "dflash":
768 return False
769 draft_config = getattr(spec, "draft_model_config", None)
770 if draft_config is None:
771 return False
772 return bool(
773 {"DFlash2DraftModel", "LiLiCorrDraftModel"}.intersection(
774 draft_config.architectures or []
775 )
776 )
777
778 def _dflash_needs_multi_kv_group(self) -> bool:
779 """Whether a DFlash draft mixes sliding-window and full attention."""
780 spec = self.speculative_config
781 if spec is None or spec.method != "dflash":
782 return False
783 draft_config = getattr(spec, "draft_model_config", None)
784 if draft_config is None:
785 return False
786 layer_types = getattr(draft_config.hf_config, "layer_types", None) or []
787 num_sliding = sum(lt == "sliding_attention" for lt in layer_types)
788 return 0 < num_sliding < len(layer_types)
789
790 def _uses_breakable_cudagraph_by_default(self) -> bool:
791 model_config = self.model_config
792 if model_config is None:
793 return False
794
795 architectures = set(model_config.architectures)
796 return bool(architectures & default_breakable_cudagraph_architectures())
797
798 def _uses_breakable_cudagraph_for_batch_invariance(self) -> bool:
799 """Avoid freezing runtime-M tile lookup in compiled forward (#54243).
800 Breakable graphs look up tuned bf16, unquantized qkv/o/gate_up/down tiles
801 at capture; lm_head runs outside compiled forward and does not benefit."""
802 from vllm.model_executor.determinism import batch_invariant_configs as bi
803 from vllm.platforms import current_platform
804
805 model = self.model_config
806 if (
807 not envs.VLLM_BATCH_INVARIANT
808 or model is None
809 or model.enforce_eager
810 or model.dtype != torch.bfloat16
811 or model.quantization is not None
812 or not current_platform.is_cuda()
813 # Sequence parallelism / async TP are torch.compile passes.
814 or self.compilation_config.pass_config.enable_sp
815 or self.compilation_config.pass_config.fuse_gemm_comms
816 ):
817 return False
818 family = bi._get_tuned_matmul_arch_family(
819 current_platform.get_device_capability()
820 )
821 if family is None or family not in bi._BATCH_INVARIANT_MATMUL_TUNED_CONFIGS:
822 return False
823 table = bi._BATCH_INVARIANT_MATMUL_TUNED_CONFIGS[family]
824 parallel = self.parallel_config
825 tp = parallel.tensor_parallel_size
826 hidden = model.get_hidden_size()
827 head = model.get_head_size()
828 heads = model.get_num_attention_heads(parallel)
829 kv_heads = model.get_num_kv_heads(parallel)
830 shapes = [((heads + 2 * kv_heads) * head, hidden), (hidden, heads * head)]
831 intermediate = getattr(model.hf_text_config, "intermediate_size", None)
832 # Per-layer sizes (e.g. Gemma3n) are not modeled.
833 if isinstance(intermediate, int):
834 shapes += [(2 * intermediate // tp, hidden), (hidden, intermediate // tp)]
835 return any(shape in table for shape in shapes)
836
837 def _maybe_enable_breakable_cudagraph(self) -> bool:
838 if (
839 "VLLM_USE_BREAKABLE_CUDAGRAPH" not in os.environ
840 and self._uses_breakable_cudagraph_by_default()
841 ):
842 os.environ["VLLM_USE_BREAKABLE_CUDAGRAPH"] = "1"
843 logger.info_once(
844 "Auto-enabling VLLM_USE_BREAKABLE_CUDAGRAPH=1. "
845 "Set VLLM_USE_BREAKABLE_CUDAGRAPH=0 to opt out."
846 )
847 elif (
848 "VLLM_USE_BREAKABLE_CUDAGRAPH" not in os.environ
849 and envs.VLLM_BATCH_INVARIANT
850 and self._uses_breakable_cudagraph_for_batch_invariance()
851 ):
852 os.environ["VLLM_USE_BREAKABLE_CUDAGRAPH"] = "1"
853 logger.info_once(
854 "VLLM_BATCH_INVARIANT=1: auto-enabling VLLM_USE_BREAKABLE_CUDAGRAPH=1. "
855 "Set VLLM_USE_BREAKABLE_CUDAGRAPH=0 to opt out."
856 )
857
858 from vllm.compilation.breakable_cudagraph import (
859 is_breakable_cudagraph_enabled,
860 )
861
862 enabled = is_breakable_cudagraph_enabled()
863 if enabled:
864 self.compilation_config.mode = CompilationMode.NONE
865 return enabled
866
867 @property
868 def needs_dp_coordinator(self) -> bool:
869 """Determine if the DPCoordinator process is needed.
870
871 The DPCoordinator is needed in two cases:
872 1. For MoE models with DP > 1: to handle wave coordination
873 (even in external LB mode, since wave coordination runs in the coordinator)
874 2. For non-MoE models in internal/hybrid LB mode: to collect and publish
875 queue stats for load balancing across DP ranks
876
877 Returns:
878 True if DPCoordinator process is needed, False otherwise.
879
880 """
881 # For non-MoE models, only need coordinator in internal/hybrid LB mode
882 # (for stats collection).
883 return self.parallel_config.data_parallel_size > 1 and (
884 self.model_config is None
885 or self.model_config.is_moe
886 or not self.parallel_config.data_parallel_external_lb
887 )
888
889 def enable_trace_function_call_for_thread(self) -> None:
890 """Set up function tracing for the current thread,
891 if enabled via the `VLLM_TRACE_FUNCTION` environment variable.
892 """
893 if envs.VLLM_TRACE_FUNCTION:
894 tmp_dir = tempfile.gettempdir()
895 # add username to tmp_dir to avoid permission issues
896 tmp_dir = os.path.join(tmp_dir, getpass.getuser())
897 filename = (
898 f"VLLM_TRACE_FUNCTION_for_process_{os.getpid()}"
899 f"_thread_{threading.get_ident()}_at_{datetime.now()}.log"
900 ).replace(" ", "_")
901 log_path = os.path.join(
902 tmp_dir,
903 "vllm",
904 f"vllm-instance-{self.instance_id}",
905 filename,
906 )
907 os.makedirs(os.path.dirname(log_path), exist_ok=True)
908 enable_trace_function_call(log_path)
909
910 @staticmethod
911 def _get_quantization_config(
912 model_config: ModelConfig, load_config: LoadConfig
913 ) -> QuantizationConfig | None:
914 """Get the quantization config."""
915 from vllm.platforms import current_platform
916
917 if model_config.quantization is not None:
918 from vllm.model_executor.model_loader.weight_utils import get_quant_config
919
920 quant_config = get_quant_config(model_config, load_config)
921 capability_tuple = current_platform.get_device_capability()
922
923 if capability_tuple is not None:
924 capability = capability_tuple.to_int()
925 if capability < quant_config.get_min_capability():
926 raise ValueError(
927 f"The quantization method {model_config.quantization} "
928 "is not supported for the current GPU. Minimum "
929 f"capability: {quant_config.get_min_capability()}. "
930 f"Current capability: {capability}."
931 )
932 supported_dtypes = quant_config.get_supported_act_dtypes()
933 if model_config.dtype not in supported_dtypes:
934 raise ValueError(
935 f"{model_config.dtype} is not supported for quantization "
936 f"method {model_config.quantization}. Supported dtypes: "
937 f"{supported_dtypes}"
938 )
939 quant_config.maybe_update_config(
940 model_config.model,
941 hf_config=model_config.hf_config,
942 revision=model_config.revision,
943 )
944 return quant_config
945 return None
946
947 @staticmethod
948 def get_quantization_config(
949 model_config: ModelConfig, load_config: LoadConfig
950 ) -> QuantizationConfig | None:
951 import copy
952
953 # For some reason, the _ version of this modifies the model_config
954 # object, so using deepcopy to avoid this problem.
955 return VllmConfig._get_quantization_config(
956 copy.deepcopy(model_config), load_config
957 )
958
959 def with_hf_config(
960 self,
961 hf_config: PreTrainedConfig,
962 architectures: list[str] | None = None,
963 ) -> "VllmConfig":
964 if architectures is not None:
965 hf_config = copy.deepcopy(hf_config)
966 hf_config.architectures = architectures
967 elif hf_config.architectures is None:
968 from transformers.models.auto.modeling_auto import (
969 MODEL_FOR_CAUSAL_LM_MAPPING_NAMES,
970 )
971
972 if hf_config.model_type in MODEL_FOR_CAUSAL_LM_MAPPING_NAMES:
973 hf_config = copy.deepcopy(hf_config)
974 hf_config.architectures = [
975 MODEL_FOR_CAUSAL_LM_MAPPING_NAMES[hf_config.model_type]
976 ]
977
978 model_config = copy.deepcopy(self.model_config)
979
980 # In Transformers v5, tie_word_embeddings belongs to the config of the class
981 # that can see both layers to be tied. For example:
982 #
983 # SomeVLModel:
984 # self.language_model = SomeLanguageModel(SomeVLTextConfig)
985 # self.vision_model = SomeVisionModel(SomeVLVisionConfig)
986 #
987 # SomeVLModelForMultimodalLM:
988 # self.model = SomeVLModel(SomeVLConfig)
989 # self.lm_head = nn.Linear()
990 #
991 # Therefore, tie_word_embeddings is defined in SomeVLConfig and is not present
992 # in SomeVLTextConfig*. In vLLM, the lm_head belongs to the language_model, so
993 # we must ensure that tie_word_embeddings is set in the language_model's config.
994 #
995 # *For some models, SomeVLTextConfig may also have a tie_word_embeddings field.
996 # This is only the case if SomeVLTextConfig is also used for a text only version
997 # of the same model. For example:
998 #
999 # SomeVLModelForCausalLM:
1000 # self.model = SomeLanguageModel(SomeVLTextConfig)
1001 # self.lm_head = nn.Linear()
1002 #
1003 # Therefore, the presence of tie_word_embeddings in SomeVLTextConfig cannot
1004 # be used as a signal for whether tie_word_embeddings should be copied from
1005 # hf_config to the language_model config.
1006 if model_config.is_multimodal_model and hasattr(
1007 model_config.hf_config, "tie_word_embeddings"
1008 ):
1009 tie_word_embeddings = model_config.hf_config.tie_word_embeddings
1010 hf_config.get_text_config().tie_word_embeddings = tie_word_embeddings
1011
1012 model_config.hf_config = hf_config
1013 model_config.model_arch_config = model_config.get_model_arch_config()
1014 model_config.is_submodel_config = True
1015
1016 return replace(self, model_config=model_config)
1017
1018 def _set_config_default(self, config_obj: Any, key: str, value: Any) -> None:
1019 """Set config attribute to default if not already set by user.
1020
1021 Args:
1022 config_obj: Configuration object to update.
1023 key: Attribute name.
1024 value: Default value (static or callable).
1025
1026 """
1027 if getattr(config_obj, key) is None:
1028 # Some config values are known before initialization and are
1029 # hard coded.
1030 # Other values depend on the user given configuration, so they are
1031 # implemented with lambda functions and decided at run time.
1032 setattr(config_obj, key, value(self) if callable(value) else value)
1033
1034 def _apply_optimization_level_defaults(self, defaults: dict[str, Any]) -> None:
1035 """Apply optimization level defaults using self as root.
1036
1037 Recursively applies values from defaults into nested config objects.
1038 Only fields present in defaults are overwritten.
1039
1040 If the user configuration does not specify a value for a default field
1041 and if the default field is still None after all user selections are
1042 applied, then default values will be applied to the field. User specified
1043 fields will not be overridden by the default.
1044
1045 Args:
1046 defaults: Dictionary of default values to apply.
1047
1048 """
1049
1050 def apply_recursive(config_obj: Any, config_defaults: dict[str, Any]) -> None:
1051 """Recursively apply defaults to config_obj, using self as root."""
1052 for key, value in config_defaults.items():
1053 if not hasattr(config_obj, key):
1054 continue
1055
1056 current = getattr(config_obj, key)
1057 if isinstance(value, dict) and is_dataclass(current):
1058 apply_recursive(current, value)
1059 else:
1060 self._set_config_default(config_obj, key, value)
1061
1062 apply_recursive(self, defaults)
1063
1064 def _maybe_override_dynamic_sd_cudagraph_mode(self) -> None:
1065 speculative_config = self.speculative_config
1066 if (
1067 speculative_config is None
1068 or not speculative_config.uses_dynamic_speculative_decoding()
1069 or not self.compilation_config.cudagraph_mode.has_full_cudagraphs()
1070 or self.use_v2_model_runner
1071 ):
1072 return
1073
1074 logger.warning_once(
1075 "Dynamic speculative decoding changes the target verification "
1076 "length at runtime. Overriding cudagraph_mode from %s to "
1077 "PIECEWISE for reliability. Use VLLM_USE_V2_MODEL_RUNNER=1 "
1078 "if you want to use full CUDA graphs.",
1079 self.compilation_config.cudagraph_mode.name,
1080 )
1081 self.compilation_config.cudagraph_mode = CUDAGraphMode.PIECEWISE
1082
1083 def _maybe_disable_dynamic_sd_for_data_parallel(self) -> None:
1084 speculative_config = self.speculative_config
1085 if (
1086 speculative_config is None
1087 or not speculative_config.uses_dynamic_speculative_decoding()
1088 or self.parallel_config.data_parallel_size <= 1
1089 ):
1090 return
1091
1092 logger.warning_once(
1093 "Dynamic speculative decoding is not supported with data "
1094 "parallelism because data-parallel ranks can select different "
1095 "speculative-token counts, causing DP divergence and deadlocks. "
1096 "Disabling num_speculative_tokens_per_batch_size and falling back "
1097 "to static num_speculative_tokens=%d.",
1098 speculative_config.num_speculative_tokens,
1099 )
1100 speculative_config.num_speculative_tokens_per_batch_size = None
1101
1102 def _normalize_piecewise_cudagraph_mode(
1103 self, *, breakable_cudagraph_enabled: bool
1104 ) -> None:
1105 compilation_config = self.compilation_config
1106 if (
1107 compilation_config.cudagraph_mode.requires_piecewise_compilation()
1108 and compilation_config.mode != CompilationMode.VLLM_COMPILE
1109 and not breakable_cudagraph_enabled
1110 ):
1111 fallback_mode = compilation_config.cudagraph_mode.without_piecewise()
1112 logger.info_once(
1113 "Cudagraph mode %s is not compatible with compilation mode %s. "
1114 "Overriding to %s.",
1115 compilation_config.cudagraph_mode,
1116 compilation_config.mode,
1117 fallback_mode,
1118 )
1119 compilation_config.cudagraph_mode = fallback_mode
1120 if fallback_mode == CUDAGraphMode.NONE:
1121 compilation_config.max_cudagraph_capture_size = 0
1122 compilation_config.cudagraph_capture_sizes = []
1123
1124 def _post_init_kv_transfer_config(self) -> None:
1125 """Update KVTransferConfig based on top-level configs in VllmConfig.
1126
1127 Right now, this function reads the offloading settings from
1128 CacheConfig and configures the KVTransferConfig accordingly.
1129 """
1130 # KV offloading is only activated when kv_offloading_size is set.
1131 if (kv_offloading_size := self.cache_config.kv_offloading_size) is None:
1132 return
1133
1134 kv_offloading_backend = self.cache_config.kv_offloading_backend
1135
1136 # If no KVTransferConfig is provided, create a default one.
1137 if self.kv_transfer_config is None:
1138 self.kv_transfer_config = KVTransferConfig()
1139
1140 if kv_offloading_backend == "native":
1141 if envs.VLLM_USE_SIMPLE_KV_OFFLOAD:
1142 config_connector = "SimpleCPUOffloadConnector"
1143 else:
1144 config_connector = "OffloadingConnector"
1145 self.kv_transfer_config.kv_connector = config_connector
1146 self.kv_transfer_config.kv_connector_extra_config.update(
1147 {"cpu_bytes_to_use": kv_offloading_size * (1 << 30)}
1148 )
1149 elif kv_offloading_backend == "lmcache":
1150 # Default to LMCache multi-process (MP) mode. The actual KV
1151 # storage capacity is managed by the standalone LMCache server
1152 # process, so ``kv_offloading_size`` is not propagated here.
1153 # ``LMCacheMPConnector`` falls back to ``tcp://localhost:5555``
1154 # when host/port are not provided via extra_config.
1155 self.kv_transfer_config.kv_connector = "LMCacheMPConnector"
1156
1157 # This is the same for all backends
1158 self.kv_transfer_config.kv_role = "kv_both"
1159
1160 def _verify_aux_output_compatibility(self) -> None:
1161 """Reject configurations unsupported by enabled auxiliary outputs."""
1162 if not self.aux_output_config.enabled:
1163 return
1164 from vllm.platforms import current_platform
1165
1166 # In-tree platforms only wire AuxOutput to MRV2. TPU and out-of-tree
1167 # platforms bring their own model runners and validate AuxOutput
1168 # support themselves.
1169 if not self.use_v2_model_runner and not (
1170 current_platform.is_tpu() or current_platform.is_out_of_tree()
1171 ):
1172 raise ValueError(
1173 "AuxOutput Connector requires Model Runner V2; set "
1174 "VLLM_USE_V2_MODEL_RUNNER=1."
1175 )
1176 if self.model_config.runner_type != "generate":
1177 raise ValueError("AuxOutput Connector only supports generate runners.")
1178 if not self.model_config.is_moe:
1179 raise ValueError("AuxOutput Connector only supports MoE models.")
1180 if not self.cache_config.enable_prefix_caching:
1181 raise ValueError("AuxOutput Connector requires prefix caching.")
1182 if (
1183 self.speculative_config is not None
1184 and self.speculative_config.enable_adaptive_verification
1185 ):
1186 raise ValueError(
1187 "--enable-return-routed-experts is incompatible with "
1188 "adaptive speculative verification."
1189 )
1190 if self.parallel_config.pipeline_parallel_size > 1:
1191 raise ValueError(
1192 "--enable-return-routed-experts is incompatible with "
1193 "pipeline parallelism (PP > 1)."
1194 )
1195 if (
1196 self.parallel_config.decode_context_parallel_size > 1
1197 or self.parallel_config.prefill_context_parallel_size > 1
1198 ):
1199 raise ValueError(
1200 "--enable-return-routed-experts is incompatible with "
1201 "context parallelism (DCP/PCP > 1)."
1202 )
1203
1204 kv_transfer_config = self.kv_transfer_config
1205 if kv_transfer_config is not None:
1206 for connector_name in (
1207 "NixlConnector",
1208 "NixlPullConnector",
1209 "NixlPushConnector",
1210 "MoRIIOConnector",
1211 "MooncakeConnector",
1212 ):
1213 if kv_transfer_config.has_connector(connector_name):
1214 raise ValueError(
1215 "--enable-return-routed-experts is incompatible with "
1216 f"{connector_name}; PD auxiliary output is not supported."
1217 )
1218
1219 def _verify_kv_transfer_compat(self) -> None:
1220 """Reject configurations that silently corrupt KV transfers."""
1221 if (
1222 self.kv_transfer_config is None
1223 or self.kv_transfer_config.kv_connector is None
1224 ):
1225 return
1226
1227 # PyTorch's expandable_segments allocator uses CUDA VMM, which can
1228 # remap a virtual address range to different physical pages over the
1229 # engine's lifetime. KV connectors that pin KV cache memory (e.g.
1230 # NixlConnector via ibv_reg_mr, MooncakeConnector) end up with their
1231 # registrations pointing at stale physical pages after any remap,
1232 # producing RDMA failures like IBV_WC_REM_ACCESS_ERR /
1233 # NIXL_ERR_REMOTE_DISCONNECT at the first inter-node KV transfer.
1234 # We can't enumerate every in-tree and out-of-tree connector that
1235 # pins memory, so we conservatively reject the combination whenever
1236 # any KV connector is configured.
1237 #
1238 # CuMem allocator is exempt: CuMemAllocator.use_memory_pool toggles
1239 # expandable_segments off around its pool (see #40812), so the KV
1240 # cache allocated within that context lands on stable physical pages
1241 # even when the env var is set.
1242 if "expandable_segments:True" not in os.environ.get(
1243 "PYTORCH_CUDA_ALLOC_CONF", ""
1244 ):
1245 return
1246 if self.model_config is not None and (self.model_config.enable_cumem_allocator):
1247 return
1248
1249 raise ValueError(
1250 f"KV connector {self.kv_transfer_config.kv_connector} is "
1251 "incompatible with PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True "
1252 "unless enable_cumem_allocator is also enabled. PyTorch's CUDA VMM "
1253 "allocator can remap KV cache virtual addresses to different "
1254 "physical pages, invalidating any pinned/registered KV memory "
1255 "(e.g. IB memory regions registered by NIXL or Mooncake). Either "
1256 "unset expandable_segments:True or enable the cumem allocator "
1257 "(sleep mode does this automatically and also "
1258 "routes KV allocations through CuMemAllocator's pool, where "
1259 "expandable_segments is automatically disabled)."
1260 )
1261
1262 def _verify_sampling_replay_config(self) -> None:
1263 model_config = self.model_config
1264 if model_config is None or not model_config.return_sampling_mask:
1265 return
1266 if not self.use_v2_model_runner:
1267 raise ValueError("sampling distribution replay requires Model Runner V2")
1268 speculative_config = self.speculative_config
1269 if (
1270 speculative_config is not None
1271 and speculative_config.enable_adaptive_verification
1272 ):
1273 raise ValueError(
1274 "sampling distribution replay with speculative decoding "
1275 "requires fixed verification boundaries; disable adaptive "
1276 "verification"
1277 )
1278 if model_config.is_diffusion:
1279 raise ValueError(
1280 "sampling distribution replay does not support diffusion models"
1281 )
1282 if model_config.logits_processors:
1283 raise ValueError(
1284 "sampling distribution replay does not support custom logits processors"
1285 )
1286 if model_config.logprobs_mode != "processed_logprobs":
1287 raise ValueError(
1288 "sampling distribution replay requires "
1289 "logprobs_mode='processed_logprobs' so that returned logprobs "
1290 "are normalized over the same nucleus as the sampling mask"
1291 )
1292
1293 def _verify_trace_replay_config(self) -> None:
1294 model_config = self.model_config
1295 if model_config is None or not model_config.enable_trace_replay:
1296 return
1297 if not self.use_v2_model_runner:
1298 raise ValueError("trace replay requires Model Runner V2")
1299
1300 def _check_watermarking_unsupported(
1301 self,
1302 *,
1303 beam_search: bool = False,
1304 custom_sampler: bool = False,
1305 ) -> None:
1306 watermark_config = getattr(self, "watermark_config", None)
1307 if watermark_config is None:
1308 return
1309 if self.speculative_config is not None:
1310 speculative_config = self.speculative_config
1311 if speculative_config.draft_sample_method != "probabilistic":
1312 raise ValueError(
1313 "Speculative decoding with watermarking requires "
1314 "draft_sample_method='probabilistic'."
1315 )
1316 if speculative_config.rejection_sample_method != "standard":
1317 raise ValueError(
1318 "Speculative decoding with watermarking requires "
1319 "rejection_sample_method='standard'."
1320 )
1321 if (
1322 speculative_config.parallel_drafting
1323 and speculative_config.method != "dspark"
1324 ):
1325 raise ValueError(
1326 "Parallel speculative drafting is not supported with watermarking."
1327 )
1328 if speculative_config.method not in ("dspark", "eagle", "eagle3", "mtp"):
1329 raise ValueError(
1330 "Watermarking supports only autoregressive model-based "
1331 "speculative decoding."
1332 )
1333 if (
1334 not watermark_config.allow_target_only_watermarking
1335 and not watermark_config.supports_speculative_decoding
1336 ):
1337 raise ValueError(
1338 f"The '{watermark_config.algorithm}' watermarking algorithm "
1339 "does not support speculative decoding. Set "
1340 "allow_target_only_watermarking=true to leave draft tokens "
1341 "unwatermarked."
1342 )
1343 if (
1344 watermark_config.allow_target_only_watermarking
1345 and not watermark_config.supports_speculative_decoding
1346 ):
1347 logger.warning_once(
1348 "Target-only watermarking leaves accepted draft tokens "
1349 "unwatermarked, weakening detectability in proportion to the "
1350 "share of output tokens supplied by accepted drafts.",
1351 scope="global",
1352 )
1353 if watermark_config.algorithm == "dual_key_gumbel" and (
1354 watermark_config.alpha != get_field(WatermarkConfig, "alpha").default
1355 ):
1356 logger.warning_once(
1357 "Speculative decoding selects the watermark key by token role: "
1358 "draft tokens use key A, recovery and bonus tokens use key B. "
1359 "The configured alpha=%s is not used.",
1360 watermark_config.alpha,
1361 scope="global",
1362 )
1363 if beam_search:
1364 raise ValueError("Beam search is not supported with watermarking.")
1365 if custom_sampler:
1366 raise ValueError(
1367 "Model-specific custom samplers are not supported with watermarking."
1368 )
1369
1370 def _resolve_and_verify_engram_config(self) -> None:
1371 """Resolve defaults and validate n-gram embedding settings."""
1372 model_config = self.model_config
1373 speculative_config = self.speculative_config
1374 # Draft configs inherit the target's communication groups and settings.
1375 # Validate the target because the draft may disable n-gram embeddings.
1376 if (
1377 speculative_config is not None
1378 and model_config is speculative_config.draft_model_config
1379 ):
1380 model_config = speculative_config.target_model_config
1381 if (
1382 model_config is not None
1383 and model_config.architecture == "DeepseekV41ForCausalLM"
1384 and getattr(model_config.hf_text_config, "engram_layer_ids", None)
1385 and self.parallel_config.use_ubatching
1386 ):
1387 raise ValueError(
1388 "DeepSeek V4.1 Engram does not support DBO or microbatching. "
1389 "Disable --enable-dbo and set --ubatch-size to 0."
1390 )
1391 if self.engram_config is None:
1392 if not model_has_engram_layers(model_config):
1393 return
1394 self.engram_config = EngramConfig()
1395 self.engram_config.verify_model_config(model_config)
1396 self.engram_config.resolve_dp_shared_memory(self.parallel_config)
1397 self.engram_config.verify_parallel_config(self.parallel_config)
1398 logger.info_once("Resolved Engram configuration: %s", str(self.engram_config))
1399
1400 def __post_init__(self):
1401 """Verify configs are valid & consistent with each other."""
1402 # To give each torch profile run a unique instance name.
1403 self.instance_id = f"{time.time_ns()}"
1404
1405 if self.model_config is not None and self.model_config.is_submodel_config:
1406 # with_hf_config() view: the parent config was already validated,
1407 # and this view's empty architecture list makes the model-dependent checks
1408 # below unsafe (e.g. use_mla resolves the architecture registry).
1409 return
1410
1411 self._resolve_mm_encoder_only()
1412
1413 if self.is_mm_encoder_only and self.cache_config.enable_prefix_caching:
1414 # Such an instance publishes encoder embeddings and runs no language
1415 # model, so it holds no KV cache for prefix caching to reuse and its
1416 # coordinator would have no group to manage. Disable before
1417 # `try_verify_and_update_config` so model config hooks (e.g. the
1418 # hybrid mamba hook setting `mamba_block_size`) already see prefix
1419 # caching as disabled.
1420 logger.info(
1421 "Disabling prefix caching: this instance runs the "
1422 "multi-modal encoder only."
1423 )
1424 self.cache_config.enable_prefix_caching = False
1425
1426 if self.performance_mode != "balanced":
1427 logger.info_once("Performance mode set to '%s'.", self.performance_mode)
1428
1429 self.try_verify_and_update_config()
1430 self._resolve_and_verify_engram_config()
1431
1432 self._check_watermarking_unsupported()
1433 # Models may have supplied their own DCP defaults above; anything still
1434 # unset falls back to the stock ones.
1435 self.parallel_config.set_dcp_defaults()
1436
1437 if self.model_config is not None:
1438 self.model_config.verify_with_parallel_config(self.parallel_config)
1439 self.model_config.verify_dual_chunk_attention_config(self.load_config)
1440
1441 self.parallel_config.is_moe_model = self.model_config.is_moe
1442
1443 if (
1444 self.model_config is not None
1445 and self.model_config.multimodal_config is not None
1446 and self.model_config.multimodal_config.language_model_only
1447 and self.compilation_config.cudagraph_mm_encoder
1448 ):
1449 raise ValueError(
1450 "--language-model-only is incompatible with "
1451 "cudagraph_mm_encoder=True, since it disables all multimodal "
1452 "inputs and the multimodal encoder is never run. Please "
1453 "disable one of them."
1454 )
1455
1456 self._verify_sampling_replay_config()
1457 self._verify_trace_replay_config()
1458
1459 # A NIXL side is either fully replicated or fully DCP-sharded; MLA only.
1460 if (
1461 self.kv_transfer_config is not None
1462 and self.kv_transfer_config.has_connector("NixlConnector")
1463 ):
1464 dcp_size = self.parallel_config.decode_context_parallel_size
1465 transfer_tp_size = max(
1466 self.parallel_config.tensor_parallel_size,
1467 self.parallel_config.prefill_context_parallel_size,
1468 )
1469 assert dcp_size in (1, transfer_tp_size), (
1470 f"decode_context_parallel_size={dcp_size} must be 1 or equal "
1471 f"to the NIXL transfer parallel size={transfer_tp_size}."
1472 )
1473 if self.model_config is not None:
1474 assert self.model_config.use_mla or dcp_size == 1, (
1475 "PD with decode_context_parallel_size > 1 is only "
1476 "supported for MLA models."
1477 )
1478 if self.lora_config is not None:
1479 self.lora_config.verify_with_model_config(self.model_config)
1480
1481 if (
1482 self.mamba_config.enable_stochastic_rounding
1483 and self.cache_config.mamba_ssm_cache_dtype != "float16"
1484 ):
1485 raise ValueError(
1486 "Stochastic rounding for Mamba cache requires "
1487 "the SSM cache to be float16. Please set it explicitly, "
1488 "by specifying `--mamba-ssm-cache-dtype float16`, or disable "
1489 "stochastic rounding by not specifying "
1490 "`--enable-mamba-cache-stochastic-rounding`."
1491 )
1492
1493 if self.quant_config is None and self.model_config is not None:
1494 self.quant_config = VllmConfig._get_quantization_config(
1495 self.model_config, self.load_config
1496 )
1497
1498 # "dummy" reads no weights at all, and the sharded formats read a vLLM
1499 # state dict, which stores tied word embeddings under the lm_head only.
1500 # Neither can tell us what the original checkpoint contained.
1501 if self.model_config is not None and self.load_config.load_format not in (
1502 "dummy",
1503 "sharded_state",
1504 "runai_streamer_sharded",
1505 ):
1506 self.model_config.maybe_untie_word_embeddings()
1507
1508 if (
1509 self.quant_config is not None
1510 and self.model_config is not None
1511 and hasattr(self.quant_config, "use_deep_gemm")
1512 and self.quant_config.use_deep_gemm is None
1513 ):
1514 from vllm.utils.deep_gemm import should_auto_disable_deep_gemm
1515
1516 model_type = getattr(self.model_config.hf_text_config, "model_type", None)
1517 if should_auto_disable_deep_gemm(model_type):
1518 self.quant_config.use_deep_gemm = False
1519 logger.warning_once(
1520 "Auto-disabled DeepGemm for model_type=%s on Blackwell. "
1521 "DeepGemm E8M0 scale format causes accuracy degradation "
1522 "for this architecture. Falling back to CUTLASS. "
1523 "To disable DeepGemm globally, set VLLM_USE_DEEP_GEMM=0.",
1524 model_type,
1525 )
1526
1527 from vllm.platforms import current_platform
1528 from vllm.v1.executor.abstract import Executor
1529
1530 executor_backend = self.parallel_config.distributed_executor_backend
1531 executor_class = Executor.get_class(self)
1532 executor_supports_async_sched = executor_class.supports_async_scheduling()
1533 uses_rocm_deepep_ht_dbo = (
1534 current_platform.is_rocm()
1535 and self.parallel_config.enable_dbo
1536 and self.parallel_config.all2all_backend == "deepep_high_throughput"
1537 )
1538
1539 if self.scheduler_config.async_scheduling:
1540 # Async scheduling explicitly enabled, hard fail any incompatibilities.
1541 # Currently, async scheduling only support eagle speculative
1542 # decoding.
1543 if uses_rocm_deepep_ht_dbo:
1544 raise ValueError(
1545 "Async scheduling is not compatible with ROCm DeepEP "
1546 "high-throughput DBO. Please use --no-async-scheduling or "
1547 "select a different all2all backend."
1548 )
1549 if self.speculative_config is not None:
1550 if (
1551 self.speculative_config.method not in get_args(EagleModelTypes)
1552 and self.speculative_config.method not in get_args(NgramGPUTypes)
1553 and self.speculative_config.method != "draft_model"
1554 and self.speculative_config.method != "dspark"
1555 and self.speculative_config.method != "dflash"
1556 ):
1557 raise ValueError(
1558 "Currently, async scheduling is only supported "
1559 "with EAGLE/MTP/Draft Model/NGram GPU/DSpark/DFlash "
1560 "kind of speculative decoding"
1561 )
1562 if self.speculative_config.disable_padded_drafter_batch:
1563 raise ValueError(
1564 "Async scheduling is not compatible with "
1565 "disable_padded_drafter_batch=True."
1566 )
1567 if not executor_supports_async_sched:
1568 raise ValueError(
1569 f"`{executor_backend}` does not support async scheduling yet."
1570 )
1571 elif self.scheduler_config.async_scheduling is None:
1572 # Enable async scheduling unless there is an incompatible option.
1573 if (
1574 self.model_config is not None
1575 and self.model_config.runner_type == "pooling"
1576 ):
1577 # The current implementation of asynchronous scheduling negatively
1578 # impacts performance of pooling models, so we disable by default.
1579 logger.debug(
1580 "Disabling asynchronous scheduling by default for pooling model."
1581 )
1582 self.scheduler_config.async_scheduling = False
1583 elif (
1584 self.speculative_config is not None
1585 and self.speculative_config.method not in get_args(EagleModelTypes)
1586 and self.speculative_config.method not in get_args(NgramGPUTypes)
1587 and self.speculative_config.method != "draft_model"
1588 and self.speculative_config.method != "dspark"
1589 and self.speculative_config.method != "dflash"
1590 ):
1591 logger.warning_once(
1592 "Async scheduling not supported with %s-based "
1593 "speculative decoding and will be disabled.",
1594 self.speculative_config.method,
1595 )
1596 self.scheduler_config.async_scheduling = False
1597 elif (
1598 self.speculative_config is not None
1599 and self.speculative_config.disable_padded_drafter_batch
1600 ):
1601 logger.warning_once(
1602 "Async scheduling is not compatible with "
1603 "disable_padded_drafter_batch=True and will be disabled.",
1604 )
1605 self.scheduler_config.async_scheduling = False
1606 elif not executor_supports_async_sched:
1607 logger.warning_once(
1608 "Async scheduling will be disabled because it is not supported "
1609 "with the `%s` distributed executor backend. ",
1610 executor_backend,
1611 )
1612 self.scheduler_config.async_scheduling = False
1613 elif uses_rocm_deepep_ht_dbo:
1614 logger.warning_once(
1615 "Async scheduling is disabled for ROCm DeepEP "
1616 "high-throughput DBO because that combination can corrupt "
1617 "DP+EP generation accuracy."
1618 )
1619 self.scheduler_config.async_scheduling = False
1620 elif (
1621 self.parallel_config.pipeline_parallel_size > 1
1622 and not self.use_v2_model_runner
1623 ):
1624 logger.warning_once(
1625 "Async scheduling is disabled because the V1 model runner "
1626 "does not support it with pipeline parallelism."
1627 )
1628 self.scheduler_config.async_scheduling = False
1629 else:
1630 self.scheduler_config.async_scheduling = True
1631
1632 if self.parallel_config.disable_nccl_for_dp_synchronization is None:
1633 if self.scheduler_config.async_scheduling:
1634 if self.parallel_config.data_parallel_size > 1 and (
1635 self.model_config is None or self.model_config.is_moe
1636 ):
1637 logger.info_once(
1638 "Disabling NCCL for DP synchronization "
1639 "when using async scheduling.",
1640 )
1641 self.parallel_config.disable_nccl_for_dp_synchronization = True
1642 else:
1643 self.parallel_config.disable_nccl_for_dp_synchronization = False
1644
1645 if (
1646 self.speculative_config is not None
1647 and self.scheduler_config.async_scheduling
1648 and self.model_config is not None
1649 and not self.model_config.disable_cascade_attn
1650 ):
1651 logger.warning_once(
1652 "Disabling cascade attention (not yet compatible with "
1653 "async speculative decoding).",
1654 )
1655 self.model_config.disable_cascade_attn = True
1656
1657 if (
1658 self.observability_config.per_request_spec_decode_metrics != "none"
1659 and self.speculative_config is None
1660 ):
1661 raise ValueError(
1662 "--per-request-spec-decode-metrics requires speculative decoding "
1663 "to be enabled (via --speculative-config)."
1664 )
1665
1666 if (
1667 self.model_config is not None
1668 and self.model_config.multimodal_config is not None
1669 and self.model_config.multimodal_config.mm_tensor_ipc == "torch_shm"
1670 and os.environ.get("VLLM_WORKER_MULTIPROC_METHOD") != "spawn"
1671 ):
1672 raise ValueError(
1673 "torch_shm is known to fail without "
1674 "VLLM_WORKER_MULTIPROC_METHOD set to spawn"
1675 )
1676
1677 if (
1678 self.model_config is not None
1679 and self.scheduler_config.enable_chunked_prefill
1680 and self.model_config.dtype == torch.float32
1681 and current_platform.get_device_capability() == (7, 5)
1682 ):
1683 logger.warning_once(
1684 "Turing devices tensor cores do not support float32 matmul. "
1685 "To workaround this limitation, vLLM will set 'ieee' input "
1686 "precision for chunked prefill triton kernels."
1687 )
1688
1689 if self.model_config is not None and self.model_config.enforce_eager:
1690 self.compilation_config.mode = CompilationMode.NONE
1691 self.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
1692 if self.parallel_config.enable_fault_tolerance:
1693 # Keep JIT warmup: in-inference Triton compilation latency
1694 # spikes can delay peer-fault detection past its deadline.
1695 logger.warning_once(
1696 "Enforce eager set, disabling torch.compile and CUDAGraphs. "
1697 "This is equivalent to setting -cc.mode=none "
1698 "-cc.cudagraph_mode=none"
1699 )
1700 else:
1701 logger.warning_once(
1702 "Enforce eager set, disabling torch.compile, CUDAGraphs, and "
1703 "JIT kernel warmup. This is equivalent to setting "
1704 "-cc.mode=none -cc.cudagraph_mode=none and "
1705 "--kernel_config.enable_jit_warmup=False"
1706 )
1707 self.kernel_config.enable_jit_warmup = False
1708
1709 if os.environ.get("TORCH_COMPILE_DISABLE") == "1":
1710 logger.warning_once(
1711 "TORCH_COMPILE_DISABLE is set, disabling torch.compile. "
1712 "This is equivalent to setting -cc.mode=none"
1713 )
1714 self.compilation_config.mode = CompilationMode.NONE
1715
1716 # TODO: This is a stopgap that turns sequence parallelism / async TP
1717 # off under batch invariance. Make the sequence-parallel reduce-scatter
1718 # path batch-invariant instead so they can stay enabled (#56370).
1719 # Sequence parallelism / async TP rewrite all_reduce + rms_norm into a
1720 # reduce-scatter whose reduction order depends on the batch, so they
1721 # are not batch-invariant. Decide this before the breakable-CUDA-graph
1722 # default below, which declines to auto-enable when they are on.
1723 pass_config = self.compilation_config.pass_config
1724 if envs.VLLM_BATCH_INVARIANT and (
1725 pass_config.enable_sp or pass_config.fuse_gemm_comms
1726 ):
1727 logger.warning_once(
1728 "Disabling sequence parallelism and async TP "
1729 "(pass_config.enable_sp / fuse_gemm_comms) when "
1730 "VLLM_BATCH_INVARIANT is enabled: the reduce-scatter path "
1731 "is not batch-invariant (see vllm-project/vllm#56370)."
1732 )
1733 pass_config.enable_sp = False
1734 pass_config.fuse_gemm_comms = False
1735
1736 breakable_cudagraph_enabled = self._maybe_enable_breakable_cudagraph()
1737
1738 if not breakable_cudagraph_enabled and (
1739 self.compilation_config.backend == "eager"
1740 or (
1741 self.compilation_config.mode is not None
1742 and self.compilation_config.mode != CompilationMode.VLLM_COMPILE
1743 )
1744 ):
1745 logger.warning_once(
1746 "Inductor compilation was disabled by user settings, "
1747 "optimizations settings that are only active during "
1748 "inductor compilation will be ignored."
1749 )
1750
1751 def has_blocked_weights():
1752 if self.quant_config is not None:
1753 if hasattr(self.quant_config, "weight_block_size"):
1754 return self.quant_config.weight_block_size is not None
1755 elif hasattr(self.quant_config, "has_blocked_weights"):
1756 return self.quant_config.has_blocked_weights()
1757 return False
1758
1759 # Enable quant_fp8 CUDA ops (TODO disable in follow up)
1760 # On H100 the CUDA kernel is faster than
1761 # native implementation
1762 # https://github.com/vllm-project/vllm/issues/25094
1763 if has_blocked_weights():
1764 custom_ops = self.compilation_config.custom_ops
1765 if "-quant_fp8" not in custom_ops:
1766 custom_ops.append("+quant_fp8")
1767
1768 current_platform.apply_config_platform_defaults(self)
1769
1770 if self.compilation_config.mode is None:
1771 if self.optimization_level > OptimizationLevel.O0:
1772 self.compilation_config.mode = CompilationMode.VLLM_COMPILE
1773 else:
1774 self.compilation_config.mode = CompilationMode.NONE
1775
1776 # By default, enable torch wrapping only when using custom Inductor lowering
1777 if self.compilation_config.ir_enable_torch_wrap is None:
1778 self.compilation_config.ir_enable_torch_wrap = (
1779 self.compilation_config.mode == CompilationMode.VLLM_COMPILE
1780 and self.compilation_config.backend == "inductor"
1781 )
1782
1783 if all(s not in self.compilation_config.custom_ops for s in ("all", "none")):
1784 if (
1785 self.compilation_config.backend == "inductor"
1786 and self.compilation_config.mode != CompilationMode.NONE
1787 ):
1788 self.compilation_config.custom_ops.append("none")
1789 else:
1790 self.compilation_config.custom_ops.append("all")
1791
1792 # This populates IR op priorities,
1793 # must happen after compilation mode and backend are decided,
1794 # but before fusion defaults are applied as those may depend on op priority.
1795 self.kernel_config.set_platform_defaults(self)
1796
1797 default_config = OPTIMIZATION_LEVEL_TO_CONFIG[self.optimization_level]
1798 self._apply_optimization_level_defaults(default_config)
1799 if self.kernel_config.enable_flashinfer_autotune is None:
1800 raise ValueError(
1801 "KernelConfig.enable_flashinfer_autotune must be set after applying "
1802 "optimization level defaults."
1803 )
1804
1805 self._maybe_disable_dynamic_sd_for_data_parallel()
1806 self._maybe_override_dynamic_sd_cudagraph_mode()
1807
1808 if (
1809 self.attention_config.hisparse_config is None
1810 and self.kv_transfer_config is not None
1811 and self.kv_transfer_config.has_connector("HiSparseConnector")
1812 ):
1813 self.attention_config.hisparse_config = HiSparseConfig()
1814
1815 if self.attention_config.hisparse_config is not None:
1816 if not current_platform.is_cuda():
1817 raise ValueError("HiSparse currently requires NVIDIA CUDA.")
1818 if self.parallel_config.pipeline_parallel_size > 1:
1819 raise ValueError("HiSparse does not support pipeline parallelism.")
1820 if self.parallel_config.decode_context_parallel_size > 1:
1821 raise ValueError(
1822 "HiSparse does not support decode context parallelism."
1823 )
1824 if not self.scheduler_config.scheduler_reserve_full_isl:
1825 # Without it, async loads admitted against free host blocks can
1826 # each wait on host pages the others hold, and waiting requests
1827 # are never preempted to free them.
1828 raise ValueError(
1829 "HiSparse requires --scheduler-reserve-full-isl; remove "
1830 "--no-scheduler-reserve-full-isl."
1831 )
1832 if self.model_config is not None and not hasattr(
1833 self.model_config.hf_config, "index_topk"
1834 ):
1835 raise ValueError(
1836 "HiSparse is only supported for DSA models with index_topk."
1837 )
1838 if self.kv_transfer_config is not None and (
1839 self.kv_transfer_config.kv_connector
1840 not in (
1841 None,
1842 "NixlConnector",
1843 "MooncakeStoreConnector",
1844 "MultiConnector",
1845 )
1846 ):
1847 logger.warning(
1848 "HiSparse host-resident KV is configured with connector "
1849 "%s. NixlConnector (GPU-staged host imports) and "
1850 "MooncakeStoreConnector (shared-store offload) are the "
1851 "validated paths; other connectors are treated as "
1852 "debug/fallback paths.",
1853 self.kv_transfer_config.kv_connector,
1854 )
1855
1856 self._normalize_piecewise_cudagraph_mode(
1857 breakable_cudagraph_enabled=breakable_cudagraph_enabled
1858 )
1859
1860 # async tp is built on top of sequence parallelism and requires it.
1861 pass_config = self.compilation_config.pass_config
1862 if pass_config.fuse_gemm_comms:
1863 pass_config.enable_sp = True
1864 if pass_config.enable_sp:
1865 if self.parallel_config.tensor_parallel_size == 1:
1866 logger.warning_once("Sequence Parallelism requires TP>1, disabling")
1867 pass_config.enable_sp = False
1868 pass_config.fuse_gemm_comms = False
1869 else:
1870 if pass_config.sp_min_token_num is None:
1871 from vllm.compilation.passes.fusion.sequence_parallelism import (
1872 get_sequence_parallelism_threshold,
1873 )
1874
1875 tp_size = self.parallel_config.tensor_parallel_size
1876 hidden_size = self.model_config.get_hidden_size()
1877 assert isinstance(self.model_config.dtype, torch.dtype)
1878 element_size = self.model_config.dtype.itemsize
1879 pass_config.sp_min_token_num = get_sequence_parallelism_threshold(
1880 hidden_size, tp_size, element_size
1881 )
1882
1883 if pass_config.sp_min_token_num is None:
1884 logger.warning_once(
1885 "Model hidden_size too small for the SP "
1886 "threshold heuristic, disabling. To force SP, "
1887 "set pass_config.sp_min_token_num manually."
1888 )
1889 pass_config.enable_sp = False
1890 pass_config.fuse_gemm_comms = False
1891
1892 from vllm.utils.torch_utils import HAS_OPAQUE_TYPE
1893
1894 if HAS_OPAQUE_TYPE:
1895 # On torch >= 2.11 the hoisted OpaqueObject approach supersedes
1896 # fast_moe_cold_start, so force it off.
1897 self.compilation_config.fast_moe_cold_start = False
1898 elif self.compilation_config.fast_moe_cold_start is None:
1899 # resolve default behavior: try to be as safe as possible
1900 # this config is unsafe if any spec decoding draft model has a MOE.
1901 # We'll conservatively turn it off if we see spec decoding.
1902 self.compilation_config.fast_moe_cold_start = (
1903 self.speculative_config is None
1904 )
1905
1906 self._set_max_num_scheduled_tokens()
1907
1908 if current_platform.support_static_graph_mode():
1909 # if cudagraph_mode has full cudagraphs, we need to check support
1910 if model_config := self.model_config:
1911 if (
1912 self.compilation_config.cudagraph_mode.has_full_cudagraphs()
1913 and model_config.pooler_config is not None
1914 ):
1915 logger.warning_once(
1916 "Pooling models do not support full cudagraphs. "
1917 "Overriding cudagraph_mode to PIECEWISE."
1918 )
1919 self.compilation_config.cudagraph_mode = CUDAGraphMode.PIECEWISE
1920 elif (
1921 model_config.is_encoder_decoder
1922 and self.compilation_config.cudagraph_mode
1923 not in (CUDAGraphMode.NONE, CUDAGraphMode.FULL_DECODE_ONLY)
1924 ):
1925 logger.info_once(
1926 "Encoder-decoder models do not support %s. "
1927 "Overriding cudagraph_mode to FULL_DECODE_ONLY.",
1928 self.compilation_config.cudagraph_mode.name,
1929 )
1930 self.compilation_config.cudagraph_mode = (
1931 CUDAGraphMode.FULL_DECODE_ONLY
1932 )
1933
1934 # Check if KV connector requires PIECEWISE mode for CUDA graphs
1935 if (
1936 self.kv_transfer_config is not None
1937 and self.kv_transfer_config.is_kv_transfer_instance
1938 and self.compilation_config.cudagraph_mode.has_full_cudagraphs()
1939 ):
1940 # Lazy import to avoid circular dependencies
1941 from vllm.distributed.kv_transfer.kv_connector.factory import (
1942 KVConnectorFactory,
1943 )
1944
1945 connector_cls = KVConnectorFactory.get_connector_class(
1946 self.kv_transfer_config
1947 )
1948 if connector_cls.requires_piecewise_for_cudagraph(
1949 self.kv_transfer_config.kv_connector_extra_config
1950 ):
1951 logger.warning_once(
1952 "KV connector %s requires PIECEWISE CUDA graph mode "
1953 "due to layerwise async operations that cannot be "
1954 "captured in CUDA graphs. "
1955 "Overriding cudagraph_mode from %s to PIECEWISE.",
1956 connector_cls.__name__,
1957 self.compilation_config.cudagraph_mode.name,
1958 )
1959 self.compilation_config.cudagraph_mode = CUDAGraphMode.PIECEWISE
1960
1961 # disable cudagraph when enforce eager execution
1962 if self.model_config is not None and self.model_config.enforce_eager:
1963 logger.info_once("Cudagraph is disabled under eager mode")
1964 self.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
1965 # override related settings when enforce eager
1966 self.compilation_config.max_cudagraph_capture_size = 0
1967 self.compilation_config.cudagraph_capture_sizes = []
1968 else:
1969 self.compilation_config.cudagraph_num_of_warmups = 1
1970
1971 self._set_cudagraph_sizes()
1972
1973 else:
1974 self.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
1975
1976 if self.cache_config.kv_sharing_fast_prefill:
1977 if (
1978 self.speculative_config is not None
1979 and self.speculative_config.use_eagle()
1980 ):
1981 raise ValueError(
1982 "Fast prefill optimization for KV sharing is not "
1983 "compatible with EAGLE as EAGLE requires correct logits "
1984 "for all tokens while fast prefill gives incorrect logits "
1985 "for prompt tokens."
1986 )
1987
1988 logger.warning_once(
1989 "--kv-sharing-fast-prefill requires changes on model side for "
1990 "correctness and to realize prefill savings."
1991 )
1992
1993 if (
1994 self.model_config
1995 and self.model_config.architecture == "WhisperForConditionalGeneration"
1996 and os.environ.get("VLLM_WORKER_MULTIPROC_METHOD") != "spawn"
1997 ):
1998 logger.warning_once(
1999 "Whisper is known to have issues with "
2000 "forked workers. If startup is hanging, "
2001 "try setting 'VLLM_WORKER_MULTIPROC_METHOD' "
2002 "to 'spawn'."
2003 )
2004
2005 if (
2006 self.kv_events_config is not None
2007 and self.kv_events_config.enable_kv_cache_events
2008 and not self.cache_config.enable_prefix_caching
2009 ):
2010 logger.warning_once(
2011 "KV cache events are on, but prefix caching is not enabled. "
2012 "Use --enable-prefix-caching to enable."
2013 )
2014 if (
2015 self.kv_events_config is not None
2016 and self.kv_events_config.publisher != "null"
2017 and not self.kv_events_config.enable_kv_cache_events
2018 ):
2019 logger.warning_once(
2020 "KV cache events are disabled, "
2021 "but the scheduler is configured to publish them. "
2022 "Modify KVEventsConfig.enable_kv_cache_events "
2023 "to True to enable."
2024 )
2025 current_platform.check_and_update_config(self)
2026
2027 # After the platform hook, which has the last word on async scheduling.
2028 if (
2029 self.diffusion_config is not None
2030 and self.scheduler_config.scheduler_cls is None
2031 ):
2032 scheduler_name = (
2033 "DiffusionAsyncScheduler"
2034 if self.scheduler_config.async_scheduling
2035 else "DiffusionScheduler"
2036 )
2037 self.scheduler_config.scheduler_cls = (
2038 f"vllm.v1.core.sched.diffusion_scheduler.{scheduler_name}"
2039 )
2040
2041 self._normalize_piecewise_cudagraph_mode(
2042 breakable_cudagraph_enabled=breakable_cudagraph_enabled
2043 )
2044
2045 self._resolve_mm_embedding_inputs()
2046 self._resolve_mm_processor_device()
2047 self._resolve_mm_video_decode_device()
2048 self._validate_mm_processor_device()
2049
2050 if self.use_v2_model_runner:
2051 self._disable_cudagraphs_for_v2_stock_torch_compile()
2052 self._validate_v2_model_runner()
2053 else:
2054 self._validate_v1_model_runner()
2055
2056 self._validate_profiler_config()
2057 self._validate_batch_sharded_sampling()
2058 self._validate_adaptive_verification()
2059
2060 # Re-compute compile ranges after platform-specific config updates
2061 # (e.g., XPU may lower max_num_batched_tokens when MLA is enabled)
2062 self._set_compile_ranges()
2063
2064 if self.parallel_config.all2all_backend == "moonep":
2065 if (
2066 self.model_config is not None
2067 and self.model_config.quantization is not None
2068 ):
2069 raise ValueError(
2070 "The moonep all2all backend currently supports unquantized "
2071 "BF16 models only; got "
2072 f"quantization={self.model_config.quantization!r}. Use a "
2073 "different --all2all-backend for quantized models."
2074 )
2075 if (
2076 self.model_config is not None
2077 and self.model_config.dtype != torch.bfloat16
2078 ):
2079 raise ValueError(
2080 "The moonep all2all backend currently supports BF16 models "
2081 f"only; got dtype={self.model_config.dtype}. Use a "
2082 "different --all2all-backend or --dtype bfloat16."
2083 )
2084 if self.parallel_config.enable_eplb:
2085 raise ValueError(
2086 "The moonep all2all backend does not support EPLB yet: "
2087 "EPLB rearranges expert parameters in a layout MoonEP's "
2088 "replicated [E+B] weights do not follow. Disable "
2089 "--enable-eplb or use a different --all2all-backend."
2090 )
2091 if self.parallel_config.expert_placement_strategy != "linear":
2092 raise ValueError(
2093 "The moonep all2all backend requires linear expert "
2094 "placement: its load-time all-gather assumes each rank "
2095 "holds a contiguous chunk of the global expert range. Got "
2096 "--expert-placement-strategy "
2097 f"{self.parallel_config.expert_placement_strategy!r}."
2098 )
2099 # Enforced here rather than in set_splitting_ops_for_v1 so it
2100 # holds for every compilation mode, and keyed on use_all2all so
2101 # PCP/SP-only topologies are covered too: MoonEP dispatch/combine
2102 # are eager-only and must not be captured.
2103 if (
2104 self.parallel_config.use_all2all
2105 and self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
2106 ):
2107 logger.info(
2108 "MoonEP: Disabling CUDA Graphs since the MoonEP "
2109 "integration is currently eager-only."
2110 )
2111 self.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
2112
2113 # Do this after all the updates to compilation_config.mode
2114 effective_dp_size = (
2115 self.parallel_config.data_parallel_size
2116 if self.model_config is None or self.model_config.is_moe
2117 else 1
2118 )
2119 self.compilation_config.set_splitting_ops_for_v1(
2120 all2all_backend=self.parallel_config.all2all_backend,
2121 data_parallel_size=effective_dp_size,
2122 )
2123
2124 if self.compilation_config.pass_config.enable_sp:
2125 # With pipeline parallelism, native rms norm tracing errors due to
2126 # incorrect residual shape.
2127 # Use custom rms norm to unblock. In the future,
2128 # the pass will operate on higher-level IR to avoid the issue.
2129 # TODO: https://github.com/vllm-project/vllm/issues/27894
2130 if self.compilation_config.mode != CompilationMode.VLLM_COMPILE:
2131 logger.warning_once(
2132 "Sequence parallelism is enabled, but running in wrong "
2133 "vllm compile mode: %s.",
2134 self.compilation_config.mode,
2135 )
2136
2137 if self.parallel_config.pipeline_parallel_size > 1:
2138 if "-rms_norm" not in self.compilation_config.custom_ops:
2139 self.compilation_config.custom_ops.append("+rms_norm")
2140 else:
2141 logger.warning_once(
2142 "Sequence parallelism not supported with "
2143 "native rms_norm when using %s, "
2144 "this will likely lead to an error.",
2145 "pipeline parallelism",
2146 )
2147
2148 # final check of cudagraph mode after all possible updates
2149 if current_platform.is_cuda_alike():
2150 if (
2151 self.compilation_config.cudagraph_mode.has_full_cudagraphs()
2152 and self.model_config is not None
2153 and not self.model_config.disable_cascade_attn
2154 and not self.compilation_config.cudagraph_mode.has_piecewise_cudagraphs() # noqa: E501
2155 ):
2156 logger.warning_once(
2157 "No piecewise cudagraph for executing cascade attention. "
2158 "Will fall back to eager execution if a batch runs into "
2159 "cascade attentions."
2160 )
2161
2162 if self.compilation_config.cudagraph_mode.requires_piecewise_compilation():
2163 assert (
2164 self.compilation_config.mode == CompilationMode.VLLM_COMPILE
2165 or envs.VLLM_USE_BREAKABLE_CUDAGRAPH
2166 ), (
2167 "Compilation mode should be CompilationMode.VLLM_COMPILE "
2168 "when cudagraph_mode piecewise cudagraphs is used, "
2169 f"cudagraph_mode={self.compilation_config.cudagraph_mode}"
2170 )
2171 if (
2172 self.model_config
2173 and envs.VLLM_BATCH_INVARIANT
2174 and not self.model_config.disable_cascade_attn
2175 ):
2176 self.model_config.disable_cascade_attn = True
2177 logger.warning_once(
2178 "Disabling cascade attention when VLLM_BATCH_INVARIANT is enabled.",
2179 )
2180
2181 if self.parallel_config.use_ubatching:
2182 a2a_backend = self.parallel_config.all2all_backend
2183 assert a2a_backend in [
2184 "deepep_low_latency",
2185 "deepep_high_throughput",
2186 "nixl_ep",
2187 ], (
2188 "Microbatching currently only supports the deepep_low_latency, "
2189 "deepep_high_throughput, and nixl_ep all2all backends. "
2190 f"{a2a_backend} is not supported. To fix use "
2191 "--all2all-backend=deepep_low_latency, "
2192 "--all2all-backend=deepep_high_throughput, or "
2193 "--all2all-backend=nixl_ep and install the matching kernels."
2194 )
2195
2196 if not self.model_config.disable_cascade_attn:
2197 self.model_config.disable_cascade_attn = True
2198 logger.warning_once("Disabling cascade attention when DBO is enabled.")
2199
2200 if not self.instance_id:
2201 self.instance_id = random_uuid()[:5]
2202
2203 if self.reasoning_config is not None and self.model_config is not None:
2204 self.reasoning_config.initialize_token_ids(self.model_config)
2205 if not self.reasoning_config.enabled:
2206 logger.warning_once(
2207 "Auto-initialization of reasoning token IDs failed. "
2208 "Please check whether your reasoning parser has implemented "
2209 "the `reasoning_start_str` and `reasoning_end_str`."
2210 )
2211
2212 # Resolve kv_offloading-derived connector name into kv_transfer_config
2213 # before the HMA check below, which inspects the connector class.
2214 self._post_init_kv_transfer_config()
2215 self._verify_aux_output_compatibility()
2216
2217 # Hybrid KV cache manager (HMA) runtime rules:
2218 # - Explicit enable (--no-disable-kv-cache-manager): error if runtime
2219 # disables it
2220 # - No preference: auto-disable for unsupported features or connector configs
2221 # - Explicit disable (--disable-kv-cache-manager): always respect it
2222 need_disable_hybrid_kv_cache_manager = False
2223 # logger should only print warning message for hybrid models. As we
2224 # can't know whether the model is hybrid or not now, so we don't log
2225 # warning message here and will log it later.
2226 if not current_platform.support_hybrid_kv_cache():
2227 # Hybrid KV cache manager is not supported on non-GPU platforms.
2228 need_disable_hybrid_kv_cache_manager = True
2229 if (
2230 self.model_config is not None
2231 and self.model_config.attention_chunk_size is not None
2232 ):
2233 if (
2234 self.speculative_config is not None
2235 and self.speculative_config.use_eagle()
2236 ):
2237 # Hybrid KV cache manager is not yet supported with chunked
2238 # local attention + eagle.
2239 need_disable_hybrid_kv_cache_manager = True
2240 elif not envs.VLLM_ALLOW_CHUNKED_LOCAL_ATTN_WITH_HYBRID_KV_CACHE:
2241 logger.warning(
2242 "There is a latency regression when using chunked local"
2243 " attention with the hybrid KV cache manager. Disabling"
2244 " it, by default. To enable it, set the environment "
2245 "VLLM_ALLOW_CHUNKED_LOCAL_ATTN_WITH_HYBRID_KV_CACHE=1."
2246 )
2247 # Hybrid KV cache manager is not yet supported with chunked
2248 # local attention.
2249 need_disable_hybrid_kv_cache_manager = True
2250
2251 if self.scheduler_config.disable_hybrid_kv_cache_manager is None:
2252 # Auto-disable HMA only when the connector config does not support it.
2253 if self.kv_transfer_config is not None:
2254 from vllm.distributed.kv_transfer.kv_connector.factory import (
2255 KVConnectorFactory,
2256 )
2257
2258 if not KVConnectorFactory.supports_hma_config(self.kv_transfer_config):
2259 need_disable_hybrid_kv_cache_manager = True
2260 logger.warning(
2261 "Turning off hybrid kv cache manager because "
2262 "`--kv-transfer-config` selects a KV connector that "
2263 "does not support it. Impact: hybrid SSM models "
2264 "(e.g. Jamba, Bamba) require HMA and will fail at "
2265 "startup without it; models with sliding window "
2266 "attention will run with reduced performance. "
2267 "To add HMA support to a KV connector, subclass "
2268 "`SupportsHMA` defined in kv_connector/v1/base.py "
2269 "(for MultiConnector, all child connectors must "
2270 "support HMA)."
2271 )
2272 self.scheduler_config.disable_hybrid_kv_cache_manager = (
2273 need_disable_hybrid_kv_cache_manager
2274 )
2275 elif (
2276 self.scheduler_config.disable_hybrid_kv_cache_manager is False
2277 and need_disable_hybrid_kv_cache_manager
2278 ):
2279 raise ValueError(
2280 "Hybrid KV cache manager was explicitly enabled but is not "
2281 "supported in this configuration. Consider omitting the "
2282 "--no-disable-hybrid-kv-cache-manager flag to let vLLM decide"
2283 " automatically."
2284 )
2285
2286 if self.scheduler_config.disable_hybrid_kv_cache_manager is None:
2287 # Default to enable HMA if not explicitly disabled by user or logic above.
2288 self.scheduler_config.disable_hybrid_kv_cache_manager = False
2289
2290 if (
2291 self.attention_config.hisparse_config is not None
2292 and self.scheduler_config.disable_hybrid_kv_cache_manager
2293 ):
2294 raise ValueError(
2295 "HiSparse requires the hybrid KV cache manager; remove "
2296 "--disable-hybrid-kv-cache-manager or use connectors that "
2297 "support HMA."
2298 )
2299
2300 if self.compilation_config.debug_dump_path:
2301 self.compilation_config.debug_dump_path = (
2302 self.compilation_config.debug_dump_path.absolute().expanduser()
2303 )
2304 if envs.VLLM_DEBUG_DUMP_PATH is not None:
2305 env_path = Path(envs.VLLM_DEBUG_DUMP_PATH).absolute().expanduser()
2306 if self.compilation_config.debug_dump_path:
2307 logger.warning(
2308 "Config-specified debug dump path is overridden"
2309 " by VLLM_DEBUG_DUMP_PATH to %s",
2310 env_path,
2311 )
2312 self.compilation_config.debug_dump_path = env_path
2313
2314 # Enable quant_fp8 CUDA ops (TODO disable in follow up)
2315 # On H100 the CUDA kernel is faster than
2316 # native implementation
2317 # https://github.com/vllm-project/vllm/issues/25094
2318 if has_blocked_weights():
2319 custom_ops = self.compilation_config.custom_ops
2320 if "-quant_fp8" not in custom_ops:
2321 custom_ops.append("+quant_fp8")
2322
2323 self._verify_kv_transfer_compat()
2324 # Log the custom passes that are enabled
2325 self.compilation_config.pass_config.log_enabled_passes()
2326
2327 def update_sizes_for_sequence_parallelism(self, possible_sizes: list) -> list:
2328 # remove the sizes that not multiple of tp_size when
2329 # enable sequence parallelism
2330 removed_sizes = [
2331 size
2332 for size in possible_sizes
2333 if size % self.parallel_config.tensor_parallel_size != 0
2334 ]
2335 if removed_sizes:
2336 logger.warning(
2337 "Batch sizes %s are removed because they are not "
2338 "multiple of tp_size %d when "
2339 "sequence parallelism is enabled",
2340 removed_sizes,
2341 self.parallel_config.tensor_parallel_size,
2342 )
2343
2344 return [
2345 size
2346 for size in possible_sizes
2347 if size % self.parallel_config.tensor_parallel_size == 0
2348 ]
2349
2350 def _set_max_num_scheduled_tokens(self):
2351 """In most cases, the scheduler may schedule a batch with as many tokens as the
2352 worker is configured to handle.
2353 """
2354 if self.speculative_config is not None:
2355 scheduled_token_delta = (
2356 self.speculative_config.max_num_new_slots_for_drafting
2357 )
2358 max_num_batched_tokens = self.scheduler_config.max_num_batched_tokens
2359 if self.scheduler_config.max_num_scheduled_tokens is None:
2360 self.scheduler_config.max_num_scheduled_tokens = max_num_batched_tokens
2361
2362 if self.scheduler_config.max_num_scheduled_tokens <= 0:
2363 raise ValueError(
2364 "max_num_scheduled_tokens is set to"
2365 f" {self.scheduler_config.max_num_scheduled_tokens} based on"
2366 " the speculative decoding settings, which does not allow"
2367 " any tokens to be scheduled. Increase max_num_batched_tokens"
2368 " to accommodate the additional draft token slots, or decrease"
2369 " num_speculative_tokens."
2370 )
2371 if self.scheduler_config.max_num_scheduled_tokens < 8192:
2372 logger.warning_once(
2373 "max_num_scheduled_tokens is set to"
2374 f" {self.scheduler_config.max_num_scheduled_tokens} based on"
2375 " the speculative decoding settings. This may lead to suboptimal"
2376 " performance. Consider increasing max_num_batched_tokens to"
2377 " accommodate the additional draft token slots, or decrease"
2378 " num_speculative_tokens.",
2379 )
2380
2381 if max_num_batched_tokens <= scheduled_token_delta:
2382 raise ValueError(
2383 "VllmConfig does not have enough slots to schedule a token and"
2384 " support the speculative decoding settings."
2385 f" Got {max_num_batched_tokens=} and {scheduled_token_delta=}."
2386 )
2387
2388 def _set_cudagraph_sizes(self):
2389 """VLLM defines the default candidate list of batch sizes for CUDA graph
2390 capture as:
2391
2392 ```python
2393 default_max_graph_size = 1024 if is_data_center_blackwell else 512
2394 decode_query_len = self.uniform_decode_query_len
2395 max_graph_size = min(
2396 max_num_seqs * decode_query_len * 2, default_max_graph_size
2397 )
2398 # 1, 2, 4, then multiples of 8 up to 256 and then multiples of 16
2399 # up to max_graph_size
2400 cudagraph_capture_sizes = [1, 2, 4] + list(range(8, 256, 8)) + list(
2401 range(256, max_graph_size + 1, 16))
2402
2403 `max_num_batched_tokens` is also appended to the list if it fits
2404 within `max_cudagraph_capture_size`, so the max batch size is captured
2405 even when off-stride. Uniform decode sizes are appended when they fit
2406 within the platform's default capture ceiling, since they need not land
2407 on an 8- or 16-token stride.
2408
2409 In the end, `vllm_config.compilation_config.cudagraph_capture_sizes`
2410 will be the final sizes to capture cudagraph (in ascending order).
2411
2412 These sizes are used to capture and reuse CUDA graphs for
2413 performance-critical paths (e.g., decoding). Capturing enables
2414 significantly faster kernel dispatch by avoiding Python overhead. The
2415 list is then filtered based on `max_num_batched_tokens` (e.g., 8192 on
2416 most GPUs), which controls the total allowed number of tokens in a
2417 batch. Since each sequence may have a variable number of tokens, the
2418 maximum usable batch size will depend on actual sequence lengths.
2419
2420 Example:
2421 With `max_num_batched_tokens = 8192`, and typical sequences
2422 averaging ~32 tokens, most practical batch sizes fall below 256.
2423 However, the system will still allow capture sizes up to the
2424 platform default if shape and memory permit.
2425
2426 Note:
2427 If users explicitly specify cudagraph capture sizes in the
2428 compilation config, those will override this default logic.
2429 At runtime:
2430
2431 - If batch size <= one of the `cudagraph_capture_sizes`, the closest
2432 padded CUDA graph will be used.
2433 - If batch size > largest `cudagraph_capture_sizes`, cudagraph will
2434 not be used.
2435
2436 """
2437 if (
2438 self.model_config is not None
2439 and not self.model_config.enforce_eager
2440 and self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
2441 ):
2442 # determine the initial max_cudagraph_capture_size
2443 max_cudagraph_capture_size = (
2444 self.compilation_config.max_cudagraph_capture_size
2445 )
2446 # Decode sizes to cover, in tokens. Populated only when the default
2447 # is computed here, so an explicit capture range is left exactly as
2448 # configured.
2449 uniform_decode_sizes: list[int] = []
2450 if max_cudagraph_capture_size is None:
2451 from vllm.platforms import current_platform
2452
2453 default_max_graph_size = (
2454 1024 if current_platform.is_device_capability_family(100) else 512
2455 )
2456 decode_query_len = self.uniform_decode_query_len
2457 max_num_seqs = self.scheduler_config.max_num_seqs
2458 max_cudagraph_capture_size = min(
2459 max_num_seqs * decode_query_len * 2, default_max_graph_size
2460 )
2461 if decode_query_len > 1:
2462 # A uniform decode batch is decode_query_len tokens per
2463 # request, so the widest one is far outside this ceiling.
2464 # Coverage comes from appending the decode sizes rather than
2465 # extending the token-strided grid. Extending that grid to
2466 # the widest decode size produces 581 sizes at
2467 # max_num_seqs=512 and 16 draft tokens, versus 100 with the
2468 # request-count grid.
2469 #
2470 # The grid would not buy decode coverage anyway. Dispatch
2471 # requires an exact multiple of decode_query_len, so a
2472 # token-strided entry is only usable when it happens to be
2473 # one; at query length 17 a captured 560 rounds to 561 and
2474 # is rejected. Scaling a request-count grid keeps every
2475 # entry usable and the count comparable to the non-
2476 # speculative case.
2477 def request_counts(max_reqs: int) -> list[int]:
2478 # At most the platform default number of requests,
2479 # mirroring the one-token-per-request decode ceiling.
2480 max_reqs = min(max_reqs, default_max_graph_size)
2481 counts = [n for n in (1, 2, 4) if n <= max_reqs]
2482 counts += list(range(8, min(max_reqs + 1, 256), 8))
2483 counts += list(range(256, max_reqs + 1, 16))
2484 return sorted(set(counts + [max_reqs]))
2485
2486 # Dynamic speculative decoding picks the draft width from
2487 # the batch size, so a decode step is only uniform within a
2488 # tier and each tier needs its own sizes. Scaling by the
2489 # widest one alone leaves the narrower tiers short: the
2490 # manager rounds a capture size up to a multiple of the
2491 # tier's query length and drops it once the implied request
2492 # count exceeds max_num_seqs, so at query length 3 sizes
2493 # built from 17 stop covering at 227 of 256 requests.
2494 decode_tiers = [(decode_query_len, max_num_seqs)]
2495 speculative_config = self.speculative_config
2496 if (
2497 speculative_config is not None
2498 and speculative_config.uses_dynamic_speculative_decoding()
2499 ):
2500 from vllm.v1.spec_decode.dynamic.utils import (
2501 build_dynamic_sd_schedule_lookup,
2502 )
2503
2504 schedule = (
2505 speculative_config.num_speculative_tokens_per_batch_size
2506 )
2507 assert schedule is not None
2508 # Read the tiers off the dense lookup the scheduler
2509 # runs on, so the clamp against num_speculative_tokens
2510 # and the carry-forward through gaps and the tail
2511 # cannot drift from it. Validation lives elsewhere; an
2512 # invalid schedule keeps the single-tier default.
2513 try:
2514 dense_schedule = build_dynamic_sd_schedule_lookup(
2515 schedule,
2516 vllm_max_batch_size=max_num_seqs,
2517 vllm_num_speculative_tokens=self.num_speculative_tokens,
2518 )
2519 except ValueError:
2520 pass
2521 else:
2522 # Ascending batch size, so the last write per
2523 # query length is the widest batch running at it.
2524 widest_batch: dict[int, int] = {}
2525 for batch_size, num_spec in enumerate(
2526 dense_schedule[1:], start=1
2527 ):
2528 widest_batch[num_spec + 1] = batch_size
2529 decode_tiers = list(widest_batch.items())
2530
2531 uniform_decode_sizes = sorted(
2532 {
2533 n * query_len
2534 for query_len, tier_max_reqs in decode_tiers
2535 for n in request_counts(tier_max_reqs)
2536 if n * query_len <= max_cudagraph_capture_size
2537 }
2538 )
2539 elif max_num_seqs <= max_cudagraph_capture_size:
2540 uniform_decode_sizes = [max_num_seqs]
2541 max_num_tokens = self.scheduler_config.max_num_batched_tokens
2542 max_cudagraph_capture_size = min(max_num_tokens, max_cudagraph_capture_size)
2543
2544 assert max_cudagraph_capture_size >= 1, (
2545 "Maximum cudagraph size should be greater than or equal to 1 "
2546 "when using cuda graph."
2547 )
2548
2549 # determine the cudagraph_capture_sizes
2550 if self.compilation_config.cudagraph_capture_sizes is not None:
2551 assert len(self.compilation_config.cudagraph_capture_sizes) > 0, (
2552 "cudagraph_capture_sizes should contain at least one element "
2553 "when using cuda graph."
2554 )
2555 # de-duplicate the sizes provided by the config
2556 dedup_sizes = list(set(self.compilation_config.cudagraph_capture_sizes))
2557 cudagraph_capture_sizes = [
2558 i for i in dedup_sizes if i <= max_num_tokens
2559 ]
2560 # sort to make sure the sizes are in ascending order
2561 cudagraph_capture_sizes.sort()
2562 else:
2563 if self.performance_mode == "interactivity":
2564 # Fine-grained CUDA graphs at small batch sizes
2565 # for minimal padding overhead
2566 interactivity_max = min(max_cudagraph_capture_size, 32)
2567 cudagraph_capture_sizes = list(range(1, interactivity_max + 1))
2568 else:
2569 cudagraph_capture_sizes = [
2570 i for i in [1, 2, 4] if i <= max_cudagraph_capture_size
2571 ]
2572 if max_cudagraph_capture_size >= 8:
2573 # Step size 8 for small batch sizes, up to 256(not included)
2574 cudagraph_capture_sizes += list(
2575 range(8, min(max_cudagraph_capture_size + 1, 256), 8)
2576 )
2577 if max_cudagraph_capture_size >= 256:
2578 # Step size 16 for larger batch sizes
2579 cudagraph_capture_sizes += list(
2580 range(256, max_cudagraph_capture_size + 1, 16)
2581 )
2582 # ensure max_num_tokens is captured if within max capture size
2583 if (
2584 max_num_tokens <= max_cudagraph_capture_size
2585 and max_num_tokens not in cudagraph_capture_sizes
2586 ):
2587 cudagraph_capture_sizes.append(max_num_tokens)
2588 # Preserve the platform's default capture ceiling. Larger
2589 # uniform decode batches fall back to eager execution unless
2590 # users explicitly configure wider capture sizes.
2591 cudagraph_capture_sizes += [
2592 size for size in uniform_decode_sizes if size <= max_num_tokens
2593 ]
2594 # de-duplicate and sort the sizes
2595 cudagraph_capture_sizes = sorted(set(cudagraph_capture_sizes))
2596
2597 if (
2598 self.parallel_config.tensor_parallel_size > 1
2599 and self.compilation_config.pass_config.enable_sp
2600 ):
2601 # Sequence parallelism only captures TP-divisible sizes, so a
2602 # wider non-divisible decode batch cannot be captured under SP.
2603 cudagraph_capture_sizes = self.update_sizes_for_sequence_parallelism(
2604 cudagraph_capture_sizes
2605 )
2606
2607 # user-specific compilation_config.max_cudagraph_capture_size get
2608 # truncated to valid_max_size when they are inconsistent.
2609 valid_max_size = (
2610 cudagraph_capture_sizes[-1] if cudagraph_capture_sizes else 0
2611 )
2612 if (
2613 self.compilation_config.max_cudagraph_capture_size is not None
2614 and self.compilation_config.max_cudagraph_capture_size != valid_max_size
2615 ):
2616 # raise error only when both two flags are user-specified
2617 # and they are inconsistent with each other
2618 if self.compilation_config.cudagraph_capture_sizes is not None:
2619 raise ValueError(
2620 "customized max_cudagraph_capture_size"
2621 f"(={self.compilation_config.max_cudagraph_capture_size}) "
2622 "should be consistent with the max value of "
2623 f"cudagraph_capture_sizes(={valid_max_size})"
2624 )
2625
2626 logger.warning(
2627 "Truncating max_cudagraph_capture_size to %d",
2628 valid_max_size,
2629 )
2630 # always set the final max_cudagraph_capture_size
2631 self.compilation_config.max_cudagraph_capture_size = valid_max_size
2632
2633 if self.compilation_config.cudagraph_capture_sizes is not None and len(
2634 cudagraph_capture_sizes
2635 ) < len(self.compilation_config.cudagraph_capture_sizes):
2636 # If users have specified capture sizes, we only need to
2637 # compare the lens before and after modification since the modified
2638 # list is only the subset of the original list.
2639 logger.warning(
2640 (
2641 "cudagraph_capture_sizes specified in compilation_config"
2642 " %s is overridden by config %s"
2643 ),
2644 self.compilation_config.cudagraph_capture_sizes,
2645 cudagraph_capture_sizes,
2646 )
2647 # always write back the final sizes
2648 self.compilation_config.cudagraph_capture_sizes = cudagraph_capture_sizes
2649
2650 else:
2651 # no cudagraph in use
2652 self.compilation_config.max_cudagraph_capture_size = 0
2653 self.compilation_config.cudagraph_capture_sizes = []
2654
2655 # complete the remaining process.
2656 self.compilation_config.post_init_cudagraph_sizes()
2657
2658 def _set_compile_ranges(self):
2659 """Set the compile ranges for the compilation config."""
2660 compilation_config = self.compilation_config
2661 computed_compile_ranges_endpoints = []
2662
2663 # The upper bound of the compile ranges is the max_num_batched_tokens.
2664 compile_range_end = self.scheduler_config.max_num_batched_tokens
2665 if compile_range_end is not None:
2666 computed_compile_ranges_endpoints.append(compile_range_end)
2667
2668 # Add the compile ranges for flashinfer/aiter.
2669 if compilation_config.pass_config.fuse_allreduce_rms:
2670 tp_size = self.parallel_config.tensor_parallel_size
2671 from vllm._aiter_ops import rocm_aiter_ops
2672
2673 max_size: int | None = None
2674 if rocm_aiter_ops.is_custom_all_reduce_enabled():
2675 from vllm.distributed.device_communicators.aiter_custom_all_reduce import ( # noqa: E501
2676 AiterCustomAllreduce,
2677 )
2678
2679 max_size = AiterCustomAllreduce.effective_max_size()
2680 else:
2681 max_size = compilation_config.pass_config.flashinfer_max_size(tp_size)
2682 if max_size is not None and self.model_config is not None:
2683 assert isinstance(self.model_config.dtype, torch.dtype)
2684 max_token_num = max_size // (
2685 self.model_config.get_hidden_size()
2686 * self.model_config.dtype.itemsize
2687 )
2688 if compile_range_end is not None and max_token_num < compile_range_end:
2689 computed_compile_ranges_endpoints.append(max_token_num)
2690 else:
2691 logger.debug(
2692 "Max num batched tokens below allreduce-rms fusion threshold, "
2693 "allreduce-rms fusion will be enabled for all num_tokens."
2694 )
2695
2696 # Add the compile ranges for sequence parallelism
2697 if compilation_config.pass_config.enable_sp:
2698 pass_config = compilation_config.pass_config
2699
2700 # Calculate min_token_num if not explicitly provided
2701 # User override works regardless of hidden_size
2702 if pass_config.sp_min_token_num is None:
2703 from vllm.compilation.passes.fusion.sequence_parallelism import (
2704 get_sequence_parallelism_threshold,
2705 )
2706
2707 tp_size = self.parallel_config.tensor_parallel_size
2708 hidden_size = self.model_config.get_hidden_size()
2709 assert isinstance(self.model_config.dtype, torch.dtype)
2710 element_size = self.model_config.dtype.itemsize
2711 pass_config.sp_min_token_num = get_sequence_parallelism_threshold(
2712 hidden_size, tp_size, element_size
2713 )
2714
2715 min_token_num = pass_config.sp_min_token_num
2716 max_num_batched_tokens = self.scheduler_config.max_num_batched_tokens
2717 if min_token_num is not None and (
2718 max_num_batched_tokens is not None
2719 and min_token_num < max_num_batched_tokens
2720 and min_token_num > 1
2721 ):
2722 # Add endpoint at min_token_num - 1 to ensure SP applies
2723 # starting from min_token_num
2724 # This creates ranges: [1, min-1] (no SP), [min, max] (SP applies)
2725 computed_compile_ranges_endpoints.append(min_token_num - 1)
2726
2727 if compilation_config.pass_config.fuse_rope_kvcache:
2728 max_token_num = (
2729 compilation_config.pass_config.rope_kvcache_fusion_max_token_num
2730 )
2731 if max_token_num is not None:
2732 if compile_range_end is not None and max_token_num < compile_range_end:
2733 computed_compile_ranges_endpoints.append(max_token_num)
2734 else:
2735 logger.debug(
2736 "Max num batched tokens below rope+kvcache fusion threshold, "
2737 "rope+kvcache fusion enabled for num_tokens <= %d.",
2738 compile_range_end,
2739 )
2740
2741 if compilation_config.pass_config.fuse_qk_norm_rope_kvcache:
2742 max_token_num = (
2743 compilation_config.pass_config.rope_kvcache_fusion_max_token_num
2744 )
2745 if max_token_num is not None:
2746 if compile_range_end is not None and max_token_num < compile_range_end:
2747 computed_compile_ranges_endpoints.append(max_token_num)
2748 else:
2749 logger.debug(
2750 "Max num batched tokens below qk_norm+rope+kvcache "
2751 "fusion threshold, fusion enabled for "
2752 "num_tokens <= %d.",
2753 compile_range_end,
2754 )
2755
2756 if compilation_config.compile_ranges_endpoints is not None:
2757 for x in compilation_config.compile_ranges_endpoints:
2758 assert isinstance(x, int)
2759 assert x > 0, f"Invalid compile range endpoint: {x}"
2760 if compile_range_end is not None and x < compile_range_end and x > 1:
2761 computed_compile_ranges_endpoints.append(x)
2762 compilation_config.compile_ranges_endpoints = sorted(
2763 computed_compile_ranges_endpoints
2764 )
2765
2766 def try_verify_and_update_config(self):
2767 if self.model_config is None:
2768 return
2769
2770 # Avoid running try_verify_and_update_config multiple times
2771 if getattr(self.model_config, "config_updated", False):
2772 return
2773 self.model_config.config_updated = True
2774
2775 architecture = self.model_config.architecture
2776 if architecture is None:
2777 return
2778
2779 from vllm.model_executor.models import ModelRegistry
2780 from vllm.model_executor.models.config import (
2781 MODELS_CONFIG_MAP,
2782 HybridAttentionMambaModelConfig,
2783 )
2784
2785 cls = MODELS_CONFIG_MAP.get(architecture, None)
2786 if cls is None:
2787 # `architecture` may be an HF base-model name (e.g. "Mamba2Model"
2788 # when `architectures` is omitted); normalize to the resolved arch
2789 # so per-arch config hooks are not skipped.
2790 architecture = ModelRegistry._normalize_arch(
2791 architecture, self.model_config
2792 )
2793 cls = MODELS_CONFIG_MAP.get(architecture, None)
2794 if cls is not None:
2795 cls.verify_and_update_config(self)
2796
2797 if self.model_config.is_hybrid:
2798 HybridAttentionMambaModelConfig.verify_and_update_config(self)
2799
2800 if self.model_config.convert_type == "classify":
2801 # Maybe convert ForCausalLM into ForSequenceClassification model.
2802 from vllm.model_executor.models.adapters import SequenceClassificationConfig
2803
2804 SequenceClassificationConfig.verify_and_update_config(self)
2805
2806 if hasattr(self.model_config, "model_weights") and is_runai_obj_uri(
2807 self.model_config.model_weights
2808 ):
2809 if self.load_config.load_format == "auto":
2810 logger.info(
2811 "Detected Run:ai model config. "
2812 "Overriding `load_format` to 'runai_streamer'"
2813 )
2814 self.load_config.load_format = "runai_streamer"
2815 elif self.load_config.load_format not in (
2816 "modelexpress",
2817 "runai_streamer",
2818 "runai_streamer_sharded",
2819 ):
2820 raise ValueError(
2821 f"To load a model from object storage (S3/GCS/Azure), "
2822 f"'load_format' must be 'modelexpress', 'runai_streamer' or "
2823 f"'runai_streamer_sharded', "
2824 f"but got '{self.load_config.load_format}'. "
2825 f"Model: {self.model_config.model}"
2826 )
2827
2828 def compile_debug_dump_path(self) -> Path | None:
2829 """Returns a rank-aware path for dumping
2830 torch.compile debug information.
2831 """
2832 if self.compilation_config.debug_dump_path is None:
2833 return None
2834 tp_rank = self.parallel_config.rank
2835 dp_rank = self.parallel_config.data_parallel_index
2836 append_path = f"rank_{tp_rank}_dp_{dp_rank}"
2837 path = self.compilation_config.debug_dump_path / append_path
2838 return path
2839
2840 def __str__(self):
2841 return (
2842 f"model={self.model_config.model!r}, "
2843 f"speculative_config={self.speculative_config!r}, "
2844 f"tokenizer={self.model_config.tokenizer!r}, "
2845 f"skip_tokenizer_init={self.model_config.skip_tokenizer_init}, "
2846 f"tokenizer_mode={self.model_config.tokenizer_mode}, "
2847 f"revision={self.model_config.revision}, "
2848 f"tokenizer_revision={self.model_config.tokenizer_revision}, "
2849 f"trust_remote_code={self.model_config.trust_remote_code}, "
2850 f"dtype={self.model_config.dtype}, "
2851 f"max_seq_len={self.model_config.max_model_len}, "
2852 f"download_dir={self.load_config.download_dir!r}, "
2853 f"load_format={self.load_config.load_format}, "
2854 f"tensor_parallel_size={self.parallel_config.tensor_parallel_size}, " # noqa
2855 f"pipeline_parallel_size={self.parallel_config.pipeline_parallel_size}, " # noqa
2856 f"data_parallel_size={self.parallel_config.data_parallel_size}, " # noqa
2857 f"decode_context_parallel_size={self.parallel_config.decode_context_parallel_size}, " # noqa
2858 f"dcp_comm_backend={self.parallel_config.dcp_comm_backend}, " # noqa
2859 f"disable_custom_all_reduce={self.parallel_config.disable_custom_all_reduce}, " # noqa
2860 f"quantization={self.model_config.quantization}, "
2861 f"quantization_config={self.model_config.quantization_config}, " # noqa
2862 f"enforce_eager={self.model_config.enforce_eager}, "
2863 f"aux_output_config={self.aux_output_config!r}, "
2864 f"kv_cache_dtype={self.cache_config.cache_dtype}, "
2865 f"device_config={self.device_config.device}, "
2866 f"structured_outputs_config={self.structured_outputs_config!r}, "
2867 f"observability_config={self.observability_config!r}, "
2868 f"seed={self.model_config.seed}, "
2869 f"served_model_name={self.model_config.served_model_name}, "
2870 f"enable_prefix_caching={self.cache_config.enable_prefix_caching}, "
2871 f"enable_chunked_prefill={self.scheduler_config.enable_chunked_prefill}, " # noqa
2872 f"pooler_config={self.model_config.pooler_config!r}, "
2873 f"compilation_config={self.compilation_config!r}, "
2874 f"kernel_config={self.kernel_config!r}"
2875 )
2876
2877 def _resolve_mm_embedding_inputs(self) -> None:
2878 """Accept embedding inputs, tensor optional, on disaggregated consumers.
2879
2880 An EC consumer loads embeddings from its connector. A KV consumer
2881 receives the prompt KV produced from those embeddings, so it does not
2882 need the tensors either. On every other deployment a missing tensor is
2883 a client error and must keep failing fast in the frontend.
2884 """
2885 model_config = self.model_config
2886 if model_config is None:
2887 return
2888 mm_config = model_config.multimodal_config
2889 if mm_config is None:
2890 return
2891
2892 ec_config = self.ec_transfer_config
2893 kv_config = self.kv_transfer_config
2894 # Derived, so overwrite unconditionally rather than honouring a value
2895 # that was set by hand.
2896 mm_config.allow_missing_mm_embeddings = (
2897 ec_config is not None and ec_config.is_ec_consumer
2898 ) or (kv_config is not None and kv_config.is_kv_consumer)
2899 if not mm_config.allow_missing_mm_embeddings:
2900 return
2901
2902 if not mm_config.enable_mm_embeds:
2903 # Allowing missing tensors still requires enabling embedding inputs
2904 # for the frontend to accept metadata-only requests.
2905 mm_config.enable_mm_embeds = True
2906 logger.info_once(
2907 "EC/KV consumer: accepting pre-computed-embedding inputs, "
2908 "which this role is sent by definition."
2909 )
2910 logger.info_once(
2911 "EC/KV consumer: pre-computed-embedding inputs may "
2912 "omit the embedding tensor."
2913 )
2914
2915 def _resolve_mm_encoder_only(self) -> None:
2916 """Enable encoder-only mode for a dedicated EC producer."""
2917 ec_config = self.ec_transfer_config
2918 if ec_config is None or not ec_config.is_encode_only:
2919 return
2920
2921 model_config = self.model_config
2922 mm_config = model_config.multimodal_config if model_config is not None else None
2923 if mm_config is None:
2924 raise ValueError(
2925 "An EC producer-only instance requires a multimodal model."
2926 )
2927 mm_config.mm_encoder_only = True
2928
2929 def _resolve_mm_processor_device(self) -> None:
2930 """Settle `--mm-processor-device=auto` now that the EC role is known.
2931
2932 "auto" means "the accelerator, but only where the processor has it to
2933 itself and its output can be handed over without a copy back to host":
2934 an encode-only instance whose tensor transport carries device tensors.
2935 Every other deployment keeps the processor on CPU.
2936
2937 An explicit device -- from `--mm-processor-device` or straight from
2938 `mm_processor_kwargs` -- is already folded in by `MultiModalConfig`, so
2939 it is left alone here and validated by `_validate_mm_processor_device`.
2940 """
2941 model_config = self.model_config
2942 if model_config is None:
2943 return
2944 mm_config = model_config.multimodal_config
2945 if mm_config is None:
2946 return
2947 if mm_config.get_mm_processor_device_type() is not None:
2948 return
2949
2950 from vllm.platforms import current_platform
2951
2952 device_type = current_platform.device_type
2953 if device_type in ("", "cpu"):
2954 return
2955
2956 ec_config = self.ec_transfer_config
2957 # An EC producer that is not also a consumer runs no forward pass and
2958 # allocates no KV cache, so frontend accelerator work has the device to
2959 # itself.
2960 if ec_config is None or not ec_config.is_encode_only:
2961 return
2962
2963 if mm_config.mm_tensor_ipc != "torch_shm":
2964 # Any other transport serializes host bytes, so the output would be
2965 # copied back, and that copy costs more than running the transform
2966 # on device saves.
2967 logger.info_once(
2968 "EPD encoder instance: keeping the multi-modal processor on CPU "
2969 "because mm_tensor_ipc=%s cannot carry device tensors. Add "
2970 "--mm-tensor-ipc=torch_shm to run it on the accelerator.",
2971 mm_config.mm_tensor_ipc,
2972 )
2973 return
2974
2975 mm_config.mm_processor_kwargs = {
2976 **(mm_config.mm_processor_kwargs or {}),
2977 "device": device_type,
2978 }
2979 logger.info_once(
2980 "EPD encoder instance: running the multi-modal processor on %s. "
2981 "Override with --mm-processor-device=cpu.",
2982 device_type,
2983 )
2984
2985 def _resolve_mm_video_decode_device(self) -> None:
2986 """Default video decoding to torchcodec GPU backend for EPD encoder-only
2987 instance if the mm processor runs on CUDA.
2988
2989 The processor consumes the decoded frames on-device in that case, so
2990 keeping the frames on the GPU skips the host round-trip through the
2991 CPU media path. An explicit codec/backend choice in
2992 `--media-io-kwargs` is left alone, and the default is skipped where
2993 torchcodec (or its FFmpeg runtime) is unavailable.
2994 """
2995 if self.model_config is None or self.model_config.multimodal_config is None:
2996 return
2997 mm_config = self.model_config.multimodal_config
2998
2999 ec_config = self.ec_transfer_config
3000 # An EC producer that is not also a consumer runs no forward pass and
3001 # allocates no KV cache, so frontend accelerator work has the device to
3002 # itself.
3003 if ec_config is None or not ec_config.is_encode_only:
3004 return
3005
3006 from vllm.platforms import current_platform
3007
3008 device_type = current_platform.device_type
3009 if (
3010 device_type != "cuda"
3011 or mm_config.get_mm_processor_device_type() != device_type
3012 ):
3013 return
3014
3015 # User set video backend or device explicitly
3016 video_kwargs = mm_config.media_io_kwargs.setdefault("video", {})
3017 if "backend" in video_kwargs or "device" in video_kwargs:
3018 return
3019
3020 from vllm.utils.import_utils import check_torchcodec_available
3021
3022 try:
3023 check_torchcodec_available()
3024 except (ImportError, RuntimeError):
3025 # torchcodec is not installed, or is installed without a usable
3026 # FFmpeg runtime (it raises rather than returning False).
3027 logger.info_once(
3028 "EPD encoder instance: keeping CPU video decoding because "
3029 "torchcodec is not available (needs a CUDA build with FFmpeg)."
3030 )
3031 return
3032
3033 video_kwargs["backend"] = "torchcodec"
3034 video_kwargs["device"] = device_type
3035 logger.info_once(
3036 "EPD encoder instance: decoding video with NVDEC (torchcodec device=%s).",
3037 device_type,
3038 )
3039
3040 def _validate_mm_processor_device(self) -> None:
3041 """Hand the EC config to `MultiModalConfig`, which owns the rule."""
3042 model_config = self.model_config
3043 if model_config is None:
3044 return
3045 mm_config = model_config.multimodal_config
3046 if mm_config is None:
3047 return
3048
3049 mm_config.validate_mm_processor_device(self.ec_transfer_config)
3050
3051 def _get_v2_model_runner_unsupported_features(self) -> list[str]:
3052 """Collect features not yet supported by the V2 model runner."""
3053 unsupported: list[str] = []
3054 speculative_config = self.speculative_config
3055
3056 if (
3057 self.compilation_config.pass_config.enable_sp
3058 and self.parallel_config.tensor_parallel_size > 1
3059 ):
3060 unsupported.append("sequence parallelism")
3061
3062 # V2 does not implement the external_launcher (torchrun) PP-output
3063 # broadcast that V1 uses to keep all ranks in sync (broadcast_pp_output).
3064 if (
3065 self.parallel_config.distributed_executor_backend == "external_launcher"
3066 and self.parallel_config.pipeline_parallel_size > 1
3067 ):
3068 unsupported.append("pipeline parallelism with external_launcher")
3069
3070 if speculative_config is not None:
3071 if speculative_config.method in (
3072 # https://github.com/vllm-project/vllm/pull/40704
3073 "ngram",
3074 "ngram_gpu",
3075 "suffix",
3076 "medusa",
3077 "mlp_speculator",
3078 "custom_class",
3079 ):
3080 unsupported.append(f"speculative method '{speculative_config.method}'")
3081
3082 # V2 EagleSpeculator does not support parallel_drafting (for P-Eagle).
3083 # DFlash and DSpark use parallel drafting natively in V2 via their
3084 # own speculators.
3085 if (
3086 speculative_config.parallel_drafting
3087 and speculative_config.method not in ("dflash", "dspark")
3088 ):
3089 unsupported.append("parallel drafting for EAGLE speculative decoding")
3090
3091 if self.parallel_config.use_ubatching:
3092 unsupported.extend(self._get_dbo_unsupported_features())
3093
3094 return unsupported
3095
3096 def _get_v1_model_runner_unsupported_features(self) -> list[str]:
3097 unsupported: list[str] = []
3098
3099 # PCP runtime support is implemented only by the V2 model runner.
3100 if self.parallel_config.prefill_context_parallel_size > 1:
3101 unsupported.append("prefill context parallel")
3102
3103 # Note(arpera):
3104 # MRV1 + PP>1 + async sched + structured output
3105 # does not work in vLLM. For more info see:
3106 # https://github.com/vllm-project/vllm/issues/45014
3107 # Since recently MRV1 has been deprecated then
3108 # there was decided not to fix the issue but instead
3109 # to disallow such configuration.
3110 # At the same time MRV2 works fine in this case.
3111 if (
3112 self.parallel_config.pipeline_parallel_size > 1
3113 and self.scheduler_config.async_scheduling
3114 ):
3115 unsupported.append("pipeline parallelism with async scheduling")
3116
3117 # DSpark is implemented only by the V2 GPU model runner.
3118 if self.speculative_config:
3119 if self.speculative_config.method == "dspark":
3120 unsupported.append("dspark speculative decoding")
3121 if self.speculative_config.enable_adaptive_verification:
3122 unsupported.append("adaptive draft verification")
3123
3124 # Mixed sliding/full DFlash drafts need multiple KV groups (V2 only).
3125 if self._dflash_needs_multi_kv_group():
3126 unsupported.append("mixed sliding/full dflash drafts")
3127
3128 # DFlash candidate heads exist only in the V2 speculator. On
3129 # V1 the same checkpoint drafts through DFlashProposer, which never
3130 # calls it, so the draft would degrade to DFlash1 silently.
3131 if self._is_dflash_candidate_draft():
3132 unsupported.append("DFlash candidate-head drafts")
3133
3134 if self.model_config is not None and self.model_config.is_diffusion:
3135 unsupported.append("diffusion models")
3136
3137 if self.parallel_config.enable_batch_sharded_sampling:
3138 unsupported.append("batch-sharded sampling")
3139
3140 return unsupported
3141
3142 def _validate_adaptive_verification(self) -> None:
3143 spec_config = self.speculative_config
3144 if not spec_config or not spec_config.enable_adaptive_verification:
3145 return
3146
3147 if self.lora_config is not None:
3148 # The per-token LoRA mapping is built from CPU placeholder boundaries,
3149 # while the trimmed batch's true boundaries are decided on the GPU.
3150 raise ValueError(
3151 "Adaptive verification is not currently compatible with LoRA"
3152 )
3153
3154 if not self.compilation_config.cudagraph_mode.has_full_cudagraphs():
3155 raise ValueError(
3156 "Adaptive verification requires full CUDA graphs. Use cudagraph "
3157 "mode FULL, FULL_DECODE_ONLY, or FULL_AND_PIECEWISE, or disable "
3158 "adaptive verification."
3159 )
3160
3161 if self.parallel_config.pipeline_parallel_size > 1:
3162 # Cost curves and confidences currently only exist on the last PP rank;
3163 # earlier ranks would diverge on the trimmed batch shape.
3164 # TODO: we should be able to support adaptive verification with PP by
3165 # broadcasting the cost curves and confidences to all ranks.
3166 raise ValueError(
3167 "Adaptive verification is not currently compatible "
3168 "with pipeline parallelism"
3169 )
3170
3171 def _validate_batch_sharded_sampling(self) -> None:
3172 """Validate `enable_batch_sharded_sampling` against the rest of the config."""
3173 if not self.parallel_config.enable_batch_sharded_sampling:
3174 # Default to False if not set.
3175 self.parallel_config.enable_batch_sharded_sampling = False
3176 return
3177
3178 blockers: list[str] = []
3179 tp_size = self.parallel_config.tensor_parallel_size
3180
3181 if tp_size <= 1:
3182 blockers.append("tensor_parallel_size is 1, so there is nothing to shard")
3183 elif self.scheduler_config.max_num_seqs < tp_size:
3184 # Requests are assigned to ranks whole, so fewer slots than ranks
3185 # leaves some ranks without work in every step.
3186 blockers.append(
3187 f"max_num_seqs ({self.scheduler_config.max_num_seqs}) is below "
3188 f"tensor_parallel_size ({tp_size})"
3189 )
3190
3191 if self.model_config is not None and self.model_config.max_logprobs < 0:
3192 # max_logprobs == -1 allows vocab-size logprob requests, which the
3193 # fixed-width logprobs gather cannot reasonably size for.
3194 blockers.append("max_logprobs is -1, allowing vocab-size logprob requests")
3195
3196 if self.model_config is not None and self.model_config.return_sampling_mask:
3197 # gather_sampler_output() drops SamplingMaskTensors: masks come back None.
3198 blockers.append(
3199 "return_sampling_mask is set and the batch-sharded gather does "
3200 "not forward sampling masks"
3201 )
3202
3203 if (
3204 self.speculative_config is not None
3205 and self.speculative_config.enable_adaptive_verification
3206 ):
3207 # Adaptive verification picks the per-request draft split on the GPU,
3208 # so cu_num_logits_np is only an upper bound, while the shard plan is
3209 # built from that CPU array. The two disagree once the budget binds.
3210 # TODO(TheEpicDolphin): Support adaptive verification with batch-sharded
3211 # sampling.
3212 blockers.append(
3213 "it does not yet work with adaptive verification, which decides "
3214 "the per-request logits counts on the GPU, where the CPU-side "
3215 "shard plan cannot see them"
3216 )
3217
3218 if blockers:
3219 raise ValueError(
3220 "Batch-sharded sampling was explicitly enabled via "
3221 "the --enable-batch-sharded-sampling flag, but is not supported "
3222 "in this configuration for the following reason(s): "
3223 f"{'; '.join(blockers)}."
3224 )
3225
3226 def _get_dbo_unsupported_features(self) -> list[str]:
3227 """Collect what the V2 model runner cannot combine with DBO.
3228
3229 The V2 runner microbatches a plain decoder forward pass. Anything that
3230 slices or replays the batch differently (drafting, adapters, pipeline
3231 stages, context parallelism, encoders) is not handled yet.
3232 """
3233 # TODO: DBO with model runner V2 is under development.
3234 # It should be enabled with explicit VLLM_USE_V2_MODEL_RUNNER environ.
3235 # Remove it when stable.
3236 if envs.VLLM_USE_V2_MODEL_RUNNER is None:
3237 return ["dual batch overlap"]
3238
3239 unsupported: list[str] = []
3240 model_config = self.model_config
3241 parallel_config = self.parallel_config
3242
3243 if self.lora_config is not None:
3244 unsupported.append("dual batch overlap with LoRA")
3245 if self.speculative_config is not None:
3246 unsupported.append("dual batch overlap with speculative decoding")
3247 if parallel_config.pipeline_parallel_size > 1:
3248 unsupported.append("dual batch overlap with pipeline parallelism")
3249 if (
3250 parallel_config.decode_context_parallel_size > 1
3251 or parallel_config.prefill_context_parallel_size > 1
3252 ):
3253 unsupported.append("dual batch overlap with context parallelism")
3254 if model_config is not None and (
3255 model_config.is_multimodal_model or model_config.is_encoder_decoder
3256 ):
3257 unsupported.append("dual batch overlap with multimodal models")
3258 if model_config is not None and model_config.is_hybrid:
3259 unsupported.append("dual batch overlap with hybrid models")
3260 if self.compilation_config.cudagraph_mode == CUDAGraphMode.PIECEWISE:
3261 # DBO captures FULL graphs only.
3262 unsupported.append("dual batch overlap with PIECEWISE CUDA graphs")
3263 if self.is_mm_encoder_only:
3264 unsupported.append("dual batch overlap with encoder only models")
3265
3266 return unsupported
3267
3268 def _validate_profiler_config(self) -> None:
3269 if self.profiler_config.profiler != "proton":
3270 return
3271
3272 from vllm.platforms import current_platform
3273
3274 if not current_platform.is_cuda():
3275 raise ValueError("The Proton profiler currently supports NVIDIA CUDA only")
3276 if (
3277 self.profiler_config.proton_graph_attribution
3278 and not self.use_v2_model_runner
3279 ):
3280 raise ValueError(
3281 "Proton CUDA graph attribution requires the V2 model runner."
3282 )
3283
3284 has_cuda_graphs = (
3285 self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
3286 or (
3287 self.compilation_config.cudagraph_mm_encoder
3288 and not (self.model_config and self.model_config.enforce_eager)
3289 )
3290 )
3291 if not has_cuda_graphs:
3292 return
3293 mode = self.profiler_config.proton_mode
3294 if mode and mode.split(":", 1)[0].lower() == "pcsampling":
3295 raise ValueError(
3296 "Proton PC sampling requires CUDA graphs to be disabled. "
3297 "Use --enforce-eager."
3298 )
3299 if not self.profiler_config.proton_graph_attribution:
3300 raise ValueError(
3301 "Proton profiling with CUDA graphs requires "
3302 "proton_graph_attribution=True to capture replayed kernels. "
3303 "Enable attribution or use --enforce-eager."
3304 )
3305
3306 def _disable_cudagraphs_for_v2_stock_torch_compile(self) -> None:
3307 """Run stock torch.compile without CUDA graphs in Model Runner V2.
3308
3309 V1 never wraps a stock-compiled model in CUDAGraphWrapper, so it runs
3310 without CUDA graphs. V2's CUDA graph manager would otherwise capture
3311 FULL graphs around the stock-compiled model, so disable them to match.
3312 """
3313 compilation_config = self.compilation_config
3314 if (
3315 compilation_config.mode != CompilationMode.STOCK_TORCH_COMPILE
3316 or compilation_config.cudagraph_mode == CUDAGraphMode.NONE
3317 ):
3318 return
3319 logger.info_once(
3320 "CUDA graphs are not supported with stock torch.compile in Model "
3321 "Runner V2. Overriding cudagraph_mode %s to NONE.",
3322 compilation_config.cudagraph_mode.name,
3323 )
3324 compilation_config.cudagraph_mode = CUDAGraphMode.NONE
3325 compilation_config.max_cudagraph_capture_size = 0
3326 compilation_config.cudagraph_capture_sizes = []
3327
3328 def _validate_v2_model_runner(self) -> None:
3329 """Check for features not yet supported by the V2 model runner."""
3330 if not HAS_TRITON:
3331 raise ValueError("Model Runner V2 requires Triton.")
3332
3333 unsupported = self._get_v2_model_runner_unsupported_features()
3334 if unsupported:
3335 raise ValueError(
3336 f"Model Runner V2 does not yet support: {', '.join(unsupported)}"
3337 )
3338
3339 def _validate_v1_model_runner(self) -> None:
3340 unsupported = self._get_v1_model_runner_unsupported_features()
3341 if unsupported:
3342 raise ValueError(
3343 f"Model Runner V1 does not support: {', '.join(unsupported)}"
3344 )
3345
3346 def adjust_dcp_kv_cache_interleave_size(
3347 self, kv_cache_config: "KVCacheConfig"
3348 ) -> None:
3349 """Normalize DCP interleave size against block_size for NIXL P/D.
3350
3351 Called by each worker (via ensure_kv_transfer_initialized), once it knows its
3352 own final block_size via kv_cache_config.
3353 """
3354 dcp_size = self.parallel_config.decode_context_parallel_size
3355 if dcp_size <= 1:
3356 return
3357 if self.parallel_config.dcp_kv_cache_interleave_size > 1 and (
3358 self.parallel_config.cp_kv_cache_interleave_size
3359 != self.parallel_config.dcp_kv_cache_interleave_size
3360 ):
3361 self.parallel_config.cp_kv_cache_interleave_size = (
3362 self.parallel_config.dcp_kv_cache_interleave_size
3363 )
3364 logger.warning_once(
3365 "cp_kv_cache_interleave_size is overridden by dcp_kv_cache"
3366 "_interleave_size. And dcp-kv-cache-interleave-size will be "
3367 "deprecated when PCP is fully supported."
3368 )
3369
3370 if self.kv_transfer_config is None or not self.kv_transfer_config.has_connector(
3371 "NixlConnector"
3372 ):
3373 return
3374 if not self.parallel_config._allow_auto_resolve_cp_interleave_size:
3375 return
3376
3377 # Get the kernel block_size, but don't use resolve_kv_cache_block_size to avoid
3378 # scaling by dcp_size (we need the local block_size here).
3379 local_block_size = min(
3380 g.kv_cache_spec.block_size for g in kv_cache_config.kv_cache_groups
3381 )
3382 if self.parallel_config.cp_kv_cache_interleave_size != local_block_size:
3383 interleave = self.parallel_config.cp_kv_cache_interleave_size
3384 self.parallel_config.cp_kv_cache_interleave_size = local_block_size
3385 logger.info_once(
3386 "When using PD disaggregation with DCP "
3387 "(decode_context_parallel_size=%d), "
3388 "cp_kv_cache_interleave_size is automatically adjusted "
3389 "from %d to block_size %d for block-level alignment.",
3390 dcp_size,
3391 interleave,
3392 local_block_size,
3393 )
3394
3395 def validate_block_size(self) -> None:
3396 """Validate block_size against DCP and mamba constraints.
3397
3398 Called after Platform.update_block_size_for_backend() has
3399 finalised block_size.
3400 """
3401 block_size = self.cache_config.block_size
3402
3403 # Skip DCP interleave-size compatibility for NIXL P/D: the interleave
3404 # size is pinned to block_size by each worker.
3405 nixl_pd_active = (
3406 self.kv_transfer_config is not None
3407 and self.kv_transfer_config.has_connector("NixlConnector")
3408 )
3409 if self.parallel_config.decode_context_parallel_size > 1 and not nixl_pd_active:
3410 assert (
3411 self.parallel_config.cp_kv_cache_interleave_size <= block_size
3412 and block_size % self.parallel_config.cp_kv_cache_interleave_size == 0
3413 ), (
3414 f"Block_size({block_size}) should be greater "
3415 "than or equal to and divisible by cp_kv_cache_interleave_size "
3416 f"({self.parallel_config.cp_kv_cache_interleave_size})."
3417 )
3418 # Mamba cache align-mode constraints
3419 if self.cache_config.mamba_cache_mode == "align":
3420 assert not self.scheduler_config.disable_chunked_mm_input, (
3421 "Chunked MM input is required because we need the flexibility "
3422 "to schedule a multiple of block_size tokens even if they are "
3423 "in the middle of a mm input"
3424 )
3425
3426 @model_validator(mode="after")
3427 def validate_nvfp4_kv_cache_with_mla(self) -> "VllmConfig":
3428 if self.model_config is None:
3429 return self
3430 # The ds_mla layouts are MLA-only by construction; the plain nvfp4
3431 # layout (head_size//2 + head_size//16) does not apply to MLA.
3432 if (
3433 self.cache_config.cache_dtype.startswith("nvfp4")
3434 and not self.cache_config.cache_dtype.endswith("_ds_mla")
3435 and self.model_config.use_mla
3436 ):
3437 raise ValueError(
3438 "nvfp4 KV cache is not supported with MLA (Multi-head Latent "
3439 "Attention) backends. Please use a different --kv-cache-dtype "
3440 "(e.g., 'fp8', 'auto', or 'nvfp4_ds_mla' with a sparse MLA "
3441 "backend) for MLA models such as DeepSeek."
3442 )
3443 return self
3444
3445 @model_validator(mode="after")
3446 def validate_mamba_block_size(self) -> "VllmConfig":
3447 if self.model_config is None:
3448 return self
3449 mamba_block_size_is_set = (
3450 self.cache_config.mamba_block_size is not None
3451 and self.cache_config.mamba_block_size != self.model_config.max_model_len
3452 )
3453 if mamba_block_size_is_set and not self.cache_config.enable_prefix_caching:
3454 raise ValueError(
3455 "--mamba-block-size can only be set with --enable-prefix-caching"
3456 )
3457 return self
3458
3459 @model_validator(mode="after")
3460 def validate_mamba_cached_kernel(self) -> "VllmConfig":
3461 if not self.cache_config.use_replayssm:
3462 self.cache_config.use_kda_recoverssm = False
3463 return self
3464
3465 kda_architectures = (
3466 "KimiLinearForCausalLM",
3467 "KimiK3ForConditionalGeneration",
3468 )
3469 is_kda_model = (
3470 self.model_config is not None
3471 and self.model_config.architecture in kda_architectures
3472 )
3473 self.cache_config.use_kda_recoverssm = (
3474 self.num_speculative_tokens > 0 and is_kda_model
3475 )
3476 use_mamba_replayssm_spec = (
3477 self.num_speculative_tokens > 0 and not self.cache_config.use_kda_recoverssm
3478 )
3479
3480 if self.model_config is not None and not self.model_config.supports_replayssm:
3481 raise ValueError(
3482 "--use-replayssm is not supported for architecture "
3483 f"{self.model_config.architecture!r}"
3484 )
3485 if (
3486 self.mamba_config.backend == MambaBackendEnum.FLASHINFER
3487 and self.cache_config.replayssm_buffer_len > 16
3488 ):
3489 raise ValueError(
3490 "FlashInfer ReplaySSM requires --replayssm-buffer-len <= 16"
3491 )
3492 if self.cache_config.use_kda_recoverssm:
3493 if self.mamba_config.enable_stochastic_rounding:
3494 raise ValueError(
3495 "RecoverSSM supports bfloat16/float32 "
3496 "SSM state caches, not --enable-mamba-cache-stochastic-"
3497 "rounding, which requires an explicit float16 cache"
3498 )
3499 if (
3500 self.cache_config.mamba_cache_mode == "align"
3501 and not self.use_v2_model_runner
3502 ):
3503 raise ValueError(
3504 "RecoverSSM with align mode requires VLLM_USE_V2_MODEL_RUNNER=1"
3505 )
3506 if self.parallel_config.pipeline_parallel_size > 1:
3507 raise ValueError(
3508 "RecoverSSM currently requires pipeline_parallel_size=1"
3509 )
3510 if self.mamba_config.backend != MambaBackendEnum.TRITON:
3511 raise ValueError("RecoverSSM requires --mamba-backend triton")
3512 elif use_mamba_replayssm_spec:
3513 if self.cache_config.mamba_cache_mode != "none":
3514 raise ValueError(
3515 "FlashInfer ReplaySSM speculative decoding requires "
3516 "--mamba-cache-mode none"
3517 )
3518 query_len = 1 + self.num_speculative_tokens
3519 if self.cache_config.replayssm_buffer_len < query_len:
3520 raise ValueError(
3521 "FlashInfer ReplaySSM speculative decoding requires "
3522 "--replayssm-buffer-len >= 1 + num_speculative_tokens "
3523 f"({query_len}); got "
3524 f"{self.cache_config.replayssm_buffer_len}"
3525 )
3526 if self.mamba_config.backend != MambaBackendEnum.FLASHINFER:
3527 raise ValueError(
3528 "Mamba2 ReplaySSM speculative decoding requires "
3529 "--mamba-backend flashinfer"
3530 )
3531 elif self.mamba_config.backend == MambaBackendEnum.FLASHINFER:
3532 if self.cache_config.mamba_cache_mode == "align":
3533 raise ValueError(
3534 "FlashInfer ReplaySSM does not support "
3535 "--mamba-cache-mode align yet; use none"
3536 )
3537 elif self.mamba_config.backend != MambaBackendEnum.TRITON:
3538 raise ValueError(
3539 "--use-replayssm requires --mamba-backend triton or flashinfer"
3540 )
3541 elif self.use_v2_model_runner:
3542 raise ValueError(
3543 "Triton ReplaySSM requires Model Runner V1; use "
3544 "--mamba-backend flashinfer or Model Runner V1"
3545 )
3546 if (
3547 self.kv_transfer_config is not None
3548 and self.kv_transfer_config.is_kv_transfer_instance
3549 ):
3550 raise ValueError(
3551 "--use-replayssm is incompatible with KV connectors "
3552 "(P/D disaggregation, KV cache offload)"
3553 )
3554 return self
3555
3556
3557 _current_vllm_config: VllmConfig | None = None
3558 _current_prefix: str | None = None
3559
3560
3561 @contextmanager
3562 def set_current_vllm_config(
3563 vllm_config: VllmConfig, check_compile=False, prefix: str | None = None
3564 ):
3565 """Temporarily set the current vLLM config.
3566 Used during model initialization.
3567 We save the current vLLM config in a global variable,
3568 so that all modules can access it, e.g. custom ops
3569 can access the vLLM config to determine how to dispatch.
3570 """
3571 global _current_vllm_config, _current_prefix
3572 old_vllm_config = _current_vllm_config
3573 old_prefix = _current_prefix
3574 from vllm.compilation.counter import compilation_counter
3575
3576 num_models_seen = compilation_counter.num_models_seen
3577 try:
3578 # Clear the compilation config cache when context changes.
3579 # This is needed since the old config may have been accessed
3580 # and cached before the new config is set.
3581 get_cached_compilation_config.cache_clear()
3582
3583 _current_vllm_config = vllm_config
3584 _current_prefix = prefix
3585 yield
3586 except Exception:
3587 raise
3588 else:
3589 if check_compile:
3590 vllm_config.compilation_config.custom_op_log_check()
3591
3592 if (
3593 check_compile
3594 and vllm_config.compilation_config.mode == CompilationMode.VLLM_COMPILE
3595 and compilation_counter.num_models_seen == num_models_seen
3596 ):
3597 # If the model supports compilation,
3598 # compilation_counter.num_models_seen should be increased
3599 # by at least 1.
3600 # If it is not increased, it means the model does not support
3601 # compilation (does not have @support_torch_compile decorator).
3602 logger.warning(
3603 "`torch.compile` is turned on, but the model %s"
3604 " does not support it. Please open an issue on GitHub"
3605 " if you want it to be supported.",
3606 vllm_config.model_config.model,
3607 )
3608 finally:
3609 _current_vllm_config = old_vllm_config
3610 _current_prefix = old_prefix
3611 # Clear the compilation config cache when context changes
3612 get_cached_compilation_config.cache_clear()
3613
3614
3615 @lru_cache(maxsize=1)
3616 def get_cached_compilation_config():
3617 """Cache config to avoid repeated calls to get_current_vllm_config()"""
3618 return get_current_vllm_config().compilation_config
3619
3620
3621 def get_current_vllm_config() -> VllmConfig:
3622 if _current_vllm_config is None:
3623 raise AssertionError(
3624 "Current vLLM config is not set. This typically means "
3625 "get_current_vllm_config() was called outside of a "
3626 "set_current_vllm_config() context, or a CustomOp was instantiated "
3627 "at module import time or model forward time when config is not set. "
3628 "For tests that directly test custom ops/modules, use the "
3629 "'default_vllm_config' pytest fixture from tests/conftest.py."
3630 )
3631 return _current_vllm_config
3632
3633
3634 def get_current_vllm_config_or_none() -> VllmConfig | None:
3635 return _current_vllm_config
3636
3637
3638 T = TypeVar("T")
3639
3640
3641 def get_layers_from_vllm_config(
3642 vllm_config: VllmConfig,
3643 layer_type: type[T],
3644 layer_names: Iterable[str] | None = None,
3645 ) -> dict[str, T]:
3646 """Get layers from the vLLM config.
3647
3648 Args:
3649 vllm_config: The vLLM config.
3650 layer_type: The type of the layer to get.
3651 layer_names: The names of the layers to get. If None, return all layers.
3652
3653 """
3654 forward_context = vllm_config.compilation_config.static_forward_context
3655 if layer_names is None:
3656 layer_names = forward_context.keys()
3657
3658 return {
3659 layer_name: layer
3660 for layer_name in layer_names
3661 if isinstance(layer := forward_context.get(layer_name), layer_type)
3662 }