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 14, 05:30 UTC
[Misc] Refactor `get_kv_cache_spec` into `AttentionLayerBase` (#26587)
Signed-off-by: NickLucche <nlucches@redhat.com>
Nicolò Lucchesi committed
Oct 18, 2025 at 15:51 UTC
b26b70bec4faa3dae487db1f3eebaddd86c1d828
10 files changed
+151
-118
vllm/attention/layer.py
+53
-5
@@ -16,6 +16,7 @@ from vllm.attention.backends.registry import _Backend, backend_name_to_enum
16
from vllm.attention.selector import get_attn_backend
17
from vllm.attention.utils.kv_sharing_utils import validate_kv_sharing_target
18
from vllm.config import CacheConfig, get_current_vllm_config
19
+from vllm.config.vllm import VllmConfig
20
from vllm.distributed.kv_transfer import (
21
get_kv_transfer_group,
22
has_kv_transfer_group,
@@ -34,7 +35,16 @@ from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
35
from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
36
from vllm.model_executor.models.vision import get_vit_attn_backend
37
from vllm.platforms import current_platform
37
-from vllm.utils import direct_register_custom_op
38
+from vllm.utils import (
39
+ direct_register_custom_op,
40
+ kv_cache_dtype_str_to_dtype,
41
+)
42
+from vllm.v1.kv_cache_interface import (
43
+ FullAttentionSpec,
44
+ KVCacheSpec,
45
+ MLAAttentionSpec,
46
+ SlidingWindowSpec,
47
+)
48
49
FP8_DTYPE = current_platform.fp8_dtype()
50
logger = init_logger(__name__)
@@ -152,6 +162,7 @@ class Attention(nn.Module, AttentionLayerBase):
162
else:
163
sliding_window = None
164
165
+ vllm_config = get_current_vllm_config()
166
if cache_config is not None:
167
kv_cache_dtype = cache_config.cache_dtype
168
block_size = cache_config.block_size
@@ -160,6 +171,9 @@ class Attention(nn.Module, AttentionLayerBase):
171
kv_cache_dtype = "auto"
172
block_size = 16
173
calculate_kv_scales = False
174
+ self.kv_cache_torch_dtype = kv_cache_dtype_str_to_dtype(
175
+ kv_cache_dtype, vllm_config.model_config
176
+ )
177
if num_kv_heads is None:
178
num_kv_heads = num_heads
179
assert num_heads % num_kv_heads == 0, (
@@ -256,7 +270,7 @@ class Attention(nn.Module, AttentionLayerBase):
270
self.use_direct_call = not current_platform.opaque_attention_op()
271
272
self.use_output = self.attn_backend.accept_output_buffer
259
- compilation_config = get_current_vllm_config().compilation_config
273
+ compilation_config = vllm_config.compilation_config
274
if prefix in compilation_config.static_forward_context:
275
raise ValueError(f"Duplicate layer name: {prefix}")
276
compilation_config.static_forward_context[prefix] = self
@@ -276,9 +290,7 @@ class Attention(nn.Module, AttentionLayerBase):
290
# this variable will not be accessed if use_direct_call is True
291
self.kv_cache = [
292
torch.tensor([])
279
- for _ in range(
280
- get_current_vllm_config().parallel_config.pipeline_parallel_size
281
- )
293
+ for _ in range(vllm_config.parallel_config.pipeline_parallel_size)
294
]
295
296
# Initialize q/k/v range constants.
@@ -394,6 +406,30 @@ class Attention(nn.Module, AttentionLayerBase):
406
def get_attn_backend(self) -> type[AttentionBackend]:
407
return self.attn_backend
408
409
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
410
+ # Block size may get updated after model loading, refresh it
411
+ block_size = vllm_config.cache_config.block_size
412
+ # Should not be called for enc-dec or encoder-only attention.
413
+ assert self.attn_type == AttentionType.DECODER
414
+ if self.sliding_window is not None:
415
+ assert not vllm_config.model_config.use_mla, (
416
+ "MLA is not supported for slidingwindow"
417
+ )
418
+ return SlidingWindowSpec(
419
+ block_size=block_size,
420
+ num_kv_heads=self.num_kv_heads,
421
+ head_size=self.head_size,
422
+ dtype=self.kv_cache_torch_dtype,
423
+ sliding_window=self.sliding_window,
424
+ )
425
+ else:
426
+ return FullAttentionSpec(
427
+ block_size=block_size,
428
+ num_kv_heads=self.num_kv_heads,
429
+ head_size=self.head_size,
430
+ dtype=self.kv_cache_torch_dtype,
431
+ )
432
+
433
434
class MultiHeadAttention(nn.Module):
435
"""Multi-headed attention without any cache, used for ViT."""
@@ -749,6 +785,18 @@ class MLAAttention(nn.Module, AttentionLayerBase):
785
def get_attn_backend(self) -> type[AttentionBackend]:
786
return self.attn_backend
787
788
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
789
+ kv_cache_dtype = kv_cache_dtype_str_to_dtype(
790
+ self.kv_cache_dtype, vllm_config.model_config
791
+ )
792
+ return MLAAttentionSpec(
793
+ block_size=vllm_config.cache_config.block_size,
794
+ num_kv_heads=1,
795
+ head_size=self.head_size,
796
+ dtype=kv_cache_dtype,
797
+ cache_dtype_str=vllm_config.cache_config.cache_dtype,
798
+ )
799
+
800
801
def wait_for_kv_layer_from_connector(layer_name: str):
802
if not has_kv_transfer_group() or not is_v1_kv_transfer_group():
vllm/attention/layers/chunked_local_attention.py
+13
@@ -9,6 +9,7 @@ from vllm import envs
9
from vllm.attention.backends.abstract import AttentionBackend, AttentionMetadata
10
from vllm.attention.selector import get_attn_backend
11
from vllm.config import CacheConfig
12
+from vllm.config.vllm import VllmConfig
13
from vllm.model_executor.layers.quantization import QuantizationConfig
14
from vllm.v1.attention.backends.utils import (
15
AttentionCGSupport,
@@ -16,6 +17,7 @@ from vllm.v1.attention.backends.utils import (
17
make_local_attention_virtual_batches,
18
subclass_attention_backend,
19
)
20
+from vllm.v1.kv_cache_interface import ChunkedLocalAttentionSpec, KVCacheSpec
21
22
from ..layer import Attention
23
@@ -67,6 +69,7 @@ class ChunkedLocalAttention(Attention):
69
kv_sharing_target_layer_name: str | None = None,
70
prefix: str = "",
71
):
72
+ self.attention_chunk_size = attention_chunk_size
73
dtype = torch.get_default_dtype()
74
if cache_config is not None:
75
kv_cache_dtype = cache_config.cache_dtype
@@ -99,3 +102,13 @@ class ChunkedLocalAttention(Attention):
102
kv_sharing_target_layer_name=kv_sharing_target_layer_name,
103
attn_backend=attn_backend,
104
)
105
+
106
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
107
+ assert self.attention_chunk_size
108
+ return ChunkedLocalAttentionSpec(
109
+ block_size=vllm_config.cache_config.block_size,
110
+ num_kv_heads=self.num_kv_heads,
111
+ head_size=self.head_size,
112
+ dtype=self.kv_cache_torch_dtype,
113
+ attention_chunk_size=self.attention_chunk_size,
114
+ )
vllm/attention/layers/cross_attention.py
+9
-1
@@ -21,7 +21,7 @@ from vllm.v1.attention.backends.utils import (
21
CommonAttentionMetadata,
22
subclass_attention_backend,
23
)
24
-from vllm.v1.kv_cache_interface import CrossAttentionSpec
24
+from vllm.v1.kv_cache_interface import CrossAttentionSpec, KVCacheSpec
25
26
logger = init_logger(__name__)
27
@@ -174,3 +174,11 @@ class CrossAttention(Attention):
174
attn_type=AttentionType.ENCODER_DECODER,
175
**kwargs,
176
)
177
+
178
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
179
+ return CrossAttentionSpec(
180
+ block_size=vllm_config.cache_config.block_size,
181
+ num_kv_heads=self.num_kv_heads,
182
+ head_size=self.head_size,
183
+ dtype=self.kv_cache_torch_dtype,
184
+ )
vllm/attention/layers/encoder_only_attention.py
+6
@@ -14,10 +14,12 @@ from vllm.attention.backends.abstract import (
14
from vllm.attention.layer import Attention
15
from vllm.attention.selector import get_attn_backend
16
from vllm.config import CacheConfig
17
+from vllm.config.vllm import VllmConfig
18
from vllm.v1.attention.backends.utils import (
19
CommonAttentionMetadata,
20
subclass_attention_backend,
21
)
22
+from vllm.v1.kv_cache_interface import KVCacheSpec
23
24
25
@functools.lru_cache
@@ -98,3 +100,7 @@ class EncoderOnlyAttention(Attention):
100
attn_type=AttentionType.ENCODER_ONLY,
101
**kwargs,
102
)
103
+
104
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
105
+ # Does not need KV cache
106
+ return None
vllm/model_executor/layers/attention_layer_base.py
+11
@@ -5,6 +5,9 @@
5
from abc import ABC, abstractmethod
6
from typing import TYPE_CHECKING
7
8
+from vllm.config import VllmConfig
9
+from vllm.v1.kv_cache_interface import KVCacheSpec
10
+
11
if TYPE_CHECKING:
12
from vllm.attention.backends.abstract import AttentionBackend
13
@@ -22,3 +25,11 @@ class AttentionLayerBase(ABC):
25
def get_attn_backend(self) -> type["AttentionBackend"]:
26
"""Get the attention backend class for this layer."""
27
pass
28
+
29
+ @abstractmethod
30
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec | None:
31
+ """
32
+ Get the KV cache spec for this layer.
33
+ May be None if the layer does not need KV cache.
34
+ """
35
+ pass
vllm/model_executor/layers/mamba/abstract.py
+29
@@ -6,7 +6,9 @@ from typing import TYPE_CHECKING
6
7
import torch
8
9
+from vllm.config import VllmConfig
10
from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
11
+from vllm.v1.kv_cache_interface import KVCacheSpec, MambaSpec
12
13
if TYPE_CHECKING:
14
from vllm.attention.backends.abstract import AttentionBackend
@@ -40,3 +42,30 @@ class MambaBase(AttentionLayerBase):
42
def get_attn_backend(self) -> type["AttentionBackend"]:
43
"""Get the attention backend class for this Mamba layer."""
44
pass
45
+
46
+ @abstractmethod
47
+ def get_state_dtype(self) -> tuple[torch.dtype, ...]:
48
+ pass
49
+
50
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec | None:
51
+ if (
52
+ vllm_config.speculative_config is not None
53
+ and vllm_config.model_config.hf_config.model_type not in ["qwen3_next"]
54
+ ):
55
+ raise NotImplementedError(
56
+ "Mamba with speculative decoding is not supported yet."
57
+ )
58
+ mamba_block_size = vllm_config.cache_config.mamba_block_size
59
+ page_size_padded = vllm_config.cache_config.mamba_page_size_padded
60
+ return MambaSpec(
61
+ shapes=self.get_state_shape(),
62
+ dtypes=self.get_state_dtype(),
63
+ block_size=mamba_block_size,
64
+ page_size_padded=page_size_padded,
65
+ mamba_type=self.mamba_type,
66
+ num_speculative_blocks=(
67
+ vllm_config.speculative_config.num_speculative_tokens
68
+ if vllm_config.speculative_config
69
+ else 0
70
+ ),
71
+ )
vllm/model_executor/models/deepseek_v2.py
+1
-1
@@ -481,7 +481,7 @@ class DeepseekV32IndexerCache(torch.nn.Module, AttentionLayerBase):
481
raise ValueError(f"Duplicate layer name: {prefix}")
482
compilation_config.static_forward_context[prefix] = self
483
484
- def get_kv_cache_spec(self) -> KVCacheSpec:
484
+ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
485
return MLAAttentionSpec( # Only has one vector instead of K + V
486
block_size=self.cache_config.block_size,
487
num_kv_heads=1,
vllm/utils/__init__.py
+9
@@ -137,6 +137,15 @@ def set_default_torch_num_threads(num_threads: int):
137
torch.set_num_threads(old_num_threads)
138
139
140
+def kv_cache_dtype_str_to_dtype(
141
+ kv_cache_dtype: str, model_config: ModelConfig
142
+) -> torch.dtype:
143
+ if kv_cache_dtype == "auto":
144
+ # Model config may not be specified for unit tests, default to float16
145
+ return model_config.dtype if model_config else torch.half
146
+ return STR_DTYPE_TO_TORCH_DTYPE[kv_cache_dtype]
147
+
148
+
149
T = TypeVar("T")
150
U = TypeVar("U")
151
vllm/v1/spec_decode/eagle.py
+1
-1
@@ -948,7 +948,7 @@ class EagleProposer:
948
indexer_layers[first_layer]
949
.get_attn_backend()
950
.get_builder_cls()(
951
- indexer_layers[first_layer].get_kv_cache_spec(),
951
+ indexer_layers[first_layer].get_kv_cache_spec(self.vllm_config),
952
self.indexer_layer_names,
953
self.vllm_config,
954
self.device,
vllm/v1/worker/gpu_model_runner.py
+19
-110
@@ -19,8 +19,6 @@ from tqdm import tqdm
19
import vllm.envs as envs
20
from vllm.attention import Attention, AttentionType
21
from vllm.attention.backends.abstract import AttentionBackend, MultipleOf
22
-from vllm.attention.layer import MLAAttention
23
-from vllm.attention.layers.chunked_local_attention import ChunkedLocalAttention
22
from vllm.compilation.counter import compilation_counter
23
from vllm.compilation.cuda_graph import CUDAGraphWrapper
24
from vllm.compilation.monitor import set_cudagraph_capturing_enabled
@@ -44,10 +42,8 @@ from vllm.distributed.parallel_state import (
42
from vllm.forward_context import BatchDescriptor, set_forward_context
43
from vllm.logger import init_logger
44
from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
47
-from vllm.model_executor.layers.mamba.abstract import MambaBase
45
from vllm.model_executor.layers.rotary_embedding import MRotaryEmbedding
46
from vllm.model_executor.model_loader import TensorizerLoader, get_model_loader
50
-from vllm.model_executor.models.deepseek_v2 import DeepseekV32IndexerCache
47
from vllm.model_executor.models.interfaces import (
48
SupportsMultiModal,
49
is_mixture_of_experts,
@@ -73,11 +69,11 @@ from vllm.sampling_params import SamplingType
69
from vllm.sequence import IntermediateTensors
70
from vllm.tasks import GenerationTask, PoolingTask, SupportedTask
71
from vllm.utils import (
76
- STR_DTYPE_TO_TORCH_DTYPE,
72
cdiv,
73
check_use_alibi,
74
get_dtype_size,
75
is_pin_memory_available,
76
+ kv_cache_dtype_str_to_dtype,
77
length_from_prompt_token_ids_or_embeds,
78
round_up,
79
supports_dynamo,
@@ -106,7 +102,6 @@ from vllm.v1.kv_cache_interface import (
102
KVCacheGroupSpec,
103
KVCacheSpec,
104
MambaSpec,
109
- MLAAttentionSpec,
105
SlidingWindowSpec,
106
UniformTypeKVCacheSpecs,
107
)
@@ -239,10 +234,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
234
self.device = device
235
self.pin_memory = is_pin_memory_available()
236
self.dtype = self.model_config.dtype
242
- if cache_config.cache_dtype == "auto":
243
- self.kv_cache_dtype = self.dtype
244
- else:
245
- self.kv_cache_dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
237
+ self.kv_cache_dtype = kv_cache_dtype_str_to_dtype(
238
+ cache_config.cache_dtype, self.model_config
239
+ )
240
241
self.is_pooling_model = model_config.runner_type == "pooling"
242
self.enable_prompt_embeds = model_config.enable_prompt_embeds
@@ -4577,109 +4571,24 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
4571
format. Layers that do not need KV cache are not included.
4572
"""
4573
4580
- block_size = self.vllm_config.cache_config.block_size
4581
- use_mla = self.vllm_config.model_config.use_mla
4582
- cache_dtype_str = self.vllm_config.cache_config.cache_dtype
4574
kv_cache_spec: dict[str, KVCacheSpec] = {}
4575
attn_layers = get_layers_from_vllm_config(self.vllm_config, AttentionLayerBase)
4576
for layer_name, attn_module in attn_layers.items():
4586
- if isinstance(attn_module, Attention):
4587
- if (
4588
- kv_tgt_layer := attn_module.kv_sharing_target_layer_name
4589
- ) is not None:
4590
- # The layer doesn't need its own KV cache and will use that of
4591
- # the target layer. We skip creating a KVCacheSpec for it, so
4592
- # that KV cache management logic will act as this layer does
4593
- # not exist, and doesn't allocate KV cache for the layer. This
4594
- # enables the memory saving of cross-layer kv sharing, allowing
4595
- # a given amount of memory to accommodate longer context lengths
4596
- # or enable more requests to be processed simultaneously.
4597
- self.shared_kv_cache_layers[layer_name] = kv_tgt_layer
4598
- continue
4599
-
4600
- # TODO(lucas): move the attention specs into the model layers like
4601
- # the attention backends
4602
- if attn_module.attn_type == AttentionType.DECODER:
4603
- if attn_module.sliding_window is not None:
4604
- assert not use_mla, "MLA is not supported for slidingwindow"
4605
- kv_cache_spec[layer_name] = SlidingWindowSpec(
4606
- block_size=block_size,
4607
- num_kv_heads=attn_module.num_kv_heads,
4608
- head_size=attn_module.head_size,
4609
- dtype=self.kv_cache_dtype,
4610
- sliding_window=attn_module.sliding_window,
4611
- )
4612
- elif self.attention_chunk_size is not None and isinstance(
4613
- attn_module, ChunkedLocalAttention
4614
- ):
4615
- kv_cache_spec[layer_name] = ChunkedLocalAttentionSpec(
4616
- block_size=block_size,
4617
- num_kv_heads=attn_module.num_kv_heads,
4618
- head_size=attn_module.head_size,
4619
- dtype=self.kv_cache_dtype,
4620
- attention_chunk_size=self.attention_chunk_size,
4621
- )
4622
- else:
4623
- kv_cache_spec[layer_name] = FullAttentionSpec(
4624
- block_size=block_size,
4625
- num_kv_heads=attn_module.num_kv_heads,
4626
- head_size=attn_module.head_size,
4627
- dtype=self.kv_cache_dtype,
4628
- )
4629
- elif attn_module.attn_type == AttentionType.ENCODER_DECODER:
4630
- kv_cache_spec[layer_name] = CrossAttentionSpec(
4631
- block_size=block_size,
4632
- num_kv_heads=attn_module.num_kv_heads,
4633
- head_size=attn_module.head_size,
4634
- dtype=self.kv_cache_dtype,
4635
- )
4636
- elif attn_module.attn_type in (
4637
- AttentionType.ENCODER,
4638
- AttentionType.ENCODER_ONLY,
4639
- ):
4640
- # encoder-only attention does not need KV cache.
4641
- continue
4642
- else:
4643
- raise ValueError(f"Unknown attention type: {attn_module.attn_type}")
4644
-
4645
- elif isinstance(attn_module, MLAAttention):
4646
- kv_cache_spec[layer_name] = MLAAttentionSpec(
4647
- block_size=block_size,
4648
- num_kv_heads=1,
4649
- head_size=attn_module.head_size,
4650
- dtype=self.kv_cache_dtype,
4651
- cache_dtype_str=cache_dtype_str,
4652
- )
4653
-
4654
- elif isinstance(attn_module, MambaBase):
4655
- if (
4656
- self.vllm_config.speculative_config is not None
4657
- and self.vllm_config.model_config.hf_config.model_type
4658
- not in ["qwen3_next"]
4659
- ):
4660
- raise NotImplementedError(
4661
- "Mamba with speculative decoding is not supported yet."
4662
- )
4663
- mamba_block_size = self.vllm_config.cache_config.mamba_block_size
4664
- page_size_padded = self.vllm_config.cache_config.mamba_page_size_padded
4665
- kv_cache_spec[layer_name] = MambaSpec(
4666
- shapes=attn_module.get_state_shape(),
4667
- dtypes=attn_module.get_state_dtype(),
4668
- block_size=mamba_block_size,
4669
- page_size_padded=page_size_padded,
4670
- mamba_type=attn_module.mamba_type,
4671
- num_speculative_blocks=(
4672
- self.speculative_config.num_speculative_tokens
4673
- if self.speculative_config
4674
- else 0
4675
- ),
4676
- )
4677
-
4678
- ds_indexer_layers = get_layers_from_vllm_config(
4679
- self.vllm_config, DeepseekV32IndexerCache
4680
- )
4681
- for layer_name, ds_indexer_module in ds_indexer_layers.items():
4682
- kv_cache_spec[layer_name] = ds_indexer_module.get_kv_cache_spec()
4577
+ if isinstance(attn_module, Attention) and (
4578
+ kv_tgt_layer := attn_module.kv_sharing_target_layer_name
4579
+ ):
4580
+ # The layer doesn't need its own KV cache and will use that of
4581
+ # the target layer. We skip creating a KVCacheSpec for it, so
4582
+ # that KV cache management logic will act as this layer does
4583
+ # not exist, and doesn't allocate KV cache for the layer. This
4584
+ # enables the memory saving of cross-layer kv sharing, allowing
4585
+ # a given amount of memory to accommodate longer context lengths
4586
+ # or enable more requests to be processed simultaneously.
4587
+ self.shared_kv_cache_layers[layer_name] = kv_tgt_layer
4588
+ continue
4589
+ # Skip modules that don't need KV cache (eg encoder-only attention)
4590
+ if spec := attn_module.get_kv_cache_spec(self.vllm_config):
4591
+ kv_cache_spec[layer_name] = spec
4592
4593
return kv_cache_spec
4594