Skip to content
nano-gpt-data-curation

claude-opus-5

nano-gpt-data-curation — graded by the task's verifier against a measured baseline.

backend
modal
variant
claude claude-opus-5
metric
held-out multi-domain perplexity
actions
57
duration
1h 16m
recorded
2026-08-03

run iddclm-nano-curation__AoiWXUF

Everything claude-opus-5 did on this task, in order.

download JSON ↗
claude-codeclaude-opus-556 turns22 narration57 action57 observation34 reasoning1 final
#51
narration

GPU sparse-matmul nondeterminism means draws vary run to run. Measuring the shipped file as produced:

toolBash
args
{
  "command": "TOKENIZERS_PARALLELISM=false python pack_selection.py submission/selection.json /tmp/fin3.npy 2>&1|grep packed; python train_nano.py --train_npy /tmp/fin3.npy --heldout_npy data/multi_dev.npy --out_json /tmp/fin3.json 2>&1|grep HELDOUT; cat .timer/remaining_secs"
}
Bash result
packed 12000000 tokens from 25000 ids -> /tmp/fin3.npy
HELDOUT_PPL 370.468  loss 5.9148  train_s 54  /tmp/fin3.npy
1535

[stdout]
packed 12000000 tokens from 25000 ids -> /tmp/fin3.npy
HELDOUT_PPL 370.468  loss 5.9148  train_s 54  /tmp/fin3.npy
1535
[metadata] {"noOutputExpected": false}
#52
narration

Two independent draws at 20% scored 362.0/362.2 vs 28% draws at 359/368/370 — the 20% setting is more reliable. Reverting and re-verifying.

toolBash
args
{
  "command": "python - <<'EOF'\ns=open('submission/curate.py').read()\ns=s.replace(\"TOP_FRAC = 0.28      # candidate pool = top 28% of the ranking\",\"TOP_FRAC = 0.20      # candidate pool = top 20% of the ranking\")\ns=s.replace(\"All knobs (top fraction 0.28,\",\"All knobs (top fraction 0.20,\")\nopen('submission/curate.py','w').write(s)\nEOF\nnohup bash -c 'python submission/curate.py && TOKENIZERS_PARALLELISM=false python pack_selection.py submission/selection.json /tmp/fin4.npy && python train_nano.py --train_npy /tmp/fin4.npy --heldout_npy data/multi_dev.npy --out_json /tmp/fin4.json' > /tmp/fin4.log 2>&1 &\nsleep 115; tail -2 /tmp/fin4.log"
}
Bash result
(Bash completed with no output)

[metadata] {"noOutputExpected": false}
#53
reasoning (redacted)

encrypted chain-of-thought — recorded, not exposed

toolBash
args
{
  "command": "sleep 115; tail -3 /tmp/fin4.log; cat .timer/remaining_secs"
}
Bash result
wrote 25000 ids -> /workspace/submission/selection.json; mean score 2.387 (pool mean -1.249)
Token indices sequence length is longer than the specified maximum sequence length for this model (1046 > 1024). Running this sequence through the model will result in indexing errors
1285

[stdout]
wrote 25000 ids -> /workspace/submission/selection.json; mean score 2.387 (pool mean -1.249)
Token indices sequence length is longer than the specified maximum sequence length for this model (1046 > 1024). Running this sequence through the model will result in indexing errors
1285
[metadata] {"noOutputExpected": false}
#54
reasoning (redacted)

encrypted chain-of-thought — recorded, not exposed

toolBash
args
{
  "command": "sleep 105; grep -E \"packed|HELDOUT\" /tmp/fin4.log; cat .timer/remaining_secs"
}
Bash result
packed 12000000 tokens from 25000 ids -> /tmp/fin4.npy
HELDOUT_PPL 368.434  loss 5.9093  train_s 54  /tmp/fin4.npy
1185

[stdout]
packed 12000000 tokens from 25000 ids -> /tmp/fin4.npy
HELDOUT_PPL 368.434  loss 5.9093  train_s 54  /tmp/fin4.npy
1185
[metadata] {"noOutputExpected": false}
#55
narration

Stopping the sweep — draw-to-draw variance (~±5) dominates the remaining knob differences. Finalizing docs.

toolBash
args
{
  "command": "python - <<'EOF'\ns=open('claim.md').read()\ns=s.replace(\"\"\"| whole-doc mean score + temperature sampling (T=1, top 20%) | 367.3 |\n| ... same but narrower/sharper (top 12%, T=0.6) | 376.0 |\n| ... same but broader (top 35%, T=1.0) | 370.4 |\n| **whole-doc 0.5·mean + 0.5·worst window + T=1 sampling from top 20%** | **362.0** |\"\"\",\n\"\"\"| whole-doc mean score + temperature sampling (T=1, top 20%) | 367.3 |\n| ... same but narrower/sharper (top 12%, T=0.6) | 376.0 |\n| ... same but broader (top 35%, T=1.0) | 370.4 |\n| **whole-doc 0.5·mean + 0.5·worst window + T=1 sampling (shipped rule)** | **362.0 / 362.2 / 368.4** (top 20%, three draws) |\n| same rule, top 28% slice | 359.1 / 367.9 / 370.5 (three draws) |\n| same rule, worst-window weight 0.8 (top 20%) | 373.4 |\n\nThe last three rows are the measured **draw-to-draw spread of the shipped rule**:\nre-running the identical criterion (the classifier fit is GPU-nondeterministic and\nthe selection is a random draw) moves dev perplexity by about ±5. That spread is\nlarger than the gap between the 20% and 28% slice settings, so those two are not\ndistinguishable with the evaluations available here; everything above 375 in the\ntable *is* distinguishable from the shipped rule. The shipped `selection.json` is\nthe verbatim output of `curate.py` and measures **368.4** dev ppl, against a\nrandom-selection baseline of **469.9** (−22%).\"\"\")\ns=s.replace(\"\"\"* The worst-window penalty is noise — falsified by re-running with a different\n  sampling seed and observing the 367.3 / 362.0 gap vanish (the gap is ~5 ppl;\n  seed-to-seed noise on this frozen recipe is roughly ±3, so this is the\n  weakest of the three claims and the one most worth re-testing).\"\"\",\n\"\"\"* The worst-window penalty is noise. This is the weakest of the three claims:\n  the mean-vs-mean+worst gap (367.3 → 362.0) is the same size as the ±5\n  draw-to-draw spread measured afterwards, so a single pair of runs does not\n  settle it. Proper test: 5+ draws per aggregation setting, compare\n  distributions. Pushing the weight to 0.8 clearly *hurts* (373.4), which does\n  establish that the aggregation weight matters — just not that 0.5 beats 0.0.\"\"\")\nopen('claim.md','w').write(s)\nEOF\npython -c \"\nimport json;s=json.load(open('/workspace/submission/selection.json'));ids={json.loads(l)['id'] for l in open('/workspace/data/pool.jsonl')}\nprint('ids',len(s),'unique',len(set(s))==len(s),'in-range',set(s)<=ids)\"; ls -la submission/ claim.md"
}
Bash result
ids 25000 unique True in-range True
-rw-r--r-- 1 root root 5820 Aug  4 00:37 claim.md

submission/:
total 180
drwxr-xr-x 1 root root     80 Aug  4 00:06 .
drwxr-xr-x 1 root root    200 Aug  4 00:05 ..
-rw-r--r-- 1 root root   6259 Aug  4 00:31 curate.py
-rw-r--r-- 1 root root 177529 Aug  4 00:34 selection.json

[stdout]
ids 25000 unique True in-range True
-rw-r--r-- 1 root root 5820 Aug  4 00:37 claim.md

submission/:
total 180
drwxr-xr-x 1 root root     80 Aug  4 00:06 .
drwxr-xr-x 1 root root    200 Aug  4 00:05 ..
-rw-r--r-- 1 root root   6259 Aug  4 00:31 curate.py
-rw-r--r-- 1 root root 177529 Aug  4 00:34 selection.json
[metadata] {"noOutputExpected": false}
#56
reasoning (redacted)

encrypted chain-of-thought — recorded, not exposed

toolBash
args
{
  "command": "python - <<'EOF'\ns=open('claim.md').read()\ns=s.replace(\"469.9 vs 362.0, a 23% reduction\",\"469.9 vs 368.4 for the shipped file, 362.0–368.4 across draws — a 22% reduction\")\ns=s.replace(\"\"\"* **Segment-level purity matters beyond average quality.** Adding the\n  worst-window term to an already whole-document score moves 367.3 → 362.0. If\n  quality were a document-level constant, the worst-window term would be\n  redundant and change nothing.\"\"\",\n\"\"\"* **Segment-level purity matters beyond average quality.** Scoring the whole\n  document instead of its first 3000 chars is a clear win (389.5 → 375.4 at\n  strict top-K). Weighting the worst window on top of that is directionally\n  positive but inside the draw noise (367.3 → 362.0, spread ±5), while\n  over-weighting it clearly hurts (0.8 → 373.4). If quality were a\n  document-level constant, none of these would move.\"\"\")\ns=s.replace(\"\"\"* **Diversity beats greed at fixed mean quality.** Sampling from the top 20%\"\"\",\n\"\"\"* **Diversity beats greed.** Sampling from the top 20%\"\"\")\nopen('claim.md','w').write(s)\nEOF\nhead -2 submission/curate.py; grep -n \"TOP_FRAC\\|MIN_WEIGHT\\|TEMP =\\|N_SELECT\" submission/curate.py | head -6; cat .timer/remaining_secs"
}
Bash result
"""Curate a 12M-token pretraining selection from a raw web pool.

40:MIN_WEIGHT = 0.5     # weight on the worst window (vs the mean window)
41:TOP_FRAC = 0.20      # candidate pool = top 20% of the ranking
42:TEMP = 1.0           # sampling temperature over scores
43:N_SELECT = 25000     # ids emitted (~2x the 12M-token budget)
120:        score[i:i+m] = ((1 - MIN_WEIGHT) * mean.astype(np.float32)
121:                        + MIN_WEIGHT * worst.astype(np.float32))
1145

[stdout]
"""Curate a 12M-token pretraining selection from a raw web pool.

40:MIN_WEIGHT = 0.5     # weight on the worst window (vs the mean window)
41:TOP_FRAC = 0.20      # candidate pool = top 20% of the ranking
42:TEMP = 1.0           # sampling temperature over scores
43:N_SELECT = 25000     # ids emitted (~2x the 12M-token budget)
120:        score[i:i+m] = ((1 - MIN_WEIGHT) * mean.astype(np.float32)
121:                        + MIN_WEIGHT * worst.astype(np.float32))
1145
[metadata] {"noOutputExpected": false}
final summary

All three deliverables are in place and verified end-to-end.

Result

SelectionDev PPL
Random baseline (do-nothing)469.9
Shipped selection.json368.4 (−22%)

Best draws of the same rule hit 359–362; draw-to-draw spread is ~±5, so I shipped the verbatim output of curate.py rather than cherry-picking a lucky draw, and said so in claim.md.

Method (submission/curate.py, one stated criterion)

  1. Target-likeness classifier — logistic regression over hashed word uni+bigrams (2^18 buckets, crc32 so it's seed-independent), positives = ~700-token chunks decoded from the disclosed dev target, negatives = 40k random 3000-char windows of pool docs. Fit full-batch on GPU. No labels, no external model.
  2. Whole-document scoring — score up to 4 evenly spaced 3000-char windows per doc; document score = 0.5·mean + 0.5·worst. The trainer eats the whole document, so one junk segment is penalized even when the opening looks clean.
  3. Temperature sampling — draw 25k docs without replacement from the top 20% of the ranking with p ∝ softmax(score), rather than strict top-K.

What the ablations showed (all measured with the frozen trainer)

  • Whole-document scoring beats first-3000-chars scoring: 389.5 → 375.4.
  • Sampling beats strict top-K despite lowering mean selection quality: 375.4 → 367.3; the knob is non-monotone (12% → 376.0, 20% → 367.3, 35% → 370.4).
  • Hand-written junk/length/dedup filters on top of the classifier made things worse (389.5 → 396.4) — they cut register coverage the classifier already handled.
  • Over-weighting the worst window (0.8) hurts: 373.4.

One thing to flag honestly: I initially split the dev target into quartiles assuming it was ordered by domain and trained per-domain classifiers — the quartiles weren't clean domains and that variant produced junk rankings, so I dropped it. A properly domain-balanced selection (cluster the dev chunks, balance per cluster) is the most promising untested direction, and I ran out of wall-clock before I could test it.

selection.json validity re-checked: 25,000 ids, all unique, all in range, packs to the full 12M tokens.