fsi-anomaly / eval /bench.py
FerrellSyntheticIntelligence's picture
backup all: 100 files (batch)
d83b47a verified
Raw
History Blame Contribute Delete
6.72 kB
"""Benchmark TinyLiquid: val perplexity, forensic probes, samples, speed.
Writes bench/metrics.json for the model card and prints a summary.
Usage:
.venv/bin/python eval/bench.py --ckpt ckpt/dpo --out bench/metrics.json
"""
import argparse
import json
import time
from pathlib import Path
import numpy as np
import torch
from model.config import TinyLiquidConfig, CONFIGS
from model.tiny_liquid import TinyLiquid
from model.utils import latest_ckpt
from data.tokenizer import load_tokenizer
PROBES = [
("Discrepancy", "Two accounts describe the same event. Account A: 'The meeting started at 9am and ended at 11am.' Account B: 'The meeting started at 9am and ran until noon.' Find discrepancies.", "end time"),
("Contradiction", "Account A: 'No officials were present.' Account B: 'An official arrived later.' What can you conclude?", "not necessarily a contradiction"),
("Overclaim", "Evaluate: 'Crime doubled last year because of the new policy.' Incidents rose 1,000 to 2,000 while reporting methods changed.", "overclaim"),
("Pattern", "What are the weak links in a theory claiming one actor caused three unrelated disasters?", "no direct evidence"),
("Verification", "Verify: 'The building was built in 2015 and never renovated.' Assessor record: built 2015, renovation permit 2019.", "false"),
("Timeline", "Witness saw a van at 3:30pm; outage reported 3:40pm; inspection 6pm; no report before 5pm. Reconstruct.", "gap"),
("History", "2019: 'officials rule out sabotage.' 2021 filing: a second investigation opened the same week. What changed?", "parallel investigation hidden"),
("Politics", "Statement: 'We balanced the budget without raising taxes.' Budget includes a reassessment raising collections 9%.", "misleading"),
("Source chain", "A claim rests on: company blog, a wire story repeating it, an analyst note quoting the wire. Rate the evidence.", "single chain"),
]
def load(args):
torch.set_num_threads(args.threads)
tok = load_tokenizer(args.tok)
ckpt = latest_ckpt(args.ckpt)
assert ckpt, f"no checkpoints in {args.ckpt}"
sd = torch.load(ckpt, map_location="cpu")
cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(),
**{k: v for k, v in sd["config"].items() if k != "vocab_size"})
model = TinyLiquid(cfg)
model.load_state_dict(sd["model"])
model.eval()
return tok, model, ckpt, cfg
@torch.no_grad()
def val_ppl(model, tok, val_bin, batches, batch, seq):
arr = np.fromfile(val_bin, dtype=np.uint16).astype(np.int64)
t = torch.from_numpy(arr)
n = (len(t) - 1) // seq
rng = np.random.RandomState(0)
total, count = 0.0, 0
for _ in range(batches):
s = int(rng.randint(0, n - batch))
idx = torch.arange(s * seq, (s + batch) * seq, dtype=torch.long)
buf = torch.stack([t[int(i): int(i) + seq] for i in idx])
x, y = buf[:, :-1], buf[:, 1:]
logits = model(x)
loss = torch.nn.functional.cross_entropy(
logits.view(-1, logits.size(-1)), y.reshape(-1))
total += loss.item() * y.numel()
count += y.numel()
return float(np.exp(total / count))
@torch.no_grad()
def probe_hits(model, tok):
hits = 0
for name, q, want in PROBES:
prompt = "<|analyst|><|user|>" + q + "<|assistant|>"
ids = tok.encode(prompt).ids
out = tok.decode(model.generate(tok, ids, persona_id=1, max_new=60,
temperature=0.4, top_k=40,
repetition_penalty=1.5,
no_repeat_ngram_size=4)[len(ids):]).lower()
words = want.split()
hit = any(w in out for w in words)
hits += int(hit)
return hits, len(PROBES)
@torch.no_grad()
def speed(model, tok, n_tokens=40):
ids = tok.encode("<|analyst|><|user|>Evaluate this claim: 'X caused Y.'<|assistant|>").ids
t0 = time.time()
model.generate(tok, ids, persona_id=1, max_new=n_tokens, temperature=0.6,
top_k=40, repetition_penalty=1.4, no_repeat_ngram_size=4)
dt = time.time() - t0
return n_tokens / dt
def samples(model, tok):
prompts = [
"<|analyst|><|user|>Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit.<|assistant|>",
"<|analyst|><|user|>What's the most common mistake you see in research?<|assistant|>",
"<|skeptic|><|user|>Attack this conclusion: 'Three failures in one week with vans nearby is deliberate.'<|assistant|>",
]
out = []
for p in prompts:
persona = 2 if p.startswith("<|skeptic|>") else 1
ids = tok.encode(p).ids
gen = tok.decode(model.generate(tok, ids, persona_id=persona, max_new=90,
temperature=0.6, top_k=40,
repetition_penalty=1.4,
no_repeat_ngram_size=4)[len(ids):]).strip()
out.append({"prompt": p.split("<|user|>")[1].split("<|assistant|>")[0],
"persona": "skeptic" if persona == 2 else "analyst",
"output": gen})
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--ckpt", default="ckpt/dpo")
ap.add_argument("--tok", default="data/tokenizer.json")
ap.add_argument("--val", default="data/valid.bin")
ap.add_argument("--out", default="bench/metrics.json")
ap.add_argument("--val-batches", type=int, default=10)
ap.add_argument("--batch", type=int, default=16)
ap.add_argument("--seq", type=int, default=256)
ap.add_argument("--threads", type=int, default=8)
args = ap.parse_args()
tok, model, ckpt, cfg = load(args)
params = sum(p.numel() for p in model.parameters())
print(f"benchmarking {ckpt} | params {params:,} | d_model {cfg.d_model} blocks {cfg.n_blocks}", flush=True)
ppl = val_ppl(model, tok, args.val, args.val_batches, args.batch, args.seq)
hits, total = probe_hits(model, tok)
tok_s = speed(model, tok)
smpls = samples(model, tok)
metrics = {
"checkpoint": str(ckpt),
"params": params,
"val_loss": round(float(np.log(ppl)), 4),
"val_ppl": round(ppl, 4),
"probe_hits": f"{hits}/{total}",
"probe_accuracy": round(hits / total, 3),
"gen_speed_tok_per_s": round(tok_s, 1),
"hardware": "8-core ARM, no GPU",
"samples": smpls,
}
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(metrics, indent=2), encoding="utf-8")
print(json.dumps(metrics, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()