AI / ML Theory
Chapter 3 · 16 min

3.6 — AdamW: decoupled weight decay

Goal: See why Adam-with-L2 is not the same as Adam-with-weight-decay, and derive the AdamW update used in every modern LLM.

1. Why this session exists

If you read Adam optimiser configs in real LLM code (GPT-NeoX, LLaMA, Mistral, nanoGPT), you almost never see Adam(weight_decay=...). You see AdamW(weight_decay=...). The difference is one line of the update, and it matters enough that every serious modern training run uses the W variant.

This session pins down why. The short version: in plain SGD, "add an L2 penalty to the loss" and "shrink weights toward zero each step" are the same thing. In Adam they are not, because of the per-parameter scaling 1/v^t1/\sqrt{\hat v_t}. Loshchilov & Hutter (2017, Decoupled Weight Decay Regularization) noticed this and fixed it by re-coupling the shrinkage in the parameter update rather than in the gradient.[2, 3, 4]


2. L2 vs weight decay in plain SGD

Start with plain SGD on a loss with L2 penalty:

L~(θ)=L(θ)+λ2∥θ∥2,∇L~=g+λθ,\tilde L(\theta) = L(\theta) + \frac{\lambda}{2} \lVert \theta \rVert^2, \qquad \nabla \tilde L = g + \lambda \theta,

with g=∇Lg = \nabla L. The SGD step is

θ←θ−α(g+λθ)=(1−αλ) θ−α g.\theta \leftarrow \theta - \alpha (g + \lambda \theta) = (1 - \alpha \lambda)\, \theta - \alpha\, g.

Read it: take the usual SGD step, then multiply θ\theta by the shrink factor (1−αλ)(1 - \alpha \lambda). This is what people call weight decay: an explicit multiplicative shrinkage of the parameters at every step.

So in plain SGD, "L2 penalty on the loss" and "decay the weights by (1−αλ)(1 - \alpha \lambda) at each step" are algebraically identical. The two names refer to the same update.


3. The non-equivalence in Adam

Now do the same with Adam. Add an L2 penalty to the loss, so the gradient becomes g~=g+λθ\tilde g = g + \lambda \theta. Adam's EMAs run on g~\tilde g, not on gg:

mt=β1mt−1+(1−β1)(gt+λθt),vt=β2vt−1+(1−β2)(gt+λθt)⊙2.m_t = \beta_1 m_{t-1} + (1 - \beta_1)(g_t + \lambda \theta_t), \qquad v_t = \beta_2 v_{t-1} + (1 - \beta_2)(g_t + \lambda \theta_t)^{\odot 2}.

The update is then

θt+1=θt−α⋅m^tv^t+ε.\theta_{t+1} = \theta_t - \alpha \cdot \frac{\hat m_t}{\sqrt{\hat v_t} + \varepsilon}.

The shrinkage of θ\theta is not a clean (1−αλ) θ(1 - \alpha \lambda)\,\theta anymore. The L2 term gets folded into the EMAs and rescaled by 1/v^t1/\sqrt{\hat v_t}. Concretely: parameters with large gradients (large vtv_t) get shrunk less, because their effective LR for the L2 term is reduced by 1/v^t1/\sqrt{\hat v_t}.

That is the wrong direction. The whole point of weight decay is to keep weights small; "shrink less the parameters that already have big gradients" defeats the regulariser.


4. The AdamW update

Loshchilov & Hutter's fix: decouple the weight decay from the gradient pipeline. Compute Adam's adaptive step on the unmodified gradient gg, then apply weight decay as a separate multiplicative shrinkage of θ\theta:

 mt=β1mt−1+(1−β1)gt,vt=β2vt−1+(1−β2)gt⊙2,m^t=mt/(1−β1t),v^t=vt/(1−β2t),θt+1=(1−αλ) θt  −  α m^tv^t+ε. \boxed{\ \begin{aligned} m_t &= \beta_1 m_{t-1} + (1 - \beta_1) g_t, \\ v_t &= \beta_2 v_{t-1} + (1 - \beta_2) g_t^{\odot 2}, \\ \hat m_t &= m_t / (1 - \beta_1^t), \qquad \hat v_t = v_t / (1 - \beta_2^t), \\ \theta_{t+1} &= (1 - \alpha \lambda)\, \theta_t \;-\; \alpha\, \frac{\hat m_t}{\sqrt{\hat v_t} + \varepsilon}. \end{aligned} \ }

The only line that changed: the parameter update now multiplies θt\theta_t by (1−αλ)(1 - \alpha \lambda) before subtracting the Adam step. The EMAs no longer see the L2 gradient.

The shrinkage rate is uniform across all parameters: every coordinate gets multiplied by exactly (1−αλ)(1 - \alpha \lambda) each step, independent of its gradient history. This is the L2-on-SGD behaviour, restored.


5. The two updates side by side

Adam-with-L2 (the "wrong" thing):

θt+1=θt−α⋅m^t(g~)v^t(g~)+ε,g~=g+λθ.\theta_{t+1} = \theta_t - \alpha \cdot \frac{\hat m_t(\tilde g) }{\sqrt{\hat v_t(\tilde g)} + \varepsilon}, \quad \tilde g = g + \lambda \theta.

AdamW (the right thing):

θt+1=θt−α⋅m^t(g)v^t(g)+ε−α λ θt.\theta_{t+1} = \theta_t - \alpha \cdot \frac{\hat m_t(g)}{\sqrt{\hat v_t(g)} + \varepsilon} - \alpha\, \lambda\, \theta_t.

The decay term in AdamW is not rescaled by v^t\sqrt{\hat v_t}. That single difference is responsible for AdamW's robustly better generalisation across vision, NLP, and LLMs.

A useful mental model: in AdamW, the shrinkage acts on the parameter; in Adam-with-L2, the shrinkage acts on the gradient. The first is what we mean by "decay". The second only happens to coincide with decay in the plain-SGD case where there is no adaptive scaling.


6. Practical defaults for LLMs

Modern LLM training (GPT-3, LLaMA, Mistral, Qwen) ships AdamW with:

  • α=6×10−4\alpha = 6 \times 10^{-4} peak (smaller for bigger models; see scaling laws in Ch.7).
  • β1=0.9\beta_1 = 0.9, β2=0.95\beta_2 = 0.95 (note: smaller than Adam's 0.9990.999; LM gradients are noisier and we want vtv_t to track them).
  • ε=10−8\varepsilon = 10^{-8}.
  • λ=0.1\lambda = 0.1 (weight decay).
  • Cosine LR schedule with linear warmup over first 1% of steps (next session).

Notably, weight decay is usually applied only to weight matrices, not to biases, layer-norm parameters, or embedding bias. Why? Layer-norm γ,β\gamma, \beta are scale parameters of the activations — shrinking them toward zero would deliberately break the unit-variance property LN tries to preserve. Biases see no L2 penalty in Bayesian-prior derivations either (see Appendix 3.A for the L2-as-Gaussian-prior derivation). Most implementations expose a param_groups mechanism so you can set weight_decay=0 on those parameters.

no_decay = ["bias", "LayerNorm.weight", "ln.weight"]
optimizer_groups = [
    {"params": [p for n, p in model.named_parameters()
                if not any(nd in n for nd in no_decay)],
     "weight_decay": 0.1},
    {"params": [p for n, p in model.named_parameters()
                if any(nd in n for nd in no_decay)],
     "weight_decay": 0.0},
]
optimizer = torch.optim.AdamW(optimizer_groups, lr=6e-4, betas=(0.9, 0.95))

This snippet (or a near-clone of it) lives in every modern LLM training script.


7. When to use Adam vs AdamW

  • AdamW is the modern default. Always start here for LLMs, large vision models, and anything with substantial regularisation.
  • Plain Adam (no decay) is fine when you don't want any weight decay — e.g., RL policy optimisation, where shrinking the policy weights toward zero has no statistical motivation.
  • Adam with L2 (as in old PyTorch's weight_decay) is a historical accident. Don't use it. If you see a paper or codebase that does, treat its weight-decay coefficients as uncalibrated.

8. Takeaways

  • In plain SGD, L2 penalty on the loss = multiplicative weight decay each step. Identically.
  • In Adam, the two are not equivalent: L2 gets rescaled by 1/v^t1/\sqrt{\hat v_t}, which weakens it for parameters with large gradients.
  • AdamW decouples them: apply (1−αλ)(1 - \alpha \lambda) shrinkage to θ\theta separately from the Adam step on the unmodified gradient gg.
  • This is the optimiser used by every modern LLM. Typical λ=0.1\lambda = 0.1.
  • Exclude biases and LayerNorm parameters from weight decay.

Self-test

(a) Verify the identity θ←θ−α(g+λθ)=(1−αλ)θ−αg\theta \leftarrow \theta - \alpha(g + \lambda \theta) = (1 - \alpha \lambda) \theta - \alpha g. Why does this same identity fail to hold once gg is replaced by an adaptive step m^/(v^+ε)\hat m / (\sqrt{\hat v} + \varepsilon)?

(b) A parameter θi\theta_i has a consistently large gradient: v^t(i)=10\sqrt{\hat v_t^{(i)}} = 10. Compare the effective shrinkage rate on θi\theta_i under Adam-with-L2 (λ=0.1\lambda = 0.1) versus AdamW (λ=0.1\lambda = 0.1). Numerically, by how much does the L2 version under-shrink relative to AdamW for this coordinate?

(c) Argue (informally) why you'd want to exclude bias parameters from weight decay. Hint: think about what the bias parameter "means" in a typical layer and what the L2-as-Gaussian-prior interpretation (3.8) would say about its prior.

(d) Modify the Adam pseudocode from 3.5 §7 to become AdamW. How many lines change? Show the diff.


References

  1. [1]

    Loshchilov, I., & Hutter, F. (2019). "Decoupled weight decay regularization". ICLR. arXiv:1711.05101 — AdamW (3.6).

    ↩
  2. [2]

    Chen, X., Liang, C., Huang, D., et al. (2023). "Symbolic discovery of optimization algorithms" (Lion). arXiv:2302.06675 — Lion (3.6 aside).

    ↩
  3. [3]

    Liu, H., Li, Z., Hall, D., et al. (2023). "Sophia: A scalable stochastic second-order optimizer for language model pre-training". arXiv:2305.14342 — Sophia (3.6 aside).

    ↩
  4. [4]

    Jordan, K., et al. (2024). "Muon: An optimizer for hidden layers in neural networks". keller-jordan.github.io — Muon (3.6 aside).

    ↩