flashinfer_sparse.py 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. """DeepSeek V4 FlashInfer sparse MLA backend."""
  4. import os
  5. from typing import TYPE_CHECKING, ClassVar, cast
  6. import torch
  7. from vllm.config import VllmConfig
  8. from vllm.distributed import get_dcp_group
  9. from vllm.logger import init_logger
  10. from vllm.config.cache import CacheDType
  11. from vllm.forward_context import get_forward_context
  12. from vllm.models.deepseek_v4.nvidia.ops.o_proj import (
  13. compute_fp8_einsum_recipe,
  14. deep_gemm_fp8_o_proj,
  15. )
  16. from vllm.models.deepseek_v4_1.attention import DeepseekV4Attention
  17. from vllm.models.deepseek_v4_1.common.ops import (
  18. build_flashinfer_mixed_sparse_indices,
  19. compute_global_topk_indices_and_lens,
  20. )
  21. from vllm.models.deepseek_v4_1.sparse_mla import (
  22. DeepseekV4FlashMLAMetadata,
  23. DeepseekV4SparseMLABackend,
  24. DeepseekV4SparseMLAMetadataBuilder,
  25. DeepseekV41SparseSWAMetadataBuilder,
  26. )
  27. from vllm.platforms import current_platform
  28. from vllm.platforms.interface import DeviceCapability
  29. from vllm.utils.flashinfer import flashinfer_trtllm_batch_decode_sparse_mla_dsv4
  30. from vllm.v1.attention.backend import AttentionCGSupport, MultipleOf
  31. from vllm.v1.attention.backends.mla.compressor_utils import (
  32. get_dspark_swa_index_width,
  33. )
  34. from vllm.v1.attention.backends.mla.sparse_swa import DeepseekSparseSWABackend
  35. from vllm.v1.attention.backends.mla.sparse_utils import (
  36. triton_filter_and_convert_dcp_index,
  37. )
  38. from vllm.v1.attention.ops.dcp import (
  39. CPTritonContext,
  40. cp_lse_ag_out_rs,
  41. dcp_a2a_lse_reduce,
  42. )
  43. if TYPE_CHECKING:
  44. from vllm.v1.attention.backends.mla.sparse_swa import DeepseekSparseSWAMetadata
  45. logger = init_logger(__name__)
  46. from vllm.model_executor.kernels.attention.dsa.candidate_blocks import ( # noqa: E402
  47. _PREFILL_TIMING as _DSV41_PREFILL_TIMING,
  48. _PrefillTimer as _DSV41PrefillTimer,
  49. prefill_acc_add as _dsv41_prefill_acc_add,
  50. )
  51. # Stage 9: DCP prefill attention via compressed-KV gather (no q gather / combine).
  52. _DCP_PREFILL_GATHER = os.environ.get("DSV41_DCP_PREFILL_GATHER", "0") == "1"
  53. # FlashInfer's SM120 sparse-MLA kernels write this (not -inf) into the LSE of a
  54. # row with no candidate and no sink; the DCP combine needs -inf there.
  55. _SM120_EMPTY_LSE = -1e29
  56. def _sm120_sparse_attention_with_lse(
  57. query: torch.Tensor,
  58. swa_kv_cache: torch.Tensor,
  59. workspace: torch.Tensor,
  60. swa_indices: torch.Tensor,
  61. swa_lens: torch.Tensor | None,
  62. compressed_kv_cache: torch.Tensor | None,
  63. extra_indices: torch.Tensor | None,
  64. extra_lens: torch.Tensor | None,
  65. out: torch.Tensor,
  66. sm_scale: float,
  67. sinks: torch.Tensor | None,
  68. lse: torch.Tensor,
  69. ) -> None:
  70. """FlashInfer's SM120 packed sparse-MLA attention, returning the LSE.
  71. Mirrors the SM120 branch of ``trtllm_batch_decode_sparse_mla_dsv4``
  72. (flashinfer 0.7.0rc1, ``flashinfer/mla/_core.py``), whose public entry
  73. hard-codes ``return_lse=False``. ``query``/``out`` are ``[T, H, 512]``
  74. bf16, ``lse`` is ``[T, H]`` fp32 and receives the LSE in base 2 of the
  75. ``sm_scale``-scaled logits with the sink folded in (``log2(2^lse +
  76. 2^(sink*log2e))``); rows with no candidate and no sink hold ``-1e30``.
  77. """
  78. from flashinfer.mla._core import (
  79. _check_sm120_dsv4_kv_cache_layout,
  80. _SparseMLASegment,
  81. _trtllm_batch_decode_sparse_mla_sm120,
  82. )
  83. num_tokens = query.shape[0]
  84. swa_kv_cache = _check_sm120_dsv4_kv_cache_layout(swa_kv_cache, "NHD", "swa_kv_cache")
  85. segments = [
  86. _SparseMLASegment(indices=swa_indices.reshape(num_tokens, -1), lengths=swa_lens)
  87. ]
  88. if extra_indices is not None:
  89. assert compressed_kv_cache is not None
  90. compressed_kv_cache = _check_sm120_dsv4_kv_cache_layout(
  91. compressed_kv_cache, "NHD", "compressed_kv_cache"
  92. )
  93. segments.append(
  94. _SparseMLASegment(
  95. indices=extra_indices.reshape(num_tokens, -1),
  96. lengths=extra_lens,
  97. kv_cache=compressed_kv_cache,
  98. )
  99. )
  100. _trtllm_batch_decode_sparse_mla_sm120(
  101. query=query.unsqueeze(1),
  102. kv_cache=swa_kv_cache,
  103. workspace_buffer=workspace,
  104. sparse_mla_segments=segments,
  105. out=out.unsqueeze(1),
  106. sm_scale=float(sm_scale),
  107. sinks=sinks,
  108. lse=lse,
  109. return_lse=True,
  110. kv_scale_format="auto",
  111. kv_cache_format="fp8",
  112. )
  113. _FLASHINFER_DSV4_WORKSPACE_BUFFER_SIZE = 128 * 1024 * 1024
  114. _flashinfer_dsv4_workspace_by_device: dict[torch.device, torch.Tensor] = {}
  115. def _get_flashinfer_dsv4_workspace(device: torch.device) -> torch.Tensor:
  116. workspace = _flashinfer_dsv4_workspace_by_device.get(device)
  117. if workspace is None:
  118. workspace = torch.zeros(
  119. _FLASHINFER_DSV4_WORKSPACE_BUFFER_SIZE,
  120. dtype=torch.uint8,
  121. device=device,
  122. )
  123. _flashinfer_dsv4_workspace_by_device[device] = workspace
  124. return workspace
  125. def _packed_block_span(pool: torch.Tensor) -> int:
  126. """Per-block stride of ``pool`` in tokens (``stride(0)//stride(-2)``): ==
  127. block_size for unpacked KV, larger when packed (#44577). Raises if not
  128. token-aligned."""
  129. block_stride = pool.stride(0)
  130. token_stride = pool.stride(-2)
  131. if block_stride % token_stride != 0:
  132. raise NotImplementedError(
  133. "FLASHINFER_MLA_SPARSE_DSV4 packed KV requires the per-block stride "
  134. f"({block_stride}) to be a multiple of the per-token stride "
  135. f"({token_stride}); this layout is not supported yet."
  136. )
  137. return block_stride // token_stride
  138. # Sparse MLA h_q counts accepted natively (flashinfer>=0.6.14, #3545).
  139. _SPARSE_MLA_SUPPORTED_Q_HEADS = (8, 16, 32, 64, 128)
  140. def _pad_to_supported_q_heads(num_heads: int) -> int:
  141. for supported in _SPARSE_MLA_SUPPORTED_Q_HEADS:
  142. if num_heads <= supported:
  143. return supported
  144. raise ValueError(
  145. f"DeepseekV4 FlashInfer MLA Sparse does not support {num_heads} heads "
  146. "(sparse MLA kernel requires h_q in {8, 16, 32, 64, 128})."
  147. )
  148. def _required_sm120_sparse_topk(vllm_config: VllmConfig, window_size: int) -> int:
  149. """Return the SM120 DSV4 SWA specialization needed by this model."""
  150. if not vllm_config.attention_config.use_non_causal:
  151. return window_size
  152. speculative_config = vllm_config.speculative_config
  153. if speculative_config is None:
  154. return window_size
  155. return get_dspark_swa_index_width(
  156. window_size,
  157. speculative_config.num_speculative_tokens,
  158. )
  159. class DeepseekV4FlashInferMLASparseBackend(DeepseekV4SparseMLABackend):
  160. """FlashInfer backend using the DSv4 sparse metadata/cache layout.
  161. Inherits the base and backend reuses its``DeepseekV4SparseMLAMetadataBuilder``
  162. """
  163. supported_dtypes: ClassVar[list[torch.dtype]] = [torch.bfloat16]
  164. supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [
  165. "auto",
  166. "bfloat16",
  167. "fp8",
  168. "fp8_e4m3",
  169. "fp8_ds_mla",
  170. ]
  171. @staticmethod
  172. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  173. # Tech2Wild/Kai 2026-09-10 (SM12x): 64-state compressed pages, so a
  174. # ratio-1 kv-source layer needs 64-token pages (128 stays first so the
  175. # cache-config default remains 128 for ratio-2 layers).
  176. if current_platform.is_cuda() and current_platform.is_device_capability_family(120):
  177. return [128, 64]
  178. return [128]
  179. @classmethod
  180. def get_compressed_block_size(cls, compress_ratio: int) -> int | None:
  181. """Tech2Wild/Kai 2026-09-10: tokens per compressed-KV page for a kv-source
  182. layer on SM12x (FlashInfer SM120 sparse-MLA prefill/decode kernels are
  183. instantiated for 64-state pages); None = use cache_config.block_size."""
  184. if compress_ratio > 0 and current_platform.is_cuda() and current_platform.is_device_capability_family(120):
  185. return 64 * compress_ratio
  186. return None
  187. @staticmethod
  188. def get_name() -> str:
  189. return "FLASHINFER_MLA_SPARSE_DSV41"
  190. @classmethod
  191. def get_supported_head_sizes(cls) -> list[int]:
  192. return [512]
  193. @classmethod
  194. def supports_sink(cls) -> bool:
  195. return True
  196. @classmethod
  197. def is_sparse(cls) -> bool:
  198. return True
  199. @classmethod
  200. def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
  201. return capability.major in [10, 12]
  202. @classmethod
  203. def supports_combination(
  204. cls,
  205. head_size: int,
  206. dtype: torch.dtype,
  207. kv_cache_dtype: CacheDType | None,
  208. block_size: int | None,
  209. use_mla: bool,
  210. has_sink: bool,
  211. use_sparse: bool,
  212. use_mm_prefix: bool,
  213. device_capability: DeviceCapability,
  214. ) -> str | None:
  215. if device_capability.major == 10:
  216. if kv_cache_dtype == "fp8_ds_mla":
  217. return (
  218. "FLASHINFER_MLA_SPARSE_DSV4 SM10x uses the plain "
  219. "per-tensor FP8 KV layout, not fp8_ds_mla"
  220. )
  221. if kv_cache_dtype not in (None, "auto", "bfloat16", "fp8", "fp8_e4m3"):
  222. return "kv_cache_dtype not supported"
  223. return None
  224. if device_capability.major == 12:
  225. if kv_cache_dtype not in ("fp8", "fp8_e4m3", "fp8_ds_mla"):
  226. return "kv_cache_dtype not supported"
  227. from vllm.utils.flashinfer import has_flashinfer_sparse_mla_sm120
  228. if not has_flashinfer_sparse_mla_sm120():
  229. return (
  230. "FLASHINFER_MLA_SPARSE_DSV4 SM120 requires FlashInfer's "
  231. "sparse MLA decode API"
  232. )
  233. return None
  234. return "FLASHINFER_MLA_SPARSE_DSV4 requires SM10x or SM12x"
  235. @staticmethod
  236. def get_builder_cls() -> type["DeepseekV4FlashInferSparseMLAMetadataBuilder"]:
  237. return DeepseekV4FlashInferSparseMLAMetadataBuilder
  238. class DeepseekV4FlashInferSparseMLAMetadataBuilder(DeepseekV4SparseMLAMetadataBuilder):
  239. """Varlen-capable metadata builder for the FlashInfer sparse MLA backend."""
  240. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
  241. def __init__(self, *args, **kwargs):
  242. super().__init__(*args, **kwargs)
  243. # DCP port staging (DCP2-PLAN.md): the sharded-KV attention path
  244. # (per-rank sparse attention + LSE combine) lands in stage 3. Until
  245. # DSV41_DCP_STAGE >= 3, a DCP>1 boot must fail here -- after the KV
  246. # cache config is computed and logged, before any forward -- rather
  247. # than run the dcp=1 kernels over a sharded cache and serve garbage.
  248. import os
  249. dcp = self.vllm_config.parallel_config.decode_context_parallel_size
  250. if dcp > 1 and int(os.environ.get("DSV41_DCP_STAGE", "3")) < 3:
  251. raise NotImplementedError(
  252. f"DeepSeek-V4.1 sparse MLA under DCP{dcp}: the attention combine "
  253. "(port stage 3) is disabled by DSV41_DCP_STAGE; unset it or run "
  254. "with --decode-context-parallel-size 1."
  255. )
  256. class DeepseekSparseSWAFlashInferMetadataBuilder(DeepseekV41SparseSWAMetadataBuilder):
  257. """SWA metadata for the FlashInfer sparse decode path (varlen decode)."""
  258. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
  259. class DeepseekSparseSWAFlashInferBackend(DeepseekSparseSWABackend):
  260. @staticmethod
  261. def get_builder_cls() -> type[DeepseekSparseSWAFlashInferMetadataBuilder]:
  262. return DeepseekSparseSWAFlashInferMetadataBuilder
  263. class DeepseekSparseSWAFlashInferSM120Backend(DeepseekSparseSWAFlashInferBackend):
  264. """Tech2Wild/Kai 2026-09-10: SM12x. FlashInfer's SM120 sparse-MLA decode and
  265. prefill kernels are instantiated only for 64-token pages
  266. (_DECODE_DSV4_PAGE_BLOCK_SIZE = 64); the base backend's "any multiple of 32"
  267. made vLLM build the SWA cache at 32-token pages, which the SM120 path rejects
  268. ("no decode kernel for this shape ... page_block_size=32")."""
  269. @staticmethod
  270. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  271. return [64]
  272. @classmethod
  273. def get_preferred_block_size(cls, default_block_size: int) -> int:
  274. return 64
  275. @classmethod
  276. def get_swa_block_size(cls) -> int:
  277. return 64
  278. class DeepseekV4FlashInferMLAAttention(DeepseekV4Attention):
  279. """FlashInfer TRTLLM-gen sparse MLA attention layer for SM100 DeepSeek V4."""
  280. backend_cls = DeepseekV4FlashInferMLASparseBackend
  281. swa_backend_cls = DeepseekSparseSWAFlashInferBackend
  282. use_fp8_ds_mla_layout: ClassVar[bool] = False
  283. @classmethod
  284. def get_padded_num_q_heads(cls, num_heads: int) -> int:
  285. return _pad_to_supported_q_heads(num_heads)
  286. def _o_proj(self, o: torch.Tensor, positions: torch.Tensor) -> torch.Tensor:
  287. return deep_gemm_fp8_o_proj(
  288. o,
  289. positions,
  290. self.rotary_emb.cos_sin_cache,
  291. self.wo_a,
  292. self.wo_b,
  293. n_groups=self.n_local_groups,
  294. heads_per_group=self.n_local_heads // self.n_local_groups,
  295. nope_dim=self.nope_head_dim,
  296. rope_dim=self.rope_head_dim,
  297. o_lora_rank=self.o_lora_rank,
  298. einsum_recipe=self._einsum_recipe,
  299. tma_aligned_scales=self._tma_aligned_scales,
  300. )
  301. def __init__(self, *args, **kwargs) -> None:
  302. super().__init__(*args, **kwargs)
  303. self._einsum_recipe, self._tma_aligned_scales = compute_fp8_einsum_recipe(
  304. self._o_proj_block_size
  305. )
  306. # Per-tensor FP8 scale buffers + precomputed scalar BMM scales. Only the
  307. # per-tensor FP8 cache path consumes these; bf16 reads ``self.scale``.
  308. if self.kv_cache_torch_dtype != torch.float8_e4m3fn:
  309. return
  310. fp8_q_scale = 1.0
  311. fp8_kv_scale = 1.0
  312. self.register_buffer(
  313. "_flashinfer_fp8_q_scale",
  314. torch.tensor([fp8_q_scale], dtype=torch.float32),
  315. persistent=False,
  316. )
  317. self.register_buffer(
  318. "_flashinfer_fp8_q_scale_inv",
  319. torch.tensor([1.0 / fp8_q_scale], dtype=torch.float32),
  320. persistent=False,
  321. )
  322. self.register_buffer(
  323. "_flashinfer_fp8_kv_scale",
  324. torch.tensor([fp8_kv_scale], dtype=torch.float32),
  325. persistent=False,
  326. )
  327. # TRTLLM-gen takes scalar scale args on a distinct C++ path vs
  328. # one-element tensors, so these are Python floats.
  329. self._flashinfer_fp8_bmm1_scale = self.scale * fp8_q_scale * fp8_kv_scale
  330. self._flashinfer_fp8_bmm2_scale = fp8_kv_scale
  331. def forward_mqa(
  332. self,
  333. q: torch.Tensor,
  334. kv: torch.Tensor,
  335. positions: torch.Tensor,
  336. output: torch.Tensor,
  337. ) -> None:
  338. # The TRTLLM-gen kernel requires h_q in {64, 128}, so the output buffer
  339. # is allocated at the padded head count while q arrives at the local
  340. # head count; _forward pads q to match before the launcher.
  341. assert output.shape[0] == q.shape[0] and output.shape[-1] == q.shape[-1], (
  342. f"output buffer shape {output.shape} incompatible with q shape {q.shape}"
  343. )
  344. assert output.shape[1] >= q.shape[1], (
  345. f"output heads {output.shape[1]} must be >= q heads {q.shape[1]}"
  346. )
  347. # Per-tensor FP8 q produces a bf16 attention output.
  348. expected_output_dtype = (
  349. torch.bfloat16 if q.dtype == torch.float8_e4m3fn else q.dtype
  350. )
  351. assert output.dtype == expected_output_dtype, (
  352. f"output dtype {output.dtype} must match expected {expected_output_dtype} "
  353. f"for q dtype {q.dtype}"
  354. )
  355. forward_context = get_forward_context()
  356. attn_metadata = forward_context.attn_metadata
  357. if attn_metadata is None:
  358. # Warmup dummy run: FlashInfer reads the cache directly and lazily
  359. # allocates its workspace, so nothing to reserve here.
  360. output.zero_()
  361. return
  362. assert isinstance(attn_metadata, dict)
  363. # Compressed-cache metadata lives on the kv-source layer's prefix;
  364. # consumers share that cache and its block table.
  365. flashmla_metadata = cast(
  366. DeepseekV4FlashMLAMetadata | None,
  367. attn_metadata.get(self.compressed_cache_prefix)
  368. if self.compressed_cache_prefix is not None
  369. else None,
  370. )
  371. swa_metadata = cast(
  372. "DeepseekSparseSWAMetadata | None",
  373. attn_metadata.get(self.swa_cache_layer.prefix),
  374. )
  375. assert swa_metadata is not None
  376. swa_only = self.compress_ratio == 0
  377. # SWA-only layers have no compressed KV cache; consumers read the kv
  378. # source's cache.
  379. self_kv_cache = None if swa_only else self._compressed_kv_cache()
  380. swa_kv_cache = self.swa_cache_layer.kv_cache
  381. self._forward(
  382. q=q,
  383. kv_cache=self_kv_cache,
  384. swa_k_cache=swa_kv_cache,
  385. swa_metadata=swa_metadata,
  386. attn_metadata=flashmla_metadata,
  387. swa_only=swa_only,
  388. output=output,
  389. )
  390. def _build_sparse_index_metadata(
  391. self,
  392. kv_cache: torch.Tensor | None,
  393. swa_k_cache: torch.Tensor,
  394. swa_metadata: "DeepseekSparseSWAMetadata",
  395. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  396. swa_only: bool,
  397. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
  398. """Build the combined sparse-index tensors for the mixed batch.
  399. Returns ``(compressed_kv_cache, seq_lens, sparse_indices,
  400. sparse_topk_lens)``.
  401. """
  402. num_decodes = swa_metadata.num_decodes
  403. num_prefills = swa_metadata.num_prefills
  404. num_decode_tokens = swa_metadata.num_decode_tokens
  405. num_prefill_tokens = swa_metadata.num_prefill_tokens
  406. num_reqs = num_decodes + num_prefills
  407. num_tokens = num_decode_tokens + num_prefill_tokens
  408. assert swa_metadata.seq_lens is not None
  409. assert swa_metadata.query_start_loc is not None
  410. assert swa_metadata.token_to_req_indices is not None
  411. assert swa_metadata.decode_swa_indices is not None
  412. assert swa_metadata.block_table is not None
  413. decode_swa_indices = swa_metadata.decode_swa_indices.reshape(
  414. num_decode_tokens, swa_metadata.decode_swa_width
  415. )
  416. decode_compressed_topk_lens = None
  417. decode_compressed_indices_are_local = False
  418. decode_is_valid_token = None
  419. if swa_only:
  420. assert self.topk_indices_buffer is not None
  421. compressed_kv_cache = swa_k_cache
  422. decode_compressed_indices = None
  423. prefill_topk_indices = self.topk_indices_buffer[
  424. num_decode_tokens:num_tokens, :0
  425. ]
  426. compressed_block_table = None
  427. compressed_block_size = swa_metadata.block_size
  428. top_k = 0
  429. else:
  430. assert kv_cache is not None
  431. assert attn_metadata is not None
  432. assert self.topk_indices_buffer is not None
  433. assert swa_metadata.is_valid_token is not None
  434. compressed_kv_cache = kv_cache
  435. compressed_block_table = attn_metadata.block_table[:num_reqs]
  436. compressed_block_size = attn_metadata.block_size // self.compress_ratio
  437. # Local indices filled by the index-source layer's indexer.
  438. if num_prefill_tokens > 0:
  439. prefill_topk_indices = self.topk_indices_buffer[
  440. num_decode_tokens:num_tokens
  441. ]
  442. top_k = prefill_topk_indices.shape[-1]
  443. else:
  444. prefill_topk_indices = self.topk_indices_buffer[:0, :0]
  445. top_k = 0
  446. decode_compressed_indices_are_local = True
  447. decode_is_valid_token = swa_metadata.is_valid_token[:num_decode_tokens]
  448. if num_decode_tokens > 0:
  449. decode_compressed_indices = self.topk_indices_buffer[:num_decode_tokens]
  450. else:
  451. # Keep the logical width aligned with the mixed-batch case so
  452. # pure-prefill steps reuse the same Triton specialization.
  453. decode_compressed_indices = prefill_topk_indices[:0]
  454. query_start_loc = swa_metadata.query_start_loc[: num_reqs + 1]
  455. seq_lens = swa_metadata.seq_lens[:num_reqs]
  456. assert seq_lens.dtype == torch.int32
  457. # SWA-only layers all build the same mixed sparse indices, so the first
  458. # one caches them for the step; indexer layers depend on their own topk
  459. # indices and stay uncached.
  460. cached_sparse = (
  461. swa_metadata.flashinfer_sparse_index_cache.get("swa_only")
  462. if swa_only
  463. else None
  464. )
  465. if cached_sparse is None:
  466. swa_block_span = _packed_block_span(swa_k_cache)
  467. compressed_block_span = _packed_block_span(compressed_kv_cache)
  468. sparse_indices, sparse_topk_lens = build_flashinfer_mixed_sparse_indices(
  469. decode_swa_indices,
  470. decode_compressed_indices,
  471. decode_compressed_topk_lens,
  472. prefill_topk_indices[:num_prefill_tokens],
  473. query_start_loc,
  474. seq_lens,
  475. swa_metadata.token_to_req_indices[:num_tokens],
  476. swa_metadata.block_table[:num_reqs],
  477. swa_metadata.block_size,
  478. compressed_block_table,
  479. compressed_block_size,
  480. self.window_size,
  481. self.compress_ratio,
  482. top_k,
  483. decode_compressed_indices_are_local=decode_compressed_indices_are_local,
  484. decode_is_valid_token=decode_is_valid_token,
  485. swa_block_span=swa_block_span,
  486. compressed_block_span=compressed_block_span,
  487. prefill_left_visible=swa_metadata.prefill_left_visible,
  488. prefill_right_visible=swa_metadata.prefill_right_visible,
  489. # getattr for tests that bypass __init__ via object.__new__.
  490. max_image_tokens=getattr(self, "max_image_tokens", 0),
  491. )
  492. if swa_only:
  493. swa_metadata.flashinfer_sparse_index_cache["swa_only"] = (
  494. sparse_indices,
  495. sparse_topk_lens,
  496. )
  497. else:
  498. sparse_indices, sparse_topk_lens = cached_sparse
  499. return compressed_kv_cache, seq_lens, sparse_indices, sparse_topk_lens
  500. def _forward(
  501. self,
  502. q: torch.Tensor,
  503. kv_cache: torch.Tensor | None,
  504. swa_k_cache: torch.Tensor,
  505. swa_metadata: "DeepseekSparseSWAMetadata",
  506. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  507. swa_only: bool,
  508. output: torch.Tensor,
  509. ) -> None:
  510. assert self.kv_cache_torch_dtype in (torch.bfloat16, torch.float8_e4m3fn)
  511. num_decodes = swa_metadata.num_decodes
  512. num_prefills = swa_metadata.num_prefills
  513. num_decode_tokens = swa_metadata.num_decode_tokens
  514. num_prefill_tokens = swa_metadata.num_prefill_tokens
  515. num_reqs = num_decodes + num_prefills
  516. num_tokens = num_decode_tokens + num_prefill_tokens
  517. if num_tokens == 0:
  518. return
  519. (
  520. compressed_kv_cache,
  521. seq_lens,
  522. sparse_indices,
  523. sparse_topk_lens,
  524. ) = self._build_sparse_index_metadata(
  525. kv_cache=kv_cache,
  526. swa_k_cache=swa_k_cache,
  527. swa_metadata=swa_metadata,
  528. attn_metadata=attn_metadata,
  529. swa_only=swa_only,
  530. )
  531. # CUDA graph execution can pad q/output past the scheduled token count;
  532. # restrict to the real tokens (the launcher validates sparse indices).
  533. query = q[:num_tokens]
  534. output = output[:num_tokens]
  535. bmm1_scale: float | torch.Tensor = self.scale
  536. bmm2_scale: float | torch.Tensor = 1.0
  537. if self.kv_cache_torch_dtype == torch.float8_e4m3fn:
  538. assert query.dtype == torch.float8_e4m3fn
  539. bmm1_scale = self._flashinfer_fp8_bmm1_scale
  540. bmm2_scale = self._flashinfer_fp8_bmm2_scale
  541. else:
  542. assert query.dtype == torch.bfloat16
  543. query = query.contiguous()
  544. # The TRTLLM-gen sparse-MLA kernel requires h_q in {64, 128}; zero-pad
  545. # the query heads to the allocated output head count. Padded heads attend
  546. # to the shared KV and are sliced off downstream (output is padded too).
  547. padded_heads = output.shape[1]
  548. if query.shape[1] < padded_heads:
  549. padded_query = query.new_zeros(
  550. (query.shape[0], padded_heads, query.shape[2])
  551. )
  552. padded_query[:, : query.shape[1], :] = query
  553. query = padded_query
  554. workspace = _get_flashinfer_dsv4_workspace(q.device)
  555. query_start_loc = swa_metadata.query_start_loc
  556. query_start_loc_cpu = swa_metadata.query_start_loc_cpu
  557. assert query_start_loc is not None and query_start_loc_cpu is not None
  558. # Keep the TRTLLM-gen decode/prefill split: the launcher is tuned for
  559. # uniform-q batches, and this avoids flattening mixed batches into one call.
  560. if num_decode_tokens > 0:
  561. decode_cu = query_start_loc[: num_decodes + 1]
  562. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  563. query=query[:num_decode_tokens],
  564. swa_kv_cache=swa_k_cache,
  565. workspace_buffer=workspace,
  566. sparse_indices=sparse_indices[:num_decode_tokens],
  567. compressed_kv_cache=compressed_kv_cache,
  568. sparse_topk_lens=sparse_topk_lens[:num_decode_tokens],
  569. seq_lens=seq_lens[:num_decodes],
  570. out=output[:num_decode_tokens],
  571. bmm1_scale=bmm1_scale,
  572. bmm2_scale=bmm2_scale,
  573. sinks=self.attn_sink,
  574. cum_seq_lens_q=decode_cu,
  575. max_q_len=swa_metadata.max_decode_query_len,
  576. )
  577. if num_prefill_tokens > 0:
  578. # The prefill query view re-anchors at offset 0, so rebase the
  579. # cumulative query offsets to start at 0.
  580. prefill_cu = (
  581. query_start_loc[num_decodes : num_reqs + 1]
  582. - query_start_loc[num_decodes]
  583. )
  584. prefill_cu_cpu = query_start_loc_cpu[num_decodes : num_reqs + 1]
  585. prefill_lens_cpu = prefill_cu_cpu[1:] - prefill_cu_cpu[:-1]
  586. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  587. query=query[num_decode_tokens:num_tokens],
  588. swa_kv_cache=swa_k_cache,
  589. workspace_buffer=workspace,
  590. sparse_indices=sparse_indices[num_decode_tokens:num_tokens],
  591. compressed_kv_cache=compressed_kv_cache,
  592. sparse_topk_lens=sparse_topk_lens[num_decode_tokens:num_tokens],
  593. seq_lens=seq_lens[num_decodes:num_reqs],
  594. out=output[num_decode_tokens:num_tokens],
  595. bmm1_scale=bmm1_scale,
  596. bmm2_scale=bmm2_scale,
  597. sinks=self.attn_sink,
  598. cum_seq_lens_q=prefill_cu,
  599. max_q_len=int(prefill_lens_cpu.max().item()),
  600. )
  601. class DeepseekV4FlashInferSM120Attention(DeepseekV4Attention):
  602. """DeepSeek V4 sparse MLA attention through FlashInfer's SM120 kernels."""
  603. backend_cls = DeepseekV4FlashInferMLASparseBackend
  604. swa_backend_cls = DeepseekSparseSWAFlashInferSM120Backend
  605. use_fp8_ds_mla_layout: ClassVar[bool] = True
  606. # DCP: per-rank partial attention with the LSE + cross-rank combine.
  607. can_return_lse_for_decode: ClassVar[bool] = True
  608. # Under DCP the per-token q gather / combine has no cross-token dependency:
  609. # bound the transient [T, 2*heads, 512] buffers inside a prefill chunk.
  610. DCP_PREFILL_TOKEN_CHUNK: ClassVar[int] = 2048
  611. @staticmethod
  612. def _get_workspace(device: torch.device) -> torch.Tensor:
  613. return _get_flashinfer_dsv4_workspace(device)
  614. @staticmethod
  615. def _as_sparse_cache(kv_cache: torch.Tensor) -> torch.Tensor:
  616. if kv_cache.dtype == torch.float8_e4m3fn:
  617. kv_cache = kv_cache.view(torch.uint8)
  618. if kv_cache.dim() == 4:
  619. return kv_cache
  620. return kv_cache.unsqueeze(-2)
  621. @classmethod
  622. def get_padded_num_q_heads(cls, num_heads: int) -> int:
  623. return _pad_to_supported_q_heads(num_heads)
  624. def _o_proj(self, o: torch.Tensor, positions: torch.Tensor) -> torch.Tensor:
  625. return deep_gemm_fp8_o_proj(
  626. o,
  627. positions,
  628. self.rotary_emb.cos_sin_cache,
  629. self.wo_a,
  630. self.wo_b,
  631. n_groups=self.n_local_groups,
  632. heads_per_group=self.n_local_heads // self.n_local_groups,
  633. nope_dim=self.nope_head_dim,
  634. rope_dim=self.rope_head_dim,
  635. o_lora_rank=self.o_lora_rank,
  636. einsum_recipe=self._einsum_recipe,
  637. tma_aligned_scales=self._tma_aligned_scales,
  638. )
  639. def __init__(self, vllm_config: VllmConfig, *args, **kwargs) -> None:
  640. super().__init__(vllm_config, *args, **kwargs)
  641. from vllm.utils.flashinfer import has_flashinfer_sparse_mla_sm120_config
  642. required_topk = _required_sm120_sparse_topk(vllm_config, self.window_size)
  643. # DCP: the kernel sees the gathered heads [rank0 heads | rank1 heads |
  644. # ...] in TP order; the local slice must be unpadded so the head
  645. # reduce-scatter hands each rank exactly its own heads back.
  646. self.n_dcp_heads = self.n_local_heads * self.dcp_world_size
  647. if self.dcp_world_size > 1:
  648. if self.padded_heads != self.n_local_heads:
  649. raise NotImplementedError(
  650. "DeepSeek-V4.1 DCP needs an unpadded local head count in "
  651. f"{_SPARSE_MLA_SUPPORTED_Q_HEADS}; got {self.n_local_heads} "
  652. f"(padded to {self.padded_heads})."
  653. )
  654. if _pad_to_supported_q_heads(self.n_dcp_heads) != self.n_dcp_heads:
  655. raise NotImplementedError(
  656. f"DeepSeek-V4.1 DCP gathered head count {self.n_dcp_heads} is "
  657. f"not a supported sparse-MLA h_q {_SPARSE_MLA_SUPPORTED_Q_HEADS}."
  658. )
  659. kernel_heads = self.n_dcp_heads if self.dcp_world_size > 1 else self.padded_heads
  660. if not has_flashinfer_sparse_mla_sm120_config(kernel_heads, required_topk):
  661. raise RuntimeError(
  662. "FLASHINFER_MLA_SPARSE_DSV4 on SM120 requires a FlashInfer "
  663. "DSV4 sparse MLA decode specialization for "
  664. f"(num_q_heads={kernel_heads}, top_k={required_topk}). "
  665. "Install a FlashInfer build containing "
  666. "flashinfer-ai/flashinfer#4380."
  667. )
  668. # DCP state: the group (consecutive TP ranks), the gathered sink
  669. # (built lazily on the first forward, after weight loading) and the
  670. # neutral SWA index rows used on ranks other than 0.
  671. self.dcp_group = get_dcp_group() if self.dcp_world_size > 1 else None
  672. self._dcp_sink: torch.Tensor | None = None
  673. self._dcp_empty_swa: dict[int, tuple[torch.Tensor, torch.Tensor]] = {}
  674. # One Triton launch context per layer for the LSE-combine kernel (a
  675. # fresh context per call re-specializes the launch on every step).
  676. self._cp_ctx = CPTritonContext() if self.dcp_world_size > 1 else None
  677. # Combine flavour: "ag_rs" (LSE all-gather + head reduce-scatter, two
  678. # collectives) or "a2a" (one all-to-all of packed output+LSE).
  679. self._dcp_combine_a2a = os.environ.get("DSV41_DCP_COMBINE", "ag_rs") == "a2a"
  680. # Optional per-phase timing (CUDA events, synchronises; diagnostics
  681. # only): DSV41_DCP_TIMING=1 logs gather / kernel / combine ms per call,
  682. # averaged over the last 100 calls, on DCP rank 0.
  683. self._dcp_timing = os.environ.get("DSV41_DCP_TIMING", "0") == "1"
  684. self._dcp_t_acc = [0.0, 0.0, 0.0, 0]
  685. if self.dcp_world_size > 1 and self.compress_ratio > 0:
  686. logger.info_once(
  687. "DeepSeek-V4.1 SM120 attention under DCP%d: %d local heads -> %d "
  688. "gathered, SWA window + sink on DCP rank 0, base-2 LSE combine",
  689. self.dcp_world_size,
  690. self.n_local_heads,
  691. self.n_dcp_heads,
  692. )
  693. self._einsum_recipe, self._tma_aligned_scales = compute_fp8_einsum_recipe(
  694. self._o_proj_block_size
  695. )
  696. # Per-tensor FP8 cache path scales.
  697. if self.kv_cache_torch_dtype != torch.float8_e4m3fn:
  698. return
  699. fp8_q_scale = 1.0
  700. fp8_kv_scale = 1.0
  701. self.register_buffer(
  702. "_flashinfer_fp8_q_scale",
  703. torch.tensor([fp8_q_scale], dtype=torch.float32),
  704. persistent=False,
  705. )
  706. self.register_buffer(
  707. "_flashinfer_fp8_q_scale_inv",
  708. torch.tensor([1.0 / fp8_q_scale], dtype=torch.float32),
  709. persistent=False,
  710. )
  711. self.register_buffer(
  712. "_flashinfer_fp8_kv_scale",
  713. torch.tensor([fp8_kv_scale], dtype=torch.float32),
  714. persistent=False,
  715. )
  716. # FlashInfer expects scalar scale arguments for this path.
  717. self._flashinfer_fp8_bmm1_scale = self.scale * fp8_q_scale * fp8_kv_scale
  718. self._flashinfer_fp8_bmm2_scale = fp8_kv_scale
  719. def _reserve_empty_forward_workspace(self) -> None:
  720. self._get_workspace(
  721. torch.device("cuda", torch.accelerator.current_device_index())
  722. )
  723. def _forward_sparse_impl(
  724. self,
  725. q: torch.Tensor,
  726. output: torch.Tensor,
  727. flashmla_metadata: DeepseekV4FlashMLAMetadata | None,
  728. swa_metadata: "DeepseekSparseSWAMetadata",
  729. self_kv_cache: torch.Tensor | None,
  730. swa_kv_cache: torch.Tensor,
  731. swa_only: bool,
  732. ) -> None:
  733. num_decode_tokens = swa_metadata.num_decode_tokens
  734. if swa_metadata.num_prefills > 0:
  735. self._forward_prefill(
  736. q=q[num_decode_tokens:],
  737. compressed_k_cache=self_kv_cache,
  738. swa_k_cache=swa_kv_cache,
  739. output=output[num_decode_tokens:],
  740. attn_metadata=flashmla_metadata,
  741. swa_metadata=swa_metadata,
  742. )
  743. if swa_metadata.num_decodes > 0:
  744. self._forward_decode(
  745. q=q[:num_decode_tokens],
  746. kv_cache=self_kv_cache,
  747. swa_metadata=swa_metadata,
  748. attn_metadata=flashmla_metadata,
  749. swa_only=swa_only,
  750. output=output[:num_decode_tokens],
  751. )
  752. def forward_mqa(
  753. self,
  754. q: torch.Tensor,
  755. kv: torch.Tensor,
  756. positions: torch.Tensor,
  757. output: torch.Tensor,
  758. ) -> None:
  759. # Output may be padded to backend-supported head counts.
  760. assert output.shape[0] == q.shape[0] and output.shape[-1] == q.shape[-1], (
  761. f"output buffer shape {output.shape} incompatible with q shape {q.shape}"
  762. )
  763. assert output.shape[1] >= q.shape[1], (
  764. f"output heads {output.shape[1]} must be >= q heads {q.shape[1]}"
  765. )
  766. # Per-tensor FP8 q produces a bf16 attention output.
  767. expected_output_dtype = (
  768. torch.bfloat16 if q.dtype == torch.float8_e4m3fn else q.dtype
  769. )
  770. assert output.dtype == expected_output_dtype, (
  771. f"output dtype {output.dtype} must match expected {expected_output_dtype} "
  772. f"for q dtype {q.dtype}"
  773. )
  774. forward_context = get_forward_context()
  775. attn_metadata = forward_context.attn_metadata
  776. if attn_metadata is None:
  777. self._reserve_empty_forward_workspace()
  778. output.zero_()
  779. return
  780. assert isinstance(attn_metadata, dict)
  781. # Compressed-cache metadata lives on the kv-source layer's prefix;
  782. # consumers share that cache and its block table.
  783. flashmla_metadata = cast(
  784. DeepseekV4FlashMLAMetadata | None,
  785. attn_metadata.get(self.compressed_cache_prefix)
  786. if self.compressed_cache_prefix is not None
  787. else None,
  788. )
  789. swa_metadata = cast(
  790. "DeepseekSparseSWAMetadata | None",
  791. attn_metadata.get(self.swa_cache_layer.prefix),
  792. )
  793. assert swa_metadata is not None
  794. swa_only = self.compress_ratio == 0
  795. # SWA-only layers have no compressed KV cache; consumers read the kv
  796. # source's cache.
  797. self_kv_cache = None if swa_only else self._compressed_kv_cache()
  798. swa_kv_cache = self.swa_cache_layer.kv_cache
  799. self._forward_sparse_impl(
  800. q=q,
  801. output=output,
  802. flashmla_metadata=flashmla_metadata,
  803. swa_metadata=swa_metadata,
  804. self_kv_cache=self_kv_cache,
  805. swa_kv_cache=swa_kv_cache,
  806. swa_only=swa_only,
  807. )
  808. def _prepare_query(self, q: torch.Tensor, output: torch.Tensor) -> torch.Tensor:
  809. if self.kv_cache_torch_dtype == torch.float8_e4m3fn:
  810. assert q.dtype == torch.float8_e4m3fn
  811. q = q.to(torch.bfloat16)
  812. else:
  813. assert q.dtype == torch.bfloat16
  814. padded_heads = output.shape[1]
  815. if q.shape[1] < padded_heads:
  816. padded_query = q.new_zeros((q.shape[0], padded_heads, q.shape[2]))
  817. padded_query[:, : q.shape[1], :] = q
  818. q = padded_query
  819. return q.contiguous()
  820. # ---- DCP helpers -------------------------------------------------------
  821. def _compressed_topk_to_slots(
  822. self,
  823. topk_indices: torch.Tensor,
  824. token_to_req_indices: torch.Tensor,
  825. block_table: torch.Tensor,
  826. block_size: int,
  827. is_valid_token: torch.Tensor,
  828. ) -> tuple[torch.Tensor, torch.Tensor]:
  829. """Indexer top-k (compressed ids) -> physical slots + per-row count.
  830. DCP1: the request-local ids map through the block table as today.
  831. DCP>1: the ids are GLOBAL compressed ids (indexer merge / short-context
  832. bypass); de-interleave them in state coordinates, drop the states other
  833. ranks own and compact this rank's slots to a prefix (-1 tail, count).
  834. ``block_size`` is the number of states per page on this rank.
  835. """
  836. num_tokens = topk_indices.shape[0]
  837. if self.dcp_world_size == 1:
  838. return compute_global_topk_indices_and_lens(
  839. topk_indices,
  840. token_to_req_indices,
  841. block_table,
  842. block_size,
  843. is_valid_token,
  844. )
  845. slots, lens = triton_filter_and_convert_dcp_index(
  846. token_to_req_indices[:num_tokens].contiguous(),
  847. block_table,
  848. topk_indices,
  849. dcp_size=self.dcp_world_size,
  850. dcp_rank=self.dcp_rank,
  851. cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
  852. BLOCK_SIZE=block_size,
  853. BLOCK_STRIDE_ROWS=block_size,
  854. NUM_TOPK_TOKENS=topk_indices.shape[1],
  855. BLOCK_N=128,
  856. return_valid_counts=True,
  857. )
  858. # CUDA-graph padding rows attend nothing (parity with the DCP1 kernel).
  859. lens.masked_fill_(~is_valid_token[:num_tokens], 0)
  860. return slots, lens
  861. # Stage 9: one prefill step's gathered compressed KV per source cache and
  862. # remapped top-k per index buffer, shared by every layer of that step. The
  863. # metadata object is held so the cache resets exactly once per step.
  864. _dcp_gather_cache: ClassVar[dict] = {"meta": None, "kv": {}, "idx": {}, "geom": None}
  865. def _dcp_prefill_gather(
  866. self,
  867. compressed_k_cache: torch.Tensor,
  868. topk_indices: torch.Tensor,
  869. token_to_req: torch.Tensor,
  870. is_valid_token: torch.Tensor,
  871. attn_metadata: DeepseekV4FlashMLAMetadata,
  872. num_decodes: int,
  873. num_prefills: int,
  874. block_size: int,
  875. ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
  876. """DCP prefill without q gather / LSE combine: gather the DCP ranks'
  877. compressed pages of every prefill request into one rank-major paged
  878. buffer and remap the GLOBAL top-k ids into it (see stage 9 notes).
  879. Returns (gathered cache [W*P_total, block_size, 1, B], slots, lens)."""
  880. cache = type(self)._dcp_gather_cache
  881. if cache["meta"] is not attn_metadata:
  882. cache["meta"] = attn_metadata
  883. cache["kv"] = {}
  884. cache["idx"] = {}
  885. cache["geom"] = None
  886. W = self.dcp_world_size
  887. K = self.cp_kv_cache_interleave_size
  888. bs = block_size
  889. dev = topk_indices.device
  890. if cache["geom"] is None:
  891. assert attn_metadata.seq_lens is not None
  892. seq_lens = attn_metadata.seq_lens[num_decodes : num_decodes + num_prefills].tolist()
  893. # states that exist after this step (state = pos // ratio) -> virtual
  894. # blocks of bs*W states = one page per rank; at least one page so
  895. # the collective never carries zero bytes.
  896. pages = [max(1, -(-(int(sl) // self.compress_ratio) // (bs * W))) for sl in seq_lens]
  897. base = [0]
  898. for p in pages:
  899. base.append(base[-1] + W * p)
  900. cache["geom"] = (
  901. pages,
  902. torch.tensor(pages, device=dev, dtype=torch.int64),
  903. torch.tensor(base[:-1], device=dev, dtype=torch.int64),
  904. )
  905. pages, pages_t, base_t = cache["geom"]
  906. key = compressed_k_cache.data_ptr()
  907. kv = cache["kv"].get(key)
  908. if kv is None:
  909. with _DSV41PrefillTimer("attn_gather", int(topk_indices.shape[0])):
  910. pool = self._as_sparse_cache(compressed_k_cache)
  911. parts = []
  912. for i, p in enumerate(pages):
  913. page_ids = attn_metadata.block_table[num_decodes + i, :p].long()
  914. local = pool.index_select(0, page_ids).contiguous()
  915. parts.append(self.dcp_group.all_gather(local, dim=0))
  916. kv = parts[0] if len(parts) == 1 else torch.cat(parts, dim=0)
  917. cache["kv"][key] = kv
  918. ikey = (topk_indices.data_ptr(), tuple(topk_indices.shape))
  919. idx = cache["idx"].get(ikey)
  920. if idx is None:
  921. n_req = max(len(pages) - 1, 0)
  922. req = (token_to_req.long() - num_decodes).clamp_(0, n_req)
  923. ids = topk_indices.long()
  924. valid = ids >= 0
  925. st = ids.clamp_min(0)
  926. span = bs * W
  927. vblock = st // span
  928. off = st % span
  929. owner = (off // K) % W
  930. loff = (off // (K * W)) * K + off % K
  931. slot = (base_t[req][:, None] + owner * pages_t[req][:, None] + vblock) * bs + loff
  932. slots = torch.where(valid, slot, torch.full_like(slot, -1)).to(torch.int32)
  933. lens = valid.sum(dim=-1, dtype=torch.int32)
  934. lens.masked_fill_(~is_valid_token[: lens.shape[0]], 0)
  935. idx = (slots, lens)
  936. cache["idx"][ikey] = idx
  937. return kv, idx[0], idx[1]
  938. def _dcp_attn_sink(self) -> torch.Tensor:
  939. """The sink for the gathered heads, in TP head order (collective)."""
  940. if self._dcp_sink is None:
  941. assert self.dcp_group is not None
  942. local = self.attn_sink.data[: self.n_local_heads].contiguous()
  943. self._dcp_sink = self.dcp_group.all_gather(local, dim=0)
  944. return self._dcp_sink
  945. def _dcp_neutral_swa(
  946. self, num_tokens: int, width: int, device: torch.device
  947. ) -> tuple[torch.Tensor, torch.Tensor]:
  948. """All -1 SWA indices and zero lengths (ranks other than 0)."""
  949. buf = self._dcp_empty_swa.get(width)
  950. if buf is None or buf[0].shape[0] < num_tokens:
  951. rows = max(num_tokens, self.max_num_batched_tokens)
  952. buf = (
  953. torch.full((rows, 1, width), -1, dtype=torch.int32, device=device),
  954. torch.zeros(rows, dtype=torch.int32, device=device),
  955. )
  956. self._dcp_empty_swa[width] = buf
  957. return buf[0][:num_tokens], buf[1][:num_tokens]
  958. def _run_sm120_dcp(
  959. self,
  960. q: torch.Tensor,
  961. swa_cache: torch.Tensor,
  962. swa_indices: torch.Tensor,
  963. swa_lens: torch.Tensor,
  964. extra_cache: torch.Tensor | None,
  965. extra_indices: torch.Tensor | None,
  966. extra_lens: torch.Tensor | None,
  967. output: torch.Tensor,
  968. ) -> None:
  969. """One DCP attention step for ``q``/``output`` of ``[T, n_local_heads, 512]``.
  970. Gather the local heads to ``[T, n_local_heads * W, 512]`` (TP order),
  971. run this rank's sparse attention over its compressed-state shard with
  972. the LSE, then combine across the DCP group and reduce-scatter the heads
  973. back. The replicated SWA window and the sink are attended on DCP rank 0
  974. only, so with ``L_r`` the per-rank base-2 LSEs (sink folded into
  975. ``L_0``) the combine ``L = log2(sum_r 2^L_r)`` and ``o = sum_r
  976. 2^(L_r - L) o_r`` reproduce the DCP1 softmax exactly.
  977. """
  978. assert self.dcp_group is not None
  979. num_tokens = q.shape[0]
  980. # Timing events cannot be synchronised inside a CUDA-graph capture (the
  981. # eager break runs on the capture stream during warmup/capture).
  982. capturing = torch.cuda.is_current_stream_capturing()
  983. prefill_timing = _DSV41_PREFILL_TIMING and num_tokens >= 1024 and not capturing
  984. timing = (self._dcp_timing or prefill_timing) and not capturing
  985. if self._dcp_timing:
  986. logger.info_once(
  987. "DCP path active on layer %d (capturing=%s, T=%d)",
  988. self.layer_id,
  989. capturing,
  990. num_tokens,
  991. )
  992. if timing:
  993. ev = [torch.cuda.Event(enable_timing=True) for _ in range(4)]
  994. ev[0].record()
  995. # Collective: every rank builds the gathered sink (lazily, once); only
  996. # DCP rank 0 applies it, together with the replicated SWA window.
  997. sink = self._dcp_attn_sink()
  998. q_gathered = self.dcp_group.all_gather(q, dim=1)
  999. num_heads = q_gathered.shape[1]
  1000. if timing:
  1001. ev[1].record()
  1002. out_gathered = torch.empty(
  1003. (num_tokens, num_heads, self.head_dim), dtype=torch.bfloat16, device=q.device
  1004. )
  1005. lse = torch.empty((num_tokens, num_heads), dtype=torch.float32, device=q.device)
  1006. if self.dcp_rank != 0:
  1007. swa_indices, swa_lens = self._dcp_neutral_swa(
  1008. num_tokens, swa_indices.shape[-1], q.device
  1009. )
  1010. _sm120_sparse_attention_with_lse(
  1011. q_gathered,
  1012. swa_cache,
  1013. self._get_workspace(q.device),
  1014. swa_indices,
  1015. swa_lens,
  1016. extra_cache,
  1017. extra_indices,
  1018. extra_lens,
  1019. out_gathered,
  1020. self.scale,
  1021. sink if self.dcp_rank == 0 else None,
  1022. lse,
  1023. )
  1024. if timing:
  1025. ev[2].record()
  1026. # Rows with no local candidate (and no sink) carry the kernel's -1e30
  1027. # LSE and a zero output; the combine kernel's max-subtracted exp2 makes
  1028. # that the merge identity without any masking (test_stage3_combine).
  1029. if self._dcp_combine_a2a:
  1030. combined = dcp_a2a_lse_reduce(
  1031. out_gathered, lse, self.dcp_group, is_lse_base_on_e=False
  1032. )
  1033. else:
  1034. combined = cp_lse_ag_out_rs(
  1035. out_gathered,
  1036. lse,
  1037. self.dcp_group,
  1038. ctx=self._cp_ctx,
  1039. is_lse_base_on_e=False,
  1040. )
  1041. output.copy_(combined)
  1042. if timing:
  1043. ev[3].record()
  1044. ev[3].synchronize()
  1045. if prefill_timing:
  1046. _dsv41_prefill_acc_add("attn_gather", ev[0].elapsed_time(ev[1]))
  1047. _dsv41_prefill_acc_add("attn_kernel", ev[1].elapsed_time(ev[2]))
  1048. _dsv41_prefill_acc_add("attn_combine", ev[2].elapsed_time(ev[3]))
  1049. acc = self._dcp_t_acc
  1050. acc[0] += ev[0].elapsed_time(ev[1])
  1051. acc[1] += ev[1].elapsed_time(ev[2])
  1052. acc[2] += ev[2].elapsed_time(ev[3])
  1053. acc[3] += 1
  1054. if self._dcp_timing and acc[3] % 100 == 0 and self.dcp_rank == 0:
  1055. logger.info(
  1056. "DCP timing layer %d (T=%d): gather %.3f ms, kernel %.3f ms, "
  1057. "combine %.3f ms per call (avg of %d)",
  1058. self.layer_id,
  1059. num_tokens,
  1060. acc[0] / acc[3],
  1061. acc[1] / acc[3],
  1062. acc[2] / acc[3],
  1063. acc[3],
  1064. )
  1065. acc[0] = acc[1] = acc[2] = 0.0
  1066. acc[3] = 0
  1067. def _forward_decode(
  1068. self,
  1069. q: torch.Tensor,
  1070. kv_cache: torch.Tensor | None,
  1071. swa_metadata: "DeepseekSparseSWAMetadata",
  1072. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  1073. swa_only: bool,
  1074. output: torch.Tensor,
  1075. ) -> None:
  1076. num_decodes = swa_metadata.num_decodes
  1077. num_decode_tokens = swa_metadata.num_decode_tokens
  1078. extra_sparse_indices = None
  1079. extra_sparse_lengths = None
  1080. if not swa_only:
  1081. if attn_metadata is None:
  1082. raise RuntimeError(
  1083. "Sparse MLA metadata is required for compressed layers."
  1084. )
  1085. if swa_metadata.is_valid_token is None:
  1086. raise RuntimeError(
  1087. "SWA validity metadata is required for compressed layers."
  1088. )
  1089. if self.topk_indices_buffer is None:
  1090. raise RuntimeError(
  1091. "Compressed-layer decode requires top-k indices from the indexer."
  1092. )
  1093. # Indices filled by the index-source layer's indexer (request-local
  1094. # at DCP1, global compressed ids under DCP).
  1095. is_valid = swa_metadata.is_valid_token[:num_decode_tokens]
  1096. block_size = attn_metadata.block_size // self.compress_ratio
  1097. global_indices, extra_sparse_lengths = self._compressed_topk_to_slots(
  1098. self.topk_indices_buffer[:num_decode_tokens],
  1099. swa_metadata.token_to_req_indices,
  1100. attn_metadata.block_table[:num_decodes],
  1101. block_size,
  1102. is_valid,
  1103. )
  1104. extra_sparse_indices = global_indices.view(num_decode_tokens, 1, -1)
  1105. swa_indices = swa_metadata.decode_swa_indices
  1106. swa_lens = swa_metadata.decode_swa_lens
  1107. assert swa_indices is not None
  1108. assert swa_lens is not None
  1109. q = self._prepare_query(q, output)
  1110. swa_cache = self._as_sparse_cache(self.swa_cache_layer.kv_cache)
  1111. extra_cache = self._as_sparse_cache(kv_cache) if kv_cache is not None else None
  1112. if extra_cache is not None and extra_sparse_indices is None:
  1113. raise RuntimeError(
  1114. "Compressed sparse MLA decode requires compressed sparse indices."
  1115. )
  1116. if self.dcp_world_size > 1 and not swa_only:
  1117. # SWA-only layers keep the DCP1 path: their cache is replicated and
  1118. # every rank runs its own heads over the whole window.
  1119. self._run_sm120_dcp(
  1120. q,
  1121. swa_cache,
  1122. swa_indices,
  1123. swa_lens,
  1124. extra_cache,
  1125. extra_sparse_indices,
  1126. extra_sparse_lengths,
  1127. output,
  1128. )
  1129. return
  1130. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  1131. query=q,
  1132. swa_kv_cache=swa_cache,
  1133. workspace_buffer=self._get_workspace(q.device),
  1134. sparse_indices=swa_indices,
  1135. compressed_kv_cache=extra_cache,
  1136. out=output,
  1137. bmm1_scale=self.scale,
  1138. sinks=self.attn_sink,
  1139. kv_layout="NHD",
  1140. swa_topk_lens=swa_lens,
  1141. extra_sparse_indices=extra_sparse_indices,
  1142. extra_sparse_topk_lens=extra_sparse_lengths,
  1143. )
  1144. def _forward_prefill(
  1145. self,
  1146. q: torch.Tensor,
  1147. compressed_k_cache: torch.Tensor | None,
  1148. swa_k_cache: torch.Tensor,
  1149. output: torch.Tensor,
  1150. attn_metadata: DeepseekV4FlashMLAMetadata | None,
  1151. swa_metadata: "DeepseekSparseSWAMetadata",
  1152. ) -> None:
  1153. swa_only = self.compress_ratio == 0
  1154. num_prefills = swa_metadata.num_prefills
  1155. num_decodes = swa_metadata.num_decodes
  1156. num_decode_tokens = swa_metadata.num_decode_tokens
  1157. num_prefill_tokens = swa_metadata.num_prefill_tokens
  1158. query_start_loc_cpu = swa_metadata.query_start_loc_cpu
  1159. assert query_start_loc_cpu is not None
  1160. prefill_token_base = query_start_loc_cpu[num_decodes]
  1161. extra_sparse_indices: torch.Tensor | None = None
  1162. extra_sparse_lengths: torch.Tensor | None = None
  1163. gathered_kv: torch.Tensor | None = None
  1164. if not swa_only:
  1165. if self.topk_indices_buffer is None:
  1166. raise RuntimeError(
  1167. "Compressed-layer prefill requires top-k indices from the indexer."
  1168. )
  1169. if attn_metadata is None:
  1170. raise RuntimeError("Compressed-layer prefill metadata is missing.")
  1171. if swa_metadata.token_to_req_indices is None:
  1172. raise RuntimeError(
  1173. "Compressed-layer prefill request mapping is missing."
  1174. )
  1175. if swa_metadata.is_valid_token is None:
  1176. raise RuntimeError(
  1177. "Compressed-layer prefill validity metadata is missing."
  1178. )
  1179. # Local indices filled by the index-source layer's indexer.
  1180. local_topk_indices = self.topk_indices_buffer[
  1181. num_decode_tokens : num_decode_tokens + num_prefill_tokens
  1182. ]
  1183. prefill_token_slice = slice(
  1184. num_decode_tokens, num_decode_tokens + num_prefill_tokens
  1185. )
  1186. block_size = attn_metadata.block_size // self.compress_ratio
  1187. if self.dcp_world_size > 1 and _DCP_PREFILL_GATHER:
  1188. assert compressed_k_cache is not None
  1189. gathered_kv, extra_sparse_indices, extra_sparse_lengths = (
  1190. self._dcp_prefill_gather(
  1191. compressed_k_cache,
  1192. local_topk_indices,
  1193. swa_metadata.token_to_req_indices[prefill_token_slice],
  1194. swa_metadata.is_valid_token[prefill_token_slice],
  1195. attn_metadata,
  1196. num_decodes,
  1197. num_prefills,
  1198. block_size,
  1199. )
  1200. )
  1201. else:
  1202. extra_sparse_indices, extra_sparse_lengths = (
  1203. self._compressed_topk_to_slots(
  1204. local_topk_indices,
  1205. swa_metadata.token_to_req_indices[prefill_token_slice],
  1206. attn_metadata.block_table,
  1207. block_size,
  1208. swa_metadata.is_valid_token[prefill_token_slice],
  1209. )
  1210. )
  1211. assert swa_metadata.prefill_swa_indices is not None
  1212. assert swa_metadata.prefill_swa_lens is not None
  1213. q = self._prepare_query(q, output)
  1214. swa_kv_paged = self._as_sparse_cache(swa_k_cache)
  1215. if swa_only:
  1216. extra_kv_paged = None
  1217. else:
  1218. if compressed_k_cache is None:
  1219. raise RuntimeError(
  1220. "Compressed sparse MLA layers require their compressed KV cache."
  1221. )
  1222. extra_kv_paged = (
  1223. gathered_kv
  1224. if gathered_kv is not None
  1225. else self._as_sparse_cache(compressed_k_cache)
  1226. )
  1227. num_chunks = (
  1228. num_prefills + self.PREFILL_CHUNK_SIZE - 1
  1229. ) // self.PREFILL_CHUNK_SIZE
  1230. for chunk_idx in range(num_chunks):
  1231. chunk_start = chunk_idx * self.PREFILL_CHUNK_SIZE
  1232. chunk_end = min(chunk_start + self.PREFILL_CHUNK_SIZE, num_prefills)
  1233. query_start = (
  1234. query_start_loc_cpu[num_decodes + chunk_start] - prefill_token_base
  1235. )
  1236. query_end = (
  1237. query_start_loc_cpu[num_decodes + chunk_end] - prefill_token_base
  1238. )
  1239. extra_sparse_indices_chunk = (
  1240. extra_sparse_indices[query_start:query_end]
  1241. if extra_sparse_indices is not None
  1242. else None
  1243. )
  1244. extra_sparse_lengths_chunk = (
  1245. extra_sparse_lengths[query_start:query_end]
  1246. if extra_sparse_lengths is not None
  1247. else None
  1248. )
  1249. q_chunk = q[query_start:query_end]
  1250. swa_indices_chunk = swa_metadata.prefill_swa_indices[query_start:query_end]
  1251. swa_lens_chunk = swa_metadata.prefill_swa_lens[query_start:query_end]
  1252. if extra_kv_paged is not None and extra_sparse_indices_chunk is None:
  1253. raise RuntimeError(
  1254. "Compressed sparse MLA prefill requires compressed sparse indices."
  1255. )
  1256. if self.dcp_world_size > 1 and not swa_only and gathered_kv is None:
  1257. # Chunk bounds come from the CPU query_start_loc (identical on
  1258. # every rank), so the collectives below line up across ranks.
  1259. qs, qe = int(query_start), int(query_end)
  1260. for sub_start in range(qs, qe, self.DCP_PREFILL_TOKEN_CHUNK):
  1261. sub_end = min(sub_start + self.DCP_PREFILL_TOKEN_CHUNK, qe)
  1262. assert extra_sparse_indices is not None
  1263. assert extra_sparse_lengths is not None
  1264. self._run_sm120_dcp(
  1265. q[sub_start:sub_end],
  1266. swa_kv_paged,
  1267. swa_metadata.prefill_swa_indices[sub_start:sub_end],
  1268. swa_metadata.prefill_swa_lens[sub_start:sub_end],
  1269. extra_kv_paged,
  1270. extra_sparse_indices[sub_start:sub_end],
  1271. extra_sparse_lengths[sub_start:sub_end],
  1272. output[sub_start:sub_end],
  1273. )
  1274. continue
  1275. with _DSV41PrefillTimer(
  1276. "attn_kernel",
  1277. int(query_end - query_start) if gathered_kv is not None else 0,
  1278. ):
  1279. flashinfer_trtllm_batch_decode_sparse_mla_dsv4(
  1280. query=q_chunk,
  1281. swa_kv_cache=swa_kv_paged,
  1282. workspace_buffer=self._get_workspace(q.device),
  1283. sparse_indices=swa_indices_chunk,
  1284. compressed_kv_cache=extra_kv_paged,
  1285. out=output[query_start:query_end],
  1286. bmm1_scale=self.scale,
  1287. sinks=self.attn_sink,
  1288. kv_layout="NHD",
  1289. swa_topk_lens=swa_lens_chunk,
  1290. extra_sparse_indices=extra_sparse_indices_chunk,
  1291. extra_sparse_topk_lens=extra_sparse_lengths_chunk,
  1292. )