Add RMNP (Row-Momentum Normalized Preconditioning) optimizer (arXiv:2603.20527)#106
Open
JohnLangford wants to merge 2 commits into
Open
Add RMNP (Row-Momentum Normalized Preconditioning) optimizer (arXiv:2603.20527)#106JohnLangford wants to merge 2 commits into
JohnLangford wants to merge 2 commits into
Conversation
…603.20527)
RMNP replaces Muon's Newton-Schulz orthogonalization with a single row-wise
(input-dimension) L2 normalization of the momentum update,
row_normalize(V) = (diag(V V^T))^{-1/2} V, cutting the per-iteration cost from
O(mn*min(m,n)) to O(mn) for an m x n weight while matching Muon-level quality
(orthogonalization and row-wise L2 normalization are asymptotically equivalent
for the Transformer).
Implemented as a DistributedOrthoBase subclass mirroring NorMuon: the row
normalization is passed as the newton_schulz_func, so it reuses Muon's momentum,
distributed megabatch assembly, weight update, and num_heads/split_sizes support
unchanged. Learning rate is not rescaled by default (adjust_lr=None), matching
the paper. Because row normalization is scale-invariant, Muon's momentum
(V <- muV + G) and the paper's EMA (V <- betaV + (1-beta)G) give the identical
normalized update.
Adds CPU-runnable primitive/equivalence tests (including the asymptotic
equivalence to orthogonalization on a diagonal-Gram matrix) and CUDA end-to-end
tests, plus README and CHANGELOG entries.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds the RMNP (Row-Momentum Normalized Preconditioning) optimizer as an opt-in member of the Muon/NorMuon family, implemented by reusing the existing Muon megabatch/distributed update path while swapping Newton–Schulz orthogonalization for a row-wise ℓ₂ normalization primitive.
Changes:
- Introduces
dion.RMNPand therow_normalizepreconditioner (Muon-compatible megabatch + distributed wiring). - Adds targeted RMNP primitive/equivalence tests plus optimizer-level parity and validation coverage.
- Updates README and CHANGELOG to document RMNP and its parallelization/sharding support.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_rmnp.py | New unit + CUDA-gated end-to-end tests for row_normalize and RMNP behavior. |
| tests/test_optimizers.py | Integrates RMNP into the shared optimizer test matrix (parity, state, validation, mixed groups). |
| README.md | Documents RMNP and adds it to the optimizer/parallelization tables and sharding section. |
| dion/rmnp.py | Implements row_normalize and the RMNP optimizer by reusing Muon’s megabatch update path. |
| dion/init.py | Exposes RMNP at the package top level. |
| CHANGELOG.md | Notes RMNP addition and its intended behavior/defaults. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+26
to
+29
| which is exactly ``(diag(X Xᵀ))^{-1/2} X``: Muon's orthogonalization | ||
| replaces the full ``(X Xᵀ)^{-1/2}`` by its diagonal, and the two coincide | ||
| when ``X Xᵀ`` is (block-)diagonally dominant -- the empirically observed | ||
| structure of the Transformer layerwise Hessian that motivates RMNP. |
Comment on lines
+59
to
+63
| """The 'row' axis is d_in (last), so per-column ℓ₂ norms are NOT unit.""" | ||
| x = torch.randn(8, 32) | ||
| y = row_normalize(x, epsilon=0.0) | ||
| # Rows (d_in) are unit-norm; columns (d_out) are generally not. | ||
| assert torch.allclose(y.norm(dim=-1), torch.ones(8), atol=1e-5) |
…ss test
- row_normalize docstring: it is RMNP (not Muon) that replaces the full
(X X^T)^{-1/2} orthogonalization factor with its diagonal (Copilot review).
- test_normalizes_input_dimension_not_output: use precise d_out/d_in axis
labels so the prose matches the assertions (Copilot review).
- Add test_sharded_update_matches_single_device: a multi-GPU test pinning that
the FSDP2-sharded RMNP update reproduces the single-device update exactly for
both row-sharding (dim 0) and column-sharding (dim 1). Fixes a collective-
outside-rank-guard deadlock caught while writing it (full_tensor() must run
on all ranks).
Contributor
Author
|
Thanks for the review — both addressed in 3b816c5:
Also added |
Comment on lines
+83
to
+86
| Because row normalization is scale-invariant, the paper's momentum EMA | ||
| ``V ← β·V + (1−β)·G`` and Muon's ``M ← μ·M + G`` produce the same normalized | ||
| update (they differ only by the constant factor ``1−β``, which cancels), so | ||
| RMNP reuses Muon's momentum machinery unchanged. |
Comment on lines
+73
to
+78
| """row_normalize(c·X) == row_normalize(X) for c > 0. | ||
|
|
||
| This is why RMNP can reuse Muon's momentum update ``M ← μM + G`` in place | ||
| of the paper's EMA ``V ← βV + (1−β)G``: the two differ only by the | ||
| constant factor ``1−β``, which cancels under row normalization. | ||
| """ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds RMNP (Row-Momentum Normalized Preconditioning), the optimizer from
Deng et al., RMNP: Row-Momentum Normalized Preconditioning for Scalable
Matrix-Based Optimization, arXiv:2603.20527,
as an opt-in member of the Muon/NorMuon family.
Method
RMNP replaces Muon's Newton-Schulz orthogonalization with a single row-wise
(input-dimension,
d_in) ℓ₂ normalization of the momentum update. For anm×nweight (m = d_out,n = d_in), the update isrow_normalizedivides each row (lengthd_in) by its own ℓ₂ norm. This isexactly Muon's orthogonalization
(V Vᵀ)^{-1/2} Vwith the full inverse-square-rootreplaced by its diagonal. The two coincide when
V Vᵀis (block-)diagonallydominant — the empirically observed structure of the Transformer layerwise
Hessian that motivates the method; the paper shows orthogonalization and row-wise
d_inℓ₂ normalization are asymptotically equivalent for the Transformer.This drops the per-iteration cost from
O(mn·min(m,n))toO(mn)while, inthe paper's experiments, matching Muon-level optimization quality.
How it's wired into dion
RMNPis a standaloneDistributedOrthoBasesubclass (dion/rmnp.py) thatmirrors
Muon/NorMuon. The row normalization is a module-levelrow_normalizeprimitive passed as the
newton_schulz_func, so it drops straight into theexisting, tested megabatch assembly path — literally "replace Newton-Schulz with
row normalization." Consequently RMNP:
muon_update_pre_orthogonalize) and weight update(
muon_update_post_orthogonalize), andmuon_update_megabatch_async;the
num_heads/split_sizesparam-group options), since the matrix isassembled the same way before the preconditioner runs;
momentum(no Newton-Schulz iteration,so no
use_triton/use_polar_express/use_gram_newton_schulzoptions).Interpretation notes (flagged, since the paper leaves small choices open)
V ← β·V + (1−β)·G; dion'sshared Muon momentum is
V ← μ·V + G. Because row normalization isscale-invariant, these differ only by the constant factor
1−β, which cancels— the normalized update is identical — so RMNP reuses Muon's momentum
unchanged. (Covered by
test_scale_invariant.)(
W ← W − η·D) and tunes the matrix LR directly, soadjust_lrdefaults toNone.spectral_norm/rms_normremain available for consistency withthe family / LR transfer, but were not studied in the RMNP paper.
epsilonis added to each row norm (X / (‖X‖ + ε)), matching both thepaper's formula and dion's Newton-Schulz stabilization idiom.
Tests
tests/test_rmnp.py(CPU-runnable primitive + equivalence checks):per-row
d_innormalization, shape preservation, scale invariance, theinput-dimension axis, epsilon stability, and the asymptotic-equivalence
claim —
row_normalizeequals the SVD polar factor (orthogonalization) tomachine precision on a diagonal-Gram matrix
V = diag(d)·Q(orthonormal rows),and diverges when rows are correlated (so the equivalence is regime-specific,
not trivial). A CUDA check also matches the shipped Newton-Schulz kernel within
its bf16 tolerance. Plus end-to-end: the applied update has unit-norm rows, and
a toy loss decreases.
tests/test_optimizers.py: aTestRMNPclass mirroringTestMuon(basic / determinism / params-change / nesterov / cautious_wd / adjust_lr /
megabatch / mixed shapes / state), plus RMNP entries in the shared
num_headsand
split_sizesfused-vs-separate parity tests, mixed matrix+scalar groups,and hyperparameter validation.
Full test suite passes locally (CUDA, 4×GPU). Sharded correctness was also
verified out-of-band: RMNP under FSDP2 row-sharding (dim 0) and column-sharding
(dim 1) reproduces the single-device result exactly.
Docs
README (intro list, parallelization table, file list, 1D-sharding section) and
CHANGELOG updated.