AbstractPhil commited on
Commit
b493b91
·
verified ·
1 Parent(s): 283127c

CELL 1 v5: properly columnar two-phase split. Phase A thin scan (id/captions/audit) with pyarrow.compute masks — exp011 gate, dexp011:154 caption coalesce using the forks own CAPTION_SENTINELS — stop at target; phase B heavy write filtered by is_in(id, split sets), image bytes never leave Arrow. Row-by-row loop removed. Both phases executed green against a real shard in 0.5s.

Browse files
Files changed (1) hide show
  1. colab/anima_closeout.ipynb +134 -93
colab/anima_closeout.ipynb CHANGED
@@ -171,132 +171,173 @@
171
  "execution_count": null,
172
  "outputs": [],
173
  "source": [
174
- "# \u2550\u2550\u2550 CELL 1 \u2014 materialize the gated split INCREMENTALLY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
175
- "# The fused dataset is ~160GB across 200+ shards; the campaign needs ~9k\n",
176
- "# rows. So: NEVER download the corpus. Shards are visited in sha256(name)\n",
177
- "# order (deterministic, free of curation-order bias), one at a time; each\n",
178
- "# is gated + deduped + slimmed into the pool, then its source file is\n",
179
- "# DELETED; we stop the moment the row target is met. Disk stays bounded at\n",
180
- "# roughly one source shard + the materialized output.\n",
181
- "#\n",
182
- "# Row rules, verbatim from the campaign record:\n",
183
- "# AUDIT GATE (pod2/dexp011:98-105): age_audit/audit must contain\n",
184
- "# \"approved\" or \"pass\" (case-insensitive); gated rows counted loudly.\n",
185
- "# CAPTION = caption_joycaption or prompt_fused fallback (dexp011:154;\n",
186
- "# cond = \"caption_joycaption nl77\"), materialized as `caption`.\n",
187
- "# DEDUP by id (first occurrence wins).\n",
188
- "# SPLIT: within the pool, sha256(id) ascending; first EVAL_N are eval.\n",
189
- "import hashlib, json, os, shutil\n",
190
  "import pyarrow as pa\n",
 
191
  "import pyarrow.parquet as pq\n",
192
  "from huggingface_hub import hf_hub_download\n",
193
  "\n",
194
- "TRAIN_TARGET = 8192 # ~2.7x exp004's criticized 2978; 10k steps @ mbs8\n",
195
- "EVAL_N = 512 # = 80k samples ~= 9 epochs over this pool\n",
 
 
 
 
196
  "KEEP_COLS = [\"id\", \"image\", \"image_width\", \"image_height\",\n",
197
- " \"caption\", \"fused_json\", \"age_audit\"] # slim rows only\n",
198
  "\n",
199
  "TRAIN_DIR = DATA / \"train\"; TRAIN_DIR.mkdir(exist_ok=True)\n",
200
  "EVAL_PARQUET = DATA / \"eval.parquet\"\n",
201
  "MANIFEST = DATA / \"split_manifest.json\"\n",
202
  "SRC = DATA / \"src\"; SRC.mkdir(exist_ok=True)\n",
203
- "\n",
204
- "def _passes_gate(val_of):\n",
205
- " vals = [str(val_of(c)) for c in (\"age_audit\", \"audit\")\n",
206
- " if val_of(c) is not None]\n",
207
- " return (not vals) or any((\"approved\" in v.lower() or \"pass\" in v.lower())\n",
208
- " for v in vals)\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  "\n",
210
  "if not MANIFEST.exists():\n",
211
- " # deterministic pseudo-random shard order \u2014 never curation order\n",
212
  " shard_order = sorted(\n",
213
  " ANIMA[\"SHARDS\"],\n",
214
  " key=lambda n: hashlib.sha256(n.encode()).hexdigest())\n",
215
  "\n",
216
- " pool = [] # slim row dicts\n",
217
- " seen_ids = set()\n",
218
- " n_seen = n_gated = n_dup = 0\n",
219
- " shards_used = []\n",
220
- " target = TRAIN_TARGET + EVAL_N\n",
221
- "\n",
222
  " for sh in shard_order:\n",
223
- " if len(pool) >= target:\n",
224
  " break\n",
225
  " local = hf_hub_download(ANIMA[\"DATASET\"], sh, repo_type=\"dataset\",\n",
226
  " local_dir=str(SRC))\n",
 
 
227
  " pf = pq.ParquetFile(local)\n",
228
- " names = pf.schema_arrow.names\n",
229
- " cols = [c for c in (\"id\", \"image\", \"image_width\", \"image_height\",\n",
230
- " \"caption_joycaption\", \"prompt_fused\",\n",
231
- " \"fused_json\", \"age_audit\", \"audit\")\n",
232
- " if c in names]\n",
233
- " for batch in pf.iter_batches(batch_size=32, columns=cols):\n",
234
- " if len(pool) >= target:\n",
235
  " break\n",
236
- " data = {c: batch.column(c) for c in\n",
237
- " (set(cols) & set(batch.schema.names))}\n",
238
- " for j in range(batch.num_rows):\n",
239
- " if len(pool) >= target:\n",
240
- " break\n",
241
- " n_seen += 1\n",
242
- " get = lambda c: (data[c][j].as_py() if c in data else None)\n",
243
- " if not _passes_gate(get):\n",
244
- " n_gated += 1\n",
245
- " continue\n",
246
- " rid = str(get(\"id\"))\n",
247
- " if rid in seen_ids:\n",
248
  " n_dup += 1\n",
249
- " continue\n",
250
- " cap = get(\"caption_joycaption\") or get(\"prompt_fused\") or \"\"\n",
251
- " if not str(cap).strip():\n",
252
- " n_gated += 1 # empty caption: unusable row\n",
253
- " continue\n",
254
- " seen_ids.add(rid)\n",
255
- " pool.append({\n",
256
- " \"id\": rid, \"image\": get(\"image\"),\n",
257
- " \"image_width\": get(\"image_width\"),\n",
258
- " \"image_height\": get(\"image_height\"),\n",
259
- " \"caption\": str(cap),\n",
260
- " \"fused_json\": str(get(\"fused_json\") or \"\"),\n",
261
- " \"age_audit\": str(get(\"age_audit\") or \"\")})\n",
262
- " shards_used.append(sh)\n",
263
- " shutil.rmtree(SRC, ignore_errors=True) # bound the disk\n",
264
- " SRC.mkdir(exist_ok=True)\n",
265
- " print(f\"[split] {sh}: pool {len(pool)}/{target} \"\n",
266
- " f\"(seen {n_seen}, gated {n_gated}, dup {n_dup})\", flush=True)\n",
267
- "\n",
268
- " assert len(pool) >= target, (\n",
269
- " f\"exhausted ALL shards with only {len(pool)} usable rows \"\n",
270
- " f\"(target {target}) \u2014 inspect the gate before training\")\n",
271
- "\n",
272
- " # split within the pool: sha256(id) ascending, first EVAL_N are eval\n",
273
- " pool.sort(key=lambda r: hashlib.sha256(r[\"id\"].encode()).hexdigest())\n",
274
- " eval_rows, train_rows = pool[:EVAL_N], pool[EVAL_N:target]\n",
275
- "\n",
276
- " def _write(rows, path):\n",
277
- " tbl = pa.table({c: [r[c] for r in rows] for c in KEEP_COLS})\n",
278
- " pq.write_table(tbl, path)\n",
279
- " return tbl.num_rows\n",
280
- "\n",
281
- " ne = _write(eval_rows, EVAL_PARQUET)\n",
282
- " nt = _write(train_rows, TRAIN_DIR / \"train.parquet\")\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  " MANIFEST.write_text(json.dumps(\n",
284
  " {\"dataset\": ANIMA[\"DATASET\"],\n",
285
  " \"shard_order\": \"sha256(shard_name) ascending\",\n",
286
  " \"shards_used\": shards_used, \"rows_seen\": n_seen,\n",
287
- " \"gated_or_empty\": n_gated, \"duplicate_ids\": n_dup,\n",
288
- " \"train_rows\": nt, \"eval_n\": ne,\n",
289
- " \"eval_ids\": [r[\"id\"] for r in eval_rows],\n",
290
  " \"caption_rule\": \"caption_joycaption or prompt_fused fallback \"\n",
291
- " \"(dexp011:154; cond = caption_joycaption nl77)\",\n",
292
  " \"gate_rule\": \"age_audit/audit must contain approved|pass \"\n",
293
  " \"(dexp011:98-105)\",\n",
294
  " \"split_rule\": \"sha256(id) ascending within the pool; first \"\n",
295
  " \"EVAL_N are eval\"},\n",
296
  " indent=1))\n",
297
- " print(f\"split DONE: {nt} train + {ne} eval from \"\n",
298
- " f\"{len(shards_used)}/{len(ANIMA['SHARDS'])} shards \"\n",
299
- " f\"(seen {n_seen}, gated/empty {n_gated}, dup {n_dup})\")\n",
300
  "else:\n",
301
  " _m = json.loads(MANIFEST.read_text())\n",
302
  " print(f\"split already materialized: {_m['train_rows']} train + \"\n",
 
171
  "execution_count": null,
172
  "outputs": [],
173
  "source": [
174
+ "# \u2550\u2550\u2550 CELL 1 \u2014 materialize the gated split (COLUMNAR, two-phase) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
175
+ "# The fused set is ~160GB; the campaign needs ~9k rows. Two bounded phases:\n",
176
+ "# A) THIN SCAN: shards in sha256(name) order; per row group, read ONLY\n",
177
+ "# id/captions/audit and evaluate everything as pyarrow.compute masks \u2014\n",
178
+ "# the exp011 audit gate, the dexp011:154 caption coalesce (using the\n",
179
+ "# fork's own CAPTION_SENTINELS), id-dedup. Stop at the row target.\n",
180
+ "# B) HEAVY WRITE: re-read just the shards phase A used, filter row groups\n",
181
+ "# by is_in(id, split set), append the derived caption column, stream\n",
182
+ "# to train/eval parquet. Image bytes never pass through Python.\n",
183
+ "# Split rule: within the pooled ids, sha256(id) ascending; first EVAL_N\n",
184
+ "# are eval. Sources deleted after phase B.\n",
185
+ "import hashlib, json, shutil, sys\n",
 
 
 
 
186
  "import pyarrow as pa\n",
187
+ "import pyarrow.compute as pc\n",
188
  "import pyarrow.parquet as pq\n",
189
  "from huggingface_hub import hf_hub_download\n",
190
  "\n",
191
+ "sys.path.insert(0, str(FORK))\n",
192
+ "from utils.parquet_source import CAPTION_SENTINELS # the fork's own set\n",
193
+ "\n",
194
+ "TRAIN_TARGET = 8192 # ~2.7x exp004's criticized 2978 rows\n",
195
+ "EVAL_N = 512 # 10k steps @ mbs8 = 80k samples ~= 9 epochs\n",
196
+ "TARGET = TRAIN_TARGET + EVAL_N\n",
197
  "KEEP_COLS = [\"id\", \"image\", \"image_width\", \"image_height\",\n",
198
+ " \"caption\", \"fused_json\", \"age_audit\"]\n",
199
  "\n",
200
  "TRAIN_DIR = DATA / \"train\"; TRAIN_DIR.mkdir(exist_ok=True)\n",
201
  "EVAL_PARQUET = DATA / \"eval.parquet\"\n",
202
  "MANIFEST = DATA / \"split_manifest.json\"\n",
203
  "SRC = DATA / \"src\"; SRC.mkdir(exist_ok=True)\n",
204
+ "_SENT = pa.array(sorted(CAPTION_SENTINELS))\n",
205
+ "\n",
206
+ "def _as_clean_str(tbl, name):\n",
207
+ " \"\"\"Column -> trimmed string array with sentinels/nulls -> null.\"\"\"\n",
208
+ " if name not in tbl.schema.names:\n",
209
+ " return pa.nulls(tbl.num_rows, pa.string())\n",
210
+ " col = pc.utf8_trim_whitespace(\n",
211
+ " pc.fill_null(pc.cast(tbl.column(name), pa.string()), \"\"))\n",
212
+ " bad = pc.is_in(col, value_set=_SENT)\n",
213
+ " return pc.if_else(bad, pa.nulls(tbl.num_rows, pa.string()), col)\n",
214
+ "\n",
215
+ "def caption_vec(tbl):\n",
216
+ " \"\"\"dexp011:154, vectorized: joycaption else prompt_fused else null.\"\"\"\n",
217
+ " return pc.coalesce(_as_clean_str(tbl, \"caption_joycaption\"),\n",
218
+ " _as_clean_str(tbl, \"prompt_fused\"))\n",
219
+ "\n",
220
+ "def gate_vec(tbl):\n",
221
+ " \"\"\"exp011 audit gate, vectorized: every present audit column must\n",
222
+ " contain approved|pass (rows with NO audit columns pass).\"\"\"\n",
223
+ " mask = pa.array([True] * tbl.num_rows)\n",
224
+ " for c in (\"age_audit\", \"audit\"):\n",
225
+ " if c in tbl.schema.names:\n",
226
+ " low = pc.utf8_lower(\n",
227
+ " pc.fill_null(pc.cast(tbl.column(c), pa.string()), \"\"))\n",
228
+ " ok = pc.or_(pc.match_substring(low, \"pass\"),\n",
229
+ " pc.match_substring(low, \"approved\"))\n",
230
+ " # null/absent value on a present column = not gated (as exp011:\n",
231
+ " # gate_vals only collects non-None values)\n",
232
+ " ok = pc.or_(ok, pc.equal(low, \"\"))\n",
233
+ " mask = pc.and_(mask, ok)\n",
234
+ " return mask\n",
235
  "\n",
236
  "if not MANIFEST.exists():\n",
 
237
  " shard_order = sorted(\n",
238
  " ANIMA[\"SHARDS\"],\n",
239
  " key=lambda n: hashlib.sha256(n.encode()).hexdigest())\n",
240
  "\n",
241
+ " # \u2500\u2500 phase A: thin columnar scan to select the pool \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
242
+ " pool_ids, seen = [], set()\n",
243
+ " n_seen = n_dropped = n_dup = 0\n",
244
+ " shards_used, local_paths = [], {}\n",
245
+ " THIN = [\"id\", \"caption_joycaption\", \"prompt_fused\", \"age_audit\", \"audit\"]\n",
 
246
  " for sh in shard_order:\n",
247
+ " if len(pool_ids) >= TARGET:\n",
248
  " break\n",
249
  " local = hf_hub_download(ANIMA[\"DATASET\"], sh, repo_type=\"dataset\",\n",
250
  " local_dir=str(SRC))\n",
251
+ " local_paths[sh] = local\n",
252
+ " shards_used.append(sh)\n",
253
  " pf = pq.ParquetFile(local)\n",
254
+ " cols = [c for c in THIN if c in pf.schema_arrow.names]\n",
255
+ " for rg in range(pf.num_row_groups):\n",
256
+ " if len(pool_ids) >= TARGET:\n",
 
 
 
 
257
  " break\n",
258
+ " tbl = pf.read_row_group(rg, columns=cols)\n",
259
+ " n_seen += tbl.num_rows\n",
260
+ " keep = pc.and_(gate_vec(tbl), pc.is_valid(caption_vec(tbl)))\n",
261
+ " n_dropped += tbl.num_rows - pc.sum(keep).as_py()\n",
262
+ " ids = tbl.column(\"id\").filter(keep).to_pylist() # THIN data only\n",
263
+ " for rid in map(str, ids):\n",
264
+ " if rid in seen:\n",
 
 
 
 
 
265
  " n_dup += 1\n",
266
+ " elif len(pool_ids) < TARGET:\n",
267
+ " seen.add(rid)\n",
268
+ " pool_ids.append(rid)\n",
269
+ " print(f\"[scan] {sh}: pool {len(pool_ids)}/{TARGET} \"\n",
270
+ " f\"(seen {n_seen}, dropped {n_dropped}, dup {n_dup})\",\n",
271
+ " flush=True)\n",
272
+ "\n",
273
+ " assert len(pool_ids) >= TARGET, (\n",
274
+ " f\"exhausted ALL shards with only {len(pool_ids)} usable rows \"\n",
275
+ " f\"(target {TARGET}) \u2014 inspect the gate before training\")\n",
276
+ "\n",
277
+ " ranked = sorted(pool_ids,\n",
278
+ " key=lambda r: hashlib.sha256(r.encode()).hexdigest())\n",
279
+ " eval_ids = pa.array(ranked[:EVAL_N])\n",
280
+ " train_ids = pa.array(ranked[EVAL_N:TARGET])\n",
281
+ "\n",
282
+ " # \u2500\u2500 phase B: heavy columnar write, filtered by id sets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
283
+ " HEAVY = [\"id\", \"image\", \"image_width\", \"image_height\",\n",
284
+ " \"caption_joycaption\", \"prompt_fused\", \"fused_json\", \"age_audit\"]\n",
285
+ " writers = {}\n",
286
+ " counts = {\"train\": 0, \"eval\": 0}\n",
287
+ "\n",
288
+ " def _emit(which, sub, path):\n",
289
+ " cap = caption_vec(sub)\n",
290
+ " out = pa.table({\n",
291
+ " \"id\": sub.column(\"id\"), \"image\": sub.column(\"image\"),\n",
292
+ " \"image_width\": sub.column(\"image_width\"),\n",
293
+ " \"image_height\": sub.column(\"image_height\"),\n",
294
+ " \"caption\": pc.fill_null(cap, \"\"),\n",
295
+ " \"fused_json\": _f(sub, \"fused_json\"),\n",
296
+ " \"age_audit\": _f(sub, \"age_audit\")})\n",
297
+ " if which not in writers:\n",
298
+ " writers[which] = pq.ParquetWriter(path, out.schema)\n",
299
+ " writers[which].write_table(out)\n",
300
+ " counts[which] += out.num_rows\n",
301
+ "\n",
302
+ " def _f(t, name):\n",
303
+ " return (pc.fill_null(pc.cast(t.column(name), pa.string()), \"\")\n",
304
+ " if name in t.schema.names\n",
305
+ " else pa.array([\"\"] * t.num_rows))\n",
306
+ "\n",
307
+ " for sh in shards_used:\n",
308
+ " pf = pq.ParquetFile(local_paths[sh])\n",
309
+ " cols = [c for c in HEAVY if c in pf.schema_arrow.names]\n",
310
+ " for rg in range(pf.num_row_groups):\n",
311
+ " tbl = pf.read_row_group(rg, columns=cols)\n",
312
+ " ids = pc.cast(tbl.column(\"id\"), pa.string())\n",
313
+ " m_ev = pc.is_in(ids, value_set=eval_ids)\n",
314
+ " m_tr = pc.is_in(ids, value_set=train_ids)\n",
315
+ " if pc.any(m_ev).as_py():\n",
316
+ " _emit(\"eval\", tbl.filter(m_ev), EVAL_PARQUET)\n",
317
+ " if pc.any(m_tr).as_py():\n",
318
+ " _emit(\"train\", tbl.filter(m_tr),\n",
319
+ " TRAIN_DIR / \"train.parquet\")\n",
320
+ " for w in writers.values():\n",
321
+ " w.close()\n",
322
+ " shutil.rmtree(SRC, ignore_errors=True) # sources no longer needed\n",
323
+ "\n",
324
+ " assert counts == {\"train\": TRAIN_TARGET, \"eval\": EVAL_N}, counts\n",
325
  " MANIFEST.write_text(json.dumps(\n",
326
  " {\"dataset\": ANIMA[\"DATASET\"],\n",
327
  " \"shard_order\": \"sha256(shard_name) ascending\",\n",
328
  " \"shards_used\": shards_used, \"rows_seen\": n_seen,\n",
329
+ " \"dropped_gate_or_caption\": n_dropped, \"duplicate_ids\": n_dup,\n",
330
+ " \"train_rows\": counts[\"train\"], \"eval_n\": counts[\"eval\"],\n",
331
+ " \"eval_ids\": eval_ids.to_pylist(),\n",
332
  " \"caption_rule\": \"caption_joycaption or prompt_fused fallback \"\n",
333
+ " \"(dexp011:154; fork CAPTION_SENTINELS applied)\",\n",
334
  " \"gate_rule\": \"age_audit/audit must contain approved|pass \"\n",
335
  " \"(dexp011:98-105)\",\n",
336
  " \"split_rule\": \"sha256(id) ascending within the pool; first \"\n",
337
  " \"EVAL_N are eval\"},\n",
338
  " indent=1))\n",
339
+ " print(f\"split DONE: {counts['train']} train + {counts['eval']} eval \"\n",
340
+ " f\"from {len(shards_used)}/{len(ANIMA['SHARDS'])} shards\")\n",
 
341
  "else:\n",
342
  " _m = json.loads(MANIFEST.read_text())\n",
343
  " print(f\"split already materialized: {_m['train_rows']} train + \"\n",