kv_cache_interface.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from __future__ import annotations
  4. import copy
  5. from collections import Counter
  6. from collections.abc import Collection, Sequence
  7. from dataclasses import dataclass, fields, replace
  8. from enum import Enum, IntEnum
  9. from fractions import Fraction
  10. from functools import cached_property
  11. from math import prod
  12. from typing import TYPE_CHECKING, TypeVar
  13. import torch
  14. from typing_extensions import Self
  15. from vllm.logger import init_logger
  16. from vllm.utils.math_utils import cdiv, round_up
  17. from vllm.utils.torch_utils import get_dtype_size
  18. from vllm.v1.attention.backends.registry import MambaAttentionBackendEnum
  19. from vllm.v1.kv_cache_layout import _DIM_B, _DIM_L, KVCacheLayout
  20. from vllm.v1.kv_cache_spec_registry import KVCacheSpecRegistry
  21. if TYPE_CHECKING:
  22. from vllm.config import VllmConfig
  23. logger = init_logger(__name__)
  24. _SpecT = TypeVar("_SpecT", bound="KVCacheSpec")
  25. # ---------------------------------------------------------------------------
  26. # KV cache quantization mode
  27. # ---------------------------------------------------------------------------
  28. class KVQuantMode(IntEnum):
  29. """KV cache quantization mode.
  30. Used by attention backends and kernels to dispatch quantization logic
  31. without string matching on ``kv_cache_dtype``.
  32. """
  33. NONE = 0
  34. FP8_PER_TENSOR = 1 # per-tensor scales (current fp8 path)
  35. INT8_PER_TOKEN_HEAD = 2 # per-token-head dynamic scales for int8
  36. FP8_PER_TOKEN_HEAD = 3 # per-token-head dynamic scales for fp8
  37. INT4_PER_TOKEN_HEAD = 4 # packed 2×int4/byte, RHT + asymmetric zp
  38. NVFP4 = 5 # packed fp4 data + fp8 block scales
  39. # Hadamard-rotated Lloyd-Max quant, packed K+V per slot.
  40. TURBOQUANT_K8V4 = 6
  41. TURBOQUANT_4BIT_NC = 7
  42. TURBOQUANT_K3V4_NC = 8
  43. TURBOQUANT_3BIT_NC = 9
  44. NVFP4_DS_MLA = 10 # opaque-bytes NVFP4 DS-MLA layouts (FlashMLA sparse)
  45. @property
  46. def is_per_token_head(self) -> bool:
  47. """True for any per-token-head quantization mode."""
  48. return self in (
  49. KVQuantMode.INT8_PER_TOKEN_HEAD,
  50. KVQuantMode.FP8_PER_TOKEN_HEAD,
  51. KVQuantMode.INT4_PER_TOKEN_HEAD,
  52. )
  53. @property
  54. def is_nvfp4(self) -> bool:
  55. """True for NVFP4 packed quantization mode."""
  56. return self == KVQuantMode.NVFP4
  57. @property
  58. def is_turboquant(self) -> bool:
  59. """True for any turboquant quantization mode."""
  60. return self in (
  61. KVQuantMode.TURBOQUANT_K8V4,
  62. KVQuantMode.TURBOQUANT_4BIT_NC,
  63. KVQuantMode.TURBOQUANT_K3V4_NC,
  64. KVQuantMode.TURBOQUANT_3BIT_NC,
  65. )
  66. def get_kv_quant_mode(kv_cache_dtype: str) -> KVQuantMode:
  67. """Map a ``kv_cache_dtype`` string to a :class:`KVQuantMode`."""
  68. if kv_cache_dtype == "int4_per_token_head":
  69. return KVQuantMode.INT4_PER_TOKEN_HEAD
  70. if kv_cache_dtype == "int8_per_token_head":
  71. return KVQuantMode.INT8_PER_TOKEN_HEAD
  72. if kv_cache_dtype == "fp8_per_token_head":
  73. return KVQuantMode.FP8_PER_TOKEN_HEAD
  74. # Must precede the ``nvfp4`` prefix test below, which would otherwise match.
  75. if kv_cache_dtype == "nvfp4_ds_mla":
  76. # Page size is keyed on cache_dtype_str in the MLA specs, not
  77. # nvfp4_kv_cache_full_dim.
  78. return KVQuantMode.NVFP4_DS_MLA
  79. if kv_cache_dtype.startswith("nvfp4"):
  80. return KVQuantMode.NVFP4
  81. if isinstance(kv_cache_dtype, str) and kv_cache_dtype.startswith("turboquant_"):
  82. return KVQuantMode[kv_cache_dtype.upper()]
  83. if isinstance(kv_cache_dtype, str) and kv_cache_dtype.startswith("fp8"):
  84. return KVQuantMode.FP8_PER_TENSOR
  85. return KVQuantMode.NONE
  86. def is_quantized_kv_cache(kv_cache_dtype: str) -> bool:
  87. return get_kv_quant_mode(kv_cache_dtype) != KVQuantMode.NONE
  88. def replace_as(
  89. spec: KVCacheSpec,
  90. target_cls: type[_SpecT],
  91. *,
  92. drop: Collection[str] = (),
  93. **changes,
  94. ) -> _SpecT:
  95. """``dataclasses.replace``, but rebuilding *spec* as *target_cls*
  96. e.g. ``SlidingWindowSpec`` -> ``FullAttentionSpec``
  97. Every field of *spec* must exist on *target_cls* unless named in *drop*;
  98. fields only *target_cls* has keep their default values.
  99. """
  100. kwargs = {
  101. f.name: getattr(spec, f.name)
  102. for f in fields(spec)
  103. if f.init and f.name not in drop
  104. }
  105. kwargs.update(changes)
  106. return target_cls(**kwargs)
  107. def kv_cache_uses_per_token_head_scales(kv_cache_dtype: str) -> bool:
  108. """Return True if *kv_cache_dtype* needs per-token-head scales."""
  109. return get_kv_quant_mode(kv_cache_dtype).is_per_token_head
  110. class KVCacheSpecKind(str, Enum):
  111. FULL_ATTENTION = "full_attention"
  112. MLA_ATTENTION = "mla_attention"
  113. SLIDING_WINDOW = "sliding_window"
  114. SLIDING_WINDOW_MLA = "sliding_window_mla"
  115. MAMBA = "mamba"
  116. CHUNKED_LOCAL_ATTENTION = "chunked_local_attention"
  117. SINK_FULL_ATTENTION = "sink_full_attention"
  118. ENCODER_ONLY_ATTENTION = "encoder_only_attention"
  119. CROSS_ATTENTION = "cross_attention"
  120. UNKNOWN = "unknown"
  121. @dataclass(frozen=True)
  122. class KVCacheSpec:
  123. """
  124. A base class for specifying the KV cache format of one layer.
  125. """
  126. # number of tokens in a block
  127. block_size: int
  128. @property
  129. def prefix_cacheable(self) -> bool:
  130. """Whether this spec's group participates in prefix caching."""
  131. return True
  132. @property
  133. def num_heads(self) -> int:
  134. raise NotImplementedError
  135. @property
  136. def tokens_per_state(self) -> int | Fraction:
  137. raise NotImplementedError
  138. @property
  139. def state_content_size_bytes(self) -> int:
  140. raise NotImplementedError
  141. @property
  142. def page_size_bytes(self) -> int:
  143. """
  144. The size of a page with `block_size` tokens in bytes.
  145. Returns:
  146. The page size
  147. """
  148. raise NotImplementedError
  149. @property
  150. def num_states(self) -> int:
  151. return self.get_num_kernel_states(self.block_size)
  152. def get_num_kernel_states(self, kernel_block_size: int) -> int:
  153. if self.tokens_per_state > 0:
  154. return kernel_block_size // self.tokens_per_state
  155. return 1
  156. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  157. """
  158. The maximum possible memory usage of this KV cache in bytes.
  159. Returns:
  160. The KV cache size in bytes
  161. """
  162. raise NotImplementedError
  163. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  164. """
  165. The number of block table entries needed per request, i.e. the row
  166. length of the worker-side block table for this cache group.
  167. Args:
  168. vllm_config: The vllm config.
  169. max_len: The maximum sequence length to size for, including the
  170. encoder length for encoder-decoder models.
  171. """
  172. return cdiv(max_len, self.block_size)
  173. def copy_with_new_block_size(self, block_size: int) -> Self:
  174. """
  175. Create a new KVCacheSpec from self but replacing the block size.
  176. """
  177. return replace(self, block_size=block_size)
  178. @classmethod
  179. def merge(cls, specs: list[Self]) -> Self:
  180. """
  181. Merge a list of KVCacheSpec objects into a single KVCacheSpec object.
  182. """
  183. assert all(spec == specs[0] for spec in specs[1:]), (
  184. "All layers in the same KV cache group must be the same."
  185. )
  186. return copy.deepcopy(specs[0])
  187. def is_uniform_with_collection(
  188. self, kv_cache_specs: dict[str, KVCacheSpec]
  189. ) -> bool:
  190. """
  191. Whether this KVCacheSpec is uniform with all specs of all layers.
  192. """
  193. uniform_type_base_spec = KVCacheSpecRegistry.get_uniform_type_base_spec(self)
  194. assert uniform_type_base_spec is not None, (
  195. f"Unsupported KV cache spec type: {type(self)}. "
  196. "Please register it using @register_kv_cache_spec decorator."
  197. )
  198. return all(
  199. isinstance(spec, uniform_type_base_spec) for spec in kv_cache_specs.values()
  200. )
  201. def group_kernel_blocks(cache: torch.Tensor, num_blocks: int) -> torch.Tensor:
  202. """View a kernel-block-granular layer cache with manager blocks as dim 0.
  203. Kernel block splitting subdivides each manager block into uniformly strided
  204. kernel blocks, so grouping is a pure view: ``(num_blocks * ratio, ...)``
  205. """
  206. if cache.shape[0] == num_blocks:
  207. return cache
  208. assert cache.shape[0] % num_blocks == 0
  209. return cache.unflatten(0, (num_blocks, -1))
  210. def compute_layer_kv_cache_shape_bytes(
  211. spec: KVCacheSpec,
  212. num_blocks: int,
  213. kernel_block_size: int | None = None,
  214. ) -> tuple[int, ...]:
  215. """Return the 4D logical shape ``(B, H, N, C)`` where C is in bytes."""
  216. bs = kernel_block_size if kernel_block_size is not None else spec.block_size
  217. assert spec.block_size % bs == 0, (
  218. f"Kernel block size {bs} must divide KV cache block size {spec.block_size}."
  219. )
  220. blocks_per_page = spec.block_size // bs
  221. return (
  222. num_blocks * blocks_per_page,
  223. spec.num_heads,
  224. spec.get_num_kernel_states(bs),
  225. spec.state_content_size_bytes,
  226. )
  227. def compute_layout_strides(
  228. spec: KVCacheSpec,
  229. num_blocks: int,
  230. num_layers: int,
  231. layout: KVCacheLayout,
  232. kernel_block_size: int | None = None,
  233. fixed_strides: tuple[int | None, ...] = (None,) * 5,
  234. ) -> tuple[int, ...]:
  235. """Byte strides in logical ``[L, B, H, N, C]`` axis order."""
  236. assert len(fixed_strides) == 5
  237. assert all(stride is None or stride > 0 for stride in fixed_strides)
  238. shape = (
  239. num_layers,
  240. *compute_layer_kv_cache_shape_bytes(spec, num_blocks, kernel_block_size),
  241. )
  242. order = layout.stride_order
  243. padded_page_size = getattr(spec, "page_size_padded", None)
  244. if padded_page_size is not None:
  245. assert kernel_block_size is None or kernel_block_size == spec.block_size, (
  246. "Padded KV pages do not support kernel block splitting."
  247. )
  248. page_grid_end = max(order.index(_DIM_L), order.index(_DIM_B)) + 1
  249. page_grid_shape = tuple(shape[dim] for dim in order[:page_grid_end])
  250. assert prod(page_grid_shape) == num_layers * num_blocks, (
  251. "Page padding requires dimensions outside the page tail to be L, B, "
  252. f"or singleton; got {layout.name} with shape {shape}."
  253. )
  254. strides = [0] * 5
  255. current_stride = 1
  256. for physical_idx, dim in reversed(tuple(enumerate(order))):
  257. if padded_page_size is not None and physical_idx == page_grid_end - 1:
  258. current_stride = max(current_stride, padded_page_size)
  259. assert current_stride % padded_page_size == 0
  260. strides[dim] = fixed_strides[dim] or current_stride
  261. current_stride = strides[dim] * shape[dim]
  262. return tuple(strides)
  263. def create_kv_cache_views(
  264. raw: torch.Tensor,
  265. spec: KVCacheSpec,
  266. num_blocks: int,
  267. layout: KVCacheLayout,
  268. kv_cache_tensor: KVCacheTensor,
  269. kernel_block_size: int | None = None,
  270. ) -> list[torch.Tensor]:
  271. """View a flat int8 buffer as one 4D ``[B, H, N, C]`` view per layer.
  272. Block ``b`` of layer ``l`` starts at the tensor offset plus its layer and
  273. block stride contributions.
  274. """
  275. num_layers = len(kv_cache_tensor.layers)
  276. layer_stride = kv_cache_tensor.layer_stride
  277. block_stride = kv_cache_tensor.block_stride
  278. shape_bytes = compute_layer_kv_cache_shape_bytes(
  279. spec, num_blocks, kernel_block_size
  280. )
  281. ratio = shape_bytes[0] // num_blocks
  282. if ratio > 1:
  283. # Kernel blocks subdivide a manager block into `ratio` equal pieces, so
  284. # they sit a constant stride apart only if a block is one dense page: no
  285. # padding at its end, and no other layer's page before the next block.
  286. dense_page_size = prod(compute_layer_kv_cache_shape_bytes(spec, 1)[1:])
  287. if block_stride != dense_page_size:
  288. raise ValueError(
  289. f"The resolved KV cache layout ({layout.name}) does not store "
  290. "blocks as dense, unpadded pages (block stride "
  291. f"{block_stride} != page {dense_page_size}), so a manager "
  292. f"block cannot be split into {ratio} kernel blocks of "
  293. f"{kernel_block_size} tokens. Reduce --block-size to "
  294. f"{kernel_block_size} or set VLLM_KV_CACHE_LAYOUT to a "
  295. "layer-compact layout (e.g. LBNHC)."
  296. )
  297. assert block_stride % ratio == 0, (
  298. f"Block stride {block_stride} must divide into {ratio} equal kernel blocks."
  299. )
  300. block_stride //= ratio
  301. logical_shape = (num_layers, *shape_bytes)
  302. strides = compute_layout_strides(
  303. spec,
  304. num_blocks,
  305. num_layers,
  306. layout,
  307. kernel_block_size,
  308. fixed_strides=(layer_stride, block_stride, None, None, None),
  309. )
  310. dtype = getattr(spec, "dtype", None)
  311. view_5d = torch.as_strided(
  312. raw,
  313. size=logical_shape,
  314. stride=strides,
  315. storage_offset=raw.storage_offset() + kv_cache_tensor.offset,
  316. )
  317. views = []
  318. for layer_idx in range(num_layers):
  319. cache_logical = view_5d[layer_idx]
  320. if dtype is not None:
  321. cache_logical = cache_logical.view(dtype)
  322. views.append(cache_logical)
  323. return views
  324. @dataclass(frozen=True, kw_only=True)
  325. class AttentionSpec(KVCacheSpec):
  326. num_kv_heads: int
  327. head_size: int
  328. dtype: torch.dtype
  329. head_size_v: int = None # type: ignore[assignment]
  330. kv_quant_mode: KVQuantMode = KVQuantMode.NONE
  331. page_size_padded: int | None = None
  332. num_head_slots: int | None = None
  333. """H of the logical ``[B, H, N, C]`` page when packing diverges from one
  334. slot per KV head. None means one slot per KV head. Published by the backend.
  335. """
  336. state_content_bytes: int | None = None
  337. """C in bytes when packed; None means dense K/V content."""
  338. tokens_per_state: int | Fraction = 1
  339. """Tokens covered by one stored state. Ints > 1 compress multiple tokens
  340. into one state (DSv4 sparse MLA); fractions < 1 store multiple states per
  341. token (Whisper block pooling: ``Fraction(1, block_pool_size)``)."""
  342. def __post_init__(self):
  343. if self.head_size_v is None:
  344. object.__setattr__(self, "head_size_v", self.head_size)
  345. @property
  346. def num_heads(self) -> int:
  347. if self.num_head_slots is not None:
  348. return self.num_head_slots
  349. return self.num_kv_heads
  350. @property
  351. def state_content_size_bytes(self) -> int:
  352. """Bytes per (head slot, stored state) cell of the page."""
  353. if self.state_content_bytes is not None:
  354. return self.state_content_bytes
  355. return (self.head_size + self.head_size_v) * get_dtype_size(self.dtype)
  356. @property
  357. def unpadded_page_size_bytes(self) -> int:
  358. return self.num_heads * self.num_states * self.state_content_size_bytes
  359. @property
  360. def page_size_bytes(self) -> int:
  361. if self.page_size_padded is not None:
  362. assert self.page_size_padded >= self.unpadded_page_size_bytes
  363. return self.page_size_padded
  364. return self.unpadded_page_size_bytes
  365. @property
  366. def real_page_size_bytes(self) -> int:
  367. """Alias of ``unpadded_page_size_bytes``
  368. TODO(lucas): follow up with TPU backend to see if we can remove this property.
  369. """
  370. return self.unpadded_page_size_bytes
  371. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  372. parallel_config = vllm_config.parallel_config
  373. kv_shard_count = parallel_config.decode_context_parallel_size
  374. return cdiv(max_len, self.block_size * kv_shard_count)
  375. @dataclass(frozen=True, kw_only=True)
  376. class FullAttentionSpec(AttentionSpec):
  377. """
  378. When hybrid allocator is disabled and the model contains both full
  379. attention layers and sliding window attention layers, sliding
  380. window attention are regarded as full attention in KV cache manager
  381. (blocks are allocated for all tokens), while computed as sliding window
  382. attention in model runner.
  383. In this case, we use FullAttentionSpec and record the sliding window size.
  384. """
  385. sliding_window: int | None = None
  386. """
  387. Default to None for not using sliding window attention.
  388. """
  389. attention_chunk_size: int | None = None
  390. non_causal: bool = False
  391. """
  392. Whether the layer attends non-causally (e.g. Prefix LM). Carried on the
  393. spec so the engine core, which collects specs from all workers before the
  394. scheduler is built, can adjust scheduling policy (chunked prefill / prefix
  395. caching) regardless of tensor-parallel layout. It does not affect the KV
  396. cache layout itself.
  397. """
  398. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  399. max_model_len = vllm_config.model_config.max_model_len
  400. dcp_world_size = vllm_config.parallel_config.decode_context_parallel_size
  401. if dcp_world_size > 1:
  402. max_model_len = cdiv(max_model_len, dcp_world_size)
  403. return cdiv(max_model_len, self.block_size) * self.page_size_bytes
  404. @classmethod
  405. def merge_window_sizes(cls, window_sizes: set[int]) -> int | None:
  406. if len(window_sizes) == 0:
  407. return None
  408. elif len(window_sizes) == 1:
  409. return window_sizes.pop()
  410. else:
  411. raise ValueError(
  412. "All attention layers in the same KV cache group must have the "
  413. "same window size."
  414. )
  415. @classmethod
  416. def merge(cls, specs: list[Self]) -> Self:
  417. """
  418. Merge a list of FullAttentionSpec objects into a single
  419. FullAttentionSpec object.
  420. """
  421. assert all(isinstance(spec, FullAttentionSpec) for spec in specs), (
  422. "All attention layers in the same KV cache group must be FullAttentionSpec."
  423. )
  424. sliding_window = set(
  425. spec.sliding_window for spec in specs if spec.sliding_window is not None
  426. )
  427. attention_chunk_size = set(
  428. spec.attention_chunk_size
  429. for spec in specs
  430. if spec.attention_chunk_size is not None
  431. )
  432. assert not any(isinstance(spec, MLAAttentionSpec) for spec in specs), (
  433. "MLAAttentionSpec should be merged in MLAAttentionSpec.merge"
  434. )
  435. merged_spec = cls(
  436. block_size=specs[0].block_size,
  437. num_kv_heads=specs[0].num_kv_heads,
  438. head_size=specs[0].head_size,
  439. head_size_v=specs[0].head_size_v,
  440. dtype=specs[0].dtype,
  441. kv_quant_mode=specs[0].kv_quant_mode,
  442. page_size_padded=specs[0].page_size_padded,
  443. num_head_slots=specs[0].num_head_slots,
  444. state_content_bytes=specs[0].state_content_bytes,
  445. tokens_per_state=specs[0].tokens_per_state,
  446. sliding_window=cls.merge_window_sizes(sliding_window),
  447. attention_chunk_size=cls.merge_window_sizes(attention_chunk_size),
  448. # If any layer in the group is non-causal, treat the group as
  449. # non-causal so the engine core disables incompatible scheduling.
  450. non_causal=any(spec.non_causal for spec in specs),
  451. )
  452. for spec in specs:
  453. for f in fields(AttentionSpec):
  454. assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
  455. "All attention layers in the same KV cache group must have "
  456. "the same attention spec."
  457. )
  458. assert (merged_spec.sliding_window is not None) + (
  459. merged_spec.attention_chunk_size is not None
  460. ) <= 1, (
  461. "Model with both sliding window layers and chunked local attention "
  462. "layers is not supported."
  463. )
  464. return merged_spec
  465. def _apply_alignment_padding(spec: MLAAttentionSpec | SlidingWindowMLASpec):
  466. if spec.alignment is None:
  467. return
  468. actual_page_size = spec.real_page_size_bytes
  469. padded_page_size = round_up(actual_page_size, spec.alignment)
  470. if padded_page_size != actual_page_size:
  471. object.__setattr__(spec, "page_size_padded", padded_page_size)
  472. @dataclass(frozen=True, kw_only=True)
  473. class MLAAttentionSpec(FullAttentionSpec):
  474. # TODO(Lucas/Chen): less hacky way to do this
  475. cache_dtype_str: str | None = None
  476. # DeepseekV4 only fields. Non-DeepseekV4 MLA models leave these at defaults.
  477. alignment: int | None = None # Default to None for no padding.
  478. model_version: str | None = None
  479. storage_block_size: int | None = None
  480. """Token width used to view storage when it differs from the kernel block."""
  481. # Marks draft groups that flatten a non-causal query block into decode rows.
  482. non_causal_multi_token_decode: bool = False
  483. # MLA stores a single latent vector per state; there is no separate V.
  484. head_size_v: int = 0
  485. def __post_init__(self):
  486. super().__post_init__()
  487. _apply_alignment_padding(self)
  488. @classmethod
  489. def merge(cls, specs: list[Self]) -> Self:
  490. assert all(isinstance(spec, MLAAttentionSpec) for spec in specs), (
  491. "All attention layers in the same KV cache group must be MLAAttentionSpec."
  492. )
  493. cache_dtype_str_set = set(spec.cache_dtype_str for spec in specs)
  494. tokens_per_state_set = set(spec.tokens_per_state for spec in specs)
  495. model_version_set = set(spec.model_version for spec in specs)
  496. storage_block_size_set = set(spec.storage_block_size for spec in specs)
  497. assert (
  498. len(cache_dtype_str_set) == 1
  499. and len(tokens_per_state_set) == 1
  500. and len(model_version_set) == 1
  501. and len(storage_block_size_set) == 1
  502. ), (
  503. "All attention layers in the same KV cache group must use the same "
  504. "quantization method, tokens per state, model version, and storage "
  505. "block size."
  506. )
  507. non_causal_mtd_set = {spec.non_causal_multi_token_decode for spec in specs}
  508. assert len(non_causal_mtd_set) == 1, (
  509. "All attention layers in the same KV cache group must agree on "
  510. "non_causal_multi_token_decode."
  511. )
  512. merged_spec = cls(
  513. block_size=specs[0].block_size,
  514. num_kv_heads=specs[0].num_kv_heads,
  515. head_size=specs[0].head_size,
  516. dtype=specs[0].dtype,
  517. kv_quant_mode=specs[0].kv_quant_mode,
  518. page_size_padded=specs[0].page_size_padded,
  519. num_head_slots=specs[0].num_head_slots,
  520. state_content_bytes=specs[0].state_content_bytes,
  521. cache_dtype_str=cache_dtype_str_set.pop(),
  522. tokens_per_state=tokens_per_state_set.pop(),
  523. model_version=model_version_set.pop(),
  524. storage_block_size=storage_block_size_set.pop(),
  525. non_causal_multi_token_decode=non_causal_mtd_set.pop(),
  526. )
  527. for spec in specs:
  528. for f in fields(AttentionSpec):
  529. assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
  530. "All attention layers in the same KV cache group must have "
  531. "the same attention spec."
  532. )
  533. return merged_spec
  534. @dataclass(frozen=True, kw_only=True)
  535. class HiddenStateCacheSpec(MLAAttentionSpec):
  536. """Marker for hidden-state cache layers used by extract_hidden_states."""
  537. pass
  538. @dataclass(frozen=True, kw_only=True)
  539. class RSWASpec(FullAttentionSpec):
  540. """KV cache spec for Reference Sliding Window Attention (R-SWA).
  541. Prefill (image + text prompt) tokens are always globally visible.
  542. Only the last ``rswa_window`` generated tokens are kept in the KV cache;
  543. gap blocks (between the prefill tail and the current decode window) are
  544. evicted during each decode step to bound memory at
  545. O(prefix_blocks + window_blocks).
  546. """
  547. rswa_window: int
  548. @classmethod
  549. def merge(cls, specs: list[RSWASpec]) -> RSWASpec:
  550. assert all(isinstance(spec, RSWASpec) for spec in specs), (
  551. "All attention layers in the same KV cache group must be RSWASpec."
  552. )
  553. rswa_windows = {spec.rswa_window for spec in specs}
  554. assert len(rswa_windows) == 1, (
  555. f"All R-SWA layers must share the same rswa_window, got {rswa_windows}"
  556. )
  557. # Delegate common field merging to the parent, then reattach rswa_window.
  558. base = FullAttentionSpec.merge(specs) # type: ignore[arg-type]
  559. return cls(
  560. block_size=base.block_size,
  561. num_kv_heads=base.num_kv_heads,
  562. head_size=base.head_size,
  563. head_size_v=base.head_size_v,
  564. dtype=base.dtype,
  565. kv_quant_mode=base.kv_quant_mode,
  566. page_size_padded=base.page_size_padded,
  567. num_head_slots=base.num_head_slots,
  568. state_content_bytes=base.state_content_bytes,
  569. tokens_per_state=base.tokens_per_state,
  570. sliding_window=base.sliding_window,
  571. attention_chunk_size=base.attention_chunk_size,
  572. non_causal=base.non_causal,
  573. rswa_window=rswa_windows.pop(),
  574. )
  575. @dataclass(frozen=True, kw_only=True)
  576. class ChunkedLocalAttentionSpec(AttentionSpec):
  577. attention_chunk_size: int
  578. def max_admission_blocks_per_request(
  579. self, max_in_flight_tokens: int, max_model_len: int
  580. ) -> int:
  581. """Per-request admission cap, in blocks.
  582. Single source of truth for both startup pool sizing
  583. (`max_memory_usage_bytes`) and the runtime admission gate, so requests
  584. admitted by startup can also be admitted at runtime.
  585. `max_in_flight_tokens` is the max tokens scheduled but not yet settled
  586. (one batch per concurrent step); see `VllmConfig.max_in_flight_tokens`.
  587. """
  588. # During chunked prefill, we hold KV for at most one chunk window plus
  589. # the in-flight tokens, since frees happen on the processed-token basis.
  590. num_tokens = min(
  591. self.attention_chunk_size + max_in_flight_tokens, max_model_len
  592. )
  593. return cdiv(num_tokens, self.block_size)
  594. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  595. max_blocks = self.max_admission_blocks_per_request(
  596. max_in_flight_tokens=vllm_config.max_in_flight_tokens,
  597. max_model_len=vllm_config.model_config.max_model_len,
  598. )
  599. return max_blocks * self.page_size_bytes
  600. def is_uniform_with_collection(
  601. self, kv_cache_specs: dict[str, KVCacheSpec]
  602. ) -> bool:
  603. return all(
  604. isinstance(spec, ChunkedLocalAttentionSpec)
  605. and spec.attention_chunk_size == self.attention_chunk_size
  606. for spec in kv_cache_specs.values()
  607. )
  608. @dataclass(frozen=True, kw_only=True)
  609. class SlidingWindowSpec(AttentionSpec):
  610. sliding_window: int
  611. # The trailing edge of the window is extended by ``extra_retained_tokens``
  612. # so that those extra trailing tokens' blocks are retained (but not
  613. # attended). This is needed for multi-module spec decoding which can
  614. # re-prefill the last num_spec_prefill_tokens - 1 tokens from the end
  615. # of the sequence, and thus needs to delay freeing/caching of blocks.
  616. extra_retained_tokens: int = 0
  617. def max_admission_blocks_per_request(
  618. self, max_in_flight_tokens: int, max_model_len: int
  619. ) -> int:
  620. """Per-request admission cap, in blocks.
  621. Single source of truth for both startup pool sizing
  622. (`max_memory_usage_bytes`) and the runtime admission gate. Per-request
  623. real-held blocks plateau at this bound because
  624. `SlidingWindowManager.remove_skipped_blocks` runs from `allocate_slots`
  625. before each chunk's `get_num_blocks_to_allocate`.
  626. `max_in_flight_tokens` is the max tokens scheduled but not yet settled
  627. (one batch per concurrent step); see `VllmConfig.max_in_flight_tokens`.
  628. """
  629. # During chunked prefill, we hold KV for the last `sliding_window-1`
  630. # computed tokens plus the in-flight tokens (frees happen on the
  631. # processed-token basis); never more than `max_model_len`. An additional
  632. # `extra_retained_tokens` trailing tokens are kept alive below the
  633. # window for multi-module spec decoding, and must be accounted here too.
  634. num_tokens = min(
  635. self.sliding_window - 1 + self.extra_retained_tokens + max_in_flight_tokens,
  636. max_model_len,
  637. )
  638. # +1 because the sliding window may not start from the beginning of
  639. # the block. E.g. block size 4 and num_token 4 needs two blocks
  640. # [XXCD][EF] to store the 6-token window [CDEF].
  641. return cdiv(num_tokens, self.block_size) + 1
  642. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  643. assert vllm_config.parallel_config.decode_context_parallel_size == 1, (
  644. "DCP not support sliding window."
  645. )
  646. max_blocks = self.max_admission_blocks_per_request(
  647. max_in_flight_tokens=vllm_config.max_in_flight_tokens,
  648. max_model_len=vllm_config.model_config.max_model_len,
  649. )
  650. return max_blocks * self.page_size_bytes
  651. def is_uniform_with_collection(
  652. self, kv_cache_specs: dict[str, KVCacheSpec]
  653. ) -> bool:
  654. return all(
  655. isinstance(spec, SlidingWindowSpec)
  656. and spec.sliding_window == self.sliding_window
  657. for spec in kv_cache_specs.values()
  658. )
  659. @dataclass(frozen=True, kw_only=True)
  660. class CircularBufferSpec(AttentionSpec):
  661. """One block per request holding the raw keys of the token group that
  662. is still being compressed.
  663. ``block_size`` is the ring capacity. It must exceed the compression ratio
  664. by the speculative lookahead: a speculative step stores all of its rows,
  665. drafts included, before acceptance is known, while the next step still
  666. reads the open group's committed keys from the ring.
  667. """
  668. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  669. # The ring occupies one block per request for its whole lifetime.
  670. del vllm_config
  671. return self.page_size_bytes
  672. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  673. del vllm_config, max_len
  674. return 1
  675. def is_uniform_with_collection(
  676. self, kv_cache_specs: dict[str, KVCacheSpec]
  677. ) -> bool:
  678. return all(
  679. isinstance(spec, CircularBufferSpec) for spec in kv_cache_specs.values()
  680. )
  681. @property
  682. def prefix_cacheable(self) -> bool:
  683. return False
  684. @dataclass(frozen=True, kw_only=True)
  685. class SlidingWindowMLASpec(SlidingWindowSpec):
  686. """Sliding window attention with MLA cache format."""
  687. cache_dtype_str: str | None = None
  688. # DeepseekV4-only: see MLAAttentionSpec.model_version.
  689. alignment: int | None = None # Default to None for no padding.
  690. model_version: str | None = None
  691. # MLA stores a single latent vector per state; there is no separate V.
  692. head_size_v: int = 0
  693. def __post_init__(self):
  694. assert self.model_version in (None, "deepseek_v4"), (
  695. f"Unsupported model version: {self.model_version}"
  696. )
  697. super().__post_init__()
  698. _apply_alignment_padding(self)
  699. @classmethod
  700. def merge(cls, specs: list[Self]) -> Self:
  701. assert all(isinstance(spec, SlidingWindowMLASpec) for spec in specs), (
  702. "All attention layers in the same KV cache group must be "
  703. "SlidingWindowMLASpec."
  704. )
  705. cache_dtype_str_set = set(spec.cache_dtype_str for spec in specs)
  706. tokens_per_state_set = set(spec.tokens_per_state for spec in specs)
  707. model_version_set = set(spec.model_version for spec in specs)
  708. sliding_window_set = set(spec.sliding_window for spec in specs)
  709. extra_retained_set = set(spec.extra_retained_tokens for spec in specs)
  710. assert (
  711. len(cache_dtype_str_set) == 1
  712. and len(tokens_per_state_set) == 1
  713. and len(model_version_set) == 1
  714. and len(sliding_window_set) == 1
  715. and len(extra_retained_set) == 1
  716. ), (
  717. "All attention layers in the same KV cache group must use the same "
  718. "quantization method, tokens per state, model version, sliding "
  719. "window size, and retained token count."
  720. )
  721. return cls(
  722. block_size=specs[0].block_size,
  723. num_kv_heads=specs[0].num_kv_heads,
  724. head_size=specs[0].head_size,
  725. dtype=specs[0].dtype,
  726. page_size_padded=specs[0].page_size_padded,
  727. num_head_slots=specs[0].num_head_slots,
  728. state_content_bytes=specs[0].state_content_bytes,
  729. sliding_window=sliding_window_set.pop(),
  730. extra_retained_tokens=extra_retained_set.pop(),
  731. cache_dtype_str=cache_dtype_str_set.pop(),
  732. tokens_per_state=tokens_per_state_set.pop(),
  733. model_version=model_version_set.pop(),
  734. )
  735. def is_uniform_with_collection(
  736. self, kv_cache_specs: dict[str, KVCacheSpec]
  737. ) -> bool:
  738. return all(
  739. isinstance(spec, SlidingWindowMLASpec)
  740. and spec.sliding_window == self.sliding_window
  741. for spec in kv_cache_specs.values()
  742. )
  743. @dataclass(frozen=True, kw_only=True)
  744. class KpoolTailSpec(SlidingWindowSpec):
  745. """One-block circular scratch cache for a kpool indexer's raw tail."""
  746. def max_admission_blocks_per_request(
  747. self, max_in_flight_tokens: int, max_model_len: int
  748. ) -> int:
  749. return 1
  750. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  751. return 1
  752. def is_uniform_with_collection(
  753. self, kv_cache_specs: dict[str, KVCacheSpec]
  754. ) -> bool:
  755. return all(isinstance(spec, KpoolTailSpec) for spec in kv_cache_specs.values())
  756. @property
  757. def prefix_cacheable(self) -> bool:
  758. return False
  759. @dataclass(frozen=True)
  760. class MambaSpec(KVCacheSpec):
  761. shapes: tuple[tuple[int, ...], ...]
  762. dtypes: tuple[torch.dtype, ...]
  763. page_size_padded: int | None = None
  764. mamba_type: MambaAttentionBackendEnum = MambaAttentionBackendEnum.MAMBA2
  765. mamba_cache_mode: str = "none"
  766. num_speculative_blocks: int = 0
  767. num_prefill_checkpoint_blocks: int = 0
  768. num_heads: int = 1
  769. tokens_per_state: int = -1
  770. # False: the state is sharded across TP ranks (e.g. GDN). True: every TP
  771. # rank holds the full state (e.g. the replicated PLE conv state).
  772. tp_replicated: bool = False
  773. @property
  774. def state_content_size_bytes(self) -> int:
  775. return sum(
  776. prod(shape) * get_dtype_size(dtype)
  777. for (shape, dtype) in zip(self.shapes, self.dtypes)
  778. )
  779. @property
  780. def real_page_size_bytes(self) -> int:
  781. return self.state_content_size_bytes
  782. @property
  783. def page_size_bytes(self) -> int:
  784. page_size = sum(
  785. prod(shape) * get_dtype_size(dtype)
  786. for (shape, dtype) in zip(self.shapes, self.dtypes)
  787. )
  788. if self.page_size_padded is not None:
  789. assert self.page_size_padded >= page_size
  790. return self.page_size_padded
  791. return page_size
  792. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  793. if vllm_config.cache_config.mamba_cache_mode == "all":
  794. max_model_len = vllm_config.model_config.max_model_len
  795. return (
  796. cdiv(max_model_len, self.block_size) + self.num_speculative_blocks
  797. ) * self.page_size_bytes
  798. elif vllm_config.cache_config.mamba_cache_mode == "align":
  799. return self.page_size_bytes * (
  800. 2 + self.num_speculative_blocks + self.num_prefill_checkpoint_blocks
  801. )
  802. else:
  803. return self.page_size_bytes * (1 + self.num_speculative_blocks)
  804. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  805. # Mamba state is replicated across DCP/PCP ranks, never sharded, so
  806. # no CP scaling applies.
  807. if vllm_config.cache_config.mamba_cache_mode == "align":
  808. # Block table rows are position-indexed over the full sequence
  809. # even though only 2 + num_speculative_blocks state blocks are
  810. # resident at a time (earlier states are nulled out by
  811. # remove_skipped_blocks), so the row length must cover max_len
  812. # rather than max_memory_usage_bytes.
  813. return cdiv(max_len, self.block_size) + self.num_speculative_blocks
  814. return cdiv(self.max_memory_usage_bytes(vllm_config), self.page_size_bytes)
  815. def is_uniform_with_collection(
  816. self, kv_cache_specs: dict[str, KVCacheSpec]
  817. ) -> bool:
  818. return all(
  819. isinstance(spec, MambaSpec)
  820. and spec.num_speculative_blocks == self.num_speculative_blocks
  821. and spec.num_prefill_checkpoint_blocks == self.num_prefill_checkpoint_blocks
  822. and spec.page_size_bytes == self.page_size_bytes
  823. and spec.tp_replicated == self.tp_replicated
  824. for spec in kv_cache_specs.values()
  825. )
  826. @dataclass(frozen=True)
  827. class EncoderOnlyAttentionSpec(AttentionSpec):
  828. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  829. # Encoder-only layers do not need KV cache
  830. return 0
  831. @dataclass(frozen=True)
  832. class CrossAttentionSpec(AttentionSpec):
  833. """
  834. KV cache spec for cross-attention layers in encoder-decoder models.
  835. """
  836. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  837. # For cross-attention, we need to cache encoder states
  838. # Get encoder length (e.g., 1500 for Whisper).
  839. max_encoder_len = vllm_config.scheduler_config.max_num_encoder_input_tokens
  840. return cdiv(max_encoder_len, self.block_size) * self.page_size_bytes
  841. @dataclass(frozen=True)
  842. class SinkFullAttentionSpec(FullAttentionSpec):
  843. sink_len: int | None = None
  844. @classmethod
  845. def merge(cls, specs: list[Self]) -> Self:
  846. """
  847. Merge a list of FullAttentionSpec objects into a single
  848. FullAttentionSpec object.
  849. """
  850. assert all(isinstance(spec, FullAttentionSpec) for spec in specs), (
  851. "All attention layers in the same KV cache group must be FullAttentionSpec."
  852. )
  853. sliding_window = set(
  854. spec.sliding_window for spec in specs if spec.sliding_window is not None
  855. )
  856. attention_chunk_size = set(
  857. spec.attention_chunk_size
  858. for spec in specs
  859. if spec.attention_chunk_size is not None
  860. )
  861. assert not any(isinstance(spec, MLAAttentionSpec) for spec in specs), (
  862. "MLAAttentionSpec should be merged in MLAAttentionSpec.merge"
  863. )
  864. merged_spec = cls(
  865. block_size=specs[0].block_size,
  866. num_kv_heads=specs[0].num_kv_heads,
  867. head_size=specs[0].head_size,
  868. head_size_v=specs[0].head_size_v,
  869. sink_len=specs[0].sink_len,
  870. dtype=specs[0].dtype,
  871. kv_quant_mode=specs[0].kv_quant_mode,
  872. page_size_padded=specs[0].page_size_padded,
  873. num_head_slots=specs[0].num_head_slots,
  874. state_content_bytes=specs[0].state_content_bytes,
  875. sliding_window=cls.merge_window_sizes(sliding_window),
  876. attention_chunk_size=cls.merge_window_sizes(attention_chunk_size),
  877. non_causal=any(spec.non_causal for spec in specs),
  878. )
  879. for spec in specs:
  880. for f in fields(AttentionSpec):
  881. assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
  882. "All attention layers in the same KV cache group must have "
  883. "the same attention spec."
  884. )
  885. assert (merged_spec.sliding_window is not None) + (
  886. merged_spec.attention_chunk_size is not None
  887. ) <= 1, (
  888. "Model with both sliding window layers and chunked local attention "
  889. "layers is not supported."
  890. )
  891. return merged_spec
  892. @dataclass(frozen=True)
  893. class UniformTypeKVCacheSpecs(KVCacheSpec):
  894. """
  895. A KV cache spec for multiple layers with the same type of attention. Here,
  896. same types means always need the same number of token slots. For example,
  897. sliding window attentions with different window sizes are not the same type
  898. and should not be merged into one UniformTypeKVCacheSpecs.
  899. """
  900. kv_cache_specs: dict[str, KVCacheSpec]
  901. @property
  902. def prefix_cacheable(self) -> bool:
  903. return all(spec.prefix_cacheable for spec in self.kv_cache_specs.values())
  904. @property
  905. def first_spec(self) -> KVCacheSpec:
  906. """Return the first spec in the group."""
  907. return next(iter(self.kv_cache_specs.values()))
  908. @property
  909. def page_size_bytes(self) -> int:
  910. return sum(spec.page_size_bytes for spec in self.kv_cache_specs.values())
  911. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  912. max_num_pages = max(
  913. cdiv(spec.max_memory_usage_bytes(vllm_config), spec.page_size_bytes)
  914. for spec in self.kv_cache_specs.values()
  915. )
  916. return max_num_pages * self.page_size_bytes
  917. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  918. # Metadata builders are constructed from the per-layer spec, so the base
  919. # cdiv(max_len, block_size) would drop its DCP sharding and size the
  920. # block table wider than those builders expect.
  921. widths = {
  922. spec.max_num_blocks_per_req(vllm_config, max_len)
  923. for spec in self.kv_cache_specs.values()
  924. }
  925. assert len(widths) == 1, (
  926. "All layers in the same KV cache group must need the same number "
  927. f"of block table entries, got {sorted(widths)}."
  928. )
  929. return next(iter(widths))
  930. @classmethod
  931. def is_uniform_type(cls, kv_cache_specs: dict[str, KVCacheSpec]) -> bool:
  932. """
  933. Whether all layers have the same type of KV cache spec.
  934. Uses the registry to determine grouping base classes, so custom specs
  935. that inherit from FullAttentionSpec are treated as full attention.
  936. """
  937. block_sizes = set(spec.block_size for spec in kv_cache_specs.values())
  938. if len(block_sizes) > 1:
  939. # Different block sizes, not uniform.
  940. return False
  941. first_spec = next(iter(kv_cache_specs.values()))
  942. return first_spec.is_uniform_with_collection(kv_cache_specs)
  943. @classmethod
  944. def from_specs(cls, kv_cache_specs: dict[str, KVCacheSpec]) -> Self | None:
  945. """
  946. Return a SameTypeKVCacheSpecs object if all layers have the same type
  947. of KV cache spec. Return None if not.
  948. """
  949. if cls.is_uniform_type(kv_cache_specs):
  950. block_size = next(iter(kv_cache_specs.values())).block_size
  951. return cls(block_size=block_size, kv_cache_specs=kv_cache_specs)
  952. else:
  953. return None
  954. def get_max_layers_per_page_size(self) -> int:
  955. """Max number of layers sharing a page size. For a balanced bucket
  956. this equals the number of repetitions of the layer pattern."""
  957. return Counter(
  958. spec.page_size_bytes for spec in self.kv_cache_specs.values()
  959. ).most_common(1)[0][1]
  960. def max_memory_usage_pages(self, vllm_config: VllmConfig) -> int:
  961. return max(
  962. cdiv(spec.max_memory_usage_bytes(vllm_config), spec.page_size_bytes)
  963. for spec in self.kv_cache_specs.values()
  964. )
  965. def iter_layer_specs(kv_cache_spec: KVCacheSpec) -> Collection[KVCacheSpec]:
  966. """The per-layer specs a KV cache group spec covers.
  967. ``UniformTypeKVCacheSpecs`` groups keep one spec per layer; every other
  968. spec describes its group on its own. Returns the layer specs either way so
  969. callers do not have to special-case the wrapper.
  970. """
  971. if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
  972. return kv_cache_spec.kv_cache_specs.values()
  973. return (kv_cache_spec,)
  974. def is_full_attention_spec(kv_cache_spec: KVCacheSpec) -> bool:
  975. """Whether a KV cache group spec is (or wraps) full attention.
  976. ``UniformTypeKVCacheSpecs`` is not itself a ``FullAttentionSpec``, so a bare
  977. isinstance check misses groups that carry the wrapper -- DeepSeek-V4's MLA
  978. layers, or any model taking the ``UniformTypeKVCacheSpecs.from_specs`` path.
  979. Every layer must be full attention: a group holding a recycling
  980. (sliding-window) layer has no stable slot layout, so callers that key data
  981. by slot cannot use it.
  982. """
  983. layer_specs = iter_layer_specs(kv_cache_spec)
  984. return len(layer_specs) > 0 and all(
  985. isinstance(spec, FullAttentionSpec) for spec in layer_specs
  986. )
  987. def get_kv_cache_spec_kind(kv_cache_spec: KVCacheSpec) -> KVCacheSpecKind:
  988. if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
  989. inner_kinds = {
  990. get_kv_cache_spec_kind(spec)
  991. for spec in kv_cache_spec.kv_cache_specs.values()
  992. }
  993. if len(inner_kinds) == 1:
  994. return next(iter(inner_kinds))
  995. # A group is only formed when all members share one registered
  996. # uniform_type_base_spec, so UNKNOWN would discard what the merge
  997. # already established.
  998. base_specs = {
  999. KVCacheSpecRegistry.get_uniform_type_base_spec(spec)
  1000. for spec in kv_cache_spec.kv_cache_specs.values()
  1001. }
  1002. if len(base_specs) == 1 and next(iter(base_specs)) is FullAttentionSpec:
  1003. return KVCacheSpecKind.FULL_ATTENTION
  1004. return KVCacheSpecKind.UNKNOWN
  1005. # Keep subclass checks before base classes so specialized specs keep their
  1006. # more precise kind.
  1007. if isinstance(kv_cache_spec, SlidingWindowMLASpec):
  1008. return KVCacheSpecKind.SLIDING_WINDOW_MLA
  1009. if isinstance(kv_cache_spec, MLAAttentionSpec):
  1010. return KVCacheSpecKind.MLA_ATTENTION
  1011. if isinstance(kv_cache_spec, SinkFullAttentionSpec):
  1012. return KVCacheSpecKind.SINK_FULL_ATTENTION
  1013. if isinstance(kv_cache_spec, FullAttentionSpec):
  1014. return KVCacheSpecKind.FULL_ATTENTION
  1015. if isinstance(kv_cache_spec, ChunkedLocalAttentionSpec):
  1016. return KVCacheSpecKind.CHUNKED_LOCAL_ATTENTION
  1017. if isinstance(kv_cache_spec, SlidingWindowSpec):
  1018. return KVCacheSpecKind.SLIDING_WINDOW
  1019. if isinstance(kv_cache_spec, MambaSpec):
  1020. return KVCacheSpecKind.MAMBA
  1021. if isinstance(kv_cache_spec, EncoderOnlyAttentionSpec):
  1022. return KVCacheSpecKind.ENCODER_ONLY_ATTENTION
  1023. if isinstance(kv_cache_spec, CrossAttentionSpec):
  1024. return KVCacheSpecKind.CROSS_ATTENTION
  1025. return KVCacheSpecKind.UNKNOWN
  1026. def get_kv_cache_spec_sliding_window(kv_cache_spec: KVCacheSpec) -> int | None:
  1027. if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
  1028. inner_windows = {
  1029. get_kv_cache_spec_sliding_window(spec)
  1030. for spec in kv_cache_spec.kv_cache_specs.values()
  1031. }
  1032. return next(iter(inner_windows)) if len(inner_windows) == 1 else None
  1033. if isinstance(kv_cache_spec, SlidingWindowSpec):
  1034. return kv_cache_spec.sliding_window
  1035. return None
  1036. @dataclass
  1037. class KVCacheTensor:
  1038. """
  1039. A class for specifying how the workers should initialize the KV cache.
  1040. Placement of a set of same-shaped layers in the KV cache allocation.
  1041. Layer ``layers[l]``'s page for block ``b`` starts at
  1042. ``offset + l * layer_stride + b * block_stride`` bytes into the backing
  1043. allocation of ``size`` bytes. Layer-outermost layouts give each layer a
  1044. contiguous region (``layer_stride = page * num_blocks``,
  1045. ``block_stride = page``); block-outermost layouts make each block a
  1046. block of all layers' pages (``layer_stride = page``, ``block_stride`` =
  1047. the packed block). Tensors whose address ranges overlap
  1048. alias the same bytes: cache groups overlay each other, which is sound
  1049. because a block ID is owned by one group at a time.
  1050. """
  1051. size: int # total size of the backing allocation in bytes
  1052. layers: list[str] # layer names in L order
  1053. layer_stride: int
  1054. block_stride: int
  1055. offset: int = 0 # byte offset of layers[0]'s block 0
  1056. @dataclass
  1057. class KVCacheGroupSpec:
  1058. """
  1059. Represents a group of model layers that share the same KV cache block table.
  1060. These layers are regarded as one layer in the KV cache manager.
  1061. """
  1062. # The names of model layers in this group
  1063. layer_names: list[str]
  1064. # The KV cache spec of this manager layer
  1065. kv_cache_spec: KVCacheSpec
  1066. # Whether this group contains EAGLE/MTP draft attention layers.
  1067. is_eagle_group: bool = False
  1068. # Whether this group is part of the externally transferable KV state.
  1069. enable_kv_transfer: bool = True
  1070. @dataclass
  1071. class KVCacheConfig:
  1072. """
  1073. The KV cache configuration of a model.
  1074. """
  1075. num_blocks: int
  1076. """The number of KV cache blocks"""
  1077. kv_cache_tensors: list[KVCacheTensor]
  1078. """How should model runner initialize the KV cache tensors for each layer"""
  1079. kv_cache_groups: list[KVCacheGroupSpec]
  1080. """
  1081. The kv cache groups of the model.
  1082. For models with only one type of attention, there is only one group that
  1083. contains all layers.
  1084. For models with multiple types of attention, there will be multiple groups,
  1085. see `_get_kv_cache_config_uniform_page_size` for more details.
  1086. """
  1087. prefix_cache_retention_interval: int | None = None
  1088. """Resolved retention policy for local prefix-cache checkpoints."""
  1089. kv_cache_layout: str | None = None
  1090. """The KV cache layout resolved by the engine core, adopted by all workers."""
  1091. @cached_property
  1092. def transfer_group_ids(self) -> tuple[int, ...]:
  1093. """IDs of cache groups that participate in external KV transfer."""
  1094. return tuple(
  1095. group_id
  1096. for group_id, group in enumerate(self.kv_cache_groups)
  1097. if group.enable_kv_transfer
  1098. )
  1099. @cached_property
  1100. def transfer_groups(self) -> tuple[KVCacheGroupSpec, ...]:
  1101. """Cache groups that participate in external KV transfer."""
  1102. return tuple(
  1103. self.kv_cache_groups[group_id] for group_id in self.transfer_group_ids
  1104. )
  1105. @cached_property
  1106. def transfer_group_index_by_layer(self) -> dict[str, int]:
  1107. """Transfer-group tuple index for each participating layer."""
  1108. return {
  1109. layer_name: group_index
  1110. for group_index, group in enumerate(self.transfer_groups)
  1111. for layer_name in group.layer_names
  1112. }
  1113. def select_transfer_block_ids(
  1114. self, block_ids: Sequence[list[int]]
  1115. ) -> tuple[list[int], ...]:
  1116. """Select block IDs for externally transferable cache groups."""
  1117. if len(block_ids) != len(self.kv_cache_groups):
  1118. raise ValueError(
  1119. f"Expected {len(self.kv_cache_groups)} KV cache groups, "
  1120. f"got {len(block_ids)}."
  1121. )
  1122. return tuple(block_ids[group_id] for group_id in self.transfer_group_ids)
  1123. @property
  1124. def has_mamba_layers(self) -> bool:
  1125. return any(
  1126. isinstance(spec, MambaSpec)
  1127. for group in self.kv_cache_groups
  1128. for spec in iter_layer_specs(group.kv_cache_spec)
  1129. )
  1130. @property
  1131. def has_mixed_precision_kv_cache(self) -> bool:
  1132. """Whether attention groups store their KV cache at more than one precision."""
  1133. kv_cache_precisions: set[tuple[torch.dtype, KVQuantMode]] = set()
  1134. for group in self.kv_cache_groups:
  1135. kv_cache_precisions.update(
  1136. (spec.dtype, spec.kv_quant_mode)
  1137. for spec in iter_layer_specs(group.kv_cache_spec)
  1138. if isinstance(spec, AttentionSpec)
  1139. )
  1140. return len(kv_cache_precisions) > 1
  1141. @property
  1142. def needs_kv_cache_zeroing(self) -> bool:
  1143. """Whether newly allocated KV cache blocks must be zeroed before use.
  1144. Required for Mamba layers, whose state is read before it is fully written
  1145. (#35219), and for mixed-precision caches, where a block reused across
  1146. groups can be reinterpreted under a different precision and decode stale
  1147. bytes to NaN/Inf. Uniform-precision caches skip zeroing.
  1148. """
  1149. return self.has_mamba_layers or self.has_mixed_precision_kv_cache