_glm53_tp3_sparse_mla_test.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. #!/usr/bin/env python3
  2. """experimental/fixes/gb10_sparse_mla.py at head counts that are not powers of two.
  3. TP=3 leaves 22 MLA heads per rank (66 padded / 3). The kernel used to take
  4. tl.arange(0, H), which needs a power of two; it now tiles at the next power of
  5. two (>= 16) with a head mask. This checks both launch paths (one program per
  6. token, and the split + combine path decode takes) against a float32 torch
  7. reference, at H = 16 and 32 (unchanged tiles) and 22 and 24 (masked), and
  8. H = 11 (TP=6: 66 / 6, a 16-row tile with 5 rows masked).
  9. Pass: every case within 5e-3 relative error of the reference, and the masked
  10. cases no worse than twice the H=16 error. On one GB10:
  11. mkdir -p /tmp/smla && cp dev/patch-tests/_glm53_tp3_sparse_mla_test.py \\
  12. experimental/fixes/gb10_sparse_mla.py /tmp/smla/
  13. sudo docker run --rm --gpus all -v /tmp/smla:/t -w /t \\
  14. --entrypoint python3 <glm53 image> /t/_glm53_tp3_sparse_mla_test.py
  15. """
  16. import importlib.util
  17. import os
  18. import sys
  19. import torch
  20. here = os.path.dirname(os.path.abspath(__file__))
  21. path = next(p for p in (os.path.join(here, "gb10_sparse_mla.py"),
  22. os.path.join(here, "../../experimental/fixes/gb10_sparse_mla.py")) if os.path.exists(p))
  23. spec = importlib.util.spec_from_file_location("gb10_sparse_mla", path)
  24. m = importlib.util.module_from_spec(spec)
  25. spec.loader.exec_module(m)
  26. dev = torch.device("cuda")
  27. D, S, TOPK = 512, 8192, 512
  28. def reference(q, kv, idx, lens, sm_scale, kv_scale):
  29. kvf = kv.float() * kv_scale
  30. out = torch.empty(q.shape, dtype=torch.float32, device=q.device)
  31. for t in range(q.shape[0]):
  32. rows = kvf[idx[t, :lens[t]].long()] # [n, D]
  33. s = (q[t].float() @ rows.T) * sm_scale # [H, n]
  34. out[t] = torch.softmax(s, -1) @ rows
  35. return out
  36. def case(H, T, splits, g):
  37. q = torch.randn(T, H, D, generator=g, device=dev).to(torch.bfloat16)
  38. kv = (torch.randn(S, D, generator=g, device=dev) * 0.5).to(torch.float8_e4m3fn)
  39. lens = torch.randint(TOPK // 2, TOPK + 1, (T,), generator=g, device=dev).int()
  40. idx = torch.stack([torch.randperm(S, generator=g, device=dev)[:TOPK] for _ in range(T)]).int()
  41. for t in range(T):
  42. idx[t, lens[t]:] = -1
  43. sm_scale, kv_scale = D ** -0.5, 0.75
  44. got = m.sparse_mla(q, kv, idx, lens, sm_scale, kv_scale, splits=splits).float()
  45. ref = reference(q, kv, idx, lens, sm_scale, kv_scale)
  46. return ((got - ref).norm() / ref.norm()).item()
  47. g = torch.Generator(device=dev).manual_seed(0)
  48. errs, fails = {}, 0
  49. for T, splits in ((64, 1), (4, 8)):
  50. for H in (11, 16, 22, 24, 32):
  51. e = case(H, T, splits, g)
  52. errs[(H, T)] = e
  53. print(f"H={H:2d} T={T:2d} splits={splits}: rel err {e:.2e}", flush=True)
  54. for (H, T), e in errs.items():
  55. if e > 5e-3 or e > 2 * max(errs[(16, T)], 1e-4):
  56. print(f"FAIL H={H} T={T}: {e:.2e}"); fails += 1
  57. print("PASS" if not fails else f"{fails} FAILED")
  58. sys.exit(1 if fails else 0)