Upload folder using huggingface_hub
Browse files- 390M_dynamic_proper_sqrt_lr.csv +0 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00001000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00002000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00003000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00003534.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00004000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00005000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00006000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00006139.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00007000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00008000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00008653.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00009000.pt +3 -0
- 390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00009042.pt +3 -0
- check_target_loss.py +1 -2
- train_390M.py +112 -127
390M_dynamic_proper_sqrt_lr.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00001000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe743aafc4e44b8ed34b1baefaf66ed668a2984d78833af0dbfb4d9bc4273d85
|
| 3 |
+
size 4205813723
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00002000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f32046e81803bcb0cbbd77836ee00e4dd6a81f14387f515fb33627fbbb43d80c
|
| 3 |
+
size 4205813723
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00003000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4e572dc1417532d473f46f78f4178e513b47f2d95b8607ba943b8f596f8b4228
|
| 3 |
+
size 4205813723
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00003534.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:538b1d82b75a25ef4b59da7e5312d60ac356d48e1ef7856be7c764a096c85c13
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00004000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f335e3d50acc98aa32eefc0ff3c61392043753cd68f323982989908fd2bd2d6d
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00005000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cb6fdf3ae40dd0a87ac20b4682873c2215882b180024f67aafe81bb6e16e1d94
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00006000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:572d4e27d8f19b3dbd6d6681b1b564137c9b573e82ca26510421214f6c15424d
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00006139.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c4cfab10e4b7ca011913a2ccc018893caff63004ce45667485a6862c53e2cc42
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00007000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9470bcf094d6ddfd8c613d08584a753e366a11953eb029f993ab3977157b70c0
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00008000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ae4a5c19cec77723c5ccc6b960aad8e6ed33f167975dce5efbf355366dce2b6
|
| 3 |
+
size 4205813787
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00008653.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3b354792c613c9a78a430e6f07e0bbc2ca4acab34d8e31fcceb5be2e21d19581
|
| 3 |
+
size 4205813851
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00009000.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a29a60defa308d66d57455e7b84dfae1fd0fafd77e8d93f53cf4ec896915657a
|
| 3 |
+
size 4205813851
|
390M_dynamic_proper_sqrt_lr/blumodel_muon_dynamic_sqrt_step00009042.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:76616c00871132c9b2b74c23f58d777b4fff3099f2a3eda592d53d38d34eda40
|
| 3 |
+
size 4205813851
|
check_target_loss.py
CHANGED
|
@@ -6,13 +6,11 @@ mid-run, e.g. train_390M_dynamicb.py). Two cases, both reported:
|
|
| 6 |
- target not yet crossed -> fit L(T) = L0 + A*T^-alpha (Eq.14) to the
|
| 7 |
post-warmup smoothed trajectory and solve for T algebraically, flagging
|
| 8 |
how far past the observed range the extrapolation reaches.
|
| 9 |
-
|
| 10 |
Everything runs on a TOKEN axis, not steps: once the batch size changes, a step
|
| 11 |
no longer stands for a fixed amount of data, so L(S) kinks at every switch and
|
| 12 |
a predicted step can't be compared across runs. For a fixed batch T = B*S, so
|
| 13 |
L0 + A*S^-alpha and L0 + A'*T^-alpha are the same curve (same L0 and alpha,
|
| 14 |
A' = A*B^alpha) -- fixed-batch results are unchanged.
|
| 15 |
-
|
| 16 |
Batch size per step: the CSV's `global_batch` column if present (the dynamic
|
| 17 |
trainer logs it), else --batch-size, else parsed from the filename (_1Mbs).
|
| 18 |
Tokens consumed before each step: the CSV's `tokens` column if present, else
|
|
@@ -361,3 +359,4 @@ def main():
|
|
| 361 |
|
| 362 |
if __name__ == "__main__":
|
| 363 |
main()
|
|
|
|
|
|
| 6 |
- target not yet crossed -> fit L(T) = L0 + A*T^-alpha (Eq.14) to the
|
| 7 |
post-warmup smoothed trajectory and solve for T algebraically, flagging
|
| 8 |
how far past the observed range the extrapolation reaches.
|
|
|
|
| 9 |
Everything runs on a TOKEN axis, not steps: once the batch size changes, a step
|
| 10 |
no longer stands for a fixed amount of data, so L(S) kinks at every switch and
|
| 11 |
a predicted step can't be compared across runs. For a fixed batch T = B*S, so
|
| 12 |
L0 + A*S^-alpha and L0 + A'*T^-alpha are the same curve (same L0 and alpha,
|
| 13 |
A' = A*B^alpha) -- fixed-batch results are unchanged.
|
|
|
|
| 14 |
Batch size per step: the CSV's `global_batch` column if present (the dynamic
|
| 15 |
trainer logs it), else --batch-size, else parsed from the filename (_1Mbs).
|
| 16 |
Tokens consumed before each step: the CSV's `tokens` column if present, else
|
|
|
|
| 359 |
|
| 360 |
if __name__ == "__main__":
|
| 361 |
main()
|
| 362 |
+
|
train_390M.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
#!/usr/bin/env python3
|
| 3 |
# =============================================================================
|
| 4 |
# train_muon.py — the MUON arm of train.py. Same model, same data order, same
|
|
@@ -460,11 +459,6 @@ class ModelConfig:
|
|
| 460 |
# Run length in TOKENS. The batch changes during the run, so the step count
|
| 461 |
# (max_steps, and with it the WSD phases) is derived from this in main().
|
| 462 |
total_tokens: int = 14_000_000_000 # what the fixed-batch arms ran: 17,802 x 786,432
|
| 463 |
-
# The train shards are cut into windows of data_grain * T tokens, the way
|
| 464 |
-
# the fixed-batch arms' loader cut them with micro-batch B = data_grain
|
| 465 |
-
# (6 in the 786K arm), so this run trains on the same tokens in the same
|
| 466 |
-
# order whatever its batch, micro-batch or GPU count (see DataLoaderLite).
|
| 467 |
-
data_grain: int = 6
|
| 468 |
|
| 469 |
# Batch-size schedule by tokens consumed: ((start_tokens, tokens/step), ...).
|
| 470 |
# From tripartite_fit.py (BATCH_SIZE_STUDY.md §4.3). For each target loss
|
|
@@ -479,9 +473,10 @@ class ModelConfig:
|
|
| 479 |
# 6.554B -> 8.324B B_opt(2.4501) = 800K
|
| 480 |
# 8.324B -> end B_opt(2.4376) = 807K (the last target fitted)
|
| 481 |
# Zhou et al. (arXiv:2601.05034, §4.3.2) also switch on tokens consumed and
|
| 482 |
-
# keep the LR fixed (LR ~ sqrt(B) gave them nothing);
|
| 483 |
-
# is that arm. THIS arm
|
| 484 |
-
#
|
|
|
|
| 485 |
# not whole numbers of sequences; resolve_batch_schedule() rounds them for
|
| 486 |
# the GPU count and main() prints what runs. Empty = global_batch throughout.
|
| 487 |
batch_schedule: tuple = ((0, 578_286),
|
|
@@ -539,8 +534,10 @@ class ModelConfig:
|
|
| 539 |
# target-loss checkpoints are never pruned
|
| 540 |
# Separate from the AdamW arm's dir: the two checkpoint formats differ (Muon
|
| 541 |
# momentum vs Adam m/v) and auto-resume picks the newest file it finds.
|
| 542 |
-
ckpt_dir: str = "
|
| 543 |
-
ckpt_prefix: str = "
|
|
|
|
|
|
|
| 544 |
|
| 545 |
# ---- runtime knobs (not in the C++ struct) ----
|
| 546 |
fused_rms_rope: bool = True
|
|
@@ -594,6 +591,7 @@ class BatchStage:
|
|
| 594 |
|
| 595 |
def resolve_batch_schedule(cfg, world_size):
|
| 596 |
"""cfg.batch_schedule -> [BatchStage], split over `world_size` GPUs.
|
|
|
|
| 597 |
A step is micro_B * grad_accum sequences on every rank, so a batch must be
|
| 598 |
a whole number of sequences per rank (a multiple of T * world_size) that
|
| 599 |
factors as micro_B * grad_accum with micro_B <= cfg.B. A target that does
|
|
@@ -638,6 +636,7 @@ def keep_batch_schedule(saved, stages, cfg, world_size):
|
|
| 638 |
"""On resume: the batches a checkpoint's run was running, split over this
|
| 639 |
many GPUs, or None if the schedule config changed since (its starts or
|
| 640 |
targets differ from `stages`) or a batch no longer splits evenly.
|
|
|
|
| 641 |
resolve_batch_schedule() rounds for the GPU count, so a run restarted on a
|
| 642 |
different count would otherwise switch to that count's rounding mid-run.
|
| 643 |
Going down by a factor (8 -> 4, 2, 1) always keeps the batches.
|
|
@@ -707,29 +706,31 @@ def print_batch_schedule(stages, runs, steps_label, ref_batch):
|
|
| 707 |
# 3. Data — port of the C++ DataLoaderLite (Tensor-Implementations/include/
|
| 708 |
# DataLoader.h), including its pinned-host / side-stream prefetch ring.
|
| 709 |
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 710 |
# Shards are HEADERLESS flat uint16 token files. A shard is "val" if "val"
|
| 711 |
# appears anywhere in its basename, "train" otherwise — this covers both an
|
| 712 |
# explicit flux_train_*/flux_val_* naming and an unmarked-train naming like
|
| 713 |
# flux_20b_shard_*.bin alongside a separately named *val* shard. For a batch
|
| 714 |
# at position p:
|
| 715 |
# x = tokens[p : p+B*T] y = tokens[p+1 : p+B*T+1]
|
| 716 |
-
#
|
| 717 |
-
#
|
| 718 |
-
#
|
| 719 |
-
#
|
| 720 |
-
#
|
| 721 |
-
#
|
| 722 |
-
#
|
| 723 |
-
#
|
| 724 |
-
#
|
| 725 |
-
#
|
| 726 |
-
#
|
| 727 |
-
# from the stream, never the stream itself. After E tokens this run is E
|
| 728 |
-
# tokens into the stream, which is where the fixed-batch arms' ranks are
|
| 729 |
-
# spread around (theirs drift apart by a few M tokens across shards).
|
| 730 |
-
#
|
| 731 |
-
# The position (shard, offset) is the same on every rank, and a checkpoint
|
| 732 |
-
# stores it to resume from.
|
| 733 |
#
|
| 734 |
# The prefetch mirrors the C++ ring: NBUF pinned staging buffers and NBUF device
|
| 735 |
# tensors, H2D issued on a side stream, CUDA events ordering producer against
|
|
@@ -740,18 +741,20 @@ class DataLoaderLite:
|
|
| 740 |
NBUF = 4
|
| 741 |
|
| 742 |
def __init__(self, B, T, split, data_root, device, master=True,
|
| 743 |
-
max_tokens_per_shard=100_000_000, rank=0, world_size=1
|
| 744 |
assert split in ("train", "val")
|
| 745 |
-
# B is also the largest micro-batch
|
| 746 |
-
#
|
| 747 |
self.B = self.B_max = B
|
|
|
|
| 748 |
self.T = T
|
| 749 |
self.split = split
|
|
|
|
| 750 |
self.max_tokens = max_tokens_per_shard
|
| 751 |
self.device = device
|
| 752 |
-
# DDP: every rank reads the SAME
|
| 753 |
-
#
|
| 754 |
-
#
|
| 755 |
self.rank = rank
|
| 756 |
self.world_size = world_size
|
| 757 |
|
|
@@ -769,14 +772,6 @@ class DataLoaderLite:
|
|
| 769 |
if master:
|
| 770 |
print(f"found {len(self.shards)} shards for split {split}")
|
| 771 |
|
| 772 |
-
# Tokens of each shard the stream uses: its whole windows (see above).
|
| 773 |
-
G = (grain or B) * T
|
| 774 |
-
self.usable = [max(0, (min(os.path.getsize(p) // 2, max_tokens_per_shard) - 1) // G) * G
|
| 775 |
-
for p in self.shards]
|
| 776 |
-
if not any(self.usable):
|
| 777 |
-
raise RuntimeError(f"no {split} shard holds one window of {G} tokens")
|
| 778 |
-
self.tok, self.tok_shard = None, None
|
| 779 |
-
|
| 780 |
BT = B * T
|
| 781 |
self.pin_x = [torch.empty(BT, dtype=torch.int64).pin_memory() for _ in range(self.NBUF)]
|
| 782 |
self.pin_y = [torch.empty(BT, dtype=torch.int64).pin_memory() for _ in range(self.NBUF)]
|
|
@@ -786,63 +781,73 @@ class DataLoaderLite:
|
|
| 786 |
self.h2d_done = [torch.cuda.Event() for _ in range(self.NBUF)]
|
| 787 |
self.h2d_recorded = [False] * self.NBUF
|
| 788 |
self.where = [(0, 0)] * self.NBUF # (shard_index, token_offset)
|
| 789 |
-
self.block = [(0, 0)] * self.NBUF #
|
| 790 |
self.reset()
|
| 791 |
|
| 792 |
-
# --
|
| 793 |
-
def
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
|
| 818 |
def reset(self):
|
| 819 |
self.seek(0, 0)
|
| 820 |
|
| 821 |
# -- position (the same on every rank) -----------------------------------
|
|
|
|
| 822 |
def state(self):
|
| 823 |
-
"""-> (shard_index,
|
| 824 |
-
|
| 825 |
-
return self.block[self.buf] if self.primed else (self.cur, self.
|
| 826 |
|
| 827 |
-
def seek(self, shard,
|
| 828 |
"""Continue from a state(). Drops anything already prefetched."""
|
| 829 |
self.copy_stream.synchronize()
|
| 830 |
-
self.
|
|
|
|
|
|
|
| 831 |
self.primed = False
|
| 832 |
self.buf = 0
|
| 833 |
|
| 834 |
-
def
|
| 835 |
-
"""
|
|
|
|
| 836 |
if not 1 <= B <= self.B_max:
|
| 837 |
raise ValueError(f"micro-batch {B} outside 1..{self.B_max}")
|
| 838 |
-
if B != self.B:
|
| 839 |
self.seek(*self.state())
|
| 840 |
-
self.B = B
|
| 841 |
-
|
| 842 |
-
def skip_tokens(self, n):
|
| 843 |
-
"""Fast-forward n tokens of the stream without touching the GPU."""
|
| 844 |
-
self.seek(*self.state())
|
| 845 |
-
self._advance(n // self.T)
|
| 846 |
|
| 847 |
# -- prefetch ------------------------------------------------------------
|
| 848 |
def _stage(self, idx):
|
|
@@ -857,17 +862,15 @@ class DataLoaderLite:
|
|
| 857 |
if self.h2d_recorded[idx]:
|
| 858 |
self.h2d_done[idx].synchronize()
|
| 859 |
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
self.pin_y[idx][at: at + k * self.T].copy_(torch.from_numpy(buf[1:]))
|
| 870 |
-
at += k * self.T
|
| 871 |
|
| 872 |
with torch.cuda.stream(self.copy_stream):
|
| 873 |
self.dev_x[idx][:B].view(-1).copy_(self.pin_x[idx][:BT], non_blocking=True)
|
|
@@ -898,6 +901,13 @@ class DataLoaderLite:
|
|
| 898 |
return (self.dev_x[i][:self.B], self.dev_y[i][:self.B],
|
| 899 |
self.where[i][0], self.where[i][1])
|
| 900 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 901 |
|
| 902 |
# =============================================================================
|
| 903 |
# 4. Model.
|
|
@@ -1469,15 +1479,6 @@ def build_argparser():
|
|
| 1469 |
ap.add_argument("--total-tokens", type=float, default=None,
|
| 1470 |
help="run length in tokens (default 14e9); the step count is "
|
| 1471 |
"derived from it and the batch schedule")
|
| 1472 |
-
ap.add_argument("--data-grain", type=int, default=None,
|
| 1473 |
-
help="cut the train shards into windows of this many sequences, "
|
| 1474 |
-
"as the fixed-batch arms' loader did with that micro-batch "
|
| 1475 |
-
"(default 6, the 786K arm's)")
|
| 1476 |
-
ap.add_argument("--realign-data", action="store_true",
|
| 1477 |
-
help="on resume from a checkpoint an earlier loader wrote, move "
|
| 1478 |
-
"the data position back to tokens-seen into the stream, "
|
| 1479 |
-
"where the fixed-batch arms are; the tokens in between are "
|
| 1480 |
-
"seen twice. Default: continue from the stored position.")
|
| 1481 |
ap.add_argument("--max-steps", type=int, default=None,
|
| 1482 |
help="caps the run for benchmarking; warmup/stable shrink "
|
| 1483 |
"proportionally so the WSD decay tail stays non-empty "
|
|
@@ -1518,7 +1519,7 @@ def build_argparser():
|
|
| 1518 |
help="per-micro-batch loss + depth probes (env: BLU_DIAG=1)")
|
| 1519 |
ap.add_argument("--warmup-measure", type=int, default=3,
|
| 1520 |
help="steps excluded from the steady-state throughput summary")
|
| 1521 |
-
ap.add_argument("--csv", default="
|
| 1522 |
help="CSV log path; appended (not truncated) on resume. The "
|
| 1523 |
"first 8 columns match the AdamW arm's CSV exactly, so "
|
| 1524 |
"the two files plot against each other directly; "
|
|
@@ -1552,7 +1553,7 @@ def build_argparser():
|
|
| 1552 |
def config_from_args(args):
|
| 1553 |
cfg = ModelConfig()
|
| 1554 |
for f in ("B", "T", "d_model", "n_layers", "q_heads", "kv_heads", "ffn_hidden",
|
| 1555 |
-
"vocab_size", "global_batch", "
|
| 1556 |
"ckpt_dir", "ckpt_prefix", "ckpt_freq", "ckpt_keep",
|
| 1557 |
"muon_lr", "muon_min_lr", "muon_momentum", "muon_ns_steps",
|
| 1558 |
"muon_adjust_lr", "target_losses", "target_loss_ema_beta",
|
|
@@ -1834,7 +1835,7 @@ def main():
|
|
| 1834 |
# the same one.
|
| 1835 |
train_loader = DataLoaderLite(cfg.B, cfg.T, "train", args.data_root, device,
|
| 1836 |
master=master_process, rank=ddp_rank,
|
| 1837 |
-
world_size=ddp_world_size
|
| 1838 |
# rank=0, world_size=1 UNCONDITIONALLY, on every rank: val_loss_steps
|
| 1839 |
# windows at fixed token offsets 0, BT, 2BT, ... Sharding this by
|
| 1840 |
# ddp_rank/ddp_world_size like the train loader would make the reported
|
|
@@ -1882,21 +1883,6 @@ def main():
|
|
| 1882 |
# are independent of B and world_size, so this is exact.
|
| 1883 |
tokens_seen = progress["tokens_seen"]
|
| 1884 |
train_loader.seek(*progress["data_pos"])
|
| 1885 |
-
# 0 when this loader wrote the checkpoint: E tokens in, the run
|
| 1886 |
-
# is E tokens into the stream. An earlier loader dropped more
|
| 1887 |
-
# of each shard's tail, which left the run ahead of that.
|
| 1888 |
-
ahead = train_loader.stream_offset(*progress["data_pos"], tokens_seen)
|
| 1889 |
-
if args.realign_data and ahead:
|
| 1890 |
-
train_loader.reset()
|
| 1891 |
-
train_loader.skip_tokens(tokens_seen)
|
| 1892 |
-
if master_process:
|
| 1893 |
-
print(f"[resume] --realign-data: moved the data position back "
|
| 1894 |
-
f"{ahead:,} tokens to {tokens_seen:,} tokens into the "
|
| 1895 |
-
f"stream; those tokens are seen a second time")
|
| 1896 |
-
elif master_process and ahead:
|
| 1897 |
-
print(f"[resume] the data position is {ahead:+,} tokens from "
|
| 1898 |
-
f"{tokens_seen:,} tokens into the stream (left by an "
|
| 1899 |
-
f"earlier loader); it stays at that offset from here")
|
| 1900 |
# Keep running the batches the run started with when this GPU
|
| 1901 |
# count can (always true going 8 -> 4, 2, 1), rather than this
|
| 1902 |
# count's own rounding: changing GPUs then changes nothing but
|
|
@@ -1916,10 +1902,11 @@ def main():
|
|
| 1916 |
f"follows the schedule above")
|
| 1917 |
else:
|
| 1918 |
# A checkpoint from a trainer without a batch schedule: assume
|
| 1919 |
-
# steps [0, start_step) ran this schedule
|
| 1920 |
-
|
| 1921 |
-
|
| 1922 |
-
|
|
|
|
| 1923 |
if master_process:
|
| 1924 |
print(f"[resume] checkpoint has no data position; replayed this "
|
| 1925 |
f"schedule to step {start_step} ({tokens_seen:,} tokens)")
|
|
@@ -2030,7 +2017,7 @@ def main():
|
|
| 2030 |
# carry straight on.
|
| 2031 |
new_stage = stage_at(stages, tokens_seen)
|
| 2032 |
if stage is None or (new_stage.batch, new_stage.micro_B) != (stage.batch, stage.micro_B):
|
| 2033 |
-
train_loader.
|
| 2034 |
grad_accum = new_stage.grad_accum
|
| 2035 |
tokens_per_step = new_stage.batch
|
| 2036 |
lr_scale = lr_batch_scale(new_stage.batch, ref_batch)
|
|
@@ -2330,8 +2317,6 @@ if __name__ == "__main__":
|
|
| 2330 |
|
| 2331 |
|
| 2332 |
|
| 2333 |
-
|
| 2334 |
-
|
| 2335 |
|
| 2336 |
|
| 2337 |
|
|
|
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# =============================================================================
|
| 3 |
# train_muon.py — the MUON arm of train.py. Same model, same data order, same
|
|
|
|
| 459 |
# Run length in TOKENS. The batch changes during the run, so the step count
|
| 460 |
# (max_steps, and with it the WSD phases) is derived from this in main().
|
| 461 |
total_tokens: int = 14_000_000_000 # what the fixed-batch arms ran: 17,802 x 786,432
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
|
| 463 |
# Batch-size schedule by tokens consumed: ((start_tokens, tokens/step), ...).
|
| 464 |
# From tripartite_fit.py (BATCH_SIZE_STUDY.md §4.3). For each target loss
|
|
|
|
| 473 |
# 6.554B -> 8.324B B_opt(2.4501) = 800K
|
| 474 |
# 8.324B -> end B_opt(2.4376) = 807K (the last target fitted)
|
| 475 |
# Zhou et al. (arXiv:2601.05034, §4.3.2) also switch on tokens consumed and
|
| 476 |
+
# keep the LR fixed (LR ~ sqrt(B) gave them nothing); train_390M_dynamicb_skip.py
|
| 477 |
+
# is that arm. THIS arm is the same run -- same loader, same batch schedule,
|
| 478 |
+
# same switch points -- except that the LR follows sqrt(batch) at every
|
| 479 |
+
# switch (see lr_ref_batch below). The fitted values are
|
| 480 |
# not whole numbers of sequences; resolve_batch_schedule() rounds them for
|
| 481 |
# the GPU count and main() prints what runs. Empty = global_batch throughout.
|
| 482 |
batch_schedule: tuple = ((0, 578_286),
|
|
|
|
| 534 |
# target-loss checkpoints are never pruned
|
| 535 |
# Separate from the AdamW arm's dir: the two checkpoint formats differ (Muon
|
| 536 |
# momentum vs Adam m/v) and auto-resume picks the newest file it finds.
|
| 537 |
+
ckpt_dir: str = "ckpt_blu_muon_dynamic_sqrt"
|
| 538 |
+
ckpt_prefix: str = "blumodel_muon_dynamic_sqrt"
|
| 539 |
+
|
| 540 |
+
|
| 541 |
|
| 542 |
# ---- runtime knobs (not in the C++ struct) ----
|
| 543 |
fused_rms_rope: bool = True
|
|
|
|
| 591 |
|
| 592 |
def resolve_batch_schedule(cfg, world_size):
|
| 593 |
"""cfg.batch_schedule -> [BatchStage], split over `world_size` GPUs.
|
| 594 |
+
|
| 595 |
A step is micro_B * grad_accum sequences on every rank, so a batch must be
|
| 596 |
a whole number of sequences per rank (a multiple of T * world_size) that
|
| 597 |
factors as micro_B * grad_accum with micro_B <= cfg.B. A target that does
|
|
|
|
| 636 |
"""On resume: the batches a checkpoint's run was running, split over this
|
| 637 |
many GPUs, or None if the schedule config changed since (its starts or
|
| 638 |
targets differ from `stages`) or a batch no longer splits evenly.
|
| 639 |
+
|
| 640 |
resolve_batch_schedule() rounds for the GPU count, so a run restarted on a
|
| 641 |
different count would otherwise switch to that count's rounding mid-run.
|
| 642 |
Going down by a factor (8 -> 4, 2, 1) always keeps the batches.
|
|
|
|
| 706 |
# 3. Data — port of the C++ DataLoaderLite (Tensor-Implementations/include/
|
| 707 |
# DataLoader.h), including its pinned-host / side-stream prefetch ring.
|
| 708 |
#
|
| 709 |
+
# THIS FILE'S LOADER is the one the 390M dynamic-batch run started with, plus a
|
| 710 |
+
# skip for shards shorter than one step (flux_20b's shard_350001), so a resume
|
| 711 |
+
# of that run continues on exactly the data it would have read. It drops each
|
| 712 |
+
# shard's tail per step, which leaves it a little ahead of the fixed-batch
|
| 713 |
+
# arms' data (~+24M tokens at 7.2B). train_390M_dynamicb.py has the loader that
|
| 714 |
+
# reads the fixed-batch arms' token stream instead (restart from scratch, or
|
| 715 |
+
# --realign-data).
|
| 716 |
+
#
|
| 717 |
# Shards are HEADERLESS flat uint16 token files. A shard is "val" if "val"
|
| 718 |
# appears anywhere in its basename, "train" otherwise — this covers both an
|
| 719 |
# explicit flux_train_*/flux_val_* naming and an unmarked-train naming like
|
| 720 |
# flux_20b_shard_*.bin alongside a separately named *val* shard. For a batch
|
| 721 |
# at position p:
|
| 722 |
# x = tokens[p : p+B*T] y = tokens[p+1 : p+B*T+1]
|
| 723 |
+
# Each micro-step reads one contiguous block of world_size*B*T tokens starting
|
| 724 |
+
# at `base`, rank r taking p = base + r*B*T, and base strides by the block. An
|
| 725 |
+
# optimizer step is grad_accum such blocks, so it reads one contiguous range of
|
| 726 |
+
# its batch's tokens; a shard tail that cannot fit a whole STEP is dropped and
|
| 727 |
+
# the loader wraps to the next shard. All ranks move through the blocks
|
| 728 |
+
# together, so the position (shard, base) is one value on every rank, and
|
| 729 |
+
# which tokens a step reads depends only on the batch sizes, not on how they
|
| 730 |
+
# are split into world_size, B and grad_accum: a run resumed on another GPU
|
| 731 |
+
# count reads the same tokens per step. The batch schedule changes the split
|
| 732 |
+
# between steps without skipping or repeating a token, and a checkpoint stores
|
| 733 |
+
# the position to resume from directly.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
#
|
| 735 |
# The prefetch mirrors the C++ ring: NBUF pinned staging buffers and NBUF device
|
| 736 |
# tensors, H2D issued on a side stream, CUDA events ordering producer against
|
|
|
|
| 741 |
NBUF = 4
|
| 742 |
|
| 743 |
def __init__(self, B, T, split, data_root, device, master=True,
|
| 744 |
+
max_tokens_per_shard=100_000_000, rank=0, world_size=1):
|
| 745 |
assert split in ("train", "val")
|
| 746 |
+
# B is also the largest micro-batch set_step() can switch to: the
|
| 747 |
+
# buffers below are sized for it. A step is grad_accum micro-steps.
|
| 748 |
self.B = self.B_max = B
|
| 749 |
+
self.grad_accum = 1
|
| 750 |
self.T = T
|
| 751 |
self.split = split
|
| 752 |
+
self.master = master
|
| 753 |
self.max_tokens = max_tokens_per_shard
|
| 754 |
self.device = device
|
| 755 |
+
# DDP: every rank reads the SAME shard list but a DISJOINT slice of each
|
| 756 |
+
# block (offset by rank), so a training step's world_size*grad_accum
|
| 757 |
+
# micro-batches never repeat a window.
|
| 758 |
self.rank = rank
|
| 759 |
self.world_size = world_size
|
| 760 |
|
|
|
|
| 772 |
if master:
|
| 773 |
print(f"found {len(self.shards)} shards for split {split}")
|
| 774 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
BT = B * T
|
| 776 |
self.pin_x = [torch.empty(BT, dtype=torch.int64).pin_memory() for _ in range(self.NBUF)]
|
| 777 |
self.pin_y = [torch.empty(BT, dtype=torch.int64).pin_memory() for _ in range(self.NBUF)]
|
|
|
|
| 781 |
self.h2d_done = [torch.cuda.Event() for _ in range(self.NBUF)]
|
| 782 |
self.h2d_recorded = [False] * self.NBUF
|
| 783 |
self.where = [(0, 0)] * self.NBUF # (shard_index, token_offset)
|
| 784 |
+
self.block = [(0, 0)] * self.NBUF # (shard_index, base) of each slot's block
|
| 785 |
self.reset()
|
| 786 |
|
| 787 |
+
# -- shard plumbing ------------------------------------------------------
|
| 788 |
+
def _load_shard(self, i):
|
| 789 |
+
self.cur = i
|
| 790 |
+
arr = np.memmap(self.shards[i], dtype=np.uint16, mode="r")
|
| 791 |
+
self.tok = arr
|
| 792 |
+
self.ntok = min(len(arr), self.max_tokens)
|
| 793 |
+
|
| 794 |
+
def _advance_shard(self):
|
| 795 |
+
self._load_shard((self.cur + 1) % len(self.shards))
|
| 796 |
+
self.base = 0
|
| 797 |
+
|
| 798 |
+
def _step_tokens(self):
|
| 799 |
+
return self.B * self.T * self.world_size * self.grad_accum
|
| 800 |
+
|
| 801 |
+
def _next_block(self):
|
| 802 |
+
"""-> base of the next block, moving past it. A block that starts a
|
| 803 |
+
step first checks the WHOLE step fits (+1 for the last shifted
|
| 804 |
+
target), and leaves the shard's tail for the next shard if not — or
|
| 805 |
+
the whole shard, if it is smaller than a step (flux_20b's
|
| 806 |
+
shard_350001 holds 574,132 tokens)."""
|
| 807 |
+
if self.micro == 0:
|
| 808 |
+
for _ in range(len(self.shards) + 1):
|
| 809 |
+
if self.base + self._step_tokens() + 1 <= self.ntok:
|
| 810 |
+
break
|
| 811 |
+
self._advance_shard()
|
| 812 |
+
if self.master and self._step_tokens() + 1 > self.ntok:
|
| 813 |
+
print(f"[data] skipping {os.path.basename(self.shards[self.cur])}: "
|
| 814 |
+
f"{self.ntok:,} tokens, less than one step "
|
| 815 |
+
f"({self._step_tokens():,})", flush=True)
|
| 816 |
+
else:
|
| 817 |
+
raise RuntimeError(f"no {self.split} shard holds one step of "
|
| 818 |
+
f"{self._step_tokens():,} tokens")
|
| 819 |
+
base = self.base
|
| 820 |
+
self.base += self.B * self.T * self.world_size
|
| 821 |
+
self.micro = (self.micro + 1) % self.grad_accum
|
| 822 |
+
return base
|
| 823 |
|
| 824 |
def reset(self):
|
| 825 |
self.seek(0, 0)
|
| 826 |
|
| 827 |
# -- position (the same on every rank) -----------------------------------
|
| 828 |
+
# Meant for between optimizer steps, where the next block starts a step.
|
| 829 |
def state(self):
|
| 830 |
+
"""-> (shard_index, base) of the next block next_batch() will return.
|
| 831 |
+
Prefetched blocks it has not returned yet do not count as consumed."""
|
| 832 |
+
return self.block[self.buf] if self.primed else (self.cur, self.base)
|
| 833 |
|
| 834 |
+
def seek(self, shard, base):
|
| 835 |
"""Continue from a state(). Drops anything already prefetched."""
|
| 836 |
self.copy_stream.synchronize()
|
| 837 |
+
self._load_shard(shard)
|
| 838 |
+
self.base = base
|
| 839 |
+
self.micro = 0 # index in its step of the next block to stage
|
| 840 |
self.primed = False
|
| 841 |
self.buf = 0
|
| 842 |
|
| 843 |
+
def set_step(self, B, grad_accum=1):
|
| 844 |
+
"""From the next step on, serve grad_accum micro-steps of B sequences
|
| 845 |
+
per rank per step."""
|
| 846 |
if not 1 <= B <= self.B_max:
|
| 847 |
raise ValueError(f"micro-batch {B} outside 1..{self.B_max}")
|
| 848 |
+
if (B, grad_accum) != (self.B, self.grad_accum):
|
| 849 |
self.seek(*self.state())
|
| 850 |
+
self.B, self.grad_accum = B, grad_accum
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
|
| 852 |
# -- prefetch ------------------------------------------------------------
|
| 853 |
def _stage(self, idx):
|
|
|
|
| 862 |
if self.h2d_recorded[idx]:
|
| 863 |
self.h2d_done[idx].synchronize()
|
| 864 |
|
| 865 |
+
# Recorded BEFORE _next_block()'s does-the-step-fit check, so a seek()
|
| 866 |
+
# to it after set_step() decides that again for the new step size.
|
| 867 |
+
self.block[idx] = (self.cur, self.base)
|
| 868 |
+
base = self._next_block()
|
| 869 |
+
pos = base + BT * self.rank
|
| 870 |
+
self.where[idx] = (self.cur, pos)
|
| 871 |
+
buf = np.asarray(self.tok[pos: pos + BT + 1]).astype(np.int64)
|
| 872 |
+
self.pin_x[idx][:BT].copy_(torch.from_numpy(buf[:-1]))
|
| 873 |
+
self.pin_y[idx][:BT].copy_(torch.from_numpy(buf[1:]))
|
|
|
|
|
|
|
| 874 |
|
| 875 |
with torch.cuda.stream(self.copy_stream):
|
| 876 |
self.dev_x[idx][:B].view(-1).copy_(self.pin_x[idx][:BT], non_blocking=True)
|
|
|
|
| 901 |
return (self.dev_x[i][:self.B], self.dev_y[i][:self.B],
|
| 902 |
self.where[i][0], self.where[i][1])
|
| 903 |
|
| 904 |
+
def skip_batches(self, n):
|
| 905 |
+
"""Fast-forward `n` micro-steps without touching the GPU (resume from a
|
| 906 |
+
checkpoint that has no stored position)."""
|
| 907 |
+
self.seek(*self.state())
|
| 908 |
+
for _ in range(n):
|
| 909 |
+
self._next_block()
|
| 910 |
+
|
| 911 |
|
| 912 |
# =============================================================================
|
| 913 |
# 4. Model.
|
|
|
|
| 1479 |
ap.add_argument("--total-tokens", type=float, default=None,
|
| 1480 |
help="run length in tokens (default 14e9); the step count is "
|
| 1481 |
"derived from it and the batch schedule")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1482 |
ap.add_argument("--max-steps", type=int, default=None,
|
| 1483 |
help="caps the run for benchmarking; warmup/stable shrink "
|
| 1484 |
"proportionally so the WSD decay tail stays non-empty "
|
|
|
|
| 1519 |
help="per-micro-batch loss + depth probes (env: BLU_DIAG=1)")
|
| 1520 |
ap.add_argument("--warmup-measure", type=int, default=3,
|
| 1521 |
help="steps excluded from the steady-state throughput summary")
|
| 1522 |
+
ap.add_argument("--csv", default="blu_muon_log_dy_sq.csv",
|
| 1523 |
help="CSV log path; appended (not truncated) on resume. The "
|
| 1524 |
"first 8 columns match the AdamW arm's CSV exactly, so "
|
| 1525 |
"the two files plot against each other directly; "
|
|
|
|
| 1553 |
def config_from_args(args):
|
| 1554 |
cfg = ModelConfig()
|
| 1555 |
for f in ("B", "T", "d_model", "n_layers", "q_heads", "kv_heads", "ffn_hidden",
|
| 1556 |
+
"vocab_size", "global_batch", "val_freq", "val_loss_steps",
|
| 1557 |
"ckpt_dir", "ckpt_prefix", "ckpt_freq", "ckpt_keep",
|
| 1558 |
"muon_lr", "muon_min_lr", "muon_momentum", "muon_ns_steps",
|
| 1559 |
"muon_adjust_lr", "target_losses", "target_loss_ema_beta",
|
|
|
|
| 1835 |
# the same one.
|
| 1836 |
train_loader = DataLoaderLite(cfg.B, cfg.T, "train", args.data_root, device,
|
| 1837 |
master=master_process, rank=ddp_rank,
|
| 1838 |
+
world_size=ddp_world_size)
|
| 1839 |
# rank=0, world_size=1 UNCONDITIONALLY, on every rank: val_loss_steps
|
| 1840 |
# windows at fixed token offsets 0, BT, 2BT, ... Sharding this by
|
| 1841 |
# ddp_rank/ddp_world_size like the train loader would make the reported
|
|
|
|
| 1883 |
# are independent of B and world_size, so this is exact.
|
| 1884 |
tokens_seen = progress["tokens_seen"]
|
| 1885 |
train_loader.seek(*progress["data_pos"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1886 |
# Keep running the batches the run started with when this GPU
|
| 1887 |
# count can (always true going 8 -> 4, 2, 1), rather than this
|
| 1888 |
# count's own rounding: changing GPUs then changes nothing but
|
|
|
|
| 1902 |
f"follows the schedule above")
|
| 1903 |
else:
|
| 1904 |
# A checkpoint from a trainer without a batch schedule: assume
|
| 1905 |
+
# steps [0, start_step) ran this schedule and replay it.
|
| 1906 |
+
done, tokens_seen = walk_schedule(stages, float("inf"), start_step)
|
| 1907 |
+
for s, n in done:
|
| 1908 |
+
train_loader.set_step(s.micro_B, s.grad_accum)
|
| 1909 |
+
train_loader.skip_batches(n * s.grad_accum)
|
| 1910 |
if master_process:
|
| 1911 |
print(f"[resume] checkpoint has no data position; replayed this "
|
| 1912 |
f"schedule to step {start_step} ({tokens_seen:,} tokens)")
|
|
|
|
| 2017 |
# carry straight on.
|
| 2018 |
new_stage = stage_at(stages, tokens_seen)
|
| 2019 |
if stage is None or (new_stage.batch, new_stage.micro_B) != (stage.batch, stage.micro_B):
|
| 2020 |
+
train_loader.set_step(new_stage.micro_B, new_stage.grad_accum)
|
| 2021 |
grad_accum = new_stage.grad_accum
|
| 2022 |
tokens_per_step = new_stage.batch
|
| 2023 |
lr_scale = lr_batch_scale(new_stage.batch, ref_batch)
|
|
|
|
| 2317 |
|
| 2318 |
|
| 2319 |
|
|
|
|
|
|
|
| 2320 |
|
| 2321 |
|
| 2322 |
|