hf_sublayer_ref.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/env python3
  2. """HF's own sublayer modules on vLLM's dumped inputs: an implementation-
  3. independent check of the token-local sublayers (mHC hyper-connection, dense
  4. MLP, MoE with router) against what vLLM produced for the same rows.
  5. For each layer in the dump:
  6. hc_pre HF Glm5NextTextHyperConnection(layer params) on vLLM's residual
  7. streams (`L<i>.hc_fused.out0`, i.e. after the deferred post) ->
  8. (post, comb, collapsed); vs vLLM `hc_fused.out1/out2` and, after
  9. HF's input_layernorm, `hc_fused.out3`.
  10. hc_post HF's combine `post*x + comb^T residual` on vLLM's fused-op inputs
  11. vs vLLM `hc_fused.out0` (and the ffn-site call `hc_fused@1`).
  12. mlp HF Glm5NextTextMLP / Glm5NextTextMoE (dequantised bf16 weights)
  13. on vLLM `mlp.in0` vs vLLM `mlp.out0`.
  14. python3 hf_sublayer_ref.py --ckpt /ckpt --layers 0,1,2,3 /hit/vec-rank0-step470.pt
  15. """
  16. import argparse
  17. import os
  18. import sys
  19. import torch
  20. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  21. import hf_ref # noqa: E402
  22. os.environ.setdefault("HF_HUB_OFFLINE", "1")
  23. os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
  24. dev = torch.device("cuda")
  25. def rel(a, b):
  26. a, b = a.float().reshape(-1), b.float().reshape(-1)
  27. return float((a - b).norm() / (b.norm() + 1e-12))
  28. def fill(module, ck, L, prefix, E):
  29. for name, p in list(module.named_parameters()):
  30. hf_ref.set_param(module, name, hf_ref.layer_tensor(ck, L, prefix + name, E))
  31. for name, b in list(module.named_buffers()):
  32. hf_ref.set_param(module, name, hf_ref.layer_tensor(ck, L, prefix + name, E), buffer=True)
  33. return module
  34. def main():
  35. ap = argparse.ArgumentParser()
  36. ap.add_argument("--ckpt", required=True)
  37. ap.add_argument("--layers", default="0,1,2,3")
  38. ap.add_argument("dump")
  39. args = ap.parse_args()
  40. from transformers import AutoConfig
  41. from transformers.models.glm5_next import modeling_glm5_next as M
  42. tc = AutoConfig.from_pretrained(args.ckpt).text_config
  43. E = int(tc.num_local_experts)
  44. n, hid = int(tc.hc_mult), int(tc.hidden_size)
  45. ck = hf_ref.Ckpt(args.ckpt)
  46. b = torch.load(args.dump, map_location="cpu")
  47. cps = b["cps"]
  48. nrows = len(b["rows"])
  49. print(f"{os.path.basename(args.dump)}: {nrows} rows, positions {b['rows'][0]['pos']}..{b['rows'][-1]['pos']}")
  50. torch.set_default_dtype(torch.bfloat16)
  51. for L in [int(x) for x in args.layers.split(",")]:
  52. with torch.no_grad():
  53. # --- hyper-connection, attention site: input = streams after the previous post ---
  54. for site, key, hcname, normname in (("attn", f"L{L}.hc_fused", "attn_hc.", "input_layernorm."),
  55. ("ffn", f"L{L}.hc_fused@1", "ffn_hc.", "post_attention_layernorm.")):
  56. if f"{key}.out0" not in cps:
  57. continue
  58. with torch.device("meta"):
  59. hc = M.Glm5NextTextHyperConnection(tc)
  60. ln = M.Glm5NextTextRMSNorm(hid, tc.rms_norm_eps)
  61. fill(hc, ck, L, hcname, E)
  62. fill(ln, ck, L, normname, E)
  63. # HF post-combine on vLLM's fused-op inputs vs vLLM out0 (residual streams)
  64. x = cps[f"{key}.in_x"].to(dev)
  65. res = cps[f"{key}.in_residual"].to(dev).view(nrows, n, hid)
  66. post_in = cps[f"{key}.in_post_layer_mix"].to(dev).view(nrows, n)
  67. comb_in = cps[f"{key}.in_comb_res_mix"].to(dev).view(nrows, n, n)
  68. streams = post_in.to(x.dtype).unsqueeze(-1) * x.unsqueeze(-2) + torch.matmul(comb_in.to(x.dtype).transpose(-1, -2), res)
  69. e_post = rel(streams, cps[f"{key}.out0"].to(dev).view(nrows, n, hid))
  70. # HF hyper-connection on vLLM's resulting streams vs vLLM's post/comb/x
  71. v_streams = cps[f"{key}.out0"].to(dev).view(1, nrows, n, hid)
  72. post, comb, collapsed = hc(v_streams)
  73. e_p = rel(post[0], cps[f"{key}.out1"].to(dev).view(nrows, n))
  74. e_c = rel(comb[0], cps[f"{key}.out2"].to(dev).view(nrows, n, n))
  75. e_x = rel(ln(collapsed[0]), cps[f"{key}.out3"].to(dev))
  76. print(f"L{L:<3d} hc {site:4s}: post-combine {e_post:.2e} | pre: post {e_p:.2e} comb {e_c:.2e} x(normed) {e_x:.2e}")
  77. # --- MLP / MoE on vLLM's mlp input ---
  78. key = f"L{L}.mlp"
  79. if f"{key}.in0" in cps:
  80. dense = L < int(tc.first_k_dense_replace)
  81. with torch.device("meta"):
  82. mlp = M.Glm5NextTextMLP(tc) if dense else M.Glm5NextTextMoE(tc)
  83. fill(mlp, ck, L, "mlp.", E)
  84. xin = cps[f"{key}.in0"].to(dev)
  85. out = mlp(xin.view(1, nrows, hid) if not dense else xin)
  86. out = out.view(nrows, hid)
  87. e = rel(out, cps[f"{key}.out0"].to(dev))
  88. per_row = [(r["pos"], round(rel(out[k], cps[f"{key}.out0"][k].to(dev)), 3)) for k, r in enumerate(b["rows"])]
  89. worst = sorted(per_row, key=lambda t: -t[1])[:4]
  90. print(f"L{L:<3d} mlp {'dense' if dense else 'moe '}: vLLM out vs HF {e:.2e} (worst rows {worst})")
  91. del mlp
  92. torch.cuda.empty_cache()
  93. ck.close()
  94. if __name__ == "__main__":
  95. main()