jayasriii commited on
Commit
0d0b167
·
verified ·
1 Parent(s): 20f04f3

Upload folder using huggingface_hub

Browse files
390M_1M_8ksteps_main.csv ADDED
The diff for this file is too large to render. See raw diff
 
390M_1M_8ksteps_main/blumodel_muon_step00003461.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec879edf1b9cad2c28ba7081c9abb090281c5fbddee2858a61b0aa70a0060e64
3
+ size 4205800408
390M_1M_8ksteps_main/blumodel_muon_step00004065.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:564f389d51056c72b1d3d42d6ed4f4559e7e0d7c70fbdcb6bbcbe7b9707f8e43
3
+ size 4205800472
390M_1M_8ksteps_main/blumodel_muon_step00005697.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e25d5877675b91fe6b19f25faa5d4f38cef0a0a15baabccefc46aed5f13cc3fd
3
+ size 4205800472
390M_1M_8ksteps_main/blumodel_muon_step00006362.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c4989384da28331f650363ef64626feb4bd7436ddd49fc0ff39e9045d7100137
3
+ size 4205800472
390M_1M_8ksteps_main/blumodel_muon_step00007249.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dfc1d398a1b17159730ad6adbbe227a58caf666f77d9f754a96ecefe80b3855b
3
+ size 4205800472
390M_1M_8ksteps_main/blumodel_muon_step00007250.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:799b23fdf13f4adce9d76a42b31ae001c73e128f946c64d198b128ecd3e45ea8
3
+ size 4205800536
390M_1M_8ksteps_main/blumodel_muon_step00008700.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:76365b7edbd7c359e38c0cb714c4d1978fc5a1763711c1d6b3a07cc8c632a4f8
3
+ size 4205800536
check_target_loss.py CHANGED
@@ -1,5 +1,3 @@
1
-
2
-
3
  #!/usr/bin/env python3
4
  """Find when a training run (CSV log) reaches a target loss, after smoothing.
5
  Two cases, both reported:
@@ -20,6 +18,11 @@ EXTRAPOLATION_WARN_FACTOR = 3.0
20
  CONVERGENCE_TOL = 0.05
21
  SWEEP_STEP = 300
22
  SWEEP_MIN_WINDOW = 500
 
 
 
 
 
23
 
24
 
25
  def power_law(s, l0, a, alpha):
@@ -34,6 +37,19 @@ def parse_batch_size_from_filename(path):
34
  return value * (1_000_000 if unit == "M" else 1_000)
35
 
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  def detect_warmup_end(df):
38
  lr_max = df["lr"].max()
39
  past_warmup = df.index[df["lr"] >= 0.99 * lr_max]
@@ -122,8 +138,12 @@ def main():
122
  p.add_argument("csv")
123
  p.add_argument("--target", type=float, nargs="+", required=True)
124
  p.add_argument("--loss-col", default="train_loss")
125
- p.add_argument("--ema-span", type=float, default=50,
126
- help="EMA span in steps for smoothing (default: 50)")
 
 
 
 
127
  p.add_argument("--batch-size", type=float, default=None,
128
  help="tokens per step; parsed from filename (e.g. _1Mbs) if omitted")
129
  p.add_argument("--fit-start-step", type=int, default=None,
@@ -155,13 +175,15 @@ def main():
155
  if fit_start is None:
156
  fit_start = detect_warmup_end(df)
157
 
158
- smoothed_full = df[args.loss_col].ewm(span=args.ema_span, adjust=False).mean()
 
159
  post_warmup = df[df["step"] >= fit_start]
160
  smoothed_post = smoothed_full.loc[post_warmup.index]
161
  steps_post = post_warmup["step"]
162
  s_max_observed = steps_post.max()
163
 
164
- print(f"loss col: {args.loss_col} | ema span: {args.ema_span} steps | "
 
165
  f"batch size: {batch_size:,.0f} tokens/step | fit start step: {fit_start}")
166
  print("-" * 100)
167
 
@@ -195,5 +217,3 @@ def main():
195
 
196
  if __name__ == "__main__":
197
  main()
198
-
199
-
 
 
 
1
  #!/usr/bin/env python3
2
  """Find when a training run (CSV log) reaches a target loss, after smoothing.
3
  Two cases, both reported:
 
18
  CONVERGENCE_TOL = 0.05
19
  SWEEP_STEP = 300
20
  SWEEP_MIN_WINDOW = 500
21
+ EMA_TOKEN_WINDOW = 100_000_000 # smoothing window in TOKENS, not steps -- see
22
+ # batchsize_scaling.py for why: a fixed step
23
+ # count covers a different amount of data per
24
+ # run depending on batch size, which biases
25
+ # the cross-batch-size comparison.
26
 
27
 
28
  def power_law(s, l0, a, alpha):
 
37
  return value * (1_000_000 if unit == "M" else 1_000)
38
 
39
 
40
+ def ema_span_for_token_window(batch_size, token_window):
41
+ """Convert a token-based smoothing window into the step-based `span`
42
+ pandas' .ewm() expects, so every run gets an equivalent amount of actual
43
+ data smoothed over regardless of its batch size."""
44
+ span = token_window / batch_size
45
+ if span < 1:
46
+ print(f"[WARN] EMA_TOKEN_WINDOW ({token_window:,}) < batch size "
47
+ f"({batch_size:,.0f}) -- window is under one step; clamping "
48
+ "span to 1 (effectively no smoothing).")
49
+ return 1.0
50
+ return span
51
+
52
+
53
  def detect_warmup_end(df):
54
  lr_max = df["lr"].max()
55
  past_warmup = df.index[df["lr"] >= 0.99 * lr_max]
 
138
  p.add_argument("csv")
139
  p.add_argument("--target", type=float, nargs="+", required=True)
140
  p.add_argument("--loss-col", default="train_loss")
141
+ p.add_argument("--ema-token-window", type=float, default=EMA_TOKEN_WINDOW,
142
+ help=f"EMA smoothing window in TOKENS, not steps (default: "
143
+ f"{EMA_TOKEN_WINDOW:,.0f}) -- converted to a step-based "
144
+ "span using --batch-size, so different batch sizes get "
145
+ "an equivalent amount of data smoothed, not an "
146
+ "equivalent step count")
147
  p.add_argument("--batch-size", type=float, default=None,
148
  help="tokens per step; parsed from filename (e.g. _1Mbs) if omitted")
149
  p.add_argument("--fit-start-step", type=int, default=None,
 
175
  if fit_start is None:
176
  fit_start = detect_warmup_end(df)
177
 
178
+ ema_span = ema_span_for_token_window(batch_size, args.ema_token_window)
179
+ smoothed_full = df[args.loss_col].ewm(span=ema_span, adjust=False).mean()
180
  post_warmup = df[df["step"] >= fit_start]
181
  smoothed_post = smoothed_full.loc[post_warmup.index]
182
  steps_post = post_warmup["step"]
183
  s_max_observed = steps_post.max()
184
 
185
+ print(f"loss col: {args.loss_col} | ema token window: {args.ema_token_window:,.0f} "
186
+ f"(~{ema_span:.1f} steps at this batch size) | "
187
  f"batch size: {batch_size:,.0f} tokens/step | fit start step: {fit_start}")
188
  print("-" * 100)
189
 
 
217
 
218
  if __name__ == "__main__":
219
  main()
 
 
loss_temp.csv ADDED
The diff for this file is too large to render. See raw diff
 
train_390M.py CHANGED
@@ -464,10 +464,10 @@ class ModelConfig:
464
  muon_lr: float = 0.001
465
  muon_min_lr: float = 0.0001
466
 
467
- max_steps: int = 26703
468
 
469
  warmup_steps: int = 500
470
- stable_steps: int = int(max_steps * 0.9) - warmup_steps # steps held at max_lr after warmup
471
 
472
 
473
  # ---- Muon (hidden 2D weights); the AdamW fields above still drive the
@@ -488,7 +488,9 @@ class ModelConfig:
488
 
489
  # ---- checkpointing ----
490
  checkpointing: bool = True
491
- ckpt_freq: int = 1000 # save every 1800 optimizer steps
 
 
492
  ckpt_keep: int = 40 # retain the most-recent N checkpoints
493
  # Separate from the AdamW arm's dir: the two checkpoint formats differ (Muon
494
  # momentum vs Adam m/v) and auto-resume picks the newest file it finds.
@@ -499,6 +501,18 @@ class ModelConfig:
499
  fused_rms_rope: bool = True
500
  activation_checkpoint: bool = False
501
  loss_chunks: int = 1
 
 
 
 
 
 
 
 
 
 
 
 
502
  # Depth probe: which layers --diag reports gradient / weight statistics for.
503
  probe_layers: tuple = (0, 8, 17, 26, 35)
504
 
@@ -1064,7 +1078,7 @@ class CheckpointManager:
1064
  out.append((int(m.group(1)), p))
1065
  return sorted(out)
1066
 
1067
- def save(self, step, model, opt, loss, last_val, cfg):
1068
  tmp = self._path(step) + ".tmp"
1069
  torch.save({"step": step, "loss": loss, "last_val": last_val,
1070
  # B/T/global_batch pin the data stride this checkpoint's
@@ -1075,6 +1089,12 @@ class CheckpointManager:
1075
  # running, so a GPU-count change alone is not a mismatch.
1076
  "run_cfg": {"B": cfg.B, "T": cfg.T,
1077
  "global_batch": cfg.global_batch},
 
 
 
 
 
 
1078
  "model": model.state_dict(),
1079
  "opt": opt.state_dict()}, tmp)
1080
  # Rename last: a crash mid-write leaves a .tmp, never a truncated
@@ -1085,18 +1105,18 @@ class CheckpointManager:
1085
  os.remove(stale)
1086
 
1087
  def load_latest(self, model, opt, cfg, master=True):
1088
- """-> (start_step, loss, last_val). (0, 0.0, nan) when there is
1089
- nothing to resume."""
1090
  existing = self._existing()
1091
  if not existing:
1092
- return 0, 0.0, float("nan")
1093
  _, path = existing[-1]
1094
  return self.load(path, model, opt, cfg, master)
1095
 
1096
  def load(self, path, model, opt, cfg, master=True):
1097
  """Load a specific checkpoint file (e.g. from --resume-from), rather
1098
  than auto-discovering the latest one in self.dir.
1099
- -> (start_step, loss, last_val)."""
1100
  blob = torch.load(path, map_location="cpu", weights_only=True)
1101
  model.load_state_dict(blob["model"])
1102
  if not opt.load_state_dict(blob["opt"]):
@@ -1112,7 +1132,8 @@ class CheckpointManager:
1112
  print(f" {m}")
1113
  print(f"[resume] loaded {path}")
1114
  return (int(blob["step"]), float(blob["loss"]),
1115
- float(blob.get("last_val", float("nan"))))
 
1116
 
1117
 
1118
  # =============================================================================
@@ -1172,7 +1193,7 @@ def build_argparser():
1172
  ap = argparse.ArgumentParser(
1173
  description="BLU single-GPU training — PyTorch port of bluscript_single.cpp")
1174
  ap.add_argument("--data-root",
1175
- default=("/home/operator/.cache/huggingface/hub/datasets--jayasriii--flux_20b/"
1176
  "/snapshots/ddc6cc7e43365a6b9096c30053df0fb001c94f4c"))
1177
  ap.add_argument("--device", type=int, default=None,
1178
  help="CUDA device index (env: BLU_DEVICE)")
@@ -1204,6 +1225,14 @@ def build_argparser():
1204
  help="caps the run for benchmarking; warmup/stable shrink "
1205
  "proportionally so the WSD decay tail stays non-empty "
1206
  "(env: BLU_MAX_STEPS)")
 
 
 
 
 
 
 
 
1207
  # ---- Muon ----
1208
  ap.add_argument("--muon-lr", type=float, default=None,
1209
  help="peak LR for the hidden 2D weights. Under the default "
@@ -1263,7 +1292,7 @@ def config_from_args(args):
1263
  "vocab_size", "global_batch", "val_freq", "val_loss_steps",
1264
  "ckpt_dir", "ckpt_prefix", "ckpt_freq", "ckpt_keep",
1265
  "muon_lr", "muon_min_lr", "muon_momentum", "muon_ns_steps",
1266
- "muon_adjust_lr"):
1267
  v = getattr(args, f, None)
1268
  if v is not None:
1269
  setattr(cfg, f, v)
@@ -1427,6 +1456,9 @@ def main():
1427
  print(f" warmup / steps : {cfg.warmup_steps} / {cfg.max_steps}")
1428
  print(f" WSD stable_steps: {cfg.stable_steps} "
1429
  f"(decay tail={cfg.max_steps - cfg.warmup_steps - cfg.stable_steps})")
 
 
 
1430
  print(f" precision : {args.precision}")
1431
  print(f" seed / determ. : {args.seed} / {args.determinism}")
1432
  print(f" rms+rope : {'fused' if cfg.fused_rms_rope else 'eager'}"
@@ -1536,15 +1568,16 @@ def main():
1536
  # explicit broadcast. Only rank 0 ever WRITES one (below).
1537
  start_step, ckpt = 0, None
1538
  last_val = float("nan")
 
1539
  if cfg.checkpointing or args.resume_from:
1540
  ckpt = CheckpointManager(cfg.ckpt_dir, cfg.ckpt_prefix, cfg.ckpt_keep)
1541
  if args.resume_from:
1542
  if master_process:
1543
  print(f"[resume] loading explicit checkpoint: {args.resume_from}")
1544
- start_step, resume_loss, last_val = ckpt.load(
1545
  args.resume_from, raw_model, opt, cfg, master_process)
1546
  else:
1547
- start_step, resume_loss, last_val = ckpt.load_latest(
1548
  raw_model, opt, cfg, master_process)
1549
  if start_step > 0:
1550
  if master_process:
@@ -1574,6 +1607,14 @@ def main():
1574
  probes = [L for L in cfg.probe_layers if L < cfg.n_layers]
1575
  steady_dts = []
1576
  t_train_start = time.perf_counter()
 
 
 
 
 
 
 
 
1577
 
1578
  # ---- Ctrl-C: finish the in-flight step, save a checkpoint, exit clean ----
1579
  # Registered here (not earlier) so a Ctrl-C during setup/data-loading still
@@ -1675,6 +1716,22 @@ def main():
1675
  dist.destroy_process_group()
1676
  raise SystemExit(f"ERROR: NaN/Inf loss at step {step}")
1677
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1678
  # Depth probe (--diag), read BEFORE the update, so these are the
1679
  # pre-update gradients. Compare the profile on a spiking step against a
1680
  # quiet one: a blow-up that begins mid-stack and grows toward the loss
@@ -1751,7 +1808,7 @@ def main():
1751
  dist.all_reduce(flag, op=dist.ReduceOp.MAX)
1752
  interrupted = flag.item() > 0
1753
 
1754
- # ---------- periodic checkpoint (also fires once on Ctrl-C) ----------
1755
  # Labeled step+1 (the next iteration to run) so load_latest resumes
1756
  # exactly where this save left off — no duplicated or skipped iteration.
1757
  # Captures weights + Adam m/v AFTER this step's update. Rank 0 only —
@@ -1760,12 +1817,19 @@ def main():
1760
  # barrier holds every rank at this point until the save (or its
1761
  # failure) is done, so a slow write can't let other ranks race ahead
1762
  # into the next step's collectives before the checkpoint is durable.
1763
- if cfg.checkpointing and ((step + 1) % cfg.ckpt_freq == 0
 
 
 
 
 
 
 
1764
  or step == cfg.max_steps - 1
1765
  or interrupted):
1766
  if master_process:
1767
  try:
1768
- ckpt.save(step + 1, raw_model, opt, loss_val, last_val, cfg)
1769
  except Exception as e: # noqa: BLE001 — advisory
1770
  print(f"[WARN] checkpoint save at step {step + 1} failed: {e}")
1771
  if ddp:
@@ -1788,6 +1852,20 @@ def main():
1788
  if log_file:
1789
  log_file.close()
1790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1791
  # ---- steady-state summary ----
1792
  # dt/throughput are this rank's own timing; under DDP all ranks run in
1793
  # lockstep (grad sync + the checkpoint barrier), so rank 0's numbers are
@@ -1855,5 +1933,6 @@ if __name__ == "__main__":
1855
 
1856
 
1857
 
 
1858
 
1859
 
 
464
  muon_lr: float = 0.001
465
  muon_min_lr: float = 0.0001
466
 
467
+ max_steps: int = 13351
468
 
469
  warmup_steps: int = 500
470
+ stable_steps: int = int(max_steps * 0.99) - warmup_steps # steps held at max_lr after warmup
471
 
472
 
473
  # ---- Muon (hidden 2D weights); the AdamW fields above still drive the
 
488
 
489
  # ---- checkpointing ----
490
  checkpointing: bool = True
491
+ ckpt_freq: int = 1000 # unused by the save trigger (milestone-based
492
+ # now, see target_losses below) — kept in case
493
+ # interval saving is wanted again later
494
  ckpt_keep: int = 40 # retain the most-recent N checkpoints
495
  # Separate from the AdamW arm's dir: the two checkpoint formats differ (Muon
496
  # momentum vs Adam m/v) and auto-resume picks the newest file it finds.
 
501
  fused_rms_rope: bool = True
502
  activation_checkpoint: bool = False
503
  loss_chunks: int = 1
504
+ # Milestones to record (step + elapsed hours) AND checkpoint on, as the
505
+ # EMA-smoothed train loss passes each one — the same shared target-loss
506
+ # set used across this project's batch-size ablation arms. Raw per-step
507
+ # loss is noisy, so the comparison runs against an EMA, not loss_val
508
+ # directly — same convention as batchsize_scaling.py's EMA_BETA=0.99.
509
+ # A checkpoint is saved the step each one is first crossed (replacing
510
+ # interval-based saving): this ablation's whole point is comparing
511
+ # batch sizes at matched loss levels, so that's the checkpoint that's
512
+ # actually useful, not whatever happens to land on a round step number.
513
+ target_losses: tuple = (11.035316, 2.690595, 2.554042, 2.505404,
514
+ 2.480678, 2.450056, 2.437589)
515
+ target_loss_ema_beta: float = 0.99
516
  # Depth probe: which layers --diag reports gradient / weight statistics for.
517
  probe_layers: tuple = (0, 8, 17, 26, 35)
518
 
 
1078
  out.append((int(m.group(1)), p))
1079
  return sorted(out)
1080
 
1081
+ def save(self, step, model, opt, loss, last_val, cfg, milestones=None):
1082
  tmp = self._path(step) + ".tmp"
1083
  torch.save({"step": step, "loss": loss, "last_val": last_val,
1084
  # B/T/global_batch pin the data stride this checkpoint's
 
1089
  # running, so a GPU-count change alone is not a mismatch.
1090
  "run_cfg": {"B": cfg.B, "T": cfg.T,
1091
  "global_batch": cfg.global_batch},
1092
+ # Target losses already crossed (loss -> (step, hours)).
1093
+ # Without this, a resume re-initializes the EMA from
1094
+ # scratch at whatever loss the model is already at, which
1095
+ # falsely reports every target below that value as
1096
+ # "reached" at the resume step, all at once.
1097
+ "milestones": milestones or {},
1098
  "model": model.state_dict(),
1099
  "opt": opt.state_dict()}, tmp)
1100
  # Rename last: a crash mid-write leaves a .tmp, never a truncated
 
1105
  os.remove(stale)
1106
 
1107
  def load_latest(self, model, opt, cfg, master=True):
1108
+ """-> (start_step, loss, last_val, milestones). (0, 0.0, nan, {})
1109
+ when there is nothing to resume."""
1110
  existing = self._existing()
1111
  if not existing:
1112
+ return 0, 0.0, float("nan"), {}
1113
  _, path = existing[-1]
1114
  return self.load(path, model, opt, cfg, master)
1115
 
1116
  def load(self, path, model, opt, cfg, master=True):
1117
  """Load a specific checkpoint file (e.g. from --resume-from), rather
1118
  than auto-discovering the latest one in self.dir.
1119
+ -> (start_step, loss, last_val, milestones)."""
1120
  blob = torch.load(path, map_location="cpu", weights_only=True)
1121
  model.load_state_dict(blob["model"])
1122
  if not opt.load_state_dict(blob["opt"]):
 
1132
  print(f" {m}")
1133
  print(f"[resume] loaded {path}")
1134
  return (int(blob["step"]), float(blob["loss"]),
1135
+ float(blob.get("last_val", float("nan"))),
1136
+ blob.get("milestones", {}))
1137
 
1138
 
1139
  # =============================================================================
 
1193
  ap = argparse.ArgumentParser(
1194
  description="BLU single-GPU training — PyTorch port of bluscript_single.cpp")
1195
  ap.add_argument("--data-root",
1196
+ default=("/root/.cache/huggingface/hub/datasets--jayasriii--flux_20b/"
1197
  "/snapshots/ddc6cc7e43365a6b9096c30053df0fb001c94f4c"))
1198
  ap.add_argument("--device", type=int, default=None,
1199
  help="CUDA device index (env: BLU_DEVICE)")
 
1225
  help="caps the run for benchmarking; warmup/stable shrink "
1226
  "proportionally so the WSD decay tail stays non-empty "
1227
  "(env: BLU_MAX_STEPS)")
1228
+ ap.add_argument("--target-losses", type=float, nargs="+", default=None,
1229
+ help="loss milestones to record (step + elapsed hours) and "
1230
+ "checkpoint on, as the EMA-smoothed train loss passes "
1231
+ "each one; default is the shared target-loss set used "
1232
+ "across this project's batch-size ablation arms.")
1233
+ ap.add_argument("--target-loss-ema-beta", type=float, default=None,
1234
+ help="EMA smoothing beta applied before comparing against "
1235
+ "--target-losses (default 0.99)")
1236
  # ---- Muon ----
1237
  ap.add_argument("--muon-lr", type=float, default=None,
1238
  help="peak LR for the hidden 2D weights. Under the default "
 
1292
  "vocab_size", "global_batch", "val_freq", "val_loss_steps",
1293
  "ckpt_dir", "ckpt_prefix", "ckpt_freq", "ckpt_keep",
1294
  "muon_lr", "muon_min_lr", "muon_momentum", "muon_ns_steps",
1295
+ "muon_adjust_lr", "target_losses", "target_loss_ema_beta"):
1296
  v = getattr(args, f, None)
1297
  if v is not None:
1298
  setattr(cfg, f, v)
 
1456
  print(f" warmup / steps : {cfg.warmup_steps} / {cfg.max_steps}")
1457
  print(f" WSD stable_steps: {cfg.stable_steps} "
1458
  f"(decay tail={cfg.max_steps - cfg.warmup_steps - cfg.stable_steps})")
1459
+ tl_str = ", ".join(f"{t:g}" for t in cfg.target_losses)
1460
+ print(f" target losses : [{tl_str}] (ema beta {cfg.target_loss_ema_beta:g}, "
1461
+ f"checkpointed on crossing — ckpt_freq is unused)")
1462
  print(f" precision : {args.precision}")
1463
  print(f" seed / determ. : {args.seed} / {args.determinism}")
1464
  print(f" rms+rope : {'fused' if cfg.fused_rms_rope else 'eager'}"
 
1568
  # explicit broadcast. Only rank 0 ever WRITES one (below).
1569
  start_step, ckpt = 0, None
1570
  last_val = float("nan")
1571
+ resumed_milestones = {}
1572
  if cfg.checkpointing or args.resume_from:
1573
  ckpt = CheckpointManager(cfg.ckpt_dir, cfg.ckpt_prefix, cfg.ckpt_keep)
1574
  if args.resume_from:
1575
  if master_process:
1576
  print(f"[resume] loading explicit checkpoint: {args.resume_from}")
1577
+ start_step, resume_loss, last_val, resumed_milestones = ckpt.load(
1578
  args.resume_from, raw_model, opt, cfg, master_process)
1579
  else:
1580
+ start_step, resume_loss, last_val, resumed_milestones = ckpt.load_latest(
1581
  raw_model, opt, cfg, master_process)
1582
  if start_step > 0:
1583
  if master_process:
 
1607
  probes = [L for L in cfg.probe_layers if L < cfg.n_layers]
1608
  steady_dts = []
1609
  t_train_start = time.perf_counter()
1610
+ ema_loss = None # EMA-smoothed train loss, checked against target_losses
1611
+ # Milestones already crossed, restored from the checkpoint on resume so a
1612
+ # restart doesn't re-discover them all at once (see CheckpointManager.save).
1613
+ milestones = dict(resumed_milestones) # target loss -> (step, elapsed_hours)
1614
+ # Remaining targets, highest loss first (the order the EMA passes them in
1615
+ # as it decreases) — excludes anything already in `milestones`.
1616
+ pending_targets = [t for t in sorted(cfg.target_losses, reverse=True)
1617
+ if t not in milestones]
1618
 
1619
  # ---- Ctrl-C: finish the in-flight step, save a checkpoint, exit clean ----
1620
  # Registered here (not earlier) so a Ctrl-C during setup/data-loading still
 
1716
  dist.destroy_process_group()
1717
  raise SystemExit(f"ERROR: NaN/Inf loss at step {step}")
1718
 
1719
+ # EMA-smoothed train loss vs. the pending milestones. loss_val is
1720
+ # already all-reduced across ranks above, so ema_loss (and the
1721
+ # milestones it crosses) comes out identical on every rank without an
1722
+ # extra collective.
1723
+ beta = cfg.target_loss_ema_beta
1724
+ ema_loss = loss_val if ema_loss is None else beta * ema_loss + (1 - beta) * loss_val
1725
+ elapsed_hr = (time.perf_counter() - t_train_start) / 3600.0
1726
+ target_reached = False
1727
+ while pending_targets and ema_loss <= pending_targets[0]:
1728
+ t = pending_targets.pop(0)
1729
+ milestones[t] = (step, elapsed_hr)
1730
+ target_reached = True
1731
+ if master_process:
1732
+ print(f"\n[target] loss <= {t:.6f} reached at step {step} "
1733
+ f"({elapsed_hr:.3f} hrs)\n", flush=True)
1734
+
1735
  # Depth probe (--diag), read BEFORE the update, so these are the
1736
  # pre-update gradients. Compare the profile on a spiking step against a
1737
  # quiet one: a blow-up that begins mid-stack and grows toward the loss
 
1808
  dist.all_reduce(flag, op=dist.ReduceOp.MAX)
1809
  interrupted = flag.item() > 0
1810
 
1811
+ # ---------- checkpoint on target-loss crossing (also end-of-run / Ctrl-C) ----------
1812
  # Labeled step+1 (the next iteration to run) so load_latest resumes
1813
  # exactly where this save left off — no duplicated or skipped iteration.
1814
  # Captures weights + Adam m/v AFTER this step's update. Rank 0 only —
 
1817
  # barrier holds every rank at this point until the save (or its
1818
  # failure) is done, so a slow write can't let other ranks race ahead
1819
  # into the next step's collectives before the checkpoint is durable.
1820
+ #
1821
+ # target_reached replaces interval-based saving: this ablation's whole
1822
+ # point is comparing batch sizes at matched loss levels, so the
1823
+ # checkpoint that's actually useful is the one at each milestone, not
1824
+ # whatever happens to land on a round step number. target_reached is
1825
+ # derived from loss_val, which is already all-reduced above, so it's
1826
+ # identical on every rank without its own collective.
1827
+ if cfg.checkpointing and (target_reached
1828
  or step == cfg.max_steps - 1
1829
  or interrupted):
1830
  if master_process:
1831
  try:
1832
+ ckpt.save(step + 1, raw_model, opt, loss_val, last_val, cfg, milestones)
1833
  except Exception as e: # noqa: BLE001 — advisory
1834
  print(f"[WARN] checkpoint save at step {step + 1} failed: {e}")
1835
  if ddp:
 
1852
  if log_file:
1853
  log_file.close()
1854
 
1855
+ # ---- target-loss milestone summary ----
1856
+ if master_process:
1857
+ print("\n" + "=" * 52)
1858
+ print(" TARGET LOSS MILESTONES")
1859
+ print("=" * 52)
1860
+ print(f" {'target loss':>12} {'step':>10} {'hours':>10}")
1861
+ for t in sorted(cfg.target_losses, reverse=True):
1862
+ if t in milestones:
1863
+ step_hit, hrs = milestones[t]
1864
+ print(f" {t:>12.6f} {step_hit:>10} {hrs:>10.3f}")
1865
+ else:
1866
+ print(f" {t:>12.6f} {'not reached':>10} {'-':>10}")
1867
+ print("=" * 52)
1868
+
1869
  # ---- steady-state summary ----
1870
  # dt/throughput are this rank's own timing; under DDP all ranks run in
1871
  # lockstep (grad sync + the checkpoint barrier), so rank 0's numbers are
 
1933
 
1934
 
1935
 
1936
+
1937
 
1938