claude-opus-4-8
nano-gpt-data-curation — graded by the task's verifier against a measured baseline.
- backend
- modal
- variant
- claude claude-opus-4-8
- metric
- held-out multi-domain perplexity
- actions
- 36
- duration
- 39m
- recorded
- 2026-08-04
run iddclm-nano-curation__FwSgZtE
Everything claude-opus-4-8 did on this task, in order.
download JSON ↗encrypted chain-of-thought — recorded, not exposed
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false; python3 submission/curate.py 2>&1 | tail -8",
"description": "Run final curate.py to produce selection.json"
}Bash result
[70s] pool tokenized, vocab=1095178
[71s] target register models built (tech tok share 0.25)
[74s] scored
quality gate keeps 165106/182016; tech pool 36208
[74s] wrote 40000 ids (prose 34000, tech 6000) -> /workspace/submission/selection.json
[stdout]
[70s] pool tokenized, vocab=1095178
[71s] target register models built (tech tok share 0.25)
[74s] scored
quality gate keeps 165106/182016; tech pool 36208
[74s] wrote 40000 ids (prose 34000, tech 6000) -> /workspace/submission/selection.json
[metadata] {"noOutputExpected": false}curate.py reproduces the pipeline (34000 prose + 6000 tech). Let me verify it matches the winning checkpoint and confirm the perplexity.
args
{
"command": "cd /workspace; python3 -c \"\nimport json\na=json.load(open('submission/selection.json')); b=json.load(open('/tmp/sel_t15.json'))\nprint('lengths', len(a), len(b), 'identical:', a==b)\nprint('unique ids:', len(set(a)), 'in range:', min(a)>=0 and max(a)<182016)\n\"",
"description": "Verify reproducibility vs winner"
}Bash result
lengths 40000 40000 identical: True
unique ids: 40000 in range: True
[stdout]
lengths 40000 40000 identical: True
unique ids: 40000 in range: True
[metadata] {"noOutputExpected": false}Confirmed: curate.py exactly reproduces the winning 40000-id selection (337.0 dev PPL, 30% below the 485.4 random baseline). Now let me empirically substantiate the mechanism for claim.md — build per-register held-out sets and measure the register-decomposed loss. First, split the dev target by register:
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false; python3 <<'PY'\nimport numpy as np\nfrom transformers import AutoTokenizer\ntok=AutoTokenizer.from_pretrained(\"gpt2\")\nho=np.load(\"/workspace/data/multi_dev.npy\")\nsegs,prev=[],0\nfor j in np.where(ho==50256)[0]:\n segs.append(ho[prev:j+1]); prev=j+1 # keep EOS\nCODE=[\"{\",\"}\",\";\",\"def \",\"function\",\"return\",\"import \",\"var \",\"==\",\"() \"]\ntech,prose=[],[]\nfor s in segs:\n txt=tok.decode([int(x) for x in s])\n is_tech=(\"</\" in txt) or (\"<p\" in txt) or (\"/>\" in txt) or (sum(txt.count(k) for k in CODE)>4)\n (tech if is_tech else prose).append(s)\ntech=np.concatenate(tech).astype(np.uint16); prose=np.concatenate(prose).astype(np.uint16)\nnp.save(\"/tmp/ho_tech.npy\",tech); np.save(\"/tmp/ho_prose.npy\",prose)\nprint(\"tech heldout tokens\",len(tech),\"prose heldout tokens\",len(prose))\nPY",
"description": "Split dev target into per-register heldout files"
}Bash result
tech heldout tokens 321805 prose heldout tokens 677789
[stdout]
tech heldout tokens 321805 prose heldout tokens 677789
[metadata] {"noOutputExpected": false}Now train on pure-prose (0% tech) vs final (15% tech), each evaluated against both register heldouts — 4 runs. Let me set this up in the background:
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\npython3 /tmp/emit2.py 0 20 /tmp/sel_prose_only.json\npython3 pack_selection.py /tmp/sel_prose_only.json /tmp/tr_prose.npy 2>&1 | grep packed\npython3 pack_selection.py /workspace/submission/selection.json /tmp/tr_final.npy 2>&1 | grep packed\necho \"packed both\"",
"description": "Generate and pack prose-only and final training sets"
}Bash result
wrote /tmp/sel_prose_only.json tech_frac 0.0 40000
packed 12000000 tokens from 40000 ids -> /tmp/tr_prose.npy
packed 12000000 tokens from 40000 ids -> /tmp/tr_final.npy
packed both
[stdout]
wrote /tmp/sel_prose_only.json tech_frac 0.0 40000
packed 12000000 tokens from 40000 ids -> /tmp/tr_prose.npy
packed 12000000 tokens from 40000 ids -> /tmp/tr_final.npy
packed both
[metadata] {"noOutputExpected": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\nfor tr in prose final; do\n for ho in tech prose; do\n python3 train_nano.py --train_npy /tmp/tr_$tr.npy --heldout_npy /tmp/ho_$ho.npy \\\n --out_json /tmp/o.json --seed 1337 2>&1 | grep HELDOUT_PPL | sed \"s#^#TRAIN=$tr HO=$ho #\"\n done\ndone",
"description": "Run 4 register-decomposed training evals",
"run_in_background": true
}Bash result
Command running in background with ID: bt24hkgs7. Output is being written to: /tmp/claude-0/-workspace/b4066b14-4abe-4b47-8865-a86bf0e3f675/tasks/bt24hkgs7.output. You will be notified when it completes. To check interim output, use Read on that file path.
Session cwd remains /workspace; directory changes made by the backgrounded command do not apply to subsequent commands.
[metadata] {"noOutputExpected": false, "backgroundTaskId": "bt24hkgs7", "backgroundCwdHint": "Session cwd remains /workspace; directory changes made by the backgrounded command do not apply to subsequent commands."}While those 4 runs execute (~5 min), let me draft claim.md with the results I'll fill in.
args
{
"command": "sleep 180; cat /tmp/claude-0/-workspace/b4066b14-4abe-4b47-8865-a86bf0e3f675/tasks/bt24hkgs7.output 2>/dev/null | grep TRAIN; echo \"remaining: $(cat /workspace/.timer/remaining_secs)\"",
"description": "Check register-decomposition results"
}Bash result · error
Exit code 143
Command timed out after 2m 0s
[error] tool reported failureencrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 120; cat /tmp/claude-0/-workspace/b4066b14-4abe-4b47-8865-a86bf0e3f675/tasks/bt24hkgs7.output 2>/dev/null | grep TRAIN; echo \"remaining: $(cat /workspace/.timer/remaining_secs)\"",
"description": "Check register-decomposition results again"
}Bash result · error
Exit code 143
Command timed out after 2m 0s
[error] tool reported failureThe 4-run job completed. Let me read the results:
args
{
"command": "cat /tmp/claude-0/-workspace/b4066b14-4abe-4b47-8865-a86bf0e3f675/tasks/bt24hkgs7.output | grep TRAIN; echo \"remaining: $(cat /workspace/.timer/remaining_secs)\"",
"description": "Read final register-decomposition results"
}Bash result
TRAIN=prose HO=tech HELDOUT_PPL 1430.054 loss 7.2655 train_s 53 /tmp/tr_prose.npy
TRAIN=prose HO=prose HELDOUT_PPL 383.630 loss 5.9497 train_s 53 /tmp/tr_prose.npy
TRAIN=final HO=tech HELDOUT_PPL 218.771 loss 5.3880 train_s 53 /tmp/tr_final.npy
TRAIN=final HO=prose HELDOUT_PPL 413.605 loss 6.0249 train_s 53 /tmp/tr_final.npy
remaining: 3458
[stdout]
TRAIN=prose HO=tech HELDOUT_PPL 1430.054 loss 7.2655 train_s 53 /tmp/tr_prose.npy
TRAIN=prose HO=prose HELDOUT_PPL 383.630 loss 5.9497 train_s 53 /tmp/tr_prose.npy
TRAIN=final HO=tech HELDOUT_PPL 218.771 loss 5.3880 train_s 53 /tmp/tr_final.npy
TRAIN=final HO=prose HELDOUT_PPL 413.605 loss 6.0249 train_s 53 /tmp/tr_final.npy
remaining: 3458
[metadata] {"noOutputExpected": false}The register decomposition strongly confirms the mechanism. Adding 15% technical data:
- Technical heldout: loss 7.27→5.39 (PPL 1430→219, a 6.5× drop)
- Prose heldout: loss 5.95→6.02 (barely changes, tiny +0.075)
The gain is concentrated entirely in the technical register, exactly as predicted. Token-weighted these reproduce the 337 aggregate. Let me write claim.md:
args
{
"file_path": "/workspace/claim.md",
"content": "# Curation claim: register coverage beats mean similarity\n\n## Hypothesis\nUnder a tiny fixed token budget (12M ≈ 6% of the pool), the dominant lever for\nheld-out perplexity on a **broad, multi-register** target (equal parts\nWikipedia / general web prose / news / technical Q&A) is **register coverage**,\nnot per-document quality or aggregate similarity to the target.\n\nConcretely: ranking pool docs by how target-like they are on average and taking\nthe top ~11k docs to fill the budget **collapses onto clean prose** and trains on\n≈0% technical/Q&A text — because that register is only ~25% of the target, so the\nblended similarity model always prefers the prose majority. The model is then\ncatastrophically bad on the technical quarter of the target, and that dominates\naggregate perplexity. Deliberately reserving a slice of the budget for the\nunderrepresented technical register — even a smaller slice than its target share —\nwins.\n\n## Mechanism / predicted observable (not the final perplexity)\nThe selection criterion changes *which registers appear in training*, and the\neffect should be **concentrated in the technical register, not spread uniformly**.\nTwo observables were predicted before the confirming run:\n\n1. **Register composition of the budget-filling docs.** A blind top-k similarity\n selection puts almost no markup/code docs in-budget; a register-balanced one\n raises it to the reserved quota.\n - Measured: blind top-k → **51 / 11000** budget docs contain markup (0.5%);\n register-balanced → the reserved **15%**.\n\n2. **Register-decomposed held-out loss.** Splitting the dev target into technical\n vs prose segments and training on prose-only vs the 15%-tech mixture, the loss\n improvement must land almost entirely on the technical segments:\n\n | train set | tech-heldout loss (ppl) | prose-heldout loss (ppl) |\n |------------------|-------------------------|--------------------------|\n | prose only (0%) | 7.27 (1430) | 5.95 (384) |\n | 15% technical | **5.39 (219)** | 6.02 (414) |\n\n Adding 15% technical data cuts technical-segment perplexity **6.5×**\n (Δloss −1.88 nats) while prose loss barely moves (+0.07). The aggregate\n token-weighted loss (0.32·5.39 + 0.68·6.02 = 5.82 → **ppl 337**) reproduces the\n measured full-dev result, and its drop from the blind selection is almost\n entirely the technical term.\n\n## Falsification\n- If register coverage were *not* the mechanism, sweeping the technical quota\n 0→40% would leave aggregate perplexity flat or monotone. **Observed instead a\n clear U-shape** with a minimum at ~15%: 0%→441, 10%→348, **15%→337**, 20%→340,\n 30%→353, 40%→355. (Full-pipeline dev ppl; random baseline 485.)\n- If the mechanism were generic \"more/better data,\" the improvement in the\n decomposition above would be spread across *both* registers. It is not — prose\n loss actually rises slightly (budget reallocated away from it) while technical\n loss collapses. A uniform improvement across registers would falsify the claim.\n- The optimum (15%) sitting **below** the target's ~25% technical share is itself\n a prediction: raw-web technical docs are noisier/higher-entropy than the\n target's curated Q&A, so a little goes a long way. If the optimum matched or\n exceeded 25%, the \"noisy-raw-technical\" reasoning would be wrong.\n\n## Transfer\n- **Method.** \"Decompose the target into registers, model each, and reserve\n budget for hard/underrepresented registers\" should transfer to any multi-domain\n target and any budget — the tighter the budget, the larger the effect, because a\n missing register can't be amortized away.\n- **The 15% number is pool-specific**, not the principle. It reflects this pool's\n technical-doc noise; a cleaner technical source would push the optimum up toward\n the target's 25% share, a dirtier one down. The **direction** (reserve *some*,\n and typically *less than the raw target share*) is the transferable part.\n- **Scale.** A larger model with more capacity for the high-entropy technical\n register should tolerate (and want) a technical fraction closer to the true 25%,\n since it is less penalized by that register's noise — a testable prediction on\n the same data with a bigger student.\n"
}Write result
File created successfully at: /workspace/claim.md (file state is current in your context — no need to Read it back)
[metadata] {"type": "create", "filePath": "/workspace/claim.md", "content": "# Curation claim: register coverage beats mean similarity\n\n## Hypothesis\nUnder a tiny fixed token budget (12M ≈ 6% of the pool), the dominant lever for\nheld-out perplexity on a **broad, multi-register** target (equal parts\nWikipedia / general web prose / news / technical Q&A) is **register coverage**,\nnot per-document quality or aggregate similarity to the target.\n\nConcretely: ranking pool docs by how target-like they are on average and taking\nthe top ~11k docs to fill the budget **collapses onto clean prose** and trains on\n≈0% technical/Q&A text — because that register is only ~25% of the target, so the\nblended similarity model always prefers the prose majority. The model is then\ncatastrophically bad on the technical quarter of the target, and that dominates\naggregate perplexity. Deliberately reserving a slice of the budget for the\nunderrepresented technical register — even a smaller slice than its target share —\nwins.\n\n## Mechanism / predicted observable (not the final perplexity)\nThe selection criterion changes *which registers appear in training*, and the\neffect should be **concentrated in the technical register, not spread uniformly**.\nTwo observables were predicted before the confirming run:\n\n1. **Register composition of the budget-filling docs.** A blind top-k similarity\n selection puts almost no markup/code docs in-budget; a register-balanced one\n raises it to the reserved quota.\n - Measured: blind top-k → **51 / 11000** budget docs contain markup (0.5%);\n register-balanced → the reserved **15%**.\n\n2. **Register-decomposed held-out loss.** Splitting the dev target into technical\n vs prose segments and training on prose-only vs the 15%-tech mixture, the loss\n improvement must land almost entirely on the technical segments:\n\n | train set | tech-heldout loss (ppl) | prose-heldout loss (ppl) |\n |------------------|-------------------------|--------------------------|\n | prose only (0%) | 7.27 (1430) | 5.95 (384) |\n | 15% technical | **5.39 (219)** | 6.02 (414) |\n\n Adding 15% technical data cuts technical-segment perplexity **6.5×**\n (Δloss −1.88 nats) while prose loss barely moves (+0.07). The aggregate\n token-weighted loss (0.32·5.39 + 0.68·6.02 = 5.82 → **ppl 337**) reproduces the\n measured full-dev result, and its drop from the blind selection is almost\n entirely the technical term.\n\n## Falsification\n- If register coverage were *not* the mechanism, sweeping the technical quota\n 0→40% would leave aggregate perplexity flat or monotone. **Observed instead a\n clear U-shape** with a minimum at ~15%: 0%→441, 10%→348, **15%→337**, 20%→340,\n 30%→353, 40%→355. (Full-pipeline dev ppl; random baseline 485.)\n- If the mechanism were generic \"more/better data,\" the improvement in the\n decomposition above would be spread across *both* registers. It is not — prose\n loss actually rises slightly (budget reallocated away from it) while technical\n loss collapses. A uniform improvement across registers would falsify the claim.\n- The optimum (15%) sitting **below** the target's ~25% technical share is itself\n a prediction: raw-web technical docs are noisier/higher-entropy than the\n target's curated Q&A, so a little goes a long way. If the optimum matched or\n exceeded 25%, the \"noisy-raw-technical\" reasoning would be wrong.\n\n## Transfer\n- **Method.** \"Decompose the target into registers, model each, and reserve\n budget for hard/underrepresented registers\" should transfer to any multi-domain\n target and any budget — the tighter the budget, the larger the effect, because a\n missing register can't be amortized away.\n- **The 15% number is pool-specific**, not the principle. It reflects this pool's\n technical-doc noise; a cleaner technical source would push the optimum up toward\n the target's 25% share, a dirtier one down. The **direction** (reserve *some*,\n and typically *less than the raw target share*) is the transferable part.\n- **Scale.** A larger model with more capacity for the high-entropy technical\n register should tolerate (and want) a technical fraction closer to the true 25%,\n since it is less penalized by that register's noise — a testable prediction on\n the same data with a bigger student.\n", "structuredPatch": [], "originalFile": null, "userModified": false}All three deliverables are complete and validated. Let me use remaining time to check whether near-duplicate documents are wasting budget (a potential further gain), then finalize. Quick near-dup prevalence check on the in-budget docs:
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false; python3 <<'PY'\nimport json, numpy as np, zlib, re\nsel=json.load(open('submission/selection.json'))\ntext={}\nfor line in open('data/pool.jsonl'):\n r=json.loads(line); text[r['id']]=r['text']\n# tokenize in priority order until ~12M gpt2 tokens to find the in-budget prefix\nfrom transformers import AutoTokenizer\ntok=AutoTokenizer.from_pretrained('gpt2')\nWORD=re.compile(r\"[a-z][a-z']+\")\ntot=0; inbudget=[]\nfor i in sel:\n n=len(tok(text[i],add_special_tokens=False).input_ids)+1\n tot+=n; inbudget.append(i)\n if tot>=12_000_000: break\nprint(\"in-budget docs:\", len(inbudget))\n# near-dup proxy: 8-shingle set jaccard is costly; use cheap 5-word shingle minhash (bottom-4)\ndef sig(t):\n w=WORD.findall(t.lower())\n if len(w)<5: return None\n sh=set()\n for k in range(0,len(w)-4, 3):\n sh.add(zlib.crc32((' '.join(w[k:k+5])).encode()))\n if not sh: return None\n return tuple(sorted(sh)[:4])\nfrom collections import Counter\nsigs=Counter()\ndupdocs=0\nseen=set()\nfor i in inbudget:\n s=sig(text[i])\n if s is None: continue\n if s in seen: dupdocs+=1\n else: seen.add(s)\nprint(\"near-dup (shared bottom-4 5gram sig) in-budget:\", dupdocs, \"/\", len(inbudget))\n# also exact prefix-120 collisions\npref=Counter(text[i][:120] for i in inbudget)\nprint(\"docs sharing a 120-char prefix:\", sum(c-1 for c in pref.values() if c>1))\nPY",
"description": "Measure near-duplicate prevalence in-budget"
}Bash result
Token indices sequence length is longer than the specified maximum sequence length for this model (5414 > 1024). Running this sequence through the model will result in indexing errors
in-budget docs: 11990
near-dup (shared bottom-4 5gram sig) in-budget: 35 / 11990
docs sharing a 120-char prefix: 6
[stdout]
Token indices sequence length is longer than the specified maximum sequence length for this model (5414 > 1024). Running this sequence through the model will result in indexing errors
in-budget docs: 11990
near-dup (shared bottom-4 5gram sig) in-budget: 35 / 11990
docs sharing a 120-char prefix: 6
[metadata] {"noOutputExpected": false}Near-duplicates are negligible (35/11990 in-budget) — the exact-dedup filter already handled real duplicates, so no further dedup gain is available. The selection is clean. Let me do a final verification of all deliverables:
args
{
"command": "cd /workspace; ls -la submission/ claim.md; echo \"---\"; python3 -c \"\nimport json\ns=json.load(open('submission/selection.json'))\nprint('selection.json:', len(s), 'ids | unique:', len(set(s))==len(s), '| in-range:', min(s)>=0 and max(s)<182016, '| priority-ordered list of ints:', all(isinstance(x,int) for x in s))\n\"; echo \"remaining_secs: $(cat .timer/remaining_secs)\"",
"description": "Final deliverable verification"
}Bash result
-rw-r--r-- 1 root root 4312 Aug 4 04:39 claim.md
submission/:
total 284
drwxr-xr-x 1 root root 120 Aug 4 04:31 .
drwxr-xr-x 1 root root 180 Aug 4 04:39 ..
-rw-r--r-- 1 root root 7257 Aug 4 04:31 curate.py
-rw-r--r-- 1 root root 282998 Aug 4 04:32 selection.json
---
selection.json: 40000 ids | unique: True | in-range: True | priority-ordered list of ints: True
remaining_secs: 3338
[stdout]
-rw-r--r-- 1 root root 4312 Aug 4 04:39 claim.md
submission/:
total 284
drwxr-xr-x 1 root root 120 Aug 4 04:31 .
drwxr-xr-x 1 root root 180 Aug 4 04:39 ..
-rw-r--r-- 1 root root 7257 Aug 4 04:31 curate.py
-rw-r--r-- 1 root root 282998 Aug 4 04:32 selection.json
---
selection.json: 40000 ids | unique: True | in-range: True | priority-ordered list of ints: True
remaining_secs: 3338
[metadata] {"noOutputExpected": false}All deliverables are complete and validated. Here's the summary.
Result
| Selection | Held-out dev PPL | vs. random |
|---|---|---|
| Random baseline | 485.4 | — |
| Blind top-k similarity (DSIR) | 441.7 | −9% |
| Register-balanced (final) | 337.0 | −30% |
What I did
Diagnosis. Decoding the disclosed target (multi_dev.npy) showed it's a broad mix — WikiText-style Wikipedia, news, general web prose, and StackOverflow-style technical Q&A (~25% of tokens). A standard DSIR n-gram importance-weighting selection scored well but collapsed onto clean prose: only 51/11000 budget-filling docs contained any markup/code. The technical quarter of the target was essentially untrained.
Fix — register-balanced curation (curate.py):
- Quality gate: length / word-count / alpha-ratio / mean-word-length + exact-dedup (drops 17k junk docs).
- DSIR domain model, split by register: build hashed unigram+bigram Naive-Bayes models of the target's technical vs prose segments against a pool background; score every doc by mean per-token log-likelihood ratio.
- Reserve budget for the hard register: interleave prose and technical docs in priority order at a swept-optimal 15% technical quota.
Tuning. Sweeping the technical fraction gave a clean U-shape (0%→441, 15%→337, 30%→353, 40%→355), minimum at 15% — below the target's 25% share because raw-web technical docs are noisier than curated Q&A.
Mechanism, verified (in claim.md). Training prose-only vs 15%-technical and evaluating on per-register held-out splits: adding 15% technical data cut technical-segment perplexity 6.5× (1430→219) while prose barely moved (384→414). The improvement is concentrated entirely in the underrepresented register — confirming the predicted observable and satisfying the falsification test.
Deliverables: /workspace/submission/selection.json (40k valid, unique, priority-ordered ids), /workspace/submission/curate.py (self-contained, reproduces the selection exactly), and /workspace/claim.md. ~3300s of budget remain unused.