Buckets:
| #!/usr/bin/env python | |
| """A40 quick-bench for fast quality A/B of gemma-4 variants (transformers, batched). | |
| AIME 2024+2025 (n=60) — the one axis that showed signal, hard reasoning, ROBUST | |
| numeric scorer (integer exact-match, NO model-grader → dodges the inspect MATH bug). | |
| Runs entirely on the A40 (transformers 5.9 + torch 2.8cu128); capability is | |
| hardware/engine-invariant so it composes with the a10g baselines. ~10 min/variant. | |
| Usage: python3 a40_quickbench.py --model /path/or/hf-id [--n 60 --batch 16 --max-new 4096] | |
| """ | |
| from __future__ import annotations | |
| import argparse, json, re, time, torch | |
| from transformers import AutoTokenizer, AutoModelForCausalLM, LogitsProcessor | |
| from datasets import load_dataset | |
| class KeepsetMask(LogitsProcessor): | |
| """Replicate the cleanstack 128k head-prune at GENERATION time: mask every | |
| non-keepset token to -inf so it can never be emitted. Lets us isolate the | |
| head-prune effect on ANY base model (e.g. bf16 ± prune) — codex's clean | |
| causal separator, no re-quantization needed.""" | |
| def __init__(self, keep_ids, vocab_size): | |
| block = torch.ones(vocab_size, dtype=torch.bool) | |
| block[torch.tensor(sorted(set(int(i) for i in keep_ids)))] = False | |
| self.block = block | |
| def __call__(self, input_ids, scores): | |
| b = self.block.to(scores.device) | |
| if b.shape[0] != scores.shape[-1]: # pad/trim to actual logit width | |
| nb = torch.ones(scores.shape[-1], dtype=torch.bool, device=scores.device) | |
| nb[: b.shape[0]] = b[: scores.shape[-1]] if b.shape[0] >= scores.shape[-1] else b | |
| b = nb | |
| scores[:, b] = float("-inf") | |
| return scores | |
| def load_keepset(path): | |
| d = json.load(open(path)) | |
| if isinstance(d, dict): | |
| for k in ("keep", "ids", "keepset", "token_ids", "keep_ids"): | |
| if k in d: | |
| return d[k] | |
| # dict of {token: id} or {id: ...} | |
| return [int(x) for x in d.keys()] if all(str(x).lstrip("-").isdigit() for x in d.keys()) else list(d.values()) | |
| return d | |
| def extract_answer(text: str): | |
| """AIME answers are integers 0-999. Prefer \\boxed{...}, else last integer.""" | |
| boxed = re.findall(r"\\boxed\{\s*(-?\d+)\s*\}", text) | |
| if boxed: | |
| try: return int(boxed[-1]) % 1000 | |
| except ValueError: pass | |
| nums = re.findall(r"-?\d+", text.replace(",", "")) | |
| return (int(nums[-1]) % 1000) if nums else None | |
| AIME_SETS = { | |
| # original 60: AIME 2024 (I+II) + 2025 (I+II) | |
| "core": [("Maxwell-Jia/AIME_2024", "train", "Problem", "Answer"), | |
| ("math-ai/aime25", "test", "problem", "answer")], | |
| # ~120: AIMO validation (2022+2023+2024, 90) + 2025 (30). Drops Maxwell-Jia 2024 to | |
| # avoid duplicating the AIMO 2024 problems. Larger n → tighter CI for the paired test. | |
| "expanded": [("AI-MO/aimo-validation-aime", "train", "problem", "answer"), | |
| ("math-ai/aime25", "test", "problem", "answer")], | |
| # ~963: every AIME 1983-2024 (933) + 2025 (30). 8x n. NOTE: old years (pre-~2010) are | |
| # likely memorized → concordant pairs that add n but little paired POWER; the recent | |
| # years carry the discriminating signal. Still a strict superset, free to run. | |
| "full": [("di-zhang-fdu/AIME_1983_2024", "train", "Question", "Answer"), | |
| ("math-ai/aime25", "test", "problem", "answer")], | |
| } | |
| def load_aime(n: int, which: str = "core"): | |
| items = [] | |
| for repo, split, pk, ak in AIME_SETS[which]: | |
| ds = load_dataset(repo, split=split) | |
| for d in ds: | |
| try: # AIME answers are ints 0-999; skip malformed rows | |
| items.append((d[pk], int(str(d[ak]).strip()) % 1000)) | |
| except (ValueError, TypeError): | |
| continue | |
| return items if n <= 0 else items[:n] | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", required=True) | |
| ap.add_argument("--n", type=int, default=60) | |
| ap.add_argument("--batch", type=int, default=16) | |
| ap.add_argument("--max-new", type=int, default=4096) | |
| ap.add_argument("--seed", type=int, default=42) | |
| ap.add_argument("--label", default="") | |
| ap.add_argument("--aime-set", default="core", choices=list(AIME_SETS), help="core(60)|expanded(~120)") | |
| ap.add_argument("--dump", default="", help="write per-problem {idx,answer,pred,correct} JSON for paired/McNemar analysis") | |
| ap.add_argument("--check", action="store_true", help="load the AIME set, print count+samples+answer-extraction, exit (no model)") | |
| ap.add_argument("--keepset", default="", help="path to pck04_keepset.json → apply 128k head-prune mask") | |
| ap.add_argument("--greedy", action="store_true", help="deterministic decode (clean A/B isolation, no sampling noise)") | |
| ap.add_argument("--gptq", action="store_true", | |
| help="load a GPTQModel-saved checkpoint (honors mixed/dynamic bits; AutoModel's GPTQ path may not)") | |
| args = ap.parse_args() | |
| torch.manual_seed(args.seed) | |
| if args.check: # validate-first: confirm the AIME set loads + answer extraction works, no model | |
| items = load_aime(args.n, args.aime_set) | |
| print(f"[check] aime-set={args.aime_set} loaded n={len(items)}", flush=True) | |
| for j in (0, len(items) // 2, len(items) - 1): | |
| p, a = items[j] | |
| print(f"[check] idx={j} ans={a} prompt[:90]={p[:90]!r}", flush=True) | |
| return | |
| tok = AutoTokenizer.from_pretrained(args.model) | |
| if tok.pad_token_id is None: | |
| tok.pad_token = tok.eos_token | |
| tok.padding_side = "left" | |
| t0 = time.time() | |
| if args.gptq: | |
| # GPTQModel.load honors per-module `dynamic` bit configs; AutoModel's GPTQ loader | |
| # may silently ignore them. Wrapper proxies .generate()/.eval(); underlying HF | |
| # model is .model (for get_output_embeddings). | |
| from gptqmodel import GPTQModel | |
| model = GPTQModel.load(args.model) | |
| else: | |
| model = AutoModelForCausalLM.from_pretrained(args.model, dtype=torch.bfloat16, | |
| device_map="cuda", attn_implementation="sdpa") | |
| model.eval() | |
| print(f"[bench] loaded {model.__class__.__name__} in {time.time()-t0:.1f}s", flush=True) | |
| logits_processor = None | |
| if args.keepset: | |
| keep = load_keepset(args.keepset) | |
| vocab = model.get_output_embeddings().weight.shape[0] | |
| logits_processor = [KeepsetMask(keep, vocab)] | |
| print(f"[bench] keepset-mask ON: {len(set(int(i) for i in keep))} kept / {vocab} vocab", flush=True) | |
| items = load_aime(args.n, args.aime_set) | |
| per_problem = [] # paired-analysis record: one row per problem, in fixed order | |
| correct, total, t0 = 0, 0, time.time() | |
| for i in range(0, len(items), args.batch): | |
| chunk = items[i:i + args.batch] | |
| prompts = [tok.apply_chat_template([{"role": "user", "content": p}], | |
| enable_thinking=True, add_generation_prompt=True, tokenize=False) | |
| for p, _ in chunk] | |
| enc = tok(prompts, return_tensors="pt", padding=True, add_special_tokens=False).to("cuda") | |
| gen_kw = dict(max_new_tokens=args.max_new, pad_token_id=tok.pad_token_id, | |
| logits_processor=logits_processor) | |
| if args.greedy: | |
| gen_kw.update(do_sample=False) | |
| else: | |
| gen_kw.update(do_sample=True, temperature=1.0, top_p=0.95, top_k=64) | |
| with torch.no_grad(): | |
| out = model.generate(**enc, **gen_kw) | |
| gens = tok.batch_decode(out[:, enc.input_ids.shape[1]:], skip_special_tokens=True) | |
| for (_, ans), g in zip(chunk, gens): | |
| pred = extract_answer(g) | |
| ok = pred is not None and pred == ans % 1000 | |
| per_problem.append({"idx": total, "answer": ans % 1000, "pred": pred, "correct": int(ok)}) | |
| correct += int(ok); total += 1 | |
| print(f"[bench] {total}/{len(items)} running_acc={correct/total:.3f} " | |
| f"({time.time()-t0:.0f}s)", flush=True) | |
| if args.dump: | |
| json.dump({"label": args.label or args.model, "aime_set": args.aime_set, | |
| "greedy": bool(args.greedy), "per_problem": per_problem}, open(args.dump, "w")) | |
| print(f"[bench] dumped per-problem → {args.dump}", flush=True) | |
| acc = correct / total | |
| se = (acc * (1 - acc) / total) ** 0.5 | |
| print(f"=====QUICKBENCH=====", flush=True) | |
| print(f"model={args.label or args.model} AIME24+25 n={total} acc={acc:.4f} " | |
| f"stderr={se:.4f} wall={time.time()-t0:.0f}s", flush=True) | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 8.58 kB
- Xet hash:
- 25025bc792bd2ead861a53e5c5bd9686e654ac90829dcaac72d83627c5ffd8c1
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.