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- 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
|
| 175 |
-
"# The fused
|
| 176 |
-
"#
|
| 177 |
-
"#
|
| 178 |
-
"#
|
| 179 |
-
"#
|
| 180 |
-
"#
|
| 181 |
-
"#\n",
|
| 182 |
-
"#
|
| 183 |
-
"#
|
| 184 |
-
"#
|
| 185 |
-
"
|
| 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 |
-
"
|
| 195 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
"KEEP_COLS = [\"id\", \"image\", \"image_width\", \"image_height\",\n",
|
| 197 |
-
" \"caption\", \"fused_json\", \"age_audit\"]
|
| 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 |
-
"
|
| 205 |
-
"
|
| 206 |
-
"
|
| 207 |
-
"
|
| 208 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 217 |
-
"
|
| 218 |
-
" n_seen =
|
| 219 |
-
" shards_used = []\n",
|
| 220 |
-
"
|
| 221 |
-
"\n",
|
| 222 |
" for sh in shard_order:\n",
|
| 223 |
-
" if len(
|
| 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 |
-
"
|
| 229 |
-
"
|
| 230 |
-
"
|
| 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 |
-
"
|
| 237 |
-
"
|
| 238 |
-
"
|
| 239 |
-
"
|
| 240 |
-
"
|
| 241 |
-
"
|
| 242 |
-
"
|
| 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 |
-
"
|
| 250 |
-
"
|
| 251 |
-
"
|
| 252 |
-
"
|
| 253 |
-
"
|
| 254 |
-
"
|
| 255 |
-
"
|
| 256 |
-
"
|
| 257 |
-
"
|
| 258 |
-
"
|
| 259 |
-
"
|
| 260 |
-
"
|
| 261 |
-
"
|
| 262 |
-
"
|
| 263 |
-
"
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"
|
| 267 |
-
"\n",
|
| 268 |
-
"
|
| 269 |
-
"
|
| 270 |
-
"
|
| 271 |
-
"\n",
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
"
|
| 275 |
-
"\n",
|
| 276 |
-
"
|
| 277 |
-
"
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
"\n",
|
| 281 |
-
"
|
| 282 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
" \"
|
| 288 |
-
" \"train_rows\":
|
| 289 |
-
" \"eval_ids\":
|
| 290 |
" \"caption_rule\": \"caption_joycaption or prompt_fused fallback \"\n",
|
| 291 |
-
" \"(dexp011:154;
|
| 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: {
|
| 298 |
-
" f\"{len(shards_used)}/{len(ANIMA['SHARDS'])} shards
|
| 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",
|