kv_cache_interface.py 62 KB

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