Skip to content

Commit 642a9d2

Browse files
Warn when zero.Init silently falls back to a single rank (#8084)
When a multi-process launcher sets WORLD_SIZE>1 but the distributed process group is not initialized before zero.Init runs (e.g. from_pretrained before deepspeed.init_distributed()), the resolved group collapses to a single rank. zero.Init then materializes every parameter whole on every rank instead of partitioning, so each rank loads the full model and OOMs with no diagnostic. Detect this case and emit an actionable warning pointing at the missing init_distributed() call. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: FNU AKSHANSH <105249360+akshansh47@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3a47d1d commit 642a9d2

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

deepspeed/runtime/zero/partition_parameters.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,35 @@ def _no_gather_coalesced(params: Iterable[Parameter]) -> AllGatherCoalescedHandl
880880
return NoGatherCoalescedHandle(params)
881881

882882

883+
def _unsharded_single_rank_warning(dp_world_size, data_parallel_group, env=None):
884+
"""Detect the silent single-rank fallback described in #8084.
885+
886+
When a multi-process launcher (``deepspeed``, ``torchrun``, accelerate, ...) sets ``WORLD_SIZE > 1`` but the
887+
distributed process group was not initialized before ``zero.Init`` ran, the group resolved here collapses to a
888+
single rank. ``zero.Init`` then creates every parameter whole on every rank instead of partitioning it, so each
889+
rank allocates the full (unsharded) model and typically OOMs. The failure is otherwise silent and looks exactly
890+
like a "model too big" OOM. Return an actionable warning message in that case, else ``None``.
891+
892+
Only the default (world-group) path is checked: an explicitly supplied ``data_parallel_group`` of size 1 is
893+
treated as intentional.
894+
"""
895+
if dp_world_size != 1 or data_parallel_group is not None:
896+
return None
897+
env = os.environ if env is None else env
898+
try:
899+
launcher_world_size = int(env.get("WORLD_SIZE", "0") or "0")
900+
except (TypeError, ValueError):
901+
return None
902+
if launcher_world_size <= 1:
903+
return None
904+
return (
905+
"zero.Init resolved a process group of world_size=1, but the launcher environment reports "
906+
f"WORLD_SIZE={launcher_world_size}. The distributed process group was likely not initialized before "
907+
"zero.Init ran (for example, `from_pretrained` executed before `deepspeed.init_distributed()`). Parameters "
908+
"will NOT be partitioned: every rank allocates the full model and will likely OOM. Call "
909+
"`deepspeed.init_distributed()` before constructing the model under zero.Init.")
910+
911+
883912
# Replaces all parameters in module with Scattered Parameters
884913
class Init(InsertPostInitMethodToModuleSubClasses):
885914
param_id = 0
@@ -1035,6 +1064,10 @@ def __init__(self,
10351064
self.rank = dist.get_rank(group=self.ds_process_group)
10361065
self.dp_world_size = dist.get_world_size(group=self.ds_process_group)
10371066

1067+
_unsharded_warning = _unsharded_single_rank_warning(self.dp_world_size, data_parallel_group)
1068+
if _unsharded_warning is not None:
1069+
logger.warning(_unsharded_warning)
1070+
10381071
self.zero_param_process_group = zero_param_parallel_group
10391072
if _ds_config is not None and _ds_config.zero_config.zero_hpz_partition_size > 1 and self.zero_param_process_group is None:
10401073
groups._create_zero_param_parallel_group(_ds_config.zero_config.zero_hpz_partition_size)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# DeepSpeed Team
5+
6+
# Regression coverage for #8084: zero.Init silently falls back to a single-rank (unsharded) group when the
7+
# distributed process group is not initialized before it runs (e.g. `from_pretrained` before
8+
# `deepspeed.init_distributed()`), so every rank allocates the full model and OOMs. The detection helper must warn
9+
# only when the launcher reports a multi-process world but the resolved group collapsed to one rank.
10+
11+
import pytest
12+
13+
from deepspeed.runtime.zero.partition_parameters import _unsharded_single_rank_warning
14+
15+
16+
def test_warns_when_launcher_multiprocess_but_group_is_single_rank():
17+
msg = _unsharded_single_rank_warning(dp_world_size=1, data_parallel_group=None, env={"WORLD_SIZE": "8"})
18+
assert msg is not None
19+
assert "WORLD_SIZE=8" in msg
20+
assert "init_distributed" in msg
21+
22+
23+
def test_no_warning_for_genuine_single_process():
24+
assert _unsharded_single_rank_warning(dp_world_size=1, data_parallel_group=None, env={"WORLD_SIZE": "1"}) is None
25+
assert _unsharded_single_rank_warning(dp_world_size=1, data_parallel_group=None, env={}) is None
26+
27+
28+
def test_no_warning_when_group_actually_shards():
29+
assert _unsharded_single_rank_warning(dp_world_size=8, data_parallel_group=None, env={"WORLD_SIZE": "8"}) is None
30+
31+
32+
def test_no_warning_when_explicit_dp_group_supplied():
33+
# An explicitly provided size-1 data_parallel_group is treated as intentional.
34+
sentinel_group = object()
35+
assert _unsharded_single_rank_warning(dp_world_size=1, data_parallel_group=sentinel_group, env={"WORLD_SIZE":
36+
"8"}) is None
37+
38+
39+
@pytest.mark.parametrize("bad", ["", "not-an-int", None])
40+
def test_malformed_world_size_does_not_raise(bad):
41+
assert _unsharded_single_rank_warning(dp_world_size=1, data_parallel_group=None, env={"WORLD_SIZE": bad}) is None

0 commit comments

Comments
 (0)