Quazim0t0's picture
Byrne-15M-Looped: inference package, scores, safetensors for leaderboard
be40882 verified
Raw
History Blame Contribute Delete
17.8 kB
"""
run_eval.py -- unified benchmark harness reproducing the Escarda-family model-card
suite for BOTH model families in one script:
* SpikeWhale V2 (config.SpikeWhaleConfig / model_v2.SpikeWhaleLM) -> Byrne-15M
* Quazimoto (model.QuazimotoLM via the repo's own generate.load_model)
-> Positronic-144M-Anti-DEG, Wheeler-DeWitt-62M (hybrid)
Metrics (identical definitions / scoring to Mark2/eval_*.py, so numbers line up
with the published cards):
WikiText-2 byte_ppl = exp( sum_token_NLL_nats / total_UTF8_bytes ) (down)
BLiMP acc = frac. of minimal pairs with logp(good) > logp(bad),
12 paradigms x 150, full-sentence sum-logprob w/ <bos> (up)
MC suite = argmax continuation log-likelihood; acc (raw sum) and
acc_norm (sum / UTF-8 bytes). Winogrande/BoolQ report acc only.
ArithMark-3.0 = HellaSwag-format MC arithmetic; acc + acc_norm, n & chance.
*_stderr = sqrt(p(1-p)/n) (binomial).
Usage:
python run_eval.py --family quazimoto --code_dir <repo> --ckpt <ckpt.pt> --name Positronic-144M
python run_eval.py --family spikewhale --code_dir <Mark2> --ckpt checkpoints/best --name Byrne-15M-Looped
"""
import os, sys, math, json, ast, argparse
from collections import defaultdict
import datasets # noqa: F401 (import pyarrow/datasets before torch/CUDA on Windows)
import torch
import torch.nn.functional as F
ARITHMARK_REPO = "AxiomicLabs/ArithMark-3.0"
# --------------------------------------------------------------------------- #
# loading -- family-specific, normalized to a uniform (call -> .logits) shape
# --------------------------------------------------------------------------- #
class _LogitsOut:
__slots__ = ("logits",)
def __init__(self, logits):
self.logits = logits
class QuazWrap:
"""Wrap a QuazimotoLM so it looks like an HF model: model(x).logits.
Quazimoto's forward is model(idx)->(logits, loss, aux); positional idx only."""
def __init__(self, model):
self.m = model
def __call__(self, input_ids=None, use_cache=False, **kw):
out = self.m(input_ids) # (logits, loss, aux)
return _LogitsOut(out[0])
def load_tokenizer(tok_path):
cand = tok_path if tok_path.endswith(".json") else os.path.join(tok_path, "tokenizer.json")
from spike_tokenizer import SpikeTokenizer
return SpikeTokenizer(vocab_file=cand)
def load_family(family, code_dir, ckpt, device):
"""Returns (model_callable, tokenizer, block_size, meta_str)."""
sys.path.insert(0, code_dir)
tok = load_tokenizer(os.path.join(code_dir, "tokenizer.json"))
if family == "spikewhale":
from config import SpikeWhaleConfig
from model_v2 import SpikeWhaleLM
if os.path.isdir(ckpt):
# Do NOT use from_pretrained: transformers 5.x wraps cls(config) in a
# meta-device context, but SpikeWhale's __init__ calls .item() on real
# tensors (fractal RoPE) which fails on meta tensors. Build on the real
# device and load the safetensors weights directly.
from safetensors.torch import load_file
cfg = SpikeWhaleConfig.from_pretrained(ckpt)
model = SpikeWhaleLM(cfg).to(device).eval()
state = load_file(os.path.join(ckpt, "model.safetensors"), device=str(device))
missing, unexpected = model.load_state_dict(state, strict=False)
if missing: print(f" [warn] missing keys: {len(missing)} (e.g. {missing[:3]})")
if unexpected: print(f" [warn] unexpected keys: {len(unexpected)} (e.g. {unexpected[:3]})")
else:
c = torch.load(ckpt, map_location=device, weights_only=False)
cfg = SpikeWhaleConfig(**c["config"])
model = SpikeWhaleLM(cfg).to(device).eval()
model.load_state_dict(c["model_state"], strict=False)
block = getattr(cfg, "max_position_embeddings", 1024)
return model, tok, block, f"spikewhale block={block}"
elif family == "quazimoto":
# use the repo's OWN loader: handles morphable/triattn hybrid rebuild
from generate import load_model as q_load
model, cfg = q_load(ckpt, device)
# fully stateless / reproducible: no online ring-memory writes
if hasattr(model, "reset_ring_memory"):
model.reset_ring_memory()
if hasattr(model, "set_ring_memory_writing"):
model.set_ring_memory_writing(False)
block = getattr(cfg, "block_size", 1024)
return QuazWrap(model), tok, block, f"quazimoto block={block}"
raise ValueError(family)
# --------------------------------------------------------------------------- #
# scoring core (verbatim logic from Mark2/eval_mc_bench.continuation_logprob)
# --------------------------------------------------------------------------- #
@torch.no_grad()
def continuation_logprob(model, tok, context, continuation, device, max_len):
ctx_ids = tok.encode(context, add_special_tokens=False)
cont_ids = tok.encode(continuation, add_special_tokens=False)
if len(cont_ids) == 0:
return -1e9, 1, max(1, len(continuation))
full = ctx_ids + cont_ids
if len(full) > max_len:
full = full[len(full) - max_len:]
# keep continuation intact; if it alone exceeds max_len, clip its front
if len(cont_ids) > max_len:
cont_ids = cont_ids[len(cont_ids) - max_len:]
x = torch.tensor([full], dtype=torch.long, device=device)
logits = model(input_ids=x, use_cache=False).logits
logprobs = F.log_softmax(logits[:, :-1, :].float(), dim=-1)
targets = x[:, 1:]
tok_lp = logprobs.gather(-1, targets.unsqueeze(-1)).squeeze(-1)[0]
cont_lp = tok_lp[-len(cont_ids):]
return float(cont_lp.sum().item()), len(cont_ids), max(1, len(continuation))
def stderr(p, n):
return math.sqrt(p * (1.0 - p) / n) if n > 0 else 0.0
# --------------------------------------------------------------------------- #
# dataset iterators (verbatim from Mark2/eval_mc_bench.py)
# --------------------------------------------------------------------------- #
def iter_piqa(limit):
from datasets import load_dataset
ds = load_dataset("parquet",
data_files="hf://datasets/ybisk/piqa@refs/convert/parquet/plain_text/validation/*.parquet",
split="train")
for n, ex in enumerate(ds):
if limit and n >= limit: break
yield ex["goal"] + " ", [ex["sol1"], ex["sol2"]], int(ex["label"])
def iter_hellaswag(limit):
from datasets import load_dataset
ds = load_dataset("parquet",
data_files="hf://datasets/Rowan/hellaswag@refs/convert/parquet/default/validation/*.parquet",
split="train")
for n, ex in enumerate(ds):
if limit and n >= limit: break
ctx = (ex["activity_label"] + ": " + ex["ctx_a"] + " " + ex["ctx_b"]).strip() + " "
gold = int(ex["label"]) if ex["label"] != "" else 0
yield ctx, ex["endings"], gold
def _label_index(labels, key):
if key in labels: return labels.index(key)
alt = {"1": "A", "2": "B", "3": "C", "4": "D", "5": "E"}
return labels.index(alt.get(key, key))
def iter_arc(config, limit):
from datasets import load_dataset
ds = load_dataset("allenai/ai2_arc", config, split="test")
for n, ex in enumerate(ds):
if limit and n >= limit: break
labels = list(ex["choices"]["label"])
ctx = "Question: " + ex["question"].strip() + "\nAnswer:"
choices = [" " + t for t in ex["choices"]["text"]]
yield ctx, choices, _label_index(labels, ex["answerKey"])
def iter_winogrande(limit):
from datasets import load_dataset
ds = load_dataset("allenai/winogrande", "winogrande_xl", split="validation")
n = 0
for ex in ds:
s = ex["sentence"]
if "_" not in s: continue
prefix, suffix = s.split("_", 1)
yield prefix, [ex["option1"] + suffix, ex["option2"] + suffix], int(ex["answer"]) - 1
n += 1
if limit and n >= limit: break
def iter_openbookqa(limit):
from datasets import load_dataset
ds = load_dataset("allenai/openbookqa", "main", split="test")
for n, ex in enumerate(ds):
if limit and n >= limit: break
labels = list(ex["choices"]["label"])
ctx = "Question: " + ex["question_stem"].strip() + "\nAnswer:"
choices = [" " + t for t in ex["choices"]["text"]]
yield ctx, choices, _label_index(labels, ex["answerKey"])
def iter_boolq(limit):
from datasets import load_dataset
ds = load_dataset("google/boolq", split="validation")
for n, ex in enumerate(ds):
if limit and n >= limit: break
ctx = ex["passage"].strip() + "\nQuestion: " + ex["question"].strip() + "?\nAnswer:"
yield ctx, [" no", " yes"], int(bool(ex["answer"]))
TASKS = {
"arc_easy": (lambda lim: iter_arc("ARC-Easy", lim), True),
"arc_challenge": (lambda lim: iter_arc("ARC-Challenge", lim), True),
"hellaswag": (lambda lim: iter_hellaswag(lim), True),
"winogrande": (lambda lim: iter_winogrande(lim), False),
"piqa": (lambda lim: iter_piqa(lim), True),
"openbookqa": (lambda lim: iter_openbookqa(lim), True),
"boolq": (lambda lim: iter_boolq(lim), False),
}
MC_ORDER = ["arc_easy", "arc_challenge", "hellaswag", "winogrande", "piqa",
"openbookqa", "boolq"]
@torch.no_grad()
def run_mc_task(name, iterator, model, tok, device, max_len):
correct = correct_norm = total = 0
for ctx, choices, gold in iterator:
raw, norm = [], []
for c in choices:
lp, ntok, nchar = continuation_logprob(model, tok, ctx, c, device, max_len)
raw.append(lp)
norm.append(lp / max(1, len(c.encode("utf-8"))))
correct += int(max(range(len(raw)), key=lambda i: raw[i]) == gold)
correct_norm += int(max(range(len(norm)), key=lambda i: norm[i]) == gold)
total += 1
if total % 500 == 0:
print(f" [{name}] {total} acc={correct/total:.3f} acc_norm={correct_norm/total:.3f}", flush=True)
return correct / total, correct_norm / total, total
# --------------------------------------------------------------------------- #
# ArithMark-3.0 (HellaSwag-format arithmetic MC)
# --------------------------------------------------------------------------- #
def _parse_list(v):
return v if isinstance(v, list) else ast.literal_eval(v)
def _meta(ex):
m = ex.get("metadata")
if isinstance(m, dict): return m
try: return ast.literal_eval(m)
except Exception: return {}
def _difficulty(ex):
d = _meta(ex).get("difficulty")
if d: return d
parts = (ex.get("activity_label") or "").split("::")
return parts[2] if len(parts) >= 3 else "unknown"
@torch.no_grad()
def run_arithmark(model, tok, device, max_len, limit):
from datasets import load_dataset
ds = load_dataset(ARITHMARK_REPO, split="train")
correct = correct_norm = total = 0
by_diff = defaultdict(lambda: [0, 0])
for n, ex in enumerate(ds):
if limit and n >= limit: break
ctx = ex["ctx"]; choices = _parse_list(ex["endings"]); gold = int(ex["label"])
raw, norm = [], []
for c in choices:
lp, ntok, nchar = continuation_logprob(model, tok, ctx, c, device, max_len)
raw.append(lp); norm.append(lp / max(1, len(c.encode("utf-8"))))
ok = int(max(range(len(norm)), key=lambda i: norm[i]) == gold)
correct += int(max(range(len(raw)), key=lambda i: raw[i]) == gold)
correct_norm += ok
d = _difficulty(ex); by_diff[d][0] += ok; by_diff[d][1] += 1
total += 1
if total % 500 == 0:
print(f" [arithmark] {total} acc={correct/total:.3f} acc_norm={correct_norm/total:.3f}", flush=True)
return correct / total, correct_norm / total, total, dict(by_diff)
# --------------------------------------------------------------------------- #
# WikiText-2 byte_ppl + BLiMP (verbatim from Mark2/eval_extra.py)
# --------------------------------------------------------------------------- #
@torch.no_grad()
def wikitext2_byte_ppl(model, tok, device, max_chars, ctx, stride):
from datasets import load_dataset
ds = load_dataset("wikitext", "wikitext-2-raw-v1", split="test")
text = "".join(r["text"] for r in ds)[:max_chars]
ids = tok.encode(text, add_special_tokens=False)
bos = getattr(tok, "bos_token_id", None)
if bos is not None: ids = [bos] + ids
total_nll, n_scored, prev_end = 0.0, 0, 0
for begin in range(0, len(ids), stride):
end = min(begin + ctx, len(ids))
trg = end - prev_end
x = torch.tensor([ids[begin:end]], device=device)
logits = model(input_ids=x, use_cache=False).logits
lp = F.log_softmax(logits[:, :-1].float(), -1)
tok_lp = lp.gather(-1, x[:, 1:].unsqueeze(-1)).squeeze(-1)[0]
sel = tok_lp[-trg:] if 0 < trg <= tok_lp.numel() else tok_lp
total_nll += float(-sel.sum().item()); n_scored += sel.numel()
prev_end = end
if end == len(ids): break
n_bytes = max(1, len(text.encode("utf-8")))
return math.exp(total_nll / n_bytes), n_bytes, n_scored
@torch.no_grad()
def seq_logprob(model, tok, sentence, device):
ids = tok.encode(sentence, add_special_tokens=False)
bos = getattr(tok, "bos_token_id", None)
if bos is not None: ids = [bos] + ids
x = torch.tensor([ids], device=device)
logits = model(input_ids=x, use_cache=False).logits
lp = F.log_softmax(logits[:, :-1].float(), -1)
return float(lp.gather(-1, x[:, 1:].unsqueeze(-1)).squeeze(-1)[0].sum().item())
@torch.no_grad()
def blimp(model, tok, device, n_paradigms, limit):
from datasets import load_dataset, get_dataset_config_names
cfgs = get_dataset_config_names("nyu-mll/blimp")
step = max(1, len(cfgs) // n_paradigms)
chosen = cfgs[::step][:n_paradigms]
per, tot_c, tot_n = {}, 0, 0
for cfg in chosen:
ds = load_dataset("nyu-mll/blimp", cfg, split="train")
c = n = 0
for ex in ds:
if limit and n >= limit: break
g = seq_logprob(model, tok, ex["sentence_good"], device)
b = seq_logprob(model, tok, ex["sentence_bad"], device)
c += int(g > b); n += 1
per[cfg] = c / n; tot_c += c; tot_n += n
print(f" [blimp] {cfg:<42} {c/n:.3f} ({c}/{n})", flush=True)
return tot_c / tot_n, per
# --------------------------------------------------------------------------- #
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--family", required=True, choices=["quazimoto", "spikewhale"])
ap.add_argument("--code_dir", required=True)
ap.add_argument("--ckpt", required=True)
ap.add_argument("--name", required=True)
ap.add_argument("--out", required=True, help="output JSON path")
ap.add_argument("--limit", type=int, default=None, help="cap per MC task (debug)")
ap.add_argument("--arith-limit", type=int, default=None)
ap.add_argument("--wt-chars", type=int, default=300_000)
ap.add_argument("--blimp-paradigms", type=int, default=12)
ap.add_argument("--blimp-limit", type=int, default=150)
ap.add_argument("--skip", nargs="*", default=[], help="stages to skip: mc lm arithmark")
args = ap.parse_args()
device = "cuda" if torch.cuda.is_available() else "cpu"
model, tok, block, meta = load_family(args.family, args.code_dir, args.ckpt, device)
max_len = min(2048, block)
wt_ctx = min(1024, block)
print(f"\n===== {args.name} ({meta}, max_len={max_len}) =====", flush=True)
R = {"name": args.name, "family": args.family, "ckpt": args.ckpt,
"block_size": block, "arithmark": ARITHMARK_REPO, "results": {}}
if "lm" not in args.skip:
print("\n--- WikiText-2 byte_ppl ---", flush=True)
bppl, nb, ns = wikitext2_byte_ppl(model, tok, device, args.wt_chars, wt_ctx, wt_ctx // 2)
print(f" byte_ppl = {bppl:.4f} ({ns} tok / {nb} bytes)", flush=True)
print("\n--- BLiMP ---", flush=True)
bacc, bper = blimp(model, tok, device, args.blimp_paradigms, args.blimp_limit)
print(f" blimp_acc = {bacc:.4f}", flush=True)
R["results"]["wikitext2_byte_ppl"] = bppl
R["results"]["blimp_acc"] = bacc
R["results"]["blimp_per_paradigm"] = bper
if "mc" not in args.skip:
mc = {}
for name in MC_ORDER:
itfac, want_norm = TASKS[name]
print(f"\n--- {name} ---", flush=True)
acc, acc_norm, n = run_mc_task(name, itfac(args.limit), model, tok, device, max_len)
print(f"[{name}] n={n} acc={acc:.4f} acc_norm={acc_norm:.4f}", flush=True)
entry = {"acc": acc, "acc_stderr": stderr(acc, n), "n": n}
if want_norm:
entry["acc_norm"] = acc_norm
entry["acc_norm_stderr"] = stderr(acc_norm, n)
mc[name] = entry
R["results"]["mc"] = mc
if "arithmark" not in args.skip:
print(f"\n--- ArithMark-3.0 ---", flush=True)
a, an, n, bd = run_arithmark(model, tok, device, max_len, args.arith_limit)
print(f"[arithmark] n={n} acc={a:.4f} acc_norm={an:.4f}", flush=True)
R["results"]["arithmark"] = {"acc": a, "acc_stderr": stderr(a, n),
"acc_norm": an, "acc_norm_stderr": stderr(an, n),
"n": n, "chance": 0.25, "by_difficulty": bd}
os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True)
with open(args.out, "w", encoding="utf-8") as f:
json.dump(R, f, indent=2)
print(f"\nwrote {args.out}", flush=True)
if __name__ == "__main__":
main()