Skip to content

feat(train): add Dion3 as an opt-in matrix optimizer - #1031

Draft
WindChimeRan wants to merge 3 commits into
vllm-project:mainfrom
WindChimeRan:feat/dion3-optimizer
Draft

feat(train): add Dion3 as an opt-in matrix optimizer#1031
WindChimeRan wants to merge 3 commits into
vllm-project:mainfrom
WindChimeRan:feat/dion3-optimizer

Conversation

@WindChimeRan

@WindChimeRan WindChimeRan commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Don't merge

upstream tracking:

Purpose

Aug 12 paper from Tri Dao's group. similar performance, 6x speedup than muon!
https://arxiv.org/pdf/2608.11612

Full claude report: https://claude.ai/code/artifact/c1de5f64-b1e9-4928-8939-280a79521b2c

image image

Qwen3-8B, Training data: 5k ultrachat from

https://huggingface.co/datasets/windchimeran/speculator-tutorial/viewer/default/tutorial_regen

Adds --optimizer dion3 as an opt-in alternative to Muon over the same matrix/scalar parameter split. Muon remains the default, and existing optimizer paths are unchanged.

Dion3 is loaded lazily because microsoft/dion is not published on PyPI.

Performance

DSpark, Qwen3-8B verifier, 512 anchors, FSDP2, median of 3 interleaved H100 runs — optimizer time per step:

GPUs AdamW Muon Dion3 Muon/Dion3
1 28.8 ms 118.4 ms 37.7 ms 3.14×
2 13.7 ms 164.9 ms 30.3 ms 5.44×
4 8.7 ms 159.7 ms 44.8 ms 3.56×
8 3.1 ms 110.4 ms 10.5 ms 10.52×

End-to-end speedup versus Muon was 1.48× on 1 GPU and 1.78× on 8 GPUs. At 3072 anchors, where optimizer time is a smaller portion of each step, the gain dropped to approximately 1.26×.

Quality is not established. An earlier untuned 300-step run—before this PR explicitly matched the scalar AdamW beta defaults—showed 1.32% higher total loss and 13.4% lower acceptance rate than Muon. Those quality results should be rerun; Dion3 therefore remains opt-in and exposes --dion-fraction for tuning.

Implementation notes

  • Reuses Muon's parameter split and preserves scalar AdamW betas (0.9, 0.999).
  • Obtains the FSDP device mesh from the sharded parameters.
  • Uses adjust_lr="rms_norm" to match Muon's effective LR convention.
  • Defaults to global row selection for world-size-independent updates.
  • Applies a scoped static-shape workaround on torch >= 2.13 for an Inductor regression; older supported versions remain unchanged.

Tests

  • python -m pytest tests/unit/train/test_dion3_optimizer.py -q: 6 passed
  • python -m pytest tests/unit/train -q: 303 passed, 5 skipped
  • Ruff check and format check passed
  • End-to-end FSDP2 runs passed for Muon, Dion3 global, and Dion3 local on 8 GPUs

Tested with torch 2.13.0+cu130, microsoft/dion@58d38ad, and 8×H100 80GB.

Checklist

I have filled in:

  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan/results, such as providing test command and pasting the results.
  • (Optional) The necessary documentation update.
  • I (a human) have written or reviewed the code in this pr to the best of my ability.

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 86a1ff61-ba6b-44e8-9707-a322e420e817

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@mergify

mergify Bot commented Aug 23, 2026

Copy link
Copy Markdown

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require approval from approved reviewers list 👀 reviews

🔴 Require approval from approved reviewers list

Waiting for any of

  • approved-reviews-by = dsikka
  • approved-reviews-by = fynnsu
  • approved-reviews-by = orestis-z
  • approved-reviews-by = rahul-tuli
  • approved-reviews-by = shanjiaz
This rule is failing.

All pull requests must have at least one approving review from a member of the approved reviewers list before merging.

  • any of:
    • approved-reviews-by = dsikka
    • approved-reviews-by = fynnsu
    • approved-reviews-by = orestis-z
    • approved-reviews-by = rahul-tuli
    • approved-reviews-by = shanjiaz

WindChimeRan and others added 3 commits August 23, 2026 01:14
Adds `--optimizer dion3`, substituting Dion3 (microsoft/dion, an alias for
NorDion2) for Muon over the identical parameter split. Muon remains the default;
nothing about the existing paths changes.

Why: Muon's optimizer step is a fixed per-parameter cost that does not shrink
with world size, because Newton-Schulz needs whole matrices and every rank has to
reassemble what FSDP just sharded. Dion3 orthogonalizes only a fraction of the
momentum matrix's rows and megabatches the sharded transfer, so its advantage
grows as you add GPUs. Measured on 8xH100 with DSpark (3 layers, Qwen3-8B
verifier, repo-default 512 anchors), optimizer time per step:

    GPUs      AdamW      Muon     Dion3   Muon/Dion3
       1       28.8     118.4      37.7        3.14x
       2       13.7     164.9      30.3        5.44x
       4        8.7     159.7      44.8        3.56x
       8        3.1     110.4      10.5       10.52x

End-to-end that is 1.48x at 1 GPU and 1.78x at 8. The win depends on the anchor
budget, since the optimizer cost is constant while forward/backward scale: at
3072 anchors the optimizer is 16-31% of the step rather than 43-58%, and the
end-to-end gain drops to ~1.26x. Peak memory per rank is consistently
Dion3 < Muon < AdamW.

Three implementation notes:

- `dion` is not published on PyPI, so it cannot be a declared dependency (PyPI
  rejects direct-URL requirements). It is imported lazily inside the branch with
  an actionable error pointing at the git install.
- The device mesh is read off the parameters rather than rebuilt, so it is by
  construction the mesh `fully_shard` used. Without it Dion3 silently takes its
  single-device path and the multi-GPU benefit disappears.
- `adjust_lr="rms_norm"` is set explicitly. It is 0.2*sqrt(max(fan_out, fan_in)),
  the same expression as torch Muon's "match_rms_adamw" default, so a given
  --muon-lr means the same effective step size on both. dion's own default
  (`spectral_norm`) is a different scale and would silently change the LR.

`dion_selection_scope` defaults to "global" rather than dion's "local": local
takes a per-rank top-k, so the update would depend on how the model is sharded
and results would not reproduce across world sizes. dion's own docs note the two
tie at moderate scale.

Quality is NOT yet established and this is why it ships opt-in: at 300 steps with
untuned hyperparameters, Dion3 reached +1.32% loss and -13.4% acceptance rate
against Muon (AdamW, for reference, is +1.82% loss and -65.6% acceptance).
Hyperparameters need a tuning pass -- dion's CHANGELOG says as much, and the
draft model's 676M orthogonalized parameters sit below the 3B scale at which
dion reports fraction=0.25 to be near-lossless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Di69UAVwpuJwDSNnEcVpD2
Signed-off-by: Ranran Haoran Zhang <ranzhang@redhat.com>
Bisected the inductor miscompile to a torch 2.13.0 regression: 2.12.0 and 2.12.1
both compile the same function cleanly. Comparing the generated Triton for the
identical dynamic-shape kernel, 2.12.1's post-loop epilogue re-materializes the
gathered value with its own load, while 2.13.0 reuses the temp emitted inside the
reduction loop body -- which is a separate scope in Triton, hence
`NameError: tmp<N> is not defined`.

So the workaround is only needed on >= 2.13. Gating it keeps older supported torch
versions (speculators allows >= 2.9.0) on the unmodified path, which matters
because pinning shapes static costs extra recompiles -- the exact complaint in
microsoft/dion#23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Di69UAVwpuJwDSNnEcVpD2
Signed-off-by: Ranran Haoran Zhang <ranzhang@redhat.com>
Signed-off-by: Ranran Haoran Zhang <ranzhang@redhat.com>
@mergify

mergify Bot commented Sep 1, 2026

Copy link
Copy Markdown

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @WindChimeRan.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant