DeepInfer/examples/neuron/compare_golden.py
whn09 8a5209ee65 Neuron basic mode — part 3: end-to-end check vs the official reference
- examples/neuron: tiny random V4.1 checkpoint in the real HF layout,
  pure-torch replacements for the official reference kernels, golden
  generation, a teacher-forced DeepInfer runner (LLM offline API) and a
  comparison tool. CPU mode: 25/27 greedy tokens match, worst logits
  cos 0.9985 / rel 0.056 against a reference self-noise floor of 0.032-0.060.
- model_loader: check parameters against the platform device, not is_cuda.
- deep_gemm paged MQA logits: do not dereference block-table slots past the
  context.
- bind_cpu: skip in Neuron CPU mode (1 core per NeuronCore share would
  serialize host compute).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:20:02 +08:00

107 lines
4.5 KiB
Python

"""Compare a candidate run against a reference golden (both in the reference_golden.py .pt format).
python -I examples/neuron/compare_golden.py ~/di/golden/medium.pt <candidate>.pt [--json out.json]
The candidate must have been run TEACHER-FORCED on the golden's tokens (prompt_ids, then golden `tokens[:-1]` as
decode inputs); otherwise decode-step logits are compared across different contexts. Required keys in the
candidate: prompt_ids, logits [steps+1, vocab]; optional: fed_tokens, hidden (list per forward call of
[n_layers, seqlen, hc_mult, dim]), indexer_topk.
Metrics per forward call (step 0 = prefill, which predicts the first token):
argmax match, rel = ||a-b|| / ||a|| of the fp32 logits, cosine, max |a-b|, top-5 overlap, KL(p_ref || p_cand),
and the golden top1-top2 margin (an argmax mismatch where the margin is below the noise floor is not a bug).
Per layer (if both have `hidden`): relative L2 error of the block output over all tokens, and of the worst token.
The reference is not bit-stable even against itself: FP8 activation quantization turns sub-ulp differences
(e.g. a different CPU thread count) into ~1e-2 relative per-layer differences, and top-k routing / index
selection compound them. golden/noise_floor_*.json records the reference-vs-reference spread; judge a
candidate against that floor, not against zero.
"""
from __future__ import annotations
import argparse
import json
import torch
def rel(a, b):
a, b = a.float(), b.float()
return ((a - b).norm() / a.norm().clamp_min(1e-30)).item()
def compare(ref: dict, cand: dict) -> dict:
assert list(ref["prompt_ids"]) == list(cand["prompt_ids"]), "different prompts"
if "fed_tokens" in cand:
exp = list(ref["tokens"][: len(cand["fed_tokens"])])
if list(cand["fed_tokens"]) != exp:
print("WARNING: candidate was not teacher-forced on the golden tokens; decode steps are not comparable")
la, lb = ref["logits"].float(), cand["logits"].float()
n = min(la.size(0), lb.size(0))
steps = []
for i in range(n):
a, b = la[i], lb[i]
top2 = a.topk(2).values
pa, lpb = a.log_softmax(-1), b.log_softmax(-1)
steps.append(
dict(
step=i,
argmax_ref=int(a.argmax()),
argmax_cand=int(b.argmax()),
match=bool(a.argmax() == b.argmax()),
margin_ref=round((top2[0] - top2[1]).item(), 5),
rel=rel(a, b),
cos=torch.nn.functional.cosine_similarity(a, b, dim=0).item(),
maxabs=(a - b).abs().max().item(),
top5_overlap=len(set(a.topk(5).indices.tolist()) & set(b.topk(5).indices.tolist())),
kl=(pa.exp() * (pa - lpb)).sum().item(),
)
)
layers = []
if ref.get("hidden") and cand.get("hidden"):
for i in range(min(len(ref["hidden"]), len(cand["hidden"]))):
ha, hb = ref["hidden"][i].float(), cand["hidden"][i].float()
if ha.shape != hb.shape:
layers.append(dict(step=i, error=f"shape {tuple(ha.shape)} vs {tuple(hb.shape)}"))
continue
dims = tuple(range(2, ha.dim()))
per_tok = (ha - hb).pow(2).sum(dims).sqrt() / ha.pow(2).sum(dims).sqrt().clamp_min(1e-30) # [L, s]
layers.append(
dict(
step=i,
rel=[rel(ha[l], hb[l]) for l in range(ha.size(0))],
worst_token_rel=per_tok.max(-1).values.tolist(),
)
)
return dict(steps=steps, layers=layers)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("ref")
ap.add_argument("cand")
ap.add_argument("--json", default=None)
args = ap.parse_args()
ref = torch.load(args.ref, weights_only=False)
cand = torch.load(args.cand, weights_only=False)
res = compare(ref, cand)
print(f"{'step':>4} {'match':>5} {'margin':>7} {'rel':>9} {'cos':>9} {'maxabs':>8} {'top5':>4} {'KL':>9}")
for s in res["steps"]:
print(
f"{s['step']:>4} {str(s['match']):>5} {s['margin_ref']:>7.3f} {s['rel']:>9.2e} {s['cos']:>9.6f} "
f"{s['maxabs']:>8.3f} {s['top5_overlap']:>4} {s['kl']:>9.2e}"
)
for L in res["layers"]:
if "error" in L:
print(f"hidden step {L['step']}: {L['error']}")
else:
print(f"hidden step {L['step']}: layer rel " + " ".join(f"{v:.1e}" for v in L["rel"]))
if args.json:
with open(args.json, "w") as fh:
json.dump(res, fh, indent=1)
if __name__ == "__main__":
main()