nf3_kernel.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """
  2. NF3 codebook MoE GEMM (Triton) — reads the checkpoint's NATIVE 8->3-byte packing directly
  3. (no repack). W[e,n,k] = NF3_codebook[code] * blockscale[e,n,k//GS]. Packing (matches build's
  4. quant_nvfp3): 8 codes -> 24-bit word -> 3 uint8 bytes; code k = (word >> 3*(k%8)) & 7.
  5. Kernel = sorted-token grouped GEMM (reuses vLLM moe_align_block_size) with inline unpack+dequant.
  6. Importable: nf3_moe_layer(hidden, w13_packed, s13, w2_packed, s2, topk_ids, topk_w, emap, ...).
  7. """
  8. import torch, triton, triton.language as tl
  9. _NF3 = [-1.0, -0.6047, -0.3563, -0.1275, 0.1275, 0.3563, 0.6047, 1.0]
  10. @triton.jit
  11. def _lut(code):
  12. return tl.where(code == 0, -1.0,
  13. tl.where(code == 1, -0.6047,
  14. tl.where(code == 2, -0.3563,
  15. tl.where(code == 3, -0.1275,
  16. tl.where(code == 4, 0.1275,
  17. tl.where(code == 5, 0.3563,
  18. tl.where(code == 6, 0.6047, 1.0)))))))
  19. @triton.jit
  20. def nf3_moe_kernel(
  21. a_ptr, b_ptr, s_ptr, c_ptr, tw_ptr,
  22. sorted_ids_ptr, expert_ids_ptr, ntpp_ptr,
  23. N, K, EM, num_valid_tokens,
  24. stride_am, stride_ak,
  25. stride_be, stride_bn, stride_bk, # packed weight [E, N, K//8*3]
  26. stride_se, stride_sn, stride_sk, # block scale [E, N, K//GS]
  27. stride_cm, stride_cn,
  28. top_k: tl.constexpr, MUL_W: tl.constexpr, GS: tl.constexpr,
  29. BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
  30. compute_type: tl.constexpr,
  31. ):
  32. pid = tl.program_id(0)
  33. num_pid_n = tl.cdiv(N, BLOCK_N)
  34. pid_m = pid // num_pid_n
  35. pid_n = pid % num_pid_n
  36. if pid_m * BLOCK_M >= tl.load(ntpp_ptr):
  37. return
  38. offs_tid = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
  39. offs_token = tl.load(sorted_ids_ptr + offs_tid)
  40. tok_mask = offs_token < num_valid_tokens
  41. e = tl.load(expert_ids_ptr + pid_m)
  42. if e == -1:
  43. return
  44. offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
  45. offs_k = tl.arange(0, BLOCK_K)
  46. a_ptrs = a_ptr + (offs_token[:, None] // top_k * stride_am + offs_k[None, :] * stride_ak)
  47. acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
  48. for kb in range(0, tl.cdiv(K, BLOCK_K)):
  49. kk = kb * BLOCK_K + offs_k # [BLOCK_K]
  50. kmask = kk < K
  51. a = tl.load(a_ptrs, mask=tok_mask[:, None] & (kk[None, :] < K), other=0.0)
  52. # native 8->3 packing: word = 3 bytes at group g=k//8; code = (word >> 3*(k%8)) & 7
  53. g = (kk[:, None] // 8)
  54. sh = (3 * (kk[:, None] % 8)).to(tl.int32)
  55. wb = e * stride_be + offs_bn[None, :] * stride_bn + (g * 3) * stride_bk
  56. b0 = tl.load(b_ptr + wb + 0 * stride_bk).to(tl.int32)
  57. b1 = tl.load(b_ptr + wb + 1 * stride_bk).to(tl.int32)
  58. b2 = tl.load(b_ptr + wb + 2 * stride_bk).to(tl.int32)
  59. word = b0 | (b1 << 8) | (b2 << 16)
  60. code = (word >> sh) & 7 # [BLOCK_K, BLOCK_N]
  61. v = _lut(code)
  62. sb = e * stride_se + offs_bn[None, :] * stride_sn + (kk[:, None] // GS) * stride_sk
  63. s = tl.load(s_ptr + sb, mask=kmask[:, None], other=0.0).to(tl.float32)
  64. b = (v * s).to(compute_type)
  65. acc = tl.dot(a, b, acc=acc)
  66. a_ptrs += BLOCK_K * stride_ak
  67. if MUL_W:
  68. w = tl.load(tw_ptr + offs_token, mask=tok_mask, other=0.0)
  69. acc = acc * w[:, None]
  70. c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_bn[None, :]
  71. tl.store(c_ptrs, acc.to(compute_type), mask=tok_mask[:, None])
  72. def pack_nf3_837(codes):
  73. """codes [E,N,K] (0..7) -> packed uint8 [E,N,K//8*3] (build's 8->24bit->3byte layout)."""
  74. E, N, K = codes.shape
  75. c = codes.to(torch.int32).reshape(E, N, K // 8, 8)
  76. word = torch.zeros(E, N, K // 8, dtype=torch.int32, device=codes.device)
  77. for i in range(8):
  78. word |= (c[..., i] & 7) << (3 * i)
  79. b = torch.stack([(word & 0xFF), ((word >> 8) & 0xFF), ((word >> 16) & 0xFF)], -1)
  80. return b.to(torch.uint8).reshape(E, N, K // 8 * 3)
  81. def ref_dequant(codes, scale, GS):
  82. nf = torch.tensor(_NF3, device=codes.device)
  83. return nf[codes.long()] * scale.repeat_interleave(GS, dim=2).float()
  84. def run_kernel(a, b, s, sorted_ids, expert_ids, ntpp, N, K, top_k, GS,
  85. mul_w=False, tw=None, out=None, BM=16, BN=64, BK=32, warps=4):
  86. M = a.shape[0]
  87. c = out if out is not None else torch.zeros(M * top_k, N, dtype=torch.bfloat16, device=a.device)
  88. grid = (triton.cdiv(sorted_ids.shape[0], BM) * triton.cdiv(N, BN),)
  89. nf3_moe_kernel[grid](
  90. a, b, s, c, tw if tw is not None else a,
  91. sorted_ids, expert_ids, ntpp,
  92. N, K, sorted_ids.shape[0], M * top_k,
  93. a.stride(0), a.stride(1),
  94. b.stride(0), b.stride(1), b.stride(2),
  95. s.stride(0), s.stride(1), s.stride(2),
  96. c.stride(0), c.stride(1),
  97. top_k=top_k, MUL_W=mul_w, GS=GS, BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK,
  98. compute_type=tl.bfloat16, num_warps=warps,
  99. )
  100. return c
  101. def nf3_moe_layer(hidden, w13_packed, s13, w2_packed, s2, topk_ids, topk_w, emap,
  102. Etot, GS, H, I, BM=16):
  103. """Full NF3 MoE layer -> [M, H]. w13_packed [Km,2I,H//8*3], w2_packed [Km,H,I//8*3]."""
  104. import torch.nn.functional as F
  105. from vllm.model_executor.layers.fused_moe.moe_align_block_size import moe_align_block_size
  106. M, tk = topk_ids.shape
  107. sids, eids, ntpp = moe_align_block_size(topk_ids.to(torch.int32), BM, Etot, expert_map=emap)
  108. sids, eids, ntpp = sids.to(torch.int32), eids.to(torch.int32), ntpp.to(torch.int32)
  109. c1 = run_kernel(hidden, w13_packed, s13, sids, eids, ntpp, 2 * I, H, tk, GS) # [M*tk, 2I]
  110. g, u = c1.chunk(2, -1)
  111. c2 = (F.silu(g.float()) * u.float()).to(torch.bfloat16) # [M*tk, I]
  112. c3 = run_kernel(c2, w2_packed, s2, sids, eids, ntpp, H, I, 1, GS,
  113. mul_w=True, tw=topk_w.reshape(-1).float()) # [M*tk, H]
  114. return c3.view(M, tk, H).sum(1)
  115. if __name__ == "__main__":
  116. import time
  117. torch.manual_seed(0); dev = "cuda"
  118. from vllm.model_executor.layers.fused_moe.moe_align_block_size import moe_align_block_size
  119. GS = 32
  120. # TEST1: single expert
  121. E, N, K = 1, 128, 256
  122. codes = torch.randint(0, 8, (E, N, K), device=dev)
  123. scale = (torch.rand(E, N, K // GS, device=dev) * 0.05 + 0.01).to(torch.float8_e4m3fn)
  124. B = pack_nf3_837(codes)
  125. a = torch.randn(16, K, device=dev, dtype=torch.bfloat16)
  126. W = ref_dequant(codes, scale, GS)[0]
  127. ref = (a.float() @ W.t()).to(torch.bfloat16)
  128. sids = torch.full((16,), 16, dtype=torch.int32, device=dev); sids[:16] = torch.arange(16, device=dev)
  129. eids = torch.zeros(1, dtype=torch.int32, device=dev); ntpp = torch.tensor([16], dtype=torch.int32, device=dev)
  130. out = run_kernel(a, B, scale, sids, eids, ntpp, N, K, 1, GS)[:16]
  131. r1 = (out.float() - ref.float()).abs().mean() / ref.float().abs().mean()
  132. print(f"TEST1 single-expert: rel={r1:.5f} {'PASS' if r1 < 0.02 else 'FAIL'}")
  133. # TEST4: full MoE layer vs reference (the integration unit)
  134. import torch.nn.functional as F
  135. Etot, Kept = 256, 64; Km = Etot - Kept
  136. emap = torch.full((Etot,), -1, dtype=torch.int32, device=dev); emap[Kept:] = torch.arange(Km, dtype=torch.int32, device=dev)
  137. Hs, Is, tk = 512, 128, 8
  138. c13 = torch.randint(0, 8, (Km, 2 * Is, Hs), device=dev); sc13 = (torch.rand(Km, 2 * Is, Hs // GS, device=dev) * 0.05 + 0.02).to(torch.float8_e4m3fn)
  139. c2t = torch.randint(0, 8, (Km, Hs, Is), device=dev); sc2 = (torch.rand(Km, Hs, Is // GS, device=dev) * 0.05 + 0.02).to(torch.float8_e4m3fn)
  140. B13, B2 = pack_nf3_837(c13), pack_nf3_837(c2t)
  141. Mt = 8
  142. hid = torch.randn(Mt, Hs, device=dev, dtype=torch.bfloat16) * 0.5
  143. tids = torch.randint(0, Etot, (Mt, tk), device=dev, dtype=torch.int32)
  144. tw = torch.softmax(torch.rand(Mt, tk, device=dev), -1)
  145. out4 = nf3_moe_layer(hid, B13, sc13, B2, sc2, tids, tw, emap, Etot, GS, Hs, Is)
  146. W13, W2 = ref_dequant(c13, sc13, GS), ref_dequant(c2t, sc2, GS)
  147. ref4 = torch.zeros(Mt, Hs, device=dev)
  148. for m in range(Mt):
  149. for slot in range(tk):
  150. gg = int(tids[m, slot])
  151. if gg < Kept: continue
  152. l = gg - Kept
  153. inter = hid[m].float() @ W13[l].t(); a_, b_ = inter.chunk(2, -1)
  154. ref4[m] += tw[m, slot].float() * ((F.silu(a_) * b_) @ W2[l].t())
  155. r4 = (out4.float() - ref4).abs().sum() / (ref4.abs().sum() + 1e-6)
  156. print(f"TEST4 full-MoE-layer: rel={r4:.5f} {'PASS' if r4 < 0.03 else 'FAIL'}")
  157. # TEST3: speed sweep (per-rank GLM shapes)
  158. H, I = 6144, 512
  159. c13b = torch.randint(0, 8, (Km, 2 * I, H), device=dev); s13b = (torch.rand(Km, 2 * I, H // GS, device=dev) * 0.03 + 0.01).to(torch.float8_e4m3fn)
  160. c2b = torch.randint(0, 8, (Km, H, I), device=dev); s2b = (torch.rand(Km, H, I // GS, device=dev) * 0.03 + 0.01).to(torch.float8_e4m3fn)
  161. B13b, B2b = pack_nf3_837(c13b), pack_nf3_837(c2b)
  162. for Mv in [1, 4, 8]:
  163. hv = torch.randn(Mv, H, device=dev, dtype=torch.bfloat16)
  164. tidv = torch.randint(Kept, Etot, (Mv, tk), device=dev, dtype=torch.int32)
  165. twv = torch.rand(Mv, tk, device=dev)
  166. for _ in range(3): nf3_moe_layer(hv, B13b, s13b, B2b, s2b, tidv, twv, emap, Etot, GS, H, I)
  167. torch.cuda.synchronize(); e0, e1 = torch.cuda.Event(True), torch.cuda.Event(True)
  168. e0.record()
  169. for _ in range(20 * 75): nf3_moe_layer(hv, B13b, s13b, B2b, s2b, tidv, twv, emap, Etot, GS, H, I)
  170. e1.record(); torch.cuda.synchronize()
  171. dt = e0.elapsed_time(e1) / 1000 / 20 / Mv
  172. print(f"TEST3 M={Mv}: {dt*1000:.2f} ms/tok -> {1/dt:.1f} tok/s (NF3 full-layer incl align, 75L, 1 GPU)")