perm_sha: identify the visit order by hashing the permutation, not just its seed (E-035 residual) -- cursor gains shuffle_perm_sha, asserted on resume; session repins the two files
Browse files- train/train_ounce100m.py +13 -6
train/train_ounce100m.py
CHANGED
|
@@ -125,10 +125,11 @@ class HubPush(TrainerCallback):
|
|
| 125 |
"""
|
| 126 |
|
| 127 |
def __init__(self, repo, api, token, seq_len, seed, fingerprint, seqs_per_step,
|
| 128 |
-
prune=True, min_free_gb=6.0, repo_type="dataset", stop_after_steps=0):
|
| 129 |
self.repo, self.api, self.token = repo, api, token
|
| 130 |
self.seq_len, self.seed, self.fingerprint = seq_len, seed, fingerprint
|
| 131 |
self.seqs_per_step, self.repo_type = seqs_per_step, repo_type
|
|
|
|
| 132 |
self.prune, self.min_free_gb = prune, min_free_gb
|
| 133 |
self.stop_after = int(stop_after_steps or 0)
|
| 134 |
self.stopped_at = None
|
|
@@ -155,7 +156,7 @@ class HubPush(TrainerCallback):
|
|
| 155 |
raise RuntimeError(f"Trainer reported a save but {d} does not exist")
|
| 156 |
samples = samples_at(step, self.seqs_per_step)
|
| 157 |
SD.save_cursor(d, SD.cursor_dict(self.seq_len, samples, self.seed, self.fingerprint,
|
| 158 |
-
step=step))
|
| 159 |
t0 = time.time()
|
| 160 |
# Upload and verify *without* pruning, roll the pointer and read it back, and only then delete the
|
| 161 |
# local copy. The old order (prune inside push_and_prune, pointer written afterwards) meant one
|
|
@@ -409,9 +410,13 @@ def main():
|
|
| 409 |
say(f"resume from {args.resume} cursor={cur}")
|
| 410 |
start_sample = (cur or {}).get("samples_consumed", 0)
|
| 411 |
start_step = (cur or {}).get("step") or 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
if cur:
|
| 413 |
for k, v in (("seq_len", args.seq_len), ("shuffle_seed", args.data_seed),
|
| 414 |
-
("dataset_files_sha", fingerprint)):
|
| 415 |
if cur.get(k) != v:
|
| 416 |
raise SystemExit(f"resume refused: cursor {k}={cur.get(k)} != current {v}. "
|
| 417 |
"A resumed run must read the same data in the same order (§3.1).")
|
|
@@ -425,6 +430,7 @@ def main():
|
|
| 425 |
start_step = start_sample // seqs_per_step
|
| 426 |
|
| 427 |
train_ds = SD.make_dataset(store, args.seq_len, args.data_seed, start_sample=start_sample)
|
|
|
|
| 428 |
# Validation comes from the mix's held-out val/ shards, never from the training stream: scoring on
|
| 429 |
# tokens the model already consumed would be a number nobody could reproduce (and §3.12).
|
| 430 |
vs = [s["file"] for s in man.get("val_shards", [])]
|
|
@@ -546,7 +552,7 @@ def main():
|
|
| 546 |
hub_cb = HubPush(args.hub_repo, api, os.environ.get("HF_TOKEN"), args.seq_len,
|
| 547 |
args.data_seed, fingerprint, seqs_per_step,
|
| 548 |
prune=args.prune, repo_type=args.hub_repo_type,
|
| 549 |
-
stop_after_steps=args.stop_after_steps)
|
| 550 |
tr.add_callback(hub_cb)
|
| 551 |
|
| 552 |
# Precision must be ASSERTED, not configured: bf16 "works" silently on Turing and fp32 master
|
|
@@ -637,10 +643,11 @@ def main():
|
|
| 637 |
shipped.append(fn)
|
| 638 |
say(f"tokenizer files shipped with the export: {shipped or 'NONE -- Phase 5 must add them'}")
|
| 639 |
SD.save_cursor(exp, SD.cursor_dict(args.seq_len, samples, args.data_seed, fingerprint,
|
| 640 |
-
step=step))
|
| 641 |
json.dump({"step": step, "start_step": start_step, "tokens_consumed": consumed,
|
| 642 |
"tokens_this_run": consumed_this_run, "seconds": round(elapsed, 1),
|
| 643 |
-
"params": pc, "fingerprint": fingerprint, "
|
|
|
|
| 644 |
"prior_loss": prior, "samples_consumed": samples,
|
| 645 |
"steps_planned": steps_planned},
|
| 646 |
open(os.path.join(exp, "run_summary.json"), "w"), indent=1, sort_keys=True)
|
|
|
|
| 125 |
"""
|
| 126 |
|
| 127 |
def __init__(self, repo, api, token, seq_len, seed, fingerprint, seqs_per_step,
|
| 128 |
+
prune=True, min_free_gb=6.0, repo_type="dataset", stop_after_steps=0, order_sha=None):
|
| 129 |
self.repo, self.api, self.token = repo, api, token
|
| 130 |
self.seq_len, self.seed, self.fingerprint = seq_len, seed, fingerprint
|
| 131 |
self.seqs_per_step, self.repo_type = seqs_per_step, repo_type
|
| 132 |
+
self.order_sha = order_sha
|
| 133 |
self.prune, self.min_free_gb = prune, min_free_gb
|
| 134 |
self.stop_after = int(stop_after_steps or 0)
|
| 135 |
self.stopped_at = None
|
|
|
|
| 156 |
raise RuntimeError(f"Trainer reported a save but {d} does not exist")
|
| 157 |
samples = samples_at(step, self.seqs_per_step)
|
| 158 |
SD.save_cursor(d, SD.cursor_dict(self.seq_len, samples, self.seed, self.fingerprint,
|
| 159 |
+
step=step, order_sha=self.order_sha))
|
| 160 |
t0 = time.time()
|
| 161 |
# Upload and verify *without* pruning, roll the pointer and read it back, and only then delete the
|
| 162 |
# local copy. The old order (prune inside push_and_prune, pointer written afterwards) meant one
|
|
|
|
| 410 |
say(f"resume from {args.resume} cursor={cur}")
|
| 411 |
start_sample = (cur or {}).get("samples_consumed", 0)
|
| 412 |
start_step = (cur or {}).get("step") or 0
|
| 413 |
+
# Identity of the visit order, derived from the corpus size and the seed rather than trusted to the
|
| 414 |
+
# seed: numpy's permutation is only stable within a version, and Kaggle updates its image underneath
|
| 415 |
+
# long runs. ~50 ms once per session, and it is what makes "resumed onto a different order" loud.
|
| 416 |
+
order_sha = SD.perm_sha(store.total_tokens // args.seq_len, args.data_seed)
|
| 417 |
if cur:
|
| 418 |
for k, v in (("seq_len", args.seq_len), ("shuffle_seed", args.data_seed),
|
| 419 |
+
("shuffle_perm_sha", order_sha), ("dataset_files_sha", fingerprint)):
|
| 420 |
if cur.get(k) != v:
|
| 421 |
raise SystemExit(f"resume refused: cursor {k}={cur.get(k)} != current {v}. "
|
| 422 |
"A resumed run must read the same data in the same order (§3.1).")
|
|
|
|
| 430 |
start_step = start_sample // seqs_per_step
|
| 431 |
|
| 432 |
train_ds = SD.make_dataset(store, args.seq_len, args.data_seed, start_sample=start_sample)
|
| 433 |
+
assert getattr(train_ds, "order_sha", None) == order_sha, "two derivations of the order disagree"
|
| 434 |
# Validation comes from the mix's held-out val/ shards, never from the training stream: scoring on
|
| 435 |
# tokens the model already consumed would be a number nobody could reproduce (and §3.12).
|
| 436 |
vs = [s["file"] for s in man.get("val_shards", [])]
|
|
|
|
| 552 |
hub_cb = HubPush(args.hub_repo, api, os.environ.get("HF_TOKEN"), args.seq_len,
|
| 553 |
args.data_seed, fingerprint, seqs_per_step,
|
| 554 |
prune=args.prune, repo_type=args.hub_repo_type,
|
| 555 |
+
stop_after_steps=args.stop_after_steps, order_sha=order_sha)
|
| 556 |
tr.add_callback(hub_cb)
|
| 557 |
|
| 558 |
# Precision must be ASSERTED, not configured: bf16 "works" silently on Turing and fp32 master
|
|
|
|
| 643 |
shipped.append(fn)
|
| 644 |
say(f"tokenizer files shipped with the export: {shipped or 'NONE -- Phase 5 must add them'}")
|
| 645 |
SD.save_cursor(exp, SD.cursor_dict(args.seq_len, samples, args.data_seed, fingerprint,
|
| 646 |
+
step=step, order_sha=order_sha))
|
| 647 |
json.dump({"step": step, "start_step": start_step, "tokens_consumed": consumed,
|
| 648 |
"tokens_this_run": consumed_this_run, "seconds": round(elapsed, 1),
|
| 649 |
+
"params": pc, "fingerprint": fingerprint, "shuffle_perm_sha": order_sha,
|
| 650 |
+
"final_loss": final_loss,
|
| 651 |
"prior_loss": prior, "samples_consumed": samples,
|
| 652 |
"steps_planned": steps_planned},
|
| 653 |
open(os.path.join(exp, "run_summary.json"), "w"), indent=1, sort_keys=True)
|