Skip to content

Fix Lion to use decoupled weight decay (default + CUDA 32-bit)#1993

Merged
matthewdouglas merged 3 commits into
bitsandbytes-foundation:mainfrom
eaglstun:fix/issue-1992
Jul 9, 2026
Merged

Fix Lion to use decoupled weight decay (default + CUDA 32-bit)#1993
matthewdouglas merged 3 commits into
bitsandbytes-foundation:mainfrom
eaglstun:fix/issue-1992

Conversation

@eaglstun

@eaglstun eaglstun commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #1992.

Problem

Lion with weight_decay > 0 applies coupled (L2) weight decay, folding decay into the gradient before the sign() in the 32-bit optimizer paths. Lion requires decoupled (AdamW-style) decay applied to the parameter directly (p *= 1 - lr*wd), outside the sign update (Chen et al. 2023).

Coupling decay into the gradient changes the input to sign(), so it corrupts the update direction, not just its magnitude.

The smoking gun that this is a bug and not a design choice: the CUDA backend is already inconsistent with itself. The 8-bit blockwise kernel and the cpu backend do decoupled decay correctly; only the 32-bit paths got it wrong.

Fix

Backend Path Before After
default backends/default/ops.py coupled ✅ decoupled
CUDA 32-bit csrc/kernels.cu::kOptimizer32bit1State coupled ✅ decoupled
CUDA 8-bit kOptimizerStatic8bit1State decoupled (already correct)
cpu backends/cpu/ops.py decoupled (already correct)

The CUDA change mirrors the existing 8-bit kernel exactly: exclude LION from the coupled gradient fold, and apply p *= (1 - lr*wd) in the LION branch before the sign update (un-scaled by the max_unorm clip, matching the reference).

I verified kOptimizer32bit1State is the only 32-bit CUDA site that applies Lion decay: the 2-state kernel is ADAM/ADEMAMIX (already decoupled), and the 1-state precondition kernel only accumulates the update norm for Lion.

Test

Adds test_lion32bit_weight_decay to tests/test_optim.py, comparing bnb.optim.Lion against the lion-pytorch reference with weight_decay=0.1. It's parametrized over get_available_devices(), so it exercises the CUDA 32-bit kernel on GPU CI. The existing test_optimizer32bit never caught this because it uses the default weight_decay=0, where coupled and decoupled coincide.

Under the bug the params diverge far past the error budget; with the fix they track the reference.

Notes / scope

  • I can run the default-backend + CPU tests locally, but not the CUDA kernel, it's unbuildable on Apple Silicon. Relying on GPU CI to validate the 32-bit CUDA path (happy to help debug any failures).
  • The Triton 1-state kernel (backends/triton/kernels_optim.py) carries the same coupled-decay bug, but it's XPU-only with no CI hardware here. Left as a follow-up, happy to open a companion PR if someone can validate on XPU.

eaglstun and others added 2 commits July 8, 2026 14:55
The shared 32-bit optimizer path in the `default` backend folded weight decay
into the gradient (coupled L2) for Lion, which corrupts the sign update. Lion
uses *decoupled* (AdamW-style) weight decay per Chen et al. 2023: shrink the
param directly (p *= 1 - lr*wd), outside the sign. This is what the cpu backend
and the CUDA 8-bit blockwise kernel already do; the default backend (used by MPS
and any device without a dedicated kernel) was the outlier.

- Drop LION (id 4) from the coupled-decay group; keep MOMENTUM/RMSPROP/ADAGRAD.
- Apply decoupled decay in the LION branch, matching cpu/ops.py.
- Add test_lion32bit_weight_decay: the existing test_optimizer32bit never caught
  this because it constructs optimizers with the default weight_decay=0, where
  coupled and decoupled coincide. New test fails without the fix, passes with it.

NOTE: the CUDA 32-bit kernel (csrc/kernels.cu::kOptimizer32bit1State) and the
Triton 1-state kernel (backends/triton/kernels_optim.py) carry the same coupled-
decay bug for Lion and need a separate upstream fix (they can't be built/tested
on Apple Silicon).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sandbytes-foundation#1992)

The shared 32-bit optimizer kernel (kOptimizer32bit1State) folded weight
decay into the gradient (coupled L2) for every optimizer, including Lion,
which corrupts Lion's sign update. Lion requires *decoupled* (AdamW-style)
decay applied to the parameter directly (p *= 1 - lr*wd), outside the sign,
per Chen et al. 2023. The CUDA 8-bit blockwise kernel, the cpu backend, and
now the default backend already do this; the 32-bit kernel was the outlier.

- Exclude LION from the coupled gradient fold.
- Apply decoupled decay to the param in the LION branch, before the sign
  update, matching kOptimizerStatic8bit1State.
- Update the NOTE in the default backend now that CUDA is fixed (only the
  Triton XPU 1-state kernel still carries the coupled-decay bug).

test_lion32bit_weight_decay (added with the default-backend fix) is
parametrized over get_available_devices(), so it exercises this kernel on
CUDA hardware in CI. It cannot be built or run on Apple Silicon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@matthewdouglas matthewdouglas added ROCm Optimizers Issues or feature requests relating to optimizers CUDA Issues and PRs related to the CUDA backend, excluding installation/support help. labels Jul 8, 2026
Comment thread bitsandbytes/backends/default/ops.py Outdated
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@matthewdouglas matthewdouglas added this to the v0.50.0 milestone Jul 8, 2026
Co-authored-by: Matthew Douglas <38992547+matthewdouglas@users.noreply.github.com>

@matthewdouglas matthewdouglas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks for the PR! Appreciate that it is well scoped and has a reasonable test addition. All of the CUDA tests are passing.

If you'd like, feel free to open an equivalent PR for the Triton backend. I wouldn't expect it to be particularly invasive and probably safe to merge whether we can validate it or not.

@matthewdouglas matthewdouglas merged commit ab9709a into bitsandbytes-foundation:main Jul 9, 2026
84 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CUDA Issues and PRs related to the CUDA backend, excluding installation/support help. Optimizers Issues or feature requests relating to optimizers ROCm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lion applies coupled (L2) weight decay instead of decoupled in 32-bit CUDA, Triton, and the default backend

2 participants