Skip to content

Commit eadfd44

Browse files
authored
chore: Merge pull request #449 from Modalities/muon
added muon optimizer component
2 parents c5dce17 + a1cff2e commit eadfd44

4 files changed

Lines changed: 174 additions & 3 deletions

File tree

‎src/modalities/config/config.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,19 @@ class AdamWOptimizerConfig(BaseModel):
170170
fused: bool | None = None
171171

172172

173+
class MuonOptimizerConfig(BaseModel):
174+
lr: float
175+
weight_decay: float
176+
momentum: float = 0.95
177+
nesterov: bool = True
178+
ns_coefficients: tuple[float, float, float] = (3.4445, -4.775, 2.0315)
179+
eps: float = 1e-07
180+
ns_steps: int = Field(default=5)
181+
adjust_lr_fn: str | None = None
182+
weight_decay_groups_excluded: list[str]
183+
wrapped_model: PydanticPytorchModuleOrListType
184+
185+
173186
class DummyLRSchedulerConfig(BaseModel):
174187
optimizer: PydanticOptimizerIFType
175188

‎src/modalities/optimizers/optimizer_factory.py‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
77
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP1
88
from torch.distributed.tensor import DTensor
9-
from torch.optim import Adam, AdamW, Optimizer
9+
from torch.optim import Adam, AdamW, Muon, Optimizer
1010

1111
from modalities.checkpointing.checkpoint_loading import FSDP1CheckpointLoadingIF
1212
from modalities.exceptions import OptimizerError
@@ -49,6 +49,33 @@ def get_adam_w(
4949
optimizer = AdamW(params=optimizer_groups, lr=lr, betas=betas, eps=eps, foreach=foreach, fused=fused)
5050
return optimizer
5151

52+
@staticmethod
53+
def get_muon(
54+
lr: float,
55+
weight_decay: float,
56+
momentum: float,
57+
nesterov: bool,
58+
ns_coefficients: tuple[float, float, float],
59+
eps: float,
60+
ns_steps: int,
61+
adjust_lr_fn: str | None,
62+
weight_decay_groups_excluded: list[str],
63+
wrapped_model: nn.Module,
64+
) -> Optimizer:
65+
optimizer_groups = get_optimizer_groups(wrapped_model, weight_decay, weight_decay_groups_excluded)
66+
optimizer = Muon(
67+
params=optimizer_groups,
68+
lr=lr,
69+
weight_decay=weight_decay,
70+
momentum=momentum,
71+
nesterov=nesterov,
72+
ns_coefficients=ns_coefficients,
73+
eps=eps,
74+
ns_steps=ns_steps,
75+
adjust_lr_fn=adjust_lr_fn,
76+
)
77+
return optimizer
78+
5279
@staticmethod
5380
def get_fsdp1_checkpointed_optimizer_(
5481
checkpoint_loading: FSDP1CheckpointLoadingIF,
@@ -149,7 +176,7 @@ def _create_optimizer_groups(
149176

150177
else:
151178
raise OptimizerError(
152-
f"model {type(model)} is not an instance of FSDP1 or FSDP2. " "Please use the correct model type."
179+
f"model {type(model)} is not an instance of FSDP1 or FSDP2. Please use the correct model type."
153180
)
154181

155182
if (

‎src/modalities/registry/components.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
LinearWarmupCosineAnnealingLRSchedulerConfig,
5353
LLMDataLoaderConfig,
5454
MemMapDatasetConfig,
55+
MuonOptimizerConfig,
5556
OneCycleLRSchedulerConfig,
5657
PackedMemMapDatasetContinuousConfig,
5758
PackedMemMapDatasetMegatronConfig,
@@ -258,6 +259,9 @@ class ComponentEntity:
258259
ComponentEntity(
259260
"optimizer", "adam_w", maybe_model_list_for_optimizer(OptimizerFactory.get_adam_w), AdamWOptimizerConfig
260261
),
262+
ComponentEntity(
263+
"optimizer", "muon", maybe_model_list_for_optimizer(OptimizerFactory.get_muon), MuonOptimizerConfig
264+
),
261265
ComponentEntity(
262266
"optimizer",
263267
"fsdp1_checkpointed",

‎tests/test_optimizer_factory.py‎

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
22
from typing import Literal
3+
from unittest.mock import MagicMock
34

45
import pytest
56
import torch
@@ -14,7 +15,8 @@
1415
from modalities.models.coca.coca_model import CoCa, CoCaConfig
1516
from modalities.models.gpt2.gpt2_model import GPT2LLM
1617
from modalities.models.model_factory import ModelFactory
17-
from modalities.optimizers.optimizer_factory import get_optimizer_groups
18+
from modalities.optimizers import optimizer_factory as optimizer_factory_module
19+
from modalities.optimizers.optimizer_factory import OptimizerFactory, get_optimizer_groups
1820
from modalities.registry.components import COMPONENTS
1921
from modalities.registry.registry import Registry
2022
from modalities.running_env.env_utils import MixedPrecisionSettings
@@ -72,6 +74,131 @@ def test_get_optimizer_groups(
7274
)
7375

7476

77+
def test_get_adam_builds_optimizer_from_groups(monkeypatch):
78+
wrapped_model = MagicMock()
79+
optimizer_groups = [{"params": [MagicMock()], "weight_decay": 0.1}]
80+
optimizer = MagicMock()
81+
get_optimizer_groups_mock = MagicMock(return_value=optimizer_groups)
82+
adam_mock = MagicMock(return_value=optimizer)
83+
84+
monkeypatch.setattr(optimizer_factory_module, "get_optimizer_groups", get_optimizer_groups_mock)
85+
monkeypatch.setattr(optimizer_factory_module, "Adam", adam_mock)
86+
87+
result = OptimizerFactory.get_adam(
88+
lr=1e-3,
89+
betas=(0.9, 0.95),
90+
eps=1e-8,
91+
weight_decay=0.1,
92+
weight_decay_groups_excluded=["embedding"],
93+
wrapped_model=wrapped_model,
94+
foreach=True,
95+
fused=False,
96+
)
97+
98+
assert result is optimizer
99+
get_optimizer_groups_mock.assert_called_once_with(wrapped_model, 0.1, ["embedding"])
100+
adam_mock.assert_called_once_with(
101+
params=optimizer_groups,
102+
lr=1e-3,
103+
betas=(0.9, 0.95),
104+
eps=1e-8,
105+
foreach=True,
106+
fused=False,
107+
)
108+
109+
110+
def test_get_adam_w_builds_optimizer_from_groups(monkeypatch):
111+
wrapped_model = MagicMock()
112+
optimizer_groups = [{"params": [MagicMock()], "weight_decay": 0.2}]
113+
optimizer = MagicMock()
114+
get_optimizer_groups_mock = MagicMock(return_value=optimizer_groups)
115+
adam_w_mock = MagicMock(return_value=optimizer)
116+
117+
monkeypatch.setattr(optimizer_factory_module, "get_optimizer_groups", get_optimizer_groups_mock)
118+
monkeypatch.setattr(optimizer_factory_module, "AdamW", adam_w_mock)
119+
120+
result = OptimizerFactory.get_adam_w(
121+
lr=2e-4,
122+
betas=(0.8, 0.99),
123+
eps=1e-6,
124+
weight_decay=0.2,
125+
weight_decay_groups_excluded=["layernorm"],
126+
wrapped_model=wrapped_model,
127+
foreach=False,
128+
fused=True,
129+
)
130+
131+
assert result is optimizer
132+
get_optimizer_groups_mock.assert_called_once_with(wrapped_model, 0.2, ["layernorm"])
133+
adam_w_mock.assert_called_once_with(
134+
params=optimizer_groups,
135+
lr=2e-4,
136+
betas=(0.8, 0.99),
137+
eps=1e-6,
138+
foreach=False,
139+
fused=True,
140+
)
141+
142+
143+
def test_get_muon_builds_optimizer_from_groups(monkeypatch):
144+
wrapped_model = MagicMock()
145+
optimizer_groups = [{"params": [MagicMock()], "weight_decay": 0.05}]
146+
optimizer = MagicMock()
147+
get_optimizer_groups_mock = MagicMock(return_value=optimizer_groups)
148+
muon_mock = MagicMock(return_value=optimizer)
149+
150+
monkeypatch.setattr(optimizer_factory_module, "get_optimizer_groups", get_optimizer_groups_mock)
151+
monkeypatch.setattr(optimizer_factory_module, "Muon", muon_mock)
152+
153+
result = OptimizerFactory.get_muon(
154+
lr=3e-4,
155+
weight_decay=0.05,
156+
momentum=0.95,
157+
nesterov=True,
158+
ns_coefficients=(1.0, 0.5, 0.25),
159+
eps=1e-9,
160+
ns_steps=7,
161+
adjust_lr_fn="cosine",
162+
weight_decay_groups_excluded=["embedding"],
163+
wrapped_model=wrapped_model,
164+
)
165+
166+
assert result is optimizer
167+
get_optimizer_groups_mock.assert_called_once_with(wrapped_model, 0.05, ["embedding"])
168+
muon_mock.assert_called_once_with(
169+
params=optimizer_groups,
170+
lr=3e-4,
171+
weight_decay=0.05,
172+
momentum=0.95,
173+
nesterov=True,
174+
ns_coefficients=(1.0, 0.5, 0.25),
175+
eps=1e-9,
176+
ns_steps=7,
177+
adjust_lr_fn="cosine",
178+
)
179+
180+
181+
def test_get_fsdp1_checkpointed_optimizer_loads_optimizer_state():
182+
checkpoint_loading = MagicMock()
183+
checkpoint_path = Path("/tmp/checkpoint")
184+
wrapped_model = MagicMock()
185+
optimizer = MagicMock()
186+
187+
result = OptimizerFactory.get_fsdp1_checkpointed_optimizer_(
188+
checkpoint_loading=checkpoint_loading,
189+
checkpoint_path=checkpoint_path,
190+
wrapped_model=wrapped_model,
191+
optimizer=optimizer,
192+
)
193+
194+
assert result is optimizer
195+
checkpoint_loading.load_optimizer_checkpoint_.assert_called_once_with(
196+
file_path=checkpoint_path,
197+
optimizer=optimizer,
198+
model=wrapped_model,
199+
)
200+
201+
75202
def _run_single_optimizer_group_case(
76203
process_id: int,
77204
world_size: int,

0 commit comments

Comments
 (0)