jayasriii commited on
Commit
3c7f694
·
verified ·
1 Parent(s): 67d1f93

Upload folder using huggingface_hub

Browse files
390M_dynamic_sqrtlr.csv ADDED
The diff for this file is too large to render. See raw diff
 
390M_dynamic_sqrtlr/blumodel_muon_dynamic_step00001000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a6c95346bd3c7098ca6a5f56850ec5036cbbdaa117fe9c5a58825d8226f3dc9e
3
+ size 4205808528
390M_dynamic_sqrtlr/blumodel_muon_dynamic_step00002000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:69431dad4d781999264aabd41cc9385dfd8578376be65c4461455a2f0396d6af
3
+ size 4205808528
390M_dynamic_sqrtlr/blumodel_muon_dynamic_step00003543.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:46b54aa4edca016657c6086e1276921ac1ad950810a043b10ac69a6d0e4f4e40
3
+ size 4205808528
390M_dynamic_sqrtlr/blumodel_muon_dynamic_step00004000.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:914cd97d61da301cbe82fc261eefb9069c016173009fc69426ba7fbe2cdd11aa
3
+ size 4205808592
check_target_loss.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Find when a training run (CSV log) reaches a target loss, after smoothing.
3
+ Works for fixed-batch runs and for dynamic-batch runs (batch size changes
4
+ mid-run, e.g. train_390M_dynamicb.py). Two cases, both reported:
5
+ - target already crossed within the logged data -> interpolated step/tokens.
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
19
+ accumulated from the per-step batch (= step * B for a fixed batch).
20
+ """
21
+ import argparse
22
+ import re
23
+ import sys
24
+
25
+ import numpy as np
26
+ import pandas as pd
27
+ from scipy.optimize import curve_fit
28
+
29
+ EXTRAPOLATION_WARN_FACTOR = 3.0
30
+ CONVERGENCE_TOL = 0.05
31
+ SWEEP_STEP = 300
32
+ SWEEP_MIN_WINDOW = 500
33
+ EMA_TOKEN_WINDOW = 100_000_000 # smoothing window in TOKENS, not steps -- see
34
+ # batchsize_scaling.py for why: a fixed step
35
+ # count covers a different amount of data per
36
+ # run depending on batch size, which biases
37
+ # the cross-batch-size comparison. The same
38
+ # holds across the stages of one dynamic run.
39
+ TOKEN_UNIT = 1e9 # the fit's x-axis is tokens / TOKEN_UNIT, which
40
+ # keeps A near O(1) so curve_fit's p0 is sane
41
+
42
+
43
+ def power_law(t, l0, a, alpha):
44
+ return l0 + a * np.power(t, -alpha)
45
+
46
+
47
+ def parse_batch_size_from_filename(path):
48
+ m = re.search(r"_([\d.]+)([MK])bs", path, re.IGNORECASE)
49
+ if not m:
50
+ return None
51
+ value, unit = float(m.group(1)), m.group(2).upper()
52
+ return value * (1_000_000 if unit == "M" else 1_000)
53
+
54
+
55
+ def parse_batch_schedule(pairs):
56
+ """TOKENS:BATCH pairs, as train_390M_dynamicb.py's --batch-schedule takes
57
+ them -> [(start_tokens, batch), ...] sorted by start."""
58
+ try:
59
+ return sorted((float(t), float(b)) for t, b in (s.split(":") for s in pairs))
60
+ except ValueError:
61
+ sys.exit(f"--batch-schedule takes TOKENS:BATCH pairs, got {' '.join(pairs)}")
62
+
63
+
64
+ def load_run(path, loss_col, batch_size_arg):
65
+ """-> (df with per-step `batch` and `tokens` columns, where the batch came from)."""
66
+ df = pd.read_csv(path)
67
+ if loss_col not in df.columns:
68
+ sys.exit(f"'{loss_col}' not in columns: {list(df.columns)}")
69
+
70
+ # A resumed run appends to its CSV from the checkpoint step, so steps the
71
+ # interrupted attempt logged past that checkpoint appear twice -- keep the
72
+ # resumed (later) rows.
73
+ df = df.drop_duplicates("step", keep="last").sort_values("step").reset_index(drop=True)
74
+
75
+ if "global_batch" in df.columns:
76
+ df["batch"] = df["global_batch"].astype(float)
77
+ source = "CSV global_batch column"
78
+ else:
79
+ b = batch_size_arg or parse_batch_size_from_filename(path)
80
+ if b is None:
81
+ sys.exit("no global_batch column and could not infer batch size "
82
+ "from filename -- pass --batch-size")
83
+ df["batch"] = float(b)
84
+ source = "--batch-size" if batch_size_arg else "filename"
85
+
86
+ if "tokens" in df.columns:
87
+ df["tokens"] = df["tokens"].astype(float)
88
+ else:
89
+ # Tokens consumed BEFORE each step (E = B*S for a fixed batch); a gap in
90
+ # the logged steps is filled with the batch of the row before it.
91
+ steps = df["step"].to_numpy(float)
92
+ batch = df["batch"].to_numpy()
93
+ tokens = np.empty(len(df))
94
+ tokens[0] = steps[0] * batch[0]
95
+ tokens[1:] = tokens[0] + np.cumsum(batch[:-1] * np.diff(steps))
96
+ df["tokens"] = tokens
97
+
98
+ df = df[df[loss_col].notna()].reset_index(drop=True)
99
+ return df, source
100
+
101
+
102
+ def batch_stages(df):
103
+ """Runs of rows with a constant batch -> [(first_row, last_row, batch), ...]."""
104
+ b = df["batch"].to_numpy()
105
+ starts = np.flatnonzero(np.r_[True, b[1:] != b[:-1]])
106
+ ends = np.r_[starts[1:] - 1, len(b) - 1]
107
+ return [(int(s), int(e), float(b[s])) for s, e in zip(starts, ends)]
108
+
109
+
110
+ def token_window_ema(loss, batch, token_window):
111
+ """EMA whose window is fixed in TOKENS: each step's weight uses the span
112
+ token_window / (that step's batch), so a batch switch mid-run doesn't change
113
+ how much data the smoothing averages over. For a fixed batch B this is
114
+ exactly pandas' .ewm(span=token_window / B, adjust=False)."""
115
+ span = token_window / batch
116
+ if (span < 1).any():
117
+ print(f"[WARN] EMA token window ({token_window:,.0f}) < batch size "
118
+ f"({batch.max():,.0f}) on some steps -- window is under one step "
119
+ "there; clamping span to 1 (effectively no smoothing).")
120
+ weight = 2.0 / (np.maximum(span, 1.0) + 1.0)
121
+ out = np.empty(len(loss))
122
+ ema = loss[0]
123
+ for i, (x, w) in enumerate(zip(loss, weight)):
124
+ ema = w * x + (1 - w) * ema
125
+ out[i] = ema
126
+ return out
127
+
128
+
129
+ def detect_warmup_end(df, stages):
130
+ # Searched within the first batch stage only: an arm that scales the LR
131
+ # with the batch (train_390_dynamic_sqrtlr.py) reaches its overall LR max
132
+ # only in its last stage, long after warmup.
133
+ first = df.iloc[: stages[0][1] + 1]
134
+ lr_max = first["lr"].max()
135
+ past_warmup = first["step"][first["lr"] >= 0.99 * lr_max]
136
+ return int(past_warmup.iloc[0]) if len(past_warmup) else 0
137
+
138
+
139
+ def interpolated_crossing(df, smoothed, target):
140
+ """-> (step, tokens) where the smoothed loss first reaches target, linearly
141
+ interpolated between the two logged rows around it; None if it never does."""
142
+ below = np.flatnonzero(smoothed <= target)
143
+ if not len(below):
144
+ return None
145
+ steps, tokens = df["step"].to_numpy(float), df["tokens"].to_numpy()
146
+ i = below[0]
147
+ if i == 0:
148
+ return steps[0], tokens[0]
149
+ l0, l1 = smoothed[i - 1], smoothed[i]
150
+ frac = 1.0 if l0 == l1 else (l0 - target) / (l0 - l1)
151
+ return (steps[i - 1] + frac * (steps[i] - steps[i - 1]),
152
+ tokens[i - 1] + frac * (tokens[i] - tokens[i - 1]))
153
+
154
+
155
+ def fit_and_solve(tokens, smoothed, target):
156
+ """-> (predicted tokens, (l0, a, alpha)), or (None, reason). A is in units
157
+ of TOKEN_UNIT tokens."""
158
+ x = tokens / TOKEN_UNIT
159
+ p0 = [smoothed.min() * 0.9, max(smoothed[0] - smoothed.min(), 1e-3), 0.3]
160
+ try:
161
+ (l0, a, alpha), _ = curve_fit(
162
+ power_law, x, smoothed, p0=p0,
163
+ bounds=([0, 1e-6, 1e-3], [smoothed.min(), np.inf, 10]),
164
+ maxfev=20000,
165
+ )
166
+ except RuntimeError as e:
167
+ return None, f"fit failed to converge: {e}"
168
+ if target <= l0:
169
+ return None, f"unreachable under this fit -- asymptote L0={l0:.4f} >= target {target}"
170
+ t_pred = (a / (target - l0)) ** (1.0 / alpha) * TOKEN_UNIT
171
+ return t_pred, (l0, a, alpha)
172
+
173
+
174
+ def tokens_to_step(t, df, schedule=None):
175
+ """Step at which `t` tokens have been consumed. Inside the logged range it
176
+ is interpolated from the log. Past it, steps are counted forward from the
177
+ last logged row at its batch -- or, when --batch-schedule is given, at each
178
+ later stage's batch once its start is reached (the trainer may round a
179
+ stage's batch to a micro-batch multiple, so steps there are approximate)."""
180
+ steps, tokens = df["step"].to_numpy(float), df["tokens"].to_numpy()
181
+ if t <= tokens[-1]:
182
+ return float(np.interp(t, tokens, steps))
183
+ step, tok, batch = steps[-1], tokens[-1], float(df["batch"].iloc[-1])
184
+ for start, next_batch in (schedule or []):
185
+ if start <= tok:
186
+ continue
187
+ n = np.ceil((start - tok) / batch) # steps still run at `batch`
188
+ if t <= tok + n * batch:
189
+ break
190
+ step, tok, batch = step + n, tok + n * batch, next_batch
191
+ return float(step + (t - tok) / batch)
192
+
193
+
194
+ def stability_sweep(df, smoothed, fit_start, target, sweep_step, min_window, schedule):
195
+ s_max_total = int(df["step"].max())
196
+ checkpoints = list(range(fit_start + min_window, s_max_total, sweep_step))
197
+ if not checkpoints or checkpoints[-1] != s_max_total:
198
+ checkpoints.append(s_max_total)
199
+
200
+ rows = []
201
+ for max_step in checkpoints:
202
+ mask = ((df["step"] >= fit_start) & (df["step"] <= max_step)).to_numpy()
203
+ if mask.sum() < 10:
204
+ continue
205
+ tokens = df["tokens"].to_numpy()[mask]
206
+ t_pred, info = fit_and_solve(tokens, smoothed[mask], target)
207
+ s_pred = tokens_to_step(t_pred, df, schedule) if t_pred is not None else None
208
+ rows.append((max_step, tokens[-1], t_pred, s_pred, info))
209
+ return rows
210
+
211
+
212
+ def print_sweep(target, rows, convergence_tol, warn_factor):
213
+ print(f"\ntarget={target} -- stability sweep")
214
+ print(f"{'max_step':>10} {'s_pred':>14} {'t_pred':>18} {'ratio':>8} {'L0':>8} {'alpha':>8} note")
215
+ for max_step, t_max, t_pred, s_pred, info in rows:
216
+ if t_pred is None:
217
+ print(f"{max_step:>10} {'--':>14} {'--':>18} {'--':>8} {'--':>8} {'--':>8} {info}")
218
+ continue
219
+ l0, a, alpha = info
220
+ ratio = t_pred / t_max
221
+ flag = " [beyond warn factor]" if ratio > warn_factor else ""
222
+ print(f"{max_step:>10} {s_pred:>14,.0f} {t_pred:>18,.0f} {ratio:>7.2f}x "
223
+ f"{l0:>8.4f} {alpha:>8.4f}{flag}")
224
+
225
+ # Judged on predicted tokens; for a fixed batch that is the same verdict as on steps.
226
+ valid = [t for _, _, t, _, _ in rows if t is not None]
227
+ if len(valid) < 3:
228
+ print("verdict: NOT ENOUGH DATA to judge convergence yet (need >=3 valid windows)")
229
+ return
230
+ t_prev2, t_prev1, t_last = valid[-3:]
231
+ rel_changes = [abs(t_prev1 - t_prev2) / t_prev2, abs(t_last - t_prev1) / t_prev1]
232
+ if all(c <= convergence_tol for c in rel_changes):
233
+ print(f"verdict: CONVERGED -- last two window-to-window changes "
234
+ f"({rel_changes[0]:.1%}, {rel_changes[1]:.1%}) both within tolerance {convergence_tol:.0%}")
235
+ else:
236
+ print(f"verdict: NOT YET CONVERGED -- last two window-to-window changes "
237
+ f"({rel_changes[0]:.1%}, {rel_changes[1]:.1%}) exceed tolerance {convergence_tol:.0%}; "
238
+ f"keep training and re-check")
239
+
240
+
241
+ def print_stages(df, stages):
242
+ print(f"{'stage':>7} {'steps':>17} {'tokens (before step)':>33} {'batch':>11}")
243
+ for k, (r0, r1, b) in enumerate(stages, 1):
244
+ s0, s1 = df["step"].iloc[r0], df["step"].iloc[r1]
245
+ t0, t1 = df["tokens"].iloc[r0], df["tokens"].iloc[r1]
246
+ print(f"{k:>7} {s0:>8} - {s1:<6} {t0:>15,.0f} - {t1:<15,.0f} {b:>11,.0f}")
247
+
248
+
249
+ def main():
250
+ p = argparse.ArgumentParser(description=__doc__,
251
+ formatter_class=argparse.RawDescriptionHelpFormatter)
252
+ p.add_argument("csv")
253
+ p.add_argument("--target", type=float, nargs="+", required=True)
254
+ p.add_argument("--loss-col", default="train_loss")
255
+ p.add_argument("--ema-token-window", type=float, default=EMA_TOKEN_WINDOW,
256
+ help=f"EMA smoothing window in TOKENS, not steps (default: "
257
+ f"{EMA_TOKEN_WINDOW:,.0f}) -- converted to a step-based "
258
+ "span using each step's own batch size, so different "
259
+ "batch sizes (across runs, or across the stages of a "
260
+ "dynamic run) get an equivalent amount of data smoothed, "
261
+ "not an equivalent step count")
262
+ p.add_argument("--batch-size", type=float, default=None,
263
+ help="tokens per step for a fixed-batch run; parsed from the "
264
+ "filename (e.g. _1Mbs) if omitted. Ignored when the CSV "
265
+ "has a global_batch column (dynamic-batch runs)")
266
+ p.add_argument("--batch-schedule", nargs="+", default=None, metavar="TOKENS:BATCH",
267
+ help="the run's batch schedule, same format as "
268
+ "train_390M_dynamicb.py --batch-schedule (e.g. "
269
+ "'0:589824 2e9:786432 8.3e9:819200'). Only used to "
270
+ "convert an extrapolated token count back to a step "
271
+ "when it lands in a stage the log hasn't reached yet; "
272
+ "without it, the last logged batch is assumed to continue")
273
+ p.add_argument("--fit-start-step", type=int, default=None,
274
+ help="default: auto-detected end of LR warmup")
275
+ p.add_argument("--fit-last-stage", action="store_true",
276
+ help="start the fit no earlier than the first step of the "
277
+ "last batch stage in the log, so the extrapolation "
278
+ "reflects the batch size the run is continuing at")
279
+ p.add_argument("--extrapolation-warn-factor", type=float,
280
+ default=EXTRAPOLATION_WARN_FACTOR)
281
+ p.add_argument("--stability-sweep", action="store_true",
282
+ help="instead of a single prediction, refit on growing windows "
283
+ "of data and report whether the extrapolated target has "
284
+ "converged yet")
285
+ p.add_argument("--sweep-step", type=int, default=SWEEP_STEP,
286
+ help="steps between successive sweep windows (default: 300)")
287
+ p.add_argument("--sweep-min-window", type=int, default=SWEEP_MIN_WINDOW,
288
+ help="post-warmup steps before the first sweep window (default: 500)")
289
+ p.add_argument("--convergence-tol", type=float, default=CONVERGENCE_TOL,
290
+ help="max relative change between successive windows to call "
291
+ "it converged (default: 0.05 = 5%%)")
292
+ args = p.parse_args()
293
+
294
+ df, batch_source = load_run(args.csv, args.loss_col, args.batch_size)
295
+ schedule = parse_batch_schedule(args.batch_schedule) if args.batch_schedule else None
296
+ stages = batch_stages(df)
297
+ dynamic = len(stages) > 1
298
+
299
+ fit_start = args.fit_start_step
300
+ if fit_start is None:
301
+ fit_start = detect_warmup_end(df, stages)
302
+ if args.fit_last_stage:
303
+ fit_start = max(fit_start, int(df["step"].iloc[stages[-1][0]]))
304
+
305
+ batch = df["batch"].to_numpy()
306
+ smoothed = token_window_ema(df[args.loss_col].to_numpy(float), batch,
307
+ args.ema_token_window)
308
+ post = (df["step"] >= fit_start).to_numpy()
309
+ if post.sum() < 10:
310
+ sys.exit(f"only {post.sum()} logged rows at or after fit start step {fit_start}")
311
+ tokens_post = df["tokens"].to_numpy()[post]
312
+ s_max_observed = df["step"].max()
313
+ t_max_observed = df["tokens"].max()
314
+
315
+ spans = args.ema_token_window / np.array([b for _, _, b in stages])
316
+ if dynamic:
317
+ batch_desc = (f"batch: DYNAMIC, {len(stages)} stages "
318
+ f"({stages[0][2]:,.0f} -> {stages[-1][2]:,.0f} tokens/step, "
319
+ f"from {batch_source})")
320
+ span_desc = f"~{spans.max():.1f}-{spans.min():.1f} steps across stages"
321
+ else:
322
+ batch_desc = f"batch size: {stages[0][2]:,.0f} tokens/step (from {batch_source})"
323
+ span_desc = f"~{spans[0]:.1f} steps at this batch size"
324
+ print(f"loss col: {args.loss_col} | ema token window: {args.ema_token_window:,.0f} "
325
+ f"({span_desc}) | {batch_desc} | fit start step: {fit_start}")
326
+ if dynamic:
327
+ print_stages(df, stages)
328
+ print("-" * 100)
329
+
330
+ for target in args.target:
331
+ cross = interpolated_crossing(df, smoothed, target)
332
+ if cross is not None:
333
+ s_cross, t_cross = cross
334
+ print(f"target={target:<8} REACHED (interpolated): "
335
+ f"step={s_cross:,.1f} tokens={t_cross:,.0f}")
336
+ continue
337
+
338
+ if args.stability_sweep:
339
+ rows = stability_sweep(df, smoothed, fit_start, target, args.sweep_step,
340
+ args.sweep_min_window, schedule)
341
+ print_sweep(target, rows, args.convergence_tol, args.extrapolation_warn_factor)
342
+ continue
343
+
344
+ t_pred, info = fit_and_solve(tokens_post, smoothed[post], target)
345
+ if t_pred is None:
346
+ print(f"target={target:<8} NOT REACHED, fit says: {info}")
347
+ continue
348
+
349
+ l0, a, alpha = info
350
+ s_pred = tokens_to_step(t_pred, df, schedule)
351
+ ratio = t_pred / t_max_observed
352
+ flag = " ** EXTRAPOLATION BEYOND WARN FACTOR **" if ratio > args.extrapolation_warn_factor else ""
353
+ step_note = ""
354
+ if dynamic and schedule is None:
355
+ step_note = f" [step assumes batch stays {stages[-1][2]:,.0f}; pass --batch-schedule]"
356
+ print(f"target={target:<8} EXTRAPOLATED (fit L0={l0:.4f} A={a:.4f} alpha={alpha:.4f}, "
357
+ f"T in {TOKEN_UNIT:.0e} tokens): step={s_pred:,.1f} tokens={t_pred:,.0f} "
358
+ f"({ratio:.2f}x past observed T={t_max_observed:,.0f}, "
359
+ f"S={s_max_observed:,.0f}){flag}{step_note}")
360
+
361
+
362
+ if __name__ == "__main__":
363
+ main()
train_390M.py ADDED
The diff for this file is too large to render. See raw diff