kda_ref.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env python3
  2. """Offline decode-step reference for every KDA layer in a glm53_step_tap dump.
  3. A decode dump holds, per KDA layer and tapped row, everything the layer's
  4. decode path consumed and produced: `kda.qkv` (merged q|k|v projection, before
  5. the short conv), `kda.g1`, `kda.beta` (raw), the request's `conv_before` and
  6. `rec_before` state blocks, and `kda.out`, `conv_after`, `rec_after`. This
  7. tool replays the step in fp32 from those inputs with the checkpoint's conv
  8. weights, A_log and dt_bias (sliced to the dump's TP rank), using the same
  9. math as T9's reference (l2-normed q/k, bounded sigmoid gate, sigmoid beta),
  10. and reports how far the dumped output and states are from it. Both conv
  11. state layouts (dim-first / state-first) and both recurrent state layouts
  12. ([H,V,K] / [H,K,V]) are tried; the matching one is reported.
  13. python3 kda_ref.py --ckpt /ckpt /hit/vec-rank0-step3257.pt:92 [more specs]
  14. """
  15. import argparse
  16. import json
  17. import os
  18. import re
  19. import torch
  20. from safetensors import safe_open
  21. dev = torch.device("cuda")
  22. PREFIX = "model.language_model.layers."
  23. class Ckpt:
  24. def __init__(self, d):
  25. self.d = d
  26. cfg = json.load(open(os.path.join(d, "config.json")))
  27. self.cfg = cfg.get("text_config", cfg)
  28. self.map = json.load(open(os.path.join(d, "model.safetensors.index.json")))["weight_map"]
  29. self._open = {}
  30. def get(self, name):
  31. f = self._open.get(self.map[name])
  32. if f is None:
  33. f = safe_open(os.path.join(self.d, self.map[name]), "pt", device="cuda")
  34. self._open[self.map[name]] = f
  35. return f.get_tensor(name)
  36. def has(self, name):
  37. return name in self.map
  38. def rel(a, b):
  39. a, b = a.float().reshape(-1), b.float().reshape(-1)
  40. return f"l2 {float((a - b).norm() / (b.norm() + 1e-12)):.2e} max {float((a - b).abs().max() / (b.abs().max() + 1e-12)):.2e}"
  41. def step_ref(q, k, v, g1, beta, a_log, dt_bias, h, lb, D):
  42. """One recurrent step. q,k,v,g1 [H,D]; beta [H]; h [H,V,K]; returns (o [H,V], h)."""
  43. A = torch.exp(a_log.float()).view(-1)
  44. qn = q / torch.sqrt((q * q).sum(-1, keepdim=True) + 1e-6) * (D**-0.5)
  45. kn = k / torch.sqrt((k * k).sum(-1, keepdim=True) + 1e-6)
  46. gate = lb / (1.0 + torch.exp(-(A[:, None] * (g1 + dt_bias))))
  47. h = h * torch.exp(gate)[:, None, :]
  48. vp = v - torch.einsum("hvk,hk->hv", h, kn)
  49. vp = vp * torch.sigmoid(beta)[:, None]
  50. h = h + vp[:, :, None] * kn[:, None, :]
  51. return torch.einsum("hvk,hk->hv", h, qn), h
  52. def main():
  53. ap = argparse.ArgumentParser()
  54. ap.add_argument("--ckpt", required=True)
  55. ap.add_argument("--layers", default="", help="comma list; default every KDA layer in the dump")
  56. ap.add_argument("--state-vs", default="", help="a prefill dump (extended tap): compare this step's rec/conv_after "
  57. "with the state that run left after its last position (extra kda.state_after_req)")
  58. ap.add_argument("specs", nargs="+", help="vec.pt:pos")
  59. args = ap.parse_args()
  60. ck = Ckpt(args.ckpt)
  61. cfg = ck.cfg
  62. lac = cfg.get("linear_attn_config") or {}
  63. H_all = int(cfg.get("linear_num_heads") or lac["num_heads"])
  64. D = int(cfg.get("linear_head_dim") or lac["head_dim"])
  65. W = int(cfg.get("linear_conv_kernel_dim") or lac["short_conv_kernel_size"])
  66. lb = float(cfg["linear_lower_bound"] if "linear_lower_bound" in cfg else lac["gate_lower_bound"])
  67. for spec in args.specs:
  68. path, _, pos = spec.rpartition(":")
  69. m = re.search(r"rank(-?\d+)", os.path.basename(path))
  70. rank = int(m.group(1)) if m else 0
  71. b = torch.load(path, map_location="cpu")
  72. rows = [i for i, r in enumerate(b["rows"]) if r["pos"] == int(pos)]
  73. if not rows:
  74. print(f"{spec}: no row at pos {pos}")
  75. continue
  76. i = rows[-1]
  77. cps = b["cps"]
  78. layers = [int(x) for x in args.layers.split(",") if x] or sorted(
  79. int(k[1:].split(".")[0]) for k in cps if k.endswith(".kda.out")
  80. )
  81. other = torch.load(args.state_vs, map_location="cpu") if args.state_vs else None
  82. print(f"\n== {os.path.basename(path)} pos {pos} rank {rank} (row {b['rows'][i]}) ==")
  83. print(f"{'layer':6s} {'conv':22s} {'out vs ref':24s} {'rec_after vs ref':24s} layouts |out|max")
  84. for L in layers:
  85. g = lambda n: cps[f"L{L}.kda.{n}"][i].to(dev)
  86. qkv, g1, beta, out = g("qkv").float(), g("g1").float(), g("beta").float(), g("out").float()
  87. cb, ca, rb, ra = g("conv_before").float(), g("conv_after").float(), g("rec_before").float(), g("rec_after").float()
  88. H = beta.numel()
  89. tp = H_all // H
  90. P = H * D # local projection size
  91. assert qkv.numel() == 3 * P, (qkv.shape, P)
  92. base = f"{PREFIX}{L}.self_attn."
  93. if not ck.has(base + "q_conv1d.weight"):
  94. base = f"{PREFIX}{L}.self_attn.linear_attn."
  95. ws = []
  96. for p in ("q", "k", "v"):
  97. w = ck.get(f"{base}{p}_conv1d.weight").float().reshape(H_all * D, W)
  98. ws.append(w[rank * P : (rank + 1) * P])
  99. cw = torch.cat(ws, 0) # [3P, W]
  100. a_log = ck.get(base + "A_log").float().reshape(-1)[rank * H : (rank + 1) * H]
  101. dt_bias = ck.get(base + "dt_bias").float().reshape(-1)[rank * P : (rank + 1) * P].view(H, D)
  102. # --- short conv, both state layouts ---
  103. best = None
  104. for layout in ("DS", "SD"):
  105. st = cb.view(3 * P, W - 1) if layout == "DS" else cb.view(W - 1, 3 * P).T
  106. win = torch.cat([st, qkv[:, None]], 1) # [3P, W]
  107. y = torch.nn.functional.silu((win * cw).sum(1))
  108. new_st = win[:, 1:]
  109. ca_v = ca.view(3 * P, W - 1) if layout == "DS" else ca.view(W - 1, 3 * P).T
  110. e = float((new_st - ca_v).abs().max())
  111. if best is None or e < best[1]:
  112. best = (layout, e, y)
  113. conv_layout, conv_err, y = best
  114. q, k, v = y.split(P)
  115. q, k, v, g1r = q.view(H, D), k.view(H, D), v.view(H, D), g1.view(H, D)
  116. # --- recurrent step, both state layouts ---
  117. res = None
  118. for hl in ("HVK", "HKV"):
  119. h0 = rb.view(H, D, D)
  120. if hl == "HKV":
  121. h0 = h0.transpose(1, 2)
  122. o, h1 = step_ref(q, k, v, g1r, beta, a_log, dt_bias, h0, lb, D)
  123. h1_cmp = h1 if hl == "HVK" else h1.transpose(1, 2)
  124. e_o = float((o.reshape(-1) - out).norm() / (out.norm() + 1e-12))
  125. if res is None or e_o < res[0]:
  126. res = (e_o, hl, o, h1_cmp)
  127. e_o, hl, o, h1 = res
  128. sv = ""
  129. if other is not None:
  130. st = other.get("extra", {}).get(f"L{L}.kda.state_after_req")
  131. if st:
  132. oc, orc = next(iter(st.values()))
  133. sv = f" | vs {os.path.basename(args.state_vs)} state: conv {rel(ca, oc.reshape(-1).to(dev))} rec {rel(ra, orc.reshape(-1).to(dev))}"
  134. print(f"L{L:<5d} {conv_layout} state maxdiff {conv_err:.1e} {rel(out, o.reshape(-1)):24s} {rel(ra, h1.reshape(-1)):24s} {conv_layout}/{hl} {float(out.abs().max()):.3f}{sv}")
  135. if __name__ == "__main__":
  136. main()