Sam Babichenko

Reading notes / NanoGPT speedrun

K-Maxwell momentum

Zheng, 2026

Momentum
gtg_tgradient of a hidden matrix on step t
β;\beta; μ\mudecay rate of a single EMA (0.95); Nesterov mixing coefficient (0.95)
mt;m_t; utu_tsingle-EMA momentum; update before orthogonalization, (1−μ)gt+μmt(1-\mu)g_t+\mu m_t
K;K; kknumber of EMA buffers; buffer index, 1 to K, shortest memory first
mk,t;m_{k,t}; βk;\beta_k; τk\tau_kbuffer k; its decay rate; its mean age βk/(1−βk)\beta_k/(1-\beta_k) in steps
wk(t);w_k(t); mˉt\bar m_tweight of buffer k at step t; mixed momentum ∑kwk(t)mk,t\sum_kw_k(t)m_{k,t}
ht(a)h_t(a)weight of the gradient from a steps ago in mˉt\bar m_t
Benchmark
n;n; Lˉ\bar Lnumber of runs; their mean validation loss in nats
Constants
3.28;3.28; 0.0040.004Track 3's target loss; the margin in (3.28−Lˉ)n≥0.004(3.28-\bar L)\sqrt n\ge0.004
3,3, 6464shortest and longest buffer mean ages on Muon, in steps
58→2658\to26mean age of the Muon mix at step 1000 and at step 3250
1000;1000; 22502250switch step on Muon; number of annealing steps after it

Context. In Muon, as in Adam, the momentum is a single a running average updated as mt=βmt−1+(1−β)gtm_t=\beta m_{t-1}+(1-\beta)g_t, so the gradient from aa steps ago carries weight (1−β)βa(1-\beta)\beta^a; β\beta is the decay rate of past gradients with one fixed decay rate, 0.95 in the speedrun's Muon. Jack Neo Zheng's post K-Maxwell Momentum, with Jeffrey Cheng as collaborator, asks what a more flexible momentum is worth when everything else is already tuned. The test bed is the optimization track of the modded-nanogpt speedrun: model, data and batch size are fixed, one forward-backward pass per step, and an entry is scored by the number of optimizer steps it needs to reach a validation loss of 3.28 of the NanoGPT speedrun. Mixing several EMAs is older (AggMo, Lucas et al., 2019, averages momentum buffers with different decay rates); the post builds on two recent two-EMA mixes, Pagliardini et al. (2025): Adam with a second, slow EMA of gradients added to the fast one; the slow buffer's weight and decay rate are ramped up over training to avoid early instability and Hu et al. (2026): Muon whose momentum is a fixed mix of two EMAs, 0.43850.4385 of one with decay 0.85 plus 0.56150.5615 of one with decay 0.98, from a model of momentum as a stress that relaxes on a fast and a slow timescale (a Maxwell element relaxes exponentially). A momentum is a weighting of past gradients by their age; the post's results suggest that the shape of that weighting matters beyond its average age, and the shape that helped here keeps a long tail of old gradients and shortens as training ends.

K-Maxwell keeps eight EMA buffers whose memories are fixed and evenly spaced on a log scale from 3 to 64 steps, and changes only how they are mixed: from step 1000 to the end of training it moves the mixture weights linearly from a mix whose ∑aa h(a)\sum_a a\,h(a), the average age in steps of the gradients in the momentum, where h(a)h(a) is the weight the momentum puts on the gradient from aa steps ago; 19 steps for a single EMA with decay 0.95 is 58 steps to one whose mean age is 26. On Track 3's tuned Muon baseline this reached the target in 3160 steps instead of 3250 (2.8% fewer), and on Muon with a hyperball constraint: each hidden weight matrix is kept at a fixed norm after every step in 3065 instead of 3125 (1.9%); on the the stack that holds Track 3's overall record (2690 steps): Muon whose momentum is first preconditioned with SOAP's row and column gradient statistics, plus several other changes stack it did not help. The schedule carries the gain: the same eight buffers with fixed weights did not beat the baseline in any configuration tried (Step 4). Why it works is open; the post's explanations, about gradient noise, curvature and the the regime, observed by Cohen et al. (2021) in full-batch training, in which the largest Hessian eigenvalue rises until it hovers near 2/η2/\eta, the stability limit of gradient descent at learning rate η\eta, while the loss keeps falling, are not tested (Step 6).

Setting. Track 3 trains GPT-2 small (124M parameters) on FineWeb with 524,288 tokens per step; only the optimizer, its hyperparameters and the initialization may change. An entry passes at the first checked step where the mean validation loss the validation cross-entropy in nats, averaged over the nn independent runs of the entry of the number of independent runs (seeds) submitted, all reported runs satisfies (3.28−Lˉ)n≥0.004(3.28-\bar L)\sqrt n\ge0.004, a one-sided z-test at p<0.001p\lt0.001 that assumes a run-to-run standard deviation of 0.0013 nats. For each hidden matrix, write the gradient of the training loss with respect to that matrix on step tt's batch for its gradient. The baseline Muon keeps one EMA mt=βmt−1+(1−β)gtm_t=\beta m_{t-1}+(1-\beta)g_t with β=0.95\beta=0.95, mixes it with the new gradient in the look-ahead form of momentum: the step uses ut=(1−μ)gt+μmtu_t=(1-\mu)g_t+\mu m_t, the new gradient plus the updated buffer, in place of mtm_t alone form ut=(1−μ)gt+μmtu_t=(1-\mu)g_t+\mu m_t with μ=0.95\mu=0.95, and orthogonalizes utu_t with Newton-Schulz steps before taking the step. K-Maxwell replaces mtm_t only; the Nesterov mix, the orthogonalization, the learning rates and their schedule stay those of the baseline (Track 3 result #36).

Consequences and limits. For someone training with Muon, the change is a few lines in the momentum plus two tuned weight vectors, for a few percent fewer steps on this benchmark and seven extra buffers per matrix, a memory cost the post calls prohibitive at very large scale. The endpoint mixes were tuned jointly with the Nesterov coefficient for one model, one data set and one training length. The anneal also spans the same steps as the learning-rate decay, linear to zero from step 975 in this trainer, and the post does not separate the two (my reading of the trainer), so a different schedule likely needs new endpoints.

Step 1

A mixture of EMAs is a weighting of past gradients by age, and its mean age is linear in the weights. Let there be the number of EMA buffers: 8 on Muon, 6 on MuonH and SOAP-Muon buffers mk,t=βkmk,t−1+(1−βk)gtm_{k,t}=\beta_km_{k,t-1}+(1-\beta_k)g_t with decay rates the fixed decay rate of buffer kk, set from its mean age by βk=τk/(τk+1)\beta_k=\tau_k/(\tau_k+1), mixed with weights the weight of buffer kk at step tt; the weights are nonnegative and sum to 1 into mˉt=∑kwk(t)mk,t\bar m_t=\sum_kw_k(t)m_{k,t}. Unrolling each EMA (ignoring how the buffers were started) gives the weight the memory kernel: the weight the mixed momentum mˉt\bar m_t puts on the gradient from aa steps before step tt on each past gradient: mˉt=∑a≥0ht(a) gt−a,ht(a)=∑k=1Kwk(t) (1−βk) βk a.\begin{aligned}\bar m_t&=\sum_{a\ge0}h_t(a)\,g_{t-a},\\h_t(a)&=\sum_{k=1}^{K}w_k(t)\,(1-\beta_k)\,\beta_k^{\,a}.\end{aligned} Write τk\tau_k=βk/(1−βk){}=\beta_k/(1-\beta_k) for buffer kk's mean age; the mixture's mean age is then ∑kwk(t)τk\sum_kw_k(t)\tau_k, linear in the weights, so interpolating the weights moves it linearly, from 58 steps to 26. The mean age does not fix the shape. In the post's example a single EMA with β=0.95\beta=0.95 and an equal mix of EMAs with mean ages 3 and 35 both have mean age 19, but the newest gradient gets 5% of the weight in the first and 13.9% in the second. The tuned mixes differ the same way (my computation from the published weights): the final mix has the single EMA's median age, 13 steps, yet puts 0.11 of its weight on gradients older than 64 steps against 0.04; the starting mix puts 0.33 there.

Step 2
The recipe fixes eight log-spaced memories and interpolates between two tuned mixes. The mean ages are τk=3 (64/3)(k−1)/7\tau_k=3\,(64/3)^{(k-1)/7} for k=1,…,8k=1,\dots,8, from 3 to 64 steps. The starting mix puts 0.857 on the 64-step buffer and spreads the rest over the other seven in proportion to kk (mean age 58); the final mix keeps only 0.097 there (mean age 26). Training runs the baseline's single EMA until step 1000, when every buffer is set to its value, so the switch step's update is the baseline's; the weights then move linearly to the final mix at step 3250. The post starts late to skip the early instability that AdEMAMix reports from a long memory. Per Muon matrix, after step 1000:
# m: the 8 buffers; betas: their decay rates
# w_start, w_end: the two tuned mixes; g: this step's gradient
for k in range(8):
    m[k] = betas[k] * m[k] + (1 - betas[k]) * g
frac = (step - 1000) / 2250            # 0 at step 1000, 1 at step 3250
w = [(1 - frac) * w_start[k] + frac * w_end[k] for k in range(8)]
m_mix = sum(w[k] * m[k] for k in range(8))
u = (1 - mu) * g + mu * m_mix           # Nesterov mix, mu = 0.95
update = newton_schulz5(u)             # Muon's orthogonalization
Step 3

On tuned Muon, K-Maxwell passes 90 steps before the single EMA and 50 before Bi-Maxwell. Eight seeds were evaluated every 10 steps from 2900 to 3250. At step 3150 the statistic is (3.28−3.27873)8=0.0036(3.28-3.27873)\sqrt8=0.0036, short of 0.004; at 3160 the mean is 3.27794 and the statistic 0.0058. The seeds' own first crossings of 3.28 average 3141 steps; the reported step is later because the mean must sit 0.004/8≈0.00140.004/\sqrt8\approx0.0014 nats below the target. Against the single-EMA baseline's ten runs the gap is significant at equal steps by the track's two-sample form of the rule, (3.27866−3.27328)/1/8+1/10=0.0113≥0.004(3.27866-3.27328)/\sqrt{1/8+1/10}=0.0113\ge0.004 at step 3250. K-Maxwell's mean loss is also below Bi-Maxwell's at every checked step from 2900 to 3250, by 0.0072 nats at 2900 and 0.0034 at 3250.

Step 4

The annealing carries the gain, transfers to MuonH, and fails on the SOAP-Muon stack. Per the experiment report, fixed-weight eight-buffer mixes did not beat the baseline in any configuration tested, even after extensive tuning; the first annealed schedules won clearly. On MuonH the recipe, lightly retuned (six buffers, switch at step 750, mean age 50 to 22), passes at 3065 against 3125. The SOAP-Muon record stack also evaluates a an exponential moving average of the weights over the last few hundred steps, evaluated in place of the final weights of the weights. On it a fixed six-buffer mix with mean age 35 passes at 2680 against 2690, not significant at equal steps (two-sample statistic 0.0010). Bi-Maxwell on that stack passes at 2635 and, per the authors' companion report, beats their mix in a paired comparison; annealing made the loss worse. The post attributes the failure to overlap between momentum and SOAP's preconditioning, the companion report to Tail-EMA, which already averages the end of training; neither is tested.

Step 5

At larger batches K-Maxwell keeps its gain and Bi-Maxwell loses it. From three seeds' step-2000 states, Muon with momentum off (μ=0\mu=0), single-EMA Muon, Bi-Maxwell and K-Maxwell were each run to step 2750 at 1, 2, 4, 8 and 16 times the standard batch. Against momentum off at the same seed and batch, K-Maxwell lowered the validation loss by 0.0047 to 0.0067 nats at every batch size, while Bi-Maxwell's gain fell from 0.0105 nats at the standard batch to 0.0020 at 8 times and −0.0006-0.0006 at 16 times, with seed-to-seed standard deviations of about 0.0002 or less. A larger batch already averages noise within the step, so a gain that comes only from averaging noise across steps should fade as the batch grows; the post reads K-Maxwell's persistent gain as doing more than that. The post's text leaves out two features of the table, both stated in the authors' linked ablation log. At the standard batch Bi-Maxwell's gain is twice K-Maxwell's, the reverse of the full runs in Step 3 (my comparison), so these short continuations do not reproduce the full-run ordering. And single-EMA Muon is within 0.0004 nats of momentum off at every batch size: over these steps ordinary momentum gives no measurable gain, and what the mixtures add is something a 19-step EMA does not provide.

Step 6

The post's explanations are untested. They are that long memory keeps persistent gradient directions early and short memory reacts faster to oscillations late, near the edge of stability; that mixing very different timescales cancels recurring errors, loosely like combining estimates computed at different step sizes so that their leading error terms cancel; and that at large batches K-Maxwell lets training reach sharper regions. None of the experiments measures curvature or oscillation. The direction of the schedule is also open: the Bi-Maxwell paper's model predicts that the best memory grows over training, because gradient directions change more slowly later, and AdEMAMix lengthens its memory, as the post notes; K-Maxwell's memory jumps from the single EMA's 19 steps to 58 at step 1000 and then shortens.

Terms

K-Maxwell
KK EMA buffers with fixed log-spaced mean ages (3 to 64 steps), mixed by weights interpolated linearly from a long-memory mix to a shorter one
Memory kernel
ht(a)=∑kwk(t)(1−βk)βkah_t(a)=\sum_kw_k(t)(1-\beta_k)\beta_k^a, the weight of the gradient from aa steps ago in the momentum
Mean gradient age
∑aa ht(a)=∑kwk(t)τk\sum_aa\,h_t(a)=\sum_kw_k(t)\tau_k, linear in the weights; it does not fix the kernel's shape
Annealed mix
mean age 58 at step 1000 to 26 at step 3250 on Muon; fixed weights never beat the baseline
Switch at step 1000
all buffers start from the single EMA's value, so the switch step's update equals the baseline's
Track 3 pass rule
(3.28−Lˉ)n≥0.004(3.28-\bar L)\sqrt n\ge0.004: a one-sided z-test at p<0.001p\lt0.001 assuming run-to-run standard deviation 0.0013 nats
Bi-Maxwell
fixed mix of two EMAs (decays 0.85 and 0.98, mean age 30); its gain fades at large batch
Edge of stability
largest Hessian eigenvalue hovering near 2/η2/\eta, the stability limit of gradient descent at learning rate η\eta
Results
Muon 3250 to 3160 steps; MuonH 3125 to 3065; SOAP-Muon no significant gain

Quiz

  1. A single EMA with decay 0.95 and an equal mix of EMAs with mean ages 3 and 35 both have mean age 19 steps. What separates them?
  2. On the SOAP-Muon record stack, K-Maxwell passed at 2680 steps against 2690. What should a practitioner conclude?
  3. In the batch-size continuations, single-EMA Muon with momentum 0.95 is within 0.0004 nats of momentum off at every batch size. What does that imply for reading the mixtures' gains?
  4. You keep Muon and the model but switch to a different learning-rate schedule. What does the note suggest about reusing K-Maxwell's endpoints?

Sources