| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- """
- NF3 codebook MoE GEMM (Triton) — reads the checkpoint's NATIVE 8->3-byte packing directly
- (no repack). W[e,n,k] = NF3_codebook[code] * blockscale[e,n,k//GS]. Packing (matches build's
- quant_nvfp3): 8 codes -> 24-bit word -> 3 uint8 bytes; code k = (word >> 3*(k%8)) & 7.
- Kernel = sorted-token grouped GEMM (reuses vLLM moe_align_block_size) with inline unpack+dequant.
- Importable: nf3_moe_layer(hidden, w13_packed, s13, w2_packed, s2, topk_ids, topk_w, emap, ...).
- """
- import torch, triton, triton.language as tl
- _NF3 = [-1.0, -0.6047, -0.3563, -0.1275, 0.1275, 0.3563, 0.6047, 1.0]
- @triton.jit
- def _lut(code):
- return tl.where(code == 0, -1.0,
- tl.where(code == 1, -0.6047,
- tl.where(code == 2, -0.3563,
- tl.where(code == 3, -0.1275,
- tl.where(code == 4, 0.1275,
- tl.where(code == 5, 0.3563,
- tl.where(code == 6, 0.6047, 1.0)))))))
- @triton.jit
- def nf3_moe_kernel(
- a_ptr, b_ptr, s_ptr, c_ptr, tw_ptr,
- sorted_ids_ptr, expert_ids_ptr, ntpp_ptr,
- N, K, EM, num_valid_tokens,
- stride_am, stride_ak,
- stride_be, stride_bn, stride_bk, # packed weight [E, N, K//8*3]
- stride_se, stride_sn, stride_sk, # block scale [E, N, K//GS]
- stride_cm, stride_cn,
- top_k: tl.constexpr, MUL_W: tl.constexpr, GS: tl.constexpr,
- BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
- compute_type: tl.constexpr,
- ):
- pid = tl.program_id(0)
- num_pid_n = tl.cdiv(N, BLOCK_N)
- pid_m = pid // num_pid_n
- pid_n = pid % num_pid_n
- if pid_m * BLOCK_M >= tl.load(ntpp_ptr):
- return
- offs_tid = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
- offs_token = tl.load(sorted_ids_ptr + offs_tid)
- tok_mask = offs_token < num_valid_tokens
- e = tl.load(expert_ids_ptr + pid_m)
- if e == -1:
- return
- offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
- offs_k = tl.arange(0, BLOCK_K)
- a_ptrs = a_ptr + (offs_token[:, None] // top_k * stride_am + offs_k[None, :] * stride_ak)
- acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
- for kb in range(0, tl.cdiv(K, BLOCK_K)):
- kk = kb * BLOCK_K + offs_k # [BLOCK_K]
- kmask = kk < K
- a = tl.load(a_ptrs, mask=tok_mask[:, None] & (kk[None, :] < K), other=0.0)
- # native 8->3 packing: word = 3 bytes at group g=k//8; code = (word >> 3*(k%8)) & 7
- g = (kk[:, None] // 8)
- sh = (3 * (kk[:, None] % 8)).to(tl.int32)
- wb = e * stride_be + offs_bn[None, :] * stride_bn + (g * 3) * stride_bk
- b0 = tl.load(b_ptr + wb + 0 * stride_bk).to(tl.int32)
- b1 = tl.load(b_ptr + wb + 1 * stride_bk).to(tl.int32)
- b2 = tl.load(b_ptr + wb + 2 * stride_bk).to(tl.int32)
- word = b0 | (b1 << 8) | (b2 << 16)
- code = (word >> sh) & 7 # [BLOCK_K, BLOCK_N]
- v = _lut(code)
- sb = e * stride_se + offs_bn[None, :] * stride_sn + (kk[:, None] // GS) * stride_sk
- s = tl.load(s_ptr + sb, mask=kmask[:, None], other=0.0).to(tl.float32)
- b = (v * s).to(compute_type)
- acc = tl.dot(a, b, acc=acc)
- a_ptrs += BLOCK_K * stride_ak
- if MUL_W:
- w = tl.load(tw_ptr + offs_token, mask=tok_mask, other=0.0)
- acc = acc * w[:, None]
- c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_bn[None, :]
- tl.store(c_ptrs, acc.to(compute_type), mask=tok_mask[:, None])
- def pack_nf3_837(codes):
- """codes [E,N,K] (0..7) -> packed uint8 [E,N,K//8*3] (build's 8->24bit->3byte layout)."""
- E, N, K = codes.shape
- c = codes.to(torch.int32).reshape(E, N, K // 8, 8)
- word = torch.zeros(E, N, K // 8, dtype=torch.int32, device=codes.device)
- for i in range(8):
- word |= (c[..., i] & 7) << (3 * i)
- b = torch.stack([(word & 0xFF), ((word >> 8) & 0xFF), ((word >> 16) & 0xFF)], -1)
- return b.to(torch.uint8).reshape(E, N, K // 8 * 3)
- def ref_dequant(codes, scale, GS):
- nf = torch.tensor(_NF3, device=codes.device)
- return nf[codes.long()] * scale.repeat_interleave(GS, dim=2).float()
- def run_kernel(a, b, s, sorted_ids, expert_ids, ntpp, N, K, top_k, GS,
- mul_w=False, tw=None, out=None, BM=16, BN=64, BK=32, warps=4):
- M = a.shape[0]
- c = out if out is not None else torch.zeros(M * top_k, N, dtype=torch.bfloat16, device=a.device)
- grid = (triton.cdiv(sorted_ids.shape[0], BM) * triton.cdiv(N, BN),)
- nf3_moe_kernel[grid](
- a, b, s, c, tw if tw is not None else a,
- sorted_ids, expert_ids, ntpp,
- N, K, sorted_ids.shape[0], M * top_k,
- a.stride(0), a.stride(1),
- b.stride(0), b.stride(1), b.stride(2),
- s.stride(0), s.stride(1), s.stride(2),
- c.stride(0), c.stride(1),
- top_k=top_k, MUL_W=mul_w, GS=GS, BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK,
- compute_type=tl.bfloat16, num_warps=warps,
- )
- return c
- def nf3_moe_layer(hidden, w13_packed, s13, w2_packed, s2, topk_ids, topk_w, emap,
- Etot, GS, H, I, BM=16):
- """Full NF3 MoE layer -> [M, H]. w13_packed [Km,2I,H//8*3], w2_packed [Km,H,I//8*3]."""
- import torch.nn.functional as F
- from vllm.model_executor.layers.fused_moe.moe_align_block_size import moe_align_block_size
- M, tk = topk_ids.shape
- sids, eids, ntpp = moe_align_block_size(topk_ids.to(torch.int32), BM, Etot, expert_map=emap)
- sids, eids, ntpp = sids.to(torch.int32), eids.to(torch.int32), ntpp.to(torch.int32)
- c1 = run_kernel(hidden, w13_packed, s13, sids, eids, ntpp, 2 * I, H, tk, GS) # [M*tk, 2I]
- g, u = c1.chunk(2, -1)
- c2 = (F.silu(g.float()) * u.float()).to(torch.bfloat16) # [M*tk, I]
- c3 = run_kernel(c2, w2_packed, s2, sids, eids, ntpp, H, I, 1, GS,
- mul_w=True, tw=topk_w.reshape(-1).float()) # [M*tk, H]
- return c3.view(M, tk, H).sum(1)
- if __name__ == "__main__":
- import time
- torch.manual_seed(0); dev = "cuda"
- from vllm.model_executor.layers.fused_moe.moe_align_block_size import moe_align_block_size
- GS = 32
- # TEST1: single expert
- E, N, K = 1, 128, 256
- codes = torch.randint(0, 8, (E, N, K), device=dev)
- scale = (torch.rand(E, N, K // GS, device=dev) * 0.05 + 0.01).to(torch.float8_e4m3fn)
- B = pack_nf3_837(codes)
- a = torch.randn(16, K, device=dev, dtype=torch.bfloat16)
- W = ref_dequant(codes, scale, GS)[0]
- ref = (a.float() @ W.t()).to(torch.bfloat16)
- sids = torch.full((16,), 16, dtype=torch.int32, device=dev); sids[:16] = torch.arange(16, device=dev)
- eids = torch.zeros(1, dtype=torch.int32, device=dev); ntpp = torch.tensor([16], dtype=torch.int32, device=dev)
- out = run_kernel(a, B, scale, sids, eids, ntpp, N, K, 1, GS)[:16]
- r1 = (out.float() - ref.float()).abs().mean() / ref.float().abs().mean()
- print(f"TEST1 single-expert: rel={r1:.5f} {'PASS' if r1 < 0.02 else 'FAIL'}")
- # TEST4: full MoE layer vs reference (the integration unit)
- import torch.nn.functional as F
- Etot, Kept = 256, 64; Km = Etot - Kept
- emap = torch.full((Etot,), -1, dtype=torch.int32, device=dev); emap[Kept:] = torch.arange(Km, dtype=torch.int32, device=dev)
- Hs, Is, tk = 512, 128, 8
- 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)
- 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)
- B13, B2 = pack_nf3_837(c13), pack_nf3_837(c2t)
- Mt = 8
- hid = torch.randn(Mt, Hs, device=dev, dtype=torch.bfloat16) * 0.5
- tids = torch.randint(0, Etot, (Mt, tk), device=dev, dtype=torch.int32)
- tw = torch.softmax(torch.rand(Mt, tk, device=dev), -1)
- out4 = nf3_moe_layer(hid, B13, sc13, B2, sc2, tids, tw, emap, Etot, GS, Hs, Is)
- W13, W2 = ref_dequant(c13, sc13, GS), ref_dequant(c2t, sc2, GS)
- ref4 = torch.zeros(Mt, Hs, device=dev)
- for m in range(Mt):
- for slot in range(tk):
- gg = int(tids[m, slot])
- if gg < Kept: continue
- l = gg - Kept
- inter = hid[m].float() @ W13[l].t(); a_, b_ = inter.chunk(2, -1)
- ref4[m] += tw[m, slot].float() * ((F.silu(a_) * b_) @ W2[l].t())
- r4 = (out4.float() - ref4).abs().sum() / (ref4.abs().sum() + 1e-6)
- print(f"TEST4 full-MoE-layer: rel={r4:.5f} {'PASS' if r4 < 0.03 else 'FAIL'}")
- # TEST3: speed sweep (per-rank GLM shapes)
- H, I = 6144, 512
- 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)
- 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)
- B13b, B2b = pack_nf3_837(c13b), pack_nf3_837(c2b)
- for Mv in [1, 4, 8]:
- hv = torch.randn(Mv, H, device=dev, dtype=torch.bfloat16)
- tidv = torch.randint(Kept, Etot, (Mv, tk), device=dev, dtype=torch.int32)
- twv = torch.rand(Mv, tk, device=dev)
- for _ in range(3): nf3_moe_layer(hv, B13b, s13b, B2b, s2b, tidv, twv, emap, Etot, GS, H, I)
- torch.cuda.synchronize(); e0, e1 = torch.cuda.Event(True), torch.cuda.Event(True)
- e0.record()
- for _ in range(20 * 75): nf3_moe_layer(hv, B13b, s13b, B2b, s2b, tidv, twv, emap, Etot, GS, H, I)
- e1.record(); torch.cuda.synchronize()
- dt = e0.elapsed_time(e1) / 1000 / 20 / Mv
- print(f"TEST3 M={Mv}: {dt*1000:.2f} ms/tok -> {1/dt:.1f} tok/s (NF3 full-layer incl align, 75L, 1 GPU)")
|