Upload folder using huggingface_hub
Browse files- 390M_1M_8ksteps_main.csv +0 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00003461.pt +3 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00004065.pt +3 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00005697.pt +3 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00006362.pt +3 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00007249.pt +3 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00007250.pt +3 -0
- 390M_1M_8ksteps_main/blumodel_muon_step00008700.pt +3 -0
- check_target_loss.py +28 -8
- loss_temp.csv +0 -0
- train_390M.py +95 -16
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-
|
| 126 |
-
help="EMA
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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
|
|
|
|
| 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 =
|
| 468 |
|
| 469 |
warmup_steps: int = 500
|
| 470 |
-
stable_steps: int = int(max_steps * 0.
|
| 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 #
|
|
|
|
|
|
|
| 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
|
| 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=("/
|
| 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 |
-
# ----------
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|