Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions deepspeed/runtime/precision_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,26 @@ def _validate_loss_scale(cls, v):
raise ValueError("fp16.loss_scale must be >= 0 (0 enables dynamic loss scaling)")
return v

@field_validator("loss_scale_window", "min_loss_scale", mode="before")
@classmethod
def _validate_positive_dynamic_scale_param(cls, v, info):
# Both parameters drive dynamic loss scaling and must be strictly positive.
# loss_scale_window is used as `stable_interval % scale_window` in
# DynamicLossScaler.update_scale, so a value of 0 raises ZeroDivisionError,
# and min_loss_scale is the loss-scale floor, which collapses if <= 0.
name = info.field_name
if isinstance(v, bool):
raise ValueError(f"fp16.{name} must be a number, not bool")
try:
number = float(v)
except (TypeError, ValueError):
raise ValueError(f"fp16.{name} must be a number")
if not math.isfinite(number):
raise ValueError(f"fp16.{name} must be a finite number (not inf/-inf/nan)")
if number <= 0:
raise ValueError(f"fp16.{name} must be > 0")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate dynamic-scale validation on dynamic loss scaling

This rejects existing static-loss-scale configs that happen to carry loss_scale_window or min_loss_scale values such as 0, even though those fields are only used when dynamic loss scaling is enabled. I checked DeepSpeedEngine.dynamic_loss_scale() in deepspeed/runtime/engine.py, which returns true only when fp16.loss_scale == 0; with loss_scale > 0 the optimizer uses the static scale and these dynamic parameters are ignored, so failing config construction here is a compatibility regression for otherwise valid static fp16 setups.

Useful? React with 👍 / 👎.

return v

initial_scale_power: int = 16
"""
For dynamic loss scaling, set initial loss scale to 2^{initial_scale_power}.
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/runtime/test_precision_config_dynamic_scale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0

# DeepSpeed Team

import pytest
from pydantic import ValidationError

from deepspeed.runtime.precision_config import DeepSpeedFP16Config


@pytest.mark.parametrize("field", ["loss_scale_window", "min_loss_scale"])
@pytest.mark.parametrize("value", [0, -1, float("inf"), float("nan"), True])
def test_fp16_dynamic_scale_rejects_invalid_values(field, value):
with pytest.raises(ValidationError):
DeepSpeedFP16Config(**{field: value})


@pytest.mark.parametrize("field", ["loss_scale_window", "min_loss_scale"])
@pytest.mark.parametrize("value", [1, 1000, "2"])
def test_fp16_dynamic_scale_accepts_valid_values(field, value):
cfg = DeepSpeedFP16Config(**{field: value})
assert getattr(cfg, field) > 0


@pytest.mark.parametrize("field", ["loss_scale_window", "min_loss_scale"])
@pytest.mark.parametrize("value", [[], {}])
def test_fp16_dynamic_scale_invalid_type_has_clear_error(field, value):
with pytest.raises(ValidationError) as excinfo:
DeepSpeedFP16Config(**{field: value})
assert "must be a number" in str(excinfo.value)
Loading