Skip to content

Commit c3a71fc

Browse files
unamedkrclaude
andcommitted
pillar1(R2): HF per-layer hidden state dump tool
tools/pillar1/hf_dump.py runs a prompt through Qwen3-0.6B FP32 (HF reference) and saves every post-layer hidden state + logits to npz. Structure captured for prompt "Hello" (1 token, dim=1024): emb — embedding output h0..h27 — output of each of 28 transformer layers logits — post-final-norm + lm_head [1, vocab=151936] tokens — input token IDs Top-5 logits on "Hello": "Answer" 8.139, "Question" 8.077 (matches R1 smoke test generation " Answer! I'm a bit confused..."). This is the ground-truth reference. R3 will add a matching dump to our engine and compute layer-by-layer diff to find the first divergence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent de2df31 commit c3a71fc

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

tools/pillar1/hf_dump.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
2+
"""HF Qwen3-0.6B per-layer hidden state dump.
3+
4+
Runs a fixed prompt through the reference model, captures the hidden
5+
state after the embedding and after each of the 28 transformer layers,
6+
and saves to an npz file for diffing against our engine's dump.
7+
8+
Usage:
9+
python hf_dump.py "Hello"
10+
-> writes hf_dump.npz with arrays emb, h0, h1, ..., h27, final_norm, logits
11+
"""
12+
import sys
13+
import numpy as np
14+
import torch
15+
from transformers import AutoModelForCausalLM, AutoTokenizer
16+
17+
MODEL = "Qwen/Qwen3-0.6B"
18+
PROMPT = sys.argv[1] if len(sys.argv) > 1 else "Hello"
19+
OUT = sys.argv[2] if len(sys.argv) > 2 else "tools/pillar1/hf_dump.npz"
20+
21+
print(f"prompt: {PROMPT!r}")
22+
23+
tok = AutoTokenizer.from_pretrained(MODEL)
24+
model = AutoModelForCausalLM.from_pretrained(MODEL, dtype=torch.float32, device_map="cpu")
25+
model.eval()
26+
27+
ids = tok.encode(PROMPT, return_tensors="pt")
28+
print(f"tokens: {ids.tolist()[0]}")
29+
30+
with torch.no_grad():
31+
out = model(ids, output_hidden_states=True, use_cache=False)
32+
33+
# out.hidden_states is a tuple of (n_layers+1) tensors, each [1, seq_len, dim]
34+
# [0] = after embedding (before any layer)
35+
# [i] = after layer (i-1)
36+
# final logits after final norm + lm_head: out.logits [1, seq_len, vocab]
37+
arrays = {}
38+
for i, h in enumerate(out.hidden_states):
39+
key = "emb" if i == 0 else f"h{i-1}"
40+
arrays[key] = h[0].cpu().float().numpy() # [seq_len, dim]
41+
print(f" {key}: shape={arrays[key].shape}, first8={arrays[key][-1, :8]}")
42+
43+
arrays["logits"] = out.logits[0].cpu().float().numpy() # [seq_len, vocab]
44+
print(f" logits: shape={arrays['logits'].shape}, top5_last=", end="")
45+
last = arrays["logits"][-1]
46+
top5 = np.argsort(-last)[:5]
47+
print({int(t): f'{last[t]:.3f} ({tok.decode([int(t)])!r})' for t in top5})
48+
49+
# Also capture tokens for comparison
50+
arrays["tokens"] = np.array(ids.tolist()[0], dtype=np.int64)
51+
52+
np.savez(OUT, **arrays)
53+
print(f"saved {OUT}")

0 commit comments

Comments
 (0)