Skip to content

feat(adamw): let a recipe exempt parameters from decoupled weight decay - #2030

Closed
ooples wants to merge 1 commit into
masterfrom
feat/adamw-weight-decay-mask
Closed

feat(adamw): let a recipe exempt parameters from decoupled weight decay#2030
ooples wants to merge 1 commit into
masterfrom
feat/adamw-weight-decay-mask

Conversation

@ooples

@ooples ooples commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Follow-up to #2029, which needed this and could not have it.

Why

WeightDecay is a single coefficient applied to the whole flat parameter vector, so there is no way to express "decay these parameters but not those". Published recipes routinely need exactly that:

  • RecurrentGemma (Botev et al., 2024) §2: "we do not apply weight decay to the parameters of the recurrent (RG-LRU) layers during training."
  • The ordinary transformer recipe exempts biases and normalization gains.

The reason is the same in both cases: decay pulls weights toward zero, which helps where magnitude is what's being regularized, and corrupts parameters whose value carries meaning — a recurrence's own decay rate, for instance.

In #2029 I left this measure out rather than ship a property that silently did nothing. This is the capability that makes it expressible.

What changed

WeightDecayMask — an optional per-parameter multiplier on the decay term.

  • null decays everything, which is exactly what every caller had before. No existing model changes behaviour.
  • Entries multiply elementwise: 1 decays normally, 0 exempts, fractions scale.
  • The gradient update is untouched — this is decoupled decay only.
  • Applied at all three eager sites (two vector paths and the element-wise span loop) so they cannot disagree.

The fused path

TryGetFusedOptimizerConfig now declines when a mask is set.

The fused config carries decay as a single float, so a masked run cannot be represented in it — the compiled kernel would keep decaying the parameters the eager path exempts. Declining is the same mechanism that method already uses for adaptive learning rates.

Silently diverging between the compiled and eager paths would be the worst option available: it would surface only as a slow accuracy drift, with nothing pointing at the optimizer.

Verification

Test Asserts
NullMask_DecaysEveryParameter... the default is unchanged behaviour
ZeroMaskEntries_ExemptExactlyThoseParameters exemption reaches the update
FractionalMaskEntries_ScaleTheDecay 0.5 mask produces exactly half the movement
MaskPresent_DeclinesFusedCompilation masked declines, unmasked still fuses

Each isolates the decay term by stepping with a zero gradient, so anything that moves a parameter is decay and only decay.

75 optimizer tests pass unchanged, including FusedSpecMatchesEagerBehaviour and the copy-constructor suite — this touches an optimizer every model uses, so that regression check is the point.

Note for reviewers

This is shared-optimizer code. The two things that make it safe are the null default (byte-identical behaviour for every current caller) and the fusion opt-out, and both are directly tested rather than asserted.

A natural next step, not included here: have RecurrentGemmaLanguageModel build a mask that zeroes its RealGatedLinearRecurrenceLayer slots, which would complete the paper's third measure. That needs a layer-to-flat-slot mapping and is worth its own PR.

🤖 Generated with Claude Code

WeightDecay is a single coefficient applied to the whole flat parameter vector, so there was
no way to express "decay these parameters but not those". Published recipes routinely need
exactly that. RecurrentGemma (Botev et al., 2024) Section 2: "we do not apply weight decay to
the parameters of the recurrent (RG-LRU) layers during training." The ordinary transformer
recipe exempts biases and normalization gains for the same reason -- decay pulls weights
toward zero, which helps where magnitude is the thing being regularized and corrupts
parameters whose VALUE carries meaning, such as a recurrence's own decay rate.

WeightDecayMask is an optional per-parameter multiplier on the decay term. Null decays
everything, which is exactly what every caller had before, so no existing model changes
behaviour. Entries multiply elementwise: 1 decays normally, 0 exempts, fractions scale. The
gradient update is untouched; this is decoupled decay only.

Applied at all three eager sites -- the two vector paths and the element-wise span loop -- so
they cannot disagree with each other.

TryGetFusedOptimizerConfig now DECLINES when a mask is set. The fused config carries decay as
a single float, so a masked run cannot be expressed in it, and the compiled kernel would go on
decaying the parameters the eager path exempts. Declining is the same mechanism that method
already uses for adaptive learning rates. Silently diverging between the two paths would be
the worst of the available options, since it would only show up as a slow accuracy drift.

Four tests: the null default decays everything, zero entries exempt exactly those parameters,
fractional entries scale the decay proportionally, and a masked optimizer reports no fused
config while an unmasked one still fuses. Each isolates the decay term by stepping with a ZERO
gradient, so anything that moves a parameter is decay and only decay.

75 optimizer tests pass unchanged, including FusedSpecMatchesEagerBehaviour and the
copy-constructor suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aidotnet_website Ready Ready Preview Aug 19, 2026 11:24pm
aidotnet-playground-api Ready Ready Preview Aug 19, 2026 11:24pm

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ooples, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9a1921f4-f6d7-40cb-b9b1-0e63a2616495

📥 Commits

Reviewing files that changed from the base of the PR and between a087259 and ab46661.

📒 Files selected for processing (3)
  • src/Models/Options/AdamWOptimizerOptions.cs
  • src/Optimizers/AdamWOptimizer.cs
  • tests/AiDotNet.Tests/UnitTests/Optimizers/AdamWWeightDecayMaskTests.cs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ooples

ooples commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #2029. feat(adamw): let a recipe exempt parameters from decoupled weight decay has been merged into fix/n1-nonfinite-training, along with the rest of the N1 cluster work.

This PR described itself as a follow-up to #2029, so keeping it on a separate branch just split one review across two places. The WeightDecayMask option, the fused-config opt-out, and AdamWWeightDecayMaskTests are all present on #2029 unchanged.

Closing here; review continues on #2029.

@ooples ooples closed this Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant