Files
wehub-resource-sync a8262fc01e
docs / build (push) Waiting to run
docs / deploy (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:10:22 +08:00

88 lines
3.6 KiB
Python

"""
Evaluate any stage checkpoint on GSM8K (greedy) and optionally dump sample generations.
Use it to build the headline "GSM8K accuracy across stages" table:
for s in base_pretrained sft dpo ppo grpo; do
PYTHONPATH=. python scripts/eval_post_training.py --ckpt /ephemeral/ckpts/$s.pt \
--label $s --limit 200 --append /ephemeral/logs/stage_table.jsonl
done
PYTHONPATH=. python scripts/eval_post_training.py --table /ephemeral/logs/stage_table.jsonl
Model dimensions are read from the checkpoint's stored ``cfg`` so you don't have to repeat
them. Reward checkpoints (which have a reward head, not an LM head only) still load because
we keep just the backbone keys for generation.
"""
from __future__ import annotations
import argparse
import json
import os
import torch
from src.models.transformer import Transformer
from src.post_training.evaluation import gsm8k_accuracy, load_gsm8k_eval
def model_from_ckpt(ckpt_path: str, device: str, overrides: dict | None = None) -> Transformer:
ck = torch.load(ckpt_path, map_location="cpu", weights_only=False)
cfg = ck.get("cfg", {}) or {}
cfg = {**cfg, **(overrides or {})}
model = Transformer(
n_head=cfg.get("n_head", 16), n_embed=cfg.get("n_embed", 1024),
context_length=cfg.get("context_length", 1024), vocab_size=cfg.get("vocab_size", 50304),
N_BLOCKS=cfg.get("n_blocks", 24),
)
state = ck["model_state_dict"] if "model_state_dict" in ck else ck
state = {k.removeprefix("module.").removeprefix("transformer."): v for k, v in state.items()}
backbone_keys = set(model.state_dict().keys())
filtered = {k: v for k, v in state.items() if k in backbone_keys}
model.load_state_dict(filtered, strict=False)
return model.to(device).eval()
def print_table(path: str):
rows = [json.loads(l) for l in open(path) if l.strip()]
print(f"\n{'stage':<18}{'GSM8K acc':>10}{'n':>8}")
print("-" * 36)
for r in rows:
print(f"{r['label']:<18}{r['accuracy']*100:>9.1f}%{r['n']:>8}")
def main():
p = argparse.ArgumentParser()
p.add_argument("--ckpt")
p.add_argument("--label", default="model")
p.add_argument("--limit", type=int, default=200)
p.add_argument("--split", default="test")
p.add_argument("--max_new_tokens", type=int, default=300)
p.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
p.add_argument("--samples", type=int, default=3)
p.add_argument("--append", default=None, help="append the result row to this JSONL")
p.add_argument("--table", default=None, help="just print a stage table from this JSONL and exit")
args = p.parse_args()
if args.table:
print_table(args.table)
return
model = model_from_ckpt(args.ckpt, args.device)
qa = load_gsm8k_eval(args.split, limit=args.limit)
res = gsm8k_accuracy(model, qa, device=args.device, max_new_tokens=args.max_new_tokens,
greedy=True, return_samples=args.samples)
print(f"[{args.label}] GSM8K {args.split} accuracy: {res['accuracy']*100:.1f}% ({res['correct']}/{res['n']})")
for s in res["samples"]:
print(f"\n Q: {s['q'][:120]}\n gold={s['gold']} correct={s['correct']}\n A: {s['response'][:300]}")
if args.append:
os.makedirs(os.path.dirname(args.append) or ".", exist_ok=True)
with open(args.append, "a") as f:
f.write(json.dumps({"label": args.label, "accuracy": res["accuracy"],
"correct": res["correct"], "n": res["n"]}) + "\n")
print(f"\nappended -> {args.append}")
if __name__ == "__main__":
main()