Description
compute_elastic_config (deepspeed/elasticity/elasticity.py) raises a bare ZeroDivisionError instead of a clear error when called with return_microbatch=True, no explicit world_size, and an elasticity config version other than 0.2 (e.g. the still-documented 0.1).
Root cause
def compute_elastic_config(ds_config: dict, target_deepspeed_version: str, world_size=0, return_microbatch=False):
world_size defaults to 0. Further down, two separate blocks divide final_batch_size by world_size:
if world_size > 0: # line 347 - correctly guarded
...
if final_batch_size // world_size % mbsz == 0: # line 355
...
if return_microbatch: # line 363 - NOT guarded on world_size
if float(elastic_config.version) == 0.2:
return final_batch_size, valid_gpus, candidate_microbatch_size
else:
micro_batch_size = None
for mbsz in sorted(list(set(elastic_config.micro_batches)), reverse=True):
if final_batch_size // world_size % mbsz == 0: # line 370 - world_size can be 0 here
micro_batch_size = mbsz
break
...
The first block (line 347) is correctly guarded by if world_size > 0:. The second block (line 363-376), reached when return_microbatch=True and world_size was never set, has no equivalent guard before reusing world_size as a divisor at line 370.
Version 0.2 configs don't hit this because _get_compatible_gpus_v02 (called earlier for version == 0.2) requires a concrete GPU count and raises a clear ElasticityConfigError ("...needs WORLD_SIZE to compute valid batch size...") long before reaching line 363 if world_size wasn't resolved. Version 0.1 (and anything else routed through the else branch) has no such requirement earlier in the function, so it computes final_batch_size/valid_gpus successfully and only fails at the very last step.
Reproduction
from deepspeed.elasticity import compute_elastic_config
ds_config = {
"elasticity": {
"enabled": True,
"max_train_batch_size": 2000,
"micro_batch_sizes": [2, 4, 6],
"min_gpus": 1,
"max_gpus": 10000,
"min_time": 20,
"version": 0.1,
}
}
compute_elastic_config(ds_config, target_deepspeed_version="0.19.3", return_microbatch=True)
Actual:
ZeroDivisionError: integer division or modulo by zero
Expected: either a clear, actionable error (matching the v0.2 path's ElasticityConfigError), or a successful result if world_size isn't actually required for this branch.
Controls, both confirmed correct:
- Same call with an explicit
world_size=4 succeeds: (1680, [1, 2, ..., 840], 6).
- The same call shape against a
version: 0.2 config fails cleanly with ElasticityConfigError: Elasticity V 0.2 needs WORLD_SIZE to compute valid batch size... instead of crashing.
Why this is reachable
compute_elastic_config is a public entry point (re-exported from deepspeed/elasticity/__init__.py), documented as "Intended to be called both by scheduling infrastructure and deepspeed runtime" - callers invoking it before a world size is known (e.g. scheduling-time capacity checks) are exactly the documented use case. version: 0.1 is still shown as a supported value in the function's own docstring example, so this isn't a deprecated-path-only issue.
Suggested fix
Guard line 370 the same way line 347 is guarded (e.g. raise ElasticityConfigError when world_size <= 0 and return_microbatch=True for non-0.2 versions), or route the non-0.2 branch through the same WORLD_SIZE environment-variable resolution used in the 0.2 path (lines 316-329) before reaching this block.
Environment
Verified against deepspeedai/DeepSpeed master branch (current as of today), pure Python - the affected logic has no CUDA/GPU dependency, reproduced by loading the real, unmodified elasticity.py/config.py/constants.py directly (only the unrelated deepspeed.utils/deepspeed.git_version_info/deepspeed.elasticity.utils import chain was stubbed to avoid pulling in torch, none of the logic under test was modified or mocked).
Description
compute_elastic_config(deepspeed/elasticity/elasticity.py) raises a bareZeroDivisionErrorinstead of a clear error when called withreturn_microbatch=True, no explicitworld_size, and an elasticity config version other than0.2(e.g. the still-documented0.1).Root cause
world_sizedefaults to0. Further down, two separate blocks dividefinal_batch_sizebyworld_size:The first block (line 347) is correctly guarded by
if world_size > 0:. The second block (line 363-376), reached whenreturn_microbatch=Trueandworld_sizewas never set, has no equivalent guard before reusingworld_sizeas a divisor at line 370.Version
0.2configs don't hit this because_get_compatible_gpus_v02(called earlier forversion == 0.2) requires a concrete GPU count and raises a clearElasticityConfigError("...needs WORLD_SIZE to compute valid batch size...") long before reaching line 363 ifworld_sizewasn't resolved. Version0.1(and anything else routed through theelsebranch) has no such requirement earlier in the function, so it computesfinal_batch_size/valid_gpussuccessfully and only fails at the very last step.Reproduction
Actual:
Expected: either a clear, actionable error (matching the v0.2 path's
ElasticityConfigError), or a successful result ifworld_sizeisn't actually required for this branch.Controls, both confirmed correct:
world_size=4succeeds:(1680, [1, 2, ..., 840], 6).version: 0.2config fails cleanly withElasticityConfigError: Elasticity V 0.2 needs WORLD_SIZE to compute valid batch size...instead of crashing.Why this is reachable
compute_elastic_configis a public entry point (re-exported fromdeepspeed/elasticity/__init__.py), documented as "Intended to be called both by scheduling infrastructure and deepspeed runtime" - callers invoking it before a world size is known (e.g. scheduling-time capacity checks) are exactly the documented use case.version: 0.1is still shown as a supported value in the function's own docstring example, so this isn't a deprecated-path-only issue.Suggested fix
Guard line 370 the same way line 347 is guarded (e.g. raise
ElasticityConfigErrorwhenworld_size <= 0andreturn_microbatch=Truefor non-0.2versions), or route the non-0.2branch through the sameWORLD_SIZEenvironment-variable resolution used in the0.2path (lines 316-329) before reaching this block.Environment
Verified against
deepspeedai/DeepSpeedmasterbranch (current as of today), pure Python - the affected logic has no CUDA/GPU dependency, reproduced by loading the real, unmodifiedelasticity.py/config.py/constants.pydirectly (only the unrelateddeepspeed.utils/deepspeed.git_version_info/deepspeed.elasticity.utilsimport chain was stubbed to avoid pulling in torch, none of the logic under test was modified or mocked).