flashinfer_sparse.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. """DeepSeek V4 FlashInfer sparse MLA backend."""
  4. from typing import TYPE_CHECKING, ClassVar, cast
  5. import torch
  6. from vllm.config import VllmConfig
  7. from vllm.config.cache import CacheDType
  8. from vllm.forward_context import get_forward_context
  9. from vllm.models.deepseek_v4.nvidia.ops.o_proj import (
  10. compute_fp8_einsum_recipe,
  11. deep_gemm_fp8_o_proj,
  12. )
  13. from vllm.models.deepseek_v4_1.attention import DeepseekV4Attention
  14. from vllm.models.deepseek_v4_1.common.ops import (
  15. build_flashinfer_mixed_sparse_indices,
  16. compute_global_topk_indices_and_lens,
  17. )
  18. from vllm.models.deepseek_v4_1.sparse_mla import (
  19. DeepseekV4FlashMLAMetadata,
  20. DeepseekV4SparseMLABackend,
  21. DeepseekV4SparseMLAMetadataBuilder,
  22. DeepseekV41SparseSWAMetadataBuilder,
  23. )
  24. from vllm.platforms import current_platform
  25. from vllm.platforms.interface import DeviceCapability
  26. from vllm.utils.flashinfer import flashinfer_trtllm_batch_decode_sparse_mla_dsv4
  27. from vllm.v1.attention.backend import AttentionCGSupport, MultipleOf
  28. from vllm.v1.attention.backends.mla.compressor_utils import (
  29. get_dspark_swa_index_width,
  30. )
  31. from vllm.v1.attention.backends.mla.sparse_swa import DeepseekSparseSWABackend
  32. if TYPE_CHECKING:
  33. from vllm.v1.attention.backends.mla.sparse_swa import DeepseekSparseSWAMetadata
  34. _FLASHINFER_DSV4_WORKSPACE_BUFFER_SIZE = 128 * 1024 * 1024
  35. _flashinfer_dsv4_workspace_by_device: dict[torch.device, torch.Tensor] = {}
  36. def _get_flashinfer_dsv4_workspace(device: torch.device) -> torch.Tensor:
  37. workspace = _flashinfer_dsv4_workspace_by_device.get(device)
  38. if workspace is None:
  39. workspace = torch.zeros(
  40. _FLASHINFER_DSV4_WORKSPACE_BUFFER_SIZE,
  41. dtype=torch.uint8,
  42. device=device,
  43. )
  44. _flashinfer_dsv4_workspace_by_device[device] = workspace
  45. return workspace
  46. def _packed_block_span(pool: torch.Tensor) -> int:
  47. """Per-block stride of ``pool`` in tokens (``stride(0)//stride(-2)``): ==
  48. block_size for unpacked KV, larger when packed (#44577). Raises if not
  49. token-aligned."""
  50. block_stride = pool.stride(0)
  51. token_stride = pool.stride(-2)
  52. if block_stride % token_stride != 0:
  53. raise NotImplementedError(
  54. "FLASHINFER_MLA_SPARSE_DSV4 packed KV requires the per-block stride "
  55. f"({block_stride}) to be a multiple of the per-token stride "
  56. f"({token_stride}); this layout is not supported yet."
  57. )
  58. return block_stride // token_stride
  59. # Sparse MLA h_q counts accepted natively (flashinfer>=0.6.14, #3545).
  60. _SPARSE_MLA_SUPPORTED_Q_HEADS = (8, 16, 32, 64, 128)
  61. def _pad_to_supported_q_heads(num_heads: int) -> int:
  62. for supported in _SPARSE_MLA_SUPPORTED_Q_HEADS:
  63. if num_heads <= supported:
  64. return supported
  65. raise ValueError(
  66. f"DeepseekV4 FlashInfer MLA Sparse does not support {num_heads} heads "
  67. "(sparse MLA kernel requires h_q in {8, 16, 32, 64, 128})."
  68. )
  69. def _required_sm120_sparse_topk(vllm_config: VllmConfig, window_size: int) -> int:
  70. """Return the SM120 DSV4 SWA specialization needed by this model."""
  71. if not vllm_config.attention_config.use_non_causal:
  72. return window_size
  73. speculative_config = vllm_config.speculative_config
  74. if speculative_config is None:
  75. return window_size
  76. return get_dspark_swa_index_width(
  77. window_size,
  78. speculative_config.num_speculative_tokens,
  79. )
  80. class DeepseekV4FlashInferMLASparseBackend(DeepseekV4SparseMLABackend):
  81. """FlashInfer backend using the DSv4 sparse metadata/cache layout.
  82. Inherits the base and backend reuses its``DeepseekV4SparseMLAMetadataBuilder``
  83. """
  84. supported_dtypes: ClassVar[list[torch.dtype]] = [torch.bfloat16]
  85. supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [
  86. "auto",
  87. "bfloat16",
  88. "fp8",
  89. "fp8_e4m3",
  90. "fp8_ds_mla",
  91. ]
  92. @staticmethod
  93. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  94. # Tech2Wild/Kai 2026-09-10 (SM12x): 64-state compressed pages, so a
  95. # ratio-1 kv-source layer needs 64-token pages (128 stays first so the
  96. # cache-config default remains 128 for ratio-2 layers).
  97. if current_platform.is_cuda() and current_platform.is_device_capability_family(120):
  98. return [128, 64]
  99. return [128]
  100. @classmethod
  101. def get_compressed_block_size(cls, compress_ratio: int) -> int | None:
  102. """Tech2Wild/Kai 2026-09-10: tokens per compressed-KV page for a kv-source
  103. layer on SM12x (FlashInfer SM120 sparse-MLA prefill/decode kernels are
  104. instantiated for 64-state pages); None = use cache_config.block_size."""
  105. if compress_ratio > 0 and current_platform.is_cuda() and current_platform.is_device_capability_family(120):
  106. return 64 * compress_ratio
  107. return None
  108. @staticmethod
  109. def get_name() -> str:
  110. return "FLASHINFER_MLA_SPARSE_DSV41"
  111. @classmethod
  112. def get_supported_head_sizes(cls) -> list[int]:
  113. return [512]
  114. @classmethod
  115. def supports_sink(cls) -> bool:
  116. return True
  117. @classmethod
  118. def is_sparse(cls) -> bool:
  119. return True
  120. @classmethod
  121. def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
  122. return capability.major in [10, 12]
  123. @classmethod
  124. def supports_combination(
  125. cls,
  126. head_size: int,
  127. dtype: torch.dtype,
  128. kv_cache_dtype: CacheDType | None,
  129. block_size: int | None,
  130. use_mla: bool,
  131. has_sink: bool,
  132. use_sparse: bool,
  133. use_mm_prefix: bool,
  134. device_capability: DeviceCapability,
  135. ) -> str | None:
  136. if device_capability.major == 10:
  137. if kv_cache_dtype == "fp8_ds_mla":
  138. return (
  139. "FLASHINFER_MLA_SPARSE_DSV4 SM10x uses the plain "
  140. "per-tensor FP8 KV layout, not fp8_ds_mla"
  141. )
  142. if kv_cache_dtype not in (None, "auto", "bfloat16", "fp8", "fp8_e4m3"):
  143. return "kv_cache_dtype not supported"
  144. return None
  145. if device_capability.major == 12:
  146. if kv_cache_dtype not in ("fp8", "fp8_e4m3", "fp8_ds_mla"):
  147. return "kv_cache_dtype not supported"
  148. from vllm.utils.flashinfer import has_flashinfer_sparse_mla_sm120
  149. if not has_flashinfer_sparse_mla_sm120():
  150. return (
  151. "FLASHINFER_MLA_SPARSE_DSV4 SM120 requires FlashInfer's "
  152. "sparse MLA decode API"
  153. )
  154. return None
  155. return "FLASHINFER_MLA_SPARSE_DSV4 requires SM10x or SM12x"
  156. @staticmethod
  157. def get_builder_cls() -> type["DeepseekV4FlashInferSparseMLAMetadataBuilder"]:
  158. return DeepseekV4FlashInferSparseMLAMetadataBuilder
  159. class DeepseekV4FlashInferSparseMLAMetadataBuilder(DeepseekV4SparseMLAMetadataBuilder):
  160. """Varlen-capable metadata builder for the FlashInfer sparse MLA backend."""
  161. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
  162. class DeepseekSparseSWAFlashInferMetadataBuilder(DeepseekV41SparseSWAMetadataBuilder):
  163. """SWA metadata for the FlashInfer sparse decode path (varlen decode)."""
  164. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
  165. class DeepseekSparseSWAFlashInferBackend(DeepseekSparseSWABackend):
  166. @staticmethod
  167. def get_builder_cls() -> type[DeepseekSparseSWAFlashInferMetadataBuilder]:
  168. return DeepseekSparseSWAFlashInferMetadataBuilder
  169. class DeepseekSparseSWAFlashInferSM120Backend(DeepseekSparseSWAFlashInferBackend):
  170. """Tech2Wild/Kai 2026-09-10: SM12x. FlashInfer's SM120 sparse-MLA decode and
  171. prefill kernels are instantiated only for 64-token pages
  172. (_DECODE_DSV4_PAGE_BLOCK_SIZE = 64); the base backend's "any multiple of 32"
  173. made vLLM build the SWA cache at 32-token pages, which the SM120 path rejects
  174. ("no decode kernel for this shape ... page_block_size=32")."""
  175. @staticmethod
  176. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  177. return [64]
  178. @classmethod
  179. def get_preferred_block_size(cls, default_block_size: int) -> int:
  180. return 64
  181. @classmethod
  182. def get_swa_block_size(cls) -> int:
  183. return 64
  184. class DeepseekV4FlashInferMLAAttention(DeepseekV4Attention):
  185. """FlashInfer TRTLLM-gen sparse MLA attention layer for SM100 DeepSeek V4."""
  186. backend_cls = DeepseekV4FlashInferMLASparseBackend
  187. swa_backend_cls = DeepseekSparseSWAFlashInferBackend
  188. use_fp8_ds_mla_layout: ClassVar[bool] = False
  189. @classmethod
  190. def get_padded_num_q_heads(cls, num_heads: int) -> int:
  191. return _pad_to_supported_q_heads(num_heads)
  192. def _o_proj(self, o: torch.Tensor, positions: torch.Tensor) -> torch.Tensor:
  193. return deep_gemm_fp8_o_proj(
  194. o,
  195. positions,
  196. self.rotary_emb.cos_sin_cache,
  197. self.wo_a,
  198. self.wo_b,
  199. n_groups=self.n_local_groups,
  200. heads_per_group=self.n_local_heads // self.n_local_groups,
  201. nope_dim=self.nope_head_dim,
  202. rope_dim=self.rope_head_dim,
  203. o_lora_rank=self.o_lora_rank,
  204. einsum_recipe=self._einsum_recipe,
  205. tma_aligned_scales=self._tma_aligned_scales,
  206. )
  207. def __init__(self, *args, **kwargs) -> None:
  208. super().__init__(*args, **kwargs)
  209. self._einsum_recipe, self._tma_aligned_scales = compute_fp8_einsum_recipe(
  210. self._o_proj_block_size
  211. )
  212. # Per-tensor FP8 scale buffers + precomputed scalar BMM scales. Only the
  213. # per-tensor FP8 cache path consumes these; bf16 reads ``self.scale``.
  214. if self.kv_cache_torch_dtype != torch.float8_e4m3fn:
  215. return
  216. fp8_q_scale = 1.0
  217. fp8_kv_scale = 1.0
  218. self.register_buffer(
  219. "_flashinfer_fp8_q_scale",
  220. torch.tensor([fp8_q_scale], dtype=torch.float32),
  221. persistent=False,
  222. )
  223. self.register_buffer(
  224. "_flashinfer_fp8_q_scale_inv",
  225. torch.tensor([1.0 / fp8_q_scale], dtype=torch.float32),
  226. persistent=False,
  227. )
  228. self.register_buffer(
  229. "_flashinfer_fp8_kv_scale",
  230. torch.tensor([fp8_kv_scale], dtype=torch.float32),
  231. persistent=False,
  232. )
  233. # TRTLLM-gen takes scalar scale args on a distinct C++ path vs
  234. # one-element tensors, so these are Python floats.
  235. self._flashinfer_fp8_bmm1_scale = self.scale * fp8_q_scale * fp8_kv_scale
  236. self._flashinfer_fp8_bmm2_scale = fp8_kv_scale
  237. def forward_mqa(
  238. self,
  239. q: torch.Tensor,
  240. kv: torch.Tensor,
  241. positions: torch.Tensor,
  242. output: torch.Tensor,
  243. ) -> None:
  244. # The TRTLLM-gen kernel requires h_q in {64, 128}, so the output buffer
  245. # is allocated at the padded head count while q arrives at the local
  246. # head count; _forward pads q to match before the launcher.
  247. assert output.shape[0] == q.shape[0] and output.shape[-1] == q.shape[-1], (
  248. f"output buffer shape {output.shape} incompatible with q shape {q.shape}"
  249. )
  250. assert output.shape[1] >= q.shape[1], (
  251. f"output heads {output.shape[1]} must be >= q heads {q.shape[1]}"
  252. )
  253. # Per-tensor FP8 q produces a bf16 attention output.
  254. expected_output_dtype = (
  255. torch.bfloat16 if q.dtype == torch.float8_e4m3fn else q.dtype
  256. )
  257. assert output.dtype == expected_output_dtype, (
  258. f"output dtype {output.dtype} must match expected {expected_output_dtype} "
  259. f"for q dtype {q.dtype}"
  260. )
  261. forward_context = get_forward_context()
  262. attn_metadata = forward_context.attn_metadata
  263. if attn_metadata is None:
  264. # Warmup dummy run: FlashInfer reads the cache directly and lazily
  265. # allocates its workspace, so nothing to reserve here.
  266. output.zero_()
  267. return
  268. assert isinstance(attn_metadata, dict)
  269. # Compressed-cache metadata lives on the kv-source layer's prefix;
  270. # consumers share that cache and its block table.
  271. flashmla_metadata = cast(
  272. DeepseekV4FlashMLAMetadata | None,
  273. attn_metadata.get(self.compressed_cache_prefix)
  274. if self.compressed_cache_prefix is not None
  275. else None,
  276. )
  277. swa_metadata = cast(
  278. "DeepseekSparseSWAMetadata | None",
  279. attn_metadata.get(self.swa_cache_layer.prefix),
  280. )
  281. assert swa_metadata is not None
  282. swa_only = self.compress_ratio == 0
  283. # SWA-only layers have no compressed KV cache; consumers read the kv
  284. # source's cache.
  285. self_kv_cache = None if swa_only else self._compressed_kv_cache()
  286. swa_kv_cache = self.swa_cache_layer.kv_cache
  287. self._forward(
  288. q=q,
  289. kv_cache=self_kv_cache,
  290. swa_k_cache=swa_kv_cache,
  291. swa_metadata=swa_metadata,
  292. attn_metadata=flashmla_metadata,
  293. swa_only=swa_only,
  294. output=output,
  295. )
  296. def _build_sparse_index_metadata(
  297. self,
  298. kv_cache: torch.Tensor | None,
  299. swa_k_cache: torch.Tensor,
  300. swa_metadata: "DeepseekSparseSWAMetadata",
  301. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  302. swa_only: bool,
  303. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  304. """Build the combined sparse-index tensors for the mixed batch.
  305. Returns ``(compressed_kv_cache, seq_lens, sparse_indices,
  306. sparse_topk_lens)``.
  307. """
  308. num_decodes = swa_metadata.num_decodes
  309. num_prefills = swa_metadata.num_prefills
  310. num_decode_tokens = swa_metadata.num_decode_tokens
  311. num_prefill_tokens = swa_metadata.num_prefill_tokens
  312. num_reqs = num_decodes + num_prefills
  313. num_tokens = num_decode_tokens + num_prefill_tokens
  314. assert swa_metadata.seq_lens is not None
  315. assert swa_metadata.query_start_loc is not None
  316. assert swa_metadata.token_to_req_indices is not None
  317. assert swa_metadata.decode_swa_indices is not None
  318. assert swa_metadata.block_table is not None
  319. decode_swa_indices = swa_metadata.decode_swa_indices.reshape(
  320. num_decode_tokens, swa_metadata.decode_swa_width
  321. )
  322. decode_compressed_topk_lens = None
  323. decode_compressed_indices_are_local = False
  324. decode_is_valid_token = None
  325. if swa_only:
  326. assert self.topk_indices_buffer is not None
  327. compressed_kv_cache = swa_k_cache
  328. decode_compressed_indices = None
  329. prefill_topk_indices = self.topk_indices_buffer[
  330. num_decode_tokens:num_tokens, :0
  331. ]
  332. compressed_block_table = None
  333. compressed_block_size = swa_metadata.block_size
  334. top_k = 0
  335. else:
  336. assert kv_cache is not None
  337. assert attn_metadata is not None
  338. assert self.topk_indices_buffer is not None
  339. assert swa_metadata.is_valid_token is not None
  340. compressed_kv_cache = kv_cache
  341. compressed_block_table = attn_metadata.block_table[:num_reqs]
  342. compressed_block_size = attn_metadata.block_size // self.compress_ratio
  343. # Local indices filled by the index-source layer's indexer.
  344. if num_prefill_tokens > 0:
  345. prefill_topk_indices = self.topk_indices_buffer[
  346. num_decode_tokens:num_tokens
  347. ]
  348. top_k = prefill_topk_indices.shape[-1]
  349. else:
  350. prefill_topk_indices = self.topk_indices_buffer[:0, :0]
  351. top_k = 0
  352. decode_compressed_indices_are_local = True
  353. decode_is_valid_token = swa_metadata.is_valid_token[:num_decode_tokens]
  354. if num_decode_tokens > 0:
  355. decode_compressed_indices = self.topk_indices_buffer[:num_decode_tokens]
  356. else:
  357. # Keep the logical width aligned with the mixed-batch case so
  358. # pure-prefill steps reuse the same Triton specialization.
  359. decode_compressed_indices = prefill_topk_indices[:0]
  360. query_start_loc = swa_metadata.query_start_loc[: num_reqs + 1]
  361. seq_lens = swa_metadata.seq_lens[:num_reqs]
  362. assert seq_lens.dtype == torch.int32
  363. # SWA-only layers all build the same mixed sparse indices, so the first
  364. # one caches them for the step; indexer layers depend on their own topk
  365. # indices and stay uncached.
  366. cached_sparse = (
  367. swa_metadata.flashinfer_sparse_index_cache.get("swa_only")
  368. if swa_only
  369. else None
  370. )
  371. if cached_sparse is None:
  372. swa_block_span = _packed_block_span(swa_k_cache)
  373. compressed_block_span = _packed_block_span(compressed_kv_cache)
  374. sparse_indices, sparse_topk_lens = build_flashinfer_mixed_sparse_indices(
  375. decode_swa_indices,
  376. decode_compressed_indices,
  377. decode_compressed_topk_lens,
  378. prefill_topk_indices[:num_prefill_tokens],
  379. query_start_loc,
  380. seq_lens,
  381. swa_metadata.token_to_req_indices[:num_tokens],
  382. swa_metadata.block_table[:num_reqs],
  383. swa_metadata.block_size,
  384. compressed_block_table,
  385. compressed_block_size,
  386. self.window_size,
  387. self.compress_ratio,
  388. top_k,
  389. decode_compressed_indices_are_local=decode_compressed_indices_are_local,
  390. decode_is_valid_token=decode_is_valid_token,
  391. swa_block_span=swa_block_span,
  392. compressed_block_span=compressed_block_span,
  393. prefill_left_visible=swa_metadata.prefill_left_visible,
  394. prefill_right_visible=swa_metadata.prefill_right_visible,
  395. # getattr for tests that bypass __init__ via object.__new__.
  396. max_image_tokens=getattr(self, "max_image_tokens", 0),
  397. )
  398. if swa_only:
  399. swa_metadata.flashinfer_sparse_index_cache["swa_only"] = (
  400. sparse_indices,
  401. sparse_topk_lens,
  402. )
  403. else:
  404. sparse_indices, sparse_topk_lens = cached_sparse
  405. return compressed_kv_cache, seq_lens, sparse_indices, sparse_topk_lens
  406. def _forward(
  407. self,
  408. q: torch.Tensor,
  409. kv_cache: torch.Tensor | None,
  410. swa_k_cache: torch.Tensor,
  411. swa_metadata: "DeepseekSparseSWAMetadata",
  412. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  413. swa_only: bool,
  414. output: torch.Tensor,
  415. ) -> None:
  416. assert self.kv_cache_torch_dtype in (torch.bfloat16, torch.float8_e4m3fn)
  417. num_decodes = swa_metadata.num_decodes
  418. num_prefills = swa_metadata.num_prefills
  419. num_decode_tokens = swa_metadata.num_decode_tokens
  420. num_prefill_tokens = swa_metadata.num_prefill_tokens
  421. num_reqs = num_decodes + num_prefills
  422. num_tokens = num_decode_tokens + num_prefill_tokens
  423. if num_tokens == 0:
  424. return
  425. (
  426. compressed_kv_cache,
  427. seq_lens,
  428. sparse_indices,
  429. sparse_topk_lens,
  430. ) = self._build_sparse_index_metadata(
  431. kv_cache=kv_cache,
  432. swa_k_cache=swa_k_cache,
  433. swa_metadata=swa_metadata,
  434. attn_metadata=attn_metadata,
  435. swa_only=swa_only,
  436. )
  437. # CUDA graph execution can pad q/output past the scheduled token count;
  438. # restrict to the real tokens (the launcher validates sparse indices).
  439. query = q[:num_tokens]
  440. output = output[:num_tokens]
  441. bmm1_scale: float | torch.Tensor = self.scale
  442. bmm2_scale: float | torch.Tensor = 1.0
  443. if self.kv_cache_torch_dtype == torch.float8_e4m3fn:
  444. assert query.dtype == torch.float8_e4m3fn
  445. bmm1_scale = self._flashinfer_fp8_bmm1_scale
  446. bmm2_scale = self._flashinfer_fp8_bmm2_scale
  447. else:
  448. assert query.dtype == torch.bfloat16
  449. query = query.contiguous()
  450. # The TRTLLM-gen sparse-MLA kernel requires h_q in {64, 128}; zero-pad
  451. # the query heads to the allocated output head count. Padded heads attend
  452. # to the shared KV and are sliced off downstream (output is padded too).
  453. padded_heads = output.shape[1]
  454. if query.shape[1] < padded_heads:
  455. padded_query = query.new_zeros(
  456. (query.shape[0], padded_heads, query.shape[2])
  457. )
  458. padded_query[:, : query.shape[1], :] = query
  459. query = padded_query
  460. workspace = _get_flashinfer_dsv4_workspace(q.device)
  461. query_start_loc = swa_metadata.query_start_loc
  462. query_start_loc_cpu = swa_metadata.query_start_loc_cpu
  463. assert query_start_loc is not None and query_start_loc_cpu is not None
  464. # Keep the TRTLLM-gen decode/prefill split: the launcher is tuned for
  465. # uniform-q batches, and this avoids flattening mixed batches into one call.
  466. if num_decode_tokens > 0:
  467. decode_cu = query_start_loc[: num_decodes + 1]
  468. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  469. query=query[:num_decode_tokens],
  470. swa_kv_cache=swa_k_cache,
  471. workspace_buffer=workspace,
  472. sparse_indices=sparse_indices[:num_decode_tokens],
  473. compressed_kv_cache=compressed_kv_cache,
  474. sparse_topk_lens=sparse_topk_lens[:num_decode_tokens],
  475. seq_lens=seq_lens[:num_decodes],
  476. out=output[:num_decode_tokens],
  477. bmm1_scale=bmm1_scale,
  478. bmm2_scale=bmm2_scale,
  479. sinks=self.attn_sink,
  480. cum_seq_lens_q=decode_cu,
  481. max_q_len=swa_metadata.max_decode_query_len,
  482. )
  483. if num_prefill_tokens > 0:
  484. # The prefill query view re-anchors at offset 0, so rebase the
  485. # cumulative query offsets to start at 0.
  486. prefill_cu = (
  487. query_start_loc[num_decodes : num_reqs + 1]
  488. - query_start_loc[num_decodes]
  489. )
  490. prefill_cu_cpu = query_start_loc_cpu[num_decodes : num_reqs + 1]
  491. prefill_lens_cpu = prefill_cu_cpu[1:] - prefill_cu_cpu[:-1]
  492. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  493. query=query[num_decode_tokens:num_tokens],
  494. swa_kv_cache=swa_k_cache,
  495. workspace_buffer=workspace,
  496. sparse_indices=sparse_indices[num_decode_tokens:num_tokens],
  497. compressed_kv_cache=compressed_kv_cache,
  498. sparse_topk_lens=sparse_topk_lens[num_decode_tokens:num_tokens],
  499. seq_lens=seq_lens[num_decodes:num_reqs],
  500. out=output[num_decode_tokens:num_tokens],
  501. bmm1_scale=bmm1_scale,
  502. bmm2_scale=bmm2_scale,
  503. sinks=self.attn_sink,
  504. cum_seq_lens_q=prefill_cu,
  505. max_q_len=int(prefill_lens_cpu.max().item()),
  506. )
  507. class DeepseekV4FlashInferSM120Attention(DeepseekV4Attention):
  508. """DeepSeek V4 sparse MLA attention through FlashInfer's SM120 kernels."""
  509. backend_cls = DeepseekV4FlashInferMLASparseBackend
  510. swa_backend_cls = DeepseekSparseSWAFlashInferSM120Backend
  511. use_fp8_ds_mla_layout: ClassVar[bool] = True
  512. @staticmethod
  513. def _get_workspace(device: torch.device) -> torch.Tensor:
  514. return _get_flashinfer_dsv4_workspace(device)
  515. @staticmethod
  516. def _as_sparse_cache(kv_cache: torch.Tensor) -> torch.Tensor:
  517. if kv_cache.dtype == torch.float8_e4m3fn:
  518. kv_cache = kv_cache.view(torch.uint8)
  519. if kv_cache.dim() == 4:
  520. return kv_cache
  521. return kv_cache.unsqueeze(-2)
  522. @classmethod
  523. def get_padded_num_q_heads(cls, num_heads: int) -> int:
  524. return _pad_to_supported_q_heads(num_heads)
  525. def _o_proj(self, o: torch.Tensor, positions: torch.Tensor) -> torch.Tensor:
  526. return deep_gemm_fp8_o_proj(
  527. o,
  528. positions,
  529. self.rotary_emb.cos_sin_cache,
  530. self.wo_a,
  531. self.wo_b,
  532. n_groups=self.n_local_groups,
  533. heads_per_group=self.n_local_heads // self.n_local_groups,
  534. nope_dim=self.nope_head_dim,
  535. rope_dim=self.rope_head_dim,
  536. o_lora_rank=self.o_lora_rank,
  537. einsum_recipe=self._einsum_recipe,
  538. tma_aligned_scales=self._tma_aligned_scales,
  539. )
  540. def __init__(self, vllm_config: VllmConfig, *args, **kwargs) -> None:
  541. super().__init__(vllm_config, *args, **kwargs)
  542. from vllm.utils.flashinfer import has_flashinfer_sparse_mla_sm120_config
  543. required_topk = _required_sm120_sparse_topk(vllm_config, self.window_size)
  544. if not has_flashinfer_sparse_mla_sm120_config(self.padded_heads, required_topk):
  545. raise RuntimeError(
  546. "FLASHINFER_MLA_SPARSE_DSV4 on SM120 requires a FlashInfer "
  547. "DSV4 sparse MLA decode specialization for "
  548. f"(num_q_heads={self.padded_heads}, top_k={required_topk}). "
  549. "Install a FlashInfer build containing "
  550. "flashinfer-ai/flashinfer#4380."
  551. )
  552. self._einsum_recipe, self._tma_aligned_scales = compute_fp8_einsum_recipe(
  553. self._o_proj_block_size
  554. )
  555. # Per-tensor FP8 cache path scales.
  556. if self.kv_cache_torch_dtype != torch.float8_e4m3fn:
  557. return
  558. fp8_q_scale = 1.0
  559. fp8_kv_scale = 1.0
  560. self.register_buffer(
  561. "_flashinfer_fp8_q_scale",
  562. torch.tensor([fp8_q_scale], dtype=torch.float32),
  563. persistent=False,
  564. )
  565. self.register_buffer(
  566. "_flashinfer_fp8_q_scale_inv",
  567. torch.tensor([1.0 / fp8_q_scale], dtype=torch.float32),
  568. persistent=False,
  569. )
  570. self.register_buffer(
  571. "_flashinfer_fp8_kv_scale",
  572. torch.tensor([fp8_kv_scale], dtype=torch.float32),
  573. persistent=False,
  574. )
  575. # FlashInfer expects scalar scale arguments for this path.
  576. self._flashinfer_fp8_bmm1_scale = self.scale * fp8_q_scale * fp8_kv_scale
  577. self._flashinfer_fp8_bmm2_scale = fp8_kv_scale
  578. def _reserve_empty_forward_workspace(self) -> None:
  579. self._get_workspace(
  580. torch.device("cuda", torch.accelerator.current_device_index())
  581. )
  582. def _forward_sparse_impl(
  583. self,
  584. q: torch.Tensor,
  585. output: torch.Tensor,
  586. flashmla_metadata: DeepseekV4FlashMLAMetadata | None,
  587. swa_metadata: "DeepseekSparseSWAMetadata",
  588. self_kv_cache: torch.Tensor | None,
  589. swa_kv_cache: torch.Tensor,
  590. swa_only: bool,
  591. ) -> None:
  592. num_decode_tokens = swa_metadata.num_decode_tokens
  593. if swa_metadata.num_prefills > 0:
  594. self._forward_prefill(
  595. q=q[num_decode_tokens:],
  596. compressed_k_cache=self_kv_cache,
  597. swa_k_cache=swa_kv_cache,
  598. output=output[num_decode_tokens:],
  599. attn_metadata=flashmla_metadata,
  600. swa_metadata=swa_metadata,
  601. )
  602. if swa_metadata.num_decodes > 0:
  603. self._forward_decode(
  604. q=q[:num_decode_tokens],
  605. kv_cache=self_kv_cache,
  606. swa_metadata=swa_metadata,
  607. attn_metadata=flashmla_metadata,
  608. swa_only=swa_only,
  609. output=output[:num_decode_tokens],
  610. )
  611. def forward_mqa(
  612. self,
  613. q: torch.Tensor,
  614. kv: torch.Tensor,
  615. positions: torch.Tensor,
  616. output: torch.Tensor,
  617. ) -> None:
  618. # Output may be padded to backend-supported head counts.
  619. assert output.shape[0] == q.shape[0] and output.shape[-1] == q.shape[-1], (
  620. f"output buffer shape {output.shape} incompatible with q shape {q.shape}"
  621. )
  622. assert output.shape[1] >= q.shape[1], (
  623. f"output heads {output.shape[1]} must be >= q heads {q.shape[1]}"
  624. )
  625. # Per-tensor FP8 q produces a bf16 attention output.
  626. expected_output_dtype = (
  627. torch.bfloat16 if q.dtype == torch.float8_e4m3fn else q.dtype
  628. )
  629. assert output.dtype == expected_output_dtype, (
  630. f"output dtype {output.dtype} must match expected {expected_output_dtype} "
  631. f"for q dtype {q.dtype}"
  632. )
  633. forward_context = get_forward_context()
  634. attn_metadata = forward_context.attn_metadata
  635. if attn_metadata is None:
  636. self._reserve_empty_forward_workspace()
  637. output.zero_()
  638. return
  639. assert isinstance(attn_metadata, dict)
  640. # Compressed-cache metadata lives on the kv-source layer's prefix;
  641. # consumers share that cache and its block table.
  642. flashmla_metadata = cast(
  643. DeepseekV4FlashMLAMetadata | None,
  644. attn_metadata.get(self.compressed_cache_prefix)
  645. if self.compressed_cache_prefix is not None
  646. else None,
  647. )
  648. swa_metadata = cast(
  649. "DeepseekSparseSWAMetadata | None",
  650. attn_metadata.get(self.swa_cache_layer.prefix),
  651. )
  652. assert swa_metadata is not None
  653. swa_only = self.compress_ratio == 0
  654. # SWA-only layers have no compressed KV cache; consumers read the kv
  655. # source's cache.
  656. self_kv_cache = None if swa_only else self._compressed_kv_cache()
  657. swa_kv_cache = self.swa_cache_layer.kv_cache
  658. self._forward_sparse_impl(
  659. q=q,
  660. output=output,
  661. flashmla_metadata=flashmla_metadata,
  662. swa_metadata=swa_metadata,
  663. self_kv_cache=self_kv_cache,
  664. swa_kv_cache=swa_kv_cache,
  665. swa_only=swa_only,
  666. )
  667. def _prepare_query(self, q: torch.Tensor, output: torch.Tensor) -> torch.Tensor:
  668. if self.kv_cache_torch_dtype == torch.float8_e4m3fn:
  669. assert q.dtype == torch.float8_e4m3fn
  670. q = q.to(torch.bfloat16)
  671. else:
  672. assert q.dtype == torch.bfloat16
  673. padded_heads = output.shape[1]
  674. if q.shape[1] < padded_heads:
  675. padded_query = q.new_zeros((q.shape[0], padded_heads, q.shape[2]))
  676. padded_query[:, : q.shape[1], :] = q
  677. q = padded_query
  678. return q.contiguous()
  679. def _forward_decode(
  680. self,
  681. q: torch.Tensor,
  682. kv_cache: torch.Tensor | None,
  683. swa_metadata: "DeepseekSparseSWAMetadata",
  684. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  685. swa_only: bool,
  686. output: torch.Tensor,
  687. ) -> None:
  688. num_decodes = swa_metadata.num_decodes
  689. num_decode_tokens = swa_metadata.num_decode_tokens
  690. extra_sparse_indices = None
  691. extra_sparse_lengths = None
  692. if not swa_only:
  693. if attn_metadata is None:
  694. raise RuntimeError(
  695. "Sparse MLA metadata is required for compressed layers."
  696. )
  697. if swa_metadata.is_valid_token is None:
  698. raise RuntimeError(
  699. "SWA validity metadata is required for compressed layers."
  700. )
  701. if self.topk_indices_buffer is None:
  702. raise RuntimeError(
  703. "Compressed-layer decode requires top-k indices from the indexer."
  704. )
  705. # Local indices filled by the index-source layer's indexer.
  706. is_valid = swa_metadata.is_valid_token[:num_decode_tokens]
  707. block_size = attn_metadata.block_size // self.compress_ratio
  708. global_indices, extra_sparse_lengths = compute_global_topk_indices_and_lens(
  709. self.topk_indices_buffer[:num_decode_tokens],
  710. swa_metadata.token_to_req_indices,
  711. attn_metadata.block_table[:num_decodes],
  712. block_size,
  713. is_valid,
  714. )
  715. extra_sparse_indices = global_indices.view(num_decode_tokens, 1, -1)
  716. swa_indices = swa_metadata.decode_swa_indices
  717. swa_lens = swa_metadata.decode_swa_lens
  718. assert swa_indices is not None
  719. assert swa_lens is not None
  720. q = self._prepare_query(q, output)
  721. swa_cache = self._as_sparse_cache(self.swa_cache_layer.kv_cache)
  722. extra_cache = self._as_sparse_cache(kv_cache) if kv_cache is not None else None
  723. if extra_cache is not None and extra_sparse_indices is None:
  724. raise RuntimeError(
  725. "Compressed sparse MLA decode requires compressed sparse indices."
  726. )
  727. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  728. query=q,
  729. swa_kv_cache=swa_cache,
  730. workspace_buffer=self._get_workspace(q.device),
  731. sparse_indices=swa_indices,
  732. compressed_kv_cache=extra_cache,
  733. out=output,
  734. bmm1_scale=self.scale,
  735. sinks=self.attn_sink,
  736. kv_layout="NHD",
  737. swa_topk_lens=swa_lens,
  738. extra_sparse_indices=extra_sparse_indices,
  739. extra_sparse_topk_lens=extra_sparse_lengths,
  740. )
  741. def _forward_prefill(
  742. self,
  743. q: torch.Tensor,
  744. compressed_k_cache: torch.Tensor | None,
  745. swa_k_cache: torch.Tensor,
  746. output: torch.Tensor,
  747. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  748. swa_metadata: "DeepseekSparseSWAMetadata",
  749. ) -> None:
  750. swa_only = self.compress_ratio == 0
  751. num_prefills = swa_metadata.num_prefills
  752. num_decodes = swa_metadata.num_decodes
  753. num_decode_tokens = swa_metadata.num_decode_tokens
  754. num_prefill_tokens = swa_metadata.num_prefill_tokens
  755. query_start_loc_cpu = swa_metadata.query_start_loc_cpu
  756. assert query_start_loc_cpu is not None
  757. prefill_token_base = query_start_loc_cpu[num_decodes]
  758. extra_sparse_indices: torch.Tensor | None = None
  759. extra_sparse_lengths: torch.Tensor | None = None
  760. if not swa_only:
  761. if self.topk_indices_buffer is None:
  762. raise RuntimeError(
  763. "Compressed-layer prefill requires top-k indices from the indexer."
  764. )
  765. if attn_metadata is None:
  766. raise RuntimeError("Compressed-layer prefill metadata is missing.")
  767. if swa_metadata.token_to_req_indices is None:
  768. raise RuntimeError(
  769. "Compressed-layer prefill request mapping is missing."
  770. )
  771. if swa_metadata.is_valid_token is None:
  772. raise RuntimeError(
  773. "Compressed-layer prefill validity metadata is missing."
  774. )
  775. # Local indices filled by the index-source layer's indexer.
  776. local_topk_indices = self.topk_indices_buffer[
  777. num_decode_tokens : num_decode_tokens + num_prefill_tokens
  778. ]
  779. prefill_token_slice = slice(
  780. num_decode_tokens, num_decode_tokens + num_prefill_tokens
  781. )
  782. block_size = attn_metadata.block_size // self.compress_ratio
  783. extra_sparse_indices, extra_sparse_lengths = (
  784. compute_global_topk_indices_and_lens(
  785. local_topk_indices,
  786. swa_metadata.token_to_req_indices[prefill_token_slice],
  787. attn_metadata.block_table,
  788. block_size,
  789. swa_metadata.is_valid_token[prefill_token_slice],
  790. )
  791. )
  792. assert swa_metadata.prefill_swa_indices is not None
  793. assert swa_metadata.prefill_swa_lens is not None
  794. q = self._prepare_query(q, output)
  795. swa_kv_paged = self._as_sparse_cache(swa_k_cache)
  796. if swa_only:
  797. extra_kv_paged = None
  798. else:
  799. if compressed_k_cache is None:
  800. raise RuntimeError(
  801. "Compressed sparse MLA layers require their compressed KV cache."
  802. )
  803. extra_kv_paged = self._as_sparse_cache(compressed_k_cache)
  804. num_chunks = (
  805. num_prefills + self.PREFILL_CHUNK_SIZE - 1
  806. ) // self.PREFILL_CHUNK_SIZE
  807. for chunk_idx in range(num_chunks):
  808. chunk_start = chunk_idx * self.PREFILL_CHUNK_SIZE
  809. chunk_end = min(chunk_start + self.PREFILL_CHUNK_SIZE, num_prefills)
  810. query_start = (
  811. query_start_loc_cpu[num_decodes + chunk_start] - prefill_token_base
  812. )
  813. query_end = (
  814. query_start_loc_cpu[num_decodes + chunk_end] - prefill_token_base
  815. )
  816. extra_sparse_indices_chunk = (
  817. extra_sparse_indices[query_start:query_end]
  818. if extra_sparse_indices is not None
  819. else None
  820. )
  821. extra_sparse_lengths_chunk = (
  822. extra_sparse_lengths[query_start:query_end]
  823. if extra_sparse_lengths is not None
  824. else None
  825. )
  826. q_chunk = q[query_start:query_end]
  827. swa_indices_chunk = swa_metadata.prefill_swa_indices[query_start:query_end]
  828. swa_lens_chunk = swa_metadata.prefill_swa_lens[query_start:query_end]
  829. if extra_kv_paged is not None and extra_sparse_indices_chunk is None:
  830. raise RuntimeError(
  831. "Compressed sparse MLA prefill requires compressed sparse indices."
  832. )
  833. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  834. query=q_chunk,
  835. swa_kv_cache=swa_kv_paged,
  836. workspace_buffer=self._get_workspace(q.device),
  837. sparse_indices=swa_indices_chunk,
  838. compressed_kv_cache=extra_kv_paged,
  839. out=output[query_start:query_end],
  840. bmm1_scale=self.scale,
  841. sinks=self.attn_sink,
  842. kv_layout="NHD",
  843. swa_topk_lens=swa_lens_chunk,
  844. extra_sparse_indices=extra_sparse_indices_chunk,
  845. extra_sparse_topk_lens=extra_sparse_lengths_chunk,
  846. )