Skip to content

Commit 3aa6f82

Browse files
committed
fix(zero): reject Muon + reduce_scatter in ZeRO-1/2
Muon's Newton-Schulz orthogonalization requires the full all-reduced gradient matrix on each rank. With reduce_scatter (the default), ZeRO-1/2 delivers each rank only its own partition slice, so a parameter whose flattened gradient crosses a partition boundary is orthogonalized on a partially-reduced, rank-divergent gradient and silently receives an incorrect update (#7807). Raise a clear error at initialization when Muon is combined with reduce_scatter, consistent with the existing ZeRO-3 guard (#7919), and add a regression test. Users should set "reduce_scatter": false to run Muon with ZeRO-1/2, as the Muon tests already do. Closes #7807 Signed-off-by: whycoming <alwaysxd666@gmail.com>
1 parent 3a47d1d commit 3aa6f82

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

deepspeed/runtime/zero/stage_1_and_2.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from deepspeed.runtime.constants import PIPE_REPLICATED
3636
from deepspeed.accelerator import get_accelerator
3737
from deepspeed.runtime.zero.muon.original_muon import muon_update
38+
from deepspeed.runtime.zero.muon.muon_optimizer import MuonWithAuxAdam
3839
from deepspeed.checkpoint.constants import (DS_VERSION, GROUP_PADDINGS, PARTITION_COUNT, LOSS_SCALER,
3940
SINGLE_PARTITION_OF_FP32_GROUPS, BASE_OPTIMIZER_STATE,
4041
BASE_OPTIMIZER_STATE_STEP, CLIP_GRAD, ZERO_STAGE, PARAM_SLICE_MAPPINGS)
@@ -218,6 +219,12 @@ def __init__(self,
218219

219220
self.reduce_scatter = reduce_scatter
220221

222+
# Muon's Newton-Schulz orthogonalization needs the full all-reduced gradient on each
223+
# rank; reduce_scatter delivers only this rank's partition slice and silently corrupts
224+
# cross-partition parameters (#7807). ZeRO-3 already guards this (see stage3.py).
225+
if isinstance(self.optimizer, MuonWithAuxAdam) and self.reduce_scatter:
226+
raise ValueError("Muon and reduce scatter cannot be used together")
227+
221228
self.overlap_comm = overlap_comm
222229

223230
self.deepspeed_adam_offload = self.cpu_offload

tests/unit/ops/muon/test_muon.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,37 @@ def test_ns_method_stage3(self, ns_method):
177177
loss = engine(x, y)
178178
engine.backward(loss)
179179
engine.step()
180+
181+
182+
class TestMuonRejectsReduceScatter(DistributedTest):
183+
"""Muon needs the full all-reduced gradient matrix on each rank for its Newton-Schulz
184+
orthogonalization. reduce_scatter only delivers each rank its own partition slice, which
185+
silently corrupts cross-partition parameters in ZeRO-1/2 (#7807). Initialization must fail
186+
loudly, consistent with the ZeRO-3 guard in stage3.py (added in #7919)."""
187+
188+
world_size = 1
189+
190+
@pytest.mark.parametrize('zero_stage', [1, 2])
191+
def test_muon_reduce_scatter_raises(self, zero_stage):
192+
config_dict = {
193+
"train_batch_size": 4,
194+
"optimizer": {
195+
"type": "muon",
196+
"params": {
197+
"lr": 0.01
198+
}
199+
},
200+
"fp16": {
201+
"enabled": True
202+
},
203+
"zero_optimization": {
204+
"stage": zero_stage,
205+
"reduce_scatter": True,
206+
},
207+
}
208+
model = SimpleModel(hidden_dim=32, nlayers=2)
209+
with pytest.raises(ValueError, match="Muon and reduce scatter cannot be used together"):
210+
deepspeed.initialize(config=config_dict,
211+
model=model,
212+
model_parameters=model.parameters(),
213+
dist_init_required=False)

0 commit comments

Comments
 (0)