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 3, 20:15 UTC
feat(kv-events): emit KV cache metadata (#40984)
Signed-off-by: PeaBrane <yanrpei@gmail.com>
Yan Ru Pei committed
May 12, 2026 at 08:58 UTC
bcb9c133bafed161b3f4d0ea391c985465429680
8 files changed
+381
-21
tests/distributed/test_kv_cache_events.py
+14
-2
@@ -9,7 +9,10 @@ from vllm.distributed.kv_events import BlockRemoved, BlockStored
9
_FAKE_HASH: bytes = b"\xab" * 32
10
11
12
-def _make_block_stored(group_idx: int | None = None) -> BlockStored:
12
+def _make_block_stored(
13
+ group_idx: int | None = None,
14
+ kv_cache_spec_sliding_window: int | None = None,
15
+) -> BlockStored:
16
return BlockStored(
17
block_hashes=[_FAKE_HASH],
18
parent_block_hash=None,
@@ -19,10 +22,13 @@ def _make_block_stored(group_idx: int | None = None) -> BlockStored:
22
medium="GPU",
23
lora_name=None,
24
group_idx=group_idx,
25
+ kv_cache_spec_sliding_window=kv_cache_spec_sliding_window,
26
)
27
28
25
-def _make_block_removed(group_idx: int | None = None) -> BlockRemoved:
29
+def _make_block_removed(
30
+ group_idx: int | None = None,
31
+) -> BlockRemoved:
32
return BlockRemoved(
33
block_hashes=[_FAKE_HASH],
34
medium="GPU",
@@ -72,3 +78,9 @@ def test_block_removed_hash_same_for_equal_group_idx():
78
event_a = _make_block_removed(group_idx=1)
79
event_b = _make_block_removed(group_idx=1)
80
assert hash(event_a) == hash(event_b)
81
+
82
+
83
+def test_block_stored_hash_differs_by_sliding_window():
84
+ event_a = _make_block_stored(group_idx=1, kv_cache_spec_sliding_window=128)
85
+ event_b = _make_block_stored(group_idx=1, kv_cache_spec_sliding_window=256)
86
+ assert hash(event_a) != hash(event_b)
tests/v1/core/test_kv_cache_utils.py
+148
@@ -43,11 +43,16 @@ from vllm.v1.kv_cache_interface import (
43
KVCacheConfig,
44
KVCacheGroupSpec,
45
KVCacheSpec,
46
+ KVCacheSpecKind,
47
KVCacheTensor,
48
MambaSpec,
49
MLAAttentionSpec,
50
+ SinkFullAttentionSpec,
51
+ SlidingWindowMLASpec,
52
SlidingWindowSpec,
53
UniformTypeKVCacheSpecs,
54
+ get_kv_cache_spec_kind,
55
+ get_kv_cache_spec_sliding_window,
56
)
57
from vllm.v1.metrics.stats import CachingMetrics, PrefixCacheStats
58
from vllm.v1.request import Request
@@ -1865,6 +1870,149 @@ def new_mla_spec(cache_dtype_str=None):
1870
)
1871
1872
1873
+def test_get_kv_cache_spec_kind_prefers_specific_attention_subclasses():
1874
+ assert get_kv_cache_spec_kind(new_mla_spec()) == KVCacheSpecKind.MLA_ATTENTION
1875
+
1876
+ sliding_window_mla_spec = SlidingWindowMLASpec(
1877
+ block_size=16,
1878
+ num_kv_heads=1,
1879
+ head_size=576,
1880
+ dtype=torch.float32,
1881
+ sliding_window=128,
1882
+ )
1883
+ assert (
1884
+ get_kv_cache_spec_kind(sliding_window_mla_spec)
1885
+ == KVCacheSpecKind.SLIDING_WINDOW_MLA
1886
+ )
1887
+
1888
+ sink_full_attention_spec = SinkFullAttentionSpec(
1889
+ block_size=16,
1890
+ num_kv_heads=1,
1891
+ head_size=64,
1892
+ dtype=torch.float32,
1893
+ sink_len=4,
1894
+ )
1895
+ assert (
1896
+ get_kv_cache_spec_kind(sink_full_attention_spec)
1897
+ == KVCacheSpecKind.SINK_FULL_ATTENTION
1898
+ )
1899
+
1900
+
1901
+def test_get_kv_cache_spec_kind_unwraps_uniform_type_specs():
1902
+ uniform_mla_spec = UniformTypeKVCacheSpecs(
1903
+ block_size=16,
1904
+ kv_cache_specs={
1905
+ "layer_1": new_mla_spec(),
1906
+ "layer_2": new_mla_spec(cache_dtype_str="fp8"),
1907
+ },
1908
+ )
1909
+ assert get_kv_cache_spec_kind(uniform_mla_spec) == KVCacheSpecKind.MLA_ATTENTION
1910
+
1911
+ uniform_swa_mla_spec = UniformTypeKVCacheSpecs(
1912
+ block_size=16,
1913
+ kv_cache_specs={
1914
+ "layer_1": SlidingWindowMLASpec(
1915
+ block_size=16,
1916
+ num_kv_heads=1,
1917
+ head_size=576,
1918
+ dtype=torch.float32,
1919
+ sliding_window=128,
1920
+ ),
1921
+ "layer_2": SlidingWindowMLASpec(
1922
+ block_size=16,
1923
+ num_kv_heads=1,
1924
+ head_size=1024,
1925
+ dtype=torch.float32,
1926
+ sliding_window=128,
1927
+ ),
1928
+ },
1929
+ )
1930
+ assert (
1931
+ get_kv_cache_spec_kind(uniform_swa_mla_spec)
1932
+ == KVCacheSpecKind.SLIDING_WINDOW_MLA
1933
+ )
1934
+
1935
+
1936
+def test_get_kv_cache_spec_kind_unknown_for_mixed_uniform_type_specs():
1937
+ uniform_mixed_spec = UniformTypeKVCacheSpecs(
1938
+ block_size=16,
1939
+ kv_cache_specs={
1940
+ "layer_1": new_mla_spec(),
1941
+ "layer_2": SlidingWindowMLASpec(
1942
+ block_size=16,
1943
+ num_kv_heads=1,
1944
+ head_size=576,
1945
+ dtype=torch.float32,
1946
+ sliding_window=128,
1947
+ ),
1948
+ },
1949
+ )
1950
+ assert get_kv_cache_spec_kind(uniform_mixed_spec) == KVCacheSpecKind.UNKNOWN
1951
+
1952
+
1953
+def test_get_kv_cache_spec_sliding_window_reads_windowed_specs():
1954
+ full_attention_spec = FullAttentionSpec(
1955
+ block_size=16,
1956
+ num_kv_heads=1,
1957
+ head_size=64,
1958
+ dtype=torch.float32,
1959
+ )
1960
+ sliding_window_spec = SlidingWindowSpec(
1961
+ block_size=16,
1962
+ num_kv_heads=1,
1963
+ head_size=64,
1964
+ dtype=torch.float32,
1965
+ sliding_window=128,
1966
+ )
1967
+
1968
+ assert get_kv_cache_spec_sliding_window(full_attention_spec) is None
1969
+ assert get_kv_cache_spec_sliding_window(sliding_window_spec) == 128
1970
+
1971
+
1972
+def test_get_kv_cache_spec_sliding_window_unwraps_uniform_type_specs():
1973
+ uniform_window_spec = UniformTypeKVCacheSpecs(
1974
+ block_size=16,
1975
+ kv_cache_specs={
1976
+ "layer_1": SlidingWindowSpec(
1977
+ block_size=16,
1978
+ num_kv_heads=1,
1979
+ head_size=64,
1980
+ dtype=torch.float32,
1981
+ sliding_window=128,
1982
+ ),
1983
+ "layer_2": SlidingWindowSpec(
1984
+ block_size=16,
1985
+ num_kv_heads=2,
1986
+ head_size=64,
1987
+ dtype=torch.float32,
1988
+ sliding_window=128,
1989
+ ),
1990
+ },
1991
+ )
1992
+ mixed_window_spec = UniformTypeKVCacheSpecs(
1993
+ block_size=16,
1994
+ kv_cache_specs={
1995
+ "layer_1": SlidingWindowSpec(
1996
+ block_size=16,
1997
+ num_kv_heads=1,
1998
+ head_size=64,
1999
+ dtype=torch.float32,
2000
+ sliding_window=128,
2001
+ ),
2002
+ "layer_2": SlidingWindowSpec(
2003
+ block_size=16,
2004
+ num_kv_heads=1,
2005
+ head_size=64,
2006
+ dtype=torch.float32,
2007
+ sliding_window=256,
2008
+ ),
2009
+ },
2010
+ )
2011
+
2012
+ assert get_kv_cache_spec_sliding_window(uniform_window_spec) == 128
2013
+ assert get_kv_cache_spec_sliding_window(mixed_window_spec) is None
2014
+
2015
+
2016
def test_merge_mla_spec():
2017
kv_cache_specs = [
2018
new_mla_spec(),
tests/v1/core/test_prefix_caching.py
+98
-9
@@ -8,6 +8,7 @@ from collections.abc import Callable
8
import pytest
9
import torch
10
11
+import vllm.v1.core.kv_cache_manager as kv_cache_manager
12
import vllm.v1.core.kv_cache_utils as kv_cache_utils
13
from vllm.distributed.kv_events import AllBlocksCleared, BlockRemoved, BlockStored
14
from vllm.lora.request import LoRARequest
@@ -35,6 +36,7 @@ from vllm.v1.kv_cache_interface import (
36
FullAttentionSpec,
37
KVCacheConfig,
38
KVCacheGroupSpec,
39
+ KVCacheSpecKind,
40
MambaSpec,
41
SlidingWindowSpec,
42
)
@@ -1933,6 +1935,7 @@ def test_kv_cache_events(blocks_to_cache: int):
1935
== len(manager.block_pool.cached_block_hash_to_block)
1936
)
1937
assert len(block.token_ids) == block.block_size * len(block.block_hashes)
1938
+ assert block.kv_cache_spec_kind == KVCacheSpecKind.FULL_ATTENTION.value
1939
assert len(manager.block_pool.kv_event_queue) == 0
1940
1941
stored_block_hash = block.block_hashes
@@ -1946,6 +1949,7 @@ def test_kv_cache_events(blocks_to_cache: int):
1949
events = manager.take_events()
1950
1951
for blocks in events[:-1]:
1952
+ assert isinstance(blocks, BlockRemoved)
1953
assert blocks.block_hashes[0] in stored_block_hash
1954
assert len(events) == blocks_to_cache + 1
1955
assert isinstance(events[-2], BlockRemoved)
@@ -2022,6 +2026,8 @@ def test_null_parent_block_hash():
2026
]
2027
assert event.block_hashes == expected_new_hashes
2028
assert event.group_idx == kv_cache_group_id
2029
+ assert event.kv_cache_spec_kind is None
2030
+ assert event.kv_cache_spec_sliding_window is None
2031
2032
# Ensure we didn't accidentally assign a hash to the null block.
2033
assert pool.null_block.block_hash is None
@@ -2095,12 +2101,14 @@ def test_block_stored_event_group_idx(group_id: int):
2101
block_size = 4
2102
num_tokens = block_size * 2
2103
2098
- pool = BlockPool(
2099
- num_gpu_blocks=5,
2104
+ manager = KVCacheManager(
2105
+ make_kv_cache_config_three_types(block_size, num_blocks=5),
2106
+ max_model_len=8192,
2107
enable_caching=True,
2101
- hash_block_size=block_size,
2108
enable_kv_cache_events=True,
2109
+ hash_block_size=block_size,
2110
)
2111
+ pool = manager.block_pool
2112
2113
req = make_request(
2114
"req_grp_idx",
@@ -2119,10 +2127,26 @@ def test_block_stored_event_group_idx(group_id: int):
2127
kv_cache_group_id=group_id,
2128
)
2129
2122
- events = pool.take_events()
2130
+ events = manager.take_events()
2131
assert len(events) == 1
2132
assert isinstance(events[0], BlockStored)
2133
assert events[0].group_idx == group_id
2134
+ assert (
2135
+ events[0].kv_cache_spec_kind
2136
+ == [
2137
+ KVCacheSpecKind.FULL_ATTENTION.value,
2138
+ KVCacheSpecKind.SLIDING_WINDOW.value,
2139
+ KVCacheSpecKind.MAMBA.value,
2140
+ ][group_id]
2141
+ )
2142
+ assert (
2143
+ events[0].kv_cache_spec_sliding_window
2144
+ == [
2145
+ None,
2146
+ 2 * block_size,
2147
+ None,
2148
+ ][group_id]
2149
+ )
2150
2151
2152
def test_block_stored_event_group_idx_multiple_groups():
@@ -2137,13 +2161,38 @@ def test_block_stored_event_group_idx_multiple_groups():
2161
block_size = 4
2162
num_tokens = block_size * 2
2163
2140
- # null block + 4 usable (2 per group)
2141
- pool = BlockPool(
2142
- num_gpu_blocks=5,
2164
+ manager = KVCacheManager(
2165
+ KVCacheConfig(
2166
+ num_blocks=5,
2167
+ kv_cache_tensors=[],
2168
+ kv_cache_groups=[
2169
+ KVCacheGroupSpec(
2170
+ ["layer1"],
2171
+ FullAttentionSpec(
2172
+ block_size=block_size,
2173
+ num_kv_heads=1,
2174
+ head_size=1,
2175
+ dtype=torch.float32,
2176
+ ),
2177
+ ),
2178
+ KVCacheGroupSpec(
2179
+ ["layer2"],
2180
+ SlidingWindowSpec(
2181
+ block_size=block_size,
2182
+ num_kv_heads=1,
2183
+ head_size=1,
2184
+ dtype=torch.float32,
2185
+ sliding_window=128,
2186
+ ),
2187
+ ),
2188
+ ],
2189
+ ),
2190
+ max_model_len=8192,
2191
enable_caching=True,
2144
- hash_block_size=block_size,
2192
enable_kv_cache_events=True,
2193
+ hash_block_size=block_size,
2194
)
2195
+ pool = manager.block_pool
2196
2197
req = make_request(
2198
"req_multi_grp",
@@ -2174,12 +2223,52 @@ def test_block_stored_event_group_idx_multiple_groups():
2223
kv_cache_group_id=1,
2224
)
2225
2177
- events = pool.take_events()
2226
+ events = manager.take_events()
2227
assert len(events) == 2
2228
assert isinstance(events[0], BlockStored)
2229
assert events[0].group_idx == 0
2230
+ assert events[0].kv_cache_spec_kind == KVCacheSpecKind.FULL_ATTENTION.value
2231
+ assert events[0].kv_cache_spec_sliding_window is None
2232
assert isinstance(events[1], BlockStored)
2233
assert events[1].group_idx == 1
2234
+ assert events[1].kv_cache_spec_kind == KVCacheSpecKind.SLIDING_WINDOW.value
2235
+ assert events[1].kv_cache_spec_sliding_window == 128
2236
+
2237
+
2238
+def test_block_stored_event_group_idx_out_of_bounds(monkeypatch):
2239
+ """Out-of-range group_idx events are returned without metadata annotation."""
2240
+ block_size = 4
2241
+ manager = KVCacheManager(
2242
+ make_kv_cache_config(block_size, num_blocks=5),
2243
+ max_model_len=8192,
2244
+ enable_caching=True,
2245
+ enable_kv_cache_events=True,
2246
+ hash_block_size=block_size,
2247
+ )
2248
+ event = BlockStored(
2249
+ block_hashes=[1],
2250
+ parent_block_hash=None,
2251
+ token_ids=list(range(block_size)),
2252
+ block_size=block_size,
2253
+ lora_id=None,
2254
+ medium=None,
2255
+ lora_name=None,
2256
+ group_idx=1,
2257
+ )
2258
+ manager.block_pool.kv_event_queue.append(event)
2259
+ warnings = []
2260
+
2261
+ def collect_warning(message, *args, **kwargs):
2262
+ del kwargs
2263
+ warnings.append(message % args if args else message)
2264
+
2265
+ monkeypatch.setattr(kv_cache_manager.logger, "warning", collect_warning)
2266
+ events = manager.take_events()
2267
+
2268
+ assert events == [event]
2269
+ assert event.kv_cache_spec_kind is None
2270
+ assert event.kv_cache_spec_sliding_window is None
2271
+ assert warnings == ["Group index `1` not in KV cache metadata"]
2272
2273
2274
@pytest.mark.parametrize("group_id", [0, 1, 2])
vllm/distributed/kv_events.py
+6
@@ -68,6 +68,10 @@ class BlockStored(KVCacheEvent):
68
"""
69
70
group_idx: int | None = None
71
+ # Store events carry cache-spec metadata so consumers can classify and
72
+ # filter groups as they are learned. Remove events only need group_idx+hash.
73
+ kv_cache_spec_kind: str | None = None
74
+ kv_cache_spec_sliding_window: int | None = None
75
76
def __hash__(self) -> int:
77
return hash(
@@ -80,6 +84,8 @@ class BlockStored(KVCacheEvent):
84
self.medium,
85
tuple(self.extra_keys) if self.extra_keys else None,
86
self.group_idx,
87
+ self.kv_cache_spec_kind,
88
+ self.kv_cache_spec_sliding_window,
89
)
90
)
91
vllm/v1/core/kv_cache_coordinator.py
+5
-5
@@ -48,11 +48,11 @@ class KVCacheCoordinator(ABC):
48
self.enable_caching = enable_caching
49
50
self.block_pool = BlockPool(
51
- kv_cache_config.num_blocks,
52
- enable_caching,
53
- hash_block_size,
54
- enable_kv_cache_events,
55
- metrics_collector,
51
+ num_gpu_blocks=kv_cache_config.num_blocks,
52
+ enable_caching=enable_caching,
53
+ hash_block_size=hash_block_size,
54
+ enable_kv_cache_events=enable_kv_cache_events,
55
+ metrics_collector=metrics_collector,
56
)
57
58
# KV cache group indices that get the EAGLE last-block drop.
vllm/v1/core/kv_cache_manager.py
+32
-3
@@ -6,12 +6,16 @@ from collections.abc import Sequence
6
from dataclasses import dataclass
7
from typing import Literal, overload
8
9
-from vllm.distributed.kv_events import KVCacheEvent
9
+from vllm.distributed.kv_events import BlockStored, KVCacheEvent
10
from vllm.logger import init_logger
11
from vllm.v1.core.kv_cache_coordinator import get_kv_cache_coordinator
12
from vllm.v1.core.kv_cache_metrics import KVCacheMetricsCollector
13
from vllm.v1.core.kv_cache_utils import KVCacheBlock
14
-from vllm.v1.kv_cache_interface import KVCacheConfig
14
+from vllm.v1.kv_cache_interface import (
15
+ KVCacheConfig,
16
+ get_kv_cache_spec_kind,
17
+ get_kv_cache_spec_sliding_window,
18
+)
19
from vllm.v1.metrics.stats import PrefixCacheStats
20
from vllm.v1.request import Request
21
@@ -149,6 +153,13 @@ class KVCacheManager:
153
self.num_kv_cache_groups = len(kv_cache_config.kv_cache_groups)
154
self.block_pool = self.coordinator.block_pool
155
self.kv_cache_config = kv_cache_config
156
+ self.kv_cache_event_metadata = tuple(
157
+ (
158
+ get_kv_cache_spec_kind(group.kv_cache_spec).value,
159
+ get_kv_cache_spec_sliding_window(group.kv_cache_spec),
160
+ )
161
+ for group in kv_cache_config.kv_cache_groups
162
+ )
163
164
# Pre-constructed KVCacheBlocks with no blocks, callers should use this
165
# via create_kv_cache_blocks instead of creating new ones to avoid GC
@@ -502,7 +513,25 @@ class KVCacheManager:
513
Returns:
514
A list of KV cache events.
515
"""
505
- return self.block_pool.take_events()
516
+ events = self.block_pool.take_events()
517
+ for event in events:
518
+ if not isinstance(event, BlockStored):
519
+ continue
520
+ if event.group_idx is None:
521
+ continue
522
+ if event.group_idx < 0 or event.group_idx >= len(
523
+ self.kv_cache_event_metadata
524
+ ):
525
+ logger.warning(
526
+ "Group index `%s` not in KV cache metadata", event.group_idx
527
+ )
528
+ continue
529
+ # Annotate here so BlockPool can keep emitting structural cache
530
+ # events without owning semantic KV cache spec metadata.
531
+ kind, sliding_window = self.kv_cache_event_metadata[event.group_idx]
532
+ event.kv_cache_spec_kind = kind
533
+ event.kv_cache_spec_sliding_window = sliding_window
534
+ return events
535
536
def get_blocks(self, request_id: str) -> KVCacheBlocks:
537
"""Get the blocks of a request."""
vllm/v1/engine/core.py
+20
-1
@@ -72,7 +72,7 @@ from vllm.v1.engine.utils import (
72
get_device_indices,
73
)
74
from vllm.v1.executor import Executor
75
-from vllm.v1.kv_cache_interface import KVCacheConfig
75
+from vllm.v1.kv_cache_interface import KVCacheConfig, get_kv_cache_spec_kind
76
from vllm.v1.metrics.stats import SchedulerStats
77
from vllm.v1.outputs import ModelRunnerOutput
78
from vllm.v1.request import Request, RequestStatus
@@ -312,6 +312,25 @@ class EngineCore:
312
def get_supported_tasks(self) -> tuple[SupportedTask, ...]:
313
return self.model_executor.supported_tasks
314
315
+ def get_kv_cache_group_metadata(self) -> list[dict[str, int | str | None]]:
316
+ """Return msgspec-serializable metadata for scheduler KV cache groups."""
317
+ kv_cache_config = getattr(self.scheduler, "kv_cache_config", None)
318
+ if kv_cache_config is None:
319
+ return []
320
+
321
+ metadata: list[dict[str, int | str | None]] = []
322
+ for group_idx, group in enumerate(kv_cache_config.kv_cache_groups):
323
+ spec = group.kv_cache_spec
324
+ metadata.append(
325
+ {
326
+ "group_idx": group_idx,
327
+ "kind": get_kv_cache_spec_kind(spec).value,
328
+ "block_size": spec.block_size,
329
+ "sliding_window": getattr(spec, "sliding_window", None),
330
+ }
331
+ )
332
+ return metadata
333
+
334
def add_request(self, request: Request, request_wave: int = 0):
335
"""Add request to the scheduler.
336
vllm/v1/kv_cache_interface.py
+58
-1
@@ -6,7 +6,7 @@ from __future__ import annotations
6
import copy
7
from collections import Counter
8
from dataclasses import dataclass, fields, replace
9
-from enum import IntEnum
9
+from enum import Enum, IntEnum
10
from math import prod
11
from typing import TYPE_CHECKING
12
@@ -78,6 +78,19 @@ def kv_cache_uses_per_token_head_scales(kv_cache_dtype: str) -> bool:
78
return get_kv_quant_mode(kv_cache_dtype).is_per_token_head
79
80
81
+class KVCacheSpecKind(str, Enum):
82
+ FULL_ATTENTION = "full_attention"
83
+ MLA_ATTENTION = "mla_attention"
84
+ SLIDING_WINDOW = "sliding_window"
85
+ SLIDING_WINDOW_MLA = "sliding_window_mla"
86
+ MAMBA = "mamba"
87
+ CHUNKED_LOCAL_ATTENTION = "chunked_local_attention"
88
+ SINK_FULL_ATTENTION = "sink_full_attention"
89
+ ENCODER_ONLY_ATTENTION = "encoder_only_attention"
90
+ CROSS_ATTENTION = "cross_attention"
91
+ UNKNOWN = "unknown"
92
+
93
+
94
@dataclass(frozen=True)
95
class KVCacheSpec:
96
"""
@@ -732,6 +745,50 @@ class UniformTypeKVCacheSpecs(KVCacheSpec):
745
)
746
747
748
+def get_kv_cache_spec_kind(kv_cache_spec: KVCacheSpec) -> KVCacheSpecKind:
749
+ if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
750
+ inner_kinds = {
751
+ get_kv_cache_spec_kind(spec)
752
+ for spec in kv_cache_spec.kv_cache_specs.values()
753
+ }
754
+ if len(inner_kinds) == 1:
755
+ return next(iter(inner_kinds))
756
+ return KVCacheSpecKind.UNKNOWN
757
+ # Keep subclass checks before base classes so specialized specs keep their
758
+ # more precise kind.
759
+ if isinstance(kv_cache_spec, SlidingWindowMLASpec):
760
+ return KVCacheSpecKind.SLIDING_WINDOW_MLA
761
+ if isinstance(kv_cache_spec, MLAAttentionSpec):
762
+ return KVCacheSpecKind.MLA_ATTENTION
763
+ if isinstance(kv_cache_spec, SinkFullAttentionSpec):
764
+ return KVCacheSpecKind.SINK_FULL_ATTENTION
765
+ if isinstance(kv_cache_spec, FullAttentionSpec):
766
+ return KVCacheSpecKind.FULL_ATTENTION
767
+ if isinstance(kv_cache_spec, ChunkedLocalAttentionSpec):
768
+ return KVCacheSpecKind.CHUNKED_LOCAL_ATTENTION
769
+ if isinstance(kv_cache_spec, SlidingWindowSpec):
770
+ return KVCacheSpecKind.SLIDING_WINDOW
771
+ if isinstance(kv_cache_spec, MambaSpec):
772
+ return KVCacheSpecKind.MAMBA
773
+ if isinstance(kv_cache_spec, EncoderOnlyAttentionSpec):
774
+ return KVCacheSpecKind.ENCODER_ONLY_ATTENTION
775
+ if isinstance(kv_cache_spec, CrossAttentionSpec):
776
+ return KVCacheSpecKind.CROSS_ATTENTION
777
+ return KVCacheSpecKind.UNKNOWN
778
+
779
+
780
+def get_kv_cache_spec_sliding_window(kv_cache_spec: KVCacheSpec) -> int | None:
781
+ if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
782
+ inner_windows = {
783
+ get_kv_cache_spec_sliding_window(spec)
784
+ for spec in kv_cache_spec.kv_cache_specs.values()
785
+ }
786
+ return next(iter(inner_windows)) if len(inner_windows) == 1 else None
787
+ if isinstance(kv_cache_spec, SlidingWindowSpec):
788
+ return kv_cache_spec.sliding_window
789
+ return None
790
+
791
+
792
@dataclass
793
class KVCacheTensor:
794
"""