@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 77a73458e3
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 7, 00:15 UTC

Reapply [Attention] Refactor `check_and_update_config` (#35122)

Signed-off-by: Matthew Bonanni <mbonanni@redhat.com>

Matthew Bonanni committed Mar 9, 2026 at 10:17 UTC 77a73458e3ae8b5b7a2a13f78d3a6b4d39b1414d
32 files changed +311 -279
tests/kernels/attention/test_attention_selector.py
+40 -35
@@ -6,7 +6,12 @@ from unittest.mock import patch
6 import pytest
7 import torch
8
9 -from vllm.config import AttentionConfig, VllmConfig, set_current_vllm_config
9 +from vllm.config import (
10 + AttentionConfig,
11 + CacheConfig,
12 + VllmConfig,
13 + set_current_vllm_config,
14 +)
15 from vllm.platforms import current_platform
16 from vllm.platforms.cpu import CpuPlatform
17 from vllm.platforms.cuda import CudaPlatform
@@ -84,12 +89,15 @@ def test_backend_selection(
89 """Test attention backend selection with valid device-backend pairs."""
90 # Create AttentionConfig with the specified backend
91 attention_config = AttentionConfig(backend=AttentionBackendEnum[name])
87 - vllm_config = VllmConfig(attention_config=attention_config)
92 + cache_config = CacheConfig(block_size=block_size)
93 + vllm_config = VllmConfig(
94 + attention_config=attention_config, cache_config=cache_config
95 + )
96
97 with set_current_vllm_config(vllm_config):
98 if device == "cpu":
99 with patch("vllm.platforms.current_platform", CpuPlatform()):
92 - backend = get_attn_backend(16, torch.float16, None, block_size)
100 + backend = get_attn_backend(16, torch.float16, None)
101 assert backend.get_name() == "CPU_ATTN"
102
103 elif device == "hip":
@@ -104,20 +112,16 @@ def test_backend_selection(
112 if name == "TRITON_MLA" and block_size == 1:
113 # TRITON_MLA doesn't support block_size == 1
114 with pytest.raises(ValueError):
107 - get_attn_backend(
108 - 576, torch.float16, None, block_size, use_mla=use_mla
109 - )
115 + get_attn_backend(576, torch.float16, None, use_mla=use_mla)
116 else:
117 # Valid backend-block_size combination
118 backend = get_attn_backend(
113 - 576, torch.float16, None, block_size, use_mla=use_mla
119 + 576, torch.float16, None, use_mla=use_mla
120 )
121 expected = name
122 assert backend.get_name() == expected
123 else:
118 - backend = get_attn_backend(
119 - 32, torch.float16, None, block_size, use_mla=use_mla
120 - )
124 + backend = get_attn_backend(32, torch.float16, None, use_mla=use_mla)
125 expected = "ROCM_ATTN"
126 assert backend.get_name() == expected
127
@@ -141,7 +145,7 @@ def test_backend_selection(
145 if capability[0] != 10:
146 pytest.skip("CUTLASS MLA is not supported on this platform")
147 backend = get_attn_backend(
144 - 576, torch.float16, None, block_size, use_mla=use_mla
148 + 576, torch.float16, None, use_mla=use_mla
149 )
150 expected = "CUTLASS_MLA"
151 assert backend.get_name() == expected
@@ -156,7 +160,7 @@ def test_backend_selection(
160 "FlashInfer MLA only supports block_size 32 or 64"
161 )
162 backend = get_attn_backend(
159 - 576, torch.float16, None, block_size, use_mla=use_mla
163 + 576, torch.float16, None, use_mla=use_mla
164 )
165 expected = "FLASHINFER_MLA"
166 assert backend.get_name() == expected
@@ -175,7 +179,6 @@ def test_backend_selection(
179 576,
180 torch.float16,
181 None,
178 - block_size,
182 use_mla=use_mla,
183 )
184 expected = name
@@ -190,27 +193,23 @@ def test_backend_selection(
193 "FlashAttention MLA not supported on this platform"
194 )
195 backend = get_attn_backend(
193 - 576, torch.float16, None, block_size, use_mla=use_mla
196 + 576, torch.float16, None, use_mla=use_mla
197 )
198 expected = "FLASH_ATTN_MLA"
199 assert backend.get_name() == expected
200 else:
201 # TRITON_MLA or other fallback
202 backend = get_attn_backend(
200 - 576, torch.float16, None, block_size, use_mla=use_mla
203 + 576, torch.float16, None, use_mla=use_mla
204 )
205 expected = "TRITON_MLA"
206 assert backend.get_name() == expected
207 elif name == "FLASHINFER":
205 - backend = get_attn_backend(
206 - 64, torch.float16, None, block_size, use_mla=use_mla
207 - )
208 + backend = get_attn_backend(64, torch.float16, None, use_mla=use_mla)
209 expected = "FLASHINFER"
210 assert backend.get_name() == expected
211 elif name == "FLASH_ATTN":
211 - backend = get_attn_backend(
212 - 32, torch.float16, None, block_size, use_mla=use_mla
213 - )
212 + backend = get_attn_backend(32, torch.float16, None, use_mla=use_mla)
213 expected = "FLASH_ATTN"
214 assert backend.get_name() == expected
215
@@ -224,12 +223,12 @@ def test_fp32_fallback(device: str):
223 with set_current_vllm_config(vllm_config):
224 if device == "cpu":
225 with patch("vllm.platforms.current_platform", CpuPlatform()):
227 - backend = get_attn_backend(16, torch.float32, None, 16)
226 + backend = get_attn_backend(16, torch.float32, None)
227 assert backend.get_name() == "CPU_ATTN"
228
229 elif device == "cuda":
230 with patch("vllm.platforms.current_platform", CudaPlatform()):
232 - backend = get_attn_backend(16, torch.float32, None, 16)
231 + backend = get_attn_backend(16, torch.float32, None)
232 assert backend.get_name() == "FLEX_ATTENTION"
233
234
@@ -241,35 +240,40 @@ def test_flash_attn(monkeypatch: pytest.MonkeyPatch):
240 )
241
242 attention_config = AttentionConfig(backend=AttentionBackendEnum.FLASH_ATTN)
244 - vllm_config = VllmConfig(attention_config=attention_config)
243 + cache_config = CacheConfig(block_size=16)
244 + vllm_config = VllmConfig(
245 + attention_config=attention_config, cache_config=cache_config
246 + )
247
248 with set_current_vllm_config(vllm_config):
249 # Unsupported CUDA arch
250 monkeypatch.setattr(torch.cuda, "get_device_capability", lambda _=None: (7, 5))
249 - backend = get_attn_backend(16, torch.float16, None, 16)
251 + backend = get_attn_backend(16, torch.float16, None)
252 assert backend.get_name() != "FLASH_ATTN"
253
254 # Reset the monkeypatch for subsequent tests
255 monkeypatch.undo()
256
257 # Unsupported data type
256 - backend = get_attn_backend(16, torch.float8_e4m3fn, None, 16)
258 + backend = get_attn_backend(16, torch.float8_e4m3fn, None)
259 assert backend.get_name() != "FLASH_ATTN"
260
261 # Unsupported kv cache data type
260 - backend = get_attn_backend(16, torch.float16, "fp8", 16)
262 + backend = get_attn_backend(16, torch.float16, "fp8")
263 assert backend.get_name() != "FLASH_ATTN"
264
265 # Unsupported block size
264 - backend = get_attn_backend(16, torch.float16, None, 8)
266 + vllm_config.cache_config.block_size = 8
267 + backend = get_attn_backend(16, torch.float16, None)
268 assert backend.get_name() != "FLASH_ATTN"
269
270 # flash-attn is not installed
271 import sys
272
273 + vllm_config.cache_config.block_size = 16
274 original_module = sys.modules.get("vllm_flash_attn")
275 monkeypatch.setitem(sys.modules, "vllm_flash_attn", None)
272 - backend = get_attn_backend(16, torch.float16, None, 16)
276 + backend = get_attn_backend(16, torch.float16, None)
277 assert backend.get_name() != "FLASH_ATTN"
278
279 # Restore the original module if it existed
@@ -279,7 +283,7 @@ def test_flash_attn(monkeypatch: pytest.MonkeyPatch):
283 monkeypatch.delitem(sys.modules, "vllm_flash_attn", raising=False)
284
285 # Unsupported head size
282 - backend = get_attn_backend(17, torch.float16, None, 16)
286 + backend = get_attn_backend(17, torch.float16, None)
287 assert backend.get_name() != "FLASH_ATTN"
288
289
@@ -320,7 +324,7 @@ def test_auto_backend_selection_behavior():
324 set_current_vllm_config(vllm_config_auto),
325 patch("vllm.platforms.current_platform", CpuPlatform()),
326 ):
323 - backend_auto = get_attn_backend(16, torch.float16, None, 16)
327 + backend_auto = get_attn_backend(16, torch.float16, None)
328
329 _cached_get_attn_backend.cache_clear()
330
@@ -328,7 +332,7 @@ def test_auto_backend_selection_behavior():
332 set_current_vllm_config(vllm_config_none),
333 patch("vllm.platforms.current_platform", CpuPlatform()),
334 ):
331 - backend_none = get_attn_backend(16, torch.float16, None, 16)
335 + backend_none = get_attn_backend(16, torch.float16, None)
336
337 # Both should select the same backend
338 assert backend_auto.get_name() == backend_none.get_name()
@@ -358,7 +362,10 @@ def test_per_head_quant_scales_backend_selection(
362 backend=AttentionBackendEnum[backend_name],
363 flash_attn_version=flash_attn_version,
364 )
361 - vllm_config = VllmConfig(attention_config=attention_config)
365 + cache_config = CacheConfig(block_size=64)
366 + vllm_config = VllmConfig(
367 + attention_config=attention_config, cache_config=cache_config
368 + )
369
370 with (
371 set_current_vllm_config(vllm_config),
@@ -376,7 +383,6 @@ def test_per_head_quant_scales_backend_selection(
383 head_size=128,
384 dtype=torch.float16,
385 kv_cache_dtype="fp8",
379 - block_size=64,
386 use_per_head_quant_scales=True,
387 )
388 assert backend.get_name() == backend_name
@@ -386,7 +392,6 @@ def test_per_head_quant_scales_backend_selection(
392 head_size=128,
393 dtype=torch.float16,
394 kv_cache_dtype="fp8",
389 - block_size=64,
395 use_per_head_quant_scales=True,
396 )
397 assert backend_name in str(exc_info.value)
tests/models/multimodal/processing/test_tensor_schema.py
+4 -1
@@ -13,6 +13,7 @@ import torch.nn as nn
13 from PIL import Image
14
15 from vllm.config import ModelConfig, VllmConfig, set_current_vllm_config
16 +from vllm.config.cache import CacheConfig
17 from vllm.config.multimodal import (
18 AudioDummyOptions,
19 BaseDummyOptions,
@@ -131,7 +132,9 @@ def initialize_dummy_model(
132 ):
133 temp_file = tempfile.mkstemp()[1]
134 current_device = torch.get_default_device()
134 - vllm_config = VllmConfig(model_config=model_config)
135 + vllm_config = VllmConfig(
136 + model_config=model_config, cache_config=CacheConfig(block_size=16)
137 + )
138 with set_current_vllm_config(vllm_config=vllm_config):
139 init_distributed_environment(
140 world_size=1,
tests/v1/spec_decode/test_eagle.py
+1 -1
@@ -80,7 +80,7 @@ def _create_proposer(
80 device = current_platform.device_type
81 vllm_config = VllmConfig(
82 model_config=model_config,
83 - cache_config=CacheConfig(),
83 + cache_config=CacheConfig(block_size=16),
84 speculative_config=speculative_config,
85 device_config=DeviceConfig(device=device),
86 parallel_config=ParallelConfig(),
vllm/config/cache.py
+26 -8
@@ -2,16 +2,15 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from dataclasses import field
5 -from typing import Literal
5 +from typing import ClassVar, Literal
6
7 -from pydantic import Field, SkipValidation, field_validator
7 +from pydantic import Field, SkipValidation, field_validator, model_validator
8
9 from vllm.config.utils import config
10 from vllm.logger import init_logger
11
12 logger = init_logger(__name__)
13
14 -BlockSize = Literal[1, 8, 16, 32, 64, 128, 256]
14 CacheDType = Literal[
15 "auto",
16 "bfloat16",
@@ -31,12 +30,13 @@ KVOffloadingBackend = Literal["native", "lmcache"]
30 class CacheConfig:
31 """Configuration for the KV cache."""
32
34 - block_size: SkipValidation[BlockSize] = None # type: ignore[assignment]
35 - """Size of a contiguous cache block in number of tokens.
33 + DEFAULT_BLOCK_SIZE: ClassVar[int] = 16
34
37 - This config has no static default. If left unspecified by the user, it will
38 - be set in `Platform.check_and_update_config()` based on the current
39 - platform."""
35 + block_size: SkipValidation[int] = None # type: ignore[assignment]
36 + """Size of a contiguous cache block in number of tokens.
37 + Accepts None (meaning "use default"). After construction, always int."""
38 + user_specified_block_size: bool = field(default=False, init=False)
39 + """Whether block_size was explicitly provided. Derived automatically."""
40 gpu_memory_utilization: float = Field(default=0.9, gt=0, le=1)
41 """The fraction of GPU memory to be used for the model executor, which can
42 range from 0 to 1. For example, a value of 0.5 would imply 50% GPU memory
@@ -169,6 +169,8 @@ class CacheConfig:
169 "prefix_caching_hash_algo",
170 "cpu_kvcache_space_bytes",
171 "mamba_page_size_padded",
172 + "user_specified_block_size",
173 + "_block_size_resolved",
174 # Post-init/derived counters
175 "num_gpu_blocks",
176 "num_cpu_blocks",
@@ -186,6 +188,22 @@ class CacheConfig:
188 # metrics info
189 return {key: str(value) for key, value in self.__dict__.items()}
190
191 + _block_size_resolved: bool = field(default=False, init=False)
192 + """Guard against pydantic re-running _apply_block_size_default."""
193 +
194 + @model_validator(mode="after")
195 + def _apply_block_size_default(self) -> "CacheConfig":
196 + # Pydantic re-runs validators when CacheConfig is nested inside
197 + # another pydantic model (e.g. VllmConfig). Guard against that.
198 + if self._block_size_resolved:
199 + return self
200 + object.__setattr__(self, "_block_size_resolved", True)
201 + if self.block_size is None:
202 + object.__setattr__(self, "block_size", self.DEFAULT_BLOCK_SIZE)
203 + else:
204 + object.__setattr__(self, "user_specified_block_size", True)
205 + return self
206 +
207 @field_validator("cache_dtype", mode="after")
208 @classmethod
209 def _validate_cache_dtype(cls, cache_dtype: CacheDType) -> CacheDType:
vllm/config/vllm.py
+47 -46
@@ -1026,32 +1026,6 @@ class VllmConfig:
1026 )
1027 current_platform.check_and_update_config(self)
1028
1029 - # If DCP, ensure the block size is right.
1030 - if self.parallel_config.decode_context_parallel_size > 1:
1031 - if self.parallel_config.dcp_kv_cache_interleave_size > 1 and (
1032 - self.parallel_config.cp_kv_cache_interleave_size
1033 - != self.parallel_config.dcp_kv_cache_interleave_size
1034 - ):
1035 - self.parallel_config.cp_kv_cache_interleave_size = (
1036 - self.parallel_config.dcp_kv_cache_interleave_size
1037 - )
1038 - logger.warning_once(
1039 - "cp_kv_cache_interleave_size is overridden by dcp_kv_cache"
1040 - "_interleave_size. And dcp-kv-cache-interleave-size will be "
1041 - "deprecated when PCP is fully supported."
1042 - )
1043 - assert (
1044 - self.parallel_config.cp_kv_cache_interleave_size
1045 - <= self.cache_config.block_size
1046 - and self.cache_config.block_size
1047 - % self.parallel_config.cp_kv_cache_interleave_size
1048 - == 0
1049 - ), (
1050 - f"Block_size({self.cache_config.block_size}) should be greater "
1051 - "than or equal to and divisible by cp_kv_cache_interleave_size "
1052 - f"({self.parallel_config.cp_kv_cache_interleave_size})."
1053 - )
1054 -
1029 # Do this after all the updates to compilation_config.mode
1030 effective_dp_size = (
1031 self.parallel_config.data_parallel_size
@@ -1219,26 +1193,6 @@ class VllmConfig:
1193 # Default to enable HMA if not explicitly disabled by user or logic above.
1194 self.scheduler_config.disable_hybrid_kv_cache_manager = False
1195
1222 - if self.cache_config.mamba_cache_mode == "align":
1223 - assert (
1224 - self.cache_config.block_size
1225 - <= self.scheduler_config.max_num_batched_tokens
1226 - ), (
1227 - "In Mamba cache align mode, block_size "
1228 - f"({self.cache_config.block_size}) must be <= "
1229 - "max_num_batched_tokens "
1230 - f"({self.scheduler_config.max_num_batched_tokens})."
1231 - )
1232 - if self.scheduler_config.long_prefill_token_threshold > 0:
1233 - assert (
1234 - self.scheduler_config.long_prefill_token_threshold
1235 - >= self.cache_config.block_size
1236 - )
1237 - assert not self.scheduler_config.disable_chunked_mm_input, (
1238 - "Chunked MM input is required because we need the flexibility to "
1239 - "schedule a multiple of block_size tokens even if they are in the "
1240 - "middle of a mm input"
1241 - )
1196 if self.compilation_config.debug_dump_path:
1197 self.compilation_config.debug_dump_path = (
1198 self.compilation_config.debug_dump_path.absolute().expanduser()
@@ -1673,6 +1627,53 @@ class VllmConfig:
1627 f"compilation_config={self.compilation_config!r}"
1628 )
1629
1630 + def validate_block_size(self) -> None:
1631 + """Validate block_size against DCP and mamba constraints.
1632 +
1633 + Called after Platform.update_block_size_for_backend() has
1634 + finalised block_size.
1635 + """
1636 + block_size = self.cache_config.block_size
1637 +
1638 + # DCP interleave-size compatibility
1639 + if self.parallel_config.decode_context_parallel_size > 1:
1640 + if self.parallel_config.dcp_kv_cache_interleave_size > 1 and (
1641 + self.parallel_config.cp_kv_cache_interleave_size
1642 + != self.parallel_config.dcp_kv_cache_interleave_size
1643 + ):
1644 + self.parallel_config.cp_kv_cache_interleave_size = (
1645 + self.parallel_config.dcp_kv_cache_interleave_size
1646 + )
1647 + logger.warning_once(
1648 + "cp_kv_cache_interleave_size is overridden by dcp_kv_cache"
1649 + "_interleave_size. And dcp-kv-cache-interleave-size will be "
1650 + "deprecated when PCP is fully supported."
1651 + )
1652 + assert (
1653 + self.parallel_config.cp_kv_cache_interleave_size <= block_size
1654 + and block_size % self.parallel_config.cp_kv_cache_interleave_size == 0
1655 + ), (
1656 + f"Block_size({block_size}) should be greater "
1657 + "than or equal to and divisible by cp_kv_cache_interleave_size "
1658 + f"({self.parallel_config.cp_kv_cache_interleave_size})."
1659 + )
1660 +
1661 + # Mamba cache align-mode constraints
1662 + if self.cache_config.mamba_cache_mode == "align":
1663 + assert block_size <= self.scheduler_config.max_num_batched_tokens, (
1664 + "In Mamba cache align mode, block_size "
1665 + f"({block_size}) must be <= "
1666 + "max_num_batched_tokens "
1667 + f"({self.scheduler_config.max_num_batched_tokens})."
1668 + )
1669 + if self.scheduler_config.long_prefill_token_threshold > 0:
1670 + assert self.scheduler_config.long_prefill_token_threshold >= block_size
1671 + assert not self.scheduler_config.disable_chunked_mm_input, (
1672 + "Chunked MM input is required because we need the flexibility "
1673 + "to schedule a multiple of block_size tokens even if they are "
1674 + "in the middle of a mm input"
1675 + )
1676 +
1677 @model_validator(mode="after")
1678 def validate_mamba_block_size(self) -> "VllmConfig":
1679 if self.model_config is None:
vllm/distributed/kv_transfer/kv_connector/utils.py
-1
@@ -500,7 +500,6 @@ def get_current_attn_backend(vllm_config: VllmConfig):
500 head_size=vllm_config.model_config.get_head_size(),
501 dtype=vllm_config.model_config.dtype,
502 kv_cache_dtype=vllm_config.cache_config.cache_dtype,
503 - block_size=vllm_config.cache_config.block_size,
503 use_mla=vllm_config.model_config.use_mla,
504 )
505 return backend
vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py
-1
@@ -726,7 +726,6 @@ class MoRIIOConnectorWorker:
726 self.model_config.get_head_size(),
727 self.model_config.dtype,
728 self.cache_config.cache_dtype,
729 - self.block_size,
729 use_mla=self.use_mla,
730 )
731
vllm/engine/arg_utils.py
+2 -3
@@ -62,7 +62,6 @@ from vllm.config import (
62 get_attr_docs,
63 )
64 from vllm.config.cache import (
65 - BlockSize,
65 CacheDType,
66 KVOffloadingBackend,
67 MambaCacheMode,
@@ -440,7 +439,7 @@ class EngineArgs:
439 max_parallel_loading_workers: int | None = (
440 ParallelConfig.max_parallel_loading_workers
441 )
443 - block_size: BlockSize = CacheConfig.block_size
442 + block_size: int | None = None
443 enable_prefix_caching: bool | None = None
444 prefix_caching_hash_algo: PrefixCachingHashAlgo = (
445 CacheConfig.prefix_caching_hash_algo
@@ -1521,7 +1520,7 @@ class EngineArgs:
1520 )
1521
1522 cache_config = CacheConfig(
1524 - block_size=self.block_size,
1523 + block_size=self.block_size, # type: ignore[arg-type]
1524 gpu_memory_utilization=self.gpu_memory_utilization,
1525 kv_cache_memory_bytes=self.kv_cache_memory_bytes,
1526 cache_dtype=resolved_cache_dtype, # type: ignore[arg-type]
vllm/model_executor/layers/attention/attention.py
-3
@@ -221,11 +221,9 @@ class Attention(nn.Module, AttentionLayerBase):
221 vllm_config = get_current_vllm_config()
222 if cache_config is not None:
223 kv_cache_dtype = cache_config.cache_dtype
224 - block_size = cache_config.block_size
224 calculate_kv_scales = cache_config.calculate_kv_scales
225 else:
226 kv_cache_dtype = "auto"
228 - block_size = 16
227 calculate_kv_scales = False
228
229 # llm-compressor mdls need to set cache_dtype to "fp8" manually.
@@ -275,7 +273,6 @@ class Attention(nn.Module, AttentionLayerBase):
273 head_size,
274 dtype,
275 kv_cache_dtype,
278 - block_size,
276 use_mla=False,
277 has_sink=self.has_sink,
278 use_mm_prefix=self.use_mm_prefix,
vllm/model_executor/layers/attention/chunked_local_attention.py
+6 -9
@@ -30,9 +30,8 @@ from vllm.v1.kv_cache_interface import (
30 def create_chunked_local_attention_backend(
31 underlying_attn_backend: AttentionBackend,
32 attention_chunk_size: int,
33 - block_size: int,
33 ) -> type[AttentionBackend]:
35 - prefix = f"ChunkedLocalAttention_{attention_chunk_size}_{block_size}_"
34 + prefix = f"ChunkedLocalAttention_{attention_chunk_size}_"
35
36 underlying_builder = underlying_attn_backend.get_builder_cls()
37 assert issubclass(underlying_builder, AttentionMetadataBuilder)
@@ -55,7 +54,9 @@ def create_chunked_local_attention_backend(
54 fast_build: bool = False,
55 ):
56 cm, make_virtual_batches_block_table = make_local_attention_virtual_batches(
58 - attention_chunk_size, common_attn_metadata, block_size
57 + attention_chunk_size,
58 + common_attn_metadata,
59 + self.kv_cache_spec.block_size,
60 )
61 metadata = super().build(common_prefix_len, cm, fast_build)
62 metadata.make_virtual_batches_block_table = make_virtual_batches_block_table
@@ -94,16 +95,12 @@ class ChunkedLocalAttention(Attention):
95 dtype = torch.get_default_dtype()
96 if cache_config is not None:
97 kv_cache_dtype = cache_config.cache_dtype
97 - block_size = cache_config.block_size
98 else:
99 kv_cache_dtype = "auto"
100 - block_size = 16
100
102 - underlying_attn_backend = get_attn_backend(
103 - head_size, dtype, kv_cache_dtype, block_size
104 - )
101 + underlying_attn_backend = get_attn_backend(head_size, dtype, kv_cache_dtype)
102 attn_backend = create_chunked_local_attention_backend(
106 - underlying_attn_backend, attention_chunk_size, block_size
103 + underlying_attn_backend, attention_chunk_size
104 )
105
106 super().__init__(
vllm/model_executor/layers/attention/cross_attention.py
-3
@@ -188,10 +188,8 @@ class CrossAttention(Attention):
188
189 if cache_config is not None:
190 kv_cache_dtype = cache_config.cache_dtype
191 - block_size = cache_config.block_size
191 else:
192 kv_cache_dtype = "auto"
194 - block_size = 16
193
194 if attn_type is not None:
195 assert attn_type == AttentionType.ENCODER_DECODER, (
@@ -202,7 +200,6 @@ class CrossAttention(Attention):
200 head_size,
201 dtype,
202 kv_cache_dtype,
205 - block_size,
203 attn_type=AttentionType.ENCODER_DECODER,
204 )
205 attn_backend = create_cross_attention_backend(underlying_attn_backend)
vllm/model_executor/layers/attention/encoder_only_attention.py
-3
@@ -66,16 +66,13 @@ class EncoderOnlyAttention(Attention):
66
67 if cache_config is not None:
68 kv_cache_dtype = cache_config.cache_dtype
69 - block_size = cache_config.block_size
69 else:
70 kv_cache_dtype = "auto"
72 - block_size = 16
71
72 underlying_attn_backend = get_attn_backend(
73 head_size,
74 dtype,
75 kv_cache_dtype,
78 - block_size,
76 attn_type=AttentionType.ENCODER_ONLY,
77 )
78
vllm/model_executor/layers/attention/mla_attention.py
+12 -8
@@ -323,11 +323,9 @@ class MLAAttention(nn.Module, AttentionLayerBase):
323
324 if cache_config is not None:
325 kv_cache_dtype = cache_config.cache_dtype
326 - block_size = cache_config.block_size
326 calculate_kv_scales = cache_config.calculate_kv_scales
327 else:
328 kv_cache_dtype = "auto"
330 - block_size = 16
329 calculate_kv_scales = False
330 self.quant_config = quant_config
331
@@ -336,7 +334,6 @@ class MLAAttention(nn.Module, AttentionLayerBase):
334 self.head_size,
335 dtype,
336 kv_cache_dtype,
339 - block_size,
337 use_mla=True,
338 use_sparse=use_sparse,
339 num_heads=self.num_heads,
@@ -449,17 +446,24 @@ class MLAAttention(nn.Module, AttentionLayerBase):
446 )
447
448 # Attributes for forward_impl method
452 - self.chunked_prefill_workspace_size = (
453 - MLACommonMetadataBuilder.determine_chunked_prefill_workspace_size(
454 - get_current_vllm_config()
455 - )
456 - )
449 + self._vllm_config = get_current_vllm_config()
450 + self._chunked_prefill_workspace_size: int | None = None
451 self._decode_concat_quant_fp8_op = _DecodeConcatQuantFP8(
452 static=True,
453 group_shape=GroupShape.PER_TENSOR,
454 compile_native=True,
455 )
456
457 + @property
458 + def chunked_prefill_workspace_size(self) -> int:
459 + if self._chunked_prefill_workspace_size is None:
460 + self._chunked_prefill_workspace_size = (
461 + MLACommonMetadataBuilder.determine_chunked_prefill_workspace_size(
462 + self._vllm_config
463 + )
464 + )
465 + return self._chunked_prefill_workspace_size
466 +
467 def forward(
468 self,
469 q: torch.Tensor,
vllm/model_executor/layers/attention/static_sink_attention.py
+3 -8
@@ -126,17 +126,13 @@ class StaticSinkAttention(Attention, CustomOp):
126
127 if cache_config is not None:
128 kv_cache_dtype = cache_config.cache_dtype
129 - block_size = cache_config.block_size
129 else:
130 kv_cache_dtype = "auto"
132 - block_size = 16
131
132 if attn_backend is not None:
133 underlying_attn_backend = attn_backend
134 else:
137 - underlying_attn_backend = get_attn_backend(
138 - head_size, dtype, kv_cache_dtype, block_size
139 - )
135 + underlying_attn_backend = get_attn_backend(head_size, dtype, kv_cache_dtype)
136 attn_backend = create_static_sink_attention_backend(
137 underlying_attn_backend, # type: ignore[arg-type]
138 sink_len=sink_len,
@@ -153,7 +149,6 @@ class StaticSinkAttention(Attention, CustomOp):
149 CustomOp.__init__(self)
150
151 self.sink_len = sink_len
156 - self.block_size = block_size
152 self.sink_populated = False
153 self.sink_key = None
154 self.sink_value = None
@@ -212,12 +207,12 @@ class StaticSinkAttention(Attention, CustomOp):
207
208 def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
209 # Block size may get updated after model loading, refresh it
215 - block_size = vllm_config.cache_config.block_size
210 + self.block_size = vllm_config.cache_config.block_size
211 # Should not be called for enc-dec or encoder-only attention.
212 assert self.attn_type == AttentionType.DECODER
213
214 return SinkFullAttentionSpec(
220 - block_size=block_size,
215 + block_size=self.block_size,
216 num_kv_heads=self.num_kv_heads,
217 head_size=self.head_size,
218 head_size_v=self.head_size_v,
vllm/model_executor/models/config.py
+3 -4
@@ -217,10 +217,9 @@ class HybridAttentionMambaModelConfig(VerifyAndUpdateConfig):
217 mamba_page_size, kernel_block_alignment_size * attn_page_size_1_token
218 )
219
220 - # override attention block size if either (a) the
221 - # user has not set it or (b) the user has set it
222 - # too small.
223 - if cache_config.block_size is None or cache_config.block_size < attn_block_size:
220 + # override attention block size if it is too small,
221 + # even if the user has explicitly set it
222 + if cache_config.block_size < attn_block_size:
223 cache_config.block_size = attn_block_size
224 logger.info(
225 "Setting attention block size to %d tokens "
vllm/model_executor/models/whisper_causal.py
-3
@@ -290,16 +290,13 @@ class WhisperCausalAttentionWithBlockPooling(Attention):
290
291 if cache_config is not None:
292 kv_cache_dtype = cache_config.cache_dtype
293 - block_size = cache_config.block_size
293 else:
294 kv_cache_dtype = "auto"
296 - block_size = 16
295
296 underlying_attn_backend = get_attn_backend(
297 head_size,
298 dtype,
299 kv_cache_dtype,
302 - block_size,
300 attn_type=attn_type,
301 )
302 attn_backend = create_whisper_attention_backend_with_block_pooling(
vllm/platforms/cpu.py
+7 -1
@@ -185,7 +185,7 @@ class CpuPlatform(Platform):
185
186 cache_config = vllm_config.cache_config
187
188 - if cache_config.block_size is None:
188 + if not cache_config.user_specified_block_size:
189 cache_config.block_size = 128
190
191 if cache_config.block_size % 32 != 0:
@@ -361,6 +361,12 @@ class CpuPlatform(Platform):
361 vllm_config.scheduler_config.DEFAULT_MAX_NUM_BATCHED_TOKENS,
362 )
363
364 + @classmethod
365 + def update_block_size_for_backend(cls, vllm_config: "VllmConfig") -> None:
366 + # TODO: CPU still sets block_size in check_and_update_config.
367 + # Move that logic here so block_size is chosen by the backend.
368 + pass
369 +
370 @classmethod
371 def get_allowed_cpu_core_node_list(cls) -> tuple[list[int], list[LogicalCPUInfo]]:
372 assert platform.system() == "Linux"
vllm/platforms/cuda.py
+29 -117
@@ -166,122 +166,12 @@ class CudaPlatformBase(Platform):
166
167 @classmethod
168 def check_and_update_config(cls, vllm_config: "VllmConfig") -> None:
169 - from vllm.v1.attention.backends.registry import AttentionBackendEnum
170 -
169 parallel_config = vllm_config.parallel_config
170 model_config = vllm_config.model_config
171
172 if parallel_config.worker_cls == "auto":
173 parallel_config.worker_cls = "vllm.v1.worker.gpu_worker.Worker"
174
177 - cache_config = vllm_config.cache_config
178 - if cache_config and cache_config.block_size is None:
179 - cache_config.block_size = 16
180 -
181 - # TODO(lucas): handle this more gracefully
182 - # Note: model_config may be None during testing
183 - # Note: block_size is initialized in
184 - # HybridAttentionMambaModelConfig.verify_and_update_config
185 - # for models with both attention and mamba,
186 - # and doesn't need to be reinitialized here
187 - if (
188 - model_config is not None
189 - and model_config.use_mla
190 - and cache_config.block_size is not None
191 - ):
192 - use_sparse = hasattr(vllm_config.model_config.hf_config, "index_topk")
193 - # If `--attention-config.backend` is not set and we are using MLA,
194 - # then we default to FlashMLA backend for non-blackwell GPUs,
195 - # else we default to CutlassMLA. For each case, we force the
196 - # required block_size.
197 - use_flashmla = False
198 - use_cutlass_mla = False
199 - use_flashinfer_mla = False
200 - use_flashmla_sparse = False
201 - use_flashinfer_mla_sparse = False
202 -
203 - from vllm.v1.attention.ops.flashmla import is_flashmla_dense_supported
204 -
205 - if vllm_config.attention_config.backend is None:
206 - # Default case
207 - hf_text_config = model_config.hf_text_config
208 - qk_nope_head_dim = getattr(hf_text_config, "qk_nope_head_dim", 1)
209 - if (
210 - cls.is_device_capability_family(100)
211 - and not use_sparse
212 - and qk_nope_head_dim == 128
213 - ):
214 - # Blackwell => Force FlashInfer MLA (unless sparse, i.e. DSv3.2)
215 - # and only if qk_nope_head_dim == 128 (kernel constraint)
216 - use_flashinfer_mla = True
217 - # Set the backend in AttentionConfig so it's used during
218 - # backend selection
219 - vllm_config.attention_config.backend = (
220 - AttentionBackendEnum.FLASHINFER_MLA
221 - )
222 - elif cls.is_device_capability_family(100) and not use_sparse:
223 - # Fall back to CUTLASS_MLA as 2nd priority on Blackwell
224 - use_cutlass_mla = True
225 - elif is_flashmla_dense_supported()[0]:
226 - # Non-Blackwell with FlashMLA support
227 - use_flashmla = True
228 - else:
229 - # Fallback: will use Triton MLA or other compatible backend
230 - pass
231 - else:
232 - # Forced case
233 - backend = vllm_config.attention_config.backend
234 - use_flashmla = backend == AttentionBackendEnum.FLASHMLA
235 - use_cutlass_mla = backend == AttentionBackendEnum.CUTLASS_MLA
236 - use_flashinfer_mla = backend == AttentionBackendEnum.FLASHINFER_MLA
237 - use_flashmla_sparse = backend == AttentionBackendEnum.FLASHMLA_SPARSE
238 - use_flashinfer_mla_sparse = (
239 - backend == AttentionBackendEnum.FLASHINFER_MLA_SPARSE
240 - )
241 -
242 - if (
243 - use_flashmla
244 - and is_flashmla_dense_supported()[0]
245 - and cache_config.block_size % 64 != 0
246 - ):
247 - cache_config.block_size = 64
248 - logger.info("Forcing kv cache block size to 64 for FlashMLA backend.")
249 -
250 - if use_cutlass_mla and cache_config.block_size % 128 != 0:
251 - cache_config.block_size = 128
252 - logger.info(
253 - "Forcing kv cache block size to 128 for CUTLASS_MLA backend."
254 - )
255 -
256 - if (
257 - use_flashinfer_mla
258 - and cache_config.block_size != 32
259 - and cache_config.block_size % 64 != 0
260 - ):
261 - cache_config.block_size = 64
262 - logger.info(
263 - "Forcing kv cache block size to 64 for FlashInferMLA backend."
264 - )
265 -
266 - if use_sparse:
267 - if not (use_flashmla_sparse or use_flashinfer_mla_sparse):
268 - use_flashmla_sparse = True
269 -
270 - if use_flashmla_sparse and cache_config.block_size != 64:
271 - cache_config.block_size = 64
272 - logger.info(
273 - "Forcing kv cache block size to 64 for FlashMLASparse backend."
274 - )
275 - elif use_flashinfer_mla_sparse and cache_config.block_size not in (
276 - 32,
277 - 64,
278 - ):
279 - cache_config.block_size = 64
280 - logger.info(
281 - "Forcing kv cache block size to 64 for FlashInferMLASparse "
282 - "backend."
283 - )
284 -
175 scheduler_config = vllm_config.scheduler_config
176 # Note: model_config may be None during testing
177 if (
@@ -312,10 +202,10 @@ class CudaPlatformBase(Platform):
202 num_heads: int | None = None,
203 ) -> tuple[
204 list[tuple["AttentionBackendEnum", int]],
315 - dict["AttentionBackendEnum", list[str]],
205 + dict["AttentionBackendEnum", tuple[int, list[str]]],
206 ]:
207 valid_backends_priorities = []
318 - invalid_reasons = {}
208 + invalid_reasons: dict[AttentionBackendEnum, tuple[int, list[str]]] = {}
209
210 backend_priorities = _get_backend_priorities(
211 attn_selector_config.use_mla,
@@ -332,7 +222,7 @@ class CudaPlatformBase(Platform):
222 except ImportError:
223 invalid_reasons_i = ["ImportError"]
224 if invalid_reasons_i:
335 - invalid_reasons[backend] = invalid_reasons_i
225 + invalid_reasons[backend] = (priority, invalid_reasons_i)
226 else:
227 valid_backends_priorities.append((backend, priority))
228
@@ -341,14 +231,13 @@ class CudaPlatformBase(Platform):
231 @classmethod
232 def get_attn_backend_cls(
233 cls,
344 - selected_backend: "AttentionBackendEnum",
234 + selected_backend: "AttentionBackendEnum | None",
235 attn_selector_config: "AttentionSelectorConfig",
236 num_heads: int | None = None,
237 ) -> str:
238 device_capability = cls.get_device_capability()
239 assert device_capability is not None
240
351 - attn_selector_config = attn_selector_config._replace(block_size=None)
241 # First try checking just the selected backend, if there is one.
242 if selected_backend is not None:
243 try:
@@ -370,7 +259,7 @@ class CudaPlatformBase(Platform):
259
260 # No selected backend or the selected backend is invalid,
261 # so we try finding a valid backend.
373 - valid_backends_priorities, invalid_reasons = cls.get_valid_backends(
262 + valid_backends_priorities, all_invalid_reasons = cls.get_valid_backends(
263 device_capability=device_capability,
264 attn_selector_config=attn_selector_config,
265 num_heads=num_heads,
@@ -379,7 +268,7 @@ class CudaPlatformBase(Platform):
268 "{"
269 + ", ".join(
270 f"{backend.name}: [{', '.join(reasons)}]"
382 - for backend, reasons in invalid_reasons.items()
271 + for backend, (_, reasons) in all_invalid_reasons.items()
272 )
273 + "}"
274 )
@@ -402,6 +291,29 @@ class CudaPlatformBase(Platform):
291 )
292 selected_index = sorted_indices[0]
293 selected_backend = valid_backends_priorities[selected_index][0]
294 + selected_priority = valid_backends_priorities[selected_index][1]
295 +
296 + # If the user specified --block-size (but not --attention-backend),
297 + # check whether that constraint precluded any higher-priority backends.
298 + if attn_selector_config.block_size is not None:
299 + excluded = [
300 + backend
301 + for backend, (priority, reasons) in all_invalid_reasons.items()
302 + if priority < selected_priority
303 + and reasons == ["block_size not supported"]
304 + ]
305 + if excluded:
306 + names = ", ".join(b.name for b in excluded)
307 + logger.warning(
308 + "--block-size %d precluded higher-priority backend(s) "
309 + "%s. Using %s instead, which may result in reduced "
310 + "performance. Consider removing --block-size to "
311 + "auto-select the optimal block size.",
312 + attn_selector_config.block_size,
313 + names,
314 + selected_backend.name,
315 + )
316 +
317 logger.info_once(
318 "Using %s attention backend out of potential backends: %s.",
319 selected_backend.name,
vllm/platforms/interface.py
+50
@@ -420,6 +420,56 @@ class Platform:
420 """
421 pass
422
423 + @classmethod
424 + def update_block_size_for_backend(cls, vllm_config: "VllmConfig") -> None:
425 + """
426 + Ensure block_size is compatible with the attention backend.
427 + """
428 + from vllm.config.cache import CacheConfig
429 +
430 + cache_config = vllm_config.cache_config
431 + if cache_config.user_specified_block_size:
432 + # User specified --block-size; keep it.
433 + return
434 +
435 + model_config = vllm_config.model_config
436 + # model_config may be None during testing.
437 + # Skip hybrid models — their block_size is managed by
438 + # HybridAttentionMambaModelConfig.
439 + if model_config is None or model_config.is_hybrid:
440 + cache_config.block_size = CacheConfig.DEFAULT_BLOCK_SIZE
441 + return
442 +
443 + from vllm.config.vllm import (
444 + get_layers_from_vllm_config,
445 + set_current_vllm_config,
446 + )
447 + from vllm.model_executor.layers.attention_layer_base import (
448 + AttentionLayerBase,
449 + )
450 +
451 + attn_layers = get_layers_from_vllm_config(
452 + vllm_config,
453 + AttentionLayerBase, # type: ignore[type-abstract]
454 + )
455 + if not attn_layers:
456 + cache_config.block_size = CacheConfig.DEFAULT_BLOCK_SIZE
457 + return
458 +
459 + first_layer = next(iter(attn_layers.values()))
460 + backend_cls = first_layer.get_attn_backend()
461 + with set_current_vllm_config(vllm_config):
462 + preferred = backend_cls.get_preferred_block_size(
463 + CacheConfig.DEFAULT_BLOCK_SIZE
464 + )
465 + if preferred != CacheConfig.DEFAULT_BLOCK_SIZE:
466 + logger.info(
467 + "Setting kv cache block size to %d for %s backend.",
468 + preferred,
469 + backend_cls.get_name(),
470 + )
471 + cache_config.block_size = preferred
472 +
473 @classmethod
474 def verify_model_arch(cls, model_arch: str) -> None:
475 """
vllm/platforms/rocm.py
+7 -1
@@ -687,7 +687,7 @@ class RocmPlatform(Platform):
687 )
688 compilation_config.cudagraph_mode = CUDAGraphMode.PIECEWISE
689
690 - if cache_config and cache_config.block_size is None:
690 + if cache_config and not cache_config.user_specified_block_size:
691 if (
692 envs.VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION and envs.VLLM_ROCM_USE_AITER
693 # NOTE: This block has been deprecated
@@ -707,6 +707,12 @@ class RocmPlatform(Platform):
707 if parallel_config.worker_cls == "auto":
708 parallel_config.worker_cls = "vllm.v1.worker.gpu_worker.Worker"
709
710 + @classmethod
711 + def update_block_size_for_backend(cls, vllm_config: "VllmConfig") -> None:
712 + # TODO: ROCm still sets block_size in check_and_update_config.
713 + # Move that logic here so block_size is chosen by the backend.
714 + pass
715 +
716 @classmethod
717 def verify_model_arch(cls, model_arch: str) -> None:
718 if model_arch in _ROCM_UNSUPPORTED_MODELS:
vllm/platforms/xpu.py
+7 -1
@@ -162,7 +162,7 @@ class XPUPlatform(Platform):
162 model_config = vllm_config.model_config
163 parallel_config = vllm_config.parallel_config
164 # in V1(or with chunked prefill) block_size is 64
165 - if cache_config and cache_config.block_size is None:
165 + if cache_config and not cache_config.user_specified_block_size:
166 cache_config.block_size = 64
167
168 # lazy import to avoid circular import
@@ -227,6 +227,12 @@ class XPUPlatform(Platform):
227 # ref. https://openucx.readthedocs.io/en/master/faq.html
228 os.environ["UCX_MEMTYPE_CACHE"] = "n"
229
230 + @classmethod
231 + def update_block_size_for_backend(cls, vllm_config: "VllmConfig") -> None:
232 + # TODO: XPU still sets block_size in check_and_update_config.
233 + # Move that logic here so block_size is chosen by the backend.
234 + pass
235 +
236 @classmethod
237 def support_hybrid_kv_cache(cls) -> bool:
238 return True
vllm/v1/attention/backend.py
+14 -9
@@ -4,7 +4,7 @@
4 from abc import ABC, abstractmethod
5 from dataclasses import dataclass, replace
6 from enum import Enum
7 -from typing import TYPE_CHECKING, Any, ClassVar, Generic, Protocol, TypeVar, get_args
7 +from typing import TYPE_CHECKING, Any, ClassVar, Generic, Protocol, TypeVar
8
9 import numpy as np
10 import torch
@@ -144,15 +144,9 @@ class AttentionBackend(ABC):
144
145 @classmethod
146 def supports_block_size(cls, block_size: int | None) -> bool:
147 - from vllm.config.cache import BlockSize
148 -
147 if block_size is None:
148 return True
149
152 - valid_sizes = get_args(BlockSize)
153 - if block_size not in valid_sizes:
154 - return False
155 -
150 supported_kernel_block_sizes = cls.get_supported_kernel_block_sizes()
151 if not supported_kernel_block_sizes:
152 return True
@@ -167,6 +161,17 @@ class AttentionBackend(ABC):
161 return True
162 return False
163
164 + @classmethod
165 + def get_preferred_block_size(cls, default_block_size: int) -> int:
166 + supported_sizes = cls.get_supported_kernel_block_sizes()
167 + if not supported_sizes:
168 + return default_block_size
169 +
170 + if cls.supports_block_size(default_block_size):
171 + return default_block_size
172 +
173 + return min(s.base if isinstance(s, MultipleOf) else s for s in supported_sizes)
174 +
175 @classmethod
176 def is_mla(cls) -> bool:
177 return False
@@ -210,7 +215,7 @@ class AttentionBackend(ABC):
215 head_size: int,
216 dtype: torch.dtype,
217 kv_cache_dtype: "CacheDType | None",
213 - block_size: int,
218 + block_size: int | None,
219 use_mla: bool,
220 has_sink: bool,
221 use_sparse: bool,
@@ -224,7 +229,7 @@ class AttentionBackend(ABC):
229 head_size: int,
230 dtype: torch.dtype,
231 kv_cache_dtype: "CacheDType | None",
227 - block_size: int,
232 + block_size: int | None,
233 use_mla: bool,
234 has_sink: bool,
235 use_sparse: bool,
vllm/v1/attention/backends/mla/flashattn_mla.py
+1 -1
@@ -75,7 +75,7 @@ class FlashAttnMLABackend(MLACommonBackend):
75 head_size: int,
76 dtype: torch.dtype,
77 kv_cache_dtype: CacheDType | None,
78 - block_size: int,
78 + block_size: int | None,
79 use_mla: bool,
80 has_sink: bool,
81 use_sparse: bool,
vllm/v1/attention/backends/mla/flashinfer_mla.py
+1 -1
@@ -69,7 +69,7 @@ class FlashInferMLABackend(MLACommonBackend):
69 head_size: int,
70 dtype: torch.dtype,
71 kv_cache_dtype: CacheDType | None,
72 - block_size: int,
72 + block_size: int | None,
73 use_mla: bool,
74 has_sink: bool,
75 use_sparse: bool,
vllm/v1/attention/backends/mla/flashinfer_mla_sparse.py
+1 -1
@@ -106,7 +106,7 @@ class FlashInferMLASparseBackend(AttentionBackend):
106 head_size: int,
107 dtype: torch.dtype,
108 kv_cache_dtype: CacheDType | None,
109 - block_size: int,
109 + block_size: int | None,
110 use_mla: bool,
111 has_sink: bool,
112 use_sparse: bool,
vllm/v1/attention/backends/mla/flashmla.py
+1 -1
@@ -80,7 +80,7 @@ class FlashMLABackend(MLACommonBackend):
80 head_size: int,
81 dtype: torch.dtype,
82 kv_cache_dtype: CacheDType | None,
83 - block_size: int,
83 + block_size: int | None,
84 use_mla: bool,
85 has_sink: bool,
86 use_sparse: bool,
vllm/v1/attention/selector.py
+6 -1
@@ -49,7 +49,6 @@ def get_attn_backend(
49 head_size: int,
50 dtype: torch.dtype,
51 kv_cache_dtype: str | None,
52 - block_size: int | None,
52 use_mla: bool = False,
53 has_sink: bool = False,
54 use_sparse: bool = False,
@@ -71,6 +70,12 @@ def get_attn_backend(
70
71 vllm_config = get_current_vllm_config()
72
73 + cache_config = vllm_config.cache_config
74 + if cache_config is not None and cache_config.user_specified_block_size:
75 + block_size = cache_config.block_size
76 + else:
77 + block_size = None
78 +
79 attn_selector_config = AttentionSelectorConfig(
80 head_size=head_size,
81 dtype=dtype,
vllm/v1/engine/core.py
+5 -1
@@ -122,7 +122,11 @@ class EngineCore:
122 num_gpu_blocks, num_cpu_blocks, kv_cache_config = self._initialize_kv_caches(
123 vllm_config
124 )
125 -
125 + if kv_cache_config.kv_cache_groups:
126 + vllm_config.cache_config.block_size = min(
127 + g.kv_cache_spec.block_size for g in kv_cache_config.kv_cache_groups
128 + )
129 + vllm_config.validate_block_size()
130 vllm_config.cache_config.num_gpu_blocks = num_gpu_blocks
131 vllm_config.cache_config.num_cpu_blocks = num_cpu_blocks
132 self.collective_rpc("initialize_cache", args=(num_gpu_blocks, num_cpu_blocks))
vllm/v1/executor/multiproc_executor.py
+4
@@ -42,6 +42,7 @@ from vllm.distributed.parallel_state import (
42 )
43 from vllm.envs import enable_envs_cache
44 from vllm.logger import init_logger
45 +from vllm.platforms import current_platform
46 from vllm.tracing import instrument, maybe_init_worker_tracer
47 from vllm.utils.network_utils import (
48 get_distributed_init_method,
@@ -617,6 +618,9 @@ class WorkerProc:
618 )
619 self.worker.load_model()
620
621 + # Set block size based on the attention backends
622 + current_platform.update_block_size_for_backend(vllm_config)
623 +
624 # Initialize message queues after init_device() since multi-node setups
625 # (nnodes_within_dp > 1) require distributed groups to be initialized
626 self._init_message_queues(input_shm_handle, vllm_config)
vllm/v1/executor/ray_executor.py
+5
@@ -387,6 +387,11 @@ class RayDistributedExecutor(Executor):
387 self.collective_rpc("init_device")
388 self.collective_rpc("load_model")
389
390 + def _update_block_size(worker):
391 + current_platform.update_block_size_for_backend(worker.vllm_config)
392 +
393 + self.collective_rpc(_update_block_size)
394 +
395 for pp_rank in range(self.parallel_config.pipeline_parallel_size):
396 self.pp_tp_workers.append([])
397 for tp_rank in range(self.parallel_config.tensor_parallel_size):
vllm/v1/executor/uniproc_executor.py
+2
@@ -12,6 +12,7 @@ import torch.distributed as dist
12
13 import vllm.envs as envs
14 from vllm.logger import init_logger
15 +from vllm.platforms import current_platform
16 from vllm.utils.network_utils import get_distributed_init_method, get_ip, get_open_port
17 from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
18 from vllm.v1.executor.abstract import Executor
@@ -47,6 +48,7 @@ class UniProcExecutor(Executor):
48 if not is_eep_new_worker:
49 self.driver_worker.init_device()
50 self.driver_worker.load_model()
51 + current_platform.update_block_size_for_backend(self.vllm_config)
52
53 def _distributed_args(self) -> tuple[str, int, int]:
54 """Return (distributed_init_method, rank, local_rank)."""
vllm/v1/worker/gpu_model_runner.py
+27 -7
@@ -32,6 +32,7 @@ from vllm.config import (
32 set_current_vllm_config,
33 update_config,
34 )
35 +from vllm.config.cache import CacheConfig
36 from vllm.distributed.ec_transfer import get_ec_transfer, has_ec_transfer
37 from vllm.distributed.eplb.eplb_state import EplbState
38 from vllm.distributed.kv_transfer import get_kv_transfer_group, has_kv_transfer_group
@@ -586,6 +587,11 @@ class GPUModelRunner(
587 custom_logitsprocs: Sequence[str | type[LogitsProcessor]] = (
588 tuple(logits_processors) if logits_processors is not None else ()
589 )
590 + placeholder_block_size = (
591 + self.cache_config.block_size or CacheConfig.DEFAULT_BLOCK_SIZE
592 + )
593 + self._init_block_sizes = [placeholder_block_size]
594 + self._init_kernel_block_sizes = [placeholder_block_size]
595 self.input_batch = InputBatch(
596 max_num_reqs=self.max_num_reqs,
597 # We need to use the encoder length for encoder-decoder
@@ -595,8 +601,8 @@ class GPUModelRunner(
601 device=self.device,
602 pin_memory=self.pin_memory,
603 vocab_size=self.model_config.get_vocab_size(),
598 - block_sizes=[self.cache_config.block_size],
599 - kernel_block_sizes=[self.cache_config.block_size],
604 + block_sizes=[placeholder_block_size],
605 + kernel_block_sizes=[placeholder_block_size],
606 is_spec_decode=bool(self.vllm_config.speculative_config),
607 logitsprocs=build_logitsprocs(
608 self.vllm_config,
@@ -6112,8 +6118,10 @@ class GPUModelRunner(
6118 ) -> None:
6119 """
6120 Re-initialize the input batch if the block sizes are different from
6115 - `[self.cache_config.block_size]`. This usually happens when there
6116 - are multiple KV cache groups.
6121 + what it was originally created with. This happens when the final
6122 + block size (determined after model loading) differs from the
6123 + placeholder used during __init__, or when there are multiple
6124 + KV cache groups.
6125
6126 Args:
6127 kv_cache_config: The KV cache configuration.
@@ -6138,14 +6146,17 @@ class GPUModelRunner(
6146 ) + kv_cache_group.kv_cache_spec.num_speculative_blocks
6147 max_num_blocks.append(max_num_blocks_per_req)
6148
6141 - if block_sizes != [self.cache_config.block_size] or kernel_block_sizes != [
6142 - self.cache_config.block_size
6143 - ]:
6149 + if (
6150 + block_sizes != self._init_block_sizes
6151 + or kernel_block_sizes != self._init_kernel_block_sizes
6152 + ):
6153 assert self.offload_config.uva.cpu_offload_gb == 0, (
6154 "Cannot re-initialize the input batch when CPU weight "
6155 "offloading is enabled. See https://github.com/vllm-project/vllm/pull/18298 " # noqa: E501
6156 "for more details."
6157 )
6158 + self._init_block_sizes = block_sizes
6159 + self._init_kernel_block_sizes = kernel_block_sizes
6160 self.input_batch = InputBatch(
6161 max_num_reqs=self.max_num_reqs,
6162 max_model_len=max_model_len,
@@ -6162,6 +6173,15 @@ class GPUModelRunner(
6173 is_pooling_model=self.is_pooling_model,
6174 )
6175
6176 + assert self._init_block_sizes == block_sizes, (
6177 + f"InputBatch block_sizes {self._init_block_sizes} != "
6178 + f"kv_cache block_sizes {block_sizes}"
6179 + )
6180 + assert self._init_kernel_block_sizes == kernel_block_sizes, (
6181 + f"InputBatch kernel_block_sizes {self._init_kernel_block_sizes} "
6182 + f"!= kv_cache kernel_block_sizes {kernel_block_sizes}"
6183 + )
6184 +
6185 def _allocate_kv_cache_tensors(
6186 self, kv_cache_config: KVCacheConfig
6187 ) -> dict[str, torch.Tensor]: