Skip to content

Commit 258959f

Browse files
physicsrobclaude
andcommitted
scripts: exhaustive-grid verification; witnessed 3M/3M on simple n=3
exhaustive_examples slices the complete (op, a, b) grid arithmetically so each shard materializes only its own range; load/generate_answers split out of run() so mismatch triage (automatic unbatched re-run, separating model errors from batching artifacts) reuses the loaded model. modal_compile.exhaustive_remote runs one shard per B200, pulling the model from the published Hub repo — the artifact of record, not a build byproduct. Witnessed 2026-07-26: physicsrob/torchwright-calculator-simple- max-digits-3, every A op B with both operands in [0, 999] and op in {+,-,*} — 3,000,000/3,000,000 exact, 8 shards, ~25 min wall. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 086a2e7 commit 258959f

2 files changed

Lines changed: 113 additions & 11 deletions

File tree

modal_compile.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,62 @@ def accuracy_remote(
160160
return {"bundle": dirname, "n": n, "seed": seed, "mismatches": n_bad}
161161

162162

163+
@app.function(gpu="B200", cpu=8, memory=65536, timeout=7200)
164+
def exhaustive_remote(
165+
repo_id: str,
166+
shard: int,
167+
n_shards: int,
168+
max_value: int = 999,
169+
batch_size: int = 8192,
170+
) -> dict:
171+
"""One shard of the exhaustive grid, straight from the published Hub repo.
172+
173+
Every ``A op B`` with both operands in ``[0, max_value]`` and each
174+
op, split op-major across ``n_shards`` workers. A mismatch is
175+
immediately re-run unbatched to separate a model error from a
176+
batching/padding artifact before it lands in the report.
177+
"""
178+
from scripts.verify_bundle_accuracy import (
179+
exhaustive_examples,
180+
expected,
181+
generate_answers,
182+
load,
183+
)
184+
185+
total = 3 * (max_value + 1) ** 2
186+
start = shard * total // n_shards
187+
stop = (shard + 1) * total // n_shards
188+
model, tok = load(repo_id)
189+
results = generate_answers(
190+
model,
191+
tok,
192+
exhaustive_examples(start, stop, max_value=max_value),
193+
batch_size=batch_size,
194+
max_new_tokens=10,
195+
)
196+
mismatches = []
197+
for a, op, b, out in results:
198+
want = expected(a, op, b)
199+
if out != want:
200+
single = generate_answers(
201+
model, tok, [(a, op, b)], batch_size=1, max_new_tokens=10
202+
)[0][3]
203+
mismatches.append(
204+
{"expr": f"{a}{op}{b}", "batched": out, "single": single, "want": want}
205+
)
206+
print(
207+
f"[shard {shard}] MISMATCH {a}{op}{b}: batched={out!r} "
208+
f"single={single!r} expected={want}",
209+
flush=True,
210+
)
211+
print(
212+
f"[shard {shard}] SUMMARY {len(results) - len(mismatches)}/{len(results)} "
213+
f"correct ({start}..{stop})",
214+
flush=True,
215+
)
216+
return {"shard": shard, "n": len(results), "mismatches": mismatches}
217+
218+
163219
@app.function(cpu=4, memory=8192, timeout=7200, volumes={BUNDLE_ROOT: bundles})
164220
def card_remote(dirname: str) -> str:
165221
"""Regenerate a volume bundle's README without recompiling.

scripts/verify_bundle_accuracy.py

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@
2222

2323
import argparse
2424
from collections import Counter
25+
from typing import TYPE_CHECKING, Any, cast
2526

2627
import numpy as np
2728

29+
if TYPE_CHECKING:
30+
from transformers import PreTrainedModel
31+
from transformers.tokenization_utils_base import PreTrainedTokenizerBase
32+
2833
Example = tuple[int, str, int]
2934

3035

@@ -49,15 +54,28 @@ def expected(a: int, op: str, b: int) -> str:
4954
return str(f(a, b))
5055

5156

52-
def run(
53-
path: str,
54-
examples: list[Example],
55-
*,
56-
batch_size: int = 250,
57-
device: str = "cuda",
58-
max_new_tokens: int = 32,
59-
) -> list[tuple[int, str, int, str]]:
60-
"""Batched greedy decode; returns ``(a, op, b, model_answer)`` rows."""
57+
def exhaustive_examples(
58+
start: int, stop: int, max_value: int = 999, ops: str = "+-*"
59+
) -> list[Example]:
60+
"""Examples ``[start, stop)`` of the exhaustive ``(op, a, b)`` grid.
61+
62+
The grid is op-major then row-major: index
63+
``i = (op_idx * (max_value+1) + a) * (max_value+1) + b`` — computed
64+
arithmetically so a shard materializes only its own slice.
65+
"""
66+
m = max_value + 1
67+
out: list[Example] = []
68+
for i in range(start, stop):
69+
op_idx, rem = divmod(i, m * m)
70+
a, b = divmod(rem, m)
71+
out.append((a, ops[op_idx], b))
72+
return out
73+
74+
75+
def load(
76+
path: str, device: str = "cuda"
77+
) -> tuple["PreTrainedModel", "PreTrainedTokenizerBase"]:
78+
"""Load a bundle (local dir or Hub repo id) in strict fp32."""
6179
import torch
6280
from transformers import AutoModelForCausalLM, AutoTokenizer
6381

@@ -68,15 +86,28 @@ def run(
6886
tok.pad_token = tok.eos_token
6987
tok.padding_side = "left"
7088
model = AutoModelForCausalLM.from_pretrained(path, torch_dtype=torch.float32)
71-
model = model.eval().to(device)
89+
return cast("PreTrainedModel", model.eval().to(device)), tok
7290

91+
92+
def generate_answers(
93+
model: "PreTrainedModel",
94+
tok: "PreTrainedTokenizerBase",
95+
examples: list[Example],
96+
*,
97+
batch_size: int = 250,
98+
max_new_tokens: int = 32,
99+
) -> list[tuple[int, str, int, str]]:
100+
"""Batched greedy decode; returns ``(a, op, b, model_answer)`` rows."""
101+
import torch
102+
103+
device = model.device
73104
results: list[tuple[int, str, int, str]] = []
74105
with torch.no_grad():
75106
for start in range(0, len(examples), batch_size):
76107
chunk = examples[start : start + batch_size]
77108
prompts = [f"{a}{op}{b}\n" for a, op, b in chunk]
78109
enc = tok(prompts, return_tensors="pt", padding=True).to(device)
79-
g = model.generate(
110+
g = cast("Any", model).generate(
80111
**enc,
81112
max_new_tokens=max_new_tokens,
82113
do_sample=False,
@@ -93,6 +124,21 @@ def run(
93124
return results
94125

95126

127+
def run(
128+
path: str,
129+
examples: list[Example],
130+
*,
131+
batch_size: int = 250,
132+
device: str = "cuda",
133+
max_new_tokens: int = 32,
134+
) -> list[tuple[int, str, int, str]]:
135+
"""Load-and-generate convenience over :func:`load` / :func:`generate_answers`."""
136+
model, tok = load(path, device)
137+
return generate_answers(
138+
model, tok, examples, batch_size=batch_size, max_new_tokens=max_new_tokens
139+
)
140+
141+
96142
def report(results: list[tuple[int, str, int, str]]) -> int:
97143
"""Print the verdict breakdown; returns the mismatch count."""
98144
by_op: Counter[str] = Counter()

0 commit comments

Comments
 (0)