_glm53_tp3pad_test.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #!/usr/bin/env python3
  2. """experimental/tp3/tp3pad.py: the load-time zero padding behind TP=3.
  3. Builds a small GLM-5.3-Flash-shaped set of tensors (one MLA layer, one KDA
  4. layer, routed experts in bf16 and NVFP4 layout, a shared expert) and runs
  5. pad_weights with a config that pads heads 4->6, KDA heads 4->6 and the MoE
  6. intermediate 32->48. Checks:
  7. - every sized tensor reaches the padded shape, the original block is
  8. byte-identical and the padding is zero (uint8 NVFP4 and scales included)
  9. - tensors that are not sized by a padded dim pass through untouched
  10. - a bf16 expert forward, silu(x Wg) * (x Wu) Wd, is bit-identical padded
  11. - with no tp_pad_orig in the config, weights pass through as-is
  12. CPU only, no vLLM needed (vllm.logger is stubbed when absent):
  13. python3 dev/patch-tests/_glm53_tp3pad_test.py
  14. """
  15. import importlib.util
  16. import logging
  17. import os
  18. import sys
  19. import types
  20. import torch
  21. try:
  22. import vllm.logger # noqa: F401
  23. except Exception:
  24. stub = types.ModuleType("vllm.logger")
  25. stub.init_logger = logging.getLogger
  26. sys.modules.setdefault("vllm", types.ModuleType("vllm"))
  27. sys.modules["vllm.logger"] = stub
  28. here = os.path.dirname(os.path.abspath(__file__))
  29. path = next(p for p in (os.path.join(here, "tp3pad.py"),
  30. os.path.join(here, "../../experimental/tp3/tp3pad.py")) if os.path.exists(p))
  31. spec = importlib.util.spec_from_file_location("tp3pad", path)
  32. m = importlib.util.module_from_spec(spec)
  33. spec.loader.exec_module(m)
  34. H0, H1, K0, K1, I0, I1 = 4, 6, 4, 6, 32, 48
  35. nope, rope, vd, hd, hid = 8, 4, 8, 16, 64
  36. cfg = types.SimpleNamespace(num_attention_heads=H1, linear_num_heads=K1, moe_intermediate_size=I1,
  37. qk_nope_head_dim=nope, qk_rope_head_dim=rope, v_head_dim=vd,
  38. linear_head_dim=hd, n_shared_experts=1,
  39. tp_pad_orig={"num_attention_heads": H0, "linear_num_heads": K0,
  40. "moe_intermediate_size": I0})
  41. g = torch.Generator().manual_seed(0)
  42. r = lambda *s: torch.randn(*s, generator=g).to(torch.bfloat16)
  43. u8 = lambda *s: torch.randint(1, 255, s, generator=g, dtype=torch.uint8)
  44. L = "model.layers.0"
  45. W = { # name: (tensor, padded shape or None if untouched)
  46. f"{L}.self_attn.q_b_proj.weight": (r(H0 * (nope + rope), 32), (H1 * (nope + rope), 32)),
  47. f"{L}.self_attn.kv_b_proj.weight": (r(H0 * (nope + vd), 16), (H1 * (nope + vd), 16)),
  48. f"{L}.self_attn.o_proj.weight": (r(hid, H0 * vd), (hid, H1 * vd)),
  49. f"{L}.self_attn.kv_a_proj_with_mqa.weight": (r(16 + rope, hid), None),
  50. f"{L}.self_attn.indexer.wq_b.weight": (r(H0 * (nope + rope), 32), None),
  51. "model.layers.1.self_attn.q_proj.weight": (r(K0 * hd, hid), (K1 * hd, hid)),
  52. "model.layers.1.self_attn.q_conv1d.weight": (r(K0 * hd, 1, 4), (K1 * hd, 1, 4)),
  53. "model.layers.1.self_attn.b_proj.weight": (r(K0, hid), (K1, hid)),
  54. "model.layers.1.self_attn.A_log": (r(K0), (K1,)),
  55. "model.layers.1.self_attn.dt_bias": (r(K0 * hd), (K1 * hd,)),
  56. "model.layers.1.self_attn.o_proj.weight": (r(hid, K0 * hd), (hid, K1 * hd)),
  57. f"{L}.mlp.experts.0.gate_proj.weight": (r(I0, hid), (I1, hid)),
  58. f"{L}.mlp.experts.0.up_proj.weight": (r(I0, hid), (I1, hid)),
  59. f"{L}.mlp.experts.0.down_proj.weight": (r(hid, I0), (hid, I1)),
  60. f"{L}.mlp.experts.1.gate_proj.weight": (u8(I0, hid // 2), (I1, hid // 2)),
  61. f"{L}.mlp.experts.1.gate_proj.weight_scale": (u8(I0, hid // 16), (I1, hid // 16)),
  62. f"{L}.mlp.experts.1.down_proj.weight": (u8(hid, I0 // 2), (hid, I1 // 2)),
  63. f"{L}.mlp.experts.1.down_proj.weight_scale": (u8(hid, I0 // 16), (hid, I1 // 16)),
  64. f"{L}.mlp.experts.1.down_proj.weight_scale_2": (r(1), None),
  65. f"{L}.mlp.shared_experts.gate_proj.weight": (r(I0, hid), (I1, hid)),
  66. f"{L}.mlp.shared_experts.down_proj.weight": (r(hid, I0), (hid, I1)),
  67. f"{L}.mlp.gate.weight": (r(8, hid), None),
  68. }
  69. fails = 0
  70. out = dict(m.pad_weights(((k, v[0]) for k, v in W.items()), cfg))
  71. for name, (t, shape) in W.items():
  72. p = out[name]
  73. if shape is None:
  74. ok = p is t
  75. else:
  76. a, b = (x.view(torch.uint8) if x.element_size() == 1 else x for x in (t, p))
  77. sl = tuple(slice(0, n) for n in t.shape)
  78. pad = b.clone(); pad[sl] = 0
  79. ok = tuple(p.shape) == shape and p.dtype == t.dtype and torch.equal(b[sl], a) and not pad.any()
  80. if not ok:
  81. print(f"FAIL {name}: {tuple(t.shape)} -> {tuple(p.shape)}"); fails += 1
  82. x = r(3, hid).float()
  83. e = lambda gw, uw, dw: (torch.nn.functional.silu(x @ gw.float().T) * (x @ uw.float().T)) @ dw.float().T
  84. E = f"{L}.mlp.experts.0."
  85. before = e(W[E + "gate_proj.weight"][0], W[E + "up_proj.weight"][0], W[E + "down_proj.weight"][0])
  86. after = e(out[E + "gate_proj.weight"], out[E + "up_proj.weight"], out[E + "down_proj.weight"])
  87. if not torch.equal(before, after):
  88. print("FAIL expert forward differs after padding"); fails += 1
  89. plain = types.SimpleNamespace(**{k: v for k, v in vars(cfg).items() if k != "tp_pad_orig"})
  90. if any(a is not b for (_, a), b in zip(m.pad_weights(((k, v[0]) for k, v in W.items()), plain),
  91. (v[0] for v in W.values()))):
  92. print("FAIL without tp_pad_orig the weights were changed"); fails += 1
  93. print(f"{len(W)} tensors checked, expert forward, pass-through: " + ("PASS" if not fails else f"{fails} FAILED"))
  94. sys.exit(1 if fails else 0)