Weekly archive · Two-circuit GRPO

Weekly research report

Anchor signal reuse builds the collapsing bias, and controlling it removes the collapse without an optimizer-state swap

Verified by Shamane

Last week's optimizer-state swap told us where to dig, and this week we dug. A weight-level autopsy shows the collapse is a small directional drift rather than a magnitude blowup, which points at the anchor correction being reused between refreshes and accumulating a coherent bias in the optimizer state. Controlling that reuse turns out to be enough on its own. Applying the anchor signal once per refresh removes the collapse and matches the optimizer-state swap, with no optimizer surgery and no extra computation.

The week in short

  • What we explored. A weight-level autopsy of the collapsed run, and a family of designs that vary one thing only, how much of the anchor signal is consumed between its 20-step refreshes.
  • What we found. The collapse is a small, directional shift at about 0.1 percent of weight movement, driven by a correction held frozen between refreshes. Using the anchor signal once per refresh prevents the collapse with no optimizer surgery, and the residual form used once is the best result of the program.
  • What we think. Training under this mismatch tolerates large unbiased noise but not small persistent biases. The damage scales with how long an error persists, not with how large it is.
  • The default we adopted. Use the anchor signal once per refresh. Every reuse design paid for its extra early speed later, either as collapse or as slow drift.

Where last week left off

Last week we reported that swapping the trainer's optimizer state from the anchor kept a 500-step run alive where the plain compressed run collapsed. We promised to measure the weight-space distortion itself and to explain why the swap helps. This week does both, and the explanation led to a cheaper fix.

The protocol is unchanged. Qwen3-4B base model, RL fine-tuning with GRPO on MATH, 4096-token context, 500 steps, one optimizer update per step. Boundary activations on the training path are compressed to 5 percent of coordinates. The anchor circuit fires every 20 steps, replaying the same batch densely at weights delayed by 20 steps to produce one uncompressed gradient, which we call the anchor signal M. The refresh period and the delay are set by the slow network path and are not ours to tune. Validation is greedy decoding on held-out MATH, and the single-run noise floor is about plus or minus 0.8 points, so differences under about 2 points are not meaningful.

Weight-space archaeology

Before changing anything, we asked what the collapse physically is. We compared how the weights move between healthy checkpoints against how they move across the collapse window.

The key finding. The collapse is a small, directional, early-layer shift at about 0.1 percent of total weight movement. It is not a magnitude event. Nothing explodes, no tensor runs away, and the checkpoints on either side of the cliff are almost the same object by any norm you compute. What differs is the direction the weights drifted in, and that direction was supplied by the correction machinery itself.

The full checkpoint-level autopsy is ported into the hub as a specialist appendix, with the causal surgery that pins the damage down and every intermediate measurement. It keeps its internal terminology and is written for readers who want all the numbers: the weight archaeology report.

Why the optimizer-state swap prevents the collapse

In that first test the anchor circuit kept its own copy of the optimizer moments, built only from the uncompressed refresh gradients and updated once per refresh.

m_anc ← 0.8 * m_anc + 0.2 * G_anchor
v_anc ← 0.95 * v_anc + 0.05 * G_anchor2

From time to time we overwrite the trainer's live moments with the anchor's copy, rescaled to the magnitude the live optimizer is running at. So the anchor holds a moving average refreshed at every fire, not a snapshot taken once. It helps because a correction that stays the same between refreshes accumulates coherently in the optimizer moments as a directional bias, and the swap wipes that bias out before it grows into damage. The weight archaeology above confirms this is the right picture, since the damage it found is exactly a small coherent drift.

The swap works, but it repairs the bias after the fact rather than preventing it, the overwrite replaces everything the optimizer had learned and not only the unwanted part, and it is surgery on the most delicate state in the trainer. That motivated this week's question. Can we prevent the collapse by changing how the current design consumes the anchor signal, without touching the optimizer at all?

Where the bias comes from

The swap treats the bias after it has accumulated. That points at the more direct question of what puts it there, and the answer is in how we consume the anchor signal between refreshes.

M refreshes every 20 steps. In the design that collapsed we applied sign(M) on every one of the 19 steps in between, so those 19 updates all carried the same correction direction. That correction is exact on the refresh step and increasingly wrong as the policy moves away from the weights it was measured at. This is the bias the optimizer integrates, and it suggests a cheaper fix than repairing the optimizer afterwards. Apply the correction once, on the refresh step, and let the other 19 steps run on the compressed gradient alone.

The one variable we vary. Reused every step means the correction from the last refresh is applied on all 19 steps in between. Used once means it is applied only on the refresh step. Everything else in the run is identical, including the refresh period, the delay, the codec and the learning rate.

Two anchor signals, one pattern

If reuse is what causes the collapse, the way the signal is merged into the gradient should not matter much. We tested that with two different corrections built from the same anchor signal.

sign form      G_corr(t) = alpha * G_comp(t) + (1 - alpha) * |G_comp(t)| * sign(M),    alpha = 0.25

residual form  delta = M - G_comp(t_refresh),    G_corr(t) = G_comp(t) + delta

The sign form transplants the anchor's update signs onto the compressed gradient and sends only signs across the slow link. The residual form measures how wrong the compressed gradient was at the refresh step and adds that difference back, which on the refresh step returns the uncompressed gradient exactly. They are different operations on the same signal, and under reuse they behave the same way.

Both corrections collapse when the signal is reused, and both survive when it is used once

Greedy validation accuracy on held-out MATH, plotted every 100 steps. The left panel is the sign form, the right panel is the residual form, and the shaded bands mark where the reused designs collapsed.

Two panels of validation accuracy over 500 steps. In the sign panel the reused design peaks at step 100 and collapses shortly after, while the used-once and optimizer-swap designs finish 500 steps. In the residual panel the reused design leads early and collapses before step 400, while the used-once design finishes 500 steps.
Reused every step, both corrections peak early and then collapse, the sign form shortly after step 150 and the residual form before step 400. Used once per refresh, both finish all 500 steps.

The figure lines up with the hypothesis. Used once per refresh, the sign correction lands on par with the optimizer-state swap, so the cheap fix and the expensive one are equivalent. The residual correction used once is the best result of the program. Neither used-once run shows any sign of the drift that killed its reused twin, and neither needed the optimizer touched.

A third correction consumed the same reused signal as a bounded convex mixture and confirmed the pattern from another angle, degrading quietly rather than collapsing. Its training vitals raised no alarm at all, since response length stayed normal, clipping stayed near zero and the rollout log-perplexity actually fell, so only greedy validation caught it.

Controlling how much of the signal is used

Since the collapse does not depend on which correction we use, the question that matters is how to use the anchor signal between refreshes. Use once and reuse every step are the two ends of one dial, so we tried the middle.

The annealed design keeps applying the correction between refreshes, but decays its weight with the number of steps since the last refresh.

G_corr(t) = G_comp(t) + da(t) * delta,    a(t) = steps since the last refresh,    d = 0.75

We ran the annealed design with the residual correction, and by the argument above the choice of correction should not matter much. It is the first design that reuses on every step and still finishes all 500 steps. Its early speed sits between the two ends, and it pays for that afterwards with a slow drift in validation. We also ran a smaller dose, a single extra application ten steps after each refresh, and it finishes as well with the same shape. Decaying the dose stretches the timeline of the damage without removing it.

Middle doses of the reused signal all finish 500 steps

Greedy validation accuracy on held-out MATH for the two middle-dose designs, with the used-once design as the reference.

Validation accuracy over 500 steps for three designs. The annealed reuse leads at step 100 and drifts down after step 200, the single extra use tracks between the other two, and the used-once reference finishes highest.
Both middle doses finish all 500 steps. The annealed design is fastest early and drifts down after its peak, the single extra use shows the same shape more mildly, and by step 500 the used-once reference ends highest, with the spread inside the noise floor.

Reading this figure with the one above, the dose buys the first hundred steps and not the last four hundred. Every design that reused more of the signal arrived earlier and then either collapsed or drifted, and by step 500 all the finished designs sit within the noise floor of each other.

The next idea. Fixing the decay rate by hand is still a prescription. Let the model decide how much anchor signal it wants, by writing the correction with an explicit weight per step and learning that weight alongside the policy.
G_corr(t) = G_comp(t) + w(t) * C(M, t)

use once    w(t) = 1 on the refresh step and 0 in between
annealed    w(t) = da(t),  with d chosen by hand
learned     w(t) = sigmoid(sphi(a(t), layer, step))

Here C(M, t) is whichever correction is in use, so both designs we ran are special cases of one expression and the only change is that w becomes a small set of parameters trained with the policy. That lets the schedule differ by layer and change as training proceeds, instead of being one number we picked. The same weight applies to the sign form directly, and that is where we want it to work first, because sending signs is the lightest traffic the slow link has to carry.

Current observations

Training under this train-inference mismatch tolerates large unbiased noise and does not tolerate small persistent biases.

The compression inflates gradient variance heavily, and every design that limits reuse survives it, because independent errors average out across steps. A reused correction is different. Its direction stays the same for every step of the interval, so those errors are perfectly correlated and add up in the optimizer state instead of cancelling. The damage scales with how long an error persists rather than with how large it is.

Proven this week. Reuse of the anchor signal between refreshes causes the damage, and this holds regardless of the signal type. A sign transplant, an additive residual and a bounded convex mixture all failed under reuse, and every design that used the signal once per refresh survived. The merge arithmetic sets the shape of the failure and the reuse sets whether it happens.

Related evidence for the same model family appears in arXiv 2602.01826, where the mismatch grows with response length and annealing the update stabilizes training. Our learning rate is fixed by the protocol, so we annealed the correction weight instead.

The default we adopted. Use the anchor signal once per refresh. It removes the collapse, matches the optimizer-state swap, and needs no optimizer surgery, no extra communication and no new hyperparameter. The next step is to learn the weight w(t) rather than prescribe it, preferably with the sign form.
Caveats. No collapse has appeared in the controlled designs so far, which is evidence and not proof. These findings come from one model, one task and one scale, and they need to be checked on bigger models and harder tasks before we rely on them.
  • Use the anchor signal once per refresh as the default.
  • Learn the per-step weight instead of fixing the decay by hand, starting with the sign form.
  • Repeat the reuse ladder on bigger models and harder tasks.

Related reading

Communication-efficient RLVR weekly report · all curves from measured runs · no smoothing
Verified by Shamane