| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- #!/usr/bin/env python3
- """T7b: sanity for T7 -- are the CUTLASS NVFP4 MoE outputs real (finite,
- non-zero, input-dependent) and do they agree with a torch dequantised
- reference? Same synthetic layer as kt7.py."""
- import sys
- import torch
- sys.argv = [sys.argv[0]]
- exec(open("/kv/kt7.py").read().split("def routing(")[0]) # reuse weights + moe()
- FP4_LUT = torch.tensor([0, 0.5, 1, 1.5, 2, 3, 4, 6], device=dev)
- def deq_fp4(packed: torch.Tensor, scale_fp8: torch.Tensor, gscale: torch.Tensor) -> torch.Tensor:
- """packed uint8 [..., K/2], scale [..., K/16] fp8, gscale scalar -> float [..., K]."""
- lo = packed & 0xF
- hi = packed >> 4
- nib = torch.stack([lo, hi], dim=-1).reshape(*packed.shape[:-1], packed.shape[-1] * 2)
- mag = FP4_LUT[(nib & 7).long()]
- sign = torch.where((nib & 8) != 0, -1.0, 1.0)
- vals = mag * sign
- sc = scale_fp8.float().repeat_interleave(16, dim=-1)
- return vals * sc * gscale
- def quant_deq_act(x: torch.Tensor, gscale_inv: float) -> torch.Tensor:
- """Emulate nvfp4 activation quant: per-16 block fp8 scale, global scale."""
- M, Kd = x.shape
- xb = x.float().view(M, Kd // 16, 16)
- amax = xb.abs().amax(-1, keepdim=True)
- # block scale (fp8) = amax / 6 * global; ModelOpt: sf = amax/6 / input_scale
- sf = (amax / 6.0 * gscale_inv).to(fp8).float()
- sf = torch.where(sf == 0, torch.ones_like(sf), sf)
- q = (xb / (sf / gscale_inv)).clamp(-6, 6)
- # round to e2m1 grid
- grid = torch.tensor([0, 0.5, 1, 1.5, 2, 3, 4, 6], device=x.device)
- idx = (q.abs().unsqueeze(-1) - grid).abs().argmin(-1)
- qv = grid[idx] * torch.sign(q)
- return (qv * (sf / gscale_inv)).view(M, Kd)
- gen = torch.Generator(device=dev).manual_seed(3)
- M = 8
- x = torch.randn(M, K, device=dev, generator=gen).to(torch.bfloat16) * 1.5
- ids = torch.stack([torch.randperm(E, device=dev, generator=gen)[:TOPK] for _ in range(M)]).to(torch.int32)
- w = torch.rand(M, TOPK, device=dev, generator=gen) + 0.1
- w = (w / w.sum(-1, keepdim=True)) * 2.5
- out = moe(x, ids, w, True, 1)
- torch.cuda.synchronize()
- print("out finite:", torch.isfinite(out).all().item(), "abs mean:", out.float().abs().mean().item(),
- "abs max:", out.float().abs().max().item(), "zero rows:", int((out.float().abs().amax(1) == 0).sum()))
- out_b = moe(x, ids, w, False, 1)
- print("bf16-input variant vs prequant: max |d| =", (out.float() - out_b.float()).abs().max().item())
- x2 = x.clone(); x2[0] = torch.randn(K, device=dev, generator=gen).to(torch.bfloat16)
- out2 = moe(x2, ids, w, True, 1)
- print("changing row 0 input changes row 0 output:", (out2[0].float() - out[0].float()).abs().max().item() > 1e-3,
- "and leaves other rows unchanged:", (out2[1:].float() - out[1:].float()).abs().max().item())
- # torch reference for a few rows
- a1_inv = float(1.0 / a1_gscale[0]) # = w13_input_scale
- xd = quant_deq_act(x, a1_inv)
- ref = torch.zeros(M, K, device=dev)
- for r in range(M):
- for j in range(TOPK):
- e = int(ids[r, j])
- W13 = deq_fp4(w13[e], w13_scale[e], float(w13_scale_2[e])) # [2N, K] rows: [w1(gate); w3(up)]
- W2 = deq_fp4(w2[e], w2_scale[e], float(w2_scale_2[e])) # [K, N]
- gate = xd[r] @ W13[:N].T
- up = xd[r] @ W13[N:].T
- gate = gate.clamp(max=10.0)
- up = up.clamp(-10.0, 10.0)
- h = torch.nn.functional.silu(gate) * up
- hd = quant_deq_act(h[None], float(w2_input_scale[e]))[0]
- ref[r] += w[r, j] * (hd @ W2.T)
- err = (out.float() - ref).abs().amax(1) / (ref.abs().amax(1) + 1e-6)
- print("per-row rel err vs torch reference:", [round(v, 3) for v in err.tolist()])
- print("ref abs mean:", ref.abs().mean().item())
|