Stoicheia-code / data /verify_fold_shards.py
anonymous-stoicheia's picture
Upload folder using huggingface_hub
7ed86c3 verified
Raw
History Blame Contribute Delete
6.13 kB
"""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 = []
# ---- load shard indices ----
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()
# offsets must be contiguous and match chars.bin size
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_punct must be pristine-first (holdout arithmetic parity with flagship)
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")
# ---- 1. census vs source ----
_, 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")
# shard letters exclude punctuation/spaces, so only sanity-bound them
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}")
# ---- 2. leakage ----
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")
# ---- 3. loader dry-run ----
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")
# ---- 4. eval holdout ----
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()