210 lines
6.5 KiB
Python
210 lines
6.5 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
|
|
import math
|
|
from pydantic import field_validator, model_validator
|
|
from deepspeed.runtime.config_utils import DeepSpeedConfigModel
|
|
from .fp16.loss_scaler import (
|
|
INITIAL_LOSS_SCALE,
|
|
SCALE_WINDOW,
|
|
DELAYED_SHIFT,
|
|
CONSECUTIVE_HYSTERESIS,
|
|
MIN_LOSS_SCALE,
|
|
)
|
|
|
|
#########################################
|
|
# BFLOAT16 support
|
|
#########################################
|
|
# BFLOAT16 feature. By default, this feature is not enabled.
|
|
# Users can configure in ds_config.json as below example:
|
|
BFLOAT16_FORMAT = '''
|
|
BFLOAT16 parameters should be of the format:
|
|
"bf16": {
|
|
"enabled": true,
|
|
"immediate_grad_update": false,
|
|
"check_grad_overflow": true
|
|
}
|
|
'''
|
|
BFLOAT16 = "bf16"
|
|
BFLOAT16_OLD = "bfloat16" # keeping for backwards compatibility
|
|
|
|
|
|
def get_bfloat16_config(param_dict):
|
|
bf16_config_dict = param_dict.get(BFLOAT16, None)
|
|
if bf16_config_dict is None:
|
|
bf16_config_dict = param_dict.get(BFLOAT16_OLD, {})
|
|
return DeepSpeedBF16Config(**bf16_config_dict)
|
|
|
|
|
|
class DeepSpeedBF16Config(DeepSpeedConfigModel):
|
|
"""
|
|
For bfloat16 configuration
|
|
"""
|
|
|
|
enabled: bool = False
|
|
"""
|
|
Enable bfloat16 mixed-precision training/inference
|
|
"""
|
|
|
|
immediate_grad_update: bool = False
|
|
"""
|
|
Apply gradient updates immediately rather than delayed.
|
|
"""
|
|
|
|
check_grad_overflow: bool = True
|
|
"""
|
|
Detect gradient overflow/underflow before the optimizer step and skip the step
|
|
when detected. Defaults to True to match the fp16 default. See issue #5242 and
|
|
PR #6976 for context.
|
|
"""
|
|
|
|
bf16_master_weights_and_grads: bool = False
|
|
"""
|
|
Maintain master weights/gradients in bf16 precision for ZeRO optimizer.
|
|
"""
|
|
|
|
bf16_optimizer_states: bool = False
|
|
"""
|
|
Keep optimizer states in bf16 (only valid when bf16_master_weights_and_grads is enabled).
|
|
"""
|
|
|
|
|
|
#########################################
|
|
# FP16 support
|
|
#########################################
|
|
# FP16 feature. By default, this feature is not enabled.
|
|
# Users can configure in ds_config.json as below example:
|
|
FP16_FORMAT = '''
|
|
FP16 parameters should be of the format:
|
|
"fp16": {
|
|
"enabled": true,
|
|
"auto_cast": false,
|
|
"loss_scale": 0,
|
|
"initial_scale_power": 16,
|
|
"loss_scale_window": 1000,
|
|
"hysteresis": 2,
|
|
"consecutive_hysteresis": false,
|
|
"min_loss_scale": 1
|
|
}
|
|
'''
|
|
FP16 = "fp16"
|
|
|
|
|
|
def get_float16_config(param_dict):
|
|
fp16_config_dict = param_dict.get(FP16, {})
|
|
return DeepSpeedFP16Config(**fp16_config_dict)
|
|
|
|
|
|
class DeepSpeedFP16Config(DeepSpeedConfigModel):
|
|
"""
|
|
For float16 configuration
|
|
"""
|
|
|
|
enabled: bool = False
|
|
"""
|
|
Enable fp16 mixed-precision training/inference
|
|
"""
|
|
|
|
auto_cast: bool = False
|
|
"""
|
|
Automatically cast inputs to fp16
|
|
"""
|
|
|
|
loss_scale: float = 0
|
|
"""
|
|
Loss scaling value. Default value of 0 means dynamic loss scaling instead of static loss scale.
|
|
"""
|
|
|
|
@field_validator("loss_scale", mode="before")
|
|
@classmethod
|
|
def _validate_loss_scale(cls, v):
|
|
if isinstance(v, bool):
|
|
raise ValueError("fp16.loss_scale must be a number, not bool")
|
|
try:
|
|
v = float(v)
|
|
except (TypeError, ValueError):
|
|
raise ValueError("fp16.loss_scale must be a number")
|
|
if not math.isfinite(v):
|
|
raise ValueError("fp16.loss_scale must be a finite number (not inf/-inf/nan)")
|
|
if v < 0:
|
|
raise ValueError("fp16.loss_scale must be >= 0 (0 enables dynamic loss scaling)")
|
|
return v
|
|
|
|
initial_scale_power: int = 16
|
|
"""
|
|
For dynamic loss scaling, set initial loss scale to 2^{initial_scale_power}.
|
|
"""
|
|
|
|
loss_scale_window: int = 1000
|
|
"""
|
|
Iteration intervals for raising/lowering dynamic loss scale value.
|
|
"""
|
|
|
|
hysteresis: int = 2
|
|
"""
|
|
Delay shift in dynamic loss scaling.
|
|
"""
|
|
|
|
consecutive_hysteresis: bool = False
|
|
"""
|
|
Refill hysteresis if iteration does not overflow/underflow.
|
|
"""
|
|
|
|
min_loss_scale: int = 1
|
|
"""
|
|
Minimum dynamic loss scale value.
|
|
"""
|
|
|
|
fp16_master_weights_and_grads: bool = False
|
|
"""
|
|
Maintain master weights in optimizer state as fp16 instead of fp32 (valid with DeepSpeedCPUAdam only).
|
|
"""
|
|
|
|
@field_validator("loss_scale_window", "min_loss_scale", mode="before")
|
|
@classmethod
|
|
def _reject_non_integer_scale_params(cls, v, info):
|
|
# Pydantic coerces bool to int (True -> 1, False -> 0) and floats to int,
|
|
# so a bool or non-finite value would silently pass the positivity check
|
|
# in _validate_dynamic_loss_scale_params. Reject those here before coercion.
|
|
field = f"fp16.{info.field_name}"
|
|
if isinstance(v, bool):
|
|
raise ValueError(f"{field} must be an integer, not bool")
|
|
if isinstance(v, float) and not math.isfinite(v):
|
|
raise ValueError(f"{field} must be a finite number (not inf/-inf/nan)")
|
|
try:
|
|
int(v)
|
|
except (TypeError, ValueError):
|
|
raise ValueError(f"{field} must be an integer")
|
|
return v
|
|
|
|
@model_validator(mode="after")
|
|
def _validate_dynamic_loss_scale_params(self):
|
|
# loss_scale_window and min_loss_scale only take effect when dynamic loss
|
|
# scaling is active, i.e. fp16 is enabled and loss_scale == 0 (see
|
|
# DeepSpeedEngine.dynamic_loss_scale). Validating them otherwise would
|
|
# reject valid static-loss-scale configs that carry unused values.
|
|
if self.enabled and self.loss_scale == 0:
|
|
# loss_scale_window is used as `stable_interval % scale_window` in
|
|
# DynamicLossScaler.update_scale, so 0 raises ZeroDivisionError.
|
|
if self.loss_scale_window <= 0:
|
|
raise ValueError(
|
|
"fp16.loss_scale_window must be > 0 when dynamic loss scaling is enabled (loss_scale=0)")
|
|
# min_loss_scale is the loss-scale floor, which collapses if <= 0.
|
|
if self.min_loss_scale <= 0:
|
|
raise ValueError("fp16.min_loss_scale must be > 0 when dynamic loss scaling is enabled (loss_scale=0)")
|
|
return self
|
|
|
|
def initial_dynamic_scale(self):
|
|
return 2**self.initial_scale_power
|
|
|
|
def dynamic_loss_scale_args(self):
|
|
return {
|
|
INITIAL_LOSS_SCALE: 2**self.initial_scale_power,
|
|
SCALE_WINDOW: self.loss_scale_window,
|
|
DELAYED_SHIFT: self.hysteresis,
|
|
CONSECUTIVE_HYSTERESIS: self.consecutive_hysteresis,
|
|
MIN_LOSS_SCALE: self.min_loss_scale,
|
|
}
|