Skip to content
HN On Hacker News ↗

bf16 might be freezing your weight updates — Lacuna

▲ 19 points • 11 comments • by martincsweiss • 3d ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this text is a mix of AI and human-written content.

80 %

AI likelihood · overall

AI
20% human-written 80% AI-generated
SEGMENTS · HUMAN 6 of 50
SEGMENTS · AI 44 of 50
WORD COUNT 1,779
PEAK AI % 100% · §3
Analyzed
Nov 5
backend: pangram/v3.3
Segments scanned
50 windows
avg 36 words each
Distribution
20 / 80%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,779 words · 50 segments analyzed

Human AI-generated
§1 Human · 0%

If you fine-tune a model with weights stored in bf16 and let torch.optim.AdamW write to them directly, you get interesting behaviour. bf16 can only represent changes to weights if the change is at least ~1/512 of the weight itself, so at small learning rates only the smallest weights can update. As a result, you have a sparse fine-tuner that trains the small-magnitude weights and freezes the rest, with the cutoff set by your learning rate. This is a thing - I found half a dozen peer-reviewed papers (with thousands of citations) that fine-tune this way, but none of them reference this behaviour. I dug into one called Quiet-STaR, because (a) it's a cool paper, (b) they released their weights, and (c) their code works! So I diffed their base model and saw that indeed the small-magnitude weights were the only ones updated during their training. Then I reran its recipe with an optimizer that applies every update, and watched it diverge. A higher-precision optimizer needs a 3 to 10 times smaller learning rate to get the same result, and I couldn't achieve a better result. This started with Barron, Gnanapragasam & Tschannen's "A Fix for Softmax and Cross-Entropy" (Oct 2026).

§2 Human · 0%

They show that the standard way of computing cross-entropy rounds the correct answer's gradient to exactly zero once the model is confident enough (past a margin of about 5.5 in bf16, which they call the precision cliff), and that this, interacting with Adam, is what produces the "slingshot" loss spikes seen during grokking.

§3 AI · 100%

Fix the rounding and the spikes go away: something people had studied as training dynamics was rounding. That made me wonder what else out there is weird like this.

§4 AI · 100%

This is one, and it runs the other way: their rounding bug adds instability to training; this one hides it.

§5 Human · 0%

The code, logs and evaluation results are in mweiss17/quietstar-bf16-adamw.

§6 Human · 10%

1.

§7 AI · 100%

How bf16 affects optimization A floating-point number has two parts that matter here.

§8 AI · 100%

The exponent picks a power-of-two interval (1 to 2, 2 to 4, 4 to 8, and so on), and the mantissa picks one of a fixed number of evenly spaced slots inside it. Every interval gets the same number of slots, however wide it is. So the values sit close together near zero and spread out as numbers get bigger: the gap between neighbouring values doubles every time you cross a power of two. Figure 1 shows this with a toy format that has only 4 slots per interval.

§9 AI · 100%

bf16 and fp32 both have 8 exponent bits, so they cover the same range of numbers. The difference is the mantissa. fp32 has 23 mantissa bits, so 8,388,608 slots per interval.

§10 AI · 100%

bf16 keeps only the top 7, so 128.

§11 AI · 100%

A bf16 number is the first half of the fp32 number, rounded.

§12 AI · 100%

Its gaps are 65,536 times wider: about 1/128 to 1/256 of the number itself. Now add a small change to a weight, as an optimizer does on every step. The result has to be rounded to a value the format can hold. If the change is less than half a gap, it rounds back to where it started and the update is lost. Adam's step is roughly the learning rate, so at 1e-6 a typical Mistral weight loses its update while a weight eight times smaller keeps it. To see what this does to training, go back to the toy format from Figure 1 and run Adam on two weights, w₁ and w₂.

§13 AI · 100%

Every dot is a value the format can hold. Two copies of Adam start from the same point and head for the same minimum. One (orange) keeps its weights in the toy format and rounds after every step. The other (blue) keeps a full-precision copy.

§14 AI · 100%

bf16 is the same picture with 128 values per interval instead of 4, so a weight loses any change smaller than roughly 1/512 of its own size.

§15 AI · 100%

2.

§16 AI · 100%

How bf16 messes with AdamW specifically When parameters are bf16 and there's no fp32 master copy underneath, AdamW has three problems: Weight decay is a no-op.

§17 AI · 100%

Decay multiplies each weight by (1 − lr·wd), a relative change of about 1e-9 at these settings. It rounds away on every weight, every step.

§18 AI · 100%

Adam's second moment freezes. The moments are stored in the parameter dtype.

§19 AI · 100%

At β₂ = 0.999 the 0.1% decay rounds away, so v can never shrink, and once it has grown to about half its true value its increments round away too. This kicks in a few hundred steps in, so it doesn't affect the 100-step run below, but it affects every normal fine-tune.

§20 AI · 100%

Updates smaller than half a gap are dropped.

§21 AI · 100%

This is the one this article is about. To be clear, these are known issues. Collage (ICML 2024) worked; the Hugging Face post "Dude, Where's My Update?"

§22 AI · 100%

(Sep 2026) and a robotics paper from two weeks ago (12 VLA codebases) show it.

§23 AI · 100%

What we're exploring is what it does to the training process.

§24 AI · 100%

3.

§25 AI · 100%

bf16 is acting as a weight mask The survival rule is |Δ| ≥ |w|/512 (roughly). This means a weight can move only if |w| ≲ 512·|Δ|. Adam's step is at most about the learning rate. So at learning rate η, only weights smaller than about 512·η can change, and we can find them statically in pretrained models.

§26 Human · 15%

We take a look at Mistral-7B below.

§27 AI · 99%

bf16's gaps only change size at powers of two, so the cutoff is 512·η rounded down to a power of two.

§28 AI · 100%

At η = 1e-6 that is 2⁻¹¹ ≈ 4.88e-4, not 5.12e-4. Every weight between 4.88e-4 and 9.77e-4 has a half-gap of about 1.9e-6, bigger than the step, so all of them are frozen. RMSNorm gains (around 2.2) never move at any learning rate up to 1e-4.

§29 AI · 100%

And it really behaves like a fixed mask during training.

§30 AI · 100%

In our rerun of the Quiet-STaR recipe (section 5), about 1 to 3% of weights change on any given step, but the cumulative set of weights that have ever changed saturates at 5.6%.

§31 AI · 100%

The same small set gets updated repeatedly.

§32 AI · 100%

4.

§33 AI · 100%

You can see it in published models Quiet-STaR (Zelikman et al., COLM 2024) teaches a language model to generate internal "thoughts" before predicting each token.

§34 AI · 100%

The paper fine-tunes Mistral-7B at learning rate 1e-6, and its released training code does exactly the thing above: bf16 weights, fused torch AdamW, no master copy. The trained checkpoint is on the Hub, so we diffed ezelikman/quietstar-8-ahead against the Mistral-7B-v0.1 it started from.

§35 AI · 99%

5.4% of the 7.24B weights changed. Above 5.12e-4, only 110 of about 6.2 billion weights changed, in any tensor, in any layer.

§36 AI · 100%

5.12e-4 is 512 × 1e-6, which is where the rough rule puts the cliff. The exact rule (512 × lr rounded down to a power of two) puts it slightly earlier, at 2⁻¹¹ ≈ 4.88e-4, and that is where the drop is: in the narrow bin just past 2⁻¹¹ (up to 5.10e-4) only 0.39% of weights changed, most likely ones sitting exactly on 2⁻¹¹ that stepped down across it. With an fp32 master copy there is no cliff: small moves accumulate, so the fraction that shows up as changed after casting back to bf16 falls off gradually with weight size instead (Figure 7).

§37 AI · 100%

Below the cliff, about 40% of weights changed, and they moved a lot: half moved by more than 16 bf16 gaps, with a mean relative change of 36%.

§38 Human · 0%

tensor class share below the cliff changed, below cliff changed, above cliff MLP 13.6% 40.4% 0.000% attention 14.9% 36.7% 0.000% embeddings 15.4% 17.5% 0.000% lm_head 10.0% 48.0% 0.000% RMSNorm 1.0% 87.0% 0.000% So the published model is Mistral with its smallest 5% of weights tuned hard and the other 95% untouched.

§39 AI · 100%

The new parts tell the same story.

§40 AI · 100%

The 3-layer mixing head is initialised N(0, 0.02) with a zero output row. Its two hidden layers are still that random matrix, the output row trained to about 3e-6, and its biases sit around 1e-9. The one component that clearly trained is the pair of thought-token embeddings, and that's because the code stores them divided by 100 and multiplies back in the forward pass, a 100× effective learning rate that lands them on the right side of the cliff.

§41 AI · 100%

5.

§42 AI · 100%

Turns out it's a feature, not a bug We reran the released recipe exactly as shipped (bf16 Mistral-7B, fused AdamW, lr 1e-6, seed 42, the script's own 100 optimizer steps, one 8×H200 node) and alongside it two corrected versions: Shadow: an fp32 copy of the weights that receives the bf16 run's exact clipped gradients through a correct fp32 AdamW. Same gradients, so it isolates what the rounding threw away. fp32 master: the whole recipe rerun with fp32 weights, bf16 autocast for the math, and a correct fused AdamW. Same seed, data and everything else. During the bf16 run, between 97% and 99.7% of each step's proposed updates rounded to zero, and by step 100 it had changed 5.6% of its weights. Evaluated with the paper's own metric and code. The first two columns are a soft score: the probability the model puts on the correct answer tokens, renormalised over the valid answers. The third is perplexity on held-out OpenWebMath with thoughts switched off. GSM8K CommonsenseQA perplexity Mistral-7B base 4.96 35.72 6.04 released bf16 recipe, step 100 7.75 44.25 8.37 shadow: same gradients, correct AdamW 0.26 0.00 1,800,000 fp32 master, same recipe 1.59 7.94 13.6 The bf16 run does what the paper says: both scores go up.

§43 AI · 100%

Both corrected versions break. The shadow ends up answering every question with a newline, even though none of its weights is more than one bf16 gap from the base model. A correct optimizer just moved all 7 billion of them a little.

§44 AI · 100%

The fp32 run blows up (perplexity 5 × 10¹¹ at step 50, newlines everywhere) and only partly recovers by step 100. So the published recipe is only stable because of the bug.

§45 AI · 100%

Learning rate 1e-6 is a setting for an optimizer that discards 99% of its updates.

§46 AI · 100%

6. What learning rate did they actually have?

§47 AI · 100%

If bf16 at 1e-6 is secretly a correct run at a smaller step, some lower fp32 learning rate might reproduce it.

§48 AI · 100%

We did a sweep and found that at 3e-7 a correct optimizer matches the bf16 run's final scores by step 50, within 0.2 points on both tasks, then goes over a cliff (pick "Step 50" in Figure 10 to see it).

§49 AI · 100%

At step 100, no correct-optimizer run beat bf16: the closest, at 1e-7, scored 41.99 on CommonsenseQA against bf16's 44.25.

§50 AI · 100%

And bf16 got there by moving a completely different set of weights: 5.6% of them, by a lot, rather than all of them by a little.