_kda_numerics_test.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/env python3
  2. """T9: GLM-5.3-Flash KDA numerics against an fp32 torch reference.
  3. Covers the three kernels the nightly uses for the recurrent state (per rank:
  4. 16 heads x 128, safe gate, lower_bound -5): FlashKDA chunked prefill (the
  5. nightly's new default on SM12x), the Triton chunk_kda_with_fused_gate prefill
  6. (what the old image ran), and fused_recurrent_kda plain decode, with the
  7. decode state seeded from the prefill's final state. The reference replicates
  8. the recurrent kernel's math: l2norm(q,k), q*=K^-0.5, g = lb/(1+exp(-exp(A)(g1+b))),
  9. h*=exp(g), v-=h@k, v*=sigmoid(beta), h+=v k^T, o=h@q.
  10. """
  11. import sys
  12. import torch
  13. import vllm._flashkda_C # noqa: F401
  14. from vllm.models.glm5next.nvidia.ops.third_party.kda import (
  15. chunk_kda_with_fused_gate,
  16. fused_recurrent_kda,
  17. )
  18. dev = torch.device("cuda")
  19. H, K, V = 16, 128, 128
  20. LB = -5.0
  21. torch.manual_seed(5)
  22. def ref_run(q, k, v, g1, beta, a_log, dt_bias, h0):
  23. """q,k,v,g1: [T,H,K] bf16; beta [T,H]; h0 [H,V,K] fp32 -> (o [T,H,V] fp32, h)"""
  24. T = q.shape[0]
  25. h = h0.clone().float()
  26. o = torch.zeros(T, H, V, device=dev)
  27. A = torch.exp(a_log.view(H).float())
  28. for t in range(T):
  29. qf, kf, vf = q[t].float(), k[t].float(), v[t].float()
  30. qn = qf / torch.sqrt((qf * qf).sum(-1, keepdim=True) + 1e-6) * (K ** -0.5)
  31. kn = kf / torch.sqrt((kf * kf).sum(-1, keepdim=True) + 1e-6)
  32. gate = LB / (1.0 + torch.exp(-(A[:, None] * (g1[t].float() + dt_bias.float())))) # [H,K]
  33. h = h * torch.exp(gate)[:, None, :]
  34. vp = vf - torch.einsum("hvk,hk->hv", h, kn)
  35. vp = vp * torch.sigmoid(beta[t].float())[:, None]
  36. h = h + vp[:, :, None] * kn[:, None, :]
  37. o[t] = torch.einsum("hvk,hk->hv", h, qn)
  38. return o, h
  39. def rel(a, b):
  40. return ((a.float() - b.float()).abs().max() / (b.float().abs().max() + 1e-6)).item()
  41. a_log = torch.log(torch.rand(1, 1, H, 1, device=dev) * 8 + 1).float().contiguous()
  42. dt_bias = (torch.randn(H * K, device=dev) * 0.5).float().contiguous()
  43. ok = True
  44. for L in (20, 300, 2500):
  45. T = L + 40
  46. q = torch.randn(T, H, K, device=dev).to(torch.bfloat16)
  47. k = torch.randn(T, H, K, device=dev).to(torch.bfloat16)
  48. v = torch.randn(T, H, V, device=dev).to(torch.bfloat16)
  49. g1 = torch.randn(T, H, K, device=dev).to(torch.bfloat16)
  50. beta = torch.randn(T, H, device=dev).to(torch.bfloat16)
  51. h0 = torch.zeros(H, V, K, device=dev)
  52. o_ref, h_ref_pref = ref_run(q[:L], k[:L], v[:L], g1[:L], beta[:L], a_log, dt_bias.view(H, K), h0)
  53. # --- FlashKDA prefill (nightly default) ---
  54. ws = torch.ops._flashkda_C.get_workspace_size(T, H, 4)
  55. workspace = torch.empty(ws, dtype=torch.uint8, device=dev)
  56. out = torch.empty(1, L, H, V, device=dev, dtype=torch.bfloat16)
  57. init = torch.zeros(1, H, V, K, device=dev)
  58. final = torch.zeros(1, H, V, K, device=dev)
  59. cu = torch.tensor([0, L], device=dev, dtype=torch.int32)
  60. torch.ops._flashkda_C.fwd(q[:L][None].contiguous(), k[:L][None].contiguous(), v[:L][None].contiguous(),
  61. g1[:L][None].contiguous(), beta[:L][None].contiguous(), K ** -0.5, out, workspace,
  62. a_log.view(-1), dt_bias.view(-1, K), LB, init.contiguous(), final, cu, None, None)
  63. torch.cuda.synchronize()
  64. e_o = rel(out[0], o_ref); e_h = rel(final[0], h_ref_pref)
  65. print(f"L={L} flashkda prefill: out rel err {e_o:.4g}, final state rel err {e_h:.4g}, "
  66. f"nan={torch.isnan(out).any().item() or torch.isnan(final).any().item()}")
  67. ok &= e_o < 0.05 and e_h < 0.05
  68. # --- Triton chunk prefill (old image path) ---
  69. init_t = torch.zeros(1, H, V, K, device=dev)
  70. o_t, h_t = chunk_kda_with_fused_gate(
  71. q=q[:L][None], k=k[:L][None], v=v[:L][None], raw_g=g1[:L][None],
  72. beta=torch.sigmoid(beta[:L].float())[None], A_log=a_log, g_bias=dt_bias,
  73. initial_state=init_t, output_final_state=True, use_qk_l2norm_in_kernel=True,
  74. cu_seqlens=cu, safe_gate=True, lower_bound=LB)
  75. torch.cuda.synchronize()
  76. e_ot = rel(o_t[0], o_ref); e_ht = rel(h_t[0], h_ref_pref)
  77. print(f"L={L} triton chunk prefill: out rel err {e_ot:.4g}, final state rel err {e_ht:.4g}")
  78. ok &= e_ot < 0.05 and e_ht < 0.05
  79. # --- plain decode from the flashkda state ---
  80. pool = torch.zeros(4, H, V, K, device=dev)
  81. pool[1] = final[0]
  82. o_dec_ref, h_dec_ref = ref_run(q[L:], k[L:], v[L:], g1[L:], beta[L:], a_log, dt_bias.view(H, K), h_ref_pref)
  83. worst = 0.0
  84. for i in range(L, T):
  85. o, _ = fused_recurrent_kda(q=q[i:i+1][None], k=k[i:i+1][None], v=v[i:i+1][None], g=g1[i:i+1][None],
  86. beta=beta[i:i+1][None], initial_state=pool, use_qk_l2norm_in_kernel=True,
  87. cu_seqlens=torch.tensor([0, 1], device=dev, dtype=torch.int32),
  88. ssm_state_indices=torch.tensor([1], device=dev, dtype=torch.int32),
  89. sigmoid_beta=True, a_log=a_log, g_bias=dt_bias, compute_gate=True, lower_bound=LB)
  90. torch.cuda.synchronize()
  91. worst = max(worst, rel(o[0, 0], o_dec_ref[i - L]))
  92. e_hd = rel(pool[1], h_dec_ref)
  93. print(f"L={L} plain decode x40: worst out rel err {worst:.4g}, state after decode rel err {e_hd:.4g}, "
  94. f"|state| {pool[1].abs().mean().item():.4g}")
  95. ok &= worst < 0.05 and e_hd < 0.05
  96. print("T9:", "PASS" if ok else "FAIL")
  97. sys.exit(0 if ok else 1)