| """Verify one fold's shards before training on them. |
| |
| Checks, per fold: |
| 1. census shard index row counts / letter sums per tier vs a fresh streaming pass |
| over the source train.jsonl.zst (expect small drops from the <95%-Greek |
| normalize filter, nothing else) |
| 2. leakage no shard record id (base id, #segN stripped) appears in the fold's |
| val.jsonl.zst or test.jsonl.zst |
| 3. loader MultiTierLoader yields records for gold/silver/bronze under stable_cfg |
| and gold under anneal_cfg with STOICHEIA_DATA at the fold root |
| 4. eval eval.intrinsic.held_out_records returns eval_n records |
| |
| Usage: |
| python data/verify_fold_shards.py --fold-src .../10-fold_split/fold_0 \ |
| --fold-root $STOICHEIA_DATA/folds/fold_0 |
| Exits non-zero on any failure. |
| """ |
| from __future__ import annotations |
|
|
| import argparse, json, subprocess, sys |
| from collections import Counter |
| from pathlib import Path |
|
|
| import numpy as np |
| import pyarrow.parquet as pq |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
|
|
| TRAIN_TIERS = {"pristine", "repaired", "bronze"} |
|
|
|
|
| def stream_ids(jsonl_zst, want_census=False): |
| proc = subprocess.Popen(["zstdcat", str(jsonl_zst)], stdout=subprocess.PIPE, text=True) |
| ids, n_rec, n_chars = set(), Counter(), Counter() |
| for line in proc.stdout: |
| rec = json.loads(line) |
| ids.add(rec["id"]) |
| if want_census: |
| n_rec[rec["tier"]] += 1 |
| n_chars[rec["tier"]] += len(rec.get("text", "")) |
| if proc.wait() != 0: |
| raise RuntimeError(f"zstdcat failed on {jsonl_zst}") |
| return ids, n_rec, n_chars |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--fold-src", required=True, help=".../10-fold_split/fold_k") |
| ap.add_argument("--fold-root", required=True, help="$STOICHEIA_DATA/folds/fold_k") |
| ap.add_argument("--eval-n", type=int, default=256) |
| a = ap.parse_args() |
| src, root = Path(a.fold_src), Path(a.fold_root) |
| fails = [] |
|
|
| |
| shard_tier_rows, shard_tier_letters, shard_ids = Counter(), Counter(), set() |
| for sd in ("v1_punct", "bronze_punct"): |
| idx = pq.read_table(root / "shards" / sd / "index.parquet") |
| tiers = idx.column("tier").to_numpy(zero_copy_only=False) |
| lens = idx.column("length").to_numpy() |
| offs = idx.column("offset").to_numpy() |
| ids = idx.column("id").to_pylist() |
| |
| if idx.num_rows: |
| ok = (offs[0] == 0 and np.all(offs[1:] == offs[:-1] + lens[:-1])) |
| nbytes = (root / "shards" / sd / "chars.bin").stat().st_size |
| if not ok or nbytes != int(offs[-1] + lens[-1]): |
| fails.append(f"{sd}: offsets not contiguous or chars.bin size mismatch") |
| for t in np.unique(tiers): |
| m = tiers == t |
| shard_tier_rows[str(t)] += int(m.sum()) |
| shard_tier_letters[str(t)] += int(lens[m].sum()) |
| shard_ids.update(i.split("#", 1)[0] for i in ids) |
| |
| v1 = pq.read_table(root / "shards" / "v1_punct" / "index.parquet") |
| tv = v1.column("tier").to_numpy(zero_copy_only=False) |
| first_rep = np.flatnonzero(tv == "repaired") |
| last_pri = np.flatnonzero(tv == "pristine") |
| if len(first_rep) and len(last_pri) and first_rep[0] < last_pri[-1]: |
| fails.append("v1_punct: not pristine-first ordered") |
|
|
| |
| _, src_rec, src_chars = stream_ids(src / "train.jsonl.zst", want_census=True) |
| print("tier source_recs shard_recs source_chars shard_letters") |
| for t in sorted(src_rec): |
| sr, hr = src_rec[t], shard_tier_rows.get(t, 0) |
| flag = "" |
| if t in TRAIN_TIERS: |
| drop = 1 - hr / max(sr, 1) |
| if drop > 0.05: |
| flag = " <-- FAIL >5% dropped" |
| fails.append(f"census: tier {t} lost {drop:.1%} of records") |
| |
| if shard_tier_letters.get(t, 0) > src_chars[t]: |
| fails.append(f"census: tier {t} shard letters exceed source chars") |
| else: |
| if hr: |
| fails.append(f"census: non-training tier {t} present in shards") |
| print(f"{t:<12}{sr:>11} {hr:>10} {src_chars[t]:>13} {shard_tier_letters.get(t,0):>13}{flag}") |
|
|
| |
| for split in ("val", "test"): |
| held_ids, _, _ = stream_ids(src / f"{split}.jsonl.zst") |
| inter = shard_ids & held_ids |
| if inter: |
| fails.append(f"leakage: {len(inter)} train shard ids in {split} (e.g. {sorted(inter)[:5]})") |
| print(f"leakage vs {split}: {len(inter)} overlaps / {len(held_ids)} {split} ids") |
|
|
| |
| from train.data import MultiTierLoader, stable_cfg, anneal_cfg |
| gdata = str(root) |
| for label, cfg in (("stable", stable_cfg(gdata)), ("anneal", anneal_cfg(gdata))): |
| ld = MultiTierLoader(cfg, rank=0, world_size=1) |
| counts = {n: len(ld.elig[n]) for n in ld.names} |
| want = {"gold", "silver", "bronze"} if label == "stable" else {"gold"} |
| if set(ld.names) != want: |
| fails.append(f"loader[{label}]: tiers {ld.names} != {sorted(want)}") |
| recs = list(ld.records(5)) |
| if len(recs) != 5 or any(len(r["chars"]) == 0 for r in recs): |
| fails.append(f"loader[{label}]: bad sample records") |
| print(f"loader[{label}]: eligible {counts}, sampled {len(recs)} records ok") |
|
|
| |
| from eval.intrinsic import held_out_records |
| recs = held_out_records(str(root / "shards" / "v1_punct"), a.eval_n) |
| if len(recs) < a.eval_n: |
| fails.append(f"eval: only {len(recs)}/{a.eval_n} held-out records") |
| print(f"eval holdout: {len(recs)}/{a.eval_n} records") |
|
|
| if fails: |
| print("\nVERIFY FAILED:") |
| for f in fails: |
| print(f" - {f}") |
| sys.exit(1) |
| print("\nVERIFY OK") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|