flash_mla_sm120.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. """SM120 FlashMLA sparse decode implementation.
  2. On SM120 (Blackwell Desktop / RTX PRO 6000) the flash_mla CUDA kernel
  3. is not available, so this module provides alternative implementations:
  4. - A fused Triton kernel (default, ``SGLANG_SM120_TRITON_FLASHMLA=1``)
  5. - A pure-PyTorch fallback (``SGLANG_SM120_TRITON_FLASHMLA=0``)
  6. The FP8 KV cache uses a page-internal layout where NOPE+ROPE data has
  7. stride (nope_dim + rope_dim*2) per token, and scales are stored in a
  8. separate region at the end of each page.
  9. """
  10. import logging
  11. import math
  12. from typing import Optional
  13. import torch
  14. import triton
  15. import triton.language as tl
  16. from sglang.srt.environ import envs
  17. from sglang.srt.utils import is_hip
  18. logger = logging.getLogger(__name__)
  19. _is_hip = is_hip()
  20. _GLM_DSA_MODEL_ARCHS = (
  21. "GlmMoeDsaForCausalLM",
  22. "GlmMoeDsaForCausalLMNextN",
  23. )
  24. # Page layout constants for DSv4-Flash (MODEL1):
  25. # nope_dim = 448, rope_dim = 64, quantize_block_size = 64
  26. # nope_rope_stride = 448 + 64*2 = 576 bytes per token
  27. # scale_stride = ceil(448/64) + 1 = 8 bytes per token (7 scales + 1 pad)
  28. # bytes_per_token = 448 + 128 + 8 = 584
  29. # page_bytes = ceil_div(page_size * 584, 576) * 576
  30. _NOPE_DIM = 448
  31. _ROPE_DIM = 64
  32. _NOPE_ROPE_STRIDE = _NOPE_DIM + _ROPE_DIM * 2 # 576
  33. _TILE_SIZE = 64
  34. _NUM_TILES = _NOPE_DIM // _TILE_SIZE # 7
  35. _SCALE_STRIDE = _NUM_TILES + 1 # 8 (7 scales + 1 pad)
  36. _D = _NOPE_DIM + _ROPE_DIM # 512
  37. def _gather_and_dequant(k_cache, indices, page_size):
  38. """Gather KV entries from the paged buffer using correct page-internal addressing.
  39. Args:
  40. k_cache: (num_pages, page_size, 1, bytes_per_token) float8_e4m3fn
  41. Non-contiguous view of the raw page buffer.
  42. indices: (...) int32/int64, token-level indices. -1 = invalid.
  43. page_size: tokens per page (256)
  44. Returns:
  45. kv: (..., _D) bfloat16, dequantized KV vectors
  46. """
  47. idx_shape = indices.shape
  48. flat_idx = indices.reshape(-1) # (N,)
  49. N = flat_idx.shape[0]
  50. device = k_cache.device
  51. # Page-level addressing
  52. page_bytes = k_cache.stride(0) # actual byte stride between pages
  53. pages = flat_idx // page_size
  54. offsets = flat_idx % page_size
  55. # Clamp invalid indices
  56. safe_pages = pages.clamp(min=0)
  57. safe_offsets = offsets.clamp(min=0)
  58. # Access raw buffer as uint8 — use as_strided to get full page view
  59. num_pages = k_cache.shape[0]
  60. raw_pages = k_cache.as_strided(
  61. (num_pages, page_bytes),
  62. (page_bytes, 1),
  63. ).view(torch.uint8) # (num_pages, page_bytes) uint8
  64. # Note: float8_e4m3fn and uint8 are both 1 byte, view is safe
  65. # Compute byte offsets within each page
  66. # NOPE: page[safe_page, safe_offset * 576 + 0:448]
  67. # ROPE: page[safe_page, safe_offset * 576 + 448:576]
  68. # SCALES: page[safe_page, page_size * 576 + safe_offset * 8 + 0:7]
  69. nope_base = safe_offsets * _NOPE_ROPE_STRIDE # (N,)
  70. nope_offsets = nope_base.unsqueeze(-1) + torch.arange(
  71. _NOPE_DIM, device=device, dtype=torch.long
  72. ) # (N, 448)
  73. rope_base = nope_base + _NOPE_DIM # (N,)
  74. rope_offsets = rope_base.unsqueeze(-1) + torch.arange(
  75. _ROPE_DIM * 2, device=device, dtype=torch.long
  76. ) # (N, 128)
  77. scale_section_offset = page_size * _NOPE_ROPE_STRIDE # 147456
  78. scale_base = scale_section_offset + safe_offsets * _SCALE_STRIDE # (N,)
  79. scale_offsets = scale_base.unsqueeze(-1) + torch.arange(
  80. _NUM_TILES, device=device, dtype=torch.long
  81. ) # (N, 7)
  82. # Gather bytes per page — use advanced indexing
  83. # raw_pages[safe_pages, nope_offsets] → (N, 448)
  84. page_idx_nope = safe_pages.unsqueeze(-1).expand_as(nope_offsets)
  85. nope_bytes = raw_pages[page_idx_nope, nope_offsets] # (N, 448) uint8
  86. page_idx_rope = safe_pages.unsqueeze(-1).expand_as(rope_offsets)
  87. rope_bytes = raw_pages[page_idx_rope, rope_offsets] # (N, 128) uint8
  88. page_idx_scale = safe_pages.unsqueeze(-1).expand_as(scale_offsets)
  89. scale_bytes = raw_pages[page_idx_scale, scale_offsets] # (N, 7) uint8
  90. # Reinterpret dtypes
  91. nope_fp8 = nope_bytes.view(torch.float8_e4m3fn) # (N, 448)
  92. rope_bf16 = rope_bytes.contiguous().view(torch.bfloat16) # (N, 64)
  93. scale_e8m0 = scale_bytes.view(torch.float8_e8m0fnu) # (N, 7)
  94. # Dequantize: nope_tile * scale_tile → bf16 (vectorized)
  95. result = torch.empty(N, _D, dtype=torch.bfloat16, device=device)
  96. result[:, :_NOPE_DIM] = (
  97. (
  98. nope_fp8.view(N, _NUM_TILES, _TILE_SIZE).float()
  99. * scale_e8m0.view(N, _NUM_TILES, 1).float()
  100. )
  101. .view(N, _NOPE_DIM)
  102. .to(torch.bfloat16)
  103. )
  104. result[:, _NOPE_DIM:] = rope_bf16
  105. return result.reshape(*idx_shape, _D)
  106. def _sm120_sparse_decode_fwd(
  107. q,
  108. k_cache,
  109. indices,
  110. topk_length,
  111. attn_sink,
  112. head_dim_v,
  113. softmax_scale,
  114. extra_k_cache=None,
  115. extra_indices=None,
  116. extra_topk_length=None,
  117. ):
  118. B, s_q, H_q, D_qk = q.shape
  119. num_pages, page_size, H_k, bpt = k_cache.shape
  120. topk = indices.shape[-1]
  121. invalid_mask = indices < 0
  122. safe_indices = indices.clamp(min=0)
  123. if topk_length is not None:
  124. topk_range = torch.arange(topk, device=topk_length.device).view(1, 1, topk)
  125. invalid_mask = invalid_mask | (topk_range >= topk_length.view(B, 1, 1))
  126. # Gather and dequantize using page-aware addressing
  127. gathered_kv = _gather_and_dequant(k_cache, safe_indices, page_size)
  128. if extra_k_cache is not None and extra_indices is not None:
  129. extra_topk = extra_indices.shape[-1]
  130. extra_page_size = extra_k_cache.shape[1]
  131. extra_invalid = extra_indices < 0
  132. extra_safe = extra_indices.clamp(min=0)
  133. if extra_topk_length is not None:
  134. extra_range = torch.arange(
  135. extra_topk, device=extra_topk_length.device
  136. ).view(1, 1, extra_topk)
  137. extra_invalid = extra_invalid | (
  138. extra_range >= extra_topk_length.view(B, 1, 1)
  139. )
  140. extra_kv = _gather_and_dequant(extra_k_cache, extra_safe, extra_page_size)
  141. gathered_kv = torch.cat([gathered_kv, extra_kv], dim=2)
  142. invalid_mask = torch.cat([invalid_mask, extra_invalid], dim=2)
  143. gathered_kv[invalid_mask] = 0.0
  144. q_f = q.float()
  145. kv_f = gathered_kv.float()
  146. kv_d = kv_f.shape[-1]
  147. if D_qk != kv_d:
  148. q_f = q_f[..., :kv_d]
  149. scores = torch.einsum("bshd,bstd->bsht", q_f, kv_f) * softmax_scale
  150. scores.masked_fill_(invalid_mask.unsqueeze(2).expand_as(scores), float("-inf"))
  151. lse = torch.logsumexp(scores, dim=-1)
  152. if attn_sink is not None:
  153. lse_for_out = torch.logsumexp(
  154. torch.stack([lse, attn_sink.view(1, 1, H_q).expand_as(lse)], dim=0), dim=0
  155. )
  156. else:
  157. lse_for_out = lse.clone()
  158. lonely = lse == float("-inf")
  159. lse_for_out[lonely] = float("inf")
  160. weights = torch.exp(scores - lse_for_out.unsqueeze(-1))
  161. out = torch.einsum("bsht,bstv->bshv", weights, kv_f[..., :head_dim_v])
  162. out[lonely.unsqueeze(-1).expand_as(out)] = 0.0
  163. return out.to(torch.bfloat16), lse.permute(0, 2, 1)
  164. # SM120 FlashMLA: default FlashInfer (CUTLASS SM120 sparse MLA decode).
  165. # Override with SGLANG_SM120_FLASHMLA_BACKEND=triton|torch to force fallback.
  166. _sm120_default_backend = envs.SGLANG_SM120_FLASHMLA_BACKEND.get()
  167. SM120_DECODE_MAX_TOKENS = 64
  168. def _flash_mla_sm120_prefill(
  169. q,
  170. k_cache,
  171. indices,
  172. topk_length,
  173. attn_sink,
  174. head_dim_v,
  175. softmax_scale,
  176. extra_k_cache,
  177. extra_indices,
  178. extra_topk_length,
  179. ):
  180. from flashinfer.mla._sparse_mla_sm120 import _sparse_mla_sm120_paged_attention
  181. q2 = q.squeeze(1) if q.ndim == 4 else q
  182. num_tokens, num_heads, _ = q2.shape
  183. dev = q2.device
  184. kv_u8 = k_cache.view(torch.uint8) if k_cache.dtype != torch.uint8 else k_cache
  185. src_pbs = k_cache.shape[1] if k_cache.ndim >= 3 else _PBS_SRC
  186. idx = indices.squeeze(1) if indices.dim() == 3 else indices
  187. kv_64 = (
  188. _split_kv_pages_to_64(kv_u8, src_pbs, touched_indices=idx)
  189. if src_pbs != _PBS_DST
  190. else kv_u8
  191. )
  192. extra_kv_u8 = (
  193. extra_k_cache.view(torch.uint8)
  194. if extra_k_cache is not None and extra_k_cache.dtype != torch.uint8
  195. else extra_k_cache
  196. )
  197. extra_idx = (
  198. extra_indices.squeeze(1)
  199. if extra_indices is not None and extra_indices.dim() == 3
  200. else extra_indices
  201. )
  202. # V4.1 ratio-1/2 sources use 256/128-token pages. The prefill
  203. # specialization needs 64-token pages for both sources, with independent
  204. # scratch buffers so converting the extra source cannot overwrite SWA.
  205. if extra_kv_u8 is not None and extra_kv_u8.shape[1] != _PBS_DST:
  206. extra_kv_u8 = _split_kv_pages_to_64(
  207. extra_kv_u8, extra_kv_u8.shape[1],
  208. touched_indices=extra_idx, buffer_tag="extra",
  209. )
  210. output = q2.new_empty((num_tokens, num_heads, head_dim_v), dtype=torch.bfloat16)
  211. out_lse = torch.empty((num_tokens, num_heads), dtype=torch.float32, device=dev)
  212. _sparse_mla_sm120_paged_attention(
  213. q2,
  214. kv_64,
  215. idx,
  216. output,
  217. out_lse,
  218. softmax_scale,
  219. topk_length=topk_length,
  220. attn_sink=attn_sink,
  221. extra_kv_cache=extra_kv_u8,
  222. extra_indices=extra_idx,
  223. extra_topk_length=extra_topk_length,
  224. mid_out=None,
  225. mid_lse=None,
  226. )
  227. return (output.unsqueeze(1), None)
  228. def flash_mla_with_kvcache_sm120(**kwargs):
  229. """SM120 FlashMLA sparse decode entry point.
  230. Dispatches to FlashInfer (default if available), Triton, or PyTorch fallback.
  231. """
  232. q = kwargs["q"]
  233. k_cache = kwargs["k_cache"]
  234. indices = kwargs["indices"]
  235. topk_length = kwargs.get("topk_length")
  236. attn_sink = kwargs.get("attn_sink")
  237. head_dim_v = kwargs["head_dim_v"]
  238. softmax_scale = kwargs.get("softmax_scale")
  239. if softmax_scale is None:
  240. softmax_scale = q.shape[-1] ** (-0.5)
  241. extra_k_cache = kwargs.get("extra_k_cache")
  242. extra_indices = kwargs.get("extra_indices_in_kvcache")
  243. extra_topk_length = kwargs.get("extra_topk_length")
  244. if _sm120_default_backend == "flashinfer":
  245. if q.shape[0] > SM120_DECODE_MAX_TOKENS:
  246. return _flash_mla_sm120_prefill(
  247. q,
  248. k_cache,
  249. indices,
  250. topk_length,
  251. attn_sink,
  252. head_dim_v,
  253. softmax_scale,
  254. extra_k_cache,
  255. extra_indices,
  256. extra_topk_length,
  257. )
  258. return _flash_mla_flashinfer(
  259. q,
  260. k_cache,
  261. indices,
  262. topk_length,
  263. attn_sink,
  264. head_dim_v,
  265. softmax_scale,
  266. extra_k_cache,
  267. extra_indices,
  268. extra_topk_length,
  269. )
  270. if _sm120_default_backend == "triton":
  271. from sglang.kernels.ops.attention.flash_mla_sm120_triton import (
  272. flash_mla_sparse_decode_triton,
  273. )
  274. out, lse = flash_mla_sparse_decode_triton(
  275. q,
  276. k_cache,
  277. indices,
  278. topk_length,
  279. attn_sink,
  280. head_dim_v,
  281. softmax_scale,
  282. extra_k_cache,
  283. extra_indices,
  284. extra_topk_length,
  285. )
  286. return (out, lse)
  287. out, lse = _sm120_sparse_decode_fwd(
  288. q,
  289. k_cache,
  290. indices,
  291. topk_length,
  292. attn_sink,
  293. head_dim_v,
  294. softmax_scale,
  295. extra_k_cache,
  296. extra_indices,
  297. extra_topk_length,
  298. )
  299. return (out, lse)
  300. # --- Page-split utilities: pbs=256 → pbs=64 ---
  301. # SGLang SWA KV cache footer layout per 256-token page:
  302. # [data: 256 * 576 bytes] [scale: 256 * 8 bytes] [padding]
  303. # FlashInfer decode_dsv4 expects per 64-token page:
  304. # [data: 64 * 576 bytes] [scale: 64 * 8 bytes] [padding to 37440]
  305. _PBS_SRC = 256 # SGLang physical page size
  306. _PBS_DST = 64 # FlashInfer page_block_size
  307. _NOPE_ROPE_STRIDE = 576 # bytes per token for nope+rope
  308. _SCALE_STRIDE = 8 # bytes per token for scale (7 + 1 pad)
  309. _BYTES_PER_DST_PAGE = (
  310. _PBS_DST * _NOPE_ROPE_STRIDE + _PBS_DST * _SCALE_STRIDE
  311. ) # 64*576 + 64*8 = 37376 + 512 = 37888
  312. # Padded to 576 alignment
  313. _BYTES_PER_DST_PAGE_PADDED = math.ceil(_BYTES_PER_DST_PAGE / 576) * 576 # 37440
  314. @triton.jit
  315. def _page_split_kernel(
  316. src_ptr,
  317. dst_ptr,
  318. N_pages,
  319. src_stride0: tl.constexpr,
  320. dst_stride0: tl.constexpr,
  321. DATA_PER_SUB: tl.constexpr, # 64 * 576 = 36864
  322. SCALE_PER_SUB: tl.constexpr, # 64 * 8 = 512
  323. SRC_SCALE_OFF: tl.constexpr, # 256 * 576 = 147456
  324. DST_SCALE_OFF: tl.constexpr, # 64 * 576 = 36864
  325. RATIO: tl.constexpr, # 4
  326. BLOCK_SIZE: tl.constexpr,
  327. mask_ptr,
  328. HAS_MASK: tl.constexpr,
  329. ):
  330. """Fused page-split: copy data+scale for all sub-pages in one kernel.
  331. When HAS_MASK is set, only pages flagged in ``mask_ptr`` (int8, 1=touched)
  332. are copied; untouched pages are skipped so the kernel no longer rewrites the
  333. entire KV pool every decode step.
  334. """
  335. pid = tl.program_id(0)
  336. page_idx = pid // RATIO
  337. sub = pid % RATIO
  338. if page_idx >= N_pages:
  339. return
  340. if HAS_MASK:
  341. if tl.load(mask_ptr + page_idx) == 0:
  342. return
  343. # All layout strides/offsets are 8-byte aligned (asserted at the call
  344. # site), so copy in u64 lanes instead of single bytes.
  345. src_u64 = src_ptr.to(tl.pointer_type(tl.uint64))
  346. dst_u64 = dst_ptr.to(tl.pointer_type(tl.uint64))
  347. src_base = src_u64 + page_idx * (src_stride0 // 8)
  348. dst_base = dst_u64 + (page_idx * RATIO + sub) * (dst_stride0 // 8)
  349. DATA_U64: tl.constexpr = DATA_PER_SUB // 8
  350. SCALE_U64: tl.constexpr = SCALE_PER_SUB // 8
  351. # Copy data region: DATA_U64 u64 lanes from src offset sub*DATA_U64
  352. data_src_off = sub * DATA_U64
  353. for start in tl.range(0, DATA_U64, BLOCK_SIZE):
  354. offs = start + tl.arange(0, BLOCK_SIZE)
  355. mask = offs < DATA_U64
  356. vals = tl.load(src_base + data_src_off + offs, mask=mask)
  357. tl.store(dst_base + offs, vals, mask=mask)
  358. # Copy scale region: SCALE_U64 u64 lanes
  359. scale_src_off = SRC_SCALE_OFF // 8 + sub * SCALE_U64
  360. for start in tl.range(0, SCALE_U64, BLOCK_SIZE):
  361. offs = start + tl.arange(0, BLOCK_SIZE)
  362. mask = offs < SCALE_U64
  363. vals = tl.load(src_base + scale_src_off + offs, mask=mask)
  364. tl.store(dst_base + DST_SCALE_OFF // 8 + offs, vals, mask=mask)
  365. @triton.jit
  366. def _page_mark_kernel(
  367. indices_ptr,
  368. mask_ptr,
  369. N_idx,
  370. SRC_PBS: tl.constexpr,
  371. BLOCK: tl.constexpr,
  372. ):
  373. """Mark touched source pages (1 byte each) from token-level indices.
  374. ``indices`` are token indices into the pbs=SRC_PBS SWA pool; -1 = invalid.
  375. Each valid token marks ``mask[token // SRC_PBS] = 1``. Concurrent stores of
  376. the same value 1 are safe (no atomic needed).
  377. """
  378. pid = tl.program_id(0)
  379. offs = pid * BLOCK + tl.arange(0, BLOCK)
  380. valid = offs < N_idx
  381. idx = tl.load(indices_ptr + offs, mask=valid, other=-1)
  382. keep = valid & (idx >= 0)
  383. page = idx // SRC_PBS
  384. tl.store(mask_ptr + page, tl.full((BLOCK,), 1, tl.int8), mask=keep)
  385. def _split_kv_pages_to_64(
  386. kv_u8: torch.Tensor,
  387. src_pbs: int,
  388. touched_indices: Optional[torch.Tensor] = None,
  389. buffer_tag: str = "primary",
  390. ) -> torch.Tensor:
  391. """Split pbs=N footer-format pages into pbs=64 footer-format pages.
  392. When ``touched_indices`` (token-level int32 indices into the pbs=src_pbs
  393. SWA pool, -1 = invalid) is provided, only the source pages that actually
  394. contain a referenced token are copied. This avoids rewriting the entire KV
  395. pool on every decode step (only ~2*batch pages are touched vs the full
  396. pool). The output buffer is persistent and reused across steps; untouched
  397. dst pages simply retain their (unreferenced) stale data.
  398. """
  399. assert src_pbs % _PBS_DST == 0 and src_pbs >= _PBS_DST
  400. if src_pbs == _PBS_DST:
  401. return kv_u8
  402. N = kv_u8.shape[0]
  403. ratio = src_pbs // _PBS_DST
  404. num_dst_pages = N * ratio
  405. from sglang.srt.runtime_context import get_resources
  406. # Pre-allocated grow-only buffer for page-split output per device.
  407. dev = kv_u8.device
  408. buffers = get_resources().buffers
  409. key = f"flash_mla_sm120_split:{dev}:{buffer_tag}"
  410. buf = buffers.get(key)
  411. if buf is None or buf.shape[0] < num_dst_pages:
  412. # The first allocation can happen under inference mode (autotune), but
  413. # the buffer is written again during CUDA graph capture outside
  414. # inference mode, where an inference tensor cannot be mutated.
  415. with torch.inference_mode(False):
  416. buf = torch.empty(
  417. num_dst_pages,
  418. _BYTES_PER_DST_PAGE_PADDED,
  419. dtype=torch.uint8,
  420. device=dev,
  421. )
  422. buffers[key] = buf
  423. out = buf[:num_dst_pages]
  424. # Get raw 2D view of source
  425. src_2d = kv_u8
  426. if src_2d.ndim == 4:
  427. src_stride0 = src_2d.stride(0)
  428. src_2d = torch.as_strided(src_2d, (N, src_stride0), (src_stride0, 1))
  429. else:
  430. src_stride0 = src_2d.stride(0)
  431. use_mask = touched_indices is not None and touched_indices.numel() > 0
  432. mask_ptr = src_2d # dummy, never dereferenced when HAS_MASK is False
  433. if use_mask:
  434. # Persistent per-device int8 mask, zeroed each call (cheap memset,
  435. # captured cleanly by CUDA graph). 1 = page is referenced this step.
  436. mkey = f"flash_mla_sm120_mask:{dev}:{buffer_tag}"
  437. mbuf = buffers.get(mkey)
  438. if mbuf is None or mbuf.shape[0] < N:
  439. # The first allocation can happen under inference mode (autotune),
  440. # but the buffer is zeroed again later during CUDA graph capture
  441. # outside inference mode -- an inference tensor cannot be mutated
  442. # there, so force a normal tensor.
  443. with torch.inference_mode(False):
  444. mbuf = torch.empty(N, dtype=torch.int8, device=dev)
  445. buffers[mkey] = mbuf
  446. mask = mbuf[:N]
  447. mask.zero_()
  448. idx_flat = touched_indices.reshape(-1).contiguous()
  449. if idx_flat.dtype != torch.int32:
  450. idx_flat = idx_flat.to(torch.int32)
  451. _MARK_BLOCK = 1024
  452. _page_mark_kernel[(triton.cdiv(idx_flat.numel(), _MARK_BLOCK),)](
  453. idx_flat,
  454. mask,
  455. idx_flat.numel(),
  456. src_pbs, # SRC_PBS
  457. _MARK_BLOCK,
  458. )
  459. mask_ptr = mask
  460. assert src_stride0 % 8 == 0 and _BYTES_PER_DST_PAGE_PADDED % 8 == 0
  461. grid = (N * ratio,)
  462. _page_split_kernel[grid](
  463. src_2d,
  464. out,
  465. N,
  466. src_stride0,
  467. _BYTES_PER_DST_PAGE_PADDED,
  468. _PBS_DST * _NOPE_ROPE_STRIDE, # DATA_PER_SUB = 36864
  469. _PBS_DST * _SCALE_STRIDE, # SCALE_PER_SUB = 512
  470. src_pbs * _NOPE_ROPE_STRIDE, # SRC_SCALE_OFF = 147456
  471. _PBS_DST * _NOPE_ROPE_STRIDE, # DST_SCALE_OFF = 36864
  472. ratio, # RATIO = 4
  473. 1024, # BLOCK_SIZE
  474. mask_ptr,
  475. use_mask, # HAS_MASK
  476. )
  477. bpt = _NOPE_ROPE_STRIDE + _SCALE_STRIDE # 584
  478. return out.as_strided(
  479. (num_dst_pages, _PBS_DST, 1, bpt),
  480. (_BYTES_PER_DST_PAGE_PADDED, bpt, bpt, 1),
  481. )
  482. def _flash_mla_flashinfer(
  483. q,
  484. k_cache,
  485. indices,
  486. topk_length,
  487. attn_sink,
  488. head_dim_v,
  489. softmax_scale,
  490. extra_k_cache,
  491. extra_indices,
  492. extra_topk_length,
  493. ):
  494. """FlashInfer SM120 sparse MLA via the paged-attention dispatcher.
  495. SGLang SWA pool uses page_size=256 (footer format: 256*576 bytes data + 256*8 bytes scale).
  496. FlashInfer decode_dsv4 fast path requires page_block_size=64 (footer: 64*576 + 64*8).
  497. We split 256-token pages into 4 virtual 64-token pages.
  498. Token indices are invariant under page-split (identity mapping).
  499. """
  500. from flashinfer.mla._sparse_mla_sm120 import (
  501. _DECODE_MAX_TOKENS as _FI_DECODE_MAX_TOKENS,
  502. )
  503. from flashinfer.mla._sparse_mla_sm120 import (
  504. _sparse_mla_sm120_paged_attention,
  505. )
  506. B, _, H, D = q.shape # (batch, 1, num_heads, head_dim)
  507. dev = q.device
  508. # Indices: no remapping needed (page-split preserves token addressing).
  509. idx = indices.squeeze(1) if indices.dim() == 3 else indices
  510. # --- Page-split: convert pbs=N kv_cache to pbs=64 view ---
  511. # Only the SWA pages actually referenced by `idx` are copied (the rest of
  512. # the persistent dst buffer is left untouched and never read).
  513. kv_u8 = k_cache.view(torch.uint8) if k_cache.dtype != torch.uint8 else k_cache
  514. src_pbs = k_cache.shape[1] if k_cache.ndim >= 3 else _PBS_SRC
  515. kv_64 = (
  516. _split_kv_pages_to_64(kv_u8, src_pbs, touched_indices=idx)
  517. if src_pbs != _PBS_DST
  518. else kv_u8
  519. )
  520. extra_kv_u8 = (
  521. extra_k_cache.view(torch.uint8)
  522. if extra_k_cache is not None and extra_k_cache.dtype != torch.uint8
  523. else extra_k_cache
  524. )
  525. extra_kv_64 = extra_kv_u8
  526. extra_idx = (
  527. extra_indices.squeeze(1)
  528. if extra_indices is not None and extra_indices.dim() == 3
  529. else extra_indices
  530. )
  531. output = torch.empty(B, H, head_dim_v, dtype=torch.bfloat16, device=dev)
  532. out_lse = torch.empty(B, H, dtype=torch.float32, device=dev)
  533. # Use split-K for decode-sized batches and paged attention otherwise.
  534. if B <= _FI_DECODE_MAX_TOKENS:
  535. topk = idx.shape[-1]
  536. extra_topk = extra_idx.shape[-1] if extra_idx is not None else 0
  537. _BI = 64
  538. num_splits = (topk + _BI - 1) // _BI + (
  539. (extra_topk + _BI - 1) // _BI if extra_topk > 0 else 0
  540. )
  541. mid_out = torch.empty(
  542. B, H, num_splits, head_dim_v, dtype=torch.bfloat16, device=dev
  543. )
  544. mid_lse = torch.empty(B, H, num_splits, dtype=torch.float32, device=dev)
  545. else:
  546. mid_out = None
  547. mid_lse = None
  548. _sparse_mla_sm120_paged_attention(
  549. q.squeeze(1) if q.ndim == 4 else q,
  550. kv_64,
  551. idx,
  552. output,
  553. out_lse,
  554. softmax_scale,
  555. d_v=head_dim_v,
  556. topk_length=topk_length,
  557. attn_sink=attn_sink,
  558. extra_kv_cache=extra_kv_64,
  559. extra_indices=extra_idx,
  560. extra_topk_length=extra_topk_length,
  561. mid_out=mid_out,
  562. mid_lse=mid_lse,
  563. )
  564. return (output.unsqueeze(1), None)
  565. def _validate_flashinfer_sparse_mla_backend(
  566. *,
  567. model_arch: str,
  568. device_sm_major: int,
  569. kv_cache_dtype: torch.dtype,
  570. prefill_impl: str,
  571. decode_impl: str,
  572. ) -> bool:
  573. selected = {prefill_impl, decode_impl}
  574. uses_flashinfer_sparse_mla = "flashinfer_sparse_mla" in selected
  575. is_glm_sm12_fp8 = (
  576. model_arch in _GLM_DSA_MODEL_ARCHS
  577. and device_sm_major == 12
  578. and kv_cache_dtype == torch.float8_e4m3fn
  579. and not _is_hip
  580. )
  581. if uses_flashinfer_sparse_mla and not is_glm_sm12_fp8:
  582. raise ValueError(
  583. "flashinfer_sparse_mla supports only GLM DSA with FP8 KV cache "
  584. "on NVIDIA SM120/SM121; "
  585. f"got model_arch={model_arch!r}, sm_major={device_sm_major}, "
  586. f"kv_cache_dtype={kv_cache_dtype}, prefill_impl={prefill_impl!r}, "
  587. f"decode_impl={decode_impl!r}."
  588. )
  589. if is_glm_sm12_fp8:
  590. unsupported = selected - {"flashinfer_sparse_mla"}
  591. if unsupported:
  592. raise ValueError(
  593. "GLM DSA with FP8 KV cache on NVIDIA SM120/SM121 supports "
  594. "only flashinfer_sparse_mla, "
  595. f"but got {sorted(unsupported)}."
  596. )
  597. return uses_flashinfer_sparse_mla
  598. def flashinfer_sparse_mla_forward(
  599. q: torch.Tensor,
  600. kv_cache: torch.Tensor,
  601. indices: torch.Tensor,
  602. seq_lens: torch.Tensor,
  603. workspace_buffer: torch.Tensor,
  604. *,
  605. page_size: int,
  606. kv_cache_dim: int,
  607. qk_nope_head_dim: int,
  608. kv_lora_rank: int,
  609. qk_rope_head_dim: int,
  610. sm_scale: float,
  611. skip_softmax_threshold_scale_factor: float | None,
  612. ) -> torch.Tensor:
  613. """Run FlashInfer's SM120 sparse MLA kernel on SGLang's packed DSA cache."""
  614. from flashinfer.mla import trtllm_batch_decode_with_kv_cache_mla
  615. topk = indices.shape[1]
  616. result = trtllm_batch_decode_with_kv_cache_mla(
  617. query=q.unsqueeze(1),
  618. kv_cache=kv_cache.view(torch.uint8)
  619. .view(-1, page_size, kv_cache_dim)
  620. .unsqueeze(1),
  621. workspace_buffer=workspace_buffer,
  622. qk_nope_head_dim=qk_nope_head_dim,
  623. kv_lora_rank=kv_lora_rank,
  624. qk_rope_head_dim=qk_rope_head_dim,
  625. block_tables=indices.unsqueeze(1),
  626. seq_lens=seq_lens,
  627. max_seq_len=topk,
  628. sparse_mla_top_k=topk,
  629. bmm1_scale=float(sm_scale),
  630. bmm2_scale=1.0,
  631. kv_scale_format="arbitrary_fp32",
  632. skip_softmax_threshold_scale_factor=skip_softmax_threshold_scale_factor,
  633. )
  634. return result.squeeze(1)