hf_compare.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. #!/usr/bin/env python3
  2. """Compare HF's per-layer residual streams (hf_ref.py output) with vLLM tap dumps
  3. at one position, layer by layer.
  4. HF layer L output = the 4 residual streams after layer L's ffn post-mix. In
  5. vLLM's DEEP dump the same tensor is the NEXT layer's fused op output
  6. `L{L+1}.hc_fused.out0` (the deferred post applied), and for the last layer
  7. `L44.hc_post.out0`. The final normed hidden is compared by applying the norm
  8. weight to the stream mean of vLLM's last output.
  9. python3 hf_compare.py --hf /out/hf_layers.pt --ckpt /ckpt --pos 338 /hit/vec-rank0-step464.pt /hit/vec-rank0-step470.pt
  10. """
  11. import argparse
  12. import json
  13. import os
  14. import torch
  15. from safetensors import safe_open
  16. def rel(a, b):
  17. a, b = a.float().reshape(-1), b.float().reshape(-1)
  18. return float((a - b).norm() / (b.norm() + 1e-12))
  19. def main():
  20. ap = argparse.ArgumentParser()
  21. ap.add_argument("--hf", required=True)
  22. ap.add_argument("--ckpt", required=True)
  23. ap.add_argument("--pos", type=int, required=True)
  24. ap.add_argument("dumps", nargs="+")
  25. args = ap.parse_args()
  26. hf = torch.load(args.hf, map_location="cpu")
  27. kp = hf["keep_pos"]
  28. if args.pos not in kp:
  29. print("HF kept positions", kp)
  30. return 1
  31. hi = kp.index(args.pos)
  32. nlayers = max(hf["layers"]) + 1
  33. idx = json.load(open(os.path.join(args.ckpt, "model.safetensors.index.json")))["weight_map"]
  34. nm = "model.language_model.norm.weight"
  35. normw = safe_open(os.path.join(args.ckpt, idx[nm]), "pt").get_tensor(nm).float()
  36. cfg = json.load(open(os.path.join(args.ckpt, "config.json")))
  37. tc = cfg.get("text_config", cfg)
  38. eps = float(tc.get("rms_norm_eps", 1e-5))
  39. dumps = []
  40. for p in args.dumps:
  41. b = torch.load(p, map_location="cpu")
  42. rows = [i for i, r in enumerate(b["rows"]) if r["pos"] == args.pos]
  43. if not rows:
  44. print(f"{p}: no row at pos {args.pos}")
  45. return 1
  46. dumps.append((os.path.basename(p), b["cps"], rows[-1]))
  47. print(f"pos {args.pos}: relative l2 difference of the residual streams vs HF, per layer")
  48. print("layer " + " ".join(f"{n:>28s}" for n, _, _ in dumps) + " (vLLM dump vs vLLM dump)")
  49. for L in range(nlayers):
  50. h = hf["layers"][L][hi].float() # [4, 4096]
  51. key = f"L{L + 1}.hc_fused.out0" if L + 1 < nlayers else f"L{L}.hc_post.out0"
  52. vals, vecs = [], []
  53. for n, cps, i in dumps:
  54. if key not in cps:
  55. vals.append(float("nan"))
  56. vecs.append(None)
  57. continue
  58. v = cps[key][i].float().view(4, -1)
  59. vals.append(rel(v, h))
  60. vecs.append(v)
  61. cross = rel(vecs[0], vecs[1]) if len(vecs) > 1 and vecs[0] is not None and vecs[1] is not None else float("nan")
  62. print(f"L{L:<5d}" + " ".join(f"{x:28.4f}" for x in vals) + f" {cross:.4f}")
  63. # final hidden: HF normed vs norm(mean of vLLM last streams)
  64. hfin = hf["final_hidden"][hi].float()
  65. print("final normed hidden vs HF:")
  66. for n, cps, i in dumps:
  67. key = f"L{nlayers - 1}.hc_post.out0"
  68. if key in cps:
  69. m = cps[key][i].float().view(4, -1).mean(0)
  70. x = m * torch.rsqrt(m.pow(2).mean() + eps) * normw
  71. print(f" {n}: rel {rel(x, hfin):.4f}")
  72. if __name__ == "__main__":
  73. raise SystemExit(main())