Cion-lab commited on
Commit
37e90cb
·
verified ·
1 Parent(s): 83e961d

pin 1dd45f80 (E-037 launcher fix)

Browse files
Files changed (1) hide show
  1. kernels/p4_stop_probe.py +217 -217
kernels/p4_stop_probe.py CHANGED
@@ -1,217 +1,217 @@
1
- # P4 probe: does --stop-after-steps really produce a resumable, Hub-verified segment boundary?
2
- #
3
- # Why this exists and what it buys with ~0.4 GPU-hours: `--stop-after-steps` is the mechanism every Phase 4
4
- # session ends on, and Gate 3 never exercised it -- P3's legs used `--max-steps`, so each leg *was* a whole
5
- # run. The launcher's plan, the trainer's stop, HubPush's push-verify-pointer-prune order, the resume scan
6
- # that refuses a stale pointer, and the final-push-plus-terminal-pointer path are therefore untested
7
- # together. E-035/4 is precisely a defect in that untested seam, and the rehearsal kernel cannot reach it
8
- # because it needs two T4s. This does, against the published mix and the frozen 22L geometry, in one
9
- # session: leg 1 trains to a mid-run stop step, leg 2 wipes the disk, resumes from the Hub and runs to the
10
- # horizon.
11
- #
12
- # The probe writes to its own checkpoint repo, never to Cion-lab/ounce100m-ckpt: the first real session has
13
- # to find that repo absent, which is the RepoMissing branch E-034 was about.
14
- #
15
- # It carries a second question, the one the user pushed back on (D-017): gradient checkpointing costs 32 %
16
- # of the throughput (9,696 -> 12,792 tok/s, 29.7 h -> 22.5 h) and gives up the memory headroom, sitting at
17
- # 12.25 GB of ~14.56. So this cell runs the *real* geometry at --accum 32, micro 4, WITHOUT checkpointing,
18
- # for 180 steps, through three push/verify/prune cycles and one forced cold resume and the end-of-run
19
- # validation pass -- the three things a 30-step throughput cell cannot show: allocator drift across a few
20
- # hundred steps, the save path's host/GPU copies while the card is 84 % full, and the eval forward pass.
21
- # Peak memory is asserted, not eyeballed. If it holds, the run adopts it as D-018 with a finer push cadence
22
- # as the bounded blast radius; if it does not, D-017 stands and this is the measurement that says so.
23
- import hashlib, json, os, shutil, signal, subprocess, sys, threading, time
24
-
25
- os.chdir("/kaggle/working")
26
- sys.path.insert(0, "/kaggle/working")
27
- REV = "91fb319faee801a74fb3199e63111db84673715f"
28
- WANT = {
29
- "ounce100m_credentials.py": ("ounce100m_credentials.py",
30
- "6525f62f03f2d73650a1eb4f70fcb52d1194caad4ca88b2d8bd8fd54f88339b6"),
31
- "shard_dataset.py": ("train/shard_dataset.py",
32
- "f35653bf4c8f2cfe7bb0c2c7835e308505fb5a84b4eb002d0f82f2cada768ca6"),
33
- "hubckpt.py": ("train/hubckpt.py",
34
- "568c31b300906cb8d78a59ef890baa9731b18e064e3d69b0eaea4b45c546f23e"),
35
- "train_ounce100m.py": ("train/train_ounce100m.py",
36
- "280f7ddc4632de30565426f1417f59c528a9ac0a59cfcfe18bc209216ede7c5e"),
37
- }
38
- BASE = "https://huggingface.co/Cion-lab/ounce100m-code/resolve/" + REV
39
- for p, (rp, want) in sorted(WANT.items()):
40
- assert subprocess.run(["curl", "-sfL", f"{BASE}/{rp}", "-o", p]).returncode == 0, ("fetch", rp)
41
- got = hashlib.sha256(open(p, "rb").read()).hexdigest()
42
- assert got == want, ("SHA MISMATCH", rp, got[:16], want[:16])
43
- print("OK", p, got[:12], flush=True)
44
-
45
- import ounce100m_credentials as C
46
- print("creds:", json.dumps(C.install(verify=True)), flush=True)
47
- import hubckpt
48
- from huggingface_hub import HfApi
49
-
50
- MIX = "Cion-lab/ounce100m-mix-v1"
51
- PROBE = "Cion-lab/ounce100m-ckpt-probe" # never the real run's repo, see the header
52
- ROOT, RUN = "/kaggle/working/mixroot", "/kaggle/working/run"
53
- # Two modes, one file, so the assertions are literally the same code in both. `smoke` is the user's
54
- # suggestion and it is the right order: 20 steps costs ~12 minutes and answers "does the training code run
55
- # at all, and does one checkpoint survive the push/verify/pointer/prune cycle" -- which is exactly what the
56
- # first run of this probe failed at, in 8.6 seconds, on a malformed torchrun command line (E-037). `soak`
57
- # is the 180-step memory question, and it is only worth 1.6 GPU-hours once the mechanics are known to work.
58
- MODE = os.environ.get("P4_PROBE_MODE", "soak")
59
- TPS = 262144 # the run's real step shape, in tokens
60
- if MODE == "smoke":
61
- STEPS, PUSH_EVERY, STOP1, VAL = 20, 10, 10, 200000
62
- T_LEG1, T_LEG2, T_FRESH = 1500, 1200, 2400
63
- else:
64
- STEPS, PUSH_EVERY, STOP1, VAL = 180, 60, 120, 2000000
65
- T_LEG1, T_LEG2, T_FRESH = 4200, 3000, 2400
66
- TOKENS = STEPS * TPS
67
- GATE_PEAK = (MODE != "smoke") # 20 steps says nothing about allocator drift
68
- T0 = time.time()
69
-
70
-
71
- def run(argv, label, timeout):
72
- print("=== " + label, flush=True)
73
- t0 = time.time()
74
- e = dict(os.environ); e["PYTHONPATH"] = "/kaggle/working"
75
- e["PYTHONUNBUFFERED"] = "1" # the -u that torchrun cannot carry
76
- p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
77
- env=e, bufsize=1, start_new_session=True)
78
- killed = []
79
-
80
- def _kill():
81
- killed.append(True)
82
- try:
83
- os.killpg(os.getpgid(p.pid), signal.SIGTERM)
84
- except Exception:
85
- p.kill()
86
-
87
- timer = threading.Timer(timeout, _kill)
88
- timer.daemon = True
89
- timer.start()
90
- keep, lines = [], []
91
- try:
92
- for line in p.stdout:
93
- line = line.rstrip("\n")
94
- lines.append(line)
95
- if line.startswith(("CKPT ", "RUN_JSON ", "resume from", "auto-resume", "precision:",
96
- "params:", "mix:", "checkpoint hub target", "segment boundary",
97
- "latest.json", "TRAIN DONE", "validation loss", "Traceback", "Error")):
98
- print(" KEY>", line[:300], flush=True)
99
- keep.append(line)
100
- del keep[:-40]
101
- finally:
102
- timer.cancel()
103
- rc = p.wait()
104
- if killed:
105
- print(" TIMEOUT after %d s" % timeout, flush=True)
106
- rc = -9
107
- if rc != 0:
108
- print(" TAIL:\n" + "\n".join(keep)[-2500:], flush=True)
109
- print("%s_RC %s seconds %.1f elapsed %.0f" % (label, rc, time.time() - t0, time.time() - T0),
110
- flush=True)
111
- return rc, "\n".join(lines)
112
-
113
-
114
- rc, out = run([sys.executable, "-c",
115
- "import sys; sys.path.insert(0, '/kaggle/working')\n"
116
- "from huggingface_hub import snapshot_download\n"
117
- "p = snapshot_download(repo_id='%s', repo_type='dataset',\n"
118
- " local_dir='/kaggle/working/mixroot', max_workers=4)\n"
119
- "print('mix at', p)\n" % MIX], "FETCH_MIX", T_FRESH)
120
- if rc != 0:
121
- raise SystemExit("VERDICT P4PROBE_STOP could not fetch the published mix")
122
- man = json.load(open(os.path.join(ROOT, "manifest.json")))
123
- print("mix", man["n_shards"], "shards", format(int(man["total_tokens"]), ","), "tokens", flush=True)
124
-
125
- common = ["train_ounce100m.py", "--root", ROOT, "--out", RUN,
126
- "--hub-repo", PROBE, "--prune", "--seq-len", "1024", "--attn", "eager", "--grad-ckpt",
127
- "--micro-batch", "4", "--accum", "32", "--no-grad-ckpt",
128
- "--tokens", str(TOKENS), "--lr", "6e-4",
129
- "--push-every-steps", str(PUSH_EVERY), "--val-tokens", str(VAL), "--log-every", "5",
130
- "--resume", "auto"]
131
-
132
- shutil.rmtree(RUN, ignore_errors=True)
133
- rc1, o1 = run(["torchrun", "--nproc_per_node=2"] + common + ["--stop-after-steps", str(STOP1)],
134
- "LEG1_STOP_EARLY", T_LEG1)
135
- api = HfApi(token=os.environ["HF_TOKEN"])
136
- try:
137
- listed = sorted(hubckpt.hub_listing(PROBE, "dataset", token=os.environ["HF_TOKEN"]))
138
- except Exception as e:
139
- listed = ["<listing failed: %s>" % type(e).__name__]
140
- pushed = sorted({k.split("/")[1] for k in listed if k.startswith("ckpt/checkpoint-")
141
- and len(k.split("/")) > 1})
142
- ptr = hubckpt.latest_pointer(PROBE, token=os.environ["HF_TOKEN"])
143
- print("LEG1 pushed:", pushed, "pointer:", {k: ptr.get(k) for k in ("step", "path_in_repo", "error")},
144
- flush=True)
145
-
146
- shutil.rmtree(RUN, ignore_errors=True) # force the cold-resume path (E-029's shape)
147
- rc2, o2 = run(["torchrun", "--nproc_per_node=2"] + common + ["--stop-after-steps", "0"],
148
- "LEG2_TO_HORIZON", T_LEG2)
149
- try:
150
- listed2 = sorted(hubckpt.hub_listing(PROBE, "dataset", token=os.environ["HF_TOKEN"]))
151
- except Exception as e:
152
- listed2 = ["<listing failed: %s>" % type(e).__name__]
153
- ptr2 = hubckpt.latest_pointer(PROBE, token=os.environ["HF_TOKEN"])
154
-
155
-
156
- def harvest(text, tag):
157
- for line in (text or "").splitlines():
158
- if line.startswith(tag):
159
- try:
160
- return json.loads(line[len(tag):].strip())
161
- except Exception:
162
- return {"unparsed": line[:200]}
163
- return {}
164
-
165
-
166
- rj1, rj2 = harvest(o1, "RUN_JSON "), harvest(o2, "RUN_JSON ")
167
- res = {
168
- "leg1_rc": rc1, "leg2_rc": rc2,
169
- "leg1": {"final_step": rj1.get("final_step"), "segment_stop": rj1.get("segment_stop"),
170
- "tokens_consumed": rj1.get("tokens_consumed"), "tok_per_s": rj1.get("tok_per_s"),
171
- "params": rj1.get("params"), "peak_gpu_gb": rj1.get("peak_gpu_gb"),
172
- "tok_per_s": rj1.get("tok_per_s"), "final_loss": rj1.get("final_loss"),
173
- "pushed": pushed, "pointer": {k: ptr.get(k) for k in ("step", "path_in_repo")}},
174
- "leg2": {"final_step": rj2.get("final_step"), "segment_stop": rj2.get("segment_stop"),
175
- "tokens_consumed": rj2.get("tokens_consumed"), "val_ppl": rj2.get("val_ppl"),
176
- "peak_gpu_gb": rj2.get("peak_gpu_gb"), "tok_per_s": rj2.get("tok_per_s"),
177
- "final_loss": rj2.get("final_loss"),
178
- "pointer_after": {k: ptr2.get(k) for k in ("step", "path_in_repo")},
179
- "has_final": any(k.startswith("final/") for k in listed2)},
180
- "expect": {"steps_planned": STEPS, "push_every": PUSH_EVERY, "stop1": STOP1,
181
- "tokens_at_stop1": STOP1 * TPS, "tokens_at_horizon": STEPS * TPS, "grad_ckpt": False},
182
- }
183
- res["checks"] = {
184
- "leg1_stopped_at_the_stop_step": rj1.get("final_step") == STOP1,
185
- "leg1_reported_a_segment": rj1.get("segment_stop") is True,
186
- # Integer compare: sorted() over names would order "checkpoint-120" before "checkpoint-60".
187
- "leg1_pushed_every_interval": sorted(int(x.split("-")[1]) for x in pushed) ==
188
- list(range(PUSH_EVERY, STOP1 + 1, PUSH_EVERY)),
189
- "leg1_pointer_at_stop": ptr.get("step") == STOP1,
190
- "leg2_resumed_and_finished": rj2.get("final_step") == STEPS,
191
- "leg2_was_not_a_segment": rj2.get("segment_stop") is False,
192
- "leg2_pushed_final": any(k.startswith("final/") for k in listed2),
193
- "all_steps_present": sorted(int(x.split("-")[1]) for x in
194
- {k.split("/")[1] for k in listed2 if k.startswith("ckpt/checkpoint-")})
195
- == list(range(PUSH_EVERY, STEPS + 1, PUSH_EVERY)),
196
- "leg2_pointer_terminal": ptr2.get("step") == STEPS and ptr2.get("path_in_repo") == "final",
197
- "tokens_match_the_arithmetic": rj1.get("tokens_consumed") == STOP1 * TPS
198
- and rj2.get("tokens_consumed") == STEPS * TPS,
199
- "params_are_the_frozen_model": rj1.get("params") == 106194240,
200
- "checkpointing_really_off": rj1.get("grad_ckpt") is False,
201
- # 13.6 GB of ~14.56 usable leaves the run room for a fragmentation spike and for the two CUDA contexts;
202
- # above that the answer to the user's question is "not on this hardware", not "keep going". Reported in
203
- # both modes, gated only in the soak: a 20-step peak is not evidence about 3,814 steps.
204
- "peak_memory_below_13_6_gb": (not GATE_PEAK) or ((rj1.get("peak_gpu_gb") or 99) <= 13.6
205
- and (rj2.get("peak_gpu_gb") or 99) <= 13.6),
206
- "no_nan_and_loss_moved": (rj1.get("final_loss") or 1e9) < 11.0
207
- and (rj2.get("final_loss") or 1e9) < 11.0,
208
- }
209
- res["PROBE_PASSED"] = all(res["checks"].values()) and rc1 == 0 and rc2 == 0
210
- print("PROBE_MODE", MODE, "steps", STEPS, "push_every", PUSH_EVERY,
211
- "stop1", STOP1, "peak_gate", GATE_PEAK, flush=True)
212
- print("PROBE_JSON_BEGIN")
213
- print(json.dumps(res, indent=1, default=str))
214
- print("PROBE_JSON_END")
215
- print("VERDICT P4PROBE", "PASS" if res["PROBE_PASSED"] else "FAIL",
216
- [k for k, v in res["checks"].items() if not v], "seconds", round(time.time() - T0, 1), flush=True)
217
- raise SystemExit(0 if res["PROBE_PASSED"] else 5)
 
1
+ # P4 probe: does --stop-after-steps really produce a resumable, Hub-verified segment boundary?
2
+ #
3
+ # Why this exists and what it buys with ~0.4 GPU-hours: `--stop-after-steps` is the mechanism every Phase 4
4
+ # session ends on, and Gate 3 never exercised it -- P3's legs used `--max-steps`, so each leg *was* a whole
5
+ # run. The launcher's plan, the trainer's stop, HubPush's push-verify-pointer-prune order, the resume scan
6
+ # that refuses a stale pointer, and the final-push-plus-terminal-pointer path are therefore untested
7
+ # together. E-035/4 is precisely a defect in that untested seam, and the rehearsal kernel cannot reach it
8
+ # because it needs two T4s. This does, against the published mix and the frozen 22L geometry, in one
9
+ # session: leg 1 trains to a mid-run stop step, leg 2 wipes the disk, resumes from the Hub and runs to the
10
+ # horizon.
11
+ #
12
+ # The probe writes to its own checkpoint repo, never to Cion-lab/ounce100m-ckpt: the first real session has
13
+ # to find that repo absent, which is the RepoMissing branch E-034 was about.
14
+ #
15
+ # It carries a second question, the one the user pushed back on (D-017): gradient checkpointing costs 32 %
16
+ # of the throughput (9,696 -> 12,792 tok/s, 29.7 h -> 22.5 h) and gives up the memory headroom, sitting at
17
+ # 12.25 GB of ~14.56. So this cell runs the *real* geometry at --accum 32, micro 4, WITHOUT checkpointing,
18
+ # for 180 steps, through three push/verify/prune cycles and one forced cold resume and the end-of-run
19
+ # validation pass -- the three things a 30-step throughput cell cannot show: allocator drift across a few
20
+ # hundred steps, the save path's host/GPU copies while the card is 84 % full, and the eval forward pass.
21
+ # Peak memory is asserted, not eyeballed. If it holds, the run adopts it as D-018 with a finer push cadence
22
+ # as the bounded blast radius; if it does not, D-017 stands and this is the measurement that says so.
23
+ import hashlib, json, os, shutil, signal, subprocess, sys, threading, time
24
+
25
+ os.chdir("/kaggle/working")
26
+ sys.path.insert(0, "/kaggle/working")
27
+ REV = "1dd45f80384750d5e1509fdcb4ca8eea6145dcf1"
28
+ WANT = {
29
+ "ounce100m_credentials.py": ("ounce100m_credentials.py",
30
+ "6525f62f03f2d73650a1eb4f70fcb52d1194caad4ca88b2d8bd8fd54f88339b6"),
31
+ "shard_dataset.py": ("train/shard_dataset.py",
32
+ "f35653bf4c8f2cfe7bb0c2c7835e308505fb5a84b4eb002d0f82f2cada768ca6"),
33
+ "hubckpt.py": ("train/hubckpt.py",
34
+ "568c31b300906cb8d78a59ef890baa9731b18e064e3d69b0eaea4b45c546f23e"),
35
+ "train_ounce100m.py": ("train/train_ounce100m.py",
36
+ "280f7ddc4632de30565426f1417f59c528a9ac0a59cfcfe18bc209216ede7c5e"),
37
+ }
38
+ BASE = "https://huggingface.co/Cion-lab/ounce100m-code/resolve/" + REV
39
+ for p, (rp, want) in sorted(WANT.items()):
40
+ assert subprocess.run(["curl", "-sfL", f"{BASE}/{rp}", "-o", p]).returncode == 0, ("fetch", rp)
41
+ got = hashlib.sha256(open(p, "rb").read()).hexdigest()
42
+ assert got == want, ("SHA MISMATCH", rp, got[:16], want[:16])
43
+ print("OK", p, got[:12], flush=True)
44
+
45
+ import ounce100m_credentials as C
46
+ print("creds:", json.dumps(C.install(verify=True)), flush=True)
47
+ import hubckpt
48
+ from huggingface_hub import HfApi
49
+
50
+ MIX = "Cion-lab/ounce100m-mix-v1"
51
+ PROBE = "Cion-lab/ounce100m-ckpt-probe" # never the real run's repo, see the header
52
+ ROOT, RUN = "/kaggle/working/mixroot", "/kaggle/working/run"
53
+ # Two modes, one file, so the assertions are literally the same code in both. `smoke` is the user's
54
+ # suggestion and it is the right order: 20 steps costs ~12 minutes and answers "does the training code run
55
+ # at all, and does one checkpoint survive the push/verify/pointer/prune cycle" -- which is exactly what the
56
+ # first run of this probe failed at, in 8.6 seconds, on a malformed torchrun command line (E-037). `soak`
57
+ # is the 180-step memory question, and it is only worth 1.6 GPU-hours once the mechanics are known to work.
58
+ MODE = os.environ.get("P4_PROBE_MODE", "soak")
59
+ TPS = 262144 # the run's real step shape, in tokens
60
+ if MODE == "smoke":
61
+ STEPS, PUSH_EVERY, STOP1, VAL = 20, 10, 10, 200000
62
+ T_LEG1, T_LEG2, T_FRESH = 1500, 1200, 2400
63
+ else:
64
+ STEPS, PUSH_EVERY, STOP1, VAL = 180, 60, 120, 2000000
65
+ T_LEG1, T_LEG2, T_FRESH = 4200, 3000, 2400
66
+ TOKENS = STEPS * TPS
67
+ GATE_PEAK = (MODE != "smoke") # 20 steps says nothing about allocator drift
68
+ T0 = time.time()
69
+
70
+
71
+ def run(argv, label, timeout):
72
+ print("=== " + label, flush=True)
73
+ t0 = time.time()
74
+ e = dict(os.environ); e["PYTHONPATH"] = "/kaggle/working"
75
+ e["PYTHONUNBUFFERED"] = "1" # the -u that torchrun cannot carry
76
+ p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
77
+ env=e, bufsize=1, start_new_session=True)
78
+ killed = []
79
+
80
+ def _kill():
81
+ killed.append(True)
82
+ try:
83
+ os.killpg(os.getpgid(p.pid), signal.SIGTERM)
84
+ except Exception:
85
+ p.kill()
86
+
87
+ timer = threading.Timer(timeout, _kill)
88
+ timer.daemon = True
89
+ timer.start()
90
+ keep, lines = [], []
91
+ try:
92
+ for line in p.stdout:
93
+ line = line.rstrip("\n")
94
+ lines.append(line)
95
+ if line.startswith(("CKPT ", "RUN_JSON ", "resume from", "auto-resume", "precision:",
96
+ "params:", "mix:", "checkpoint hub target", "segment boundary",
97
+ "latest.json", "TRAIN DONE", "validation loss", "Traceback", "Error")):
98
+ print(" KEY>", line[:300], flush=True)
99
+ keep.append(line)
100
+ del keep[:-40]
101
+ finally:
102
+ timer.cancel()
103
+ rc = p.wait()
104
+ if killed:
105
+ print(" TIMEOUT after %d s" % timeout, flush=True)
106
+ rc = -9
107
+ if rc != 0:
108
+ print(" TAIL:\n" + "\n".join(keep)[-2500:], flush=True)
109
+ print("%s_RC %s seconds %.1f elapsed %.0f" % (label, rc, time.time() - t0, time.time() - T0),
110
+ flush=True)
111
+ return rc, "\n".join(lines)
112
+
113
+
114
+ rc, out = run([sys.executable, "-c",
115
+ "import sys; sys.path.insert(0, '/kaggle/working')\n"
116
+ "from huggingface_hub import snapshot_download\n"
117
+ "p = snapshot_download(repo_id='%s', repo_type='dataset',\n"
118
+ " local_dir='/kaggle/working/mixroot', max_workers=4)\n"
119
+ "print('mix at', p)\n" % MIX], "FETCH_MIX", T_FRESH)
120
+ if rc != 0:
121
+ raise SystemExit("VERDICT P4PROBE_STOP could not fetch the published mix")
122
+ man = json.load(open(os.path.join(ROOT, "manifest.json")))
123
+ print("mix", man["n_shards"], "shards", format(int(man["total_tokens"]), ","), "tokens", flush=True)
124
+
125
+ common = ["train_ounce100m.py", "--root", ROOT, "--out", RUN,
126
+ "--hub-repo", PROBE, "--prune", "--seq-len", "1024", "--attn", "eager", "--grad-ckpt",
127
+ "--micro-batch", "4", "--accum", "32", "--no-grad-ckpt",
128
+ "--tokens", str(TOKENS), "--lr", "6e-4",
129
+ "--push-every-steps", str(PUSH_EVERY), "--val-tokens", str(VAL), "--log-every", "5",
130
+ "--resume", "auto"]
131
+
132
+ shutil.rmtree(RUN, ignore_errors=True)
133
+ rc1, o1 = run(["torchrun", "--nproc_per_node=2"] + common + ["--stop-after-steps", str(STOP1)],
134
+ "LEG1_STOP_EARLY", T_LEG1)
135
+ api = HfApi(token=os.environ["HF_TOKEN"])
136
+ try:
137
+ listed = sorted(hubckpt.hub_listing(PROBE, "dataset", token=os.environ["HF_TOKEN"]))
138
+ except Exception as e:
139
+ listed = ["<listing failed: %s>" % type(e).__name__]
140
+ pushed = sorted({k.split("/")[1] for k in listed if k.startswith("ckpt/checkpoint-")
141
+ and len(k.split("/")) > 1})
142
+ ptr = hubckpt.latest_pointer(PROBE, token=os.environ["HF_TOKEN"])
143
+ print("LEG1 pushed:", pushed, "pointer:", {k: ptr.get(k) for k in ("step", "path_in_repo", "error")},
144
+ flush=True)
145
+
146
+ shutil.rmtree(RUN, ignore_errors=True) # force the cold-resume path (E-029's shape)
147
+ rc2, o2 = run(["torchrun", "--nproc_per_node=2"] + common + ["--stop-after-steps", "0"],
148
+ "LEG2_TO_HORIZON", T_LEG2)
149
+ try:
150
+ listed2 = sorted(hubckpt.hub_listing(PROBE, "dataset", token=os.environ["HF_TOKEN"]))
151
+ except Exception as e:
152
+ listed2 = ["<listing failed: %s>" % type(e).__name__]
153
+ ptr2 = hubckpt.latest_pointer(PROBE, token=os.environ["HF_TOKEN"])
154
+
155
+
156
+ def harvest(text, tag):
157
+ for line in (text or "").splitlines():
158
+ if line.startswith(tag):
159
+ try:
160
+ return json.loads(line[len(tag):].strip())
161
+ except Exception:
162
+ return {"unparsed": line[:200]}
163
+ return {}
164
+
165
+
166
+ rj1, rj2 = harvest(o1, "RUN_JSON "), harvest(o2, "RUN_JSON ")
167
+ res = {
168
+ "leg1_rc": rc1, "leg2_rc": rc2,
169
+ "leg1": {"final_step": rj1.get("final_step"), "segment_stop": rj1.get("segment_stop"),
170
+ "tokens_consumed": rj1.get("tokens_consumed"), "tok_per_s": rj1.get("tok_per_s"),
171
+ "params": rj1.get("params"), "peak_gpu_gb": rj1.get("peak_gpu_gb"),
172
+ "tok_per_s": rj1.get("tok_per_s"), "final_loss": rj1.get("final_loss"),
173
+ "pushed": pushed, "pointer": {k: ptr.get(k) for k in ("step", "path_in_repo")}},
174
+ "leg2": {"final_step": rj2.get("final_step"), "segment_stop": rj2.get("segment_stop"),
175
+ "tokens_consumed": rj2.get("tokens_consumed"), "val_ppl": rj2.get("val_ppl"),
176
+ "peak_gpu_gb": rj2.get("peak_gpu_gb"), "tok_per_s": rj2.get("tok_per_s"),
177
+ "final_loss": rj2.get("final_loss"),
178
+ "pointer_after": {k: ptr2.get(k) for k in ("step", "path_in_repo")},
179
+ "has_final": any(k.startswith("final/") for k in listed2)},
180
+ "expect": {"steps_planned": STEPS, "push_every": PUSH_EVERY, "stop1": STOP1,
181
+ "tokens_at_stop1": STOP1 * TPS, "tokens_at_horizon": STEPS * TPS, "grad_ckpt": False},
182
+ }
183
+ res["checks"] = {
184
+ "leg1_stopped_at_the_stop_step": rj1.get("final_step") == STOP1,
185
+ "leg1_reported_a_segment": rj1.get("segment_stop") is True,
186
+ # Integer compare: sorted() over names would order "checkpoint-120" before "checkpoint-60".
187
+ "leg1_pushed_every_interval": sorted(int(x.split("-")[1]) for x in pushed) ==
188
+ list(range(PUSH_EVERY, STOP1 + 1, PUSH_EVERY)),
189
+ "leg1_pointer_at_stop": ptr.get("step") == STOP1,
190
+ "leg2_resumed_and_finished": rj2.get("final_step") == STEPS,
191
+ "leg2_was_not_a_segment": rj2.get("segment_stop") is False,
192
+ "leg2_pushed_final": any(k.startswith("final/") for k in listed2),
193
+ "all_steps_present": sorted(int(x.split("-")[1]) for x in
194
+ {k.split("/")[1] for k in listed2 if k.startswith("ckpt/checkpoint-")})
195
+ == list(range(PUSH_EVERY, STEPS + 1, PUSH_EVERY)),
196
+ "leg2_pointer_terminal": ptr2.get("step") == STEPS and ptr2.get("path_in_repo") == "final",
197
+ "tokens_match_the_arithmetic": rj1.get("tokens_consumed") == STOP1 * TPS
198
+ and rj2.get("tokens_consumed") == STEPS * TPS,
199
+ "params_are_the_frozen_model": rj1.get("params") == 106194240,
200
+ "checkpointing_really_off": rj1.get("grad_ckpt") is False,
201
+ # 13.6 GB of ~14.56 usable leaves the run room for a fragmentation spike and for the two CUDA contexts;
202
+ # above that the answer to the user's question is "not on this hardware", not "keep going". Reported in
203
+ # both modes, gated only in the soak: a 20-step peak is not evidence about 3,814 steps.
204
+ "peak_memory_below_13_6_gb": (not GATE_PEAK) or ((rj1.get("peak_gpu_gb") or 99) <= 13.6
205
+ and (rj2.get("peak_gpu_gb") or 99) <= 13.6),
206
+ "no_nan_and_loss_moved": (rj1.get("final_loss") or 1e9) < 11.0
207
+ and (rj2.get("final_loss") or 1e9) < 11.0,
208
+ }
209
+ res["PROBE_PASSED"] = all(res["checks"].values()) and rc1 == 0 and rc2 == 0
210
+ print("PROBE_MODE", MODE, "steps", STEPS, "push_every", PUSH_EVERY,
211
+ "stop1", STOP1, "peak_gate", GATE_PEAK, flush=True)
212
+ print("PROBE_JSON_BEGIN")
213
+ print(json.dumps(res, indent=1, default=str))
214
+ print("PROBE_JSON_END")
215
+ print("VERDICT P4PROBE", "PASS" if res["PROBE_PASSED"] else "FAIL",
216
+ [k for k, v in res["checks"].items() if not v], "seconds", round(time.time() - T0, 1), flush=True)
217
+ raise SystemExit(0 if res["PROBE_PASSED"] else 5)