marlin_moe_nondet.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env python3
  2. """fused_marlin_moe returns different bits for the same inputs at some M.
  3. No checkpoint needed: random NVFP4 weights, one CUDA device. Runs the same
  4. call repeatedly and compares the outputs bit for bit.
  5. python marlin_moe_nondet.py
  6. """
  7. import argparse
  8. import torch
  9. from vllm.model_executor.layers.fused_moe.experts.marlin_moe import fused_marlin_moe
  10. from vllm.model_executor.layers.quantization.utils.marlin_utils_fp4 import (
  11. rand_marlin_weight_nvfp4_like,
  12. )
  13. from vllm.scalar_type import scalar_types
  14. DEV = "cuda"
  15. def quant_stack(experts, n, k, group):
  16. q, s, g = [], [], []
  17. for _ in range(experts):
  18. w = torch.randn(n, k, device=DEV, dtype=torch.bfloat16) * 0.05
  19. _, qw, sw, gw = rand_marlin_weight_nvfp4_like(w, group)
  20. q.append(qw), s.append(sw), g.append(gw)
  21. return torch.stack(q), torch.stack(s), torch.stack(g)
  22. def trial(w1, w2, m, reps, experts, hidden, topk):
  23. gen = torch.Generator(device=DEV).manual_seed(1)
  24. x = (torch.randn(m, hidden, device=DEV, generator=gen) * 0.5).to(torch.bfloat16)
  25. weights, ids = torch.topk(
  26. torch.softmax(torch.randn(m, experts, device=DEV, generator=gen), -1), topk, dim=-1
  27. )
  28. weights = (weights / weights.sum(-1, keepdim=True)).float()
  29. ids = ids.to(torch.int32)
  30. outs = []
  31. for _ in range(reps):
  32. out = fused_marlin_moe(
  33. x, w1[0], w2[0], None, None, w1[1], w2[1], weights, ids,
  34. scalar_types.float4_e2m1f.id, global_num_experts=experts,
  35. global_scale1=w1[2], global_scale2=w2[2],
  36. )
  37. torch.cuda.synchronize()
  38. outs.append(out.clone())
  39. differing = sum(1 for o in outs[1:] if not torch.equal(o, outs[0]))
  40. worst = max(float((o.float() - outs[0].float()).abs().max()) for o in outs[1:])
  41. return differing, worst
  42. def main():
  43. ap = argparse.ArgumentParser()
  44. ap.add_argument("--experts", type=int, default=288)
  45. ap.add_argument("--topk", type=int, default=8)
  46. ap.add_argument("--hidden", type=int, default=4096)
  47. ap.add_argument("--inter", type=int, default=2048)
  48. ap.add_argument("--group", type=int, default=16)
  49. ap.add_argument("--reps", type=int, default=24)
  50. ap.add_argument("--m", type=int, nargs="+", default=[1, 1024, 2048, 2816, 3072, 3104, 3136])
  51. a = ap.parse_args()
  52. torch.manual_seed(0)
  53. w1 = quant_stack(a.experts, 2 * a.inter, a.hidden, a.group) # gate and up
  54. w2 = quant_stack(a.experts, a.hidden, a.inter, a.group) # down
  55. print(f"{torch.cuda.get_device_name(0)} experts={a.experts} topk={a.topk} "
  56. f"hidden={a.hidden} inter={a.inter} reps={a.reps}")
  57. for m in a.m:
  58. differing, worst = trial(w1, w2, m, a.reps, a.experts, a.hidden, a.topk)
  59. print(f"M={m:<6} runs differing from the first: {differing}/{a.reps - 1} "
  60. f"max abs diff {worst:.3e}")
  61. if __name__ == "__main__":
  62. main()