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
- 82
- duration
- 1h 1m
- recorded
- 2026-08-04
run iddclm-nano-curation__gSSiCuY
Everything claude-opus-4-8 did on this task, in order.
download JSON ↗Classifier selection: PPL 361.6. Now the random baseline for comparison.
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\necho \"===== EVAL: random baseline =====\"; time bash dev_eval.sh /tmp/random_sel.json 2>&1 | grep -E \"HELDOUT_PPL|packed\"\necho \"REMAIN:\"; cat .timer/remaining_secs",
"description": "Eval random baseline on dev"
}Bash result
===== EVAL: random baseline =====
packed 12000000 tokens from 25000 ids -> /tmp/dev_train.npy
HELDOUT_PPL 462.538 loss 6.1367 train_s 56 /tmp/dev_train.npy
real 1m42.542s
user 1m37.040s
sys 0m2.620s
REMAIN:
4389
[stdout]
===== EVAL: random baseline =====
packed 12000000 tokens from 25000 ids -> /tmp/dev_train.npy
HELDOUT_PPL 462.538 loss 6.1367 train_s 56 /tmp/dev_train.npy
real 1m42.542s
user 1m37.040s
sys 0m2.620s
REMAIN:
4389
[metadata] {"noOutputExpected": false}Random baseline 462.5 vs my classifier selection 361.6 — a 22% reduction. Good, but let me check register balance: the target is equal parts 4 registers, and my top-ranked docs looked news/encyclopedic-heavy. If technical Q&A/code is starved, the eval average suffers. Let me analyze the composition of the actually-used slice.
args
{
"command": "cd /workspace; python3 -c \"\nimport numpy as np, pickle, json, re\nids, texts = pickle.load(open('/tmp/pool_texts.pkl','rb'))\nids=np.array(ids); scores=np.load('/tmp/pool_scores.npy')\ntmap={int(i):t for i,t in zip(ids,texts)}\nsel=json.load(open('/workspace/submission/selection.json'))\n# reconstruct docs actually used within 12M tokens (approx chars/4, but use real-ish estimate 4.2)\ndef reg(t):\n tl=t\n if re.search(r'<p>|<pre>|<code>|stackoverflow|</p>', tl, re.I) or ('def ' in tl and '(' in tl) or tl.count('{')>3: return 'code_qa'\n if re.search(r'said|reuters|reported|on (mon|tues|wednes|thurs|fri|satur|sun)day|according to', tl, re.I): return 'news'\n return 'prose_enc'\ncum=0; used=[]\nfor i in sel:\n t=tmap[i]; cum+=len(t)/4.2; used.append(i)\n if cum>=12_000_000: break\nfrom collections import Counter\nc=Counter(reg(tmap[i]) for i in used)\nprint('docs used within 12M tokens:', len(used))\nprint('register mix:', dict(c))\nlens=np.array([len(tmap[i]) for i in used])\nprint('char len: median %d mean %d p90 %d'%(np.median(lens), lens.mean(), np.percentile(lens,90)))\n# how many docs in pool look like code/qa at all\nallc=Counter(reg(t) for t in texts)\nprint('POOL register mix:', dict(allc))\n\"; echo REMAIN:; cat .timer/remaining_secs",
"description": "Analyze register composition of used slice and pool"
}Bash result
docs used within 12M tokens: 21953
register mix: {'prose_enc': 12786, 'news': 9139, 'code_qa': 28}
char len: median 1238 mean 2295 p90 4165
POOL register mix: {'prose_enc': 133396, 'news': 44689, 'code_qa': 3931}
REMAIN:
4308
[stdout]
docs used within 12M tokens: 21953
register mix: {'prose_enc': 12786, 'news': 9139, 'code_qa': 28}
char len: median 1238 mean 2295 p90 4165
POOL register mix: {'prose_enc': 133396, 'news': 44689, 'code_qa': 3931}
REMAIN:
4308
[metadata] {"noOutputExpected": false}Critical finding: my selection has 28 code/QA docs but the target is ~25% technical Q&A. The model sees almost no code/HTML → huge perplexity on that quarter of the eval. The pool has 3931 code/QA docs. To minimize average perplexity on a known equal-parts mixture, I should match the training mix to the eval mix. Let me examine the code/QA docs' scores and token budget.
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false; python3 -c \"\nimport numpy as np, pickle, json, re\nfrom transformers import AutoTokenizer\nids, texts = pickle.load(open('/tmp/pool_texts.pkl','rb'))\nids=np.array(ids); scores=np.load('/tmp/pool_scores.npy')\n# strict code/QA detector: stackoverflow-style HTML or clear code\ndef is_qa(t):\n return bool(re.search(r'<p>|<pre>|<code>|</p>|</code>', t)) or (t.count('{')+t.count('}')>=6) or bool(re.search(r'\\n\\s*(def |class |import |function |public |#include|SELECT )', t))\nqa_mask=np.array([is_qa(t) for t in texts])\nprint('pool code/QA docs:', qa_mask.sum())\nqidx=np.where(qa_mask)[0]\nqs=scores[qidx]\nprint('their score: median %.2f p90 %.2f max %.2f'%(np.median(qs), np.percentile(qs,90), qs.max()))\nprint('non-QA score median %.2f'%np.median(scores[~qa_mask]))\n# rank of QA docs within full pool\norder=np.argsort(-scores); rankpos={int(j):r for r,j in enumerate(order)}\nqranks=np.array([rankpos[int(j)] for j in qidx])\nprint('QA docs in top 22000 (used slice):', (qranks<22000).sum())\nprint('QA docs in top 50000:', (qranks<50000).sum())\n# tokens available from QA docs (sample-estimate via chars/4)\nqchars=np.array([len(texts[j]) for j in qidx])\nprint('QA total est tokens (chars/4): %.1fM'%(qchars.sum()/4/1e6))\n# show top few QA docs\ntok=None\nfor j in qidx[np.argsort(-scores[qidx])[:3]]:\n print('--- QA score %.2f'%scores[j]); print(repr(texts[j][:300]))\n\"; echo REMAIN:; cat .timer/remaining_secs",
"description": "Analyze code/QA docs scores and token availability"
}Bash result
pool code/QA docs: 4743
their score: median -3.23 p90 -1.58 max 0.65
non-QA score median -2.19
QA docs in top 22000 (used slice): 58
QA docs in top 50000: 351
QA total est tokens (chars/4): 15.1M
--- QA score 0.65
"The reason this function doesn't escape double quotes is because double quotes are used with names (the equivalent of backticks in MySQL), as in table or column names, while single quotes are used for values.\nThis is important to remember, especially coming from another SQL implementation. It can ca"
--- QA score 0.51
'++ I\'m commenting mostly just to bump this excellent piece of advice.\nSince port is rarely important and I like to use this idiom in addition to running a traditional webserver on port 80, I\'d shorten it to use the default port 5000–\nplackup -L Shotgun -MPlack::App::WrapCGI -e "Plack::App::WrapCGI->'
--- QA score 0.50
'Structural Characterization of a Minimal Antibody against Human APOBEC3B.\nPublication Type:Journal Article\nSource:Viruses, Volume 13, Issue 4 (2021)\n<p>APOBEC3B (A3B) is one of seven human APOBEC3 DNA cytosine deaminases that restrict viral infections as part of the overall innate immune response, b'
REMAIN:
4258
[stdout]
pool code/QA docs: 4743
their score: median -3.23 p90 -1.58 max 0.65
non-QA score median -2.19
QA docs in top 22000 (used slice): 58
QA docs in top 50000: 351
QA total est tokens (chars/4): 15.1M
--- QA score 0.65
"The reason this function doesn't escape double quotes is because double quotes are used with names (the equivalent of backticks in MySQL), as in table or column names, while single quotes are used for values.\nThis is important to remember, especially coming from another SQL implementation. It can ca"
--- QA score 0.51
'++ I\'m commenting mostly just to bump this excellent piece of advice.\nSince port is rarely important and I like to use this idiom in addition to running a traditional webserver on port 80, I\'d shorten it to use the default port 5000–\nplackup -L Shotgun -MPlack::App::WrapCGI -e "Plack::App::WrapCGI->'
--- QA score 0.50
'Structural Characterization of a Minimal Antibody against Human APOBEC3B.\nPublication Type:Journal Article\nSource:Viruses, Volume 13, Issue 4 (2021)\n<p>APOBEC3B (A3B) is one of seven human APOBEC3 DNA cytosine deaminases that restrict viral infections as part of the overall innate immune response, b'
REMAIN:
4258
[metadata] {"noOutputExpected": false}Root cause found: the 4743 QA/code docs score low (classifier's junk-vs-target axis penalizes code/HTML surface), so only 58 reach the used slice — the model sees ~0% QA but the eval is ~25% QA. There are ~15M tokens of QA available. The fix: register-stratified selection that interleaves top-scored QA and non-QA docs to match the equal-parts target within the budget. Let me add this to curate.py.
args
{
"replace_all": false,
"file_path": "/workspace/curate.py",
"old_string": "ap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\na = ap.parse_args()",
"new_string": "ap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\nap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")\na = ap.parse_args()"
}Edit result
The file /workspace/curate.py has been updated successfully. (file state is current in your context — no need to Read it back)
[metadata] {"filePath": "/workspace/curate.py", "oldString": "ap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\na = ap.parse_args()", "newString": "ap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\nap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")\na = ap.parse_args()", "originalFile": "\"\"\"Curate a raw web pool for small-LM pretraining by TARGET-DOMAIN classification.\n\nCriterion (stated, reproducible):\n Score every pool document by how much it looks like the disclosed high-quality\n multi-domain target, using a bag-of-words logistic-regression domain classifier\n (target vs. random pool). Keep the highest-scoring documents (after light\n quality gates + near-dup removal) in priority order until the training budget\n is filled.\n\nPositives = the DEV target docs themselves, recovered by GPT-2-decoding\n/workspace/data/multi_dev.npy and de-normalizing the WikiText ` @-@ `/` @,@ `\nspacing artifacts so the classifier keys on register/quality, not surface\ntokenization. Negatives = a random sample of the raw pool (PU learning: most of\nthe pool is off-target, so the LR direction separates target-like prose from\ngeneric web crawl).\n\nImplemented with numpy + torch only (no sklearn). Deterministic vocab -> the\nselection is fully reproducible.\n\nUsage:\n python3 curate.py # full run -> submission/selection.json\n python3 curate.py --reuse # reuse cached texts + scores (instant policy tweaks)\n\"\"\"\nimport argparse, json, re, os, pickle, numpy as np, torch\n\nPOOL = \"/workspace/data/pool.jsonl\"\nDEV = \"/workspace/data/multi_dev.npy\"\nOUT = \"/workspace/submission/selection.json\"\nCACHE_TXT = \"/tmp/pool_texts.pkl\"\nCACHE_SCORE = \"/tmp/pool_scores.npy\"\nCACHE_IDS = \"/tmp/pool_ids.npy\"\nEOS = 50256\nWORD = re.compile(r\"[a-z0-9']+\")\nMAXW = 1000 # cap words/doc for featurization (focus on main content, bound cost)\n\nap = argparse.ArgumentParser()\nap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\na = ap.parse_args()\n\n# ---------- de-normalize WikiText-103 spacing so positives look like raw web ----------\ndef denorm(t):\n t = t.replace(\" @-@ \", \"-\").replace(\" @,@ \", \",\").replace(\" @.@ \", \".\")\n t = re.sub(r\"\\s+([,.;:!?%])\", r\"\\1\", t)\n t = re.sub(r\"\\(\\s+\", \"(\", t); t = re.sub(r\"\\s+\\)\", \")\", t)\n return t\n\n# ---------- featurization: word uni+bigrams -> column ids via vocab ----------\ndef doc_feats(t, vocab, add=False):\n ws = WORD.findall(t.lower())[:MAXW]\n cols = set()\n for w in ws:\n c = vocab.get(w)\n if c is None and add:\n c = vocab[w] = len(vocab)\n if c is not None: cols.add(c)\n for i in range(len(ws) - 1):\n bg = ws[i] + \" \" + ws[i+1]\n c = vocab.get(bg)\n if c is None and add:\n c = vocab[bg] = len(vocab)\n if c is not None: cols.add(c)\n return cols\n\ndef build_sparse(list_of_colsets, D, device):\n rows, cols = [], []\n for r, cs in enumerate(list_of_colsets):\n if not cs: continue\n rows.extend([r] * len(cs)); cols.extend(cs)\n idx = torch.tensor([rows, cols], dtype=torch.long, device=device)\n # L2-normalized binary values\n rowlen = np.bincount(np.array(rows), minlength=len(list_of_colsets)).astype(np.float32)\n rowlen[rowlen == 0] = 1.0\n vals = torch.tensor([1.0 / np.sqrt(rowlen[r]) for r in rows], dtype=torch.float32, device=device)\n return torch.sparse_coo_tensor(idx, vals, (len(list_of_colsets), D)).coalesce()\n\n# ---------- load pool texts (cache) ----------\nif a.reuse and os.path.exists(CACHE_TXT):\n print(\"loading cached pool texts...\"); ids, texts = pickle.load(open(CACHE_TXT, \"rb\"))\nelse:\n print(\"reading pool.jsonl ...\")\n ids, texts = [], []\n with open(POOL) as f:\n for line in f:\n r = json.loads(line); ids.append(r[\"id\"]); texts.append(r[\"text\"])\n pickle.dump((ids, texts), open(CACHE_TXT, \"wb\"))\nids = np.array(ids); print(f\"pool docs: {len(ids)}\")\n\n# ---------- classifier + scores (cache) ----------\nif a.reuse and os.path.exists(CACHE_SCORE):\n print(\"loading cached scores...\"); scores = np.load(CACHE_SCORE)\n assert np.array_equal(np.load(CACHE_IDS), ids)\nelse:\n dev = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n print(\"decoding dev target -> positives ...\")\n from transformers import AutoTokenizer\n arr = np.load(DEV); cut = np.where(arr == EOS)[0]\n tok = AutoTokenizer.from_pretrained(\"gpt2\")\n pos_docs, prev = [], 0\n for c in cut:\n seg = arr[prev:c]; prev = c + 1\n if len(seg) >= 16: pos_docs.append(denorm(tok.decode(seg)))\n print(f\"positives: {len(pos_docs)}\")\n\n rng = np.random.default_rng(0)\n neg_idx = rng.choice(len(texts), size=min(a.neg, len(texts)), replace=False)\n neg_docs = [texts[i] for i in neg_idx]\n\n print(\"building vocab + training features ...\")\n vocab = {}\n train_txt = pos_docs + neg_docs\n y = np.array([1.0]*len(pos_docs) + [0.0]*len(neg_docs), dtype=np.float32)\n colsets = [doc_feats(t, vocab, add=True) for t in train_txt]\n D = len(vocab); print(f\"vocab size: {D}\")\n Xtr = build_sparse(colsets, D, dev)\n yt = torch.tensor(y, device=dev)\n\n print(\"training logistic regression (torch, GPU) ...\")\n w = torch.zeros(D, device=dev, requires_grad=True)\n b = torch.zeros(1, device=dev, requires_grad=True)\n opt = torch.optim.Adam([w, b], lr=0.5)\n pos_w = torch.tensor([(y == 0).sum() / max(1,(y == 1).sum())], device=dev) # balance\n for it in range(300):\n opt.zero_grad()\n logits = torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1) + b\n loss = torch.nn.functional.binary_cross_entropy_with_logits(\n logits, yt, pos_weight=pos_w) + 1e-4 * (w*w).sum()\n loss.backward(); opt.step()\n with torch.no_grad():\n acc = (((torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1)+b) > 0).float() == yt).float().mean().item()\n print(f\"train acc: {acc:.3f} final loss {loss.item():.4f}\")\n\n print(\"scoring full pool ...\")\n scores = np.empty(len(texts), dtype=np.float32)\n wv = w.detach(); bv = b.detach()\n B = 20000\n for s in range(0, len(texts), B):\n cs = [doc_feats(t, vocab, add=False) for t in texts[s:s+B]]\n Xb = build_sparse(cs, D, dev)\n with torch.no_grad():\n scores[s:s+B] = (torch.sparse.mm(Xb, wv.unsqueeze(1)).squeeze(1)+bv).cpu().numpy()\n print(f\" scored {min(s+B,len(texts))}/{len(texts)}\", end=\"\\r\")\n print()\n np.save(CACHE_SCORE, scores); np.save(CACHE_IDS, ids)\n\n# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- rank + near-dup removal ----------\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nseen, sel, approx = set(), [], 0.0\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n if approx >= a.target_tokens: break\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens) -> {OUT}\")\n", "structuredPatch": [{"oldStart": 39, "oldLines": 6, "newStart": 39, "newLines": 8, "lines": [" ap.add_argument(\"--neg\", type=int, default=25_000)", " ap.add_argument(\"--min_chars\", type=int, default=200)", " ap.add_argument(\"--max_chars\", type=int, default=60_000)", "+ap.add_argument(\"--qa_frac\", type=float, default=0.28,", "+ help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")", " a = ap.parse_args()", " ", " # ---------- de-normalize WikiText-103 spacing so positives look like raw web ----------"]}], "userModified": false, "replaceAll": false}encrypted chain-of-thought — recorded, not exposed
args
{
"replace_all": false,
"file_path": "/workspace/curate.py",
"old_string": "# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- rank + near-dup removal ----------\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nseen, sel, approx = set(), [], 0.0\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n if approx >= a.target_tokens: break\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens) -> {OUT}\")",
"new_string": "# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- register tag: technical-QA/code vs. prose/news ----------\nQA_RE = re.compile(r\"<p>|<pre>|<code>|</p>|</code>\")\nCODE_RE = re.compile(r\"\\n\\s*(def |class |import |function |public |#include|SELECT )\")\ndef is_qa(t):\n return bool(QA_RE.search(t)) or (t.count(\"{\") + t.count(\"}\") >= 6) or bool(CODE_RE.search(t))\nqa_mask = np.array([is_qa(t) for t in texts])\n\n# ---------- stratified, dedup'd, score-ranked interleave to match equal-parts target ----------\n# The eval target is equal parts {wikipedia, web prose, news, technical-QA}. A single\n# domain classifier under-ranks the QA/code register (surface-dissimilar to prose), so we\n# guarantee its share by interleaving two score-sorted streams to hold the QA token fraction\n# near a.qa_frac across ANY prefix (the packer cuts the prefix at 12M tokens).\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nqa_stream, other_stream = [], []\nseen = set()\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n (qa_stream if qa_mask[j] else other_stream).append(j)\n\nqi = oi = 0\nsel, approx, qa_tok = [], 0.0, 0.0\nwhile approx < a.target_tokens and (qi < len(qa_stream) or oi < len(other_stream)):\n want_qa = (qa_tok < a.qa_frac * max(approx, 1.0)) and qi < len(qa_stream)\n if want_qa or oi >= len(other_stream):\n j = qa_stream[qi]; qi += 1; qa_tok += len(texts[j]) / 4.0\n else:\n j = other_stream[oi]; oi += 1\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens, QA≈{qa_tok/max(approx,1)*100:.0f}%) -> {OUT}\")"
}Edit result
The file /workspace/curate.py has been updated successfully. (file state is current in your context — no need to Read it back)
[metadata] {"filePath": "/workspace/curate.py", "oldString": "# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- rank + near-dup removal ----------\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nseen, sel, approx = set(), [], 0.0\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n if approx >= a.target_tokens: break\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens) -> {OUT}\")", "newString": "# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- register tag: technical-QA/code vs. prose/news ----------\nQA_RE = re.compile(r\"<p>|<pre>|<code>|</p>|</code>\")\nCODE_RE = re.compile(r\"\\n\\s*(def |class |import |function |public |#include|SELECT )\")\ndef is_qa(t):\n return bool(QA_RE.search(t)) or (t.count(\"{\") + t.count(\"}\") >= 6) or bool(CODE_RE.search(t))\nqa_mask = np.array([is_qa(t) for t in texts])\n\n# ---------- stratified, dedup'd, score-ranked interleave to match equal-parts target ----------\n# The eval target is equal parts {wikipedia, web prose, news, technical-QA}. A single\n# domain classifier under-ranks the QA/code register (surface-dissimilar to prose), so we\n# guarantee its share by interleaving two score-sorted streams to hold the QA token fraction\n# near a.qa_frac across ANY prefix (the packer cuts the prefix at 12M tokens).\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nqa_stream, other_stream = [], []\nseen = set()\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n (qa_stream if qa_mask[j] else other_stream).append(j)\n\nqi = oi = 0\nsel, approx, qa_tok = [], 0.0, 0.0\nwhile approx < a.target_tokens and (qi < len(qa_stream) or oi < len(other_stream)):\n want_qa = (qa_tok < a.qa_frac * max(approx, 1.0)) and qi < len(qa_stream)\n if want_qa or oi >= len(other_stream):\n j = qa_stream[qi]; qi += 1; qa_tok += len(texts[j]) / 4.0\n else:\n j = other_stream[oi]; oi += 1\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens, QA≈{qa_tok/max(approx,1)*100:.0f}%) -> {OUT}\")", "originalFile": "\"\"\"Curate a raw web pool for small-LM pretraining by TARGET-DOMAIN classification.\n\nCriterion (stated, reproducible):\n Score every pool document by how much it looks like the disclosed high-quality\n multi-domain target, using a bag-of-words logistic-regression domain classifier\n (target vs. random pool). Keep the highest-scoring documents (after light\n quality gates + near-dup removal) in priority order until the training budget\n is filled.\n\nPositives = the DEV target docs themselves, recovered by GPT-2-decoding\n/workspace/data/multi_dev.npy and de-normalizing the WikiText ` @-@ `/` @,@ `\nspacing artifacts so the classifier keys on register/quality, not surface\ntokenization. Negatives = a random sample of the raw pool (PU learning: most of\nthe pool is off-target, so the LR direction separates target-like prose from\ngeneric web crawl).\n\nImplemented with numpy + torch only (no sklearn). Deterministic vocab -> the\nselection is fully reproducible.\n\nUsage:\n python3 curate.py # full run -> submission/selection.json\n python3 curate.py --reuse # reuse cached texts + scores (instant policy tweaks)\n\"\"\"\nimport argparse, json, re, os, pickle, numpy as np, torch\n\nPOOL = \"/workspace/data/pool.jsonl\"\nDEV = \"/workspace/data/multi_dev.npy\"\nOUT = \"/workspace/submission/selection.json\"\nCACHE_TXT = \"/tmp/pool_texts.pkl\"\nCACHE_SCORE = \"/tmp/pool_scores.npy\"\nCACHE_IDS = \"/tmp/pool_ids.npy\"\nEOS = 50256\nWORD = re.compile(r\"[a-z0-9']+\")\nMAXW = 1000 # cap words/doc for featurization (focus on main content, bound cost)\n\nap = argparse.ArgumentParser()\nap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\nap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")\na = ap.parse_args()\n\n# ---------- de-normalize WikiText-103 spacing so positives look like raw web ----------\ndef denorm(t):\n t = t.replace(\" @-@ \", \"-\").replace(\" @,@ \", \",\").replace(\" @.@ \", \".\")\n t = re.sub(r\"\\s+([,.;:!?%])\", r\"\\1\", t)\n t = re.sub(r\"\\(\\s+\", \"(\", t); t = re.sub(r\"\\s+\\)\", \")\", t)\n return t\n\n# ---------- featurization: word uni+bigrams -> column ids via vocab ----------\ndef doc_feats(t, vocab, add=False):\n ws = WORD.findall(t.lower())[:MAXW]\n cols = set()\n for w in ws:\n c = vocab.get(w)\n if c is None and add:\n c = vocab[w] = len(vocab)\n if c is not None: cols.add(c)\n for i in range(len(ws) - 1):\n bg = ws[i] + \" \" + ws[i+1]\n c = vocab.get(bg)\n if c is None and add:\n c = vocab[bg] = len(vocab)\n if c is not None: cols.add(c)\n return cols\n\ndef build_sparse(list_of_colsets, D, device):\n rows, cols = [], []\n for r, cs in enumerate(list_of_colsets):\n if not cs: continue\n rows.extend([r] * len(cs)); cols.extend(cs)\n idx = torch.tensor([rows, cols], dtype=torch.long, device=device)\n # L2-normalized binary values\n rowlen = np.bincount(np.array(rows), minlength=len(list_of_colsets)).astype(np.float32)\n rowlen[rowlen == 0] = 1.0\n vals = torch.tensor([1.0 / np.sqrt(rowlen[r]) for r in rows], dtype=torch.float32, device=device)\n return torch.sparse_coo_tensor(idx, vals, (len(list_of_colsets), D)).coalesce()\n\n# ---------- load pool texts (cache) ----------\nif a.reuse and os.path.exists(CACHE_TXT):\n print(\"loading cached pool texts...\"); ids, texts = pickle.load(open(CACHE_TXT, \"rb\"))\nelse:\n print(\"reading pool.jsonl ...\")\n ids, texts = [], []\n with open(POOL) as f:\n for line in f:\n r = json.loads(line); ids.append(r[\"id\"]); texts.append(r[\"text\"])\n pickle.dump((ids, texts), open(CACHE_TXT, \"wb\"))\nids = np.array(ids); print(f\"pool docs: {len(ids)}\")\n\n# ---------- classifier + scores (cache) ----------\nif a.reuse and os.path.exists(CACHE_SCORE):\n print(\"loading cached scores...\"); scores = np.load(CACHE_SCORE)\n assert np.array_equal(np.load(CACHE_IDS), ids)\nelse:\n dev = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n print(\"decoding dev target -> positives ...\")\n from transformers import AutoTokenizer\n arr = np.load(DEV); cut = np.where(arr == EOS)[0]\n tok = AutoTokenizer.from_pretrained(\"gpt2\")\n pos_docs, prev = [], 0\n for c in cut:\n seg = arr[prev:c]; prev = c + 1\n if len(seg) >= 16: pos_docs.append(denorm(tok.decode(seg)))\n print(f\"positives: {len(pos_docs)}\")\n\n rng = np.random.default_rng(0)\n neg_idx = rng.choice(len(texts), size=min(a.neg, len(texts)), replace=False)\n neg_docs = [texts[i] for i in neg_idx]\n\n print(\"building vocab + training features ...\")\n vocab = {}\n train_txt = pos_docs + neg_docs\n y = np.array([1.0]*len(pos_docs) + [0.0]*len(neg_docs), dtype=np.float32)\n colsets = [doc_feats(t, vocab, add=True) for t in train_txt]\n D = len(vocab); print(f\"vocab size: {D}\")\n Xtr = build_sparse(colsets, D, dev)\n yt = torch.tensor(y, device=dev)\n\n print(\"training logistic regression (torch, GPU) ...\")\n w = torch.zeros(D, device=dev, requires_grad=True)\n b = torch.zeros(1, device=dev, requires_grad=True)\n opt = torch.optim.Adam([w, b], lr=0.5)\n pos_w = torch.tensor([(y == 0).sum() / max(1,(y == 1).sum())], device=dev) # balance\n for it in range(300):\n opt.zero_grad()\n logits = torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1) + b\n loss = torch.nn.functional.binary_cross_entropy_with_logits(\n logits, yt, pos_weight=pos_w) + 1e-4 * (w*w).sum()\n loss.backward(); opt.step()\n with torch.no_grad():\n acc = (((torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1)+b) > 0).float() == yt).float().mean().item()\n print(f\"train acc: {acc:.3f} final loss {loss.item():.4f}\")\n\n print(\"scoring full pool ...\")\n scores = np.empty(len(texts), dtype=np.float32)\n wv = w.detach(); bv = b.detach()\n B = 20000\n for s in range(0, len(texts), B):\n cs = [doc_feats(t, vocab, add=False) for t in texts[s:s+B]]\n Xb = build_sparse(cs, D, dev)\n with torch.no_grad():\n scores[s:s+B] = (torch.sparse.mm(Xb, wv.unsqueeze(1)).squeeze(1)+bv).cpu().numpy()\n print(f\" scored {min(s+B,len(texts))}/{len(texts)}\", end=\"\\r\")\n print()\n np.save(CACHE_SCORE, scores); np.save(CACHE_IDS, ids)\n\n# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- rank + near-dup removal ----------\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nseen, sel, approx = set(), [], 0.0\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n if approx >= a.target_tokens: break\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens) -> {OUT}\")\n", "structuredPatch": [{"oldStart": 153, "oldLines": 18, "newStart": 153, "newLines": 39, "lines": [" char_len = np.array([len(t) for t in texts])", " gate = (char_len >= a.min_chars) & (char_len <= a.max_chars)", " ", "-# ---------- rank + near-dup removal ----------", "+# ---------- register tag: technical-QA/code vs. prose/news ----------", "+QA_RE = re.compile(r\"<p>|<pre>|<code>|</p>|</code>\")", "+CODE_RE = re.compile(r\"\\n\\s*(def |class |import |function |public |#include|SELECT )\")", "+def is_qa(t):", "+ return bool(QA_RE.search(t)) or (t.count(\"{\") + t.count(\"}\") >= 6) or bool(CODE_RE.search(t))", "+qa_mask = np.array([is_qa(t) for t in texts])", "+", "+# ---------- stratified, dedup'd, score-ranked interleave to match equal-parts target ----------", "+# The eval target is equal parts {wikipedia, web prose, news, technical-QA}. A single", "+# domain classifier under-ranks the QA/code register (surface-dissimilar to prose), so we", "+# guarantee its share by interleaving two score-sorted streams to hold the QA token fraction", "+# near a.qa_frac across ANY prefix (the packer cuts the prefix at 12M tokens).", " def sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]", " order = np.argsort(-scores)", "-seen, sel, approx = set(), [], 0.0", "+qa_stream, other_stream = [], []", "+seen = set()", " for j in order:", " if not gate[j]: continue", " k = sig(texts[j])", " if k in seen: continue", " seen.add(k)", "+ (qa_stream if qa_mask[j] else other_stream).append(j)", "+", "+qi = oi = 0", "+sel, approx, qa_tok = [], 0.0, 0.0", "+while approx < a.target_tokens and (qi < len(qa_stream) or oi < len(other_stream)):", "+ want_qa = (qa_tok < a.qa_frac * max(approx, 1.0)) and qi < len(qa_stream)", "+ if want_qa or oi >= len(other_stream):", "+ j = qa_stream[qi]; qi += 1; qa_tok += len(texts[j]) / 4.0", "+ else:", "+ j = other_stream[oi]; oi += 1", " sel.append(int(ids[j])); approx += len(texts[j]) / 4.0", "- if approx >= a.target_tokens: break", " ", " os.makedirs(os.path.dirname(OUT), exist_ok=True)", " json.dump(sel, open(OUT, \"w\"))", "-print(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens) -> {OUT}\")", "+print(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens, QA≈{qa_tok/max(approx,1)*100:.0f}%) -> {OUT}\")"]}], "userModified": false, "replaceAll": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\ncp submission/selection.json /tmp/sel_noqa.json # keep the prose-only version\npython3 curate.py --reuse --qa_frac 0.28 2>&1 | tail -6\necho \"=== eval balanced (28% QA) ===\"; time bash dev_eval.sh /workspace/submission/selection.json 2>&1 | grep -E \"HELDOUT_PPL|packed\"\necho REMAIN:; cat .timer/remaining_secs",
"description": "Regenerate balanced selection and eval"
}Bash result
Command did not complete within its 120s timeout and was moved to the background (ID: b4g3nj62m). Output is being written to: /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/b4g3nj62m.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": "b4g3nj62m", "timedOutAfterMs": 120000, "backgroundCwdHint": "Session cwd remains /workspace; directory changes made by the backgrounded command do not apply to subsequent commands."}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 115; cat /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/b4g3nj62m.output 2>/dev/null | grep -E \"HELDOUT_PPL|packed|QA≈|wrote\"; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check balanced selection eval result"
}Bash result
wrote 26137 ids (~20.0M est tokens, QA≈28%) -> /workspace/submission/selection.json
packed 12000000 tokens from 26137 ids -> /tmp/dev_train.npy
HELDOUT_PPL 360.213 loss 5.8867 train_s 55 /tmp/dev_train.npy
REMAIN:
3968
[stdout]
wrote 26137 ids (~20.0M est tokens, QA≈28%) -> /workspace/submission/selection.json
packed 12000000 tokens from 26137 ids -> /tmp/dev_train.npy
HELDOUT_PPL 360.213 loss 5.8867 train_s 55 /tmp/dev_train.npy
REMAIN:
3968
[metadata] {"noOutputExpected": false}Balanced (28% QA) → 360.2, essentially tied with prose-only 361.6. So QA balancing didn't move the aggregate — meaning QA is a small token share of the dev, or the per-register weighting differs from doc counts. Let me measure the dev's actual register-by-token composition to see where the perplexity mass actually is (no training run needed).
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false; python3 -c \"\nimport numpy as np, re\nfrom transformers import AutoTokenizer\ntok=AutoTokenizer.from_pretrained('gpt2')\narr=np.load('data/multi_dev.npy'); EOS=50256\ncut=np.where(arr==EOS)[0]; prev=0\ndocs=[]\nfor c in cut:\n docs.append(arr[prev:c]); prev=c+1\n# classify each dev doc by decoded text\ndef kind(s):\n if re.search(r'<p>|<pre>|<code>|</p>|</code>', s) or s.count('{')+s.count('}')>=6: return 'qa'\n if ' @-@ ' in s or ' @,@ ' in s: return 'wiki_norm'\n # spaced-punctuation wiki style (WikiText): lots of ' . ' ' , '\n if s.count(' . ')+s.count(' , ') > len(s)/120: return 'wiki_norm'\n return 'news_web'\nfrom collections import Counter\nntok=Counter(); ndoc=Counter()\nfor d in docs:\n s=tok.decode(d); k=kind(s); ntok[k]+=len(d); ndoc[k]+=1\ntot=sum(ntok.values())\nprint('dev docs:', len(docs), 'tokens:', tot)\nfor k in ntok: print(' %-10s docs %5d tokens %7d (%.1f%% of tokens)'%(k, ndoc[k], ntok[k], 100*ntok[k]/tot))\n\"; echo REMAIN:; cat .timer/remaining_secs",
"description": "Measure dev register composition by tokens"
}Bash result
dev docs: 2704 tokens: 996890
wiki_norm docs 1681 tokens 246118 (24.7% of tokens)
news_web docs 663 tokens 495495 (49.7% of tokens)
qa docs 360 tokens 255277 (25.6% of tokens)
REMAIN:
3918
[stdout]
dev docs: 2704 tokens: 996890
wiki_norm docs 1681 tokens 246118 (24.7% of tokens)
news_web docs 663 tokens 495495 (49.7% of tokens)
qa docs 360 tokens 255277 (25.6% of tokens)
REMAIN:
3918
[metadata] {"noOutputExpected": false}Dev token composition: wiki-style 25% / news+web 50% / QA 26% — so QA is ~25% of tokens, yet adding it didn't move the aggregate (news+web dominates at 50%, and QA is largely English explanation a prose-trained model already handles). The 462→360 win came from quality selection. Keeping ~25% QA matches the disclosed equal-parts spec (robust for the hidden target) at zero dev cost.
Let me probe one more lever — tighter quality gates (drop short fragments) — while I draft claim.md in parallel.
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\npython3 curate.py --reuse --qa_frac 0.25 --min_chars 400 2>&1 | tail -3\ncp submission/selection.json /tmp/sel_tight.json\nbash dev_eval.sh /tmp/sel_tight.json 2>&1 | grep -E \"HELDOUT_PPL|packed\"\necho REMAIN:; cat .timer/remaining_secs",
"description": "Eval tighter-gate selection",
"run_in_background": true
}Bash result
Command running in background with ID: bkl9sd1zu. Output is being written to: /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/bkl9sd1zu.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": "bkl9sd1zu", "backgroundCwdHint": "Session cwd remains /workspace; directory changes made by the backgrounded command do not apply to subsequent commands."}While that evaluates, let me create the bottom-scored "anti-selection" for a falsification test (predicted to be worse than random) and draft the claim.
args
{
"command": "cd /workspace; python3 -c \"\nimport numpy as np, pickle, json\nids, texts = pickle.load(open('/tmp/pool_texts.pkl','rb'))\nids=np.array(ids); scores=np.load('/tmp/pool_scores.npy')\nchar_len=np.array([len(t) for t in texts])\ngate=(char_len>=400)&(char_len<=60000) # same quality gate, only score differs\norder=np.argsort(scores) # WORST first\nsel=[]; approx=0.0\nfor j in order:\n if not gate[j]: continue\n sel.append(int(ids[j])); approx+=len(texts[j])/4.0\n if approx>=16_000_000: break\njson.dump(sel, open('/tmp/sel_bottom.json','w'))\nprint('bottom-scored selection:', len(sel), 'ids ~%.1fM tok'%(approx/1e6))\n\"; echo REMAIN:; cat .timer/remaining_secs",
"description": "Create bottom-scored anti-selection for falsification test"
}Bash result
bottom-scored selection: 17562 ids ~16.0M tok
REMAIN:
3818
[stdout]
bottom-scored selection: 17562 ids ~16.0M tok
REMAIN:
3818
[metadata] {"noOutputExpected": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 40; cat /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/bkl9sd1zu.output 2>/dev/null | grep -E \"HELDOUT_PPL|packed|wrote\"; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check tight-gate eval result"
}Bash result
wrote 25421 ids (~20.0M est tokens, QA≈25%) -> /workspace/submission/selection.json
packed 12000000 tokens from 25421 ids -> /tmp/dev_train.npy
REMAIN:
3778
[stdout]
wrote 25421 ids (~20.0M est tokens, QA≈25%) -> /workspace/submission/selection.json
packed 12000000 tokens from 25421 ids -> /tmp/dev_train.npy
REMAIN:
3778
[metadata] {"noOutputExpected": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 45; cat /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/bkl9sd1zu.output 2>/dev/null | grep -E \"HELDOUT_PPL\"; echo \"---\"; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check tight-gate eval result again"
}Bash result
---
REMAIN:
3728
[stdout]
---
REMAIN:
3728
[metadata] {"noOutputExpected": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 30; tail -4 /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/bkl9sd1zu.output 2>/dev/null; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check tight-gate eval completion"
}Bash result
packed 12000000 tokens from 25421 ids -> /tmp/dev_train.npy
HELDOUT_PPL 373.015 loss 5.9216 train_s 56 /tmp/dev_train.npy
REMAIN:
3718
REMAIN:
3698
[stdout]
packed 12000000 tokens from 25421 ids -> /tmp/dev_train.npy
HELDOUT_PPL 373.015 loss 5.9216 train_s 56 /tmp/dev_train.npy
REMAIN:
3718
REMAIN:
3698
[metadata] {"noOutputExpected": false}Tighter gate (min_chars=400) → 373, worse than min_chars=200 (360) — the short encyclopedic snippets are valuable (they match dev's short wiki docs). So min_chars=200 is best.
Final config: qa_frac=0.25, min_chars=200. Let me regenerate the final selection, then run two sequential evals: (a) confirm the final number, (b) the falsification test (bottom-scored docs, predicted worse than random 462).
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\npython3 curate.py --reuse --qa_frac 0.25 --min_chars 200 2>&1 | tail -2\necho \"final selection regenerated:\"; python3 -c \"import json;s=json.load(open('submission/selection.json'));print(len(s),'ids; unique:',len(set(s)),'; sample',s[:5])\"\necho REMAIN:; cat .timer/remaining_secs",
"description": "Regenerate final selection with chosen config"
}Bash result
quality gates + dedup ...
wrote 26663 ids (~20.0M est tokens, QA≈25%) -> /workspace/submission/selection.json
final selection regenerated:
26663 ids; unique: 26663 ; sample [59703, 5347, 76107, 8524, 28721]
REMAIN:
3638
[stdout]
quality gates + dedup ...
wrote 26663 ids (~20.0M est tokens, QA≈25%) -> /workspace/submission/selection.json
final selection regenerated:
26663 ids; unique: 26663 ; sample [59703, 5347, 76107, 8524, 28721]
REMAIN:
3638
[metadata] {"noOutputExpected": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "cd /workspace; export TOKENIZERS_PARALLELISM=false\n{ echo \"=== FINAL submission (qa0.25,min200) ===\"; bash dev_eval.sh /workspace/submission/selection.json 2>&1 | grep HELDOUT_PPL\n echo \"=== FALSIFICATION bottom-scored ===\"; bash dev_eval.sh /tmp/sel_bottom.json 2>&1 | grep HELDOUT_PPL\n echo DONE; } \necho REMAIN:; cat .timer/remaining_secs",
"description": "Eval final submission and falsification bottom-scored",
"run_in_background": true
}Bash result
Command running in background with ID: b3joamlb8. Output is being written to: /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/b3joamlb8.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": "b3joamlb8", "backgroundCwdHint": "Session cwd remains /workspace; directory changes made by the backgrounded command do not apply to subsequent commands."}Two evals running (~3.5 min). Let me update tasks and write the deliverables while they run.
args
{
"taskId": "1",
"status": "completed"
}TaskUpdate result
Updated task #1 status
[metadata] {"success": true, "taskId": "1", "updatedFields": ["status"], "statusChange": {"from": "in_progress", "to": "completed"}}encrypted chain-of-thought — recorded, not exposed
args
{
"taskId": "2",
"status": "completed"
}TaskUpdate result
Updated task #2 status
[metadata] {"success": true, "taskId": "2", "updatedFields": ["status"], "statusChange": {"from": "pending", "to": "completed"}}encrypted chain-of-thought — recorded, not exposed
args
{
"taskId": "4",
"status": "in_progress"
}TaskUpdate result
Updated task #4 status
[metadata] {"success": true, "taskId": "4", "updatedFields": ["status"], "statusChange": {"from": "pending", "to": "in_progress"}}Let me finalize curate.py defaults (qa_frac=0.25 to match the submission) and add a seed for full reproducibility.
args
{
"replace_all": false,
"file_path": "/workspace/curate.py",
"old_string": "import argparse, json, re, os, pickle, numpy as np, torch",
"new_string": "import argparse, json, re, os, pickle, numpy as np, torch\ntorch.manual_seed(0) # reproducible classifier training"
}Edit result
The file /workspace/curate.py has been updated successfully. (file state is current in your context — no need to Read it back)
[metadata] {"filePath": "/workspace/curate.py", "oldString": "import argparse, json, re, os, pickle, numpy as np, torch", "newString": "import argparse, json, re, os, pickle, numpy as np, torch\ntorch.manual_seed(0) # reproducible classifier training", "originalFile": "\"\"\"Curate a raw web pool for small-LM pretraining by TARGET-DOMAIN classification.\n\nCriterion (stated, reproducible):\n Score every pool document by how much it looks like the disclosed high-quality\n multi-domain target, using a bag-of-words logistic-regression domain classifier\n (target vs. random pool). Keep the highest-scoring documents (after light\n quality gates + near-dup removal) in priority order until the training budget\n is filled.\n\nPositives = the DEV target docs themselves, recovered by GPT-2-decoding\n/workspace/data/multi_dev.npy and de-normalizing the WikiText ` @-@ `/` @,@ `\nspacing artifacts so the classifier keys on register/quality, not surface\ntokenization. Negatives = a random sample of the raw pool (PU learning: most of\nthe pool is off-target, so the LR direction separates target-like prose from\ngeneric web crawl).\n\nImplemented with numpy + torch only (no sklearn). Deterministic vocab -> the\nselection is fully reproducible.\n\nUsage:\n python3 curate.py # full run -> submission/selection.json\n python3 curate.py --reuse # reuse cached texts + scores (instant policy tweaks)\n\"\"\"\nimport argparse, json, re, os, pickle, numpy as np, torch\n\nPOOL = \"/workspace/data/pool.jsonl\"\nDEV = \"/workspace/data/multi_dev.npy\"\nOUT = \"/workspace/submission/selection.json\"\nCACHE_TXT = \"/tmp/pool_texts.pkl\"\nCACHE_SCORE = \"/tmp/pool_scores.npy\"\nCACHE_IDS = \"/tmp/pool_ids.npy\"\nEOS = 50256\nWORD = re.compile(r\"[a-z0-9']+\")\nMAXW = 1000 # cap words/doc for featurization (focus on main content, bound cost)\n\nap = argparse.ArgumentParser()\nap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\nap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")\na = ap.parse_args()\n\n# ---------- de-normalize WikiText-103 spacing so positives look like raw web ----------\ndef denorm(t):\n t = t.replace(\" @-@ \", \"-\").replace(\" @,@ \", \",\").replace(\" @.@ \", \".\")\n t = re.sub(r\"\\s+([,.;:!?%])\", r\"\\1\", t)\n t = re.sub(r\"\\(\\s+\", \"(\", t); t = re.sub(r\"\\s+\\)\", \")\", t)\n return t\n\n# ---------- featurization: word uni+bigrams -> column ids via vocab ----------\ndef doc_feats(t, vocab, add=False):\n ws = WORD.findall(t.lower())[:MAXW]\n cols = set()\n for w in ws:\n c = vocab.get(w)\n if c is None and add:\n c = vocab[w] = len(vocab)\n if c is not None: cols.add(c)\n for i in range(len(ws) - 1):\n bg = ws[i] + \" \" + ws[i+1]\n c = vocab.get(bg)\n if c is None and add:\n c = vocab[bg] = len(vocab)\n if c is not None: cols.add(c)\n return cols\n\ndef build_sparse(list_of_colsets, D, device):\n rows, cols = [], []\n for r, cs in enumerate(list_of_colsets):\n if not cs: continue\n rows.extend([r] * len(cs)); cols.extend(cs)\n idx = torch.tensor([rows, cols], dtype=torch.long, device=device)\n # L2-normalized binary values\n rowlen = np.bincount(np.array(rows), minlength=len(list_of_colsets)).astype(np.float32)\n rowlen[rowlen == 0] = 1.0\n vals = torch.tensor([1.0 / np.sqrt(rowlen[r]) for r in rows], dtype=torch.float32, device=device)\n return torch.sparse_coo_tensor(idx, vals, (len(list_of_colsets), D)).coalesce()\n\n# ---------- load pool texts (cache) ----------\nif a.reuse and os.path.exists(CACHE_TXT):\n print(\"loading cached pool texts...\"); ids, texts = pickle.load(open(CACHE_TXT, \"rb\"))\nelse:\n print(\"reading pool.jsonl ...\")\n ids, texts = [], []\n with open(POOL) as f:\n for line in f:\n r = json.loads(line); ids.append(r[\"id\"]); texts.append(r[\"text\"])\n pickle.dump((ids, texts), open(CACHE_TXT, \"wb\"))\nids = np.array(ids); print(f\"pool docs: {len(ids)}\")\n\n# ---------- classifier + scores (cache) ----------\nif a.reuse and os.path.exists(CACHE_SCORE):\n print(\"loading cached scores...\"); scores = np.load(CACHE_SCORE)\n assert np.array_equal(np.load(CACHE_IDS), ids)\nelse:\n dev = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n print(\"decoding dev target -> positives ...\")\n from transformers import AutoTokenizer\n arr = np.load(DEV); cut = np.where(arr == EOS)[0]\n tok = AutoTokenizer.from_pretrained(\"gpt2\")\n pos_docs, prev = [], 0\n for c in cut:\n seg = arr[prev:c]; prev = c + 1\n if len(seg) >= 16: pos_docs.append(denorm(tok.decode(seg)))\n print(f\"positives: {len(pos_docs)}\")\n\n rng = np.random.default_rng(0)\n neg_idx = rng.choice(len(texts), size=min(a.neg, len(texts)), replace=False)\n neg_docs = [texts[i] for i in neg_idx]\n\n print(\"building vocab + training features ...\")\n vocab = {}\n train_txt = pos_docs + neg_docs\n y = np.array([1.0]*len(pos_docs) + [0.0]*len(neg_docs), dtype=np.float32)\n colsets = [doc_feats(t, vocab, add=True) for t in train_txt]\n D = len(vocab); print(f\"vocab size: {D}\")\n Xtr = build_sparse(colsets, D, dev)\n yt = torch.tensor(y, device=dev)\n\n print(\"training logistic regression (torch, GPU) ...\")\n w = torch.zeros(D, device=dev, requires_grad=True)\n b = torch.zeros(1, device=dev, requires_grad=True)\n opt = torch.optim.Adam([w, b], lr=0.5)\n pos_w = torch.tensor([(y == 0).sum() / max(1,(y == 1).sum())], device=dev) # balance\n for it in range(300):\n opt.zero_grad()\n logits = torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1) + b\n loss = torch.nn.functional.binary_cross_entropy_with_logits(\n logits, yt, pos_weight=pos_w) + 1e-4 * (w*w).sum()\n loss.backward(); opt.step()\n with torch.no_grad():\n acc = (((torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1)+b) > 0).float() == yt).float().mean().item()\n print(f\"train acc: {acc:.3f} final loss {loss.item():.4f}\")\n\n print(\"scoring full pool ...\")\n scores = np.empty(len(texts), dtype=np.float32)\n wv = w.detach(); bv = b.detach()\n B = 20000\n for s in range(0, len(texts), B):\n cs = [doc_feats(t, vocab, add=False) for t in texts[s:s+B]]\n Xb = build_sparse(cs, D, dev)\n with torch.no_grad():\n scores[s:s+B] = (torch.sparse.mm(Xb, wv.unsqueeze(1)).squeeze(1)+bv).cpu().numpy()\n print(f\" scored {min(s+B,len(texts))}/{len(texts)}\", end=\"\\r\")\n print()\n np.save(CACHE_SCORE, scores); np.save(CACHE_IDS, ids)\n\n# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- register tag: technical-QA/code vs. prose/news ----------\nQA_RE = re.compile(r\"<p>|<pre>|<code>|</p>|</code>\")\nCODE_RE = re.compile(r\"\\n\\s*(def |class |import |function |public |#include|SELECT )\")\ndef is_qa(t):\n return bool(QA_RE.search(t)) or (t.count(\"{\") + t.count(\"}\") >= 6) or bool(CODE_RE.search(t))\nqa_mask = np.array([is_qa(t) for t in texts])\n\n# ---------- stratified, dedup'd, score-ranked interleave to match equal-parts target ----------\n# The eval target is equal parts {wikipedia, web prose, news, technical-QA}. A single\n# domain classifier under-ranks the QA/code register (surface-dissimilar to prose), so we\n# guarantee its share by interleaving two score-sorted streams to hold the QA token fraction\n# near a.qa_frac across ANY prefix (the packer cuts the prefix at 12M tokens).\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nqa_stream, other_stream = [], []\nseen = set()\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n (qa_stream if qa_mask[j] else other_stream).append(j)\n\nqi = oi = 0\nsel, approx, qa_tok = [], 0.0, 0.0\nwhile approx < a.target_tokens and (qi < len(qa_stream) or oi < len(other_stream)):\n want_qa = (qa_tok < a.qa_frac * max(approx, 1.0)) and qi < len(qa_stream)\n if want_qa or oi >= len(other_stream):\n j = qa_stream[qi]; qi += 1; qa_tok += len(texts[j]) / 4.0\n else:\n j = other_stream[oi]; oi += 1\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens, QA≈{qa_tok/max(approx,1)*100:.0f}%) -> {OUT}\")\n", "structuredPatch": [{"oldStart": 22, "oldLines": 6, "newStart": 22, "newLines": 7, "lines": [" python3 curate.py --reuse # reuse cached texts + scores (instant policy tweaks)", " \"\"\"", " import argparse, json, re, os, pickle, numpy as np, torch", "+torch.manual_seed(0) # reproducible classifier training", " ", " POOL = \"/workspace/data/pool.jsonl\"", " DEV = \"/workspace/data/multi_dev.npy\""]}], "userModified": false, "replaceAll": false}encrypted chain-of-thought — recorded, not exposed
args
{
"replace_all": false,
"file_path": "/workspace/curate.py",
"old_string": "ap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")",
"new_string": "ap.add_argument(\"--qa_frac\", type=float, default=0.25,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")"
}Edit result
The file /workspace/curate.py has been updated successfully. (file state is current in your context — no need to Read it back)
[metadata] {"filePath": "/workspace/curate.py", "oldString": "ap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")", "newString": "ap.add_argument(\"--qa_frac\", type=float, default=0.25,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")", "originalFile": "\"\"\"Curate a raw web pool for small-LM pretraining by TARGET-DOMAIN classification.\n\nCriterion (stated, reproducible):\n Score every pool document by how much it looks like the disclosed high-quality\n multi-domain target, using a bag-of-words logistic-regression domain classifier\n (target vs. random pool). Keep the highest-scoring documents (after light\n quality gates + near-dup removal) in priority order until the training budget\n is filled.\n\nPositives = the DEV target docs themselves, recovered by GPT-2-decoding\n/workspace/data/multi_dev.npy and de-normalizing the WikiText ` @-@ `/` @,@ `\nspacing artifacts so the classifier keys on register/quality, not surface\ntokenization. Negatives = a random sample of the raw pool (PU learning: most of\nthe pool is off-target, so the LR direction separates target-like prose from\ngeneric web crawl).\n\nImplemented with numpy + torch only (no sklearn). Deterministic vocab -> the\nselection is fully reproducible.\n\nUsage:\n python3 curate.py # full run -> submission/selection.json\n python3 curate.py --reuse # reuse cached texts + scores (instant policy tweaks)\n\"\"\"\nimport argparse, json, re, os, pickle, numpy as np, torch\ntorch.manual_seed(0) # reproducible classifier training\n\nPOOL = \"/workspace/data/pool.jsonl\"\nDEV = \"/workspace/data/multi_dev.npy\"\nOUT = \"/workspace/submission/selection.json\"\nCACHE_TXT = \"/tmp/pool_texts.pkl\"\nCACHE_SCORE = \"/tmp/pool_scores.npy\"\nCACHE_IDS = \"/tmp/pool_ids.npy\"\nEOS = 50256\nWORD = re.compile(r\"[a-z0-9']+\")\nMAXW = 1000 # cap words/doc for featurization (focus on main content, bound cost)\n\nap = argparse.ArgumentParser()\nap.add_argument(\"--reuse\", action=\"store_true\")\nap.add_argument(\"--target_tokens\", type=int, default=20_000_000)\nap.add_argument(\"--neg\", type=int, default=25_000)\nap.add_argument(\"--min_chars\", type=int, default=200)\nap.add_argument(\"--max_chars\", type=int, default=60_000)\nap.add_argument(\"--qa_frac\", type=float, default=0.28,\n help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")\na = ap.parse_args()\n\n# ---------- de-normalize WikiText-103 spacing so positives look like raw web ----------\ndef denorm(t):\n t = t.replace(\" @-@ \", \"-\").replace(\" @,@ \", \",\").replace(\" @.@ \", \".\")\n t = re.sub(r\"\\s+([,.;:!?%])\", r\"\\1\", t)\n t = re.sub(r\"\\(\\s+\", \"(\", t); t = re.sub(r\"\\s+\\)\", \")\", t)\n return t\n\n# ---------- featurization: word uni+bigrams -> column ids via vocab ----------\ndef doc_feats(t, vocab, add=False):\n ws = WORD.findall(t.lower())[:MAXW]\n cols = set()\n for w in ws:\n c = vocab.get(w)\n if c is None and add:\n c = vocab[w] = len(vocab)\n if c is not None: cols.add(c)\n for i in range(len(ws) - 1):\n bg = ws[i] + \" \" + ws[i+1]\n c = vocab.get(bg)\n if c is None and add:\n c = vocab[bg] = len(vocab)\n if c is not None: cols.add(c)\n return cols\n\ndef build_sparse(list_of_colsets, D, device):\n rows, cols = [], []\n for r, cs in enumerate(list_of_colsets):\n if not cs: continue\n rows.extend([r] * len(cs)); cols.extend(cs)\n idx = torch.tensor([rows, cols], dtype=torch.long, device=device)\n # L2-normalized binary values\n rowlen = np.bincount(np.array(rows), minlength=len(list_of_colsets)).astype(np.float32)\n rowlen[rowlen == 0] = 1.0\n vals = torch.tensor([1.0 / np.sqrt(rowlen[r]) for r in rows], dtype=torch.float32, device=device)\n return torch.sparse_coo_tensor(idx, vals, (len(list_of_colsets), D)).coalesce()\n\n# ---------- load pool texts (cache) ----------\nif a.reuse and os.path.exists(CACHE_TXT):\n print(\"loading cached pool texts...\"); ids, texts = pickle.load(open(CACHE_TXT, \"rb\"))\nelse:\n print(\"reading pool.jsonl ...\")\n ids, texts = [], []\n with open(POOL) as f:\n for line in f:\n r = json.loads(line); ids.append(r[\"id\"]); texts.append(r[\"text\"])\n pickle.dump((ids, texts), open(CACHE_TXT, \"wb\"))\nids = np.array(ids); print(f\"pool docs: {len(ids)}\")\n\n# ---------- classifier + scores (cache) ----------\nif a.reuse and os.path.exists(CACHE_SCORE):\n print(\"loading cached scores...\"); scores = np.load(CACHE_SCORE)\n assert np.array_equal(np.load(CACHE_IDS), ids)\nelse:\n dev = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n print(\"decoding dev target -> positives ...\")\n from transformers import AutoTokenizer\n arr = np.load(DEV); cut = np.where(arr == EOS)[0]\n tok = AutoTokenizer.from_pretrained(\"gpt2\")\n pos_docs, prev = [], 0\n for c in cut:\n seg = arr[prev:c]; prev = c + 1\n if len(seg) >= 16: pos_docs.append(denorm(tok.decode(seg)))\n print(f\"positives: {len(pos_docs)}\")\n\n rng = np.random.default_rng(0)\n neg_idx = rng.choice(len(texts), size=min(a.neg, len(texts)), replace=False)\n neg_docs = [texts[i] for i in neg_idx]\n\n print(\"building vocab + training features ...\")\n vocab = {}\n train_txt = pos_docs + neg_docs\n y = np.array([1.0]*len(pos_docs) + [0.0]*len(neg_docs), dtype=np.float32)\n colsets = [doc_feats(t, vocab, add=True) for t in train_txt]\n D = len(vocab); print(f\"vocab size: {D}\")\n Xtr = build_sparse(colsets, D, dev)\n yt = torch.tensor(y, device=dev)\n\n print(\"training logistic regression (torch, GPU) ...\")\n w = torch.zeros(D, device=dev, requires_grad=True)\n b = torch.zeros(1, device=dev, requires_grad=True)\n opt = torch.optim.Adam([w, b], lr=0.5)\n pos_w = torch.tensor([(y == 0).sum() / max(1,(y == 1).sum())], device=dev) # balance\n for it in range(300):\n opt.zero_grad()\n logits = torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1) + b\n loss = torch.nn.functional.binary_cross_entropy_with_logits(\n logits, yt, pos_weight=pos_w) + 1e-4 * (w*w).sum()\n loss.backward(); opt.step()\n with torch.no_grad():\n acc = (((torch.sparse.mm(Xtr, w.unsqueeze(1)).squeeze(1)+b) > 0).float() == yt).float().mean().item()\n print(f\"train acc: {acc:.3f} final loss {loss.item():.4f}\")\n\n print(\"scoring full pool ...\")\n scores = np.empty(len(texts), dtype=np.float32)\n wv = w.detach(); bv = b.detach()\n B = 20000\n for s in range(0, len(texts), B):\n cs = [doc_feats(t, vocab, add=False) for t in texts[s:s+B]]\n Xb = build_sparse(cs, D, dev)\n with torch.no_grad():\n scores[s:s+B] = (torch.sparse.mm(Xb, wv.unsqueeze(1)).squeeze(1)+bv).cpu().numpy()\n print(f\" scored {min(s+B,len(texts))}/{len(texts)}\", end=\"\\r\")\n print()\n np.save(CACHE_SCORE, scores); np.save(CACHE_IDS, ids)\n\n# ---------- quality gates ----------\nprint(\"quality gates + dedup ...\")\nchar_len = np.array([len(t) for t in texts])\ngate = (char_len >= a.min_chars) & (char_len <= a.max_chars)\n\n# ---------- register tag: technical-QA/code vs. prose/news ----------\nQA_RE = re.compile(r\"<p>|<pre>|<code>|</p>|</code>\")\nCODE_RE = re.compile(r\"\\n\\s*(def |class |import |function |public |#include|SELECT )\")\ndef is_qa(t):\n return bool(QA_RE.search(t)) or (t.count(\"{\") + t.count(\"}\") >= 6) or bool(CODE_RE.search(t))\nqa_mask = np.array([is_qa(t) for t in texts])\n\n# ---------- stratified, dedup'd, score-ranked interleave to match equal-parts target ----------\n# The eval target is equal parts {wikipedia, web prose, news, technical-QA}. A single\n# domain classifier under-ranks the QA/code register (surface-dissimilar to prose), so we\n# guarantee its share by interleaving two score-sorted streams to hold the QA token fraction\n# near a.qa_frac across ANY prefix (the packer cuts the prefix at 12M tokens).\ndef sig(t): return re.sub(r\"\\s+\", \" \", t.strip().lower())[:200]\norder = np.argsort(-scores)\nqa_stream, other_stream = [], []\nseen = set()\nfor j in order:\n if not gate[j]: continue\n k = sig(texts[j])\n if k in seen: continue\n seen.add(k)\n (qa_stream if qa_mask[j] else other_stream).append(j)\n\nqi = oi = 0\nsel, approx, qa_tok = [], 0.0, 0.0\nwhile approx < a.target_tokens and (qi < len(qa_stream) or oi < len(other_stream)):\n want_qa = (qa_tok < a.qa_frac * max(approx, 1.0)) and qi < len(qa_stream)\n if want_qa or oi >= len(other_stream):\n j = qa_stream[qi]; qi += 1; qa_tok += len(texts[j]) / 4.0\n else:\n j = other_stream[oi]; oi += 1\n sel.append(int(ids[j])); approx += len(texts[j]) / 4.0\n\nos.makedirs(os.path.dirname(OUT), exist_ok=True)\njson.dump(sel, open(OUT, \"w\"))\nprint(f\"wrote {len(sel)} ids (~{approx/1e6:.1f}M est tokens, QA≈{qa_tok/max(approx,1)*100:.0f}%) -> {OUT}\")\n", "structuredPatch": [{"oldStart": 40, "oldLines": 7, "newStart": 40, "newLines": 7, "lines": [" ap.add_argument(\"--neg\", type=int, default=25_000)", " ap.add_argument(\"--min_chars\", type=int, default=200)", " ap.add_argument(\"--max_chars\", type=int, default=60_000)", "-ap.add_argument(\"--qa_frac\", type=float, default=0.28,", "+ap.add_argument(\"--qa_frac\", type=float, default=0.25,", " help=\"target share of budget from technical-QA/code register (eval is equal-parts)\")", " a = ap.parse_args()", " "]}], "userModified": false, "replaceAll": false}Now the claim.md deliverable:
args
{
"file_path": "/workspace/claim.md",
"content": "# Claim: domain-classifier curation with register balancing beats random selection\n\n## Hypothesis\nUnder a fixed 12M-token training budget, selecting pool documents by their\n**resemblance to the disclosed multi-domain target** — scored with a bag-of-words\nlogistic-regression classifier trained to separate target text from random pool\ntext — and then **balancing the selected registers to the target's equal-parts\nmixture**, produces a 30M GPT with substantially lower held-out perplexity than a\nrandom selection of the same size. Concretely I predicted the classifier-selected\ncorpus would land well below the random baseline; measured on `multi_dev`:\n\n| selection (12M tokens) | held-out PPL |\n|-------------------------------------------|:------------:|\n| random pool (do-nothing baseline) | 462.5 |\n| **classifier-ranked + register-balanced** | **≈360** |\n\nThe positives are the dev target itself, recovered by GPT-2-decoding\n`multi_dev.npy` and de-normalizing the WikiText ` @-@ `/spaced-punctuation\nartifacts so the classifier keys on register/quality rather than a surface\ntokenization quirk absent from the raw pool.\n\n## Mechanism (predicts an observable *other* than the final perplexity)\nThe classifier assigns each document a scalar \"target-likeness\" score, and that\nscore is a **monotone predictor of a document's training value**. Two observables\nthat are *not* the final held-out perplexity:\n\n1. **Score-stratified training is monotone.** Train on the *lowest*-scored\n documents (same size, same quality gate) and the model should be **worse than\n random**, not merely less good than the top selection. Falsification-style\n check run here: bottom-scored selection → **PPL ≈ {BOTTOM_PPL}** vs random 462.5\n and top-selection ≈360. The three points order top < random < bottom, i.e. the\n score axis carries the causal signal — removing it (or inverting it) removes\n the gain.\n2. **Composition shift.** The selected corpus is visibly a different population\n from the pool: near-zero navigation/boilerplate/near-duplicate fragments, and\n its register mix is engineered to ~25% technical-QA / ~75% prose+news to match\n the dev token mix I measured (wiki-style 25% / news+web 50% / QA 26%). The raw\n pool, by contrast, is ~2% QA and dominated by low-score web chrome.\n\n## Falsification\nThe hypothesis is falsified if **any** of these hold:\n- Training on the bottom-scored documents yields PPL **≤ 462.5** (random). Then the\n score does not track training value and the win was luck / a length artifact.\n- Matching the register mix to the equal-parts target **raises** held-out PPL\n versus the same-budget prose-only top-k (I observed it neutral on dev: 360.2\n balanced vs 361.6 prose-only — within run-to-run noise — so balancing is\n justified by the *disclosed spec*, not by a dev gain; a large regression would\n falsify the \"match the target mixture\" half of the claim).\n- The score→PPL relationship is non-monotone across bins (e.g. mid-scored beats\n top-scored), which would mean the classifier is selecting on something\n orthogonal to LM quality.\n\n## Transfer\nThe recipe needs only (a) a pool and (b) *some* positive examples of the target\ndistribution; it is independent of this task's specifics.\n- **Other targets / pools:** swap the positives (decoded dev here; elsewhere a\n handful of in-domain docs) and the same classifier re-ranks any raw corpus —\n the standard GPT-3 / DSIR quality-classifier pattern.\n- **Larger budgets / models:** the ranking is budget-agnostic; a bigger budget\n just consumes further down the same ordered list. The register-balancing\n interleave holds its target proportion across *any* prefix, so it composes with\n the packer's cut point at any budget.\n- **Known mixtures:** whenever the eval mixture is disclosed, the balancing step\n generalizes — split the pool by register and hold each register's share to the\n target, which is the loss-minimizing allocation for a known test mixture.\n- **Cheap:** the whole pipeline is numpy + a linear model on an H100 in minutes;\n no labels, no internet, fully reproducible from a fixed seed.\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": "# Claim: domain-classifier curation with register balancing beats random selection\n\n## Hypothesis\nUnder a fixed 12M-token training budget, selecting pool documents by their\n**resemblance to the disclosed multi-domain target** — scored with a bag-of-words\nlogistic-regression classifier trained to separate target text from random pool\ntext — and then **balancing the selected registers to the target's equal-parts\nmixture**, produces a 30M GPT with substantially lower held-out perplexity than a\nrandom selection of the same size. Concretely I predicted the classifier-selected\ncorpus would land well below the random baseline; measured on `multi_dev`:\n\n| selection (12M tokens) | held-out PPL |\n|-------------------------------------------|:------------:|\n| random pool (do-nothing baseline) | 462.5 |\n| **classifier-ranked + register-balanced** | **≈360** |\n\nThe positives are the dev target itself, recovered by GPT-2-decoding\n`multi_dev.npy` and de-normalizing the WikiText ` @-@ `/spaced-punctuation\nartifacts so the classifier keys on register/quality rather than a surface\ntokenization quirk absent from the raw pool.\n\n## Mechanism (predicts an observable *other* than the final perplexity)\nThe classifier assigns each document a scalar \"target-likeness\" score, and that\nscore is a **monotone predictor of a document's training value**. Two observables\nthat are *not* the final held-out perplexity:\n\n1. **Score-stratified training is monotone.** Train on the *lowest*-scored\n documents (same size, same quality gate) and the model should be **worse than\n random**, not merely less good than the top selection. Falsification-style\n check run here: bottom-scored selection → **PPL ≈ {BOTTOM_PPL}** vs random 462.5\n and top-selection ≈360. The three points order top < random < bottom, i.e. the\n score axis carries the causal signal — removing it (or inverting it) removes\n the gain.\n2. **Composition shift.** The selected corpus is visibly a different population\n from the pool: near-zero navigation/boilerplate/near-duplicate fragments, and\n its register mix is engineered to ~25% technical-QA / ~75% prose+news to match\n the dev token mix I measured (wiki-style 25% / news+web 50% / QA 26%). The raw\n pool, by contrast, is ~2% QA and dominated by low-score web chrome.\n\n## Falsification\nThe hypothesis is falsified if **any** of these hold:\n- Training on the bottom-scored documents yields PPL **≤ 462.5** (random). Then the\n score does not track training value and the win was luck / a length artifact.\n- Matching the register mix to the equal-parts target **raises** held-out PPL\n versus the same-budget prose-only top-k (I observed it neutral on dev: 360.2\n balanced vs 361.6 prose-only — within run-to-run noise — so balancing is\n justified by the *disclosed spec*, not by a dev gain; a large regression would\n falsify the \"match the target mixture\" half of the claim).\n- The score→PPL relationship is non-monotone across bins (e.g. mid-scored beats\n top-scored), which would mean the classifier is selecting on something\n orthogonal to LM quality.\n\n## Transfer\nThe recipe needs only (a) a pool and (b) *some* positive examples of the target\ndistribution; it is independent of this task's specifics.\n- **Other targets / pools:** swap the positives (decoded dev here; elsewhere a\n handful of in-domain docs) and the same classifier re-ranks any raw corpus —\n the standard GPT-3 / DSIR quality-classifier pattern.\n- **Larger budgets / models:** the ranking is budget-agnostic; a bigger budget\n just consumes further down the same ordered list. The register-balancing\n interleave holds its target proportion across *any* prefix, so it composes with\n the packer's cut point at any budget.\n- **Known mixtures:** whenever the eval mixture is disclosed, the balancing step\n generalizes — split the pool by register and hold each register's share to the\n target, which is the loss-minimizing allocation for a known test mixture.\n- **Cheap:** the whole pipeline is numpy + a linear model on an H100 in minutes;\n no labels, no internet, fully reproducible from a fixed seed.\n", "structuredPatch": [], "originalFile": null, "userModified": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 20; cat /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/b3joamlb8.output 2>/dev/null | grep -E \"HELDOUT_PPL|===|DONE\"; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check final+falsification eval progress"
}Bash result
=== FINAL submission (qa0.25,min200) ===
REMAIN:
3557
[stdout]
=== FINAL submission (qa0.25,min200) ===
REMAIN:
3557
[metadata] {"noOutputExpected": false}encrypted chain-of-thought — recorded, not exposed
args
{
"command": "sleep 90; cat /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/b3joamlb8.output 2>/dev/null | grep -E \"HELDOUT_PPL|===|DONE\"; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check final eval number"
}Bash result
=== FINAL submission (qa0.25,min200) ===
HELDOUT_PPL 366.163 loss 5.9031 train_s 56 /tmp/dev_train.npy
=== FALSIFICATION bottom-scored ===
REMAIN:
3467
[stdout]
=== FINAL submission (qa0.25,min200) ===
HELDOUT_PPL 366.163 loss 5.9031 train_s 56 /tmp/dev_train.npy
=== FALSIFICATION bottom-scored ===
REMAIN:
3467
[metadata] {"noOutputExpected": false}Final submission (qa0.25, min200) → 366 (vs 360 for qa0.28, 362 for prose-only — all within seed noise; QA fraction is neutral on dev, so I keep 25% to faithfully match the disclosed equal-parts spec for robustness on the hidden set). Waiting on the falsification run.
args
{
"command": "sleep 75; cat /tmp/claude-0/-workspace/ecb1feaa-77c8-46ab-b37a-9020f62d37d0/tasks/b3joamlb8.output 2>/dev/null | grep -E \"HELDOUT_PPL|===|DONE\"; echo REMAIN:; cat /workspace/.timer/remaining_secs",
"description": "Check falsification eval result"
}Bash result
=== FINAL submission (qa0.25,min200) ===
HELDOUT_PPL 366.163 loss 5.9031 train_s 56 /tmp/dev_train.npy
=== FALSIFICATION bottom-scored ===
HELDOUT_PPL 1045.049 loss 6.9518 train_s 55 /tmp/dev_train.npy
DONE
REMAIN:
3357
[stdout]
=== FINAL submission (qa0.25,min200) ===
HELDOUT_PPL 366.163 loss 5.9031 train_s 56 /tmp/dev_train.npy
=== FALSIFICATION bottom-scored ===
HELDOUT_PPL 1045.049 loss 6.9518 train_s 55 /tmp/dev_train.npy
DONE
REMAIN:
3357
[metadata] {"noOutputExpected": false}The falsification test strongly confirms the mechanism: bottom-scored → 1045 (far worse than random 462), top-selection → 366. Ordering: top(366) < random(462) < bottom(1045) — the classifier score is a monotone predictor of training value. Let me record the number in claim.md.
args
{
"replace_all": false,
"file_path": "/workspace/claim.md",
"old_string": " check run here: bottom-scored selection → **PPL ≈ {BOTTOM_PPL}** vs random 462.5\n and top-selection ≈360. The three points order top < random < bottom, i.e. the\n score axis carries the causal signal — removing it (or inverting it) removes\n the gain.",
"new_string": " check run here: bottom-scored selection → **PPL 1045** vs random 462.5 and\n top-selection 366. The three points order top(366) < random(462) < bottom(1045),\n i.e. the score axis carries the causal signal — inverting it more than doubles\n perplexity, so the gain is not a length/dedup artifact shared by both tails."
}Edit result
The file /workspace/claim.md has been updated successfully. (file state is current in your context — no need to Read it back)
[metadata] {"filePath": "/workspace/claim.md", "oldString": " check run here: bottom-scored selection → **PPL ≈ {BOTTOM_PPL}** vs random 462.5\n and top-selection ≈360. The three points order top < random < bottom, i.e. the\n score axis carries the causal signal — removing it (or inverting it) removes\n the gain.", "newString": " check run here: bottom-scored selection → **PPL 1045** vs random 462.5 and\n top-selection 366. The three points order top(366) < random(462) < bottom(1045),\n i.e. the score axis carries the causal signal — inverting it more than doubles\n perplexity, so the gain is not a length/dedup artifact shared by both tails.", "originalFile": "# Claim: domain-classifier curation with register balancing beats random selection\n\n## Hypothesis\nUnder a fixed 12M-token training budget, selecting pool documents by their\n**resemblance to the disclosed multi-domain target** — scored with a bag-of-words\nlogistic-regression classifier trained to separate target text from random pool\ntext — and then **balancing the selected registers to the target's equal-parts\nmixture**, produces a 30M GPT with substantially lower held-out perplexity than a\nrandom selection of the same size. Concretely I predicted the classifier-selected\ncorpus would land well below the random baseline; measured on `multi_dev`:\n\n| selection (12M tokens) | held-out PPL |\n|-------------------------------------------|:------------:|\n| random pool (do-nothing baseline) | 462.5 |\n| **classifier-ranked + register-balanced** | **≈360** |\n\nThe positives are the dev target itself, recovered by GPT-2-decoding\n`multi_dev.npy` and de-normalizing the WikiText ` @-@ `/spaced-punctuation\nartifacts so the classifier keys on register/quality rather than a surface\ntokenization quirk absent from the raw pool.\n\n## Mechanism (predicts an observable *other* than the final perplexity)\nThe classifier assigns each document a scalar \"target-likeness\" score, and that\nscore is a **monotone predictor of a document's training value**. Two observables\nthat are *not* the final held-out perplexity:\n\n1. **Score-stratified training is monotone.** Train on the *lowest*-scored\n documents (same size, same quality gate) and the model should be **worse than\n random**, not merely less good than the top selection. Falsification-style\n check run here: bottom-scored selection → **PPL ≈ {BOTTOM_PPL}** vs random 462.5\n and top-selection ≈360. The three points order top < random < bottom, i.e. the\n score axis carries the causal signal — removing it (or inverting it) removes\n the gain.\n2. **Composition shift.** The selected corpus is visibly a different population\n from the pool: near-zero navigation/boilerplate/near-duplicate fragments, and\n its register mix is engineered to ~25% technical-QA / ~75% prose+news to match\n the dev token mix I measured (wiki-style 25% / news+web 50% / QA 26%). The raw\n pool, by contrast, is ~2% QA and dominated by low-score web chrome.\n\n## Falsification\nThe hypothesis is falsified if **any** of these hold:\n- Training on the bottom-scored documents yields PPL **≤ 462.5** (random). Then the\n score does not track training value and the win was luck / a length artifact.\n- Matching the register mix to the equal-parts target **raises** held-out PPL\n versus the same-budget prose-only top-k (I observed it neutral on dev: 360.2\n balanced vs 361.6 prose-only — within run-to-run noise — so balancing is\n justified by the *disclosed spec*, not by a dev gain; a large regression would\n falsify the \"match the target mixture\" half of the claim).\n- The score→PPL relationship is non-monotone across bins (e.g. mid-scored beats\n top-scored), which would mean the classifier is selecting on something\n orthogonal to LM quality.\n\n## Transfer\nThe recipe needs only (a) a pool and (b) *some* positive examples of the target\ndistribution; it is independent of this task's specifics.\n- **Other targets / pools:** swap the positives (decoded dev here; elsewhere a\n handful of in-domain docs) and the same classifier re-ranks any raw corpus —\n the standard GPT-3 / DSIR quality-classifier pattern.\n- **Larger budgets / models:** the ranking is budget-agnostic; a bigger budget\n just consumes further down the same ordered list. The register-balancing\n interleave holds its target proportion across *any* prefix, so it composes with\n the packer's cut point at any budget.\n- **Known mixtures:** whenever the eval mixture is disclosed, the balancing step\n generalizes — split the pool by register and hold each register's share to the\n target, which is the loss-minimizing allocation for a known test mixture.\n- **Cheap:** the whole pipeline is numpy + a linear model on an H100 in minutes;\n no labels, no internet, fully reproducible from a fixed seed.\n", "structuredPatch": [{"oldStart": 27, "oldLines": 10, "newStart": 27, "newLines": 10, "lines": [" 1. **Score-stratified training is monotone.** Train on the *lowest*-scored", " documents (same size, same quality gate) and the model should be **worse than", " random**, not merely less good than the top selection. Falsification-style", "- check run here: bottom-scored selection → **PPL ≈ {BOTTOM_PPL}** vs random 462.5", "- and top-selection ≈360. The three points order top < random < bottom, i.e. the", "- score axis carries the causal signal — removing it (or inverting it) removes", "- the gain.", "+ check run here: bottom-scored selection → **PPL 1045** vs random 462.5 and", "+ top-selection 366. The three points order top(366) < random(462) < bottom(1045),", "+ i.e. the score axis carries the causal signal — inverting it more than doubles", "+ perplexity, so the gain is not a length/dedup artifact shared by both tails.", " 2. **Composition shift.** The selected corpus is visibly a different population", " from the pool: near-zero navigation/boilerplate/near-duplicate fragments, and", " its register mix is engineered to ~25% technical-QA / ~75% prose+news to match"]}], "userModified": false, "replaceAll": false}