Skip to content

Commit e614064

Browse files
committed
Fix fp16 ZeRO stage-0 loss scaling in engine.backward()
When fp16 is enabled with ZeRO stage 0, engine.backward() falls through to loss.backward() directly. FP16_Optimizer.step() always divides gradients by cur_scale (typically 32768), so gradients that were never pre-scaled are unscaled by the same factor, making effective parameter updates ~cur_scale times too small and stalling training. Add an explicit elif branch for fp16 + ZeRO-0 that routes through FP16_Optimizer.backward(), which pre-scales the loss by cur_scale before calling backward(), matching what step() expects. The existing # TODO comment on line 3325 acknowledges this path was left incomplete. The ZeRO stages > 0 and AMP paths are handled correctly; only the fp16 non-ZeRO path was missing. Tested with a CIFAR-10 MoE benchmark (fp16, ZeRO stage 0, 8 experts): - Before: accuracy 22-28%, loss stalls at ~1.8 - After: accuracy 49%, loss converges to ~0.7 Signed-off-by: Roohollah Etemadi <Roohollah.Etemadi@amd.com>
1 parent 56de570 commit e614064

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

deepspeed/runtime/engine.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3325,6 +3325,16 @@ def backward(self, loss, retain_graph=False, scale_wrt_gas=True):
33253325
# TODO: handle these scaling with direct calls to loss.backward()
33263326
if isinstance(self.optimizer, ZeROOptimizer):
33273327
loss = self.optimizer.scale_if_loss(loss)
3328+
elif self.fp16_enabled() and not self.zero_optimization():
3329+
# fp16 + ZeRO stage 0: FP16_Optimizer.step() always divides gradients
3330+
# by cur_scale, so loss must be pre-scaled before backward. Calling
3331+
# loss.backward() directly makes effective gradient updates ~cur_scale
3332+
# times too small, stalling training. Route through
3333+
# FP16_Optimizer.backward() which applies the scaling correctly.
3334+
self.optimizer.backward(loss, **backward_kwargs)
3335+
self._backward_epilogue()
3336+
self._running_engine_backward = False
3337+
return gas_scaled_loss
33283338
elif self.torch_autocast_z0_gradscaler:
33293339
loss = self.torch_autocast_z0_gradscaler.scale(loss)
33303340

0 commit comments

Comments
 (0)