hub sync for staged sources + cold-resume rehearsal script
Browse files- build/build_mix.py +72 -0
- build/hubsync.py +105 -0
- build/smoke_hub_resume.py +100 -0
build/build_mix.py
CHANGED
|
@@ -219,12 +219,34 @@ class State:
|
|
| 219 |
|
| 220 |
# --------------------------------------------------------------------- stage
|
| 221 |
def stage(args, st, tokenizer):
|
|
|
|
|
|
|
|
|
|
| 222 |
for src in SOURCES:
|
| 223 |
key = src_key(src)
|
| 224 |
rec = st.d["staged"].get(key, {})
|
| 225 |
if rec.get("complete"):
|
| 226 |
print(f"stage: skip {key} (complete)", flush=True)
|
| 227 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
want = min(src["tokens"], args.cap_tokens_per_source or src["tokens"])
|
| 229 |
if args.budget_tokens and st.staged_tokens_total() >= args.budget_tokens:
|
| 230 |
print("stage: overall budget reached", flush=True)
|
|
@@ -328,6 +350,19 @@ def stage(args, st, tokenizer):
|
|
| 328 |
"docs_seen": docs_seen, "dropped": dict(drops),
|
| 329 |
"target": want, "seconds": round(time.monotonic() - t0, 1)}
|
| 330 |
st.commit(new_h, new_k)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
el = time.monotonic() - t0
|
| 332 |
print(f"stage: {key} {got:,} tok of {want:,} from {docs_seen:,} docs; "
|
| 333 |
f"dup={drops['exact_dup']} keydup={drops['key_dup']} lang={drops['non_english']} "
|
|
@@ -433,6 +468,9 @@ def main():
|
|
| 433 |
ap.add_argument("--max-chars", type=int, default=100_000)
|
| 434 |
ap.add_argument("--sec-per-source", type=int, default=0)
|
| 435 |
ap.add_argument("--only", default="", help="comma list of config names to stage")
|
|
|
|
|
|
|
|
|
|
| 436 |
ap.add_argument("--stage-only", action="store_true")
|
| 437 |
ap.add_argument("--merge-only", action="store_true")
|
| 438 |
args = ap.parse_args()
|
|
@@ -451,7 +489,41 @@ def main():
|
|
| 451 |
print(f"tokenizer vocab={tk.get_vocab_size()} "
|
| 452 |
f"sha256={hashlib.sha256(open(tok_path, 'rb').read()).hexdigest()[:12]}", flush=True)
|
| 453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
st = State(args.root)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
t0 = time.monotonic()
|
| 456 |
if not args.merge_only:
|
| 457 |
stage(args, st, tk)
|
|
|
|
| 219 |
|
| 220 |
# --------------------------------------------------------------------- stage
|
| 221 |
def stage(args, st, tokenizer):
|
| 222 |
+
remote = HS.remote_stage_files(args.hub_repo) if (args.hub_repo and not args.merge_only) else set()
|
| 223 |
+
if args.hub_repo:
|
| 224 |
+
print(f"hub: {len(remote)} staged files already on {args.hub_repo}", flush=True)
|
| 225 |
for src in SOURCES:
|
| 226 |
key = src_key(src)
|
| 227 |
rec = st.d["staged"].get(key, {})
|
| 228 |
if rec.get("complete"):
|
| 229 |
print(f"stage: skip {key} (complete)", flush=True)
|
| 230 |
continue
|
| 231 |
+
if args.hub_repo:
|
| 232 |
+
# A source whose shards AND record are already on the Hub is done; pulling the record is
|
| 233 |
+
# cheaper than re-staging and keeps the merge's proportional weights honest.
|
| 234 |
+
have = [p for p in remote if p.startswith(f"stage/{key}/") and p.endswith(".bin")]
|
| 235 |
+
if have and f"stage/{key}/record.json" in remote:
|
| 236 |
+
try:
|
| 237 |
+
HS.download_missing(st.root, [key], args.hub_repo, remote=remote)
|
| 238 |
+
rec_json = json.load(open(os.path.join(st.stage_dir, key, "record.json")))
|
| 239 |
+
st.d["staged"][key] = {"complete": True, "shards": rec_json["shards"],
|
| 240 |
+
"tokens": rec_json["tokens"],
|
| 241 |
+
"docs_seen": rec_json["docs_seen"],
|
| 242 |
+
"dropped": rec_json["dropped"],
|
| 243 |
+
"target": rec_json["tokens"], "restored_from_hub": True}
|
| 244 |
+
st.commit([], [])
|
| 245 |
+
print(f"stage: restored {key} from Hub ({rec_json['tokens']:,} tok), skipping",
|
| 246 |
+
flush=True)
|
| 247 |
+
continue
|
| 248 |
+
except Exception as e:
|
| 249 |
+
print(f"!! restore failed for {key}: {type(e).__name__}: {str(e)[:140]}", flush=True)
|
| 250 |
want = min(src["tokens"], args.cap_tokens_per_source or src["tokens"])
|
| 251 |
if args.budget_tokens and st.staged_tokens_total() >= args.budget_tokens:
|
| 252 |
print("stage: overall budget reached", flush=True)
|
|
|
|
| 350 |
"docs_seen": docs_seen, "dropped": dict(drops),
|
| 351 |
"target": want, "seconds": round(time.monotonic() - t0, 1)}
|
| 352 |
st.commit(new_h, new_k)
|
| 353 |
+
# /kaggle/working is destroyed when the session ends, so a source that took 40 minutes to stage
|
| 354 |
+
# must not exist only there. Published per source, because that is the unit a resume can skip.
|
| 355 |
+
if complete and args.hub_repo:
|
| 356 |
+
with open(os.path.join(st.stage_dir, key, "record.json"), "w") as f:
|
| 357 |
+
json.dump({"key": key, "src": {k: v for k, v in src.items()},
|
| 358 |
+
"shards": shards, "tokens": got, "docs_seen": docs_seen,
|
| 359 |
+
"dropped": dict(drops)}, f, indent=1)
|
| 360 |
+
try:
|
| 361 |
+
n = HS.upload_source(st.root, key, args.hub_repo)
|
| 362 |
+
print(f" published {key} -> {args.hub_repo}/stage/{key} ({n} files)", flush=True)
|
| 363 |
+
except Exception as e:
|
| 364 |
+
print(f"!! publish FAILED for {key}: {type(e).__name__}: {str(e)[:160]} -- "
|
| 365 |
+
f"this source will have to be re-staged if the session dies", flush=True)
|
| 366 |
el = time.monotonic() - t0
|
| 367 |
print(f"stage: {key} {got:,} tok of {want:,} from {docs_seen:,} docs; "
|
| 368 |
f"dup={drops['exact_dup']} keydup={drops['key_dup']} lang={drops['non_english']} "
|
|
|
|
| 468 |
ap.add_argument("--max-chars", type=int, default=100_000)
|
| 469 |
ap.add_argument("--sec-per-source", type=int, default=0)
|
| 470 |
ap.add_argument("--only", default="", help="comma list of config names to stage")
|
| 471 |
+
ap.add_argument("--hub-repo", default="Cion-lab/ounce100m-mix-stage",
|
| 472 |
+
help="staged sources are published here so a session kill cannot lose them; "
|
| 473 |
+
"empty string disables sync")
|
| 474 |
ap.add_argument("--stage-only", action="store_true")
|
| 475 |
ap.add_argument("--merge-only", action="store_true")
|
| 476 |
args = ap.parse_args()
|
|
|
|
| 489 |
print(f"tokenizer vocab={tk.get_vocab_size()} "
|
| 490 |
f"sha256={hashlib.sha256(open(tok_path, 'rb').read()).hexdigest()[:12]}", flush=True)
|
| 491 |
|
| 492 |
+
global HS
|
| 493 |
+
HS = None
|
| 494 |
+
if args.hub_repo:
|
| 495 |
+
try:
|
| 496 |
+
import hubsync as HS_mod
|
| 497 |
+
HS = HS_mod
|
| 498 |
+
HS.ensure_repo(args.hub_repo)
|
| 499 |
+
except Exception as e:
|
| 500 |
+
print(f"!! hub sync unavailable: {type(e).__name__}: {str(e)[:160]}; "
|
| 501 |
+
f"build proceeds but is NOT interruption-safe", flush=True)
|
| 502 |
+
HS = None
|
| 503 |
+
|
| 504 |
st = State(args.root)
|
| 505 |
+
if args.hub_repo and not st.d["staged"]:
|
| 506 |
+
# The state file lives on /kaggle/working too, so in a fresh session it is empty even when the
|
| 507 |
+
# Hub holds a complete stage. Rebuild the stage bookkeeping from the Hub's record.json files --
|
| 508 |
+
# without this, --merge-only on a new instance would find nothing and quietly emit an empty mix.
|
| 509 |
+
try:
|
| 510 |
+
remote = HS.remote_stage_files(args.hub_repo)
|
| 511 |
+
keys = sorted({p.split("/")[1] for p in remote if p.startswith("stage/") and "/" in p})
|
| 512 |
+
pulled = HS.download_missing(args.root, keys, args.hub_repo, remote=remote)
|
| 513 |
+
for k in keys:
|
| 514 |
+
rp = os.path.join(args.root, "stage", k, "record.json")
|
| 515 |
+
if not os.path.exists(rp):
|
| 516 |
+
continue
|
| 517 |
+
j = json.load(open(rp))
|
| 518 |
+
st.d["staged"][k] = {"complete": True, "shards": j["shards"], "tokens": j["tokens"],
|
| 519 |
+
"docs_seen": j["docs_seen"], "dropped": j["dropped"],
|
| 520 |
+
"target": j["tokens"], "restored_from_hub": True}
|
| 521 |
+
st.commit([], [])
|
| 522 |
+
print(f"hub: restored {len(st.d['staged'])} staged source(s) "
|
| 523 |
+
f"({st.staged_tokens_total():,} tokens)", flush=True)
|
| 524 |
+
except Exception as e:
|
| 525 |
+
print(f"!! hub restore failed: {type(e).__name__}: {str(e)[:160]}", flush=True)
|
| 526 |
+
|
| 527 |
t0 = time.monotonic()
|
| 528 |
if not args.merge_only:
|
| 529 |
stage(args, st, tk)
|
build/hubsync.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hub sync for the mix build: the instance is scratch, so nothing that took an hour to produce may live
|
| 2 |
+
# only there (§3.13 "never let the instance be the only copy", applied to a CPU job rather than a
|
| 3 |
+
# checkpoint).
|
| 4 |
+
#
|
| 5 |
+
# Granularity is one *source*: staging a source takes 5-40 minutes, so publishing its shards the moment
|
| 6 |
+
# it finishes bounds the loss from any interruption to the one source in flight, and a resume can skip
|
| 7 |
+
# every source already on the Hub without re-reading it.
|
| 8 |
+
#
|
| 9 |
+
# The stage repo is private-in-practice but public by project rule; it holds no credentials and no
|
| 10 |
+
# benchmark material -- only token ids from the approved mix.
|
| 11 |
+
|
| 12 |
+
import json
|
| 13 |
+
import os
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def repo_id_from_env(default=None):
|
| 17 |
+
return os.environ.get("OUNCE100M_STAGE_REPO") or default
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def api():
|
| 21 |
+
import os
|
| 22 |
+
|
| 23 |
+
import ounce100m_credentials
|
| 24 |
+
|
| 25 |
+
ounce100m_credentials.install()
|
| 26 |
+
from huggingface_hub import HfApi
|
| 27 |
+
|
| 28 |
+
return HfApi(token=os.environ["HF_TOKEN"])
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def ensure_repo(repo):
|
| 32 |
+
from huggingface_hub import create_repo
|
| 33 |
+
|
| 34 |
+
# hub 1.11 (the image's version) takes the owner inside repo_id and has no `namespace` kwarg (E-013)
|
| 35 |
+
create_repo(repo_id=repo, repo_type="dataset", private=False, exist_ok=True)
|
| 36 |
+
return repo
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def remote_stage_files(repo):
|
| 40 |
+
"""Set of relative paths under stage/ that already exist on the Hub."""
|
| 41 |
+
import urllib.request
|
| 42 |
+
|
| 43 |
+
url = f"https://huggingface.co/api/datasets/{repo}/tree/main/stage?recursive=true"
|
| 44 |
+
try:
|
| 45 |
+
with urllib.request.urlopen(url, timeout=90) as r:
|
| 46 |
+
entries = json.loads(r.read().decode())
|
| 47 |
+
except Exception:
|
| 48 |
+
return set()
|
| 49 |
+
out = set()
|
| 50 |
+
for e in entries:
|
| 51 |
+
if e.get("type") != "file":
|
| 52 |
+
continue
|
| 53 |
+
out.add(e["path"])
|
| 54 |
+
# a directory in the tree listing means we must recurse; recursive=true should cover it, but
|
| 55 |
+
# if the server ignored the flag we get dirs and no files, which the caller detects as empty
|
| 56 |
+
return out
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def upload_source(root, key, repo):
|
| 60 |
+
"""Publish stage/<key>/* in one commit. Returns the file names pushed."""
|
| 61 |
+
a = api()
|
| 62 |
+
local = os.path.join(root, "stage", key)
|
| 63 |
+
files = sorted(f for f in os.listdir(local) if f.endswith((".bin", ".json")))
|
| 64 |
+
if not files:
|
| 65 |
+
return []
|
| 66 |
+
a.upload_folder(folder_path=local, path_in_repo=f"stage/{key}", repo_id=repo,
|
| 67 |
+
repo_type="dataset", commit_message=f"stage {key}: {len(files)} shard(s)",
|
| 68 |
+
allow_patterns=["*.bin", "*.json"])
|
| 69 |
+
return files
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def download_missing(root, keys_wanted, repo, remote=None):
|
| 73 |
+
"""Fetch stage/<key>/*.bin for every wanted key not already on disk. Returns {key: [filenames]}."""
|
| 74 |
+
from huggingface_hub import hf_hub_download
|
| 75 |
+
|
| 76 |
+
remote = remote if remote is not None else remote_stage_files(repo)
|
| 77 |
+
got = {}
|
| 78 |
+
for key in keys_wanted:
|
| 79 |
+
want = sorted(p for p in remote if p.startswith(f"stage/{key}/") and p.endswith(".bin"))
|
| 80 |
+
if not want:
|
| 81 |
+
got[key] = []
|
| 82 |
+
continue
|
| 83 |
+
local_dir = os.path.join(root, "stage", key)
|
| 84 |
+
os.makedirs(local_dir, exist_ok=True)
|
| 85 |
+
have = {f"stage/{key}/{f}" for f in os.listdir(local_dir)}
|
| 86 |
+
for rp in want:
|
| 87 |
+
if rp in have:
|
| 88 |
+
continue
|
| 89 |
+
hf_hub_download(repo_id=repo, repo_type="dataset", filename=rp,
|
| 90 |
+
local_dir=root, force_download=False)
|
| 91 |
+
got[key] = sorted(os.listdir(local_dir))
|
| 92 |
+
return got
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def status(repo):
|
| 96 |
+
import urllib.request
|
| 97 |
+
|
| 98 |
+
url = f"https://huggingface.co/api/datasets/{repo}"
|
| 99 |
+
try:
|
| 100 |
+
with urllib.request.urlopen(url, timeout=60) as r:
|
| 101 |
+
d = json.loads(r.read().decode())
|
| 102 |
+
return {"exists": True, "gated": d.get("gated"), "private": d.get("private"),
|
| 103 |
+
"lastModified": d.get("lastModified"), "id": d.get("id")}
|
| 104 |
+
except Exception as e:
|
| 105 |
+
return {"exists": False, "error": f"{type(e).__name__}: {str(e)[:120]}"}
|
build/smoke_hub_resume.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Cold-resume rehearsal for the mix builder, in one session.
|
| 2 |
+
#
|
| 3 |
+
# What it proves and why it matters: /kaggle/working is destroyed when a Kaggle session ends, so the
|
| 4 |
+
# staged shards AND build_state.json are gone even though the Hub still holds the stage. The full build is
|
| 5 |
+
# hours of streaming; if a session kill meant starting that over, the build would never finish. This
|
| 6 |
+
# script therefore:
|
| 7 |
+
# 1. stages two small sources and publishes them to a throwaway stage repo,
|
| 8 |
+
# 2. WIPES the local tree -- a faithful simulation of the instance dying, not a soft restart,
|
| 9 |
+
# 3. runs --merge-only against the Hub alone, which must restore the stage bookkeeping from the Hub's
|
| 10 |
+
# record.json files and emit a mixed shard containing both sources,
|
| 11 |
+
# 4. runs verify_mix.py over the result, and
|
| 12 |
+
# 5. deletes the throwaway repo so no test artifact is mistaken for the real stage later.
|
| 13 |
+
#
|
| 14 |
+
# Step 3 is the one that would catch a silent failure: with an empty state and no restore path, merge
|
| 15 |
+
# would find nothing and emit an empty mix while still exiting 0.
|
| 16 |
+
#
|
| 17 |
+
# CPU session, zero GPU quota. No credentials in this file; they come from the private store (D-006).
|
| 18 |
+
|
| 19 |
+
import json
|
| 20 |
+
import os
|
| 21 |
+
import shutil
|
| 22 |
+
import subprocess
|
| 23 |
+
import sys
|
| 24 |
+
import time
|
| 25 |
+
|
| 26 |
+
ROOT = "/kaggle/working/mixroot"
|
| 27 |
+
REPO = "Cion-lab/ounce100m-mix-stagetest"
|
| 28 |
+
TWO = "cosmopedia__khanacademy,SimpleStories__default"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def sh(argv, label):
|
| 32 |
+
print(f"=== {label}", flush=True)
|
| 33 |
+
p = subprocess.run([sys.executable] + argv, cwd="/kaggle/working", capture_output=True, text=True,
|
| 34 |
+
timeout=3600)
|
| 35 |
+
out = p.stdout
|
| 36 |
+
for line in out.splitlines():
|
| 37 |
+
print(" |", line[:220], flush=True)
|
| 38 |
+
if p.returncode != 0:
|
| 39 |
+
print(" STDERR:", (p.stderr or "")[-1500:], flush=True)
|
| 40 |
+
return {"rc": p.returncode, "out": out[-6000:], "err": (p.stderr or "")[-1500:]}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def grab_json(text, begin, end):
|
| 44 |
+
if begin in text and end in text:
|
| 45 |
+
try:
|
| 46 |
+
return json.loads(text.split(begin, 1)[1].split(end, 1)[0])
|
| 47 |
+
except Exception:
|
| 48 |
+
return None
|
| 49 |
+
return None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def main():
|
| 53 |
+
R = {"repo": REPO, "sources": TWO}
|
| 54 |
+
base = ["build_mix.py", "--root", ROOT, "--only", TWO, "--cap-tokens-per-source", "1200000",
|
| 55 |
+
"--hub-repo", REPO]
|
| 56 |
+
|
| 57 |
+
shutil.rmtree(ROOT, ignore_errors=True)
|
| 58 |
+
r1 = sh(base + ["--stage-only"], "1. stage two sources and publish")
|
| 59 |
+
R["stage"] = {"rc": r1["rc"], "json": grab_json(r1["out"], "BUILD_JSON_BEGIN", "BUILD_JSON_END")}
|
| 60 |
+
|
| 61 |
+
# the wipe IS the test: after this, nothing local survives except this script and the code
|
| 62 |
+
shutil.rmtree(ROOT, ignore_errors=True)
|
| 63 |
+
R["wiped"] = {"root_exists": os.path.exists(ROOT),
|
| 64 |
+
"stage_dir_exists": os.path.exists(os.path.join(ROOT, "stage"))}
|
| 65 |
+
|
| 66 |
+
r2 = sh(base + ["--merge-only"], "2. cold resume: merge from the Hub with an empty local tree")
|
| 67 |
+
R["merge"] = {"rc": r2["rc"], "json": grab_json(r2["out"], "BUILD_JSON_BEGIN", "BUILD_JSON_END")}
|
| 68 |
+
|
| 69 |
+
r3 = sh(["verify_mix.py", "--root", ROOT, "--min-sources-per-shard", "2",
|
| 70 |
+
"--sample-decodes", "3"], "3. verify the restored mix")
|
| 71 |
+
R["verify"] = grab_json(r3["out"], "VERIFY_JSON_BEGIN", "VERIFY_JSON_END")
|
| 72 |
+
|
| 73 |
+
staged = (R["merge"].get("json") or {}).get("total_tokens", 0)
|
| 74 |
+
R["COLD_RESUME_PASSED"] = bool(
|
| 75 |
+
r1["rc"] == 0 and r2["rc"] == 0 and staged > 1_000_000
|
| 76 |
+
and (R["verify"] or {}).get("PASS") is True
|
| 77 |
+
and (R["verify"] or {}).get("distinct_sources_in_mix") == 2)
|
| 78 |
+
|
| 79 |
+
# cleanup: the throwaway stage repo must not outlive the test, or a later real build could restore
|
| 80 |
+
# capped 1.2M-token sources and believe they were complete
|
| 81 |
+
def cleanup():
|
| 82 |
+
import ounce100m_credentials
|
| 83 |
+
ounce100m_credentials.install()
|
| 84 |
+
from huggingface_hub import delete_repo
|
| 85 |
+
delete_repo(repo_id=REPO, repo_type="dataset", token=os.environ["HF_TOKEN"])
|
| 86 |
+
return "deleted"
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
R["cleanup"] = cleanup()
|
| 90 |
+
except Exception as e:
|
| 91 |
+
R["cleanup"] = {"error": f"{type(e).__name__}: {str(e)[:200]}",
|
| 92 |
+
"action_needed": f"delete the throwaway dataset {REPO} manually"}
|
| 93 |
+
|
| 94 |
+
print("SMOKE_JSON_BEGIN")
|
| 95 |
+
print(json.dumps(R, indent=1, default=str)[:6000])
|
| 96 |
+
print("SMOKE_JSON_END")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
main()
|