Skip to content

Commit ba90c5a

Browse files
committed
add AI written tests
Signed-off-by: Hao Wu <skyw@nvidia.com>
1 parent f74b869 commit ba90c5a

2 files changed

Lines changed: 299 additions & 0 deletions

File tree

tests/muown_reference.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# Reference Muown implementation, adapted from the authors' code for use as a test oracle:
17+
# https://github.com/kcc-lion/muown/blob/main/optim/muown.py
18+
# Lion et al., "Muown: Row-Norm Control for Muon Optimization", arXiv:2605.10797 (paper: CC BY 4.0).
19+
# The repository did not declare a code license at the time of copying.
20+
"""Reference Muown implementation used as a test oracle."""
21+
22+
from typing import Callable
23+
24+
import torch
25+
from torch import Tensor
26+
from torch.optim.optimizer import Optimizer
27+
28+
29+
def _wn_pre_ns(W: Tensor, g: Tensor, v_norm: Tensor, grad_W: Tensor) -> tuple[Tensor, Tensor, Tensor]:
30+
"""Reconstruct direction v from (W, g, v_norm) and split grad_W into (grad_g, grad_v)."""
31+
u = W / g
32+
v = u * v_norm
33+
grad_g = (grad_W * u).sum(dim=1, keepdim=True)
34+
grad_v = (g / v_norm) * (grad_W - u * grad_g)
35+
return v, grad_g, grad_v
36+
37+
38+
def _wn_recompose(W: Tensor, g: Tensor, v_new: Tensor) -> Tensor:
39+
"""Write W = g * v_new / ||v_new||_row in place and return the new row norms."""
40+
v_norm_new = v_new.norm(dim=1, keepdim=True)
41+
W.copy_(g * (v_new / v_norm_new))
42+
return v_norm_new
43+
44+
45+
class MuownReference(Optimizer):
46+
"""Single-process reference Muown with an injected orthogonalization callable."""
47+
48+
def __init__(
49+
self,
50+
params,
51+
orthogonalize_fn: Callable[[Tensor], Tensor],
52+
lr: float = 3e-4,
53+
momentum: float = 0.95,
54+
nesterov: bool = False,
55+
betas: tuple[float, float] = (0.9, 0.95),
56+
weight_decay: float = 0.0,
57+
adam_eps: float = 1e-8,
58+
) -> None:
59+
self._orthogonalize_fn = orthogonalize_fn
60+
defaults = dict(
61+
lr=lr,
62+
momentum=momentum,
63+
nesterov=nesterov,
64+
betas=betas,
65+
weight_decay=weight_decay,
66+
adam_eps=adam_eps,
67+
)
68+
super().__init__(params, defaults)
69+
70+
def _init_state_2d(self, p: Tensor, state: dict) -> None:
71+
w_norm = p.data.norm(dim=1, keepdim=True)
72+
state["g"] = w_norm.clone()
73+
state["v_norm"] = w_norm.clone()
74+
state["m_v"] = torch.zeros_like(p.data)
75+
state["m_g"] = torch.zeros_like(w_norm)
76+
state["v_g"] = torch.zeros_like(w_norm)
77+
state["step"] = 0
78+
79+
@torch.no_grad()
80+
def step(self, closure=None):
81+
loss = None
82+
if closure is not None:
83+
with torch.enable_grad():
84+
loss = closure()
85+
86+
for group in self.param_groups:
87+
lr = group["lr"]
88+
momentum = group["momentum"]
89+
nesterov = group["nesterov"]
90+
betas = group["betas"]
91+
weight_decay = group["weight_decay"]
92+
adam_eps = group["adam_eps"]
93+
94+
for p in group["params"]:
95+
if p.grad is None:
96+
continue
97+
98+
grad = p.grad
99+
state = self.state[p]
100+
101+
if len(state) == 0:
102+
self._init_state_2d(p, state)
103+
104+
state["step"] += 1
105+
step = state["step"]
106+
107+
g = state["g"]
108+
v_norm = state["v_norm"]
109+
m_v = state["m_v"]
110+
m_g = state["m_g"]
111+
v_g = state["v_g"]
112+
if weight_decay != 0.0:
113+
W_old = p.data.clone()
114+
115+
# Fused: reconstruct v + compute weight norm gradients
116+
v, grad_g, grad_v = _wn_pre_ns(p.data, g, v_norm, grad)
117+
118+
# Muon update on v: momentum + orthogonalization
119+
m_v.mul_(momentum).add_(grad_v)
120+
if nesterov:
121+
update = grad_v.add(m_v, alpha=momentum)
122+
else:
123+
update = m_v.clone()
124+
125+
# Injected orthogonalization (folds in the 0.2 * sqrt(max(m, n)) scaling).
126+
update = self._orthogonalize_fn(update)
127+
v_new = v.add(update, alpha=-lr)
128+
129+
# Adam update on g (small [out_features, 1] vectors)
130+
beta1, beta2 = betas
131+
m_g.mul_(beta1).add_(grad_g, alpha=1 - beta1)
132+
v_g.mul_(beta2).addcmul_(grad_g, grad_g, value=1 - beta2)
133+
bc1 = 1 - beta1**step
134+
bc2 = 1 - beta2**step
135+
g.addcdiv_(m_g / bc1, (v_g / bc2).sqrt().add_(adam_eps), value=-lr)
136+
137+
# Fused: recompose W = g * v_new / ||v_new||, writes directly into p.data
138+
state["v_norm"] = _wn_recompose(p.data, g, v_new)
139+
if weight_decay != 0.0:
140+
p.data.add_(W_old, alpha=-lr * weight_decay)
141+
g.copy_(p.data.norm(dim=1, keepdim=True))
142+
143+
return loss

tests/test_muown.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
import torch
16+
from absl import flags, logging
17+
from absl.testing import absltest, parameterized
18+
from muown_reference import MuownReference
19+
20+
from emerging_optimizers.orthogonalized_optimizers.muown import Muown
21+
22+
23+
flags.DEFINE_enum("device", "cpu", ["cpu", "cuda"], "Device to run tests on")
24+
flags.DEFINE_integer("seed", None, "Random seed for reproducible tests")
25+
FLAGS = flags.FLAGS
26+
27+
28+
def setUpModule() -> None:
29+
if FLAGS.seed is not None:
30+
logging.info("Setting random seed to %d", FLAGS.seed)
31+
torch.manual_seed(FLAGS.seed)
32+
if torch.cuda.is_available():
33+
torch.cuda.manual_seed_all(FLAGS.seed)
34+
# Use the most precise fp32 matmul path so the reference (which calls the optimizer's
35+
# orthogonalization outside the fp32_matmul_precision context) computes bit-comparable results.
36+
torch.set_float32_matmul_precision("highest")
37+
38+
39+
class MuownTest(parameterized.TestCase):
40+
def setUp(self):
41+
super().setUp()
42+
self.device = FLAGS.device
43+
44+
@parameterized.product(shape=[(8, 16), (16, 8), (33, 65)])
45+
def test_smoke(self, shape):
46+
"""A few steps run and keep the weight finite."""
47+
p = torch.nn.Parameter(torch.randn(shape, device=self.device))
48+
opt = Muown([p], lr=1e-2, weight_decay=0.01)
49+
for _ in range(3):
50+
p.grad = torch.randn_like(p)
51+
opt.step()
52+
self.assertTrue(torch.isfinite(p).all())
53+
54+
def test_raises_on_non_2d(self):
55+
"""Muown supports 2D parameters only."""
56+
for shape in [(8,), (2, 3, 4)]:
57+
p = torch.nn.Parameter(torch.randn(shape, device=self.device))
58+
p.grad = torch.randn_like(p)
59+
opt = Muown([p], lr=1e-2)
60+
with self.assertRaises(TypeError):
61+
opt.step()
62+
63+
def test_raises_on_closure(self):
64+
"""Closures are not supported."""
65+
p = torch.nn.Parameter(torch.randn((8, 16), device=self.device))
66+
p.grad = torch.randn_like(p)
67+
opt = Muown([p], lr=1e-2)
68+
with self.assertRaises(ValueError):
69+
opt.step(lambda: 0.0)
70+
71+
@parameterized.product(shape=[(8, 16), (16, 8)], weight_decay=[0.0, 0.1])
72+
def test_row_norm_equals_magnitude_state(self, shape, weight_decay):
73+
"""The reparameterization invariant ||W_row|| == g holds after each step."""
74+
p = torch.nn.Parameter(torch.randn(shape, device=self.device))
75+
opt = Muown([p], lr=1e-2, weight_decay=weight_decay)
76+
for _ in range(4):
77+
p.grad = torch.randn_like(p)
78+
opt.step()
79+
row_norm = p.detach().norm(dim=1, keepdim=True)
80+
torch.testing.assert_close(
81+
row_norm,
82+
opt.state[p]["g"],
83+
atol=1e-5,
84+
rtol=1e-5,
85+
msg=lambda m: f"Row norms of W must equal the magnitude state g.\n\n{m}",
86+
)
87+
88+
def test_weight_decay_shrinks_magnitude(self):
89+
"""Decoupled weight decay shrinks the magnitude g relative to the no-decay run."""
90+
p_wd = torch.nn.Parameter(torch.randn((16, 32), device=self.device))
91+
p_no = torch.nn.Parameter(p_wd.detach().clone())
92+
opt_wd = Muown([p_wd], lr=1e-2, weight_decay=0.1)
93+
opt_no = Muown([p_no], lr=1e-2, weight_decay=0.0)
94+
for _ in range(5):
95+
grad = torch.randn_like(p_wd)
96+
p_wd.grad = grad.clone()
97+
p_no.grad = grad.clone()
98+
opt_wd.step()
99+
opt_no.step()
100+
self.assertLess(opt_wd.state[p_wd]["g"].sum().item(), opt_no.state[p_no]["g"].sum().item())
101+
102+
@parameterized.product(shape=[(8, 16), (16, 8), (33, 65)], momentum=[0.0, 0.95])
103+
def test_agrees_with_reference(self, shape, momentum):
104+
"""Muown matches the authors' reference implementation (no weight decay).
105+
106+
The reference uses classic (heavy-ball) momentum while Muown uses EMA momentum; the two differ by
107+
a constant factor that the scale-invariant Newton-Schulz orthogonalization removes, so the updates
108+
agree. Both use the same injected orthogonalization, so only float rounding (lerp vs mul/add,
109+
Newton-Schulz normalization) separates them — hence a tight tolerance rather than bit-identity.
110+
"""
111+
p = torch.nn.Parameter(torch.randn(shape, device=self.device))
112+
p_ref = torch.nn.Parameter(p.detach().clone())
113+
114+
opt = Muown(
115+
[p],
116+
lr=1e-2,
117+
momentum=momentum,
118+
weight_decay=0.0,
119+
extra_scale_factor=0.2,
120+
fp32_matmul_prec="highest",
121+
)
122+
# Hold orthogonalization identical: feed the reference Muown's own scaled_orthogonalize_fn.
123+
opt_ref = MuownReference(
124+
[p_ref],
125+
orthogonalize_fn=opt.scaled_orthogonalize_fn,
126+
lr=1e-2,
127+
momentum=momentum,
128+
nesterov=False,
129+
weight_decay=0.0,
130+
)
131+
132+
for _ in range(5):
133+
grad = torch.randn_like(p)
134+
p.grad = grad.clone()
135+
p_ref.grad = grad.clone()
136+
opt.step()
137+
opt_ref.step()
138+
139+
torch.testing.assert_close(
140+
p.detach(),
141+
p_ref.detach(),
142+
atol=1e-5,
143+
rtol=1e-4,
144+
msg=lambda m: f"Muown weight diverged from the reference implementation.\n\n{m}",
145+
)
146+
torch.testing.assert_close(
147+
opt.state[p]["g"],
148+
opt_ref.state[p_ref]["g"],
149+
atol=1e-5,
150+
rtol=1e-4,
151+
msg=lambda m: f"Muown magnitude g diverged from the reference implementation.\n\n{m}",
152+
)
153+
154+
155+
if __name__ == "__main__":
156+
absltest.main()

0 commit comments

Comments
 (0)