gb10_sparse_mla.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """Sparse NoPE MLA attention for GB10 in Triton: each query token attends to
  2. its own top-k KV rows (valid prefix of `idx`, -1 past it).
  3. One program per query token: all heads' rows of Q stay in registers, and the
  4. selected KV rows stream through in blocks, gathered by index. K and V are the
  5. same latent row (NoPE MLA), FP8 E4M3 with one tensor-wide scale.
  6. Small batches (decode: 1 + drafts tokens per request) leave most of the 48 SMs
  7. idle with one program per token, so they split each token's rows across
  8. SPLITS programs, which write partial softmax state that a second kernel
  9. combines (flash-decoding).
  10. """
  11. import torch
  12. import triton
  13. import triton.language as tl
  14. @triton.jit
  15. def _sparse_mla_split(q_ptr, kv_ptr, idx_ptr, len_ptr, acc_ptr, ml_ptr, qk_scale, sq_t, sq_h,
  16. H: tl.constexpr, D: tl.constexpr, TOPK: tl.constexpr, CHUNK: tl.constexpr,
  17. SPLITS: tl.constexpr, BLOCK_N: tl.constexpr, HP: tl.constexpr):
  18. t = tl.program_id(0)
  19. sp = tl.program_id(1)
  20. heads = tl.arange(0, HP)
  21. hm = heads < H
  22. dims = tl.arange(0, D)
  23. q = tl.load(q_ptr + t * sq_t + heads[:, None] * sq_h + dims[None, :], mask=hm[:, None], other=0.0).to(tl.float16)
  24. n_valid = tl.load(len_ptr + t)
  25. m_i = tl.full([HP], float("-inf"), tl.float32)
  26. l_i = tl.zeros([HP], tl.float32)
  27. acc = tl.zeros([HP, D], tl.float32)
  28. for start in range(sp * CHUNK, sp * CHUNK + CHUNK, BLOCK_N):
  29. offs = start + tl.arange(0, BLOCK_N)
  30. live = offs < n_valid
  31. rows = tl.load(idx_ptr + t * TOPK + offs, mask=live, other=0)
  32. kv = tl.load(kv_ptr + rows[:, None].to(tl.int64) * D + dims[None, :], mask=live[:, None], other=0.0)
  33. kv = kv.to(tl.float16)
  34. s = tl.dot(q, tl.trans(kv)) * qk_scale
  35. s = tl.where(live[None, :], s, float("-inf"))
  36. m_new = tl.maximum(m_i, tl.max(s, 1))
  37. alpha = tl.where(m_new == float("-inf"), 1.0, tl.exp2(m_i - m_new))
  38. p = tl.where(m_new[:, None] == float("-inf"), 0.0, tl.exp2(s - m_new[:, None]))
  39. l_i = l_i * alpha + tl.sum(p, 1)
  40. acc = acc * alpha[:, None] + tl.dot(p.to(tl.float16), kv)
  41. m_i = m_new
  42. base = (t * SPLITS + sp) * H
  43. tl.store(acc_ptr + (base + heads[:, None]) * D + dims[None, :], acc, mask=hm[:, None])
  44. tl.store(ml_ptr + (base + heads) * 2, m_i, mask=hm)
  45. tl.store(ml_ptr + (base + heads) * 2 + 1, l_i, mask=hm)
  46. @triton.jit
  47. def _sparse_mla_combine(acc_ptr, ml_ptr, o_ptr, kv_scale, H: tl.constexpr, D: tl.constexpr, SPLITS: tl.constexpr):
  48. t = tl.program_id(0)
  49. h = tl.program_id(1)
  50. dims = tl.arange(0, D)
  51. sps = tl.arange(0, SPLITS)
  52. base = (t * SPLITS + sps) * H + h
  53. m = tl.load(ml_ptr + base * 2)
  54. l = tl.load(ml_ptr + base * 2 + 1)
  55. m_max = tl.max(m, 0)
  56. w = tl.where(m == float("-inf"), 0.0, tl.exp2(m - m_max))
  57. l_sum = tl.sum(l * w, 0)
  58. acc = tl.load(acc_ptr + base[:, None] * D + dims[None, :])
  59. out = tl.sum(acc * w[:, None], 0)
  60. out = tl.where(l_sum > 0, out / l_sum, 0.0) * kv_scale
  61. tl.store(o_ptr + (t * H + h) * D + dims, out.to(o_ptr.dtype.element_ty))
  62. @triton.jit
  63. def _sparse_mla_fwd(q_ptr, kv_ptr, idx_ptr, len_ptr, o_ptr, qk_scale, kv_scale, sq_t, sq_h,
  64. H: tl.constexpr, D: tl.constexpr, TOPK: tl.constexpr, BLOCK_N: tl.constexpr, HP: tl.constexpr):
  65. t = tl.program_id(0)
  66. heads = tl.arange(0, HP)
  67. hm = heads < H
  68. dims = tl.arange(0, D)
  69. q = tl.load(q_ptr + t * sq_t + heads[:, None] * sq_h + dims[None, :], mask=hm[:, None], other=0.0).to(tl.float16)
  70. n_valid = tl.load(len_ptr + t)
  71. m_i = tl.full([HP], float("-inf"), tl.float32)
  72. l_i = tl.zeros([HP], tl.float32)
  73. acc = tl.zeros([HP, D], tl.float32)
  74. for start in range(0, TOPK, BLOCK_N):
  75. offs = start + tl.arange(0, BLOCK_N)
  76. live = offs < n_valid
  77. rows = tl.load(idx_ptr + t * TOPK + offs, mask=live, other=0)
  78. kv = tl.load(kv_ptr + rows[:, None].to(tl.int64) * D + dims[None, :], mask=live[:, None], other=0.0)
  79. kv = kv.to(tl.float16)
  80. s = tl.dot(q, tl.trans(kv)) * qk_scale # [H, BLOCK_N], log2 domain
  81. s = tl.where(live[None, :], s, float("-inf"))
  82. m_new = tl.maximum(m_i, tl.max(s, 1))
  83. alpha = tl.exp2(m_i - m_new)
  84. p = tl.exp2(s - m_new[:, None])
  85. l_i = l_i * alpha + tl.sum(p, 1)
  86. acc = acc * alpha[:, None] + tl.dot(p.to(tl.float16), kv)
  87. m_i = m_new
  88. out = tl.where(l_i[:, None] > 0, acc / l_i[:, None], 0.0) * kv_scale # a query with no KV rows gives 0
  89. tl.store(o_ptr + t * H * D + heads[:, None] * D + dims[None, :], out.to(o_ptr.dtype.element_ty), mask=hm[:, None])
  90. # Programs a split launch aims for: two per SM on GB10's 48.
  91. _SPLIT_TARGET = 96
  92. def sparse_mla(q, kv, idx, lens, sm_scale, kv_scale=1.0, block_n=32, num_warps=4, num_stages=2, splits=None):
  93. """q [T, H, D] bf16 (last dim contiguous), kv [S, D] fp8 e4m3, idx [T, TOPK]
  94. int32 global rows with the valid ones first, lens [T] int32 valid counts.
  95. Returns [T, H, D] bf16. The 1.44 folds exp into exp2."""
  96. T, H, D = q.shape
  97. # TP=3: 22 or 24 heads/rank. Tile at the next power of two (>=16 for tl.dot), masked.
  98. hp = max(16, triton.next_power_of_2(H))
  99. assert q.stride(2) == 1 and kv.is_contiguous() and idx.stride(1) == 1
  100. out = torch.empty(T, H, D, dtype=q.dtype, device=q.device)
  101. qk_scale = sm_scale * kv_scale * 1.4426950408889634
  102. topk = idx.shape[1]
  103. if splits is None:
  104. splits = 1
  105. while T * splits < _SPLIT_TARGET and splits < 16 and topk % (splits * 2 * block_n) == 0:
  106. splits *= 2
  107. if splits > 1:
  108. acc = torch.empty(T, splits, H, D, dtype=torch.float32, device=q.device)
  109. ml = torch.empty(T, splits, H, 2, dtype=torch.float32, device=q.device)
  110. _sparse_mla_split[(T, splits)](q, kv, idx, lens, acc, ml, qk_scale, q.stride(0), q.stride(1),
  111. H=H, D=D, TOPK=topk, CHUNK=topk // splits, SPLITS=splits,
  112. BLOCK_N=block_n, HP=hp, num_warps=num_warps, num_stages=num_stages)
  113. _sparse_mla_combine[(T, H)](acc, ml, out, kv_scale, H=H, D=D, SPLITS=splits, num_warps=4)
  114. return out
  115. _sparse_mla_fwd[(T,)](q, kv, idx, lens, out, qk_scale, kv_scale, q.stride(0), q.stride(1),
  116. H=H, D=D, TOPK=idx.shape[1],
  117. BLOCK_N=block_n, HP=hp, num_warps=num_warps, num_stages=num_stages)
  118. return out