kv_cache_interface.py 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
  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, field, 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. class SparseCacheRole(str, Enum):
  122. SPARSE = "sparse"
  123. INDEXER = "indexer"
  124. @dataclass(frozen=True)
  125. class KVCacheSpec:
  126. """A base class for specifying the KV cache format of one layer."""
  127. # number of tokens in a block
  128. block_size: int
  129. block_stride_alignment: int | None = field(default=None, kw_only=True)
  130. """Required byte alignment between physical blocks, including packed layers."""
  131. def __post_init__(self):
  132. if self.block_stride_alignment is not None and self.block_stride_alignment <= 0:
  133. raise ValueError("block_stride_alignment must be positive")
  134. @property
  135. def prefix_cacheable(self) -> bool:
  136. """Whether this spec's group participates in prefix caching."""
  137. return True
  138. @property
  139. def prefix_replay_tokens(self) -> int:
  140. """DeepSeek-V4.1 only: bounded replay. Currently only for DSV41 SWA."""
  141. return 0
  142. @property
  143. def num_heads(self) -> int:
  144. raise NotImplementedError
  145. @property
  146. def tokens_per_state(self) -> int | Fraction:
  147. raise NotImplementedError
  148. @property
  149. def state_content_size_bytes(self) -> int:
  150. raise NotImplementedError
  151. @property
  152. def page_size_bytes(self) -> int:
  153. """The size of a page with `block_size` tokens in bytes.
  154. Returns:
  155. The page size
  156. """
  157. raise NotImplementedError
  158. @property
  159. def num_states(self) -> int:
  160. return self.get_num_kernel_states(self.block_size)
  161. def get_num_kernel_states(self, kernel_block_size: int) -> int:
  162. if self.tokens_per_state > 0:
  163. return kernel_block_size // self.tokens_per_state
  164. return 1
  165. @property
  166. def block_table_token_alignment(self) -> int | None:
  167. return 128
  168. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  169. """The maximum possible memory usage of this KV cache in bytes.
  170. Returns:
  171. The KV cache size in bytes
  172. """
  173. raise NotImplementedError
  174. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  175. """The number of block table entries needed per request, i.e. the row
  176. length of the worker-side block table for this cache group.
  177. Args:
  178. vllm_config: The vllm config.
  179. max_len: The maximum sequence length to size for, including the
  180. encoder length for encoder-decoder models.
  181. """
  182. return cdiv(max_len, self.block_size)
  183. def copy_with_new_block_size(self, block_size: int) -> Self:
  184. """Create a new KVCacheSpec from self but replacing the block size."""
  185. return replace(self, block_size=block_size)
  186. @classmethod
  187. def merge(cls, specs: list[Self]) -> Self:
  188. """Merge a list of KVCacheSpec objects into a single KVCacheSpec object."""
  189. if not all(spec == specs[0] for spec in specs[1:]):
  190. raise AssertionError(
  191. "All layers in the same KV cache group must be the same."
  192. )
  193. return copy.deepcopy(specs[0])
  194. def is_uniform_with_collection(
  195. self, kv_cache_specs: dict[str, KVCacheSpec]
  196. ) -> bool:
  197. """Whether this KVCacheSpec is uniform with all specs of all layers."""
  198. uniform_type_base_spec = KVCacheSpecRegistry.get_uniform_type_base_spec(self)
  199. assert uniform_type_base_spec is not None, (
  200. f"Unsupported KV cache spec type: {type(self)}. "
  201. "Please register it using @register_kv_cache_spec decorator."
  202. )
  203. return all(
  204. isinstance(spec, uniform_type_base_spec) for spec in kv_cache_specs.values()
  205. )
  206. @property
  207. def has_layer_views(self) -> bool:
  208. """Whether generic allocation creates per-layer cache views.
  209. Specs without a per-layer shape keep the raw backing tensor and lay it
  210. out themselves. They also have no attention module of their own, so
  211. they take no part in attention-backend or kernel-block selection.
  212. """
  213. return True
  214. @property
  215. def uses_slot_mapping(self) -> bool:
  216. """Whether the worker computes a per-token slot mapping for this spec.
  217. Specs that address their pages themselves (raw storage, ring buffers)
  218. take no slot mapping row.
  219. """
  220. return True
  221. def group_kernel_blocks(cache: torch.Tensor, num_blocks: int) -> torch.Tensor:
  222. """View a kernel-block-granular layer cache with manager blocks as dim 0.
  223. Kernel block splitting subdivides each manager block into uniformly strided
  224. kernel blocks, so grouping is a pure view: ``(num_blocks * ratio, ...)``
  225. """
  226. if cache.shape[0] == num_blocks:
  227. return cache
  228. assert cache.shape[0] % num_blocks == 0
  229. return cache.unflatten(0, (num_blocks, -1))
  230. def compute_layer_kv_cache_shape_bytes(
  231. spec: KVCacheSpec,
  232. num_blocks: int,
  233. kernel_block_size: int | None = None,
  234. ) -> tuple[int, ...]:
  235. """Return the 4D logical shape ``(B, H, N, C)`` where C is in bytes."""
  236. bs = kernel_block_size if kernel_block_size is not None else spec.block_size
  237. assert spec.block_size % bs == 0, (
  238. f"Kernel block size {bs} must divide KV cache block size {spec.block_size}."
  239. )
  240. blocks_per_page = spec.block_size // bs
  241. return (
  242. num_blocks * blocks_per_page,
  243. spec.num_heads,
  244. spec.get_num_kernel_states(bs),
  245. spec.state_content_size_bytes,
  246. )
  247. def compute_layout_strides(
  248. spec: KVCacheSpec,
  249. num_blocks: int,
  250. num_layers: int,
  251. layout: KVCacheLayout,
  252. kernel_block_size: int | None = None,
  253. fixed_strides: tuple[int | None, ...] = (None,) * 5,
  254. ) -> tuple[int, ...]:
  255. """Byte strides in logical ``[L, B, H, N, C]`` axis order."""
  256. assert len(fixed_strides) == 5
  257. assert all(stride is None or stride > 0 for stride in fixed_strides)
  258. shape = (
  259. num_layers,
  260. *compute_layer_kv_cache_shape_bytes(spec, num_blocks, kernel_block_size),
  261. )
  262. order = layout.stride_order
  263. padded_page_size = getattr(spec, "page_size_padded", None)
  264. if padded_page_size is not None:
  265. assert kernel_block_size is None or kernel_block_size == spec.block_size, (
  266. "Padded KV pages do not support kernel block splitting."
  267. )
  268. page_grid_end = max(order.index(_DIM_L), order.index(_DIM_B)) + 1
  269. page_grid_shape = tuple(shape[dim] for dim in order[:page_grid_end])
  270. assert prod(page_grid_shape) == num_layers * num_blocks, (
  271. "Page padding requires dimensions outside the page tail to be L, B, "
  272. f"or singleton; got {layout.name} with shape {shape}."
  273. )
  274. strides = [0] * 5
  275. current_stride = 1
  276. for physical_idx, dim in reversed(tuple(enumerate(order))):
  277. if padded_page_size is not None and physical_idx == page_grid_end - 1:
  278. current_stride = max(current_stride, padded_page_size)
  279. assert current_stride % padded_page_size == 0
  280. strides[dim] = fixed_strides[dim] or current_stride
  281. current_stride = strides[dim] * shape[dim]
  282. return tuple(strides)
  283. def create_kv_cache_views(
  284. raw: torch.Tensor,
  285. spec: KVCacheSpec,
  286. num_blocks: int,
  287. layout: KVCacheLayout,
  288. kv_cache_tensor: KVCacheTensor,
  289. kernel_block_size: int | None = None,
  290. ) -> list[torch.Tensor]:
  291. """View a flat int8 buffer as one 4D ``[B, H, N, C]`` view per layer.
  292. Block ``b`` of layer ``l`` starts at the tensor offset plus its layer and
  293. block stride contributions.
  294. """
  295. num_layers = len(kv_cache_tensor.layers)
  296. layer_stride = kv_cache_tensor.layer_stride
  297. block_stride = kv_cache_tensor.block_stride
  298. shape_bytes = compute_layer_kv_cache_shape_bytes(
  299. spec, num_blocks, kernel_block_size
  300. )
  301. ratio = shape_bytes[0] // num_blocks
  302. if ratio > 1:
  303. # Kernel blocks subdivide a manager block into `ratio` equal pieces, so
  304. # they sit a constant stride apart only if a block is one dense page: no
  305. # padding at its end, and no other layer's page before the next block.
  306. dense_page_size = prod(compute_layer_kv_cache_shape_bytes(spec, 1)[1:])
  307. if block_stride != dense_page_size:
  308. raise ValueError(
  309. f"The resolved KV cache layout ({layout.name}) does not store "
  310. "blocks as dense, unpadded pages (block stride "
  311. f"{block_stride} != page {dense_page_size}), so a manager "
  312. f"block cannot be split into {ratio} kernel blocks of "
  313. f"{kernel_block_size} tokens. Reduce --block-size to "
  314. f"{kernel_block_size} or set VLLM_KV_CACHE_LAYOUT to a "
  315. "layer-compact layout (e.g. LBNHC)."
  316. )
  317. assert block_stride % ratio == 0, (
  318. f"Block stride {block_stride} must divide into {ratio} equal kernel blocks."
  319. )
  320. block_stride //= ratio
  321. logical_shape = (num_layers, *shape_bytes)
  322. strides = compute_layout_strides(
  323. spec,
  324. num_blocks,
  325. num_layers,
  326. layout,
  327. kernel_block_size,
  328. fixed_strides=(layer_stride, block_stride, None, None, None),
  329. )
  330. dtype = getattr(spec, "dtype", None)
  331. view_5d = torch.as_strided(
  332. raw,
  333. size=logical_shape,
  334. stride=strides,
  335. storage_offset=raw.storage_offset() + kv_cache_tensor.offset,
  336. )
  337. views = []
  338. for layer_idx in range(num_layers):
  339. cache_logical = view_5d[layer_idx]
  340. if dtype is not None:
  341. cache_logical = cache_logical.view(dtype)
  342. views.append(cache_logical)
  343. return views
  344. @dataclass(frozen=True, kw_only=True)
  345. class HiSparseHotSpec(KVCacheSpec):
  346. """Ephemeral per-request HiSparse hot-cache allocation."""
  347. page_size: int
  348. blocks_per_request: int
  349. @property
  350. def prefix_cacheable(self) -> bool:
  351. return False
  352. @property
  353. def page_size_bytes(self) -> int:
  354. return self.page_size
  355. @property
  356. def block_table_token_alignment(self) -> int | None:
  357. return None
  358. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  359. return self.blocks_per_request * self.page_size
  360. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  361. return self.blocks_per_request
  362. @property
  363. def has_layer_views(self) -> bool:
  364. return False
  365. @property
  366. def uses_slot_mapping(self) -> bool:
  367. return False
  368. @dataclass(frozen=True, kw_only=True)
  369. class HiSparseResidentSpec(KVCacheSpec):
  370. """Reclaimable GPU-resident pages for host-backed HiSparse KV."""
  371. page_size: int
  372. @property
  373. def prefix_cacheable(self) -> bool:
  374. return False
  375. @property
  376. def page_size_bytes(self) -> int:
  377. return self.page_size
  378. def max_admission_blocks_per_request(
  379. self, max_in_flight_tokens: int, max_model_len: int
  380. ) -> int:
  381. num_tokens = min(max_in_flight_tokens, max_model_len)
  382. return cdiv(num_tokens, self.block_size)
  383. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  384. return cdiv(vllm_config.model_config.max_model_len, self.block_size) * (
  385. self.page_size
  386. )
  387. @property
  388. def has_layer_views(self) -> bool:
  389. return False
  390. @dataclass(frozen=True, kw_only=True)
  391. class AttentionSpec(KVCacheSpec):
  392. num_kv_heads: int
  393. head_size: int
  394. dtype: torch.dtype
  395. head_size_v: int = None # type: ignore[assignment]
  396. kv_quant_mode: KVQuantMode = KVQuantMode.NONE
  397. page_size_padded: int | None = None
  398. num_head_slots: int | None = None
  399. """H of the logical ``[B, H, N, C]`` page when packing diverges from one
  400. slot per KV head. None means one slot per KV head. Published by the backend.
  401. """
  402. state_content_bytes: int | None = None
  403. """C in bytes when packed; None means dense K/V content."""
  404. tokens_per_state: int | Fraction = 1
  405. """Tokens covered by one stored state. Ints > 1 compress multiple tokens
  406. into one state (DSv4 sparse MLA); fractions < 1 store multiple states per
  407. token (Whisper block pooling: ``Fraction(1, block_pool_size)``)."""
  408. def __post_init__(self):
  409. super().__post_init__()
  410. if self.head_size_v is None:
  411. object.__setattr__(self, "head_size_v", self.head_size)
  412. @property
  413. def num_heads(self) -> int:
  414. if self.num_head_slots is not None:
  415. return self.num_head_slots
  416. return self.num_kv_heads
  417. @property
  418. def state_content_size_bytes(self) -> int:
  419. """Bytes per (head slot, stored state) cell of the page."""
  420. if self.state_content_bytes is not None:
  421. return self.state_content_bytes
  422. return (self.head_size + self.head_size_v) * get_dtype_size(self.dtype)
  423. @property
  424. def unpadded_page_size_bytes(self) -> int:
  425. return self.num_heads * self.num_states * self.state_content_size_bytes
  426. @property
  427. def page_size_bytes(self) -> int:
  428. if self.page_size_padded is not None:
  429. assert self.page_size_padded >= self.unpadded_page_size_bytes
  430. return self.page_size_padded
  431. return self.unpadded_page_size_bytes
  432. @property
  433. def real_page_size_bytes(self) -> int:
  434. """Alias of ``unpadded_page_size_bytes``
  435. TODO(lucas): follow up with TPU backend to see if we can remove this property.
  436. """
  437. return self.unpadded_page_size_bytes
  438. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  439. parallel_config = vllm_config.parallel_config
  440. kv_shard_count = parallel_config.decode_context_parallel_size
  441. return cdiv(max_len, self.block_size * kv_shard_count)
  442. @dataclass(frozen=True, kw_only=True)
  443. class FullAttentionSpec(AttentionSpec):
  444. """When hybrid allocator is disabled and the model contains both full
  445. attention layers and sliding window attention layers, sliding
  446. window attention are regarded as full attention in KV cache manager
  447. (blocks are allocated for all tokens), while computed as sliding window
  448. attention in model runner.
  449. In this case, we use FullAttentionSpec and record the sliding window size.
  450. """
  451. sliding_window: int | None = None
  452. """
  453. Default to None for not using sliding window attention.
  454. """
  455. attention_chunk_size: int | None = None
  456. non_causal: bool = False
  457. """
  458. Whether the layer attends non-causally (e.g. Prefix LM). Carried on the
  459. spec so the engine core, which collects specs from all workers before the
  460. scheduler is built, can adjust scheduling policy (chunked prefill / prefix
  461. caching) regardless of tensor-parallel layout. It does not affect the KV
  462. cache layout itself.
  463. """
  464. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  465. max_model_len = vllm_config.model_config.max_model_len
  466. dcp_world_size = vllm_config.parallel_config.decode_context_parallel_size
  467. if dcp_world_size > 1:
  468. max_model_len = cdiv(max_model_len, dcp_world_size)
  469. return cdiv(max_model_len, self.block_size) * self.page_size_bytes
  470. @classmethod
  471. def merge_window_sizes(cls, window_sizes: set[int]) -> int | None:
  472. if len(window_sizes) == 0:
  473. return None
  474. elif len(window_sizes) == 1:
  475. return window_sizes.pop()
  476. else:
  477. raise ValueError(
  478. "All attention layers in the same KV cache group must have the "
  479. "same window size."
  480. )
  481. @classmethod
  482. def merge(cls, specs: list[Self]) -> Self:
  483. """Merge a list of FullAttentionSpec objects into a single
  484. FullAttentionSpec object.
  485. """
  486. assert all(isinstance(spec, FullAttentionSpec) for spec in specs), (
  487. "All attention layers in the same KV cache group must be FullAttentionSpec."
  488. )
  489. sliding_window = set(
  490. spec.sliding_window for spec in specs if spec.sliding_window is not None
  491. )
  492. attention_chunk_size = set(
  493. spec.attention_chunk_size
  494. for spec in specs
  495. if spec.attention_chunk_size is not None
  496. )
  497. assert not any(isinstance(spec, MLAAttentionSpec) for spec in specs), (
  498. "MLAAttentionSpec should be merged in MLAAttentionSpec.merge"
  499. )
  500. merged_spec = cls(
  501. block_size=specs[0].block_size,
  502. block_stride_alignment=specs[0].block_stride_alignment,
  503. num_kv_heads=specs[0].num_kv_heads,
  504. head_size=specs[0].head_size,
  505. head_size_v=specs[0].head_size_v,
  506. dtype=specs[0].dtype,
  507. kv_quant_mode=specs[0].kv_quant_mode,
  508. page_size_padded=specs[0].page_size_padded,
  509. num_head_slots=specs[0].num_head_slots,
  510. state_content_bytes=specs[0].state_content_bytes,
  511. tokens_per_state=specs[0].tokens_per_state,
  512. sliding_window=cls.merge_window_sizes(sliding_window),
  513. attention_chunk_size=cls.merge_window_sizes(attention_chunk_size),
  514. # If any layer in the group is non-causal, treat the group as
  515. # non-causal so the engine core disables incompatible scheduling.
  516. non_causal=any(spec.non_causal for spec in specs),
  517. )
  518. for spec in specs:
  519. for f in fields(AttentionSpec):
  520. assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
  521. "All attention layers in the same KV cache group must have "
  522. "the same attention spec."
  523. )
  524. assert (merged_spec.sliding_window is not None) + (
  525. merged_spec.attention_chunk_size is not None
  526. ) <= 1, (
  527. "Model with both sliding window layers and chunked local attention "
  528. "layers is not supported."
  529. )
  530. return merged_spec
  531. def _apply_alignment_padding(spec: MLAAttentionSpec | SlidingWindowMLASpec):
  532. if spec.alignment is None:
  533. return
  534. actual_page_size = spec.real_page_size_bytes
  535. padded_page_size = round_up(actual_page_size, spec.alignment)
  536. if padded_page_size != actual_page_size:
  537. object.__setattr__(spec, "page_size_padded", padded_page_size)
  538. @dataclass(frozen=True, kw_only=True)
  539. class MLAAttentionSpec(FullAttentionSpec):
  540. # TODO(Lucas/Chen): less hacky way to do this
  541. cache_dtype_str: str | None = None
  542. # DeepseekV4 only fields. Non-DeepseekV4 MLA models leave these at defaults.
  543. alignment: int | None = None # Default to None for no padding.
  544. model_version: str | None = None
  545. cache_role: SparseCacheRole = SparseCacheRole.SPARSE
  546. is_index_group_leader: bool = False
  547. storage_block_size: int | None = None
  548. """Token width used to view storage when it differs from the kernel block."""
  549. # Group capability enabled when any member flattens a non-causal query block
  550. # into decode rows. Runtime metadata still selects causal vs. non-causal mode.
  551. non_causal_multi_token_decode: bool = False
  552. # MLA stores a single latent vector per state; there is no separate V.
  553. head_size_v: int = 0
  554. def __post_init__(self):
  555. super().__post_init__()
  556. _apply_alignment_padding(self)
  557. @classmethod
  558. def merge(cls, specs: list[Self]) -> Self:
  559. assert all(isinstance(spec, MLAAttentionSpec) for spec in specs), (
  560. "All attention layers in the same KV cache group must be MLAAttentionSpec."
  561. )
  562. cache_dtype_str_set = set(spec.cache_dtype_str for spec in specs)
  563. tokens_per_state_set = set(spec.tokens_per_state for spec in specs)
  564. model_version_set = set(spec.model_version for spec in specs)
  565. cache_role_set = {spec.cache_role for spec in specs}
  566. index_group_leader_set = {spec.is_index_group_leader for spec in specs}
  567. storage_block_size_set = set(spec.storage_block_size for spec in specs)
  568. block_stride_alignment_set = {spec.block_stride_alignment for spec in specs}
  569. assert (
  570. len(cache_dtype_str_set) == 1
  571. and len(tokens_per_state_set) == 1
  572. and len(model_version_set) == 1
  573. and len(cache_role_set) == 1
  574. and len(index_group_leader_set) == 1
  575. and len(storage_block_size_set) == 1
  576. and len(block_stride_alignment_set) == 1
  577. ), (
  578. "All attention layers in the same KV cache group must use the same "
  579. "quantization method, tokens per state, model version, cache role, "
  580. "index-sharing role, storage block size and block stride alignment."
  581. )
  582. merged_spec = cls(
  583. block_size=specs[0].block_size,
  584. num_kv_heads=specs[0].num_kv_heads,
  585. head_size=specs[0].head_size,
  586. dtype=specs[0].dtype,
  587. kv_quant_mode=specs[0].kv_quant_mode,
  588. page_size_padded=specs[0].page_size_padded,
  589. num_head_slots=specs[0].num_head_slots,
  590. state_content_bytes=specs[0].state_content_bytes,
  591. cache_dtype_str=cache_dtype_str_set.pop(),
  592. tokens_per_state=tokens_per_state_set.pop(),
  593. model_version=model_version_set.pop(),
  594. cache_role=cache_role_set.pop(),
  595. is_index_group_leader=index_group_leader_set.pop(),
  596. storage_block_size=storage_block_size_set.pop(),
  597. block_stride_alignment=block_stride_alignment_set.pop(),
  598. non_causal_multi_token_decode=any(
  599. spec.non_causal_multi_token_decode for spec in specs
  600. ),
  601. )
  602. for spec in specs:
  603. for f in fields(AttentionSpec):
  604. assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
  605. "All attention layers in the same KV cache group must have "
  606. "the same attention spec."
  607. )
  608. return merged_spec
  609. @dataclass(frozen=True, kw_only=True)
  610. class HiddenStateCacheSpec(MLAAttentionSpec):
  611. """Marker for hidden-state cache layers used by extract_hidden_states."""
  612. pass
  613. @dataclass(frozen=True, kw_only=True)
  614. class RSWASpec(FullAttentionSpec):
  615. """KV cache spec for Reference Sliding Window Attention (R-SWA).
  616. Prefill (image + text prompt) tokens are always globally visible.
  617. Only the last ``rswa_window`` generated tokens are kept in the KV cache;
  618. gap blocks (between the prefill tail and the current decode window) are
  619. evicted during each decode step to bound memory at
  620. O(prefix_blocks + window_blocks).
  621. """
  622. rswa_window: int
  623. @classmethod
  624. def merge(cls, specs: list[RSWASpec]) -> RSWASpec:
  625. assert all(isinstance(spec, RSWASpec) for spec in specs), (
  626. "All attention layers in the same KV cache group must be RSWASpec."
  627. )
  628. rswa_windows = {spec.rswa_window for spec in specs}
  629. assert len(rswa_windows) == 1, (
  630. f"All R-SWA layers must share the same rswa_window, got {rswa_windows}"
  631. )
  632. # Delegate common field merging to the parent, then reattach rswa_window.
  633. base = FullAttentionSpec.merge(specs) # type: ignore[arg-type]
  634. return cls(
  635. block_size=base.block_size,
  636. block_stride_alignment=base.block_stride_alignment,
  637. num_kv_heads=base.num_kv_heads,
  638. head_size=base.head_size,
  639. head_size_v=base.head_size_v,
  640. dtype=base.dtype,
  641. kv_quant_mode=base.kv_quant_mode,
  642. page_size_padded=base.page_size_padded,
  643. num_head_slots=base.num_head_slots,
  644. state_content_bytes=base.state_content_bytes,
  645. tokens_per_state=base.tokens_per_state,
  646. sliding_window=base.sliding_window,
  647. attention_chunk_size=base.attention_chunk_size,
  648. non_causal=base.non_causal,
  649. rswa_window=rswa_windows.pop(),
  650. )
  651. @dataclass(frozen=True, kw_only=True)
  652. class ChunkedLocalAttentionSpec(AttentionSpec):
  653. attention_chunk_size: int
  654. def max_admission_blocks_per_request(
  655. self, max_in_flight_tokens: int, max_model_len: int
  656. ) -> int:
  657. """Per-request admission cap, in blocks.
  658. Single source of truth for both startup pool sizing
  659. (`max_memory_usage_bytes`) and the runtime admission gate, so requests
  660. admitted by startup can also be admitted at runtime.
  661. `max_in_flight_tokens` is the max tokens scheduled but not yet settled
  662. (one batch per concurrent step); see `VllmConfig.max_in_flight_tokens`.
  663. """
  664. # During chunked prefill, we hold KV for at most one chunk window plus
  665. # the in-flight tokens, since frees happen on the processed-token basis.
  666. num_tokens = min(
  667. self.attention_chunk_size + max_in_flight_tokens, max_model_len
  668. )
  669. return cdiv(num_tokens, self.block_size)
  670. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  671. max_blocks = self.max_admission_blocks_per_request(
  672. max_in_flight_tokens=vllm_config.max_in_flight_tokens,
  673. max_model_len=vllm_config.model_config.max_model_len,
  674. )
  675. return max_blocks * self.page_size_bytes
  676. def is_uniform_with_collection(
  677. self, kv_cache_specs: dict[str, KVCacheSpec]
  678. ) -> bool:
  679. return all(
  680. isinstance(spec, ChunkedLocalAttentionSpec)
  681. and spec.attention_chunk_size == self.attention_chunk_size
  682. for spec in kv_cache_specs.values()
  683. )
  684. @dataclass(frozen=True, kw_only=True)
  685. class SlidingWindowSpec(AttentionSpec):
  686. sliding_window: int
  687. # The trailing edge of the window is extended by ``extra_retained_tokens``
  688. # so that those extra trailing tokens' blocks are retained (but not
  689. # attended). This is needed for multi-module spec decoding which can
  690. # re-prefill the last num_spec_prefill_tokens - 1 tokens from the end
  691. # of the sequence, and thus needs to delay freeing/caching of blocks.
  692. extra_retained_tokens: int = 0
  693. def max_admission_blocks_per_request(
  694. self, max_in_flight_tokens: int, max_model_len: int
  695. ) -> int:
  696. """Per-request admission cap, in blocks.
  697. Single source of truth for both startup pool sizing
  698. (`max_memory_usage_bytes`) and the runtime admission gate. Per-request
  699. real-held blocks plateau at this bound because
  700. `SlidingWindowManager.remove_skipped_blocks` runs from `allocate_slots`
  701. before each chunk's `get_num_blocks_to_allocate`.
  702. `max_in_flight_tokens` is the max tokens scheduled but not yet settled
  703. (one batch per concurrent step); see `VllmConfig.max_in_flight_tokens`.
  704. """
  705. # During chunked prefill, we hold KV for the last `sliding_window-1`
  706. # computed tokens plus the in-flight tokens (frees happen on the
  707. # processed-token basis); never more than `max_model_len`. An additional
  708. # `extra_retained_tokens` trailing tokens are kept alive below the
  709. # window for multi-module spec decoding, and must be accounted here too.
  710. num_tokens = min(
  711. self.sliding_window - 1 + self.extra_retained_tokens + max_in_flight_tokens,
  712. max_model_len,
  713. )
  714. # +1 because the sliding window may not start from the beginning of
  715. # the block. E.g. block size 4 and num_token 4 needs two blocks
  716. # [XXCD][EF] to store the 6-token window [CDEF].
  717. return cdiv(num_tokens, self.block_size) + 1
  718. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  719. assert vllm_config.parallel_config.decode_context_parallel_size == 1, (
  720. "DCP not support sliding window."
  721. )
  722. max_blocks = self.max_admission_blocks_per_request(
  723. max_in_flight_tokens=vllm_config.max_in_flight_tokens,
  724. max_model_len=vllm_config.model_config.max_model_len,
  725. )
  726. return max_blocks * self.page_size_bytes
  727. def is_uniform_with_collection(
  728. self, kv_cache_specs: dict[str, KVCacheSpec]
  729. ) -> bool:
  730. return all(
  731. isinstance(spec, SlidingWindowSpec)
  732. and spec.sliding_window == self.sliding_window
  733. for spec in kv_cache_specs.values()
  734. )
  735. @dataclass(frozen=True, kw_only=True)
  736. class CircularBufferSpec(AttentionSpec):
  737. """One block per request holding the raw keys of the token group that
  738. is still being compressed.
  739. ``block_size`` is the ring capacity. It must exceed the compression ratio
  740. by the speculative lookahead: a speculative step stores all of its rows,
  741. drafts included, before acceptance is known, while the next step still
  742. reads the open group's committed keys from the ring.
  743. """
  744. @property
  745. def block_table_token_alignment(self) -> int | None:
  746. return None
  747. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  748. # The ring occupies one block per request for its whole lifetime.
  749. del vllm_config
  750. return self.page_size_bytes
  751. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  752. del vllm_config, max_len
  753. return 1
  754. def is_uniform_with_collection(
  755. self, kv_cache_specs: dict[str, KVCacheSpec]
  756. ) -> bool:
  757. return all(
  758. isinstance(spec, CircularBufferSpec) for spec in kv_cache_specs.values()
  759. )
  760. @property
  761. def prefix_cacheable(self) -> bool:
  762. return False
  763. @property
  764. def uses_slot_mapping(self) -> bool:
  765. return False
  766. @dataclass(frozen=True, kw_only=True)
  767. class SlidingWindowMLASpec(SlidingWindowSpec):
  768. """Sliding window attention with MLA cache format."""
  769. cache_dtype_str: str | None = None
  770. # DeepseekV4-only: see MLAAttentionSpec.model_version.
  771. alignment: int | None = None # Default to None for no padding.
  772. model_version: str | None = None
  773. bounded_replay: bool = False
  774. # MLA stores a single latent vector per state; there is no separate V.
  775. head_size_v: int = 0
  776. def __post_init__(self):
  777. assert self.model_version in (None, "deepseek_v4"), (
  778. f"Unsupported model version: {self.model_version}"
  779. )
  780. super().__post_init__()
  781. _apply_alignment_padding(self)
  782. @property
  783. def prefix_cacheable(self) -> bool:
  784. return not self.bounded_replay
  785. @property
  786. def prefix_replay_tokens(self) -> int:
  787. return self.sliding_window if self.bounded_replay else 0
  788. @classmethod
  789. def merge(cls, specs: list[Self]) -> Self:
  790. assert all(isinstance(spec, SlidingWindowMLASpec) for spec in specs), (
  791. "All attention layers in the same KV cache group must be "
  792. "SlidingWindowMLASpec."
  793. )
  794. cache_dtype_str_set = set(spec.cache_dtype_str for spec in specs)
  795. tokens_per_state_set = set(spec.tokens_per_state for spec in specs)
  796. model_version_set = set(spec.model_version for spec in specs)
  797. sliding_window_set = set(spec.sliding_window for spec in specs)
  798. extra_retained_set = set(spec.extra_retained_tokens for spec in specs)
  799. bounded_replay_set = set(spec.bounded_replay for spec in specs)
  800. block_stride_alignment_set = {spec.block_stride_alignment for spec in specs}
  801. assert (
  802. len(cache_dtype_str_set) == 1
  803. and len(tokens_per_state_set) == 1
  804. and len(model_version_set) == 1
  805. and len(sliding_window_set) == 1
  806. and len(extra_retained_set) == 1
  807. and len(bounded_replay_set) == 1
  808. and len(block_stride_alignment_set) == 1
  809. ), (
  810. "All attention layers in the same KV cache group must use the same "
  811. "quantization method, tokens per state, model version, sliding "
  812. "window size, retained token count, and replay policy."
  813. )
  814. return cls(
  815. block_size=specs[0].block_size,
  816. block_stride_alignment=block_stride_alignment_set.pop(),
  817. num_kv_heads=specs[0].num_kv_heads,
  818. head_size=specs[0].head_size,
  819. dtype=specs[0].dtype,
  820. page_size_padded=specs[0].page_size_padded,
  821. num_head_slots=specs[0].num_head_slots,
  822. state_content_bytes=specs[0].state_content_bytes,
  823. sliding_window=sliding_window_set.pop(),
  824. extra_retained_tokens=extra_retained_set.pop(),
  825. cache_dtype_str=cache_dtype_str_set.pop(),
  826. tokens_per_state=tokens_per_state_set.pop(),
  827. model_version=model_version_set.pop(),
  828. bounded_replay=bounded_replay_set.pop(),
  829. )
  830. def is_uniform_with_collection(
  831. self, kv_cache_specs: dict[str, KVCacheSpec]
  832. ) -> bool:
  833. return all(
  834. isinstance(spec, SlidingWindowMLASpec)
  835. and spec.sliding_window == self.sliding_window
  836. and spec.bounded_replay == self.bounded_replay
  837. for spec in kv_cache_specs.values()
  838. )
  839. @dataclass(frozen=True, kw_only=True)
  840. class KpoolTailSpec(SlidingWindowSpec):
  841. """One-block circular scratch cache for a kpool indexer's raw tail."""
  842. def max_admission_blocks_per_request(
  843. self, max_in_flight_tokens: int, max_model_len: int
  844. ) -> int:
  845. return 1
  846. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  847. return 1
  848. def is_uniform_with_collection(
  849. self, kv_cache_specs: dict[str, KVCacheSpec]
  850. ) -> bool:
  851. return all(isinstance(spec, KpoolTailSpec) for spec in kv_cache_specs.values())
  852. @property
  853. def prefix_cacheable(self) -> bool:
  854. return False
  855. @property
  856. def uses_slot_mapping(self) -> bool:
  857. return False
  858. @dataclass(frozen=True)
  859. class MambaSpec(KVCacheSpec):
  860. shapes: tuple[tuple[int, ...], ...]
  861. dtypes: tuple[torch.dtype, ...]
  862. page_size_padded: int | None = None
  863. mamba_type: MambaAttentionBackendEnum = MambaAttentionBackendEnum.MAMBA2
  864. mamba_cache_mode: str = "none"
  865. num_speculative_blocks: int = 0
  866. num_prefill_checkpoint_blocks: int = 0
  867. prefill_checkpoint_alignment: int | None = None
  868. num_heads: int = 1
  869. tokens_per_state: int = -1
  870. # False: the state is sharded across TP ranks (e.g. GDN). True: every TP
  871. # rank holds the full state (e.g. the replicated PLE conv state).
  872. tp_replicated: bool = False
  873. @property
  874. def state_content_size_bytes(self) -> int:
  875. return sum(
  876. prod(shape) * get_dtype_size(dtype)
  877. for (shape, dtype) in zip(self.shapes, self.dtypes)
  878. )
  879. @property
  880. def real_page_size_bytes(self) -> int:
  881. return self.state_content_size_bytes
  882. @property
  883. def page_size_bytes(self) -> int:
  884. page_size = sum(
  885. prod(shape) * get_dtype_size(dtype)
  886. for (shape, dtype) in zip(self.shapes, self.dtypes)
  887. )
  888. if self.page_size_padded is not None:
  889. assert self.page_size_padded >= page_size
  890. return self.page_size_padded
  891. return page_size
  892. @property
  893. def block_table_token_alignment(self) -> int | None:
  894. return None
  895. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  896. if vllm_config.cache_config.mamba_cache_mode == "all":
  897. max_model_len = vllm_config.model_config.max_model_len
  898. return (
  899. cdiv(max_model_len, self.block_size) + self.num_speculative_blocks
  900. ) * self.page_size_bytes
  901. elif vllm_config.cache_config.mamba_cache_mode == "align":
  902. return self.page_size_bytes * (
  903. 2 + self.num_speculative_blocks + self.num_prefill_checkpoint_blocks
  904. )
  905. else:
  906. return self.page_size_bytes * (1 + self.num_speculative_blocks)
  907. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  908. # Mamba state is replicated across DCP/PCP ranks, never sharded, so
  909. # no CP scaling applies.
  910. if vllm_config.cache_config.mamba_cache_mode == "align":
  911. # Block table rows are position-indexed over the full sequence
  912. # even though only 2 + num_speculative_blocks state blocks are
  913. # resident at a time (earlier states are nulled out by
  914. # remove_skipped_blocks), so the row length must cover max_len
  915. # rather than max_memory_usage_bytes.
  916. return cdiv(max_len, self.block_size) + self.num_speculative_blocks
  917. return cdiv(self.max_memory_usage_bytes(vllm_config), self.page_size_bytes)
  918. def is_uniform_with_collection(
  919. self, kv_cache_specs: dict[str, KVCacheSpec]
  920. ) -> bool:
  921. return all(
  922. isinstance(spec, MambaSpec)
  923. and spec.num_speculative_blocks == self.num_speculative_blocks
  924. and spec.num_prefill_checkpoint_blocks == self.num_prefill_checkpoint_blocks
  925. and spec.prefill_checkpoint_alignment == self.prefill_checkpoint_alignment
  926. and spec.page_size_bytes == self.page_size_bytes
  927. and spec.tp_replicated == self.tp_replicated
  928. for spec in kv_cache_specs.values()
  929. )
  930. def get_mamba_prefill_checkpoint_position(
  931. num_tokens: int,
  932. hash_block_size: int,
  933. drop_eagle_block: bool,
  934. ) -> int:
  935. """Return the reusable Mamba checkpoint boundary for a prefill."""
  936. checkpoint_position = (num_tokens - 1) // hash_block_size * hash_block_size
  937. if drop_eagle_block:
  938. checkpoint_position -= hash_block_size
  939. return max(checkpoint_position, 0)
  940. def is_mamba_prefill_checkpoint_valid(
  941. query_start: int,
  942. query_end: int,
  943. checkpoint_position: int,
  944. hash_block_size: int,
  945. mamba_block_size: int,
  946. checkpoint_alignment: int | None,
  947. ) -> bool:
  948. """Whether a backend can export the checkpoint in this query."""
  949. if checkpoint_alignment is None:
  950. return False
  951. assert checkpoint_alignment > 0
  952. initial_state_col = (query_start - 1) // mamba_block_size
  953. checkpoint_col = cdiv(query_end, mamba_block_size) - 2
  954. return (
  955. query_start % hash_block_size == 0
  956. and checkpoint_col > initial_state_col
  957. and query_start + hash_block_size <= checkpoint_position
  958. and query_start < checkpoint_position < query_end
  959. and (checkpoint_position - query_start) % checkpoint_alignment == 0
  960. )
  961. @dataclass(frozen=True)
  962. class EncoderOnlyAttentionSpec(AttentionSpec):
  963. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  964. # Encoder-only layers do not need KV cache
  965. return 0
  966. @dataclass(frozen=True)
  967. class CrossAttentionSpec(AttentionSpec):
  968. """KV cache spec for cross-attention layers in encoder-decoder models."""
  969. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  970. # For cross-attention, we need to cache encoder states
  971. # Get encoder length (e.g., 1500 for Whisper).
  972. max_encoder_len = vllm_config.scheduler_config.max_num_encoder_input_tokens
  973. return cdiv(max_encoder_len, self.block_size) * self.page_size_bytes
  974. @dataclass(frozen=True)
  975. class SinkFullAttentionSpec(FullAttentionSpec):
  976. sink_len: int | None = None
  977. @classmethod
  978. def merge(cls, specs: list[Self]) -> Self:
  979. """Merge a list of FullAttentionSpec objects into a single
  980. FullAttentionSpec object.
  981. """
  982. assert all(isinstance(spec, FullAttentionSpec) for spec in specs), (
  983. "All attention layers in the same KV cache group must be FullAttentionSpec."
  984. )
  985. sliding_window = set(
  986. spec.sliding_window for spec in specs if spec.sliding_window is not None
  987. )
  988. attention_chunk_size = set(
  989. spec.attention_chunk_size
  990. for spec in specs
  991. if spec.attention_chunk_size is not None
  992. )
  993. assert not any(isinstance(spec, MLAAttentionSpec) for spec in specs), (
  994. "MLAAttentionSpec should be merged in MLAAttentionSpec.merge"
  995. )
  996. merged_spec = cls(
  997. block_size=specs[0].block_size,
  998. num_kv_heads=specs[0].num_kv_heads,
  999. head_size=specs[0].head_size,
  1000. head_size_v=specs[0].head_size_v,
  1001. sink_len=specs[0].sink_len,
  1002. block_stride_alignment=specs[0].block_stride_alignment,
  1003. dtype=specs[0].dtype,
  1004. kv_quant_mode=specs[0].kv_quant_mode,
  1005. page_size_padded=specs[0].page_size_padded,
  1006. num_head_slots=specs[0].num_head_slots,
  1007. state_content_bytes=specs[0].state_content_bytes,
  1008. sliding_window=cls.merge_window_sizes(sliding_window),
  1009. attention_chunk_size=cls.merge_window_sizes(attention_chunk_size),
  1010. non_causal=any(spec.non_causal for spec in specs),
  1011. )
  1012. for spec in specs:
  1013. for f in fields(AttentionSpec):
  1014. assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
  1015. "All attention layers in the same KV cache group must have "
  1016. "the same attention spec."
  1017. )
  1018. assert (merged_spec.sliding_window is not None) + (
  1019. merged_spec.attention_chunk_size is not None
  1020. ) <= 1, (
  1021. "Model with both sliding window layers and chunked local attention "
  1022. "layers is not supported."
  1023. )
  1024. return merged_spec
  1025. @dataclass(frozen=True)
  1026. class UniformTypeKVCacheSpecs(KVCacheSpec):
  1027. """A KV cache spec for multiple layers with the same type of attention. Here,
  1028. same types means always need the same number of token slots. For example,
  1029. sliding window attentions with different window sizes are not the same type
  1030. and should not be merged into one UniformTypeKVCacheSpecs.
  1031. """
  1032. kv_cache_specs: dict[str, KVCacheSpec]
  1033. @property
  1034. def prefix_cacheable(self) -> bool:
  1035. return all(spec.prefix_cacheable for spec in self.kv_cache_specs.values())
  1036. @property
  1037. def prefix_replay_tokens(self) -> int:
  1038. return max(spec.prefix_replay_tokens for spec in self.kv_cache_specs.values())
  1039. @property
  1040. def first_spec(self) -> KVCacheSpec:
  1041. """Return the first spec in the group."""
  1042. return next(iter(self.kv_cache_specs.values()))
  1043. @property
  1044. def page_size_bytes(self) -> int:
  1045. return sum(spec.page_size_bytes for spec in self.kv_cache_specs.values())
  1046. @property
  1047. def block_table_token_alignment(self) -> int | None:
  1048. return self.first_spec.block_table_token_alignment
  1049. def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
  1050. max_num_pages = max(
  1051. cdiv(spec.max_memory_usage_bytes(vllm_config), spec.page_size_bytes)
  1052. for spec in self.kv_cache_specs.values()
  1053. )
  1054. return max_num_pages * self.page_size_bytes
  1055. def max_num_blocks_per_req(self, vllm_config: VllmConfig, max_len: int) -> int:
  1056. # Metadata builders are constructed from the per-layer spec, so the base
  1057. # cdiv(max_len, block_size) would drop its DCP sharding and size the
  1058. # block table wider than those builders expect.
  1059. widths = {
  1060. spec.max_num_blocks_per_req(vllm_config, max_len)
  1061. for spec in self.kv_cache_specs.values()
  1062. }
  1063. assert len(widths) == 1, (
  1064. "All layers in the same KV cache group must need the same number "
  1065. f"of block table entries, got {sorted(widths)}."
  1066. )
  1067. return next(iter(widths))
  1068. @classmethod
  1069. def is_uniform_type(cls, kv_cache_specs: dict[str, KVCacheSpec]) -> bool:
  1070. """Whether all layers have the same type of KV cache spec.
  1071. Uses the registry to determine grouping base classes, so custom specs
  1072. that inherit from FullAttentionSpec are treated as full attention.
  1073. """
  1074. block_sizes = set(spec.block_size for spec in kv_cache_specs.values())
  1075. if len(block_sizes) > 1:
  1076. # Different block sizes, not uniform.
  1077. return False
  1078. first_spec = next(iter(kv_cache_specs.values()))
  1079. return first_spec.is_uniform_with_collection(kv_cache_specs)
  1080. @classmethod
  1081. def from_specs(cls, kv_cache_specs: dict[str, KVCacheSpec]) -> Self | None:
  1082. """Return a SameTypeKVCacheSpecs object if all layers have the same type
  1083. of KV cache spec. Return None if not.
  1084. """
  1085. if cls.is_uniform_type(kv_cache_specs):
  1086. block_size = next(iter(kv_cache_specs.values())).block_size
  1087. return cls(block_size=block_size, kv_cache_specs=kv_cache_specs)
  1088. else:
  1089. return None
  1090. def get_max_layers_per_page_size(self) -> int:
  1091. """Max number of layers sharing a page size. For a balanced bucket
  1092. this equals the number of repetitions of the layer pattern."""
  1093. return Counter(
  1094. spec.page_size_bytes for spec in self.kv_cache_specs.values()
  1095. ).most_common(1)[0][1]
  1096. def max_memory_usage_pages(self, vllm_config: VllmConfig) -> int:
  1097. return max(
  1098. cdiv(spec.max_memory_usage_bytes(vllm_config), spec.page_size_bytes)
  1099. for spec in self.kv_cache_specs.values()
  1100. )
  1101. @property
  1102. def uses_slot_mapping(self) -> bool:
  1103. return self.first_spec.uses_slot_mapping
  1104. def iter_layer_specs(kv_cache_spec: KVCacheSpec) -> Collection[KVCacheSpec]:
  1105. """The per-layer specs a KV cache group spec covers.
  1106. ``UniformTypeKVCacheSpecs`` groups keep one spec per layer; every other
  1107. spec describes its group on its own. Returns the layer specs either way so
  1108. callers do not have to special-case the wrapper.
  1109. """
  1110. if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
  1111. return kv_cache_spec.kv_cache_specs.values()
  1112. return (kv_cache_spec,)
  1113. def is_full_attention_spec(kv_cache_spec: KVCacheSpec) -> bool:
  1114. """Whether a KV cache group spec is (or wraps) full attention.
  1115. ``UniformTypeKVCacheSpecs`` is not itself a ``FullAttentionSpec``, so a bare
  1116. isinstance check misses groups that carry the wrapper -- DeepSeek-V4's MLA
  1117. layers, or any model taking the ``UniformTypeKVCacheSpecs.from_specs`` path.
  1118. Every layer must be full attention: a group holding a recycling
  1119. (sliding-window) layer has no stable slot layout, so callers that key data
  1120. by slot cannot use it.
  1121. """
  1122. layer_specs = iter_layer_specs(kv_cache_spec)
  1123. return len(layer_specs) > 0 and all(
  1124. isinstance(spec, FullAttentionSpec) for spec in layer_specs
  1125. )
  1126. def get_kv_cache_spec_kind(kv_cache_spec: KVCacheSpec) -> KVCacheSpecKind:
  1127. if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
  1128. inner_kinds = {
  1129. get_kv_cache_spec_kind(spec)
  1130. for spec in kv_cache_spec.kv_cache_specs.values()
  1131. }
  1132. if len(inner_kinds) == 1:
  1133. return next(iter(inner_kinds))
  1134. # A group is only formed when all members share one registered
  1135. # uniform_type_base_spec, so UNKNOWN would discard what the merge
  1136. # already established.
  1137. base_specs = {
  1138. KVCacheSpecRegistry.get_uniform_type_base_spec(spec)
  1139. for spec in kv_cache_spec.kv_cache_specs.values()
  1140. }
  1141. if len(base_specs) == 1 and next(iter(base_specs)) is FullAttentionSpec:
  1142. return KVCacheSpecKind.FULL_ATTENTION
  1143. return KVCacheSpecKind.UNKNOWN
  1144. # Keep subclass checks before base classes so specialized specs keep their
  1145. # more precise kind.
  1146. if isinstance(kv_cache_spec, SlidingWindowMLASpec):
  1147. return KVCacheSpecKind.SLIDING_WINDOW_MLA
  1148. if isinstance(kv_cache_spec, MLAAttentionSpec):
  1149. return KVCacheSpecKind.MLA_ATTENTION
  1150. if isinstance(kv_cache_spec, SinkFullAttentionSpec):
  1151. return KVCacheSpecKind.SINK_FULL_ATTENTION
  1152. if isinstance(kv_cache_spec, FullAttentionSpec):
  1153. return KVCacheSpecKind.FULL_ATTENTION
  1154. if isinstance(kv_cache_spec, ChunkedLocalAttentionSpec):
  1155. return KVCacheSpecKind.CHUNKED_LOCAL_ATTENTION
  1156. if isinstance(kv_cache_spec, SlidingWindowSpec):
  1157. return KVCacheSpecKind.SLIDING_WINDOW
  1158. if isinstance(kv_cache_spec, MambaSpec):
  1159. return KVCacheSpecKind.MAMBA
  1160. if isinstance(kv_cache_spec, EncoderOnlyAttentionSpec):
  1161. return KVCacheSpecKind.ENCODER_ONLY_ATTENTION
  1162. if isinstance(kv_cache_spec, CrossAttentionSpec):
  1163. return KVCacheSpecKind.CROSS_ATTENTION
  1164. return KVCacheSpecKind.UNKNOWN
  1165. def get_kv_cache_spec_sliding_window(kv_cache_spec: KVCacheSpec) -> int | None:
  1166. if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs):
  1167. inner_windows = {
  1168. get_kv_cache_spec_sliding_window(spec)
  1169. for spec in kv_cache_spec.kv_cache_specs.values()
  1170. }
  1171. return next(iter(inner_windows)) if len(inner_windows) == 1 else None
  1172. if isinstance(kv_cache_spec, SlidingWindowSpec):
  1173. return kv_cache_spec.sliding_window
  1174. return None
  1175. @dataclass
  1176. class KVCacheTensor:
  1177. """A class for specifying how the workers should initialize the KV cache.
  1178. Placement of a set of same-shaped layers in the KV cache allocation.
  1179. Layer ``layers[l]``'s page for block ``b`` starts at
  1180. ``offset + l * layer_stride + b * block_stride`` bytes into the backing
  1181. allocation of ``size`` bytes. Layer-outermost layouts give each layer a
  1182. contiguous region (``layer_stride = page * num_blocks``,
  1183. ``block_stride = page``); block-outermost layouts make each block a
  1184. block of all layers' pages (``layer_stride = page``, ``block_stride`` =
  1185. the packed block). Tensors whose address ranges overlap
  1186. alias the same bytes: cache groups overlay each other, which is sound
  1187. because a block ID is owned by one group at a time.
  1188. """
  1189. size: int # total size of the backing allocation in bytes
  1190. layers: list[str] # layer names in L order
  1191. layer_stride: int
  1192. block_stride: int
  1193. offset: int = 0 # byte offset of layers[0]'s block 0
  1194. host_resident: bool = False
  1195. class KVCacheGroupRole(str, Enum):
  1196. DEFAULT = "default"
  1197. HISPARSE_SOURCE = "hisparse_source"
  1198. HISPARSE_INDEXER = "hisparse_indexer"
  1199. @dataclass
  1200. class KVCacheGroupSpec:
  1201. """Represents a group of model layers that share the same KV cache block table.
  1202. These layers are regarded as one layer in the KV cache manager.
  1203. """
  1204. # The names of model layers in this group
  1205. layer_names: list[str]
  1206. # The KV cache spec of this manager layer
  1207. kv_cache_spec: KVCacheSpec
  1208. # Whether this group contains EAGLE/MTP draft attention layers.
  1209. is_eagle_group: bool = False
  1210. # Host groups use the dedicated HiSparse pool; others share the device pool.
  1211. host_resident: bool = False
  1212. # Whether this group is part of the externally transferable KV state.
  1213. # Ephemeral accelerator-side replicas are rebuilt from their transferable
  1214. # source group after a connector load.
  1215. enable_kv_transfer: bool = True
  1216. role: KVCacheGroupRole = KVCacheGroupRole.DEFAULT
  1217. @dataclass
  1218. class KVCacheConfig:
  1219. """The KV cache configuration of a model."""
  1220. num_blocks: int
  1221. """The number of KV cache blocks"""
  1222. kv_cache_tensors: list[KVCacheTensor]
  1223. """How should model runner initialize the KV cache tensors for each layer"""
  1224. kv_cache_groups: list[KVCacheGroupSpec]
  1225. """
  1226. The kv cache groups of the model.
  1227. For models with only one type of attention, there is only one group that
  1228. contains all layers.
  1229. For models with multiple types of attention, there will be multiple groups,
  1230. see `_get_kv_cache_config_uniform_page_size` for more details.
  1231. """
  1232. prefix_cache_retention_interval: int | None = None
  1233. """Resolved retention policy for local prefix-cache checkpoints."""
  1234. kv_cache_layout: str | None = None
  1235. """The KV cache layout resolved by the engine core, adopted by all workers."""
  1236. hisparse_host_num_blocks: int | None = None
  1237. """Capacity of the dedicated HiSparse host-block manager, when enabled."""
  1238. hisparse_host_block_stride: int | None = None
  1239. """Physical bytes between consecutive HiSparse host blocks."""
  1240. hisparse_shared_host_pool: bool = False
  1241. """Whether local TP ranks share one physical HiSparse host pool."""
  1242. draft_num_blocks: int | None = None
  1243. """GLM-5-Next with a drafter: blocks in the drafter groups' own pool, sized
  1244. for their sliding windows instead of one draft page per target block."""
  1245. draft_layer_names: tuple[str, ...] = ()
  1246. """The drafter layers whose tensors address that pool."""
  1247. @cached_property
  1248. def transfer_group_ids(self) -> tuple[int, ...]:
  1249. """IDs of cache groups that participate in external KV transfer."""
  1250. return tuple(
  1251. group_id
  1252. for group_id, group in enumerate(self.kv_cache_groups)
  1253. if group.enable_kv_transfer
  1254. )
  1255. @cached_property
  1256. def prefix_cacheable_group_ids(self) -> tuple[int, ...]:
  1257. """IDs of transferable groups eligible for hash-addressed stores."""
  1258. return tuple(
  1259. group_id
  1260. for group_id in self.transfer_group_ids
  1261. if self.kv_cache_groups[group_id].kv_cache_spec.prefix_cacheable
  1262. )
  1263. @cached_property
  1264. def prefix_cacheable_groups(self) -> tuple[KVCacheGroupSpec, ...]:
  1265. """Transferable groups eligible for hash-addressed stores."""
  1266. return tuple(
  1267. self.kv_cache_groups[group_id]
  1268. for group_id in self.prefix_cacheable_group_ids
  1269. )
  1270. @cached_property
  1271. def transfer_groups(self) -> tuple[KVCacheGroupSpec, ...]:
  1272. """Cache groups that participate in external KV transfer."""
  1273. return tuple(
  1274. self.kv_cache_groups[group_id] for group_id in self.transfer_group_ids
  1275. )
  1276. @cached_property
  1277. def transfer_group_index_by_layer(self) -> dict[str, int]:
  1278. """Transfer-group tuple index for each participating layer."""
  1279. return {
  1280. layer_name: group_index
  1281. for group_index, group in enumerate(self.transfer_groups)
  1282. for layer_name in group.layer_names
  1283. }
  1284. def select_transfer_block_ids(
  1285. self, block_ids: Sequence[list[int]]
  1286. ) -> tuple[list[int], ...]:
  1287. """Select block IDs for externally transferable cache groups."""
  1288. if len(block_ids) != len(self.kv_cache_groups):
  1289. raise ValueError(
  1290. f"Expected {len(self.kv_cache_groups)} KV cache groups, "
  1291. f"got {len(block_ids)}."
  1292. )
  1293. return tuple(block_ids[group_id] for group_id in self.transfer_group_ids)
  1294. @property
  1295. def host_group_ids(self) -> tuple[int, ...]:
  1296. return tuple(
  1297. group_id
  1298. for group_id, group in enumerate(self.kv_cache_groups)
  1299. if group.host_resident
  1300. )
  1301. def num_blocks_of(self, tensor: KVCacheTensor) -> int:
  1302. """Number of blocks addressable by the pool backing ``tensor``."""
  1303. if self.draft_num_blocks is not None and tensor.layers[0] in self.draft_layer_names:
  1304. return self.draft_num_blocks
  1305. if not tensor.host_resident:
  1306. return self.num_blocks
  1307. assert self.hisparse_host_num_blocks is not None
  1308. return self.hisparse_host_num_blocks
  1309. @property
  1310. def has_mamba_layers(self) -> bool:
  1311. return any(
  1312. isinstance(spec, MambaSpec)
  1313. for group in self.kv_cache_groups
  1314. for spec in iter_layer_specs(group.kv_cache_spec)
  1315. )
  1316. @property
  1317. def has_mixed_precision_kv_cache(self) -> bool:
  1318. """Whether device attention caches use more than one precision."""
  1319. kv_cache_precisions: set[tuple[torch.dtype, KVQuantMode]] = set()
  1320. for group in self.kv_cache_groups:
  1321. if group.host_resident:
  1322. continue
  1323. kv_cache_precisions.update(
  1324. (spec.dtype, spec.kv_quant_mode)
  1325. for spec in iter_layer_specs(group.kv_cache_spec)
  1326. if isinstance(spec, AttentionSpec)
  1327. )
  1328. return len(kv_cache_precisions) > 1
  1329. @property
  1330. def needs_kv_cache_zeroing(self) -> bool:
  1331. """Whether newly allocated KV cache blocks must be zeroed before use.
  1332. Required for Mamba layers, whose state is read before it is fully written
  1333. (#35219), and for mixed-precision caches, where a block reused across
  1334. groups can be reinterpreted under a different precision and decode stale
  1335. bytes to NaN/Inf. Uniform-precision caches skip zeroing.
  1336. """
  1337. return self.has_mamba_layers or self.has_mixed_precision_kv_cache