5610 lines
272 KiB
Python
Executable File
5610 lines
272 KiB
Python
Executable File
# Copyright (c) Microsoft Corporation.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
|
|
import os
|
|
import re
|
|
import stat
|
|
import torch
|
|
import hashlib
|
|
from collections import defaultdict, OrderedDict, deque
|
|
from shutil import copyfile
|
|
import gc
|
|
|
|
from torch.nn.modules import Module
|
|
from torch.nn.parameter import Parameter
|
|
from torch.optim import Optimizer
|
|
from torch.optim.lr_scheduler import _LRScheduler
|
|
from torch._utils import _flatten_dense_tensors, _unflatten_dense_tensors
|
|
from contextlib import contextmanager
|
|
|
|
from typing import Callable, Dict, Union, Iterable, Container, List
|
|
|
|
import deepspeed
|
|
|
|
from deepspeed import comm as dist
|
|
from deepspeed.runtime.utils import see_memory_usage, DummyOptim, register_output_backward_hooks, check_internal_apis_for_count_used_parameters
|
|
from .zero.offload_config import OffloadDeviceEnum, OffloadStateTypeEnum
|
|
from deepspeed.runtime.base_optimizer import ZeROOptimizer
|
|
from deepspeed.runtime.zero.stage_1_and_2 import DeepSpeedZeroOptimizer
|
|
from deepspeed.runtime.zenflow.zenflow_stage_1_and_2 import ZenFlowZeroOptimizer
|
|
from deepspeed.runtime.zero.partition_parameters import ZeroParamStatus
|
|
from deepspeed.runtime.zero.utils import is_zero_supported_optimizer, ZeRORuntimeException
|
|
from deepspeed.runtime.zero.parameter_offload import DeepSpeedZeRoOffload, ZeROOrderedDict, ensure_zero_ordered_dict
|
|
from deepspeed.runtime.zero.config import ZERO_OPTIMIZATION
|
|
from deepspeed.runtime.zenflow.engine import (configure_zenflow, zenflow_step, is_zenflow_update_boundary,
|
|
sync_zenflow_optimizer_lr)
|
|
|
|
from deepspeed.runtime.fp16.fused_optimizer import FP16_Optimizer
|
|
from deepspeed.runtime.fp16.loss_scaler import LossScaleConfig, LossScaleProfile
|
|
from deepspeed.runtime.fp16.unfused_optimizer import FP16_UnfusedOptimizer
|
|
from deepspeed.runtime.bf16_optimizer import BF16_Optimizer
|
|
|
|
from deepspeed.linear.optimized_linear import LoRAOptimizedLinear
|
|
from deepspeed.module_inject.layers import GatherReplacedLayerParams, configure_tensor_parallel_runtime, collect_autotp_universal_checkpoint_info
|
|
from deepspeed.module_inject.auto_ep_folding import (clear_autoep_folding_gradient_corrected,
|
|
is_autoep_folding_gradient_corrected,
|
|
reduce_autoep_folding_gradient)
|
|
from deepspeed.runtime.config import DEEPSPEED_OPTIMIZERS, \
|
|
ADAGRAD_OPTIMIZER, ADAM_OPTIMIZER, ADAMW_OPTIMIZER, LAMB_OPTIMIZER, ONEBIT_ADAM_OPTIMIZER, ONEBIT_LAMB_OPTIMIZER, \
|
|
TORCH_ADAM_PARAM, ADAM_W_MODE, ADAM_W_MODE_DEFAULT, ZERO_ONE_ADAM_OPTIMIZER, MUADAM_OPTIMIZER, MUADAMW_OPTIMIZER, \
|
|
MUSGD_OPTIMIZER, LION_OPTIMIZER, MUON_OPTIMIZER
|
|
|
|
from deepspeed.runtime.model_checkpointing.constants import ValidationMode, \
|
|
CHECKPOINT_TAG_VALIDATION, CHECKPOINT_WRITER, CHECKPOINT_SERIALIZATION
|
|
|
|
from deepspeed.runtime.dataloader import DeepSpeedDataLoader
|
|
from deepspeed.runtime.zero.muon.muon_optimizer import MuonWithAuxAdam
|
|
from deepspeed.runtime.constants import \
|
|
ROUTE_TRAIN, ROUTE_PREDICT, ROUTE_EVAL, \
|
|
PLD_THETA, PLD_GAMMA, BFLOAT16, FP16, AMP, GRADIENT_ACCUMULATION_STEPS, \
|
|
DATA_PARALLEL_GROUP, GLOBAL_RANK, DDP_BFLOAT16
|
|
from deepspeed.runtime.zero.config import ZeroStageEnum
|
|
from deepspeed.compression import compression_scheduler
|
|
from deepspeed.compression.constants import \
|
|
WEIGHT_QUANTIZE_IN_FORWARD_ENABLED, \
|
|
WEIGHT_QUANTIZATION, SHARED_PARAMETERS, \
|
|
WEIGHT_QUANTIZE_ENABLED, \
|
|
WEIGHT_QUANTIZE_GROUPS, \
|
|
WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE, \
|
|
WEIGHT_QUANTIZE_CHANGE_RATIO, \
|
|
WEIGHT_QUANTIZE_TYPE, \
|
|
WEIGHT_QUANTIZE_ROUNDING, \
|
|
WEIGHT_QUANTIZE_VERBOSE, \
|
|
WEIGHT_QUANTIZE_KERNEL
|
|
from deepspeed.checkpoint.constants import (
|
|
AUTOEP_ZERO3_EXPERT_STATE_FORMAT_VERSION,
|
|
AUTOEP_ZERO3_EXPERT_STATE_FORMAT_VERSION_KEY,
|
|
AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY,
|
|
AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT,
|
|
EXPERT_PARAMETER_PATTERNS,
|
|
FOLDING_METADATA_KEY,
|
|
FROZEN_PARAM_FRAGMENTS,
|
|
OPTIMIZER_STATE_DICT,
|
|
UNIVERSAL_CHECKPOINT_INFO,
|
|
UNIVERSAL_CHECKPOINT_VERSION_KEY,
|
|
UNIVERSAL_CHECKPOINT_VERSION_VALUE,
|
|
)
|
|
from deepspeed.checkpoint.autoep_zero3_metadata import (
|
|
is_autoep_zero3_partitioned_entry,
|
|
validate_autoep_zero3_partitioned_metadata,
|
|
)
|
|
from deepspeed.checkpoint.utils import clone_tensors_for_torch_save
|
|
from deepspeed.checkpoint.ds_to_universal import dp_index_to_str
|
|
from deepspeed.runtime.sparse_tensor import SparseTensor
|
|
|
|
from deepspeed.runtime import lr_schedules
|
|
from deepspeed.utils import groups
|
|
from deepspeed.utils import logger, log_dist, log_dist_once, instrument_w_nvtx
|
|
from deepspeed.utils.torch import required_torch_version, is_functorch_transforming
|
|
from deepspeed.utils.z3_leaf_module import apply_zero_leaf_module_config
|
|
from deepspeed.utils.timer import NoopTimer, ThroughputTimer, SynchronizedWallClockTimer, \
|
|
FORWARD_MICRO_TIMER, BACKWARD_MICRO_TIMER, BACKWARD_INNER_MICRO_TIMER, BACKWARD_REDUCE_MICRO_TIMER, \
|
|
STEP_MICRO_TIMER, \
|
|
FORWARD_GLOBAL_TIMER, BACKWARD_GLOBAL_TIMER, BACKWARD_INNER_GLOBAL_TIMER, BACKWARD_REDUCE_GLOBAL_TIMER, \
|
|
STEP_GLOBAL_TIMER
|
|
from deepspeed.utils.debug import debug_extract_module_and_param_names, debug_clear_module_and_param_names
|
|
from deepspeed.monitor.monitor import MonitorMaster
|
|
from deepspeed.runtime.progressive_layer_drop import ProgressiveLayerDrop
|
|
from deepspeed.runtime.utils import clip_grad_norm_, compare_tensors_in_structures, maybe_loss_for_backward
|
|
from deepspeed.runtime.eigenvalue import Eigenvalue
|
|
from deepspeed.runtime.data_pipeline.constants import DATA_SAMPLING, \
|
|
DATA_ROUTING, DATA_SAMPLING_ENABLED, CURRICULUM_LEARNING, \
|
|
CURRICULUM_LEARNING_ENABLED, DATA_SAMPLING_NUM_WORKERS, RANDOM_LTD, \
|
|
RANDOM_LTD_ENABLED, RANDOM_LTD_LAYER_ID, RANDOM_LTD_LAYER_NUM, \
|
|
RANDOM_LTD_LAYER_TOKEN_LR_SCHEDULE, RANDOM_LTD_LAYER_TOKEN_LR_ENABLED, \
|
|
RANDOM_LTD_GLOBAL_BATCH_SIZE, RANDOM_LTD_MICRO_BATCH_SIZE, DATA_EFFICIENCY
|
|
from deepspeed.runtime.data_pipeline.curriculum_scheduler import CurriculumScheduler
|
|
from deepspeed.runtime.checkpoint_engine import (create_checkpoint_engine, TorchCheckpointEngine, CheckpointCommitInfo)
|
|
|
|
from deepspeed.runtime.data_pipeline.data_routing.scheduler import RandomLTDScheduler
|
|
from deepspeed.runtime.data_pipeline.data_routing.helper import remove_random_ltd_state_dict
|
|
from deepspeed.runtime.data_pipeline.data_routing.basic_layer import RandomLayerTokenDrop
|
|
|
|
from deepspeed.utils.zero_to_fp32 import get_fp32_state_dict_from_zero_checkpoint
|
|
from deepspeed.runtime.torch_autocast import init_autocast_params, get_default_autocast_lower_precision_modules, autocast_if_enabled
|
|
|
|
from .pipe.module import PipelineModule
|
|
from .utils import get_ma_status
|
|
from .compiler import is_compile_supported, compiled_autograd
|
|
from ..ops.adam import FusedAdam
|
|
from ..moe.sharded_moe import TopKGate, MOELayer
|
|
from ..moe.layer import MoE
|
|
from ..moe.utils import is_moe_param, configure_moe_param_groups
|
|
from ..git_version_info import version
|
|
|
|
from deepspeed.profiling.flops_profiler.profiler import FlopsProfiler
|
|
from deepspeed.utils.logging import print_json_dist, print_configuration, set_log_level_from_string
|
|
|
|
from deepspeed.accelerator import get_accelerator
|
|
|
|
from deepspeed.runtime.config import DtypeEnum
|
|
|
|
from deepspeed.compile.util import (is_deepcompile_supported, get_deepcompile_handle, deepcompile_backward_prologue,
|
|
deepcompile_backward_epilogue)
|
|
from deepspeed.compile.backend import register_compile_pass, opt_passes
|
|
from deepspeed.compile.passes import zero3_compile, prefetch, selective_gather, offload_adam_states
|
|
from deepspeed.compile.init_z1 import init_z1
|
|
from deepspeed.compile.init_z3 import init_z3
|
|
from deepspeed.compile.z3_eager_fallback import deepcompile_z3_forward_context
|
|
from deepspeed.compile.init_sp import init_autosp
|
|
|
|
MEMORY_OPT_ALLREDUCE_SIZE = 500000000
|
|
|
|
DeepSpeedOptimizerCallable = \
|
|
Callable[[Union[Iterable[Parameter], Dict[str, Iterable]]], Optimizer]
|
|
DeepSpeedSchedulerCallable = Callable[[Optimizer], _LRScheduler]
|
|
|
|
try:
|
|
import apex
|
|
from apex import amp
|
|
APEX_INSTALLED = True
|
|
except ImportError:
|
|
# Fail silently so we don't spam logs unnecessarily if user isn't using amp
|
|
APEX_INSTALLED = False
|
|
|
|
|
|
def split_half_float_double_sparse(tensors):
|
|
device_type = get_accelerator().device_name()
|
|
supported_types = get_accelerator().supported_dtypes()
|
|
|
|
for t in tensors:
|
|
assert t.dtype in supported_types, f"attempting to reduce an unsupported grad type: {t.dtype}"
|
|
|
|
sparse_tensor_buckets, dense_tensor_buckets = [], []
|
|
for i, dtype in enumerate(supported_types):
|
|
sparse_bucket, dense_bucket = [], []
|
|
for t in tensors:
|
|
if t.dtype == dtype:
|
|
if isinstance(t, SparseTensor):
|
|
sparse_bucket.append(t)
|
|
else:
|
|
dense_bucket.append(t)
|
|
if sparse_bucket:
|
|
sparse_tensor_buckets.append((dtype, sparse_bucket))
|
|
if dense_bucket:
|
|
dense_tensor_buckets.append((dtype, dense_bucket))
|
|
return sparse_tensor_buckets, dense_tensor_buckets
|
|
|
|
|
|
class EngineTimers(object):
|
|
r"""Wallclock timers for DeepSpeedEngine"""
|
|
|
|
def __init__(self, enable_micro_timers, enable_global_timers):
|
|
self.forward_timers = []
|
|
self.backward_timers = []
|
|
self.backward_inner_timers = []
|
|
self.backward_reduce_timers = []
|
|
self.step_timers = []
|
|
self.global_timers = []
|
|
self.micro_timers = []
|
|
|
|
if enable_micro_timers:
|
|
self.forward_timers += [FORWARD_MICRO_TIMER]
|
|
self.backward_timers += [BACKWARD_MICRO_TIMER]
|
|
self.backward_inner_timers += [BACKWARD_INNER_MICRO_TIMER]
|
|
self.backward_reduce_timers += [BACKWARD_REDUCE_MICRO_TIMER]
|
|
self.step_timers += [STEP_MICRO_TIMER]
|
|
self.micro_timers += [
|
|
FORWARD_MICRO_TIMER, BACKWARD_MICRO_TIMER, BACKWARD_INNER_MICRO_TIMER, BACKWARD_REDUCE_MICRO_TIMER,
|
|
STEP_MICRO_TIMER
|
|
]
|
|
|
|
if enable_global_timers:
|
|
self.forward_timers += [FORWARD_GLOBAL_TIMER]
|
|
self.backward_timers += [BACKWARD_GLOBAL_TIMER]
|
|
self.backward_inner_timers += [BACKWARD_INNER_GLOBAL_TIMER]
|
|
self.backward_reduce_timers += [BACKWARD_REDUCE_GLOBAL_TIMER]
|
|
self.step_timers += [STEP_GLOBAL_TIMER]
|
|
self.global_timers += [
|
|
FORWARD_GLOBAL_TIMER, BACKWARD_GLOBAL_TIMER, BACKWARD_INNER_GLOBAL_TIMER, BACKWARD_REDUCE_GLOBAL_TIMER,
|
|
STEP_GLOBAL_TIMER
|
|
]
|
|
|
|
def active_timers(self):
|
|
return self.micro_timers + self.global_timers
|
|
|
|
|
|
def _eigenvalue_summary_events(block_eigenvalue, global_samples):
|
|
return [(f"Train/Eigenvalues/ModelBlockParam_{i}", ev_value[0], global_samples)
|
|
for i, ev_value in enumerate(block_eigenvalue.values())]
|
|
|
|
|
|
class DeepSpeedEngine(Module):
|
|
r"""DeepSpeed engine for training."""
|
|
|
|
def __init__(self,
|
|
args,
|
|
model,
|
|
optimizer=None,
|
|
model_parameters=None,
|
|
training_data=None,
|
|
lr_scheduler=None,
|
|
mpu=None,
|
|
dist_init_required=None,
|
|
collate_fn=None,
|
|
config=None,
|
|
config_class=None,
|
|
mesh_device=None,
|
|
dont_change_device=False):
|
|
super(DeepSpeedEngine, self).__init__()
|
|
self.dont_change_device = dont_change_device
|
|
self.client_optimizer = optimizer
|
|
self.client_lr_scheduler = lr_scheduler
|
|
self.training_data = training_data
|
|
self.collate_fn = collate_fn
|
|
self.mpu = mpu
|
|
self.all_to_all_group = None
|
|
self.data_parallel_group = None
|
|
self.global_steps = 0
|
|
self.global_samples = 0
|
|
self.micro_steps = 0
|
|
self.skipped_steps = 0
|
|
self.gradient_average = True
|
|
self.warn_unscaled_loss = True
|
|
self.config = config
|
|
self._config = config_class
|
|
self.loaded_checkpoint_mp_world_size = None
|
|
self.loaded_checkpoint_dp_world_size = None
|
|
self.enable_backward_allreduce = True
|
|
self.inside_no_sync_ctxt = False
|
|
self.progressive_layer_drop = None
|
|
self.eigenvalue = None
|
|
self.block_eigenvalue = None
|
|
self.gas_boundary_ctr = 0
|
|
self.dist_backend = get_accelerator().communication_backend_name()
|
|
self.has_moe_layers = False
|
|
self.num_experts = []
|
|
self.gate_modules = []
|
|
self.moe_layers = []
|
|
self._step_applied = False
|
|
self._global_grad_norm = None
|
|
self.use_ds_comm = False # False --> Use torch.dist, True --> Use ds.comm backend.
|
|
self.checkpoint_engine = None
|
|
self.optimizer = None
|
|
self.basic_optimizer = None
|
|
self.lr_scheduler = None
|
|
|
|
self._is_gradient_accumulation_boundary = None
|
|
self.scale_wrt_gas = None
|
|
self.losses = None
|
|
self.mesh_device = mesh_device
|
|
self._autoep_folding_spec = None
|
|
self._autoep_folding_group_handles = None
|
|
|
|
# Flag to indicate that scale() was called before manual backward pass
|
|
self._manual_backward_expected = False
|
|
|
|
# for debug purposes - can then debug print: debug_get_module_name(module)
|
|
debug_extract_module_and_param_names(model)
|
|
|
|
if self.mesh_device:
|
|
groups.mesh_device = self.mesh_device
|
|
|
|
self._do_args_sanity_check(args)
|
|
self._configure_with_arguments(args, mpu)
|
|
self._do_sanity_check()
|
|
if self.log_level() is not None:
|
|
set_log_level_from_string(self.log_level())
|
|
self._configure_expert_parallel(model)
|
|
if self.autotp_size() > 1:
|
|
self._configure_tensor_parallel(model, self.tensor_parallel_config())
|
|
see_memory_usage("DeepSpeed Engine: After args sanity test", force=self.memory_breakdown())
|
|
if mpu is not None:
|
|
if self.elasticity_enabled():
|
|
if not self.is_elastic_model_parallel_supported():
|
|
assert not self.elasticity_enabled(), ("Elasticity is not currently supported"
|
|
" with model parallelism.")
|
|
|
|
self._set_distributed_vars(args)
|
|
|
|
dist.configure(self._config)
|
|
|
|
self.monitor = MonitorMaster(self._config.monitor_config)
|
|
|
|
see_memory_usage(
|
|
"DeepSpeed Engine: Before configure distributed model",
|
|
force=self.memory_breakdown(),
|
|
)
|
|
|
|
self.pipeline_parallelism = isinstance(model, PipelineModule)
|
|
|
|
self._deepcompile_active = False
|
|
|
|
# Configure distributed model
|
|
self._configure_distributed_model(model)
|
|
|
|
# These hooks should be disabled later if DeepCompile is not active.
|
|
self.module_forward_pre_hook = self._create_module_forward_pre_hook()
|
|
self.module_forward_post_hook = self._create_module_forward_post_hook()
|
|
|
|
# needed for zero_to_fp32 weights reconstruction to remap nameless data to state_dict
|
|
self.param_names = {param: name for name, param in model.named_parameters()}
|
|
|
|
self._get_model_parameters()
|
|
|
|
see_memory_usage("DeepSpeed Engine: After configure distributed model")
|
|
|
|
# Configure wall clock timers
|
|
self.timers = SynchronizedWallClockTimer()
|
|
# Throughput timer
|
|
self.tput_timer = ThroughputTimer(self._config.timers_config,
|
|
batch_size=self.train_batch_size(),
|
|
steps_per_output=self.steps_per_print(),
|
|
monitor_memory=False)
|
|
|
|
log_dist(f"DeepSpeed Flops Profiler Enabled: {self.flops_profiler_enabled()}", ranks=[0])
|
|
|
|
if self.flops_profiler_enabled():
|
|
self.flops_profiler = FlopsProfiler(self.module, self, self.flops_profiler_recompute_fwd_factor())
|
|
|
|
if training_data:
|
|
self.training_dataloader = self.deepspeed_io(training_data)
|
|
else:
|
|
self.training_dataloader = None
|
|
|
|
# Configure optimizer and scheduler
|
|
has_optimizer = False
|
|
|
|
if optimizer or self.optimizer_name():
|
|
has_optimizer = True
|
|
# If no parameters given by init default to module parameters
|
|
if model_parameters is None:
|
|
model_parameters = self.module.parameters()
|
|
|
|
# Convert model parameters from generator to list
|
|
if not isinstance(model_parameters, list):
|
|
model_parameters = list(model_parameters)
|
|
|
|
# grad scaler only for Z0 (no ZeRO) + fp16 + torch_autocast
|
|
# ZeRO1/2/3 optimizers have their own grad scaler logic
|
|
self.torch_autocast_z0_gradscaler = None
|
|
if self.torch_autocast_enabled():
|
|
init_autocast_params(self, self.torch_autocast_dtype(), self.torch_autocast_lower_precision_safe_modules())
|
|
if (not self.zero_optimization() and self.torch_autocast_dtype() == torch.float16):
|
|
self.torch_autocast_z0_gradscaler = torch.amp.GradScaler(device=get_accelerator().device_name())
|
|
|
|
self._configure_zenflow = lambda: configure_zenflow(self)
|
|
self._is_zenflow_update_boundary = lambda: is_zenflow_update_boundary(self)
|
|
self._zenflow_step = lambda lr_kwargs: zenflow_step(self, lr_kwargs)
|
|
self._sync_zenflow_optimizer_lr = lambda: sync_zenflow_optimizer_lr(self)
|
|
|
|
self._configure_zenflow()
|
|
|
|
if has_optimizer:
|
|
self._configure_optimizer(optimizer, model_parameters)
|
|
self._configure_lr_scheduler()
|
|
self._report_progress(0)
|
|
elif self.zero_optimization():
|
|
# no optim selected but zero is enabled
|
|
self.optimizer = self._configure_zero_optimizer(optimizer=None)
|
|
elif self.bfloat16_enabled():
|
|
self.optimizer = self._configure_bf16_optimizer(optimizer=None)
|
|
|
|
# Hook optimizer for snip_momentum pruning
|
|
if hasattr(model, 'pruners'):
|
|
from ..compression.helper import rewrite_optimizer_step
|
|
self.optimizer.pruners = model.pruners
|
|
rewrite_optimizer_step(self.optimizer)
|
|
|
|
# Bookkeeping for sparse support
|
|
self.sparse_tensor_module_names = set()
|
|
# if self.sparse_gradients_enabled():
|
|
for name, module in self.module.named_modules():
|
|
if isinstance(module, (torch.nn.Embedding, torch.nn.EmbeddingBag)) and self.sparse_gradients_enabled():
|
|
self.sparse_tensor_module_names.add(name + ".weight")
|
|
logger.info("Will convert {} to sparse tensor during training".format(name))
|
|
|
|
self._optimized_linear_offload_setup()
|
|
|
|
self.save_non_zero_checkpoint = False
|
|
self.save_zero_checkpoint = False
|
|
if not isinstance(self.optimizer, DeepSpeedZeRoOffload):
|
|
self._configure_checkpointing()
|
|
|
|
if self.eigenvalue_enabled():
|
|
self.eigenvalue = self._configure_eigenvalue()
|
|
|
|
if self.pld_enabled():
|
|
self.progressive_layer_drop = self._configure_progressive_layer_drop()
|
|
|
|
if self.curriculum_enabled_legacy():
|
|
self.curriculum_scheduler_legacy = self._configure_curriculum_scheduler_legacy()
|
|
|
|
if self.random_ltd_enabled():
|
|
random_ltd_config = self.random_ltd_config()
|
|
random_ltd_config[RANDOM_LTD_GLOBAL_BATCH_SIZE] = self.train_batch_size()
|
|
random_ltd_config[RANDOM_LTD_MICRO_BATCH_SIZE] = self.train_micro_batch_size_per_gpu()
|
|
self.random_ltd_scheduler = self._configure_random_ltd_scheduler(random_ltd_config)
|
|
|
|
# Engine timers
|
|
|
|
self.engine_timers = EngineTimers(enable_micro_timers=self.wall_clock_breakdown(),
|
|
enable_global_timers=self.wall_clock_breakdown()
|
|
or self.flops_profiler_enabled())
|
|
|
|
self.engine_timers_cache = {}
|
|
|
|
if self.global_rank == 0:
|
|
self._config.print("DeepSpeedEngine configuration")
|
|
if self.dump_state():
|
|
print_configuration(self, "DeepSpeedEngine")
|
|
|
|
# Use torch (un)flatten ops
|
|
self.flatten = _flatten_dense_tensors
|
|
self.unflatten = _unflatten_dense_tensors
|
|
|
|
self._is_compiled = False
|
|
if is_deepcompile_supported():
|
|
# Predefined compile passes
|
|
self.register_compile_pass(zero3_compile.NAME, zero3_compile.add_z3_gather_release)
|
|
self.register_compile_pass(prefetch.NAME, prefetch.schedule_prefetch)
|
|
self.register_compile_pass(selective_gather.NAME, selective_gather.selective_gather)
|
|
self.register_compile_pass(offload_adam_states.NAME, offload_adam_states.move_opt_states)
|
|
|
|
# We now support PyTorch style backward, but it relies on the counter in ZeRO optimizers.
|
|
# However, we need some internal APIs to count the number of only used parameters.
|
|
# So we only enable this feature when those internal APIs are available.
|
|
# Otherwise, we fallback to DeepSpeed style backward only.
|
|
# See `count_used_parameters_in_backward` for more details.
|
|
self._running_engine_backward = False
|
|
self._support_torch_style_backward = False
|
|
# Flag to control whether gradients should be scaled by gradient accumulation steps
|
|
self._scale_wrt_gas = True
|
|
if isinstance(self.optimizer, ZeROOptimizer) and check_internal_apis_for_count_used_parameters():
|
|
self._support_torch_style_backward = True
|
|
# These hooks are used for non-scalar backward support, such as `out.backward(out_grad)`,
|
|
# not for `engine.backward(loss)`. In this case, we need to ensure that the preprocessing
|
|
# and postprocessing around the backward call are handled correctly.
|
|
# However, we cannot use `register_full_backward_hook` for post-backward hooks.
|
|
# If none of the module inputs require gradients, `register_full_backward_hook` fires
|
|
# when the gradients of the module outputs are computed. Our gradient
|
|
# accumulation hooks are called later. But we want `_backward_post_hook` to be called
|
|
# only after all gradients have been computed.
|
|
# To handle this, the optimizer maintains a counter to track the number of gradients
|
|
# that have been computed. When all gradients are ready, it calls `_backward_post_hook`.
|
|
# See also: https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.register_full_backward_hook
|
|
self.optimizer.register_grad_acc_post_hook(self._backward_post_hook)
|
|
|
|
self._is_compiled_autograd_enabled = False
|
|
self._compile_kwargs = {}
|
|
|
|
if self.dist_backend is None:
|
|
self.enable_backward_allreduce = False
|
|
|
|
def _optimized_linear_offload_setup(self):
|
|
self.optimized_linear_base_weight_sharding = False
|
|
self.optimized_linear_lora_enabled = False
|
|
offload_ratio = None
|
|
for _, module in self.module.named_modules():
|
|
if isinstance(module, LoRAOptimizedLinear):
|
|
self.optimized_linear_lora_enabled = True
|
|
if offload_ratio is not None:
|
|
assert offload_ratio == module.lora_config.offload_ratio, \
|
|
"all lora_config offload ratios should be the same across the model"
|
|
offload_ratio = module.lora_config.offload_ratio
|
|
if module.zero_shards > 1:
|
|
# set attr so checkpoint saving can handle BWS properly
|
|
self.optimized_linear_base_weight_sharding = True
|
|
|
|
if offload_ratio is None:
|
|
# Nothing enabled, do nothing
|
|
return
|
|
|
|
total_params = 0
|
|
for _, p in self.module.named_parameters():
|
|
if hasattr(p, 'ds_optim_param'):
|
|
total_params += p.numel()
|
|
|
|
offload_limit = total_params * offload_ratio
|
|
logger.info(f'offloading {offload_ratio*100}% of eligible params, specifically {offload_limit} params')
|
|
total_offloaded = 0
|
|
for _, p in self.module.named_parameters():
|
|
if hasattr(p, 'ds_optim_param'):
|
|
if total_offloaded < offload_limit:
|
|
total_offloaded += p.numel()
|
|
p.ds_offload = True
|
|
p.offload()
|
|
else:
|
|
p.ds_offload = False
|
|
|
|
def _configure_expert_parallel(self, model):
|
|
"""Initialize AutoEP: detect MoE layers, create EP groups, replace with EP-enabled layers."""
|
|
autoep_config = self._config.expert_parallel_config
|
|
if autoep_config is None or not autoep_config.enabled:
|
|
return
|
|
|
|
from deepspeed.module_inject.auto_ep import AutoEP
|
|
from deepspeed.module_inject.auto_ep_config import validate_autoep_config, validate_autoep_post_detection
|
|
from deepspeed.module_inject.auto_ep_folding import build_folding_spec, validate_folding_global
|
|
|
|
ep_size = autoep_config.autoep_size
|
|
tp_size = self.autotp_size()
|
|
sp_size = self._autoep_sequence_parallel_world_size()
|
|
pp_size = 1
|
|
if self.mpu is not None:
|
|
from deepspeed.utils.bwc import bwc_pipeline_parallel_world_size, bwc_tensor_model_parallel_world_size
|
|
bwc_tp_size = bwc_tensor_model_parallel_world_size(self.mpu)
|
|
if bwc_tp_size != 1:
|
|
raise ValueError("AutoEP does not currently support tensor model parallelism from mpu "
|
|
f"(bwc_tensor_model_parallel_world_size={bwc_tp_size}). Disable tensor model "
|
|
"parallelism for this run; AutoEP+TP support is planned as follow-up work.")
|
|
pp_size = bwc_pipeline_parallel_world_size(self.mpu)
|
|
|
|
world_size = dist.get_world_size()
|
|
validate_autoep_config(autoep_config, world_size, pp_size, tp_size, sp_size)
|
|
folding_spec = build_folding_spec(
|
|
world_size=world_size,
|
|
pp_size=pp_size,
|
|
tp_size=max(tp_size, 1),
|
|
ep_size=ep_size,
|
|
etp_size=autoep_config.expert_tensor_parallel_size,
|
|
mp_mode="tp" if tp_size > 1 else "sp",
|
|
)
|
|
compile_config = getattr(self._config, "compile_config", None)
|
|
validate_folding_global(
|
|
folding_spec,
|
|
zero_stage=self.zero_optimization_stage(),
|
|
sp_size=sp_size,
|
|
deepcompile_enabled=bool(getattr(compile_config, "deepcompile", False)),
|
|
use_data_before_expert_parallel=self._config.use_data_before_expert_parallel_,
|
|
mpu=self.mpu,
|
|
autoep_enabled=autoep_config.enabled,
|
|
tp_preset=getattr(self._config.tensor_parallel_config, "preset_model", None),
|
|
ep_preset=autoep_config.preset_model,
|
|
zero_offload_optimizer=self.zero_offload_optimizer() is not None,
|
|
zero_offload_param=self.zero_offload_param() is not None,
|
|
)
|
|
self._autoep_folding_spec = folding_spec
|
|
|
|
# Create EP/EDP process groups
|
|
mp_size = max(tp_size, sp_size, 1)
|
|
mp_mode = "tp" if tp_size > 1 else "sp"
|
|
groups._create_expert_and_data_parallel(
|
|
expert_parallel_size_=ep_size,
|
|
mp_size=mp_size,
|
|
pp_size=pp_size,
|
|
mp_mode=mp_mode,
|
|
use_data_before_expert_parallel_=self._config.use_data_before_expert_parallel_,
|
|
folding_spec=folding_spec if tp_size > 1 else None,
|
|
)
|
|
|
|
# Derive EP rank
|
|
ep_group_name = f"ep_size_{ep_size}"
|
|
ep_group = groups._get_expert_parallel_group(ep_group_name)
|
|
ep_rank = dist.get_rank(group=ep_group)
|
|
|
|
# Detect and replace MoE layers
|
|
auto_ep = AutoEP(model, autoep_config)
|
|
specs = auto_ep.ep_parser()
|
|
|
|
if specs:
|
|
validate_autoep_post_detection(autoep_config, specs)
|
|
auto_ep.replace_moe_layers(specs, ep_size=ep_size, ep_rank=ep_rank)
|
|
logger.info(f"AutoEP: replaced {len(specs)} MoE layer(s) with ep_size={ep_size}")
|
|
|
|
# Re-tag optimizer flags for newly created AutoEP parameters
|
|
from deepspeed import set_optimizer_flags
|
|
set_optimizer_flags(self._config, model)
|
|
|
|
def _autoep_sequence_parallel_world_size(self):
|
|
if self.mpu is not None and hasattr(self.mpu, 'get_sequence_parallel_world_size'):
|
|
return self.mpu.get_sequence_parallel_world_size()
|
|
return groups._get_sequence_parallel_world_size()
|
|
|
|
def _configure_tensor_parallel(self, model, tp_config):
|
|
self._configure_tensor_parallel_states(model)
|
|
configure_tensor_parallel_runtime(tp_config)
|
|
self._apply_autotp_partitioning(model, tp_config)
|
|
|
|
def _configure_tensor_parallel_states(self, model):
|
|
"""
|
|
Configures the tensor parallel states for the model.
|
|
This includes setting up the tensor parallel groups, initializing the TP mesh,
|
|
and registering a pre-hook to ensure that the Dataloader inputs are consistent across ranks.
|
|
"""
|
|
self._set_client_model(model)
|
|
# sanity check
|
|
# currently, the compatibility between 'autotp' and 'zero > 1' has not been validated
|
|
assert self.zero_optimization_stage(
|
|
) <= 2, "Currently, the compatibility between 'autotp' and 'zero_stage = 3' has not been validated"
|
|
|
|
self.mpu = groups
|
|
self.mpu._init_tp_mesh_device(tensor_model_parallel_size=self.autotp_size())
|
|
|
|
self.first_dataloader_check = None
|
|
|
|
def check_dataloader_inputs_same_across_ranks(module, args, kwargs):
|
|
|
|
def broadcast_and_check(args, bcast_rank, bcast_group):
|
|
if isinstance(args, tuple):
|
|
args = list(args)
|
|
if len(args) > 0:
|
|
if self.mpu.get_tensor_model_parallel_rank() == 0:
|
|
_src_args = [args]
|
|
dist.broadcast_object_list(object_list=_src_args,
|
|
src=bcast_rank,
|
|
group=bcast_group,
|
|
device=torch.device(get_accelerator().current_device_name()))
|
|
# Rank 0 does not need to compare with itself
|
|
is_equal = True
|
|
else:
|
|
_src_args = [None]
|
|
dist.broadcast_object_list(object_list=_src_args,
|
|
src=bcast_rank,
|
|
group=bcast_group,
|
|
device=torch.device(get_accelerator().current_device_name()))
|
|
|
|
is_equal = compare_tensors_in_structures(args, _src_args[0])
|
|
|
|
equal_tensor = torch.tensor(is_equal,
|
|
dtype=self.communication_data_type,
|
|
device=torch.device(get_accelerator().current_device_name()))
|
|
dist.all_reduce(equal_tensor, group=bcast_group)
|
|
assert torch.equal(
|
|
equal_tensor,
|
|
torch.tensor(groups.get_tensor_model_parallel_world_size(),
|
|
dtype=self.communication_data_type,
|
|
device=torch.device(get_accelerator().current_device_name()))
|
|
), "Data inconsistency within the TP group. Please check the Dataloader implementation to ensure consistency."
|
|
|
|
bcast_rank = self.mpu.get_tensor_model_parallel_src_rank()
|
|
bcast_group = self.mpu.get_tensor_model_parallel_group()
|
|
|
|
broadcast_and_check(args, bcast_rank, bcast_group)
|
|
broadcast_and_check(kwargs, bcast_rank, bcast_group)
|
|
|
|
logger.info(":The Dataloader has passed the TP group consistency check.")
|
|
self.first_dataloader_check.remove()
|
|
|
|
self.first_dataloader_check = self.module.register_forward_pre_hook(check_dataloader_inputs_same_across_ranks,
|
|
prepend=True,
|
|
with_kwargs=True)
|
|
|
|
def _apply_autotp_partitioning(self, model, tp_config):
|
|
if getattr(model, "ds_autotp_parsed", False):
|
|
return
|
|
if get_accelerator().is_available() and self.local_rank >= 0:
|
|
get_accelerator().set_device(self.local_rank)
|
|
|
|
tp_size = self.autotp_size()
|
|
if tp_config.tensor_parallel.tp_size not in (1, tp_size):
|
|
raise ValueError(f"tensor_parallel.tp.tp_size ({tp_config.tensor_parallel.tp_size}) "
|
|
f"does not match tensor_parallel.autotp_size ({tp_size}).")
|
|
tp_config.tensor_parallel.tp_size = tp_size
|
|
if tp_config.tensor_parallel.tp_group is None:
|
|
tp_config.tensor_parallel.tp_group = groups.get_tensor_model_parallel_group()
|
|
|
|
from deepspeed.module_inject.auto_tp import AutoTP
|
|
|
|
# Tensor parallel priority: custom config > HF tp_plan > AutoTP
|
|
partition_config = None
|
|
if hasattr(tp_config, "get_partition_config_object"):
|
|
partition_config = tp_config.get_partition_config_object()
|
|
|
|
if partition_config is not None:
|
|
autotp = AutoTP(module=model,
|
|
all_reduce_linears=(),
|
|
prefix="",
|
|
state_dict=None,
|
|
linear_layer_setting=(torch.nn.Linear, torch.nn.Embedding),
|
|
orig_layer_impl=None,
|
|
keep_module_on_host=tp_config.keep_module_on_host,
|
|
partition_config=partition_config)
|
|
autotp.set_tensor_parallel_config(tp_size, tp_config.tensor_parallel.tp_group)
|
|
autotp.update_linear_policies()
|
|
autotp._replace_module(model)
|
|
setattr(model, UNIVERSAL_CHECKPOINT_INFO, collect_autotp_universal_checkpoint_info(model))
|
|
setattr(model, "ds_autotp_parsed", True)
|
|
return
|
|
|
|
if tp_size <= 1:
|
|
setattr(model, "ds_autotp_parsed", True)
|
|
return
|
|
|
|
model_config = getattr(model, "config", None)
|
|
from deepspeed.module_inject import replace_transformer_layer
|
|
|
|
from deepspeed.runtime.tensor_parallel.config import _get_hf_tp_plan
|
|
|
|
hf_tp_plan = _get_hf_tp_plan(model)
|
|
if hf_tp_plan:
|
|
from deepspeed.module_inject.tp_plan_converter import TPPlanConverter
|
|
from deepspeed.module_inject.autotp_config import AutoTPConfig
|
|
|
|
layer_specs = TPPlanConverter.convert(hf_tp_plan)
|
|
if layer_specs is not None:
|
|
logger.info(f"Using HuggingFace tp_plan with {len(layer_specs)} layer specifications")
|
|
tp_plan_config = AutoTPConfig(tp_size=tp_size, layer_specs=layer_specs)
|
|
autotp = AutoTP(
|
|
module=model,
|
|
all_reduce_linears=(),
|
|
prefix="",
|
|
state_dict=None,
|
|
linear_layer_setting=(torch.nn.Linear, torch.nn.Embedding),
|
|
orig_layer_impl=None,
|
|
keep_module_on_host=tp_config.keep_module_on_host,
|
|
partition_config=tp_plan_config,
|
|
)
|
|
autotp.set_tensor_parallel_config(tp_size, tp_config.tensor_parallel.tp_group)
|
|
autotp.update_linear_policies()
|
|
autotp._replace_module(model)
|
|
setattr(model, "ds_autotp_parsed", True)
|
|
return
|
|
|
|
parser_dict = AutoTP.tp_parser(model)
|
|
for client_module, injection_policy in parser_dict:
|
|
tp_config.injection_policy_tuple = injection_policy
|
|
replace_transformer_layer(client_module, model, None, tp_config, model_config)
|
|
|
|
setattr(model, UNIVERSAL_CHECKPOINT_INFO, collect_autotp_universal_checkpoint_info(model))
|
|
setattr(model, "ds_autotp_parsed", True)
|
|
|
|
def __del__(self):
|
|
try:
|
|
self.destroy()
|
|
except Exception as exc:
|
|
# Avoid destructor-time exceptions for partially initialized engines.
|
|
logger.debug("DeepSpeedEngine.__del__ cleanup skipped: %s", exc, exc_info=True)
|
|
|
|
def destroy(self):
|
|
optimizer = getattr(self, "optimizer", None)
|
|
if optimizer is not None and hasattr(optimizer, 'destroy'):
|
|
optimizer.destroy()
|
|
if self.is_deepcompile_active():
|
|
get_deepcompile_handle().cleanup()
|
|
debug_clear_module_and_param_names()
|
|
|
|
checkpoint_engine = getattr(self, "checkpoint_engine", None)
|
|
if checkpoint_engine is not None and checkpoint_engine.is_decoupled():
|
|
checkpoint_engine.cleanup()
|
|
|
|
def _get_model_parameters(self):
|
|
if self.autotuning_profile_model_info():
|
|
self.autotuning_model_info = {}
|
|
num_params = 0
|
|
trainable_num_params = 0
|
|
|
|
for p in self.module.parameters():
|
|
# since user code might call deepspeed.zero.Init() before deepspeed.initialize(), need to check the attribute to check if the parameter is partitioned in zero 3 already or not
|
|
n = 0
|
|
if hasattr(p, "ds_tensor"): # if the parameter is partitioned in zero 3
|
|
n += p.ds_numel
|
|
else: # if the parameter is not partitioned in zero 3 yet
|
|
n += p.numel()
|
|
num_params += n
|
|
if p.requires_grad:
|
|
trainable_num_params += n
|
|
if self.global_rank == 0:
|
|
self.autotuning_model_info["num_params"] = num_params * self.mp_world_size
|
|
self.autotuning_model_info["trainable_num_params"] = trainable_num_params * self.mp_world_size
|
|
|
|
logger.info(f"model parameter = {num_params}")
|
|
|
|
def get_batch_info(self):
|
|
"""Get all training batch related settings.
|
|
Returns:
|
|
train_batch_size (int): The effective training batch size. This is the amount of data
|
|
samples that leads to one step of model update.
|
|
train_micro_batch_size_per_gpu (int): Batch size to be processed by one GPU in one
|
|
step (without gradient accumulation).
|
|
gradient_accumulation_steps (int): Number of training steps to accumulate gradients
|
|
before averaging and applying them.
|
|
"""
|
|
return (
|
|
self.train_batch_size,
|
|
self.train_micro_batch_size_per_gpu,
|
|
self.gradient_accumulation_steps,
|
|
)
|
|
|
|
def set_train_batch_size(self, train_batch_size):
|
|
"""Adjust the global batch size by increasing or decreasing the number of
|
|
micro-batches (i.e., gradient accumulation steps). The size of each micro-batch
|
|
(i.e., ``train_micro_batch_size_per_gpu``) is not changed.
|
|
Args:
|
|
train_batch_size (int): The new global batch size for training.
|
|
Raises:
|
|
ValueError: if ``train_batch_size`` is not divisible by the
|
|
configured micro-batch size and data parallelism.
|
|
"""
|
|
if train_batch_size % (self.train_micro_batch_size_per_gpu() * self.dp_world_size) != 0:
|
|
#print(f'{train_batch_size=} {self.train_micro_batch_size_per_gpu()=} {self.dp_world_size=}')
|
|
raise ValueError('Train batch size must be divisible by micro-batch data parallelism')
|
|
new_gas = train_batch_size // (self.train_micro_batch_size_per_gpu() * self.dp_world_size)
|
|
# overwrite config
|
|
self._config.train_batch_size = train_batch_size
|
|
self._config.gradient_accumulation_steps = new_gas
|
|
|
|
def set_train_micro_batch_size(self, micro_batch_size):
|
|
"""Adjust the micro batch size(i.e., the micro batch size in every data parallel group),
|
|
while keep the gradient accumulation steps the same.
|
|
Args:
|
|
micro_batch_size (int): The new micro batch size for training.
|
|
"""
|
|
# overwrite config
|
|
new_global_batch_size = micro_batch_size * self._config.gradient_accumulation_steps * self.dp_world_size
|
|
self._config.train_batch_size = new_global_batch_size
|
|
self._config.train_micro_batch_size_per_gpu = micro_batch_size
|
|
|
|
def set_data_post_process_func(self, post_process_func):
|
|
if self.training_dataloader is not None:
|
|
self.training_dataloader.post_process_func = post_process_func
|
|
|
|
def set_custom_curriculum_learning_schedule(self, schedule_func_dict):
|
|
if self.training_dataloader is not None and self.curriculum_learning_enabled():
|
|
self.training_dataloader.data_sampler.set_custom_curriculum_learning_schedule(schedule_func_dict)
|
|
|
|
def get_global_grad_norm(self) -> float:
|
|
"""Return the 2-norm of all gradients. If there is model parallelism,
|
|
the norm will be global.
|
|
The computed norm will be cached and reused until the next step() pass.
|
|
.. note::
|
|
In the presence of model parallelism, this is a collective call
|
|
and acts as a barrier among ``mpu.get_model_parallel_group()``.
|
|
Returns:
|
|
float: norm
|
|
"""
|
|
return self._global_grad_norm
|
|
|
|
def __getattr__(self, name):
|
|
"""
|
|
Pass through attributes defined in the model if they are not overridden by ds-engine.
|
|
"""
|
|
|
|
_module = {}
|
|
if "module" in self.__dict__:
|
|
_module = self.__dict__['module']
|
|
if name in dir(self):
|
|
return getattr(self, name)
|
|
elif name in dir(_module):
|
|
return getattr(_module, name)
|
|
else:
|
|
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
|
|
def checkpoint_serialization_enabled(self):
|
|
return self._config.checkpoint_config[CHECKPOINT_SERIALIZATION]
|
|
|
|
def checkpoint_writer_enabled(self):
|
|
return self._config.checkpoint_config[CHECKPOINT_WRITER] is not None
|
|
|
|
def checkpoint_tag_validation_enabled(self):
|
|
return self._config.checkpoint_config[CHECKPOINT_TAG_VALIDATION] != ValidationMode.IGNORE
|
|
|
|
def checkpoint_tag_validation_fail(self):
|
|
return self._config.checkpoint_config[CHECKPOINT_TAG_VALIDATION] == ValidationMode.FAIL
|
|
|
|
def elasticity_enabled(self):
|
|
return self._config.elasticity_enabled
|
|
|
|
def is_elastic_model_parallel_supported(self):
|
|
if self.elasticity_enabled():
|
|
# Add code for finding number of GPUs per node automatically
|
|
if self._config.num_gpus_per_node % self._config.elastic_model_parallel_size == 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def pld_enabled(self):
|
|
return self._config.pld_enabled
|
|
|
|
def pld_params(self):
|
|
return self._config.pld_params
|
|
|
|
def pld_theta(self):
|
|
return self.pld_params()[PLD_THETA]
|
|
|
|
def pld_gamma(self):
|
|
return self.pld_params()[PLD_GAMMA]
|
|
|
|
def eigenvalue_enabled(self):
|
|
return self._config.eigenvalue_enabled
|
|
|
|
def eigenvalue_verbose(self):
|
|
return self._config.eigenvalue_verbose
|
|
|
|
def eigenvalue_max_iter(self):
|
|
return self._config.eigenvalue_max_iter
|
|
|
|
def eigenvalue_tol(self):
|
|
return self._config.eigenvalue_tol
|
|
|
|
def eigenvalue_stability(self):
|
|
return self._config.eigenvalue_stability
|
|
|
|
def eigenvalue_gas_boundary_resolution(self):
|
|
return self._config.eigenvalue_gas_boundary_resolution
|
|
|
|
def eigenvalue_layer_name(self):
|
|
return self._config.eigenvalue_layer_name
|
|
|
|
def eigenvalue_layer_num(self):
|
|
return self._config.eigenvalue_layer_num
|
|
|
|
def curriculum_enabled_legacy(self):
|
|
return self._config.curriculum_enabled_legacy
|
|
|
|
def curriculum_params_legacy(self):
|
|
return self._config.curriculum_params_legacy
|
|
|
|
def data_efficiency_enabled(self):
|
|
return self._config.data_efficiency_enabled
|
|
|
|
def data_efficiency_config(self):
|
|
return self._config.data_efficiency_config
|
|
|
|
def data_sampling_enabled(self):
|
|
return self._config.data_efficiency_config[DATA_SAMPLING][DATA_SAMPLING_ENABLED]
|
|
|
|
def data_sampling_config(self):
|
|
return self._config.data_efficiency_config[DATA_SAMPLING]
|
|
|
|
def curriculum_learning_enabled(self):
|
|
return self._config.data_efficiency_config[DATA_SAMPLING][CURRICULUM_LEARNING][CURRICULUM_LEARNING_ENABLED]
|
|
|
|
def curriculum_learning_config(self):
|
|
return self._config.data_efficiency_config[DATA_SAMPLING][CURRICULUM_LEARNING]
|
|
|
|
def random_ltd_enabled(self):
|
|
return self._config.data_efficiency_config[DATA_ROUTING][RANDOM_LTD][RANDOM_LTD_ENABLED]
|
|
|
|
def random_ltd_config(self):
|
|
return self._config.data_efficiency_config[DATA_ROUTING][RANDOM_LTD]
|
|
|
|
def random_ltd_initialize(self):
|
|
assert self.random_ltd_enabled()
|
|
random_ltd_config = self.random_ltd_config()
|
|
random_ltd_queue = deque([x for x in sorted(random_ltd_config[RANDOM_LTD_LAYER_ID])])
|
|
count = 0
|
|
for name, layer in self.module.named_modules():
|
|
if isinstance(layer, RandomLayerTokenDrop):
|
|
if len(random_ltd_queue) != 0 and str(random_ltd_queue[0]) in name: ###[1,2,3]
|
|
layer.init_config(random_ltd_config, self.random_ltd_scheduler, count)
|
|
random_ltd_queue.popleft()
|
|
count += 1
|
|
|
|
if random_ltd_config[RANDOM_LTD_LAYER_NUM] != count:
|
|
raise ValueError(f'random_ltd_layer_num {random_ltd_config[RANDOM_LTD_LAYER_NUM]} must be \
|
|
equivalent to the len of random_ltd_layer_id {count}')
|
|
|
|
if random_ltd_config[RANDOM_LTD_LAYER_TOKEN_LR_SCHEDULE][RANDOM_LTD_LAYER_TOKEN_LR_ENABLED]:
|
|
assert self.client_lr_scheduler is None
|
|
raise ValueError('not yet support')
|
|
#self.lr_scheduler = lr_schedules.WarmupLayerTokenDecayLR(self.optimizer, self.random_ltd_scheduler)
|
|
|
|
def get_data_parallel_rank(self):
|
|
return groups.get_data_parallel_rank()
|
|
|
|
def get_tensor_parallel_rank(self):
|
|
return groups.get_tensor_model_parallel_rank()
|
|
|
|
def get_model_parallel_rank(self):
|
|
return groups.get_model_parallel_rank()
|
|
|
|
def get_sequence_parallel_group(self):
|
|
return self.seq_parallel_group
|
|
|
|
def wall_clock_breakdown(self):
|
|
return self._config.wall_clock_breakdown
|
|
|
|
def flops_profiler_enabled(self):
|
|
return self._config.flops_profiler_config.enabled or self.autotuning_enabled()
|
|
|
|
def flops_profiler_recompute_fwd_factor(self):
|
|
return self._config.flops_profiler_config.recompute_fwd_factor
|
|
|
|
def flops_profiler_profile_step(self):
|
|
step = self._config.flops_profiler_config.profile_step
|
|
if self._config.autotuning_config.enabled:
|
|
step = self.autotuning_start_profile_step()
|
|
return step
|
|
|
|
def flops_profiler_module_depth(self):
|
|
return self._config.flops_profiler_config.module_depth
|
|
|
|
def flops_profiler_top_modules(self):
|
|
return self._config.flops_profiler_config.top_modules
|
|
|
|
def flops_profiler_detailed(self):
|
|
if self._config.autotuning_config.enabled:
|
|
return False
|
|
return self._config.flops_profiler_config.detailed
|
|
|
|
def flops_profiler_output_file(self):
|
|
return self._config.flops_profiler_config.output_file
|
|
|
|
def memory_breakdown(self):
|
|
return self._config.memory_breakdown
|
|
|
|
def autotuning_enabled(self):
|
|
return self._config.autotuning_config.enabled
|
|
|
|
def autotuning_start_profile_step(self):
|
|
return self._config.autotuning_config.start_profile_step
|
|
|
|
def autotuning_end_profile_step(self):
|
|
return self._config.autotuning_config.end_profile_step
|
|
|
|
def autotuning_metric_path(self):
|
|
path = self._config.autotuning_config.metric_path
|
|
if not path:
|
|
path = os.path.join(os.getcwd(), "autotuning_metric.json")
|
|
return path
|
|
|
|
def autotuning_model_info_path(self):
|
|
path = self._config.autotuning_config.model_info_path
|
|
if not path:
|
|
path = os.path.join(os.getcwd(), "autotuning_model_info.json")
|
|
return path
|
|
|
|
def autotuning_metric(self):
|
|
return self._config.autotuning_config.metric
|
|
|
|
def autotuning_profile_model_info(self):
|
|
return self.autotuning_enabled(
|
|
) and self._config.autotuning_config.model_info and self._config.autotuning_config.model_info.get(
|
|
"profile", False)
|
|
|
|
def sparse_gradients_enabled(self):
|
|
return self._config.sparse_gradients_enabled
|
|
|
|
def train_batch_size(self):
|
|
return self._config.train_batch_size
|
|
|
|
def train_micro_batch_size_per_gpu(self):
|
|
return self._config.train_micro_batch_size_per_gpu
|
|
|
|
def optimizer_name(self):
|
|
return (self.client_optimizer.__class__.__name__ if self.client_optimizer else self._config.optimizer_name)
|
|
|
|
def optimizer_params(self):
|
|
return self._config.optimizer_params
|
|
|
|
def optimizer_legacy_fusion(self):
|
|
return self._config.optimizer_legacy_fusion
|
|
|
|
def scheduler_name(self):
|
|
return self._config.scheduler_name
|
|
|
|
def scheduler_params(self):
|
|
return self._config.scheduler_params
|
|
|
|
def quantize_training(self):
|
|
return (
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS]
|
|
[WEIGHT_QUANTIZE_IN_FORWARD_ENABLED],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_ENABLED],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_GROUPS],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS]
|
|
[WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_CHANGE_RATIO],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_TYPE],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_ROUNDING],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_VERBOSE],
|
|
self._config.compression_config[WEIGHT_QUANTIZATION][SHARED_PARAMETERS][WEIGHT_QUANTIZE_KERNEL],
|
|
)
|
|
|
|
def zero_optimization(self):
|
|
return self._config.zero_enabled
|
|
|
|
def zero_allow_untested_optimizer(self):
|
|
return self._config.zero_allow_untested_optimizer
|
|
|
|
def zero_force_ds_cpu_optimizer(self):
|
|
return self._config.zero_force_ds_cpu_optimizer
|
|
|
|
def zero_reduce_scatter(self):
|
|
return self._config.zero_config.reduce_scatter
|
|
|
|
def zero_overlap_comm(self):
|
|
return self._config.zero_config.overlap_comm
|
|
|
|
def zero_offload_optimizer(self):
|
|
return self._config.zero_config.offload_optimizer
|
|
|
|
def zero_offload_param(self):
|
|
return self._config.zero_config.offload_param
|
|
|
|
def zero_use_cpu_optimizer(self):
|
|
if self._config.zero_config.offload_optimizer is not None:
|
|
return self._config.zero_config.offload_optimizer.device in [OffloadDeviceEnum.cpu, OffloadDeviceEnum.nvme]
|
|
return False
|
|
|
|
def zero_cpu_offload(self):
|
|
if self._config.zero_config.offload_optimizer is not None:
|
|
return self._config.zero_config.offload_optimizer.device == OffloadDeviceEnum.cpu
|
|
return False
|
|
|
|
def zero_partial_offload(self):
|
|
return getattr(self._config.zero_config.offload_optimizer, "ratio", 1.0)
|
|
|
|
def super_offload(self):
|
|
return getattr(self._config.zero_config.offload_optimizer, "super_offload", False)
|
|
|
|
def cpuadam_cores_perc(self):
|
|
return getattr(self._config.zero_config.offload_optimizer, "cpuadam_cores_perc", 0.9)
|
|
|
|
def zero_sub_group_size(self):
|
|
return self._config.zero_config.sub_group_size
|
|
|
|
def zero_optimization_stage(self):
|
|
return self._config.zero_optimization_stage
|
|
|
|
def compile_zero_optimization_stage(self):
|
|
"""Determines if zero-pass is set in deepcompile's passes attributes."""
|
|
return "z1" in self._config.compile_config.passes or "z3" in self._config.compile_config.passes
|
|
|
|
def compile_autosp(self):
|
|
"""Determines if AutoSP is set in deepcompile's passes attributes."""
|
|
return "autosp" in (getattr(self._config.compile_config, "passes", None) or [])
|
|
|
|
def mics_shard_size(self):
|
|
return self._config.mics_shard_size
|
|
|
|
def zero_reduce_bucket_size(self):
|
|
return self._config.zero_config.reduce_bucket_size
|
|
|
|
def zero_multi_rank_bucket_allreduce(self):
|
|
return self._config.zero_config.use_multi_rank_bucket_allreduce
|
|
|
|
def zero_allgather_bucket_size(self):
|
|
return self._config.zero_config.allgather_bucket_size
|
|
|
|
def zero_optimization_partition_gradients(self):
|
|
return self.zero_optimization_stage() >= ZeroStageEnum.gradients
|
|
|
|
def zero_optimization_partition_weights(self):
|
|
return self.zero_optimization_stage() >= ZeroStageEnum.weights
|
|
|
|
def is_first_weights_partition_group(self):
|
|
ret = True if self.mics_shard_size() < 0 \
|
|
and self.zero_optimization_partition_weights() else False
|
|
if self.mics_shard_size() > 0 and self.global_rank < self.mics_shard_size():
|
|
ret = True
|
|
return ret
|
|
|
|
def zero_contiguous_gradients(self):
|
|
return self._config.zero_config.contiguous_gradients
|
|
|
|
def zero_load_from_fp32_weights(self):
|
|
return self._config.zero_config.load_from_fp32_weights
|
|
|
|
def zero_elastic_checkpoint(self):
|
|
return self._config.zero_config.elastic_checkpoint
|
|
|
|
def zero_nvme_offload_optimizer(self):
|
|
return getattr(self.optimizer, "swap_optimizer", False)
|
|
|
|
def zero_max_live_parameters(self):
|
|
return self._config.zero_config.max_live_parameters
|
|
|
|
def zero_max_reuse_distance(self):
|
|
return self._config.zero_config.max_reuse_distance
|
|
|
|
def zero_prefetch_bucket_size(self):
|
|
return self._config.zero_config.prefetch_bucket_size
|
|
|
|
def zero_module_granularity_threshold(self):
|
|
return self._config.zero_config.module_granularity_threshold
|
|
|
|
def zero_param_persistence_threshold(self):
|
|
return self._config.zero_config.param_persistence_threshold
|
|
|
|
def zero_model_persistence_threshold(self):
|
|
return self._config.zero_config.model_persistence_threshold
|
|
|
|
def zero_gather_16bit_weights_on_model_save(self):
|
|
return self._config.zero_config.gather_16bit_weights_on_model_save
|
|
|
|
def zero_grad_hooks(self):
|
|
return self._config.zero_config.grad_hooks
|
|
|
|
def zero_legacy_stage1(self):
|
|
return self._config.zero_config.legacy_stage1
|
|
|
|
def zero_ignore_unused_parameters(self):
|
|
return self._config.zero_config.ignore_unused_parameters
|
|
|
|
def zero_save_muon_momentum_buffer_in_memory(self):
|
|
return self._config.zero_config.save_muon_momentum_buffer_in_memory
|
|
|
|
def tensor_parallel_config(self):
|
|
return self._config.tensor_parallel_config
|
|
|
|
def autotp_size(self):
|
|
return self._config.tensor_parallel_config.autotp_size
|
|
|
|
def graph_harvesting(self):
|
|
return self._config.graph_harvesting
|
|
|
|
def fp16_enabled(self):
|
|
return self._config.float16_config.enabled
|
|
|
|
def bfloat16_enabled(self):
|
|
return self._config.bfloat16_config.enabled
|
|
|
|
def fp16_master_weights_and_gradients(self):
|
|
return self._config.float16_config.fp16_master_weights_and_grads
|
|
|
|
def bf16_master_weights_and_gradients(self):
|
|
return self._config.bfloat16_config.bf16_master_weights_and_grads
|
|
|
|
def bf16_optimizer_states(self):
|
|
return self._config.bfloat16_config.bf16_optimizer_states
|
|
|
|
def amp_enabled(self):
|
|
return self._config.amp_enabled
|
|
|
|
def amp_params(self):
|
|
return self._config.amp_params
|
|
|
|
def torch_autocast_enabled(self) -> bool:
|
|
return self._config.torch_autocast_enabled
|
|
|
|
def torch_autocast_dtype(self) -> torch.dtype:
|
|
return self._config.torch_autocast_dtype
|
|
|
|
def torch_autocast_lower_precision_safe_modules(self) -> List[str]:
|
|
module_names = self._config.torch_autocast_lower_precision_safe_modules
|
|
return get_default_autocast_lower_precision_modules() if module_names is None else module_names
|
|
|
|
def fp16_auto_cast(self):
|
|
return self._config.float16_config.auto_cast
|
|
|
|
def loss_scale(self):
|
|
return self._config.float16_config.loss_scale
|
|
|
|
def gradient_accumulation_steps(self):
|
|
return self._config.gradient_accumulation_steps
|
|
|
|
def use_node_local_storage(self):
|
|
return self._config.use_node_local_storage
|
|
|
|
def load_universal_checkpoint(self):
|
|
return self._config.load_universal_checkpoint
|
|
|
|
def log_level(self):
|
|
return self._config.log_level
|
|
|
|
@property
|
|
def communication_data_type(self):
|
|
res = self._config.communication_data_type
|
|
if res is not None:
|
|
return res
|
|
|
|
if self.fp16_enabled():
|
|
return torch.float16
|
|
|
|
if self.bfloat16_enabled():
|
|
return torch.bfloat16
|
|
|
|
return torch.float32
|
|
|
|
@communication_data_type.setter
|
|
def communication_data_type(self, value):
|
|
self._config.communication_data_type = value
|
|
|
|
def postscale_gradients(self):
|
|
return not self._config.prescale_gradients
|
|
|
|
def gradient_predivide_factor(self):
|
|
return self._config.gradient_predivide_factor
|
|
|
|
def steps_per_print(self):
|
|
return self._config.steps_per_print
|
|
|
|
def zero_allgather_partitions(self):
|
|
return self._config.zero_config.allgather_partitions
|
|
|
|
def zero_round_robin_gradients(self):
|
|
return self._config.zero_config.round_robin_gradients
|
|
|
|
def zero_hpz_partition_size(self):
|
|
return self._config.zero_config.zero_hpz_partition_size
|
|
|
|
def zero_quantized_weights(self):
|
|
return self._config.zero_config.zero_quantized_weights
|
|
|
|
def zero_quantized_nontrainable_weights(self):
|
|
return self._config.zero_config.zero_quantized_nontrainable_weights
|
|
|
|
def zero_quantized_gradients(self):
|
|
return self._config.zero_config.zero_quantized_gradients
|
|
|
|
def zeropp_loco_param(self):
|
|
return self._config.zero_config.zeropp_loco_param
|
|
|
|
def zero_log_trace_cache_warnings(self):
|
|
return self._config.zero_config.log_trace_cache_warnings
|
|
|
|
def zero_allgather_sequential(self):
|
|
return self._config.zero_config.allgather_sequential
|
|
|
|
def is_sanity_checks_enabled(self):
|
|
return self._config.zero_config.enable_sanity_checks
|
|
|
|
def dump_state(self):
|
|
return self._config.dump_state
|
|
|
|
def gradient_clipping(self):
|
|
return self._config.gradient_clipping
|
|
|
|
def dynamic_loss_scale(self):
|
|
return self._config.float16_config.loss_scale == 0
|
|
|
|
def initial_dynamic_scale(self):
|
|
return self._config.float16_config.initial_dynamic_scale()
|
|
|
|
def dynamic_loss_scale_args(self):
|
|
return self._config.float16_config.dynamic_loss_scale_args()
|
|
|
|
def swap_tensor_config(self):
|
|
return self._config.swap_tensor_config
|
|
|
|
def aio_config(self):
|
|
return self._config.aio_config
|
|
|
|
def zenflow_config(self):
|
|
return self._config.zero_config.zenflow
|
|
|
|
def get_data_types(self):
|
|
model_dtype = torch.float32
|
|
if self.fp16_enabled():
|
|
model_dtype = torch.float16
|
|
elif self.bfloat16_enabled():
|
|
model_dtype = torch.bfloat16
|
|
|
|
if self._config.grad_accum_dtype is None:
|
|
grad_accum_dtype = model_dtype
|
|
else:
|
|
grad_accum_dtype = DtypeEnum(self._config.grad_accum_dtype).value
|
|
return (model_dtype, grad_accum_dtype)
|
|
|
|
def _assert_valid_mixed_precision_config(self):
|
|
"""param_dtype, if set, must match the enabled fp16/bf16 mode.
|
|
|
|
The optimizer/master-weight/reduction paths derive the model dtype from
|
|
the fp16/bf16 flags, so a divergent param_dtype is unsafe.
|
|
"""
|
|
if self._config.param_dtype is None:
|
|
return
|
|
if self.fp16_enabled():
|
|
model_dtype = torch.half
|
|
elif self.bfloat16_enabled():
|
|
model_dtype = torch.bfloat16
|
|
else:
|
|
model_dtype = torch.float32
|
|
requested = DtypeEnum(self._config.param_dtype).value
|
|
assert requested == model_dtype, (f"data_types.param_dtype='{self._config.param_dtype}' conflicts with the "
|
|
f"enabled precision mode (model dtype {model_dtype}). Set the matching "
|
|
f"fp16/bf16 'enabled' flag or omit data_types.param_dtype.")
|
|
|
|
def _mixed_precision_dtypes(self):
|
|
"""Resolve (param_dtype, buffer_dtype) for the module cast.
|
|
|
|
param_dtype follows the enabled fp16/bf16 mode (None if neither).
|
|
buffer_dtype: config override, else None (buffers keep loaded dtype).
|
|
Mismatched param_dtype is rejected in _assert_valid_mixed_precision_config.
|
|
"""
|
|
if self.fp16_enabled():
|
|
param_dtype = torch.half
|
|
elif self.bfloat16_enabled():
|
|
param_dtype = torch.bfloat16
|
|
else:
|
|
param_dtype = None
|
|
|
|
buffer_dtype = None
|
|
if self._config.buffer_dtype is not None:
|
|
buffer_dtype = DtypeEnum(self._config.buffer_dtype).value
|
|
|
|
return param_dtype, buffer_dtype
|
|
|
|
def _cast_module_mixed_precision(self, param_dtype, buffer_dtype, is_zero_init_model):
|
|
"""Cast params to param_dtype; cast buffers only when buffer_dtype is set."""
|
|
# ZeRO-Init params are already at the configured dtype and partitioned, so
|
|
# the per-parameter cast applies only in the non-zero-init path.
|
|
if param_dtype is not None and not is_zero_init_model:
|
|
for p in self.module.parameters(recurse=True):
|
|
if p.is_floating_point() and p.dtype != param_dtype:
|
|
p.data = p.data.to(param_dtype)
|
|
|
|
# Buffers are never ZeRO-partitioned.
|
|
if buffer_dtype is not None:
|
|
for b in self.module.buffers(recurse=True):
|
|
if b.is_floating_point() and b.dtype != buffer_dtype:
|
|
b.data = b.data.to(buffer_dtype)
|
|
|
|
def _optimizer_has_ckpt_event_prologue(self):
|
|
return self.optimizer is not None and hasattr(self.optimizer, 'checkpoint_event_prologue')
|
|
|
|
def _optimizer_has_ckpt_event_epilogue(self):
|
|
return self.optimizer is not None and hasattr(self.optimizer, 'checkpoint_event_epilogue')
|
|
|
|
def _configure_lr_scheduler(self):
|
|
if self.client_lr_scheduler:
|
|
if isinstance(self.client_lr_scheduler, Callable):
|
|
log_dist('DeepSpeed using client callable to create LR scheduler', ranks=[0])
|
|
self.lr_scheduler = self.client_lr_scheduler(self.basic_optimizer)
|
|
else:
|
|
log_dist('DeepSpeed using client LR scheduler', ranks=[0])
|
|
self.lr_scheduler = self.client_lr_scheduler
|
|
else:
|
|
# load lr scheduler from json configuration if lr scheduler is not defined and passed in
|
|
lr_scheduler = self._scheduler_from_config(self.optimizer)
|
|
log_dist(f"DeepSpeed using configured LR scheduler = {self.scheduler_name()}", ranks=[0])
|
|
self.lr_scheduler = lr_scheduler
|
|
|
|
log_dist(f'DeepSpeed LR Scheduler = {self.lr_scheduler}', ranks=[0])
|
|
|
|
def _configure_checkpointing(self):
|
|
# Enable optimization to parallelize checkpointing of DP state
|
|
optimize_dp_state = not self.zero_optimization_partition_weights()
|
|
self.checkpoint_engine = create_checkpoint_engine(config_params=self._config,
|
|
groups=groups,
|
|
zero_stage=self.zero_optimization_stage(),
|
|
has_moe_layers=self.has_moe_layers,
|
|
optimize_dp_state=optimize_dp_state)
|
|
|
|
dp_rank = groups._get_sequence_data_parallel_rank()
|
|
rank = self.local_rank if self.use_node_local_storage() else dp_rank
|
|
|
|
# Determine if this data parallel process needs to store the model checkpoint
|
|
if self.checkpoint_engine.is_data_parallel_writer(rank) \
|
|
or (self.zero_optimization_partition_weights() and self.is_first_weights_partition_group()):
|
|
self.save_non_zero_checkpoint = True
|
|
|
|
if hasattr(self.optimizer, 'dp_process_group'):
|
|
param_rank = dist.get_rank(group=self.optimizer.dp_process_group)
|
|
|
|
# Only the first parameter parallel process needs to store the
|
|
# optimizer state checkpoints for zero
|
|
self.save_zero_checkpoint = param_rank == dp_rank
|
|
|
|
def _scheduler_from_config(self, optimizer):
|
|
scheduler_name = self.scheduler_name()
|
|
if scheduler_name is not None:
|
|
if hasattr(lr_schedules, scheduler_name):
|
|
scheduler = getattr(lr_schedules, scheduler_name)
|
|
else:
|
|
assert hasattr(torch.optim.lr_scheduler,
|
|
scheduler_name), f"DeepSpeed does not recognize LR scheduler {scheduler_name}"
|
|
|
|
scheduler = getattr(torch.optim.lr_scheduler, scheduler_name)
|
|
|
|
scheduler_params = self.scheduler_params()
|
|
instantiated_scheduler = scheduler(optimizer, **scheduler_params)
|
|
return instantiated_scheduler
|
|
else:
|
|
return None
|
|
|
|
def _set_distributed_vars(self, args):
|
|
device_rank = args.device_rank if args is not None and hasattr(args, 'device_rank') else self.local_rank
|
|
if device_rank >= 0:
|
|
get_accelerator().set_device(device_rank)
|
|
self.device = torch.device(get_accelerator().device_name(device_rank))
|
|
self.world_size = dist.get_world_size()
|
|
self.global_rank = dist.get_rank()
|
|
else:
|
|
self.world_size = 1
|
|
self.global_rank = 0
|
|
self.device = get_accelerator().device()
|
|
|
|
# Configure based on command line arguments
|
|
def _configure_with_arguments(self, args, mpu):
|
|
# After the distributed backend is initialized we are guaranteed the LOCAL_RANK
|
|
# environment variable is set. We must align args.local_rank to this value for
|
|
# backwards compatibility with scripts relying on [args|self].local_rank containing
|
|
# the correct local rank info. _do_args_sanity_check will ensure this is the case.
|
|
|
|
if "OMPI_COMM_WORLD_LOCAL_RANK" in os.environ:
|
|
ompi_local_rank = os.environ.get("OMPI_COMM_WORLD_LOCAL_RANK")
|
|
local_rank = os.environ.get('LOCAL_RANK', ompi_local_rank)
|
|
assert ompi_local_rank == local_rank, f"LOCAL_RANK ({local_rank}) != OMPI_COMM_WORLD_LOCAL_RANK ({ompi_local_rank}), " \
|
|
"not sure how to proceed as we're seeing conflicting local rank info."
|
|
os.environ['LOCAL_RANK'] = local_rank
|
|
|
|
self.local_rank = int(os.environ['LOCAL_RANK'])
|
|
if hasattr(args, 'local_rank'):
|
|
args.local_rank = self.local_rank
|
|
|
|
# Validate command line arguments
|
|
def _do_args_sanity_check(self, args):
|
|
assert "LOCAL_RANK" in os.environ or "OMPI_COMM_WORLD_LOCAL_RANK" in os.environ, "DeepSpeed requires the LOCAL_RANK environment " \
|
|
"variable, it is set by the deepspeed launcher, deepspeed.init_distributed, or the torch's launcher. If using a " \
|
|
"different launcher please ensure LOCAL_RANK is set prior to initializing deepspeed."
|
|
|
|
if hasattr(args, 'local_rank') and args.local_rank is not None:
|
|
assert isinstance(args.local_rank,
|
|
int), f"args.local_rank of {args.local_rank} is an unknown type {type(args.local_rank)}"
|
|
if args.local_rank >= 0:
|
|
env_local_rank = int(os.environ.get("LOCAL_RANK"))
|
|
assert (
|
|
env_local_rank == args.local_rank
|
|
), f"Mismatch in local rank setting, args.local_rank={args.local_rank} but env['LOCAL_RANK']={env_local_rank}."
|
|
|
|
def _is_supported_optimizer(self, optimizer_name):
|
|
return (optimizer_name in DEEPSPEED_OPTIMIZERS or getattr(torch.optim, optimizer_name, None) is not None)
|
|
|
|
def _supported_optims(self):
|
|
FairseqOptimizer = None
|
|
try:
|
|
from fairseq.optim.fairseq_optimizer import FairseqOptimizer
|
|
except ImportError:
|
|
pass
|
|
|
|
expected_optim_types = [Optimizer]
|
|
if FairseqOptimizer:
|
|
# fairseq optims are not torch.optim objects
|
|
expected_optim_types.append(FairseqOptimizer)
|
|
return expected_optim_types
|
|
|
|
# Validate configuration based on command line arguments
|
|
def _do_sanity_check(self):
|
|
if self.fp16_enabled() and not get_accelerator().is_fp16_supported():
|
|
raise ValueError("Type fp16 is not supported on your device.")
|
|
|
|
if self.bfloat16_enabled() and not get_accelerator().is_bf16_supported():
|
|
raise ValueError("Type bf16 is not supported on your device.")
|
|
|
|
self._assert_valid_mixed_precision_config()
|
|
|
|
expected_optim_types = self._supported_optims()
|
|
expected_optim_types += [type(None), Callable]
|
|
assert isinstance(self.client_optimizer, tuple(expected_optim_types)), \
|
|
f'Client Optimizer is of unexpected type {type(self.client_optimizer)}'
|
|
|
|
if not self.client_optimizer:
|
|
if self.optimizer_name() is not None:
|
|
assert self._is_supported_optimizer(
|
|
self.optimizer_name()), "{} is not a supported DeepSpeed Optimizer".format(self.optimizer_name())
|
|
|
|
if (self.optimizer_name() == LAMB_OPTIMIZER or self.optimizer_name() == ONEBIT_LAMB_OPTIMIZER):
|
|
assert (self.dynamic_loss_scale()), "DeepSpeed {} optimizer requires dynamic loss scaling".format(
|
|
self.optimizer_name())
|
|
|
|
# Detect invalid combinations of client optimizer and client scheduler
|
|
if isinstance(self.client_lr_scheduler, _LRScheduler):
|
|
assert isinstance(self.client_optimizer, Optimizer), \
|
|
f'Client Optimizer (type = {type(self.client_optimizer)} is not instantiated but Client LR Scheduler is instantiated'
|
|
|
|
def _broadcast_model(self):
|
|
if self.dist_backend is None:
|
|
return
|
|
|
|
def is_replicated(p):
|
|
if hasattr(p, "ds_status") and p.ds_status is not ZeroParamStatus.AVAILABLE:
|
|
return False
|
|
elif hasattr(p, 'ds_optim_param'):
|
|
# do not broadcast OptimizedLinear parameters, they are unique per base weight shard
|
|
return False
|
|
return True
|
|
|
|
for n, p in self.module.named_parameters():
|
|
# Broadcast the model for different parameters
|
|
if is_moe_param(p):
|
|
if torch.is_tensor(p) and is_replicated(p):
|
|
dist.broadcast(p.data,
|
|
groups._get_expert_broadcast_src_rank(p.group_name),
|
|
group=self.expert_data_parallel_group[p.group_name])
|
|
else:
|
|
if torch.is_tensor(p) and is_replicated(p):
|
|
dist.broadcast(p.data, groups._get_broadcast_src_rank(), group=self.seq_data_parallel_group)
|
|
|
|
@staticmethod
|
|
def __check_params(model: Module, dtype: torch.dtype) -> None:
|
|
return
|
|
|
|
def _set_client_model(self, model):
|
|
# register client model in _modules so that nn.module methods work correctly
|
|
modules = self.__dict__.get('_modules')
|
|
modules['module'] = model
|
|
# register module attribute in engine but avoid getattr
|
|
self.__dict__['module'] = model
|
|
|
|
def _configure_distributed_model(self, model):
|
|
self._set_client_model(model)
|
|
apply_zero_leaf_module_config(self.module, getattr(self._config.zero_config, "leaf_module", None))
|
|
is_zero_init_model = self.zero_optimization_partition_weights() and any(
|
|
[hasattr(param, "ds_id") for param in self.module.parameters()])
|
|
|
|
if self.fp16_enabled() or self.bfloat16_enabled():
|
|
check_dtype = torch.half if self.fp16_enabled() else torch.bfloat16
|
|
if is_zero_init_model:
|
|
self.__check_params(self.module, check_dtype)
|
|
# Cast params only; preserve fp32 buffers (e.g. rotary inv_freq)
|
|
# unless buffer_dtype is set. Replaces blanket module.half()/bfloat16().
|
|
param_dtype, buffer_dtype = self._mixed_precision_dtypes()
|
|
self._cast_module_mixed_precision(param_dtype, buffer_dtype, is_zero_init_model)
|
|
else:
|
|
self.__check_params(self.module, torch.float)
|
|
|
|
# zero.Init() handles device placement of model
|
|
if not (self.dont_change_device or is_zero_init_model):
|
|
self.module.to(self.device)
|
|
|
|
# MoE related initialization
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
for _, module in self.module.named_modules():
|
|
if isinstance(module, MoE):
|
|
self.has_moe_layers = True
|
|
self.num_experts.append(module.num_experts)
|
|
elif _AutoEPMoELayer is not None and isinstance(module, _AutoEPMoELayer):
|
|
self.has_moe_layers = True
|
|
self.num_experts.append(module.num_experts)
|
|
|
|
if self.has_moe_layers:
|
|
for _, module in self.module.named_modules():
|
|
if isinstance(module, TopKGate):
|
|
self.gate_modules.append(module)
|
|
if self.wall_clock_breakdown():
|
|
module.wall_clock_breakdown = True
|
|
if isinstance(module, MOELayer):
|
|
self.moe_layers.append(module)
|
|
if self.wall_clock_breakdown():
|
|
module.wall_clock_breakdown = True
|
|
|
|
# Pass the mpu from here to groups. For subsequent use, just query groups
|
|
if self.mpu is not None:
|
|
groups.mpu = self.mpu
|
|
|
|
folding_group_handles = None
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_folding import FoldingGroupHandles, local_folding_ranks
|
|
except ImportError:
|
|
FoldingGroupHandles = None
|
|
local_folding_ranks = None
|
|
if (FoldingGroupHandles is not None and self._autoep_folding_spec is not None
|
|
and self._autoep_folding_spec.tp_size > 1):
|
|
ep_group_name = f"ep_size_{self._autoep_folding_spec.ep_size}"
|
|
rank = dist.get_rank()
|
|
local_ranks = local_folding_ranks(rank, self._autoep_folding_spec)
|
|
folding_group_handles = FoldingGroupHandles(
|
|
spec=self._autoep_folding_spec,
|
|
tp_group=groups.get_tensor_model_parallel_group(),
|
|
dense_dp_group=groups._get_data_parallel_group(),
|
|
ep_group=groups._get_expert_parallel_group(ep_group_name),
|
|
edp_group=groups._get_expert_data_parallel_group(ep_group_name),
|
|
ep_group_name=ep_group_name,
|
|
tp_ranks=local_ranks["tp"],
|
|
dense_dp_ranks=local_ranks["dense_dp"],
|
|
ep_ranks=local_ranks["ep"],
|
|
edp_ranks=local_ranks["edp"],
|
|
)
|
|
self._autoep_folding_group_handles = folding_group_handles
|
|
|
|
# Set deepspeed parallelism spec. for the model including expert parallelism
|
|
for _, module in self.module.named_modules():
|
|
if _AutoEPMoELayer is not None and isinstance(module, _AutoEPMoELayer):
|
|
module.set_deepspeed_parallelism(self._config.use_data_before_expert_parallel_,
|
|
folding_group_handles=folding_group_handles)
|
|
elif hasattr(module, 'set_deepspeed_parallelism'):
|
|
module.set_deepspeed_parallelism(self._config.use_data_before_expert_parallel_)
|
|
|
|
# Query the groups module to get information about various parallel groups
|
|
self.local_all_to_all_group = None
|
|
if self.zero_quantized_gradients():
|
|
message = "Using LoCo quantized gradients" if self.zeropp_loco_param() else "Using quantized gradients"
|
|
log_dist(message, ranks=[0])
|
|
self.local_all_to_all_group = groups._get_local_all_to_all_group()
|
|
self.data_parallel_group = groups._get_data_parallel_group()
|
|
self.dp_world_size = groups._get_data_parallel_world_size()
|
|
self.seq_data_parallel_group = groups._get_sequence_data_parallel_group()
|
|
self.seq_dp_world_size = groups._get_sequence_data_parallel_world_size()
|
|
self.mp_world_size = groups._get_model_parallel_world_size()
|
|
self.expert_parallel_group = groups._get_expert_parallel_group_dict()
|
|
self.expert_data_parallel_group = groups._get_expert_data_parallel_group_dict()
|
|
self.sequence_parallel_size = groups._get_sequence_parallel_world_size()
|
|
if self.sequence_parallel_size > 1:
|
|
# Inserted Warning for PyTorch < 2.3
|
|
if not required_torch_version(min_version=2.3):
|
|
logger.warning(
|
|
"DeepSpeed Sequence Parallelism (Ulysses) with PyTorch < 2.3 may encounter "
|
|
"rank indexing errors during backward pass when sp_size < world_size. "
|
|
"Please use the weighted all-reduce workaround shown in the regression test "
|
|
"(https://github.com/deepspeedai/DeepSpeed/blob/master/tests/unit/sequence_parallelism/test_ulysses.py) "
|
|
"or upgrade to PyTorch 2.3+.")
|
|
self.communication_data_type = self._config.seq_parallel_communication_data_type
|
|
self.seq_parallel_group = groups._get_sequence_parallel_group()
|
|
|
|
if dist.get_rank() == 0:
|
|
summary = "********** distributed groups summary **********\n"
|
|
summary += f"\t {self.dp_world_size=}\n"
|
|
summary += f"\t {self.mp_world_size=}\n"
|
|
summary += f"\t {self.seq_dp_world_size=}\n"
|
|
summary += f"\t {self.sequence_parallel_size=}\n"
|
|
summary += "***********************************************"
|
|
logger.info(summary)
|
|
|
|
if not (self.amp_enabled() or is_zero_init_model):
|
|
self._broadcast_model()
|
|
|
|
def _validate_zero3_moe_compatibility(self):
|
|
if not self.has_moe_layers:
|
|
return
|
|
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
|
|
autoep_layers = []
|
|
native_moe_layers = []
|
|
for name, module in self.module.named_modules():
|
|
if isinstance(module, MoE):
|
|
native_moe_layers.append(name)
|
|
elif _AutoEPMoELayer is not None and isinstance(module, _AutoEPMoELayer):
|
|
autoep_layers.append(name)
|
|
|
|
if native_moe_layers:
|
|
raise AssertionError("Native DeepSpeed MoE is not supported with ZeRO Stage 3. "
|
|
"Use AutoEP or choose ZeRO stage 1/2.")
|
|
if not autoep_layers:
|
|
raise AssertionError("MoE not supported with Stage 3")
|
|
autotp_size = self.autotp_size()
|
|
if autotp_size not in (0, 1):
|
|
raise AssertionError("AutoEP with ZeRO Stage 3 does not support AutoTP yet "
|
|
f"(tensor_parallel.autotp_size={autotp_size}).")
|
|
if self.sequence_parallel_size != 1:
|
|
raise AssertionError("AutoEP with ZeRO Stage 3 does not support sequence parallelism yet "
|
|
f"(sequence_parallel_size={self.sequence_parallel_size}).")
|
|
if self.zero_quantized_gradients():
|
|
raise AssertionError("AutoEP with ZeRO Stage 3 does not support zero_quantized_gradients or LoCo "
|
|
"quantized gradients yet.")
|
|
mics_shard_size = getattr(self._config, "mics_shard_size", 0)
|
|
if mics_shard_size > 0:
|
|
raise AssertionError("AutoEP with ZeRO Stage 3 does not support MiCS yet "
|
|
f"(mics_shard_size={mics_shard_size}).")
|
|
hpz_partition_size = getattr(getattr(self._config, "zero_config", None), "zero_hpz_partition_size", 1)
|
|
if hpz_partition_size > 1:
|
|
raise AssertionError("AutoEP with ZeRO Stage 3 does not support hpZeRO secondary tensor groups yet "
|
|
f"(zero_optimization.zero_hpz_partition_size={hpz_partition_size}).")
|
|
|
|
expert_tp_size = getattr(self._config.expert_parallel_config, "expert_tensor_parallel_size", 1)
|
|
if expert_tp_size != 1:
|
|
raise AssertionError("AutoEP with ZeRO Stage 3 only supports "
|
|
"expert_parallel.expert_tensor_parallel_size=1.")
|
|
|
|
@staticmethod
|
|
def _is_same_process_group(group_a, group_b):
|
|
if group_a is group_b:
|
|
return True
|
|
if group_a is None or group_b is None:
|
|
return False
|
|
return dist.get_all_ranks_from_group(group_a) == dist.get_all_ranks_from_group(group_b)
|
|
|
|
def _resolve_zero3_param_placement(self):
|
|
for name, param in self.module.named_parameters():
|
|
family = getattr(param, "ds_zero_placement_family", "replicated")
|
|
if family == "autoep_expert":
|
|
group_name = getattr(param, "ds_zero_partition_group_name", getattr(param, "group_name", None))
|
|
if group_name is None:
|
|
raise AssertionError(f"AutoEP expert parameter '{name}' is missing a ZeRO partition group name.")
|
|
partition_group = groups._get_expert_data_parallel_group(group_name)
|
|
elif family == "replicated":
|
|
group_name = None
|
|
partition_group = self.seq_data_parallel_group
|
|
else:
|
|
raise AssertionError(f"Parameter '{name}' has unsupported ZeRO placement family '{family}'.")
|
|
|
|
if hasattr(param, "ds_id"):
|
|
# Already ZeRO-partitioned, e.g. converted under zero.Init.
|
|
# The partition group was fixed at conversion time and cannot
|
|
# be re-resolved here. An expert parameter partitioned over
|
|
# any other group would silently reduce-scatter different
|
|
# experts across the wrong ranks, so fail fast instead of
|
|
# recording placement metadata the partitioning does not match.
|
|
actual_group = getattr(param, "ds_process_group", None)
|
|
if family == "autoep_expert" and not self._is_same_process_group(actual_group, partition_group):
|
|
raise AssertionError(f"AutoEP expert parameter '{name}' was already ZeRO-partitioned over a "
|
|
"non-expert process group. Build the model so AutoEP expert parameters are "
|
|
"created by the engine transform instead of wrapping AutoEPMoELayer modules "
|
|
"directly in zero.Init.")
|
|
if actual_group is not None:
|
|
# Keep placement metadata consistent with the actual
|
|
# partitioning rather than the freshly resolved target.
|
|
partition_group = actual_group
|
|
|
|
param.ds_zero_placement_family = family
|
|
param.ds_zero_partition_group_name = group_name
|
|
param.ds_zero_partition_process_group = partition_group
|
|
param.ds_zero_partition_rank = dist.get_rank(group=partition_group)
|
|
param.ds_zero_partition_world_size = dist.get_world_size(group=partition_group)
|
|
|
|
# check if parameters are duplicated in optimizer param_groups
|
|
def _check_for_duplicates(self, optimizer):
|
|
for name, param in self.module.named_parameters():
|
|
param_id = id(param)
|
|
|
|
def ids_list(group):
|
|
return [id(param) for param in group]
|
|
|
|
occurrence = sum([
|
|
ids_list(group['params']).count(param_id) if param_id in ids_list(group['params']) else 0
|
|
for group in optimizer.param_groups
|
|
])
|
|
assert occurrence <= 1, f"Parameter with name: {name} occurs multiple times in optimizer.param_groups. Make sure it only appears once to prevent undefined behavior."
|
|
|
|
def _do_optimizer_sanity_check(self, basic_optimizer):
|
|
model_dtype, grad_accum_dtype = self.get_data_types()
|
|
zero_enabled = self.zero_optimization()
|
|
amp_enabled = self.amp_enabled()
|
|
# config based assertions
|
|
assert (
|
|
not (amp_enabled and zero_enabled)
|
|
), "Amp and ZeRO are not currently compatible, please use (legacy) fp16 mode which performs similar to amp opt_mode=O2"
|
|
if zero_enabled:
|
|
if not is_zero_supported_optimizer(basic_optimizer):
|
|
assert (
|
|
self.zero_allow_untested_optimizer()
|
|
), 'You are using an untested ZeRO Optimizer. Please add <"zero_allow_untested_optimizer": true> in the configuration file to use it.'
|
|
|
|
if self.global_rank == 0:
|
|
logger.warning("**** You are using ZeRO with an untested optimizer, proceed with caution *****")
|
|
if model_dtype == torch.bfloat16 and grad_accum_dtype == torch.float32 and self.zero_optimization_stage(
|
|
) == 1 and not self.zero_cpu_offload():
|
|
return BFLOAT16
|
|
return ZERO_OPTIMIZATION
|
|
elif amp_enabled:
|
|
if model_dtype != grad_accum_dtype:
|
|
raise NotImplementedError(
|
|
"Model data type and gradient accumulation data type must be equal to use Amp")
|
|
if model_dtype == torch.bfloat16 or model_dtype == torch.float16:
|
|
raise NotImplementedError("Cannot enable both amp with (legacy) fp16 or bfloat16 mode")
|
|
try:
|
|
logger.info("Initializing Apex amp from: {}".format(amp.__path__))
|
|
except NameError:
|
|
# If apex/amp is available it will be imported above
|
|
raise RuntimeError("Unable to import apex/amp, please make sure it is installed")
|
|
return AMP
|
|
# data type checks
|
|
elif model_dtype == grad_accum_dtype:
|
|
if model_dtype == torch.float32:
|
|
return None
|
|
if model_dtype == torch.bfloat16 and self.pipeline_parallelism:
|
|
logger.warning(
|
|
"**** BF16 gradient accumulation is not safe numerically with large number of accumulation steps, proceed with caution *****"
|
|
)
|
|
return BFLOAT16
|
|
return FP16 if model_dtype == torch.float16 else DDP_BFLOAT16
|
|
else:
|
|
raise NotImplementedError(f"unsupported mix of {model_dtype=} and {grad_accum_dtype=}")
|
|
|
|
return None
|
|
|
|
# Configure optimizer
|
|
def _configure_optimizer(self, client_optimizer, model_parameters):
|
|
if client_optimizer is None:
|
|
if self.has_moe_layers:
|
|
model_parameters = configure_moe_param_groups(model_parameters)
|
|
basic_optimizer = self._configure_basic_optimizer(model_parameters)
|
|
log_dist(f"Using DeepSpeed Optimizer param name {self.optimizer_name()} as basic optimizer", ranks=[0])
|
|
else:
|
|
if isinstance(client_optimizer, tuple(self._supported_optims())):
|
|
basic_optimizer = client_optimizer
|
|
log_dist('Using client Optimizer as basic optimizer', ranks=[0])
|
|
else:
|
|
basic_optimizer = client_optimizer(model_parameters)
|
|
log_dist('Using client callable to create basic optimizer', ranks=[0])
|
|
|
|
if (self.zero_use_cpu_optimizer() and not isinstance(basic_optimizer, deepspeed.ops.adam.DeepSpeedCPUAdam)
|
|
and not isinstance(basic_optimizer, deepspeed.ops.lion.DeepSpeedCPULion)):
|
|
if self.zero_force_ds_cpu_optimizer():
|
|
msg = f'You are using ZeRO-Offload with a client provided optimizer ({type(basic_optimizer)}) which in most cases will yield poor performance. Please either use deepspeed.ops.adam.DeepSpeedCPUAdam or set an optimizer in your ds-config (https://www.deepspeed.ai/docs/config-json/#optimizer-parameters). If you really want to use a custom optimizer w. ZeRO-Offload and understand the performance impacts you can also set <"zero_force_ds_cpu_optimizer": false> in your configuration file.'
|
|
raise ZeRORuntimeException(msg)
|
|
|
|
basic_optimizer.param_groups[:] = [pg for pg in basic_optimizer.param_groups if len(pg["params"]) != 0]
|
|
log_dist("Removing param_group that has no 'params' in the basic Optimizer", ranks=[0])
|
|
|
|
self._check_for_duplicates(basic_optimizer)
|
|
|
|
self.basic_optimizer = basic_optimizer
|
|
log_dist(f"DeepSpeed Basic Optimizer = {basic_optimizer.__class__.__name__}", ranks=[0])
|
|
|
|
optimizer_wrapper = self._do_optimizer_sanity_check(basic_optimizer)
|
|
|
|
if optimizer_wrapper == ZERO_OPTIMIZATION:
|
|
self.optimizer = self._configure_zero_optimizer(basic_optimizer)
|
|
elif optimizer_wrapper == AMP:
|
|
amp_params = self.amp_params()
|
|
log_dist(f"Initializing AMP with these params: {amp_params}", ranks=[0])
|
|
model, self.optimizer = amp.initialize(self.module, basic_optimizer, **amp_params)
|
|
self._set_client_model(model)
|
|
self._broadcast_model()
|
|
# TODO: maybe need to broadcast experts differently?
|
|
elif optimizer_wrapper in [FP16, DDP_BFLOAT16]:
|
|
lp_dtype = torch.float16 if optimizer_wrapper == FP16 else torch.bfloat16
|
|
self.optimizer = self._configure_fp16_optimizer(basic_optimizer, lp_dtype)
|
|
elif optimizer_wrapper == BFLOAT16:
|
|
self.optimizer = self._configure_bf16_optimizer(basic_optimizer)
|
|
else:
|
|
self.optimizer = basic_optimizer
|
|
|
|
self._configure_autoep_folding_optimizer_gradient_reduction()
|
|
log_dist("DeepSpeed Final Optimizer = {}".format(self.optimizer.__class__.__name__), ranks=[0])
|
|
|
|
self.compression_scheduler = self._configure_compression_scheduler()
|
|
self.quantizer = self._configure_quantization()
|
|
|
|
def _configure_autoep_folding_optimizer_gradient_reduction(self):
|
|
configure = getattr(self.optimizer, "configure_autoep_folding_tp_gradient_reduction", None)
|
|
if configure is None:
|
|
return
|
|
configure(getattr(self, "_autoep_folding_spec", None))
|
|
|
|
def _configure_basic_optimizer(self, model_parameters):
|
|
# Copy so the pop() calls below (torch_adam, adam_w_mode, fp32_optimizer_states) do not
|
|
# mutate the shared config dict returned by optimizer_params().
|
|
optimizer_parameters = dict(self.optimizer_params() or {})
|
|
# print(optimizer_parameters.keys())
|
|
if "max_grad_norm" in optimizer_parameters.keys():
|
|
raise ValueError(
|
|
"'max_grad_norm' is not supported as an optimizer parameter, please switch to using the deepspeed parameter 'gradient_clipping' see: https://www.deepspeed.ai/docs/config-json/#gradient-clipping for more details"
|
|
)
|
|
|
|
if self.optimizer_name() in [ADAM_OPTIMIZER, ADAMW_OPTIMIZER]:
|
|
torch_adam = optimizer_parameters.pop(TORCH_ADAM_PARAM, False)
|
|
adam_w_mode = optimizer_parameters.pop(ADAM_W_MODE, ADAM_W_MODE_DEFAULT)
|
|
|
|
# Optimizer name of Adam forces AdamW logic unless adam_w_mode is explicitly set
|
|
effective_adam_w_mode = self.optimizer_name() == ADAMW_OPTIMIZER or adam_w_mode
|
|
|
|
if torch_adam:
|
|
if not effective_adam_w_mode:
|
|
optimizer = torch.optim.Adam(model_parameters, **optimizer_parameters)
|
|
else:
|
|
optimizer = torch.optim.AdamW(model_parameters, **optimizer_parameters)
|
|
else:
|
|
if self.zero_use_cpu_optimizer():
|
|
from deepspeed.ops.adam import DeepSpeedCPUAdam, ZenFlowCPUAdam
|
|
CPUAdam = ZenFlowCPUAdam if self.zenflow else DeepSpeedCPUAdam
|
|
|
|
zenflow_kwargs = {'overlap_step': self.overlap_step} if self.zenflow else {}
|
|
# Pop so a user-supplied value does not collide with the keyword built below.
|
|
# None means the user did not set it, so no override warning is needed.
|
|
user_fp32_optimizer_states = optimizer_parameters.pop('fp32_optimizer_states', None)
|
|
if self.bf16_optimizer_states():
|
|
# bf16 moments are required so the offloaded state matches the bf16 master weights.
|
|
if user_fp32_optimizer_states:
|
|
logger.warning("bf16_optimizer_states is enabled; overriding fp32_optimizer_states "
|
|
"to False so CPU Adam moments are stored in bf16.")
|
|
fp32_optimizer_states = False
|
|
elif user_fp32_optimizer_states is None:
|
|
# Default preserves the pre-existing fp32 optimizer-state behavior.
|
|
fp32_optimizer_states = True
|
|
else:
|
|
fp32_optimizer_states = user_fp32_optimizer_states
|
|
optimizer = CPUAdam(model_parameters,
|
|
**optimizer_parameters,
|
|
adamw_mode=effective_adam_w_mode,
|
|
fp32_optimizer_states=fp32_optimizer_states,
|
|
**zenflow_kwargs)
|
|
else:
|
|
from deepspeed.ops.adam import FusedAdam
|
|
|
|
optimizer = FusedAdam(
|
|
model_parameters,
|
|
**optimizer_parameters,
|
|
adam_w_mode=effective_adam_w_mode,
|
|
)
|
|
|
|
elif self.optimizer_name() == ADAGRAD_OPTIMIZER:
|
|
if self.zero_use_cpu_optimizer():
|
|
from deepspeed.ops.adagrad import DeepSpeedCPUAdagrad
|
|
optimizer = DeepSpeedCPUAdagrad(model_parameters, **optimizer_parameters)
|
|
else:
|
|
optimizer = torch.optim.Adagrad(model_parameters, **optimizer_parameters)
|
|
elif self.optimizer_name() == LAMB_OPTIMIZER:
|
|
from deepspeed.ops.lamb import FusedLamb
|
|
|
|
optimizer = FusedLamb(model_parameters, **optimizer_parameters)
|
|
elif self.optimizer_name() == ONEBIT_ADAM_OPTIMIZER:
|
|
assert not self.zero_optimization(), "1bit-Adam is not compatible with ZeRO"
|
|
from deepspeed.runtime.fp16.onebit.adam import OnebitAdam
|
|
|
|
optimizer = OnebitAdam(model_parameters, self, **optimizer_parameters)
|
|
if not self.fp16_enabled():
|
|
logger.warning("Currently the convergence of 1-bit Adam is only verified under FP16")
|
|
elif self.optimizer_name() == ZERO_ONE_ADAM_OPTIMIZER:
|
|
assert not self.zero_optimization(), "0/1 Adam is not compatible with ZeRO"
|
|
from deepspeed.runtime.fp16.onebit.zoadam import ZeroOneAdam
|
|
|
|
optimizer = ZeroOneAdam(model_parameters, self, **optimizer_parameters)
|
|
if not self.fp16_enabled():
|
|
logger.warning('Currently the convergence of 0/1 Adam is only verified under FP16')
|
|
elif self.optimizer_name() == ONEBIT_LAMB_OPTIMIZER:
|
|
assert not self.zero_optimization(), "1bit-Lamb is not compatible with ZeRO"
|
|
from deepspeed.runtime.fp16.onebit.lamb import OnebitLamb
|
|
|
|
optimizer = OnebitLamb(model_parameters, self, **optimizer_parameters)
|
|
if not self.fp16_enabled():
|
|
logger.warning("Currently the convergence of 1-bit Lamb is only verified under FP16")
|
|
elif self.optimizer_name() == LION_OPTIMIZER:
|
|
if self.zero_use_cpu_optimizer():
|
|
from deepspeed.ops.lion import DeepSpeedCPULion
|
|
optimizer = DeepSpeedCPULion(model_parameters, **optimizer_parameters)
|
|
else:
|
|
from deepspeed.ops.lion import FusedLion
|
|
optimizer = FusedLion(model_parameters, **optimizer_parameters)
|
|
elif self.optimizer_name() == MUADAM_OPTIMIZER:
|
|
try:
|
|
from mup import MuAdam
|
|
except ImportError:
|
|
logger.error("Install mup to use MuAdam optimizer")
|
|
optimizer = MuAdam(model_parameters, **optimizer_parameters)
|
|
elif self.optimizer_name() == MUADAMW_OPTIMIZER:
|
|
try:
|
|
from mup import MuAdamW
|
|
except ImportError:
|
|
logger.error("Install mup to use MuAdamW optimizer")
|
|
optimizer = MuAdamW(model_parameters, **optimizer_parameters)
|
|
elif self.optimizer_name() == MUSGD_OPTIMIZER:
|
|
try:
|
|
from mup import MuSGD
|
|
except ImportError:
|
|
logger.error("Install mup to use MuSGD optimizer")
|
|
optimizer = MuSGD(model_parameters, **optimizer_parameters)
|
|
elif self.optimizer_name() == MUON_OPTIMIZER:
|
|
zero_stage = self.zero_optimization_stage()
|
|
# Flatten param group dicts (created by MoE/EP) into a raw parameter list
|
|
all_params = []
|
|
for item in model_parameters:
|
|
if isinstance(item, dict):
|
|
all_params.extend(item['params'])
|
|
else:
|
|
all_params.append(item)
|
|
if not all([hasattr(p, 'use_muon') for p in all_params]):
|
|
msg = "Muon optimizer is used, but the use_muon attribute is NOT configured for some of the model parameters, " \
|
|
"please set by `param.use_muon = True / False` for all params"
|
|
logger.error(msg)
|
|
muon_params = [p for p in all_params if p.use_muon and p.requires_grad]
|
|
non_muon_params = [p for p in all_params if (not p.use_muon) and p.requires_grad]
|
|
param_groups = []
|
|
if muon_params:
|
|
accepted_parameters = dict()
|
|
for key in ["lr", "momentum", "weight_decay", "muon_lr", "ns_method"]:
|
|
if key in optimizer_parameters:
|
|
if key == "muon_lr": # muon_lr will override lr
|
|
accepted_parameters['lr'] = optimizer_parameters[key]
|
|
else:
|
|
accepted_parameters[key] = optimizer_parameters[key]
|
|
param_groups.append(dict(params=muon_params, use_muon=True, name='muon-params', **accepted_parameters))
|
|
if non_muon_params:
|
|
accepted_parameters = dict()
|
|
for key in ["lr", "betas", "eps", "weight_decay", "adam_lr"]:
|
|
if key in optimizer_parameters:
|
|
if key == "adam_lr": # adam_lr will override lr
|
|
accepted_parameters['lr'] = optimizer_parameters[key]
|
|
else:
|
|
accepted_parameters[key] = optimizer_parameters[key]
|
|
param_groups.append(
|
|
dict(params=non_muon_params, use_muon=False, name='adam-params', **accepted_parameters))
|
|
if self.has_moe_layers:
|
|
from deepspeed.moe.utils import split_params_into_different_moe_groups_for_optimizer
|
|
param_groups = split_params_into_different_moe_groups_for_optimizer(param_groups)
|
|
optimizer = MuonWithAuxAdam(param_groups)
|
|
else:
|
|
torch_optimizer = getattr(torch.optim, self.optimizer_name())
|
|
optimizer = torch_optimizer(model_parameters, **optimizer_parameters)
|
|
return optimizer
|
|
|
|
def _configure_compression_scheduler(self):
|
|
return compression_scheduler(self.module, self._config.compression_config)
|
|
|
|
def _configure_random_ltd_scheduler(self, configs):
|
|
return RandomLTDScheduler(configs)
|
|
|
|
def _configure_quantization(self):
|
|
(
|
|
quantize_weight_in_forward,
|
|
quantize_enabled,
|
|
q_groups,
|
|
q_mixed_fp16,
|
|
q_change_ratio,
|
|
q_type,
|
|
q_rounding,
|
|
q_verbose,
|
|
use_quantizer_kernel,
|
|
) = self.quantize_training()
|
|
if quantize_enabled and not quantize_weight_in_forward:
|
|
assert self.fp16_enabled(
|
|
), "MoQ (quantize in optimization step) weight quantization is only supported for FP16"
|
|
quantizer = None
|
|
if quantize_enabled and not quantize_weight_in_forward:
|
|
from deepspeed.runtime.quantize import Quantizer
|
|
|
|
quantizer = Quantizer(
|
|
q_groups,
|
|
q_mixed_fp16,
|
|
q_change_ratio,
|
|
q_type,
|
|
q_rounding,
|
|
q_verbose,
|
|
self.eigenvalue_enabled(),
|
|
use_quantizer_kernel,
|
|
self.eigenvalue_layer_num() if self.eigenvalue_enabled() else 0,
|
|
)
|
|
return quantizer
|
|
|
|
def _configure_fp16_optimizer(self, optimizer, low_precision_dtype):
|
|
dynamic_loss_args = self.dynamic_loss_scale_args()
|
|
clip_grad = self.gradient_clipping()
|
|
|
|
if APEX_INSTALLED:
|
|
fused_opts = (apex.optimizers.FusedAdam, FusedAdam)
|
|
else:
|
|
fused_opts = FusedAdam
|
|
|
|
use_fused_optimizer = isinstance(optimizer, fused_opts) \
|
|
or self.optimizer_name() in [ONEBIT_ADAM_OPTIMIZER, ZERO_ONE_ADAM_OPTIMIZER]
|
|
loss_scale_profile = LossScaleProfile.FUSED if use_fused_optimizer else LossScaleProfile.UNFUSED
|
|
initial_dynamic_scale = self.initial_dynamic_scale() if loss_scale_profile == LossScaleProfile.FUSED else None
|
|
loss_scale_config = LossScaleConfig(
|
|
low_precision_dtype=low_precision_dtype,
|
|
dynamic_loss_scale=self.dynamic_loss_scale(),
|
|
static_loss_scale=self.loss_scale(),
|
|
dynamic_loss_args=dynamic_loss_args,
|
|
profile=loss_scale_profile,
|
|
initial_dynamic_scale=initial_dynamic_scale,
|
|
)
|
|
|
|
if use_fused_optimizer:
|
|
if loss_scale_config.dynamic_loss_scale:
|
|
log_dist('Creating fp16 optimizer with dynamic loss scale', ranks=[0])
|
|
else:
|
|
log_dist(f'Creating fp16 optimizer with static loss scale: {loss_scale_config.cur_scale}', ranks=[0])
|
|
timers = self.timers if self.wall_clock_breakdown() else NoopTimer()
|
|
optimizer = FP16_Optimizer(
|
|
optimizer,
|
|
deepspeed=self,
|
|
loss_scale_config=loss_scale_config,
|
|
low_precision_dtype=low_precision_dtype,
|
|
mpu=self.mpu,
|
|
clip_grad=clip_grad,
|
|
fused_adam_legacy=self.optimizer_legacy_fusion(),
|
|
timers=timers,
|
|
has_moe_layers=self.has_moe_layers,
|
|
)
|
|
else:
|
|
if loss_scale_config.dynamic_loss_scale:
|
|
log_dist('Creating fp16 unfused optimizer with dynamic loss scale', ranks=[0])
|
|
else:
|
|
log_dist(f'Creating fp16 unfused optimizer with static loss scale: {loss_scale_config.cur_scale}',
|
|
ranks=[0])
|
|
optimizer = FP16_UnfusedOptimizer(
|
|
optimizer,
|
|
deepspeed=self,
|
|
loss_scale_config=loss_scale_config,
|
|
low_precision_dtype=low_precision_dtype,
|
|
mpu=self.mpu,
|
|
clip_grad=clip_grad,
|
|
fused_lamb_legacy=self.optimizer_name() == LAMB_OPTIMIZER,
|
|
)
|
|
|
|
return optimizer
|
|
|
|
def _configure_bf16_optimizer(self, optimizer):
|
|
clip_grad = self.gradient_clipping()
|
|
|
|
if optimizer is None:
|
|
optimizer = DummyOptim(list(self.module.parameters()))
|
|
|
|
log_dist('Creating BF16 optimizer', ranks=[0])
|
|
|
|
timers = self.timers if self.wall_clock_breakdown() else NoopTimer()
|
|
optimizer = BF16_Optimizer(optimizer,
|
|
self.param_names,
|
|
bfloat16_config=self._config.bfloat16_config,
|
|
mpu=self.mpu,
|
|
clip_grad=clip_grad,
|
|
allgather_bucket_size=self.zero_allgather_bucket_size(),
|
|
dp_process_group=self.seq_data_parallel_group,
|
|
timers=timers,
|
|
grad_acc_dtype=self.get_data_types()[1],
|
|
graph_harvesting=self.graph_harvesting(),
|
|
has_moe_layers=self.has_moe_layers)
|
|
|
|
return optimizer
|
|
|
|
def _configure_zero_optimizer(self, optimizer):
|
|
zero_stage = self.zero_optimization_stage()
|
|
|
|
mics_shard_size = self.mics_shard_size()
|
|
model_dtype, gradient_accumulation_dtype = self.get_data_types()
|
|
|
|
if self.bfloat16_enabled():
|
|
check_grad_overflow = self._config.bfloat16_config.check_grad_overflow
|
|
elif self.fp16_enabled():
|
|
check_grad_overflow = True
|
|
else:
|
|
check_grad_overflow = False
|
|
|
|
timers = self.timers if self.wall_clock_breakdown() else NoopTimer()
|
|
|
|
if optimizer is None:
|
|
optimizer = DummyOptim(list(self.module.parameters()))
|
|
|
|
if self.zero_legacy_stage1():
|
|
raise Exception(
|
|
"The deprecated version of ZeRO Stage 1 is not supported in deepspeed >= 0.5.9. Please downgrade to a version less than 0.5.9 if you need to use this deprecated version of ZeRO."
|
|
)
|
|
|
|
if zero_stage <= ZeroStageEnum.gradients:
|
|
overlap_comm = self.zero_overlap_comm()
|
|
contiguous_gradients = self.zero_contiguous_gradients()
|
|
round_robin_gradients = self.zero_round_robin_gradients()
|
|
assert not isinstance(optimizer, DummyOptim), "zero stage {} requires an optimizer".format(zero_stage)
|
|
|
|
log_dist(f'Creating {model_dtype} ZeRO stage {zero_stage} optimizer', ranks=[0])
|
|
|
|
if isinstance(self.module, PipelineModule):
|
|
if overlap_comm:
|
|
logger.warning("Pipeline parallelism does not support overlapped communication, will be disabled.")
|
|
overlap_comm = False
|
|
Stage1And2ZeroOptimizer = DeepSpeedZeroOptimizer if not self.zenflow else ZenFlowZeroOptimizer.create(
|
|
zenflow_config=self.zenflow_config())
|
|
|
|
optimizer = Stage1And2ZeroOptimizer(
|
|
optimizer,
|
|
self.param_names,
|
|
timers=timers,
|
|
optimizer_params=self.optimizer_params(),
|
|
static_loss_scale=self.loss_scale(),
|
|
dynamic_loss_scale=self.dynamic_loss_scale(),
|
|
dynamic_loss_args=self.dynamic_loss_scale_args(),
|
|
clip_grad=self.gradient_clipping(),
|
|
contiguous_gradients=contiguous_gradients,
|
|
reduce_bucket_size=self.zero_reduce_bucket_size(),
|
|
use_multi_rank_bucket_allreduce=self.zero_multi_rank_bucket_allreduce(),
|
|
allgather_bucket_size=self.zero_allgather_bucket_size(),
|
|
dp_process_group=self.seq_data_parallel_group,
|
|
expert_parallel_group=self.expert_parallel_group if self.has_moe_layers else None,
|
|
expert_data_parallel_group=self.expert_data_parallel_group if self.has_moe_layers else None,
|
|
reduce_scatter=self.zero_reduce_scatter(),
|
|
overlap_comm=overlap_comm,
|
|
offload_optimizer_config=self.zero_offload_optimizer(),
|
|
zenflow_config=self.zenflow_config(),
|
|
mpu=self.mpu,
|
|
postscale_gradients=self.postscale_gradients(),
|
|
gradient_predivide_factor=self.gradient_predivide_factor(),
|
|
gradient_accumulation_steps=self.gradient_accumulation_steps(),
|
|
ignore_unused_parameters=self.zero_ignore_unused_parameters(),
|
|
partition_grads=zero_stage == ZeroStageEnum.gradients,
|
|
round_robin_gradients=round_robin_gradients,
|
|
has_moe_layers=self.has_moe_layers,
|
|
fp16_master_weights_and_gradients=self.fp16_master_weights_and_gradients(),
|
|
bf16_master_weights_and_gradients=self.bf16_master_weights_and_gradients(),
|
|
bf16_optimizer_states=self.bf16_optimizer_states(),
|
|
gradient_accumulation_dtype=gradient_accumulation_dtype,
|
|
communication_data_type=self.communication_data_type,
|
|
elastic_checkpoint=self.zero_elastic_checkpoint(),
|
|
check_grad_overflow=check_grad_overflow)
|
|
|
|
elif zero_stage == ZeroStageEnum.weights:
|
|
self._validate_zero3_moe_compatibility()
|
|
if self.has_moe_layers:
|
|
self._resolve_zero3_param_placement()
|
|
if isinstance(optimizer, DummyOptim):
|
|
log_dist("Creating ZeRO Offload", ranks=[0])
|
|
zero_param_parallel_group = groups._get_zero_param_intra_parallel_group()
|
|
if self.zero_hpz_partition_size() > 1 and zero_param_parallel_group is None:
|
|
self._set_zero_group_parallelism()
|
|
zero_param_parallel_group = groups._get_zero_param_intra_parallel_group()
|
|
optimizer = DeepSpeedZeRoOffload(
|
|
self.module,
|
|
timers=timers,
|
|
ds_config=self.config,
|
|
overlap_comm=self.zero_overlap_comm(),
|
|
prefetch_bucket_size=self.zero_prefetch_bucket_size(),
|
|
max_reuse_distance=self.zero_max_reuse_distance(),
|
|
max_live_parameters=self.zero_max_live_parameters(),
|
|
param_persistence_threshold=self.zero_param_persistence_threshold(),
|
|
model_persistence_threshold=self.zero_model_persistence_threshold(),
|
|
offload_param_config=self.zero_offload_param(),
|
|
mpu=self.mpu,
|
|
zero_param_parallel_group=zero_param_parallel_group,
|
|
zero_quantized_weights=self.zero_quantized_weights(),
|
|
zero_quantized_nontrainable_weights=self.zero_quantized_nontrainable_weights(),
|
|
zero_module_granularity_threshold=self.zero_module_granularity_threshold(),
|
|
log_trace_cache_warnings=self.zero_log_trace_cache_warnings(),
|
|
)
|
|
else:
|
|
log_dist(
|
|
f'Creating fp16 ZeRO stage {zero_stage} optimizer,'
|
|
f' MiCS is enabled {mics_shard_size>0},'
|
|
f' Hierarchical params gather {self._config.mics_hierarchial_params_gather}',
|
|
ranks=[0])
|
|
if mics_shard_size > 0:
|
|
return self._return_mics_optimizer(optimizer, timers)
|
|
|
|
if self.zero_allgather_sequential():
|
|
log_dist(f"If zero_allgather_sequential is True, set prefetch_bucket_size to 1", ranks=[0])
|
|
self._config.zero_config.prefetch_bucket_size = 1
|
|
|
|
log_dist(f'Creating {model_dtype} ZeRO stage {zero_stage} optimizer', ranks=[0])
|
|
from deepspeed.runtime.zero.stage3 import DeepSpeedZeroOptimizer_Stage3
|
|
from deepspeed.runtime.superoffload.superoffload_stage3 import SuperOffloadOptimizer_Stage3
|
|
Stage3ZeroOptimizer = DeepSpeedZeroOptimizer_Stage3 if not self.super_offload(
|
|
) else SuperOffloadOptimizer_Stage3
|
|
optimizer = Stage3ZeroOptimizer(
|
|
self.module,
|
|
optimizer,
|
|
self.param_names,
|
|
timers=timers,
|
|
ds_config=self.config,
|
|
static_loss_scale=self.loss_scale(),
|
|
dynamic_loss_scale=self.dynamic_loss_scale(),
|
|
dynamic_loss_args=self.dynamic_loss_scale_args(),
|
|
clip_grad=self.gradient_clipping(),
|
|
contiguous_gradients=self.zero_contiguous_gradients(),
|
|
reduce_bucket_size=self.zero_reduce_bucket_size(),
|
|
prefetch_bucket_size=self.zero_prefetch_bucket_size(),
|
|
max_reuse_distance=self.zero_max_reuse_distance(),
|
|
max_live_parameters=self.zero_max_live_parameters(),
|
|
param_persistence_threshold=self.zero_param_persistence_threshold(),
|
|
model_persistence_threshold=self.zero_model_persistence_threshold(),
|
|
dp_process_group=self.seq_data_parallel_group,
|
|
all2all_process_group=self.local_all_to_all_group,
|
|
reduce_scatter=self.zero_reduce_scatter(),
|
|
overlap_comm=self.zero_overlap_comm(),
|
|
offload_optimizer_config=self.zero_offload_optimizer(),
|
|
offload_param_config=self.zero_offload_param(),
|
|
zenflow_config=self.zenflow_config(),
|
|
sub_group_size=self.zero_sub_group_size(),
|
|
offload_ratio=self.zero_partial_offload(),
|
|
mpu=self.mpu,
|
|
postscale_gradients=self.postscale_gradients(),
|
|
gradient_predivide_factor=self.gradient_predivide_factor(),
|
|
gradient_accumulation_steps=self.gradient_accumulation_steps(),
|
|
aio_config=self.aio_config(),
|
|
gradient_accumulation_dtype=gradient_accumulation_dtype,
|
|
communication_data_type=self.communication_data_type,
|
|
fp16_master_weights_and_gradients=self.fp16_master_weights_and_gradients(),
|
|
bf16_master_weights_and_gradients=self.bf16_master_weights_and_gradients(),
|
|
bf16_optimizer_states=self.bf16_optimizer_states(),
|
|
zero_hpz_partition_size=self.zero_hpz_partition_size(),
|
|
zero_quantized_weights=self.zero_quantized_weights(),
|
|
zero_quantized_nontrainable_weights=self.zero_quantized_nontrainable_weights(),
|
|
zero_module_granularity_threshold=self.zero_module_granularity_threshold(),
|
|
zeropp_loco_param=self.zeropp_loco_param(),
|
|
log_trace_cache_warnings=self.zero_log_trace_cache_warnings(),
|
|
enable_sanity_checks=self.is_sanity_checks_enabled(),
|
|
cpuadam_cores_perc=self.cpuadam_cores_perc(),
|
|
save_muon_momentum_buffer_in_memory=self.zero_save_muon_momentum_buffer_in_memory(),
|
|
)
|
|
|
|
else:
|
|
raise NotImplementedError("ZeRO stage {} not implemented".format(zero_stage))
|
|
|
|
return optimizer
|
|
|
|
def _return_mics_optimizer(self, basic_optimizer, timers):
|
|
from deepspeed.runtime.zero.mics import MiCS_Optimizer
|
|
model_dtype, gradient_accumulation_dtype = self.get_data_types()
|
|
optimizer = MiCS_Optimizer(self.module,
|
|
basic_optimizer,
|
|
self.param_names,
|
|
timers=timers,
|
|
ds_config=self.config,
|
|
static_loss_scale=self.loss_scale(),
|
|
dynamic_loss_scale=self.dynamic_loss_scale(),
|
|
dynamic_loss_args=self.dynamic_loss_scale_args(),
|
|
clip_grad=self.gradient_clipping(),
|
|
contiguous_gradients=self.zero_contiguous_gradients(),
|
|
reduce_bucket_size=self.zero_reduce_bucket_size(),
|
|
prefetch_bucket_size=self.zero_prefetch_bucket_size(),
|
|
max_reuse_distance=self.zero_max_reuse_distance(),
|
|
max_live_parameters=self.zero_max_live_parameters(),
|
|
param_persistence_threshold=self.zero_param_persistence_threshold(),
|
|
model_persistence_threshold=self.zero_model_persistence_threshold(),
|
|
dp_process_group=self.seq_data_parallel_group,
|
|
reduce_scatter=self.zero_reduce_scatter(),
|
|
overlap_comm=self.zero_overlap_comm(),
|
|
offload_optimizer_config=self.zero_offload_optimizer(),
|
|
offload_param_config=self.zero_offload_param(),
|
|
sub_group_size=self.zero_sub_group_size(),
|
|
mpu=self.mpu,
|
|
postscale_gradients=self.postscale_gradients(),
|
|
gradient_predivide_factor=self.gradient_predivide_factor(),
|
|
gradient_accumulation_steps=self.gradient_accumulation_steps(),
|
|
aio_config=self.aio_config(),
|
|
gradient_accumulation_dtype=gradient_accumulation_dtype,
|
|
communication_data_type=self.communication_data_type,
|
|
fp16_master_weights_and_gradients=self.fp16_master_weights_and_gradients(),
|
|
bf16_master_weights_and_gradients=self.bf16_master_weights_and_gradients(),
|
|
bf16_optimizer_states=self.bf16_optimizer_states())
|
|
return optimizer
|
|
|
|
def _configure_eigenvalue(self):
|
|
eigenvalue = Eigenvalue(
|
|
verbose=self.eigenvalue_verbose(),
|
|
max_iter=self.eigenvalue_max_iter(),
|
|
tol=self.eigenvalue_tol(),
|
|
stability=self.eigenvalue_stability(),
|
|
gas_boundary_resolution=self.eigenvalue_gas_boundary_resolution(),
|
|
layer_name=self.eigenvalue_layer_name(),
|
|
layer_num=self.eigenvalue_layer_num(),
|
|
)
|
|
|
|
return eigenvalue
|
|
|
|
def _configure_progressive_layer_drop(self):
|
|
pld = ProgressiveLayerDrop(theta=self.pld_theta(), gamma=self.pld_gamma())
|
|
|
|
return pld
|
|
|
|
def _configure_curriculum_scheduler_legacy(self):
|
|
scheduler = CurriculumScheduler(self.curriculum_params_legacy())
|
|
return scheduler
|
|
|
|
@staticmethod
|
|
def is_map_style_dataset(obj):
|
|
return hasattr(obj, "__getitem__") and hasattr(obj, "__len__")
|
|
|
|
@staticmethod
|
|
def is_iterable_style_dataset(obj):
|
|
return isinstance(obj, torch.utils.data.IterableDataset) # hasattr(obj, "__iter__") should work as well
|
|
|
|
def dataloader_drop_last(self):
|
|
return self._config.dataloader_drop_last
|
|
|
|
def was_step_applied(self) -> bool:
|
|
"""Returns True if the latest ``step()`` produced in parameter updates.
|
|
Note that a ``False`` return is not an error condition. Steps are frequently
|
|
no-ops, such as between gradient accumulation boundaries or when overflows
|
|
occur.
|
|
Returns:
|
|
bool: Whether the latest ``step()`` modified model parameters.
|
|
"""
|
|
return self._step_applied
|
|
|
|
def deepspeed_io(self,
|
|
dataset,
|
|
batch_size=None,
|
|
route=ROUTE_TRAIN,
|
|
pin_memory=True,
|
|
data_sampler=None,
|
|
collate_fn=None,
|
|
num_local_io_workers=None):
|
|
if not (self.is_map_style_dataset(dataset) or self.is_iterable_style_dataset(dataset)):
|
|
raise ValueError("Training data must be a torch Dataset")
|
|
|
|
if batch_size is None:
|
|
batch_size = self.train_micro_batch_size_per_gpu()
|
|
|
|
if collate_fn is None:
|
|
collate_fn = self.collate_fn
|
|
|
|
# Currently we only use timer in train route
|
|
deepspeed_io_timer = None
|
|
if route == ROUTE_TRAIN:
|
|
deepspeed_io_timer = self.tput_timer
|
|
|
|
# If mpu is provided, forward world size and parallel rank to sampler.
|
|
data_parallel_world_size = self.dp_world_size
|
|
data_parallel_rank = self.global_rank
|
|
if self.mpu is not None:
|
|
data_parallel_world_size = self.mpu.get_data_parallel_world_size()
|
|
data_parallel_rank = self.mpu.get_data_parallel_rank()
|
|
|
|
if data_sampler is None and (route == ROUTE_PREDICT or route == ROUTE_EVAL):
|
|
data_sampler = torch.utils.data.DistributedSampler(
|
|
dataset,
|
|
num_replicas=data_parallel_world_size,
|
|
rank=data_parallel_rank,
|
|
shuffle=False,
|
|
)
|
|
|
|
deepspeed_dataloader_config = {}
|
|
if self.curriculum_learning_enabled():
|
|
deepspeed_dataloader_config = {
|
|
CURRICULUM_LEARNING: self.curriculum_learning_enabled(),
|
|
DATA_EFFICIENCY: self.data_efficiency_config(),
|
|
DATA_PARALLEL_GROUP: self.data_parallel_group,
|
|
GRADIENT_ACCUMULATION_STEPS: self.gradient_accumulation_steps(),
|
|
GLOBAL_RANK: self.global_rank,
|
|
DATA_SAMPLING_NUM_WORKERS: self.data_sampling_config()[DATA_SAMPLING_NUM_WORKERS]
|
|
}
|
|
return DeepSpeedDataLoader(dataset=dataset,
|
|
batch_size=batch_size,
|
|
pin_memory=pin_memory,
|
|
collate_fn=collate_fn,
|
|
local_rank=self.local_rank,
|
|
tput_timer=deepspeed_io_timer,
|
|
num_local_io_workers=num_local_io_workers,
|
|
data_sampler=data_sampler,
|
|
data_parallel_world_size=data_parallel_world_size,
|
|
data_parallel_rank=data_parallel_rank,
|
|
dataloader_drop_last=self.dataloader_drop_last(),
|
|
deepspeed_dataloader_config=deepspeed_dataloader_config)
|
|
|
|
def train(self, mode=True):
|
|
r""""""
|
|
|
|
self.warn_unscaled_loss = True
|
|
self.module.train(mode)
|
|
|
|
def eval(self):
|
|
r""""""
|
|
|
|
self.warn_unscaled_loss = True
|
|
self.module.train(False)
|
|
|
|
def _scale_loss_by_gas(self, prescaled_loss, eval_micro_batches=None):
|
|
# In pipeline evaluation, there is an option to use different micro-bs, which creates different number of
|
|
# micro batches, thus the training gas, is not valid in this case. need to use the number of eval_micro_batches
|
|
scaling_factor = self.gradient_accumulation_steps() if eval_micro_batches is None else eval_micro_batches
|
|
if isinstance(prescaled_loss, torch.Tensor):
|
|
scaled_loss = prescaled_loss / scaling_factor
|
|
elif isinstance(prescaled_loss, tuple) or isinstance(prescaled_loss, list):
|
|
scaled_loss = []
|
|
for l in prescaled_loss:
|
|
if isinstance(l, torch.Tensor):
|
|
scaled_loss.append(l / scaling_factor)
|
|
else:
|
|
scaled_loss.append(l)
|
|
else:
|
|
scaled_loss = prescaled_loss
|
|
if self.warn_unscaled_loss:
|
|
logger.warning(f"DeepSpeed unable to scale loss because of type: {type(prescaled_loss)}")
|
|
self.warn_unscaled_loss = False
|
|
|
|
return scaled_loss
|
|
|
|
def _create_module_forward_pre_hook(self):
|
|
|
|
def _module_forward_pre_hook(module, inputs, kwargs):
|
|
return self._forward_prologue(inputs, kwargs)
|
|
|
|
return self.module.register_forward_pre_hook(_module_forward_pre_hook, prepend=False, with_kwargs=True)
|
|
|
|
def _create_module_forward_post_hook(self):
|
|
|
|
def _module_forward_post_hook(module, input, output):
|
|
self._forward_epilogue()
|
|
|
|
return self.module.register_forward_hook(_module_forward_post_hook)
|
|
|
|
def _forward_prologue(self, inputs, kwargs):
|
|
return_modified = False
|
|
|
|
if not self.autotuning_profile_model_info():
|
|
see_memory_usage("Engine before forward", force=self.memory_breakdown())
|
|
|
|
flops_profiler_active = (self.flops_profiler_enabled()
|
|
and self.global_steps == self.flops_profiler_profile_step() and self.global_rank == 0)
|
|
|
|
# used to check quantization happens at step 0!
|
|
if self.global_steps == 0 and hasattr(self, "compression_scheduler"):
|
|
self.compression_scheduler.step(step_zero_check=True)
|
|
if self.quantizer:
|
|
tensor_to_quantize = self.optimizer.bit16_groups if self.zero_optimization_stage(
|
|
) == 2 else self.optimizer.fp16_groups
|
|
if self.compression_scheduler.weight_quantization_enabled:
|
|
self.quantizer.quantize(
|
|
tensor_to_quantize,
|
|
(self.optimizer.overflow if self.fp16_enabled() else False),
|
|
self.eigenvalue_enabled(),
|
|
None,
|
|
)
|
|
return_modified = True
|
|
|
|
if flops_profiler_active:
|
|
self.flops_profiler.start_profile(ignore_list=None)
|
|
|
|
if kwargs is not None:
|
|
if self.module.training:
|
|
if self.progressive_layer_drop:
|
|
kwargs.update(self.progressive_layer_drop.get_state())
|
|
|
|
if self.__class__.__name__ != "PipelineEngine":
|
|
# TODO: The above if condition is a HACK since for PipelineEngine
|
|
# it's difficult to inject argument in forward pass.
|
|
if self.module.training and self.curriculum_enabled_legacy():
|
|
self.curriculum_scheduler_legacy.update_difficulty(self.global_steps + 1)
|
|
if self.curriculum_params_legacy()["curriculum_type"] == "seqlen":
|
|
kwargs.update({"curriculum_seqlen": self.curriculum_scheduler_legacy.get_current_difficulty()})
|
|
return_modified = True
|
|
|
|
if self.module.training and self.random_ltd_enabled():
|
|
self.random_ltd_scheduler.update_seq(self.global_steps)
|
|
|
|
if self.training_dataloader is None:
|
|
self.tput_timer.start()
|
|
|
|
self._start_timers(self.engine_timers.forward_timers)
|
|
|
|
if self.zero_optimization_partition_weights():
|
|
# Enable automated discovery of external parameters by indicating that
|
|
# we are in a forward pass.
|
|
for module in self.module.modules():
|
|
ensure_zero_ordered_dict(module)
|
|
module._parameters._in_forward = True
|
|
|
|
if self.fp16_auto_cast():
|
|
inputs = self._cast_inputs_half(inputs)
|
|
return_modified = True
|
|
|
|
if return_modified:
|
|
return inputs, kwargs
|
|
|
|
def _forward_epilogue(self):
|
|
if self.zero_optimization_partition_weights():
|
|
# Disable automated discovery of external parameters
|
|
for module in self.module.modules():
|
|
if isinstance(module._parameters, ZeROOrderedDict):
|
|
module._parameters._in_forward = False
|
|
|
|
self._stop_timers(self.engine_timers.forward_timers)
|
|
|
|
flops_profiler_active = (self.flops_profiler_enabled()
|
|
and self.global_steps == self.flops_profiler_profile_step() and self.global_rank == 0)
|
|
|
|
if flops_profiler_active:
|
|
self.flops_profiler.stop_profile()
|
|
|
|
if not self.autotuning_profile_model_info():
|
|
see_memory_usage("Engine after forward", force=self.memory_breakdown())
|
|
|
|
@instrument_w_nvtx
|
|
def forward(self, *inputs, **kwargs):
|
|
r"""Execute forward propagation
|
|
Arguments:
|
|
*inputs: Variable length input list
|
|
**kwargs: variable length keyword arguments
|
|
"""
|
|
# Clear the backward seen flag at the start of each forward pass.
|
|
# This is used to track multiple gradient hook phases with reentrant checkpointing.
|
|
if isinstance(self.optimizer, ZeROOptimizer):
|
|
self.optimizer.clear_backward_seen_flag()
|
|
|
|
if self.autotuning_profile_model_info():
|
|
ma = get_ma_status()
|
|
|
|
if self.is_deepcompile_enabled() and not self.is_deepcompile_active() and not self.is_compiled:
|
|
log_dist_once(
|
|
"DeepCompile is enabled but engine.compile() has not been called; executing without DeepCompile until compile() runs.",
|
|
ranks=[0])
|
|
|
|
if self.is_deepcompile_active() and hasattr(self, "launch_compile_passes"):
|
|
# We can't have this in forward prologue as the compiler compiles hooks including the forward prologue.
|
|
self.launch_compile_passes(self.global_steps)
|
|
|
|
with deepcompile_z3_forward_context(self), autocast_if_enabled(self):
|
|
loss = self.module(*inputs, **kwargs)
|
|
|
|
# Register output backward hooks
|
|
# preprocess_once_fn is called for preprocessing
|
|
# preprocess_per_tensor_fn scales a tensor for gradient accumulation
|
|
register_output_backward_hooks(loss,
|
|
preprocess_once_fn=self._backward_prologue,
|
|
preprocess_per_tensor_fn=self._backward_prologue_per_tensor)
|
|
|
|
if self.autotuning_profile_model_info():
|
|
activation_mem = get_ma_status() - ma
|
|
self.autotuning_model_info["activation_mem_per_gpu"] = activation_mem
|
|
print_json_dist(self.autotuning_model_info, [0], path=self.autotuning_model_info_path())
|
|
exit()
|
|
|
|
return loss
|
|
|
|
def _cast_inputs_half(self, inputs):
|
|
if isinstance(inputs, (list, tuple)):
|
|
new_inputs = []
|
|
for v in inputs:
|
|
new_inputs.append(self._cast_inputs_half(v))
|
|
return inputs.__class__(new_inputs)
|
|
elif isinstance(inputs, dict):
|
|
new_inputs = {}
|
|
for k, v in inputs.items():
|
|
new_inputs[k] = self._cast_inputs_half(v)
|
|
return new_inputs
|
|
elif hasattr(inputs, 'half') and inputs.is_floating_point():
|
|
return inputs.half()
|
|
else:
|
|
return inputs
|
|
|
|
def print_forward_breakdown(self, fwd_time):
|
|
gate_time = 0.0
|
|
moe_time = 0.0
|
|
falltoall = 0.0
|
|
salltoall = 0.0
|
|
|
|
for gate in self.gate_modules:
|
|
#logger.info(f"Individual TopK gate time: {gate.gate_time:.2f} ms")
|
|
gate_time += gate.gate_time
|
|
|
|
for l in self.moe_layers:
|
|
#logger.info(f"MoE layer; total: {l.time_moe:.2f} ms, first alltoall: {l.time_falltoall:.2f}, second alltoall: {l.time_salltoall:.2f}")
|
|
moe_time += l.time_moe
|
|
falltoall += l.time_falltoall
|
|
salltoall += l.time_salltoall
|
|
|
|
# TODO: Allreduce/average them across ranks for more accurate timing.
|
|
|
|
# if deepspeed.comm.get_rank() == 0:
|
|
log_dist(
|
|
f"time (ms) | fwd: {fwd_time:.2f} (fwd_moe: {moe_time:.2f}, 1st_a2a: {falltoall:.2f}, 2nd_a2a: {salltoall:.2f}, top_k: {gate_time:.2f})",
|
|
ranks=[0])
|
|
|
|
@instrument_w_nvtx
|
|
def allreduce_gradients(self, bucket_size=MEMORY_OPT_ALLREDUCE_SIZE):
|
|
# Skip gradient reduction when DeepCompile is enabled
|
|
# DeepCompile handles its own gradient reduction through compiled graph operations
|
|
if self.is_deepcompile_active() and not self.compile_autosp():
|
|
return
|
|
|
|
# Pass (PP) gas boundary flag to optimizer (required for zero)
|
|
self.optimizer.is_gradient_accumulation_boundary = self.is_gradient_accumulation_boundary()
|
|
if self.is_gradient_accumulation_boundary():
|
|
self._reduce_autoep_folding_tp_replicated_gradients()
|
|
# ZeRO stage >= 2 communicates during non gradient accumulation boundaries as well
|
|
if self.zero_optimization_partition_gradients():
|
|
self.optimizer.overlapping_partition_gradients_reduce_epilogue()
|
|
|
|
# Communicate only at gradient accumulation boundaries
|
|
elif self.is_gradient_accumulation_boundary():
|
|
if self.zero_optimization_stage() == ZeroStageEnum.optimizer_states and hasattr(
|
|
self.optimizer, 'reduce_gradients'):
|
|
self.optimizer.reduce_gradients(pipeline_parallel=self.pipeline_parallelism)
|
|
else:
|
|
grads = None
|
|
self.buffered_allreduce_fallback(grads=grads, elements_per_buffer=bucket_size)
|
|
elif self.zenflow:
|
|
self.optimizer.reduce_gradients(pipeline_parallel=self.pipeline_parallelism)
|
|
|
|
def _reduce_autoep_folding_tp_replicated_gradients(self):
|
|
folding_spec = getattr(self, "_autoep_folding_spec", None)
|
|
if folding_spec is None or folding_spec.tp_size <= 1 or not dist.is_initialized():
|
|
return
|
|
if (isinstance(self.optimizer, ZeROOptimizer) and getattr(self.optimizer, "partition_gradients", False)
|
|
and getattr(self.optimizer, "autoep_folding_tp_group", None) is not None):
|
|
return
|
|
tp_group = groups.get_tensor_model_parallel_group()
|
|
if tp_group is None:
|
|
return
|
|
|
|
for param_name, param in self.module.named_parameters():
|
|
if not param.requires_grad or param.grad is None:
|
|
continue
|
|
if is_autoep_folding_gradient_corrected(param):
|
|
clear_autoep_folding_gradient_corrected(param)
|
|
continue
|
|
reduce_autoep_folding_gradient(folding_spec, param, param.grad, tp_group=tp_group, param_name=param_name)
|
|
|
|
def _backward_prologue(self):
|
|
if is_functorch_transforming():
|
|
return
|
|
self._start_timers(self.engine_timers.backward_timers)
|
|
|
|
# When necessary internal APIs are not available, we disable direct calls to tensor.backward()
|
|
# and limit to engine.backward(loss) only.
|
|
if not self._support_torch_style_backward and not self._running_engine_backward:
|
|
raise RuntimeError("Direct calls to tensor.backward() are not supported in this configuration. "
|
|
"This occurs when either: (1) your PyTorch version lacks required internal APIs, "
|
|
"or (2) using ZeRO stage 0. "
|
|
"Please use engine.backward(loss) instead.")
|
|
|
|
see_memory_usage("Engine before backward", force=self.memory_breakdown())
|
|
|
|
assert not self.eigenvalue_enabled(), "Eigenvalue is not supported with non-scalar backward"
|
|
assert not self.amp_enabled(), "Apex AMP is not supported with non-scalar backward"
|
|
|
|
if self.is_deepcompile_active():
|
|
deepcompile_backward_prologue(self.is_gradient_accumulation_boundary())
|
|
|
|
if isinstance(self.optimizer, ZeROOptimizer):
|
|
self.optimizer.backward_prologue()
|
|
self.optimizer.enter_backward()
|
|
self.optimizer.queue_post_backward_callback()
|
|
|
|
if self.zenflow and self.auto_update:
|
|
self.optimizer.zenflow_state ^= 1
|
|
|
|
if self.zero_optimization():
|
|
self.optimizer.is_gradient_accumulation_boundary = self.is_gradient_accumulation_boundary()
|
|
|
|
self._start_timers(self.engine_timers.backward_inner_timers)
|
|
|
|
def _backward_epilogue(self):
|
|
self._stop_timers(self.engine_timers.backward_inner_timers)
|
|
self._start_timers(self.engine_timers.backward_reduce_timers)
|
|
# BF16_Optimizer (without immediate_grad_update) accumulates low
|
|
# precision grads into a separate fp32 buffer in backward_epilogue().
|
|
# Run it before allreduce so the boundary microbatch is reduced.
|
|
bf16_optimizer = isinstance(self.optimizer, BF16_Optimizer)
|
|
if bf16_optimizer:
|
|
self.optimizer.backward_epilogue()
|
|
|
|
if self.enable_backward_allreduce and not self.inside_no_sync_ctxt:
|
|
# Traditional code path that allreduces the module parameter grads
|
|
self.allreduce_gradients()
|
|
|
|
if isinstance(self.optimizer, ZeROOptimizer):
|
|
if not bf16_optimizer:
|
|
self.optimizer.backward_epilogue()
|
|
self.optimizer.exit_backward()
|
|
|
|
if self.is_deepcompile_active():
|
|
deepcompile_backward_epilogue()
|
|
|
|
see_memory_usage("Engine after backward", force=self.memory_breakdown())
|
|
self._stop_timers(self.engine_timers.backward_reduce_timers)
|
|
self._stop_timers(self.engine_timers.backward_timers)
|
|
|
|
def _backward_prologue_per_tensor(self, grad):
|
|
if is_functorch_transforming():
|
|
return grad
|
|
# Only scale gradients if scale_wrt_gas is True, consistent with backward() parameter
|
|
if grad is not None and self._scale_wrt_gas:
|
|
return grad / self.gradient_accumulation_steps()
|
|
return grad
|
|
|
|
def _backward_post_hook(self):
|
|
if is_functorch_transforming():
|
|
return
|
|
if not self._running_engine_backward:
|
|
# Check if loss scaling was required but not applied
|
|
needs_scaler = False
|
|
if isinstance(self.optimizer, ZeROOptimizer):
|
|
needs_scaler = self.optimizer.needs_scaler()
|
|
elif self.torch_autocast_z0_gradscaler is not None:
|
|
needs_scaler = True
|
|
elif self.amp_enabled():
|
|
needs_scaler = True
|
|
|
|
if needs_scaler and not self._manual_backward_expected:
|
|
# User called backward() directly without using engine.scale() or engine.backward()
|
|
error_msg = ("Loss scaling is required for this configuration, but backward() was called "
|
|
"directly without scaling the loss. Please use one of the following:"
|
|
" 1. engine.backward(loss)"
|
|
" 2. engine.scale(loss).backward()")
|
|
if self.amp_enabled():
|
|
error_msg += " Note: AMP (NVIDIA Apex) only supports engine.backward(loss)."
|
|
raise RuntimeError(error_msg)
|
|
|
|
# Clear the flag for next backward
|
|
self._manual_backward_expected = False
|
|
|
|
self._backward_epilogue()
|
|
|
|
@contextmanager
|
|
def no_sync(self):
|
|
r"""
|
|
Context manager to disable gradient reduction during backward pass.
|
|
This context manager has the following effects on other DeepSpeed features:
|
|
1. Incompatible with ZeRO stage 2/3 which rely on reduction for gradient partitioning.
|
|
2. It is illegal to call engine.step() within the context manager.
|
|
3. Tracking of gradient accumulation steps is disabled.
|
|
"""
|
|
assert not self.zero_optimization_partition_gradients(), \
|
|
f"no_sync context manager is incompatible with gradient partitioning logic of ZeRO stage {self.zero_optimization_stage()}"
|
|
|
|
assert not self.inside_no_sync_ctxt, "no_sync context manager reentry is unsupported"
|
|
|
|
self.inside_no_sync_ctxt = True
|
|
try:
|
|
yield
|
|
finally:
|
|
self.inside_no_sync_ctxt = False
|
|
|
|
@contextmanager
|
|
def coalesce_grad_reduction(self):
|
|
r"""Coalesce ZeRO 1/2/3 gradient reduction across multiple engine.backward()
|
|
calls. One with-block == one optimizer step: every backward inside
|
|
leaves grads locally on params, and the flush on exit issues a single
|
|
reduction pass that populates averaged_gradients for the next step().
|
|
|
|
Constraints:
|
|
- engine.step() inside the block raises.
|
|
- Reentry / nesting with engine.no_sync() raises.
|
|
- Do not span multiple gradient_accumulation_steps with multiple
|
|
with-blocks; the flush overwrites averaged_gradients each exit.
|
|
|
|
Unsupported (NotImplementedError): ZeRO stage 0, BF16/FP16_Optimizer
|
|
wrappers, PipelineModule.
|
|
"""
|
|
stage = self.zero_optimization_stage()
|
|
if stage not in (ZeroStageEnum.optimizer_states, ZeroStageEnum.gradients, ZeroStageEnum.weights):
|
|
raise NotImplementedError(f"coalesce_grad_reduction requires ZeRO stage 1/2/3, got stage {int(stage)}")
|
|
if self.pipeline_parallelism:
|
|
raise NotImplementedError("coalesce_grad_reduction is not supported under pipeline parallelism")
|
|
optimizer = self.optimizer
|
|
if not hasattr(optimizer, "_coalesce_grad_reduction"):
|
|
# BF16_Optimizer / FP16_Optimizer route grads through their own
|
|
# backward_epilogue path, bypassing DeepSpeedZeroOptimizer's
|
|
# per-param hooks that this context relies on.
|
|
raise NotImplementedError(
|
|
f"coalesce_grad_reduction does not yet support optimizer wrapper {type(optimizer).__name__}")
|
|
assert not self.inside_no_sync_ctxt, \
|
|
"coalesce_grad_reduction cannot be nested inside another no_sync context"
|
|
|
|
# Engine boundary is the source of truth; optimizer's copy is overwritten
|
|
# by _backward_prologue from the engine value on each backward, so we
|
|
# only need to save/restore the engine flag.
|
|
saved_engine_boundary = self._is_gradient_accumulation_boundary
|
|
self.inside_no_sync_ctxt = True
|
|
optimizer._coalesce_grad_reduction = True
|
|
try:
|
|
yield
|
|
finally:
|
|
# Reset _coalesce_grad_reduction BEFORE the flush so the reducer calls
|
|
# we drive in the flush helpers do NOT short-circuit at our guard
|
|
# in process_gradients / reduce_ready_partitions_and_remove_grads.
|
|
optimizer._coalesce_grad_reduction = False
|
|
self.inside_no_sync_ctxt = False
|
|
self._is_gradient_accumulation_boundary = True
|
|
optimizer.is_gradient_accumulation_boundary = True
|
|
try:
|
|
# Drive a single reduction pass over locally accumulated grads.
|
|
# Iterate explicitly (rather than calling reduce_gradients) so
|
|
# the path works regardless of overlap_comm / contiguous_gradients,
|
|
# both of which alter reduce_gradients's control flow.
|
|
if stage == ZeroStageEnum.weights:
|
|
self._flush_coalesced_reduction_zero3(optimizer)
|
|
else:
|
|
self._flush_coalesced_reduction_zero12(optimizer)
|
|
finally:
|
|
self._is_gradient_accumulation_boundary = saved_engine_boundary
|
|
|
|
def _flush_coalesced_reduction_zero12(self, optimizer):
|
|
# Quiesce the reduction stream before re-entering it (overlap_comm uses
|
|
# a separate stream + double-buffered ipg bucket). Without this the
|
|
# bucket.index swap in reduce_independent_p_g_buckets_and_remove_grads
|
|
# may race against the previous step's residual reduction.
|
|
if getattr(optimizer, "overlap_comm", False) and hasattr(optimizer, "reduction_stream"):
|
|
if not get_accelerator().resolves_data_dependency():
|
|
optimizer.reduction_stream.synchronize()
|
|
# Ensure ipg bucket buffers exist (process_gradients normally allocates
|
|
# them via setup_buckets, but we suppressed it during coalesce period).
|
|
# Note: micro_step_id increments by 1 here for the whole coalesce block,
|
|
# which is fine -- copy_grads_in_partition's accumulate condition uses
|
|
# micro_step_id > 0 OR not boundary, and we force boundary=True.
|
|
optimizer.setup_buckets()
|
|
for i, group in enumerate(optimizer.bit16_groups):
|
|
for param in group:
|
|
if not param.requires_grad:
|
|
continue
|
|
# use_grad_accum_attribute=True parks the accumulated grad in
|
|
# param.grad_accum instead of param.grad (backward_epilogue
|
|
# routes it there each microbatch). get_gradient_for_reduction
|
|
# returns the right one for both modes.
|
|
if optimizer.get_gradient_for_reduction(param) is None:
|
|
continue
|
|
optimizer.reduce_ready_partitions_and_remove_grads(param, i)
|
|
optimizer.overlapping_partition_gradients_reduce_epilogue()
|
|
|
|
def _flush_coalesced_reduction_zero3(self, optimizer):
|
|
# Leaf-module unused-param zero-fill (stage3.py:1336-1337) runs from
|
|
# the leaf module's own backward hook, BEFORE the reducer call we
|
|
# suppress. So by flush time the leaf params already have grads (real
|
|
# or zero-filled) populated by the hook regardless of _coalesce_grad_reduction.
|
|
for group in optimizer.fp16_groups:
|
|
for param in group:
|
|
if param.requires_grad and param.grad is not None:
|
|
optimizer.reduce_ready_partitions_and_remove_grads(param)
|
|
optimizer.independent_gradient_partition_epilogue()
|
|
|
|
def scale(self, loss):
|
|
r"""Apply loss scaler for manual backward pass.
|
|
|
|
Use this method when calling loss.backward() directly instead of engine.backward().
|
|
This applies the appropriate loss scaler for mixed precision training, allowing you
|
|
to manually control the backward pass while still benefiting from DeepSpeed's
|
|
gradient scaling functionality.
|
|
|
|
Example::
|
|
|
|
output = engine(input)
|
|
loss = criterion(output, target)
|
|
scaled_loss = engine.scale(loss)
|
|
scaled_loss.backward() # Manual backward call
|
|
engine.step()
|
|
|
|
Arguments:
|
|
loss: Scalar loss tensor to be scaled
|
|
|
|
Returns:
|
|
Scaled loss tensor ready for .backward() call
|
|
|
|
Raises:
|
|
RuntimeError: If AMP (NVIDIA Apex) is enabled. AMP requires using engine.backward()
|
|
directly as it uses a context manager that cannot be separated from
|
|
the backward call.
|
|
AssertionError: If loss is not a scalar tensor with grad_fn, or if no optimizer
|
|
is configured.
|
|
"""
|
|
assert self.optimizer is not None and not isinstance(self.optimizer, DummyOptim), \
|
|
"must provide optimizer during init in order to use scale"
|
|
assert maybe_loss_for_backward(loss), \
|
|
"loss must be a scalar tensor with grad_fn. For non-scalar tensors, use tensor.backward(grad)"
|
|
|
|
# AMP (NVIDIA Apex) uses a context manager that wraps both scaling and backward,
|
|
# so it cannot be used with manual backward calls
|
|
if self.amp_enabled():
|
|
raise RuntimeError("engine.scale() is not compatible with AMP (NVIDIA Apex). "
|
|
"When using AMP, you must call engine.backward(loss) instead of manual backward.")
|
|
|
|
# Apply loss scaler based on optimizer type
|
|
scaled_loss = loss
|
|
if isinstance(self.optimizer, ZeROOptimizer):
|
|
scaled_loss = self.optimizer.scale_if_loss(scaled_loss)
|
|
elif self.torch_autocast_z0_gradscaler:
|
|
scaled_loss = self.torch_autocast_z0_gradscaler.scale(scaled_loss)
|
|
|
|
# Mark that scale() was called for validation in backward hook
|
|
self._manual_backward_expected = True
|
|
|
|
return scaled_loss
|
|
|
|
@instrument_w_nvtx
|
|
def backward(self, loss, retain_graph=False, scale_wrt_gas=True):
|
|
r"""Execute backward pass on the loss
|
|
Arguments:
|
|
loss: Torch tensor on which to execute backward propagation
|
|
retain_graph: bool, default: false
|
|
forward on user defined choice of retain_graph
|
|
scale_wrt_gas: bool, default: true
|
|
whether to scale gradients and return value by gradient accumulation steps
|
|
"""
|
|
assert self.optimizer is not None and not isinstance(self.optimizer, DummyOptim), \
|
|
"must provide optimizer during init in order to use backward"
|
|
assert maybe_loss_for_backward(
|
|
loss), "loss must be a scalar tensor. If you need to pass output gradients, backward() of output tensors"
|
|
|
|
self._running_engine_backward = True
|
|
# Store scale_wrt_gas so the hook can respect it
|
|
self._scale_wrt_gas = scale_wrt_gas
|
|
|
|
# Set flag to prevent hooks from firing (we'll manually call prologue/epilogue)
|
|
backward_kwargs = {"retain_graph": retain_graph}
|
|
if self.eigenvalue_enabled():
|
|
backward_kwargs["create_graph"] = True
|
|
backward_kwargs["retain_graph"] = True
|
|
|
|
# Used only for return value
|
|
gas_scaled_loss = loss / self.gradient_accumulation_steps() if scale_wrt_gas else loss
|
|
|
|
# TODO: handle these scaling with direct calls to loss.backward()
|
|
if isinstance(self.optimizer, ZeROOptimizer):
|
|
loss = self.optimizer.scale_if_loss(loss)
|
|
elif self.torch_autocast_z0_gradscaler:
|
|
loss = self.torch_autocast_z0_gradscaler.scale(loss)
|
|
|
|
with compiled_autograd(self._is_compiled_autograd_enabled, self._compile_kwargs):
|
|
if self.zero_optimization() or not self.amp_enabled():
|
|
loss.backward(**backward_kwargs)
|
|
elif self.amp_enabled():
|
|
# AMP requires delaying unscale when inside gradient accumulation boundaries
|
|
# https://nvidia.github.io/apex/advanced.html#gradient-accumulation-across-iterations
|
|
delay_unscale = not self.is_gradient_accumulation_boundary()
|
|
with amp.scale_loss(loss, self.optimizer, delay_unscale=delay_unscale) as scaled_loss:
|
|
scaled_loss.backward(**backward_kwargs)
|
|
|
|
# backward_epilogue is not called in a hook when self._support_torch_style_backward is False
|
|
self._backward_epilogue()
|
|
|
|
self._running_engine_backward = False
|
|
|
|
return gas_scaled_loss
|
|
|
|
def is_gradient_accumulation_boundary(self):
|
|
"""
|
|
Query whether the current micro-batch is at the boundary of
|
|
gradient accumulation, and thus will trigger gradient reductions and
|
|
an optimizer step.
|
|
|
|
Returns:
|
|
bool: if the current step is a gradient accumulation boundary.
|
|
|
|
"""
|
|
if self._is_gradient_accumulation_boundary is None:
|
|
if self.zenflow:
|
|
return self._is_zenflow_update_boundary()
|
|
else:
|
|
return (self.micro_steps + 1) % self.gradient_accumulation_steps() == 0
|
|
else:
|
|
return self._is_gradient_accumulation_boundary
|
|
|
|
def set_gradient_accumulation_boundary(self, is_boundary):
|
|
"""
|
|
Manually overrides the DeepSpeed engine's gradient accumulation boundary state, this is an optional
|
|
feature and should be used with care. The state should be set before to the intended
|
|
value before each forward/backward. The final forward/backward should have the
|
|
boundary state set to True. This style allows client code to only call engine.step() once after all
|
|
the gradient accumulation passes are complete. See example below:
|
|
.. code-block:: python
|
|
engine.set_gradient_accumulation_boundary(False)
|
|
for _ in range(gradient_accumulation_steps - 1):
|
|
micro_batch = next(data_loader)
|
|
loss = engine(micro_batch)
|
|
engine.backward(loss)
|
|
engine.set_gradient_accumulation_boundary(True)
|
|
micro_batch = next(data_loader)
|
|
loss = engine(micro_batch)
|
|
engine.backward(loss)
|
|
engine.step()
|
|
Arguments:
|
|
is_boundary (bool): are we at a gradient accumulation boundary or not?
|
|
"""
|
|
self._is_gradient_accumulation_boundary = is_boundary
|
|
self.optimizer.is_gradient_accumulation_boundary = is_boundary
|
|
|
|
def zero_grad(self):
|
|
"""
|
|
Zero parameter grads.
|
|
"""
|
|
for param_name, param in self.module.named_parameters():
|
|
param.grad = None
|
|
|
|
def clip_fp32_gradients(self):
|
|
clip_grad_norm_(parameters=self.module.parameters(), max_norm=self.gradient_clipping(), mpu=self.mpu)
|
|
|
|
def _take_model_step(self, lr_kwargs, block_eigenvalue={}):
|
|
if self.gradient_clipping() > 0.0:
|
|
if self.torch_autocast_z0_gradscaler:
|
|
# Unscale for gradient clipping
|
|
self.torch_autocast_z0_gradscaler.unscale_(self.optimizer)
|
|
if not (self.fp16_enabled() or self.bfloat16_enabled() or self.amp_enabled() or self.zero_optimization()):
|
|
self.clip_fp32_gradients()
|
|
elif self.amp_enabled():
|
|
# AMP's recommended way of doing clipping
|
|
# https://nvidia.github.io/apex/advanced.html#gradient-clipping
|
|
master_params = amp.master_params(self.optimizer)
|
|
clip_grad_norm_(parameters=master_params, max_norm=self.gradient_clipping(), mpu=self.mpu)
|
|
if self.torch_autocast_z0_gradscaler:
|
|
self.torch_autocast_z0_gradscaler.step(self.optimizer)
|
|
self.torch_autocast_z0_gradscaler.update()
|
|
else:
|
|
self.optimizer.step()
|
|
|
|
if hasattr(self.optimizer, '_global_grad_norm'):
|
|
self._global_grad_norm = self.optimizer._global_grad_norm
|
|
|
|
# Quantize the updated parameter if there is no overflow
|
|
if self.quantizer:
|
|
tensor_to_quantize = self.optimizer.bit16_groups if self.zero_optimization_stage(
|
|
) == 2 else self.optimizer.fp16_groups
|
|
if self.compression_scheduler.weight_quantization_enabled:
|
|
self.quantizer.quantize(
|
|
tensor_to_quantize,
|
|
(self.optimizer.overflow if self.fp16_enabled() else False),
|
|
self.eigenvalue_enabled(),
|
|
block_eigenvalue,
|
|
)
|
|
# zero grad in basic optimizer could be unreliable and may not exhibit
|
|
# the behavior that we want
|
|
if self.bfloat16_enabled():
|
|
# TODO: Temporary until bf16_optimizer and zero_optimizer are integrated
|
|
if hasattr(self.optimizer, "zero_grad"):
|
|
self.optimizer.zero_grad()
|
|
else:
|
|
self.zero_grad()
|
|
elif self.zero_optimization() or self.fp16_enabled() or self.amp_enabled():
|
|
self.optimizer.zero_grad()
|
|
else:
|
|
self.zero_grad()
|
|
|
|
# Check overflow here since in DS fp16 optimizer, the overflow is updated in above step() function.
|
|
overflow = False
|
|
if hasattr(self.optimizer, "overflow"):
|
|
overflow = self.optimizer.overflow
|
|
self._step_applied = not overflow
|
|
|
|
if overflow:
|
|
self.skipped_steps += 1
|
|
else:
|
|
self.compression_scheduler.step()
|
|
if self.lr_scheduler is not None:
|
|
try:
|
|
self.lr_scheduler.step(**(lr_kwargs or {}))
|
|
except TypeError:
|
|
# XXX Hack to work with Megatron 2.0 and DeepSpeed pipelines.
|
|
# We don't currently have a way to specify lr_kwargs from
|
|
# pipe_engine.train_batch()
|
|
self.lr_scheduler.step(self.train_batch_size())
|
|
|
|
if self.steps_per_print() is not None:
|
|
report_progress = self.global_rank == 0 if self.global_rank else True
|
|
if report_progress and (self.global_steps + 1) % self.steps_per_print() == 0:
|
|
self._report_progress(self.global_steps + 1)
|
|
|
|
self.losses = None
|
|
self.global_steps += 1
|
|
self.global_samples += self.train_batch_size()
|
|
|
|
def step(self, lr_kwargs=None):
|
|
r"""Execute the weight update step after forward and backward propagation
|
|
on effective_train_batch.
|
|
"""
|
|
assert not self.inside_no_sync_ctxt, \
|
|
"It is illegal to call Engine.step() inside no_sync context manager"
|
|
|
|
see_memory_usage("Engine before step", force=self.memory_breakdown())
|
|
|
|
# Check early because self.global_steps is incremented at some point here.
|
|
# TODO: Delay self.global_steps increment until very end of this function.
|
|
flops_profiler_active = self.flops_profiler_enabled(
|
|
) and self.global_steps == self.flops_profiler_profile_step() and self.global_rank == 0
|
|
|
|
self._start_timers(self.engine_timers.step_timers)
|
|
|
|
assert self.optimizer is not None and not isinstance(self.optimizer, DummyOptim), \
|
|
"must provide optimizer during init in order to use step"
|
|
|
|
report_progress = False
|
|
|
|
self._step_applied = False # assume False, will flip to True
|
|
|
|
if self.zenflow:
|
|
self.optimizer._sync_selective_optimizer_lr()
|
|
if self.auto_update:
|
|
self.update_interval += 1
|
|
|
|
# Update the model when we reach gradient accumulation boundaries
|
|
if self.is_gradient_accumulation_boundary():
|
|
self.gas_boundary_ctr += 1
|
|
|
|
if self.checkpoint_engine.is_decoupled():
|
|
self._commit_decoupled_checkpoint()
|
|
|
|
if (self.eigenvalue_enabled() and (self.gas_boundary_ctr % self.eigenvalue_gas_boundary_resolution() == 0)
|
|
and self.quantizer.any_precision_switch()):
|
|
log_dist("computing eigenvalue...", ranks=[0])
|
|
loss_scale = self._get_optimizer_loss_scale() or 1.0
|
|
self.block_eigenvalue = self.eigenvalue.compute_eigenvalue(self.module, self.device, loss_scale)
|
|
|
|
if self.progressive_layer_drop:
|
|
self.progressive_layer_drop.update_state(self.global_steps)
|
|
|
|
if (self.eigenvalue_enabled() and not self.gas_boundary_ctr % self.eigenvalue_gas_boundary_resolution()
|
|
and self.quantizer.any_precision_switch()):
|
|
self._take_model_step(lr_kwargs, self.block_eigenvalue)
|
|
else:
|
|
self._take_model_step(lr_kwargs)
|
|
|
|
report_progress = self.global_rank == 0 if self.global_rank else True
|
|
|
|
if self.zenflow:
|
|
self._zenflow_step(lr_kwargs)
|
|
|
|
self.tput_timer.stop(global_step=self.is_gradient_accumulation_boundary(), report_speed=report_progress)
|
|
|
|
self._stop_timers(self.engine_timers.step_timers)
|
|
|
|
# Log learning rate
|
|
if self.monitor.enabled:
|
|
if self.is_gradient_accumulation_boundary():
|
|
if self.global_rank == 0:
|
|
self.summary_events = [("Train/Samples/lr", self.get_lr()[0], self.global_samples)]
|
|
|
|
loss_scale = self._get_optimizer_loss_scale() if self.fp16_enabled() else None
|
|
if loss_scale is not None:
|
|
self.summary_events.append((
|
|
"Train/Samples/loss_scale",
|
|
loss_scale,
|
|
self.global_samples,
|
|
))
|
|
|
|
if (self.eigenvalue_enabled()
|
|
and not self.gas_boundary_ctr % self.eigenvalue_gas_boundary_resolution()):
|
|
self.summary_events.extend(
|
|
_eigenvalue_summary_events(self.block_eigenvalue, self.global_samples))
|
|
self.monitor.write_events(self.summary_events)
|
|
|
|
# Check flops profiling
|
|
if flops_profiler_active:
|
|
if self.autotuning_enabled():
|
|
self.flops = self.flops_profiler.get_total_flops() * 3
|
|
self.fwd_duration = self.flops_profiler.get_total_duration()
|
|
else:
|
|
self.flops_profiler.print_model_profile(
|
|
profile_step=self.global_steps,
|
|
module_depth=self.flops_profiler_module_depth(),
|
|
top_modules=self.flops_profiler_top_modules(),
|
|
detailed=self.flops_profiler_detailed(),
|
|
output_file=self.flops_profiler_output_file(),
|
|
)
|
|
self.flops_profiler.end_profile()
|
|
|
|
if self.autotuning_enabled() and self.global_steps == (self.autotuning_end_profile_step() + 1):
|
|
self._autotuning_exit()
|
|
|
|
if self.wall_clock_breakdown():
|
|
# Update client accessible wall clock timers cache
|
|
self._update_wall_clock_timers()
|
|
|
|
# Log micro timing and reset
|
|
self.timers.log(names=self.engine_timers.micro_timers, memory_breakdown=self.memory_breakdown())
|
|
|
|
if self.wall_clock_breakdown() or self.flops_profiler_enabled():
|
|
# Log global timing and reset
|
|
if self.is_gradient_accumulation_boundary():
|
|
if self.monitor.enabled:
|
|
self._write_monitor()
|
|
|
|
if self.has_moe_layers:
|
|
fwd_time = self.timers(FORWARD_GLOBAL_TIMER).elapsed(reset=False)
|
|
self.print_forward_breakdown(fwd_time=fwd_time)
|
|
|
|
self.timers.log(self.engine_timers.global_timers)
|
|
|
|
self.micro_steps += 1
|
|
see_memory_usage("Engine after step", force=self.memory_breakdown())
|
|
|
|
def _start_timers(self, timer_names):
|
|
for name in timer_names:
|
|
self.timers(name).start()
|
|
|
|
def _stop_timers(self, timer_names):
|
|
record = self.is_gradient_accumulation_boundary() and \
|
|
self.flops_profiler_enabled() and \
|
|
(self.global_steps >= self.flops_profiler_profile_step())
|
|
for name in timer_names:
|
|
self.timers(name).stop(record=record)
|
|
|
|
def _update_wall_clock_timers(self):
|
|
self.engine_timers_cache = {}
|
|
for name in self.engine_timers.active_timers():
|
|
self.engine_timers_cache[name] = self.timers(name).elapsed(reset=False)
|
|
|
|
def get_wall_clock_timers(self):
|
|
r"""
|
|
Return a dict snapshot of the Engine's wall clock timers.
|
|
"""
|
|
return self.engine_timers_cache
|
|
|
|
def _autotuning_exit(self):
|
|
if self.global_rank == 0:
|
|
msg = self.timers.get_mean([
|
|
FORWARD_GLOBAL_TIMER,
|
|
BACKWARD_GLOBAL_TIMER,
|
|
STEP_GLOBAL_TIMER,
|
|
], reset=False)
|
|
titer = 0.0
|
|
titer += msg[FORWARD_GLOBAL_TIMER] if FORWARD_GLOBAL_TIMER in msg else 0
|
|
titer += msg[BACKWARD_GLOBAL_TIMER] if BACKWARD_GLOBAL_TIMER in msg else 0
|
|
titer += msg[STEP_GLOBAL_TIMER] if STEP_GLOBAL_TIMER in msg else 0
|
|
titer *= self.gradient_accumulation_steps()
|
|
msg["latency"] = titer
|
|
msg["FLOPS_per_gpu"] = self.flops * 1_000_000 * self.gradient_accumulation_steps() / titer
|
|
msg["throughput"] = self.train_batch_size() * 1_000_000 / \
|
|
msg["latency"]
|
|
print_json_dist(msg, [0], path=self.autotuning_metric_path())
|
|
log_dist(
|
|
f"Wrote metrics to {self.autotuning_metric_path()}, {os.path.abspath(self.autotuning_metric_path())}",
|
|
ranks=[0])
|
|
import atexit
|
|
atexit.register(print, "Autotuning: done with running current ds config.")
|
|
exit()
|
|
|
|
def _write_monitor(self):
|
|
if self.global_rank == 0:
|
|
self.summary_events = [
|
|
(
|
|
"Train/Samples/elapsed_time_ms_forward",
|
|
self.timers(FORWARD_GLOBAL_TIMER).elapsed(reset=False),
|
|
self.global_samples,
|
|
),
|
|
(
|
|
"Train/Samples/elapsed_time_ms_backward",
|
|
self.timers(BACKWARD_GLOBAL_TIMER).elapsed(reset=False),
|
|
self.global_samples,
|
|
),
|
|
(
|
|
"Train/Samples/elapsed_time_ms_backward_inner",
|
|
self.timers(BACKWARD_INNER_GLOBAL_TIMER).elapsed(reset=False),
|
|
self.global_samples,
|
|
),
|
|
(
|
|
"Train/Samples/elapsed_time_ms_backward_allreduce",
|
|
self.timers(BACKWARD_REDUCE_GLOBAL_TIMER).elapsed(reset=False),
|
|
self.global_samples,
|
|
),
|
|
(
|
|
"Train/Samples/elapsed_time_ms_step",
|
|
self.timers(STEP_GLOBAL_TIMER).elapsed(reset=False),
|
|
self.global_samples,
|
|
),
|
|
]
|
|
self.monitor.write_events(self.summary_events)
|
|
|
|
def _get_optimizer_param(self, param_name):
|
|
result = []
|
|
if not self.optimizer:
|
|
return result
|
|
for group in self.optimizer.param_groups:
|
|
if param_name in group:
|
|
result.append(group[param_name])
|
|
else:
|
|
result.append(0.0)
|
|
return result
|
|
|
|
def _get_optimizer_loss_scale(self):
|
|
if not self.optimizer:
|
|
return None
|
|
if hasattr(self.optimizer, "loss_scale_config"):
|
|
return self.optimizer.loss_scale_config.cur_scale
|
|
return getattr(self.optimizer, "cur_scale", None)
|
|
|
|
def get_lr(self):
|
|
return self._get_optimizer_param("lr")
|
|
|
|
def get_type(self):
|
|
return self._get_optimizer_param("type")
|
|
|
|
def get_mom(self):
|
|
if self.optimizer_name() in ["SGD", "RMSprop"]:
|
|
return self._get_optimizer_param("momentum")
|
|
else:
|
|
return self._get_optimizer_param("betas")
|
|
|
|
def get_pld_theta(self):
|
|
if self.progressive_layer_drop:
|
|
return self.progressive_layer_drop.get_theta()
|
|
else:
|
|
return None
|
|
|
|
def _report_progress(self, step):
|
|
lr = self.get_lr()
|
|
mom = self.get_mom()
|
|
log_dist(f"step={step}, skipped={self.skipped_steps}, lr={lr}, mom={mom}", ranks=[0])
|
|
|
|
def allreduce_bucket(self, bucket, dp_group, dp_world_size=None):
|
|
tensor = self.flatten(bucket)
|
|
|
|
tensor_to_allreduce = tensor
|
|
|
|
if self.communication_data_type != tensor.dtype:
|
|
tensor_to_allreduce = tensor.to(self.communication_data_type)
|
|
|
|
if dp_world_size is None:
|
|
dp_world_size = dist.get_world_size(group=dp_group)
|
|
if self.postscale_gradients():
|
|
if self.gradient_predivide_factor() != 1.0:
|
|
tensor_to_allreduce.mul_(1.0 / self.gradient_predivide_factor())
|
|
|
|
dist.all_reduce(tensor_to_allreduce, group=dp_group)
|
|
if self.gradient_average:
|
|
if self.gradient_predivide_factor() != dp_world_size:
|
|
tensor_to_allreduce.mul_(self.gradient_predivide_factor() / dp_world_size)
|
|
else:
|
|
tensor_to_allreduce.mul_(1. / dp_world_size)
|
|
dist.all_reduce(tensor_to_allreduce, group=dp_group)
|
|
|
|
if self.communication_data_type != tensor.dtype and tensor is not tensor_to_allreduce:
|
|
tensor.copy_(tensor_to_allreduce)
|
|
|
|
return tensor
|
|
|
|
def allreduce_and_copy(self, small_bucket, dp_group, dp_world_size=None):
|
|
allreduced = self.allreduce_bucket(small_bucket, dp_group, dp_world_size)
|
|
for buf, synced in zip(small_bucket, self.unflatten(allreduced, small_bucket)):
|
|
buf.copy_(synced)
|
|
|
|
def allreduce_no_retain(self, bucket, dp_group, numel_per_bucket=500000000, dp_world_size=None):
|
|
small_bucket = []
|
|
numel = 0
|
|
for tensor in bucket:
|
|
small_bucket.append(tensor)
|
|
numel = numel + tensor.numel()
|
|
if numel > numel_per_bucket:
|
|
self.allreduce_and_copy(small_bucket, dp_group, dp_world_size)
|
|
small_bucket = []
|
|
numel = 0
|
|
if len(small_bucket) > 0:
|
|
self.allreduce_and_copy(small_bucket, dp_group, dp_world_size)
|
|
|
|
def _get_gradients_for_reduction(self):
|
|
non_expert_grads = []
|
|
expert_grads = {}
|
|
if self.has_moe_layers:
|
|
for key in self.expert_data_parallel_group.keys():
|
|
expert_grads[key] = []
|
|
|
|
for param_name, param in self.module.named_parameters():
|
|
if not param.requires_grad:
|
|
continue
|
|
|
|
# Skip empty parameters (numel=0) as they contribute nothing to gradient reduction
|
|
# and cause issues with flatten/unflatten operations
|
|
if param.numel() == 0:
|
|
continue
|
|
|
|
if param.grad is None:
|
|
# In cases where there is an imbalance of empty grads across
|
|
# ranks we must create empty grads, this will ensure that every
|
|
# rank is reducing the same size. In some cases it may make
|
|
# sense in the future to support the ability to average not
|
|
# w.r.t. world size but with a different value.
|
|
param.grad = torch.zeros(param.size(), dtype=param.dtype, device=param.device)
|
|
|
|
grad_data = param.grad.data
|
|
if param_name in self.sparse_tensor_module_names or grad_data.is_sparse:
|
|
# Call param.grad without data to avoid problem with setting of updated grads
|
|
grad_data = SparseTensor(param.grad)
|
|
|
|
if is_moe_param(param):
|
|
expert_grads[param.group_name].append(grad_data)
|
|
else:
|
|
non_expert_grads.append(grad_data)
|
|
|
|
return non_expert_grads, expert_grads
|
|
|
|
def _reduce_non_expert_gradients(self, grads, elements_per_buffer):
|
|
split_sparse_tensor_buckets, split_dense_tensor_buckets = split_half_float_double_sparse(grads)
|
|
if self.pipeline_parallelism:
|
|
dp_group = self.mpu.get_data_parallel_group()
|
|
dp_world_size = dist.get_world_size(dp_group)
|
|
else:
|
|
dp_group = groups._get_sequence_data_parallel_group()
|
|
dp_world_size = dist.get_world_size(dp_group) / float(self.sequence_parallel_size)
|
|
for _, sparse_bucket_tuple in enumerate(split_sparse_tensor_buckets):
|
|
if sparse_bucket_tuple:
|
|
bucket_type, sparse_bucket = sparse_bucket_tuple
|
|
self.sparse_allreduce_no_retain(sparse_bucket, dp_group=dp_group, dp_world_size=dp_world_size)
|
|
|
|
for _, dense_bucket_tuple in enumerate(split_dense_tensor_buckets):
|
|
if dense_bucket_tuple:
|
|
bucket_type, dense_bucket = dense_bucket_tuple
|
|
self.allreduce_no_retain(dense_bucket,
|
|
dp_group=dp_group,
|
|
numel_per_bucket=elements_per_buffer,
|
|
dp_world_size=dp_world_size)
|
|
|
|
def _reduce_expert_gradients(self, expert_grads, elements_per_buffer):
|
|
# to maintain the gradients value unaffected by ep_size setting,
|
|
# utilize dp_world_size for allreduce average
|
|
dp_world_size = dist.get_world_size(groups._get_data_parallel_group())
|
|
for ep_name, expert_grads_group in expert_grads.items():
|
|
ep_dp_group = groups._get_expert_data_parallel_group(ep_name)
|
|
split_sparse_tensor_buckets, split_dense_tensor_buckets = split_half_float_double_sparse(
|
|
expert_grads_group)
|
|
|
|
for _, sparse_bucket_tuple in enumerate(split_sparse_tensor_buckets):
|
|
if sparse_bucket_tuple:
|
|
bucket_type, sparse_bucket = sparse_bucket_tuple
|
|
self.sparse_allreduce_no_retain(sparse_bucket, dp_group=ep_dp_group, dp_world_size=dp_world_size)
|
|
|
|
for _, dense_bucket_tuple in enumerate(split_dense_tensor_buckets):
|
|
if dense_bucket_tuple:
|
|
bucket_type, dense_bucket = dense_bucket_tuple
|
|
# Separate between diff groups
|
|
self.allreduce_no_retain(dense_bucket,
|
|
dp_group=ep_dp_group,
|
|
numel_per_bucket=elements_per_buffer,
|
|
dp_world_size=dp_world_size)
|
|
|
|
def buffered_allreduce_fallback(self, grads=None, elements_per_buffer=500000000):
|
|
if grads is None:
|
|
if hasattr(self.optimizer, "get_grads_for_reduction"):
|
|
# This is currently for BF16 optimizer
|
|
non_expert_grads, expert_grads = self.optimizer.get_grads_for_reduction()
|
|
else:
|
|
non_expert_grads, expert_grads = self._get_gradients_for_reduction()
|
|
else:
|
|
assert not self.has_moe_layers, "attempting to reduce grads in unsupported way w.r.t. MoE"
|
|
non_expert_grads = grads
|
|
|
|
self._reduce_non_expert_gradients(non_expert_grads, elements_per_buffer)
|
|
|
|
if self.has_moe_layers:
|
|
self._reduce_expert_gradients(expert_grads, elements_per_buffer)
|
|
|
|
def sparse_allreduce_no_retain(self, bucket, dp_group, dp_world_size=None):
|
|
allreduced_sparses = self.sparse_allreduce_bucket(bucket, dp_group, dp_world_size)
|
|
# Densify sparse tensor and copy back to original location
|
|
for tensor in allreduced_sparses:
|
|
if tensor.is_sparse:
|
|
tensor.orig_dense_tensor.data = tensor.to_coo_tensor()
|
|
else:
|
|
tensor.orig_dense_tensor.copy_(tensor.to_dense())
|
|
|
|
def sparse_allreduce_bucket(self, bucket, dp_group, dp_world_size=None):
|
|
sparse_list = []
|
|
for sparse in bucket:
|
|
sparse_list.append(self.sparse_allreduce(sparse, dp_group, dp_world_size))
|
|
return sparse_list
|
|
|
|
def sparse_allreduce(self, sparse, dp_group, dp_world_size=None):
|
|
original_data_type = sparse.values.dtype
|
|
if self.communication_data_type != sparse.values.dtype:
|
|
if self.communication_data_type in (torch.float16, torch.bfloat16):
|
|
indices = sparse.indices.to(torch.int32)
|
|
else:
|
|
indices = sparse.indices
|
|
values = sparse.values.to(self.communication_data_type)
|
|
else:
|
|
indices = sparse.indices
|
|
values = sparse.values
|
|
|
|
if dp_world_size is None:
|
|
dp_world_size = dist.get_world_size(group=dp_group)
|
|
if self.postscale_gradients():
|
|
if self.gradient_average:
|
|
values.mul_(self.gradient_predivide_factor() / (dp_world_size))
|
|
else:
|
|
values.mul_(1. / (dp_world_size))
|
|
|
|
indices_device_list = self.sparse_all_gather(indices, dp_group)
|
|
values_device_list = self.sparse_all_gather(values, dp_group)
|
|
|
|
sparse.indices = torch.cat(indices_device_list).to(torch.long)
|
|
sparse.values = torch.cat(values_device_list).to(original_data_type)
|
|
return sparse
|
|
|
|
def sparse_all_gather(self, value, dp_group):
|
|
my_size = torch.LongTensor([value.size()[0]]).to(self.device)
|
|
all_sizes = self.all_gather_scalar(my_size, dp_group)
|
|
max_size = torch.cat(all_sizes).max()
|
|
fill_size = max_size - my_size
|
|
|
|
assert value.dim() in [1, 2]
|
|
if value.dim() == 1:
|
|
if fill_size > 0:
|
|
value = torch.cat([value, value.new_empty(fill_size)])
|
|
tensor_list = [value.new_empty(max_size) for _ in range(dist.get_world_size(group=dp_group))]
|
|
else:
|
|
if fill_size > 0:
|
|
value = torch.cat([value, value.new_empty(fill_size, value.size()[1])])
|
|
tensor_list = [
|
|
value.new_empty(max_size,
|
|
value.size()[1]) for _ in range(dist.get_world_size(group=dp_group))
|
|
]
|
|
|
|
dist.all_gather(tensor_list, value, group=dp_group)
|
|
tensors = []
|
|
for dev_idx, t in enumerate(tensor_list):
|
|
size = all_sizes[dev_idx][0]
|
|
tensors.append(t.index_select(0, torch.arange(size, dtype=torch.long, device=self.device)))
|
|
|
|
return tensors
|
|
|
|
def all_gather_scalar(self, value, dp_group):
|
|
tensor_list = [value.new_zeros(value.size()) for _ in range(dist.get_world_size(group=dp_group))]
|
|
dist.all_gather(tensor_list, value, group=dp_group)
|
|
return tensor_list
|
|
|
|
def module_state_dict(self, destination=None, prefix="", keep_vars=False, exclude_frozen_parameters=False):
|
|
sd = self.module.state_dict(destination=destination, prefix=prefix, keep_vars=keep_vars)
|
|
|
|
# Remove frozen parameter weights from state_dict if specified
|
|
if exclude_frozen_parameters:
|
|
for n, p in self.module.named_parameters():
|
|
if not p.requires_grad and n in sd:
|
|
del sd[n]
|
|
|
|
if self.random_ltd_enabled():
|
|
sd = remove_random_ltd_state_dict(sd)
|
|
return sd
|
|
|
|
@staticmethod
|
|
def _make_autoep_folding_metadata(folding_spec,
|
|
*,
|
|
family,
|
|
ep_rank,
|
|
zero_partition_group,
|
|
zero_partition_rank,
|
|
zero_partition_count,
|
|
param_families=None):
|
|
from deepspeed.checkpoint.autoep_universal import make_folding_metadata
|
|
|
|
return make_folding_metadata(tp_size=folding_spec.tp_size,
|
|
tp_rank=groups.get_tensor_model_parallel_rank(),
|
|
ep_size=folding_spec.ep_size,
|
|
ep_rank=ep_rank,
|
|
zero_partition_group=zero_partition_group,
|
|
zero_partition_rank=zero_partition_rank,
|
|
zero_partition_count=zero_partition_count,
|
|
family=family,
|
|
param_families=param_families)
|
|
|
|
@staticmethod
|
|
def _autoep_non_expert_param_families(state_dict):
|
|
families = {}
|
|
for key in state_dict.keys():
|
|
if ".router." in key:
|
|
families[key] = "router_gate_replicated"
|
|
elif ".shared_experts" in key:
|
|
families[key] = "shared_expert"
|
|
else:
|
|
families[key] = "dense"
|
|
return families
|
|
|
|
@staticmethod
|
|
def _autoep_param_family(param_name):
|
|
if ".experts." in param_name:
|
|
return "routed_expert"
|
|
if ".router." in param_name:
|
|
return "router_gate_replicated"
|
|
if ".shared_experts" in param_name:
|
|
return "shared_expert"
|
|
return "dense"
|
|
|
|
def _autoep_zero_optimizer_param_families(self):
|
|
optimizer = self.optimizer
|
|
real_dp_groups = getattr(optimizer, "real_dp_process_group", [])
|
|
partition_counts = getattr(optimizer, "partition_count", [])
|
|
param_families = {}
|
|
for group_idx, param_shapes in enumerate(self._get_zero_param_shapes()):
|
|
process_group = real_dp_groups[group_idx] if group_idx < len(
|
|
real_dp_groups) else optimizer.dp_process_group
|
|
partition_count = (partition_counts[group_idx]
|
|
if group_idx < len(partition_counts) else dist.get_world_size(group=process_group))
|
|
partition_rank = dist.get_rank(group=process_group)
|
|
for param_name in param_shapes.keys():
|
|
family = DeepSpeedEngine._autoep_param_family(param_name)
|
|
zero_partition_group = "edp" if family == "routed_expert" else "dense_dp"
|
|
param_families[param_name] = {
|
|
"family": family,
|
|
"zero_partition_group": zero_partition_group,
|
|
"zero_partition_rank": partition_rank,
|
|
"zero_partition_count": partition_count,
|
|
}
|
|
return param_families
|
|
|
|
@staticmethod
|
|
def _validate_autoep_folding_checkpoint_metadata(state,
|
|
*,
|
|
folding_spec,
|
|
family,
|
|
zero_partition_group,
|
|
zero_partition_count,
|
|
tp_rank=None,
|
|
ep_rank=None,
|
|
zero_partition_rank=None,
|
|
param_families=None,
|
|
require_when_folded=True):
|
|
has_metadata = isinstance(state, dict) and FOLDING_METADATA_KEY in state
|
|
folded_runtime = folding_spec is not None and folding_spec.tp_size > 1
|
|
if has_metadata and not folded_runtime:
|
|
raise RuntimeError("Folded AutoEP+AutoTP checkpoint requires a folded runtime with matching "
|
|
"tensor_parallel.autotp_size and expert_parallel.autoep_size.")
|
|
if not folded_runtime:
|
|
return
|
|
if require_when_folded and not has_metadata:
|
|
raise RuntimeError("Missing AutoEP+AutoTP folding metadata in folded checkpoint.")
|
|
if not has_metadata:
|
|
return
|
|
|
|
from deepspeed.checkpoint.autoep_universal import validate_folding_metadata
|
|
|
|
validate_folding_metadata(state,
|
|
tp_size=folding_spec.tp_size,
|
|
ep_size=folding_spec.ep_size,
|
|
etp_size=folding_spec.etp_size,
|
|
etp_rank=0,
|
|
tp_rank=tp_rank,
|
|
ep_rank=ep_rank,
|
|
zero_partition_group=zero_partition_group,
|
|
zero_partition_rank=zero_partition_rank,
|
|
zero_partition_count=zero_partition_count,
|
|
param_families=param_families,
|
|
family=family,
|
|
shared_expert_placement="tp_sharded",
|
|
dispatch_strategy="route_full_partition_dispatch")
|
|
|
|
@staticmethod
|
|
def load_moe_state_dict(checkpoint_path,
|
|
tag,
|
|
state_dict,
|
|
old_moe_load,
|
|
model=None,
|
|
mpu=None,
|
|
num_experts=1,
|
|
checkpoint_engine=TorchCheckpointEngine(),
|
|
autoep_layers=None,
|
|
folding_spec=None):
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
|
|
has_autoep_layers = _AutoEPMoELayer is not None and model is not None and any(
|
|
isinstance(m, _AutoEPMoELayer) for _, m in model.named_modules())
|
|
folded_autoep_tp = folding_spec is not None and folding_spec.tp_size > 1
|
|
|
|
if old_moe_load:
|
|
if has_autoep_layers:
|
|
raise RuntimeError("Legacy checkpoint format (old_moe_load) is incompatible with AutoEP layers. "
|
|
"Use Universal Checkpointing to convert the checkpoint first.")
|
|
expp_rank = groups._get_expert_data_parallel_rank(groups._get_max_expert_size_name())
|
|
|
|
num_local_experts = max(num_experts) // groups._get_expert_parallel_world_size(
|
|
groups._get_max_expert_size_name())
|
|
for local_expert_id in range(num_local_experts):
|
|
global_expert_id = expp_rank * num_local_experts + local_expert_id
|
|
expert_state_dict = checkpoint_engine.load(
|
|
DeepSpeedEngine._get_expert_ckpt_name(
|
|
checkpoint_path,
|
|
-1, # -1 means ignore layer_id
|
|
global_expert_id,
|
|
tag,
|
|
mpu),
|
|
map_location=torch.device('cpu'))
|
|
|
|
# Updating global -> local expert ids
|
|
moe_str_prefix = '.deepspeed_moe.experts.deepspeed_experts.'
|
|
for key in list(expert_state_dict.keys()):
|
|
local_key = key.replace(f'{moe_str_prefix}{global_expert_id}',
|
|
f'{moe_str_prefix}{local_expert_id}')
|
|
expert_state_dict[local_key] = expert_state_dict.pop(key)
|
|
state_dict.update(expert_state_dict)
|
|
|
|
else:
|
|
# Validate AutoEP metadata if present
|
|
if autoep_layers is not None:
|
|
if not isinstance(autoep_layers, list):
|
|
raise RuntimeError(
|
|
f"ds_autoep_layers metadata is malformed: expected list, got {type(autoep_layers).__name__}")
|
|
seen_ids = set()
|
|
required_fields = {
|
|
'moe_layer_id', 'module_path', 'num_experts', 'num_local_experts', 'ep_size', 'expert_key_prefix'
|
|
}
|
|
for entry in autoep_layers:
|
|
if not isinstance(entry, dict):
|
|
raise RuntimeError(
|
|
f"ds_autoep_layers entry is malformed: expected dict, got {type(entry).__name__}")
|
|
missing = required_fields - entry.keys()
|
|
if missing:
|
|
raise RuntimeError(f"ds_autoep_layers entry is invalid: missing fields {sorted(missing)}")
|
|
lid = entry['moe_layer_id']
|
|
if lid in seen_ids:
|
|
raise RuntimeError(f"ds_autoep_layers metadata has duplicate moe_layer_id: {lid}")
|
|
seen_ids.add(lid)
|
|
elif has_autoep_layers:
|
|
logger.warning("Checkpoint does not contain ds_autoep_layers metadata. "
|
|
"Loading AutoEP expert weights using best-effort module detection.")
|
|
|
|
moe_layer_id = 0
|
|
for n_module, module in model.named_modules():
|
|
if isinstance(module, MoE): # and deepspeed.comm.get_rank() == 0:
|
|
group_name = module.expert_group_name
|
|
num_local_experts = module.num_local_experts
|
|
expp_rank = groups._get_expert_parallel_rank(group_name)
|
|
# loop all local_experts
|
|
for local_expert_id in range(num_local_experts):
|
|
global_expert_id = expp_rank * num_local_experts + local_expert_id
|
|
expert_state_dict = checkpoint_engine.load(DeepSpeedEngine._get_expert_ckpt_name(
|
|
checkpoint_path, moe_layer_id, global_expert_id, tag, mpu),
|
|
map_location=torch.device('cpu'))
|
|
# print(expert_state_dict.keys())
|
|
# Updating global -> local expert ids
|
|
moe_str_prefix = '.deepspeed_moe.experts.deepspeed_experts.'
|
|
for key in list(expert_state_dict.keys()):
|
|
local_key = key.replace(f'{moe_str_prefix}{global_expert_id}',
|
|
f'{moe_str_prefix}{local_expert_id}')
|
|
expert_state_dict[local_key] = expert_state_dict.pop(key)
|
|
state_dict.update(expert_state_dict)
|
|
moe_layer_id += 1
|
|
|
|
elif _AutoEPMoELayer is not None and isinstance(module, _AutoEPMoELayer):
|
|
group_name = module.ep_group_name
|
|
num_local_experts = module.num_local_experts
|
|
expp_rank = groups._get_expert_parallel_rank(group_name)
|
|
exp_dp_rank = groups._get_expert_data_parallel_rank(group_name)
|
|
module_prefix = f"{n_module}." if n_module else ""
|
|
|
|
# Collect per-expert tensors to stack
|
|
stacked = {wname: [] for wname in ('w1', 'w2', 'w3')}
|
|
|
|
for local_expert_id in range(num_local_experts):
|
|
global_expert_id = expp_rank * num_local_experts + local_expert_id
|
|
expert_ckpt_path = DeepSpeedEngine._get_expert_ckpt_name(checkpoint_path, moe_layer_id,
|
|
global_expert_id, tag, mpu)
|
|
if not os.path.exists(expert_ckpt_path):
|
|
raise FileNotFoundError(f"Expert checkpoint file not found: {expert_ckpt_path}. "
|
|
f"Expected layer_{moe_layer_id} expert_{global_expert_id}.")
|
|
expert_sd = checkpoint_engine.load(expert_ckpt_path, map_location=torch.device('cpu'))
|
|
DeepSpeedEngine._validate_autoep_folding_checkpoint_metadata(
|
|
expert_sd,
|
|
folding_spec=folding_spec,
|
|
family="routed_expert",
|
|
zero_partition_group="edp",
|
|
zero_partition_count=folding_spec.edp_size if folded_autoep_tp else None,
|
|
tp_rank=groups.get_tensor_model_parallel_rank() if folded_autoep_tp else None,
|
|
ep_rank=expp_rank if folded_autoep_tp else None)
|
|
|
|
for wname in ('w1', 'w2', 'w3'):
|
|
fused_key = f"{module_prefix}experts.{wname}"
|
|
expert_key = f"{fused_key}.{global_expert_id}"
|
|
if expert_key not in expert_sd:
|
|
raise RuntimeError(f"Expert checkpoint file is corrupt: key '{expert_key}' not found "
|
|
f"in {expert_ckpt_path}")
|
|
tensor = expert_sd[expert_key]
|
|
if tensor.dim() != 2:
|
|
raise RuntimeError(f"Expert checkpoint file is corrupt: expected 2D tensor for "
|
|
f"'{expert_key}', got {tensor.dim()}D in {expert_ckpt_path}")
|
|
stacked[wname].append(tensor)
|
|
|
|
# Stack back to fused [E_local, ...] format
|
|
for wname in ('w1', 'w2', 'w3'):
|
|
fused_key = f"{module_prefix}experts.{wname}"
|
|
state_dict[fused_key] = torch.stack(stacked[wname], dim=0)
|
|
|
|
moe_layer_id += 1
|
|
|
|
def load_module_state_dict(self,
|
|
checkpoint,
|
|
strict=True,
|
|
custom_load_fn=None,
|
|
fetch_z3_params=False,
|
|
z3_params_to_fetch=None,
|
|
allowed_missing_keys=None):
|
|
if z3_params_to_fetch is not None:
|
|
params_to_fetch = [
|
|
p for p in z3_params_to_fetch if hasattr(p, 'ds_id') and p.ds_status == ZeroParamStatus.NOT_AVAILABLE
|
|
]
|
|
elif fetch_z3_params:
|
|
params_to_fetch = [
|
|
p for p in self.module.parameters()
|
|
if hasattr(p, 'ds_id') and p.ds_status == ZeroParamStatus.NOT_AVAILABLE
|
|
]
|
|
else:
|
|
params_to_fetch = []
|
|
|
|
with deepspeed.zero.GatheredParameters(params_to_fetch, modifier_rank=0):
|
|
module_state_dict = checkpoint['module']
|
|
if custom_load_fn:
|
|
custom_load_fn(src=module_state_dict, dst=self.module)
|
|
else:
|
|
load_result = self.module.load_state_dict(
|
|
module_state_dict, # TODO
|
|
strict=strict and allowed_missing_keys is None)
|
|
# The expert-key allowance only tightens strict loads; a caller
|
|
# passing strict=False keeps the usual non-strict semantics.
|
|
if strict and allowed_missing_keys is not None:
|
|
missing_keys = set(load_result.missing_keys)
|
|
unexpected_keys = set(load_result.unexpected_keys)
|
|
unexpected_missing = missing_keys - set(allowed_missing_keys)
|
|
if unexpected_missing or unexpected_keys:
|
|
raise RuntimeError("Checkpoint module state did not match the model outside AutoEP expert "
|
|
f"parameters: missing={sorted(unexpected_missing)}, "
|
|
f"unexpected={sorted(unexpected_keys)}")
|
|
|
|
if checkpoint.get(FROZEN_PARAM_FRAGMENTS, None) is not None:
|
|
saved_frozen_params = checkpoint[FROZEN_PARAM_FRAGMENTS]
|
|
for param in self.module.parameters():
|
|
if param.requires_grad:
|
|
continue
|
|
if param not in self.param_names:
|
|
raise ValueError(f"failed to find frozen {param} in named params")
|
|
name = self.param_names[param]
|
|
if hasattr(param, 'ds_id'):
|
|
param.ds_tensor.data.copy_(saved_frozen_params[name].data)
|
|
else:
|
|
param.data.copy_(saved_frozen_params[name].data)
|
|
|
|
def _get_zero_ckpt_prefix(self, dp_rank, bf16_mode):
|
|
return f'{"bf16_" if bf16_mode else ""}zero_pp_rank_{dp_rank}'
|
|
|
|
def _get_rank_zero_ckpt_name(self, checkpoints_path, tag, mp_rank, dp_rank, bf16_mode):
|
|
file_prefix = self._get_zero_ckpt_prefix(dp_rank, bf16_mode=bf16_mode)
|
|
zero_ckpt_name = os.path.join(
|
|
checkpoints_path,
|
|
str(tag),
|
|
f"{file_prefix}_mp_rank_{mp_rank:02d}_optim_states.pt",
|
|
)
|
|
return zero_ckpt_name
|
|
|
|
def _get_zero_ckpt_name(self, checkpoints_path, tag):
|
|
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
|
pp_rank = dist.get_rank(group=self.optimizer.dp_process_group)
|
|
bf16_mode = self.bfloat16_enabled()
|
|
return self._get_rank_zero_ckpt_name(checkpoints_path, tag, mp_rank, pp_rank, bf16_mode)
|
|
|
|
def _get_ckpt_name(self, checkpoints_path, tag, mp_placeholder=None, pp_placeholder=None):
|
|
if mp_placeholder is not None:
|
|
mp_rank_str = mp_placeholder
|
|
else:
|
|
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
|
mp_rank_str = f"{mp_rank:02d}"
|
|
|
|
if self.zero_optimization_partition_weights():
|
|
if pp_placeholder is not None:
|
|
pp_rank = pp_placeholder
|
|
else:
|
|
pp_rank = dist.get_rank(group=self.optimizer.dp_process_group)
|
|
|
|
filename = "zero_pp_rank_{}".format(pp_rank)
|
|
ckpt_name = os.path.join(
|
|
checkpoints_path,
|
|
str(tag),
|
|
f"{filename}_mp_rank_{mp_rank_str}_model_states.pt",
|
|
)
|
|
else:
|
|
ckpt_name = os.path.join(
|
|
checkpoints_path,
|
|
str(tag),
|
|
"mp_rank_" + mp_rank_str + "_model_states.pt",
|
|
)
|
|
return ckpt_name
|
|
|
|
def _get_optimizer_ckpt_name(self, checkpoints_path, tag, expp_rank):
|
|
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
|
ckpt_name = os.path.join(checkpoints_path, str(tag),
|
|
f'expp_rank_{expp_rank}_mp_rank_{mp_rank:02d}_optim_states.pt')
|
|
return ckpt_name
|
|
|
|
@staticmethod
|
|
def _get_expert_ckpt_name(checkpoints_path, layer_id, expert_id, tag, mpu=None):
|
|
mp_rank = 0 if mpu is None else mpu.get_model_parallel_rank()
|
|
if layer_id <= -1:
|
|
# Used to support old checkpoint loading
|
|
ckpt_name = os.path.join(checkpoints_path, '' if tag is None else str(tag),
|
|
f'expert_{expert_id}_mp_rank_{mp_rank:02d}_model_states.pt')
|
|
else:
|
|
# Used to support new checkpoint loading
|
|
ckpt_name = os.path.join(checkpoints_path, '' if tag is None else str(tag),
|
|
f'layer_{layer_id}_expert_{expert_id}_mp_rank_{mp_rank:02d}_model_states.pt')
|
|
return ckpt_name
|
|
|
|
def _get_all_ckpt_names(self, checkpoints_path, tag):
|
|
# It is required that (checkpoints_path, tag) are consistent among all ranks.
|
|
ckpt_file_pattern = self._get_ckpt_name(checkpoints_path,
|
|
tag,
|
|
mp_placeholder="*",
|
|
pp_placeholder="0" if self.load_universal_checkpoint() else None)
|
|
import glob
|
|
|
|
ckpt_files = glob.glob(ckpt_file_pattern)
|
|
ckpt_files.sort()
|
|
return ckpt_files
|
|
|
|
def load_checkpoint(self,
|
|
load_dir,
|
|
tag=None,
|
|
load_module_strict=True,
|
|
load_optimizer_states=True,
|
|
load_lr_scheduler_states=True,
|
|
load_module_only=False,
|
|
custom_load_fn=None):
|
|
"""
|
|
Load training checkpoint
|
|
|
|
Arguments:
|
|
load_dir: Required. Directory to load the checkpoint from
|
|
tag: Checkpoint tag used as a unique identifier for checkpoint, if not provided will attempt to load tag in 'latest' file
|
|
load_module_strict: Optional. Boolean to strictly enforce that the keys in state_dict of module and checkpoint match.
|
|
load_optimizer_states: Optional. Boolean to load the training optimizer states from Checkpoint. Ex. ADAM's momentum and variance
|
|
load_lr_scheduler_states: Optional. Boolean to add the learning rate scheduler states from Checkpoint.
|
|
load_module_only: Optional. Boolean to load only the model weights from the checkpoint. Ex. warmstarting.
|
|
custom_load_fn: Optional. Custom model load function.
|
|
|
|
Returns:
|
|
A tuple of ``load_path`` and ``client_state``.
|
|
*``load_path``: Path of the loaded checkpoint. ``None`` if loading the checkpoint failed.
|
|
*``client_state``: State dictionary used for loading required training states in the client code.
|
|
|
|
Important: under ZeRO3, one cannot load checkpoint with ``engine.load_checkpoint()`` right
|
|
after ``engine.save_checkpoint()``. It is because ``engine.module`` is partitioned, and
|
|
``load_checkpoint()`` wants a pristine model. If insisting to do so, please reinitialize engine
|
|
before ``load_checkpoint()``.
|
|
|
|
"""
|
|
|
|
if tag is None:
|
|
latest_tag = "latest_universal" if self.load_universal_checkpoint() else "latest"
|
|
latest_path = os.path.join(load_dir, latest_tag)
|
|
if os.path.isfile(latest_path):
|
|
with open(latest_path, "r") as fd:
|
|
tag = fd.read().strip()
|
|
else:
|
|
if self.load_universal_checkpoint():
|
|
raise ValueError(f'Invalid for universal checkpoint: {latest_path} does not exist')
|
|
else:
|
|
logger.warning(
|
|
f"Unable to find latest file at {latest_path}, if trying to load latest "
|
|
"checkpoint please ensure this file exists or pass an explicit checkpoint tag when loading a checkpoint."
|
|
)
|
|
return None, None
|
|
|
|
if self._optimizer_has_ckpt_event_prologue():
|
|
# Prepare for checkpoint load by ensuring all parameters are partitioned
|
|
self.optimizer.checkpoint_event_prologue()
|
|
|
|
load_path, client_states = self._load_checkpoint(load_dir,
|
|
tag,
|
|
load_module_strict=load_module_strict,
|
|
load_optimizer_states=load_optimizer_states,
|
|
load_lr_scheduler_states=load_lr_scheduler_states,
|
|
load_module_only=load_module_only,
|
|
custom_load_fn=custom_load_fn)
|
|
|
|
load_zero_checkpoint = load_path is not None and self.zero_optimization()
|
|
if load_zero_checkpoint and not self.zero_nvme_offload_optimizer():
|
|
autoep_zero3_partition_native_load = self.has_moe_layers and self.zero_optimization_partition_weights()
|
|
if ((load_optimizer_states and not load_module_only) or self.load_universal_checkpoint()
|
|
or autoep_zero3_partition_native_load):
|
|
success = self._load_zero_checkpoint(load_dir,
|
|
tag,
|
|
load_optimizer_states=load_optimizer_states
|
|
and not load_module_only)
|
|
else:
|
|
success = False
|
|
if not success:
|
|
self.optimizer._restore_from_bit16_weights()
|
|
|
|
if self.zero_nvme_offload_optimizer():
|
|
from shutil import copytree, disk_usage
|
|
rank = self.local_rank if self.use_node_local_storage() else self.global_rank
|
|
rank_dir = "rank" + dp_index_to_str(rank)
|
|
offload_dir = self.optimizer.optimizer_swapper.swap_folder
|
|
offload_ckpt_dir = os.path.join(load_dir, tag, "offloaded_tensors", rank_dir)
|
|
_, _, free = disk_usage(offload_dir)
|
|
logger.info(
|
|
f"Copying NVMe offload checkpoint from {offload_ckpt_dir} to {offload_dir}, {free / 1e9:,.2f} GB free on target filesystem..."
|
|
)
|
|
copytree(offload_ckpt_dir, offload_dir, dirs_exist_ok=True)
|
|
_, _, free = disk_usage(offload_dir)
|
|
logger.info(f"Copying complete! {free / 1e9:,.2f} GB free on target filesystem")
|
|
self.optimizer.reset_swap_buffers()
|
|
|
|
if self._optimizer_has_ckpt_event_epilogue():
|
|
self.optimizer.checkpoint_event_epilogue()
|
|
|
|
if self.load_universal_checkpoint() and not self.zero_optimization_partition_weights():
|
|
self.optimizer.update_lp_params()
|
|
|
|
return load_path, client_states
|
|
|
|
@staticmethod
|
|
def _uses_autoep_zero3_partitioned_experts(autoep_layers):
|
|
if not isinstance(autoep_layers, list):
|
|
return False
|
|
DeepSpeedEngine._validate_autoep_zero3_partitioned_metadata(autoep_layers, require_partitioned=False)
|
|
return any(is_autoep_zero3_partitioned_entry(entry) for entry in autoep_layers)
|
|
|
|
@staticmethod
|
|
def _validate_autoep_zero3_partitioned_metadata(autoep_layers, model=None, require_partitioned=True):
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
|
|
expected_expert_prefixes = None
|
|
if _AutoEPMoELayer is not None and model is not None:
|
|
expected_expert_prefixes = {
|
|
module_name: f"{module_name}.experts" if module_name else "experts"
|
|
for module_name, module in model.named_modules() if isinstance(module, _AutoEPMoELayer)
|
|
}
|
|
if not expected_expert_prefixes:
|
|
expected_expert_prefixes = None
|
|
|
|
validate_autoep_zero3_partitioned_metadata(autoep_layers,
|
|
require_partitioned=require_partitioned,
|
|
expected_expert_prefixes=expected_expert_prefixes,
|
|
version_context="This DeepSpeed build")
|
|
|
|
@staticmethod
|
|
def _autoep_expert_parameter_names(autoep_layers, model):
|
|
names = set()
|
|
if isinstance(autoep_layers, list):
|
|
DeepSpeedEngine._validate_autoep_zero3_partitioned_metadata(autoep_layers, model=model)
|
|
for entry in autoep_layers:
|
|
if not isinstance(entry, dict):
|
|
continue
|
|
if not is_autoep_zero3_partitioned_entry(entry):
|
|
continue
|
|
prefix = entry.get('expert_key_prefix')
|
|
if prefix:
|
|
names.update(f"{prefix}.{wname}" for wname in ('w1', 'w2', 'w3'))
|
|
|
|
if names:
|
|
return names
|
|
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
if _AutoEPMoELayer is None or model is None:
|
|
return names
|
|
|
|
for module_name, module in model.named_modules():
|
|
if not isinstance(module, _AutoEPMoELayer):
|
|
continue
|
|
module_prefix = f"{module_name}." if module_name else ""
|
|
names.update(f"{module_prefix}experts.{wname}" for wname in ('w1', 'w2', 'w3'))
|
|
return names
|
|
|
|
def _load_checkpoint(self,
|
|
load_dir,
|
|
tag,
|
|
load_module_strict=True,
|
|
load_optimizer_states=True,
|
|
load_lr_scheduler_states=True,
|
|
load_module_only=False,
|
|
custom_load_fn=None):
|
|
|
|
from deepspeed.runtime.state_dict_factory import SDLoaderFactory
|
|
|
|
ckpt_list = self._get_all_ckpt_names(load_dir, tag)
|
|
sd_loader = SDLoaderFactory.get_sd_loader(ckpt_list, checkpoint_engine=self.checkpoint_engine)
|
|
|
|
is_pipe_parallel = isinstance(self.module, PipelineModule)
|
|
|
|
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
|
load_path, checkpoint, _ = sd_loader.load(self.mp_world_size, mp_rank, is_pipe_parallel=is_pipe_parallel)
|
|
|
|
if checkpoint is None:
|
|
return None, None
|
|
|
|
folding_spec = getattr(self, "_autoep_folding_spec", None)
|
|
folded_autoep_tp = folding_spec is not None and folding_spec.tp_size > 1
|
|
ep_group_name = f"ep_size_{folding_spec.ep_size}" if folded_autoep_tp else None
|
|
DeepSpeedEngine._validate_autoep_folding_checkpoint_metadata(
|
|
checkpoint,
|
|
folding_spec=folding_spec,
|
|
family="dense",
|
|
zero_partition_group="dense_dp",
|
|
zero_partition_count=folding_spec.dp_size if folded_autoep_tp else None,
|
|
tp_rank=groups.get_tensor_model_parallel_rank() if folded_autoep_tp else None)
|
|
|
|
fetch_z3_params = False
|
|
z3_params_to_fetch = None
|
|
autoep_partitioned_experts = False
|
|
allowed_missing_keys = None
|
|
if self.zero_optimization_partition_weights() and not load_optimizer_states and not self.has_moe_layers:
|
|
checkpoint['module'] = get_fp32_state_dict_from_zero_checkpoint(load_dir)
|
|
fetch_z3_params = True
|
|
|
|
if is_pipe_parallel:
|
|
# Pipeline parallelism uses this to load its own checkpoint files.
|
|
self._curr_ckpt_path = os.path.join(load_dir, tag)
|
|
|
|
# Universal Checkpoint restores parameters from the zero/ layout, so
|
|
# do not require regular MoE expert checkpoint files in that path.
|
|
if self.has_moe_layers and not self.load_universal_checkpoint():
|
|
# print(checkpoint.keys())
|
|
old_moe_load = False
|
|
if not isinstance(checkpoint['num_experts'], list):
|
|
old_moe_load = True
|
|
from deepspeed.checkpoint.constants import AUTOEP_LAYERS_KEY, AUTOEP_LAYERS_KEY_LEGACY
|
|
autoep_layers = checkpoint.get(AUTOEP_LAYERS_KEY)
|
|
if autoep_layers is None:
|
|
autoep_layers = checkpoint.get(AUTOEP_LAYERS_KEY_LEGACY)
|
|
autoep_partitioned_experts = (self.zero_optimization_partition_weights()
|
|
and DeepSpeedEngine._uses_autoep_zero3_partitioned_experts(autoep_layers))
|
|
if autoep_partitioned_experts:
|
|
allowed_missing_keys = DeepSpeedEngine._autoep_expert_parameter_names(autoep_layers, self.module)
|
|
if not allowed_missing_keys:
|
|
raise RuntimeError("AutoEP ZeRO-3 partition-native checkpoint metadata did not identify any "
|
|
"live expert parameters to restore from ZeRO shards.")
|
|
else:
|
|
DeepSpeedEngine.load_moe_state_dict(load_dir,
|
|
tag,
|
|
state_dict=checkpoint['module'],
|
|
old_moe_load=old_moe_load,
|
|
model=self.module,
|
|
mpu=self.mpu,
|
|
num_experts=self.num_experts,
|
|
checkpoint_engine=self.checkpoint_engine,
|
|
autoep_layers=autoep_layers,
|
|
folding_spec=folding_spec)
|
|
if self.zero_optimization_partition_weights():
|
|
z3_params_to_fetch = []
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
if _AutoEPMoELayer is not None:
|
|
for _, module in self.module.named_modules():
|
|
if isinstance(module, _AutoEPMoELayer):
|
|
z3_params_to_fetch.extend(module.experts.parameters())
|
|
if not self.load_universal_checkpoint():
|
|
self.load_module_state_dict(checkpoint=checkpoint,
|
|
strict=load_module_strict,
|
|
custom_load_fn=custom_load_fn,
|
|
fetch_z3_params=fetch_z3_params,
|
|
z3_params_to_fetch=z3_params_to_fetch,
|
|
allowed_missing_keys=allowed_missing_keys)
|
|
|
|
self.loaded_checkpoint_dp_world_size = checkpoint['dp_world_size']
|
|
|
|
optim_checkpoint = None
|
|
if load_module_only:
|
|
deepspeed_states = ['module']
|
|
if self.optimizer is not None and hasattr(self.optimizer, 'refresh_fp32_params'):
|
|
self.optimizer.refresh_fp32_params()
|
|
else:
|
|
has_zero_optimizer_state = self.zero_optimization()
|
|
if load_optimizer_states and self.optimizer is not None and not has_zero_optimizer_state:
|
|
if self.has_moe_layers:
|
|
largest_group_name = groups._get_max_expert_size_name()
|
|
expp_rank = groups._get_expert_parallel_rank(largest_group_name)
|
|
exp_dp_rank = groups._get_expert_data_parallel_rank(largest_group_name)
|
|
optim_load_path = self._get_optimizer_ckpt_name(load_dir, tag, expp_rank)
|
|
optim_checkpoint = self.checkpoint_engine.load(optim_load_path, map_location=torch.device('cpu'))
|
|
DeepSpeedEngine._validate_autoep_folding_checkpoint_metadata(
|
|
optim_checkpoint,
|
|
folding_spec=folding_spec,
|
|
family="routed_expert",
|
|
zero_partition_group="edp",
|
|
zero_partition_count=folding_spec.edp_size if folded_autoep_tp else None,
|
|
tp_rank=groups.get_tensor_model_parallel_rank() if folded_autoep_tp else None,
|
|
ep_rank=expp_rank if folded_autoep_tp else None)
|
|
else:
|
|
optim_checkpoint = checkpoint
|
|
|
|
if self.fp16_enabled() or self.bfloat16_enabled():
|
|
self.optimizer.load_state_dict(optim_checkpoint['optimizer'],
|
|
load_optimizer_states=load_optimizer_states)
|
|
else:
|
|
self.optimizer.load_state_dict(optim_checkpoint['optimizer'])
|
|
|
|
if load_lr_scheduler_states and self.lr_scheduler is not None:
|
|
self.lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])
|
|
|
|
if self.random_ltd_enabled() and self.random_ltd_scheduler is not None and 'random_ltd' in checkpoint:
|
|
self.random_ltd_scheduler.load_state_dict(checkpoint['random_ltd'])
|
|
|
|
if self.training_dataloader is not None and self.curriculum_learning_enabled(
|
|
) and 'data_sampler' in checkpoint:
|
|
self.training_dataloader.data_sampler.load_state_dict(checkpoint['data_sampler'])
|
|
|
|
def get_sparse_tensor_module_names(original_set, loaded_set, original_parameters, loaded_parameters):
|
|
result = set()
|
|
|
|
for name in original_set:
|
|
if name in loaded_parameters and name not in loaded_set:
|
|
continue # parameter existed in previous model and was not sparse
|
|
result.add(name)
|
|
|
|
for name in loaded_set:
|
|
if name in original_parameters:
|
|
result.add(name) # parameter exists in both configs and it was sparse
|
|
|
|
return result
|
|
|
|
if 'sparse_tensor_module_names' in checkpoint:
|
|
sparse_tensor_module_names = checkpoint['sparse_tensor_module_names']
|
|
elif 'csr_tensor_module_names' in checkpoint:
|
|
sparse_tensor_module_names = checkpoint['csr_tensor_module_names']
|
|
else:
|
|
sparse_tensor_module_names = None
|
|
if sparse_tensor_module_names is not None:
|
|
if load_module_strict:
|
|
self.sparse_tensor_module_names = sparse_tensor_module_names
|
|
else:
|
|
self.sparse_tensor_module_names = get_sparse_tensor_module_names(
|
|
self.sparse_tensor_module_names, sparse_tensor_module_names,
|
|
dict(self.module.named_parameters()), checkpoint["module"])
|
|
|
|
self.global_steps = checkpoint['global_steps']
|
|
self.global_samples = checkpoint.get('global_samples', self.global_steps * self.train_batch_size())
|
|
self.skipped_steps = checkpoint['skipped_steps']
|
|
self.loaded_checkpoint_mp_world_size = checkpoint['mp_world_size']
|
|
deepspeed_states = [
|
|
'module', 'sparse_tensor_module_names', 'skipped_steps', 'global_steps', 'dp_world_size',
|
|
'mp_world_size', 'data_sampler', 'random_ltd'
|
|
]
|
|
client_state = {}
|
|
|
|
if load_lr_scheduler_states:
|
|
deepspeed_states.append('lr_scheduler')
|
|
if load_optimizer_states:
|
|
deepspeed_states.append('optimizer')
|
|
|
|
client_state = {key: value for key, value in checkpoint.items() if key not in deepspeed_states}
|
|
|
|
if optim_checkpoint is not None:
|
|
client_state['optimizer'] = optim_checkpoint['optimizer']
|
|
|
|
return load_path, client_state
|
|
|
|
def _load_zero_checkpoint(self, load_dir, tag, load_optimizer_states=True):
|
|
|
|
load_serial = None
|
|
# When use loading checkpoint serial, checkpoint loading start from local rank 0,
|
|
# all other local rank would be paused, waiting for its rank-1 peer ready and its notification.
|
|
if self._config.zero_config.pipeline_loading_checkpoint:
|
|
assert self.zero_optimization_stage(
|
|
) == ZeroStageEnum.weights, "Only stage3 support for pipeline checkpoint loading"
|
|
load_serial = torch.zeros(1).to(self.device)
|
|
if dist.get_local_rank() != 0:
|
|
dist.recv(tensor=load_serial, src=dist.get_rank() - 1)
|
|
if self.load_universal_checkpoint():
|
|
zero_sd_list = None
|
|
checkpoint_folder = f'{os.path.join(load_dir, tag)}'
|
|
else:
|
|
if load_optimizer_states and self.seq_dp_world_size != self.loaded_checkpoint_dp_world_size:
|
|
raise ZeRORuntimeException("The checkpoint being loaded used a DP " \
|
|
f"world size of {self.loaded_checkpoint_dp_world_size} but the " \
|
|
f"current world size is {self.seq_dp_world_size}. Automatic adjustment " \
|
|
"of ZeRO's optimizer state partitioning with a new world size is not " \
|
|
"currently supported.")
|
|
checkpoint_folder = None
|
|
zero_sd_list = self._get_all_zero_checkpoints(load_dir, tag)
|
|
if zero_sd_list is None:
|
|
return False
|
|
|
|
param_shapes = self._get_zero_param_shapes()
|
|
self.optimizer.load_state_dict(state_dict_list=zero_sd_list,
|
|
load_optimizer_states=load_optimizer_states,
|
|
load_from_fp32_weights=self.zero_load_from_fp32_weights(),
|
|
checkpoint_folder=checkpoint_folder,
|
|
load_serial=load_serial,
|
|
param_shapes=param_shapes)
|
|
|
|
if self.load_universal_checkpoint():
|
|
logger.info(f'loaded universal zero checkpoints from {checkpoint_folder} for rank {self.global_rank}')
|
|
else:
|
|
logger.info(f"loading {len(zero_sd_list)} zero partition checkpoints for rank {self.global_rank}")
|
|
return True
|
|
|
|
def _get_mp_rank_zero_checkpoint_names(self, load_dir, tag, mp_rank, dp_world_size, bf16_mode):
|
|
zero_ckpt_names = []
|
|
for dp_rank in range(dp_world_size):
|
|
ckpt_name = self._get_rank_zero_ckpt_name(checkpoints_path=load_dir,
|
|
tag=tag,
|
|
mp_rank=mp_rank,
|
|
dp_rank=dp_rank,
|
|
bf16_mode=bf16_mode)
|
|
zero_ckpt_names.append(ckpt_name)
|
|
|
|
return zero_ckpt_names
|
|
|
|
def _get_all_zero_checkpoint_names(self, load_dir, tag, bf16_mode):
|
|
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
|
zero_ckpt_names = self._get_mp_rank_zero_checkpoint_names(load_dir=load_dir,
|
|
tag=tag,
|
|
mp_rank=mp_rank,
|
|
dp_world_size=self.loaded_checkpoint_dp_world_size,
|
|
bf16_mode=bf16_mode)
|
|
for i, ckpt_name in enumerate(zero_ckpt_names):
|
|
if not os.path.exists(ckpt_name):
|
|
# transparently handle the old file pattern for optim_states
|
|
if "optim_states.pt" in ckpt_name:
|
|
ckpt_name_try = ckpt_name.replace("_optim_states.pt", "optim_states.pt")
|
|
if os.path.exists(ckpt_name_try):
|
|
zero_ckpt_names[i] = ckpt_name_try
|
|
continue
|
|
|
|
return zero_ckpt_names
|
|
|
|
def _get_all_zero_checkpoint_state_dicts(self, zero_ckpt_names):
|
|
zero_sd_list = []
|
|
folding_spec = getattr(self, "_autoep_folding_spec", None)
|
|
folded_autoep_tp = folding_spec is not None and folding_spec.tp_size > 1
|
|
zero_partition_count = None
|
|
zero_param_families = None
|
|
if folded_autoep_tp:
|
|
zero_partition_count = dist.get_world_size(group=self.optimizer.dp_process_group)
|
|
zero_param_families = self._autoep_zero_optimizer_param_families()
|
|
for i, ckpt_name in enumerate(zero_ckpt_names):
|
|
_state = None
|
|
if ckpt_name is None:
|
|
_state = {OPTIMIZER_STATE_DICT: None}
|
|
# Fully load state for current rank
|
|
elif self.zero_elastic_checkpoint() or dist.get_rank(group=self.optimizer.dp_process_group) == i:
|
|
_state = self.checkpoint_engine.load(
|
|
ckpt_name,
|
|
map_location='cpu',
|
|
)
|
|
else:
|
|
_state = {OPTIMIZER_STATE_DICT: None}
|
|
if _state.get(OPTIMIZER_STATE_DICT) is not None or FOLDING_METADATA_KEY in _state:
|
|
DeepSpeedEngine._validate_autoep_folding_checkpoint_metadata(
|
|
_state,
|
|
folding_spec=folding_spec,
|
|
family="zero_optimizer_state",
|
|
zero_partition_group="per_family",
|
|
zero_partition_count=zero_partition_count,
|
|
tp_rank=groups.get_tensor_model_parallel_rank() if folded_autoep_tp else None,
|
|
zero_partition_rank=i if folded_autoep_tp else None,
|
|
param_families=zero_param_families)
|
|
zero_sd_list.append(_state)
|
|
|
|
zero_optimizer_sd = [sd[OPTIMIZER_STATE_DICT] for sd in zero_sd_list]
|
|
logger.info(f"successfully read {len(zero_optimizer_sd)} ZeRO state_dicts for rank {self.global_rank}")
|
|
return zero_optimizer_sd
|
|
|
|
def _get_all_zero_checkpoints(self, load_dir, tag):
|
|
for bf16_mode in [self.bfloat16_enabled(), not self.bfloat16_enabled()]:
|
|
zero_ckpt_names = self._get_all_zero_checkpoint_names(load_dir, tag, bf16_mode)
|
|
if zero_ckpt_names is not None:
|
|
# Warn if loading checkpoint of different bit16 type
|
|
if bf16_mode is not self.bfloat16_enabled():
|
|
checkpoint_bit16 = BFLOAT16 if bf16_mode else FP16
|
|
engine_bit16 = BFLOAT16 if self.bfloat16_enabled() else FP16
|
|
logger.warning(f'Loading {checkpoint_bit16} zero checkpoints into {engine_bit16} training engine')
|
|
return self._get_all_zero_checkpoint_state_dicts(zero_ckpt_names)
|
|
|
|
return None
|
|
|
|
def _checkpoint_tag_validation(self, tag):
|
|
if self.checkpoint_tag_validation_enabled():
|
|
s_hash = hashlib.sha1(tag.encode())
|
|
bhash = torch.ByteTensor([s_hash.digest()]).flatten().to(self.device)
|
|
max_bhash = bhash.clone()
|
|
min_bhash = bhash.clone()
|
|
dist.all_reduce(max_bhash, op=dist.ReduceOp.MAX)
|
|
dist.all_reduce(min_bhash, op=dist.ReduceOp.MIN)
|
|
valid = all(min_bhash == bhash) and all(max_bhash == bhash)
|
|
msg = (f"[rank={dist.get_rank()}] The checkpoint tag name '{tag}' is not consistent across "
|
|
"all ranks. Including rank unique information in checkpoint tag could cause issues when "
|
|
"restoring with different world sizes.")
|
|
if self.checkpoint_tag_validation_fail():
|
|
assert valid, msg
|
|
elif not valid:
|
|
logger.warning(msg)
|
|
|
|
def save_checkpoint(self, save_dir, tag=None, client_state={}, save_latest=True, exclude_frozen_parameters=False):
|
|
"""Save training checkpoint
|
|
|
|
Arguments:
|
|
save_dir: Required. Directory for saving the checkpoint
|
|
tag: Optional. Checkpoint tag used as a unique identifier for the checkpoint, global step is
|
|
used if not provided. Tag name must be the same across all ranks.
|
|
client_state: Optional. State dictionary used for saving required training states in the client code.
|
|
save_latest: Optional. Save a file 'latest' pointing to the latest saved checkpoint.
|
|
exclude_frozen_parameters: Optional. Exclude frozen parameters from checkpointed state.
|
|
Important: all processes must call this method and not just the process with rank 0. It is
|
|
because each process needs to save its master weights and scheduler+optimizer states. This
|
|
method will hang waiting to synchronize with other processes if it's called just for the
|
|
process with rank 0.
|
|
|
|
"""
|
|
if not save_dir:
|
|
raise ValueError(f"save_dir must be a non-empty string, got {save_dir!r}")
|
|
|
|
if self._optimizer_has_ckpt_event_prologue():
|
|
# Custom preparation for checkpoint save, if applicable
|
|
self.optimizer.checkpoint_event_prologue()
|
|
|
|
rank = self.local_rank if self.use_node_local_storage() else self.global_rank
|
|
|
|
# This is to make sure the checkpoint names are created without collision
|
|
# There seems to be issue creating them in parallel
|
|
|
|
# Ensure save_dir directory exists
|
|
if rank == 0:
|
|
self.checkpoint_engine.makedirs(save_dir, exist_ok=True)
|
|
dist.barrier()
|
|
|
|
if tag is None:
|
|
tag = f"global_step{self.global_steps}"
|
|
|
|
# Ensure tag is a string
|
|
tag = str(tag)
|
|
commit_info = CheckpointCommitInfo(tag=tag, save_dir=save_dir, save_latest=save_latest)
|
|
|
|
self.checkpoint_engine.create(commit_info)
|
|
|
|
# Ensure checkpoint tag is consistent across ranks
|
|
self._checkpoint_tag_validation(tag)
|
|
|
|
if self.has_moe_layers:
|
|
self.save_non_zero_checkpoint = False
|
|
self._create_checkpoint_file(save_dir, tag, False)
|
|
self._save_moe_checkpoint(save_dir,
|
|
tag,
|
|
client_state=client_state,
|
|
exclude_frozen_parameters=exclude_frozen_parameters)
|
|
|
|
# We distribute the task of saving layer checkpoint files among
|
|
# data parallel instances, so all procs should call _save_checkpoint.
|
|
# All procs then call module_state_dict(), but only procs of data
|
|
# parallel rank 0 save the general model params.
|
|
if not self.has_moe_layers:
|
|
self._create_checkpoint_file(save_dir, tag, False)
|
|
self._save_checkpoint(save_dir,
|
|
tag,
|
|
client_state=client_state,
|
|
exclude_frozen_parameters=exclude_frozen_parameters)
|
|
|
|
if self.save_zero_checkpoint:
|
|
self._create_zero_checkpoint_files(save_dir, tag)
|
|
self._save_zero_checkpoint(save_dir, tag)
|
|
|
|
if self.zero_nvme_offload_optimizer():
|
|
from shutil import copytree, disk_usage
|
|
rank_dir = "rank" + dp_index_to_str(rank)
|
|
offload_dir = self.optimizer.optimizer_swapper.swap_folder
|
|
offload_ckpt_dir = os.path.join(save_dir, tag, "offloaded_tensors", rank_dir)
|
|
_, _, free = disk_usage(save_dir)
|
|
logger.info(
|
|
f"Copying NVMe offload files from {offload_dir} to {offload_ckpt_dir}, {free / 1e9:,.2f} GB free on target filesystem..."
|
|
)
|
|
copytree(offload_dir,
|
|
offload_ckpt_dir,
|
|
ignore=lambda _, dir_list: list(filter(lambda x: 'gradient' in x, dir_list)),
|
|
dirs_exist_ok=False)
|
|
_, _, free = disk_usage(save_dir)
|
|
logger.info(f"Copying complete! {free / 1e9:,.2f} GB free on target filesystem")
|
|
|
|
if self._optimizer_has_ckpt_event_epilogue():
|
|
self.optimizer.checkpoint_event_epilogue()
|
|
|
|
# Save latest checkpoint tag
|
|
if not self.checkpoint_engine.is_decoupled():
|
|
commit_info = CheckpointCommitInfo(tag=tag, save_dir=save_dir, save_latest=save_latest)
|
|
self.checkpoint_engine.commit(commit_info)
|
|
if save_latest and self.global_rank == 0:
|
|
with open(os.path.join(save_dir, 'latest'), 'w') as fd:
|
|
fd.write(tag)
|
|
|
|
dist.barrier()
|
|
|
|
return True
|
|
|
|
def _commit_decoupled_checkpoint(self):
|
|
assert self.checkpoint_engine.is_decoupled(), \
|
|
f'{self.checkpoint_engine} is not a Decoupled Checkpoint Engine'
|
|
|
|
commit_info = self.checkpoint_engine.get_commit_info()
|
|
if commit_info is None:
|
|
return
|
|
|
|
self.checkpoint_engine.commit(commit_info)
|
|
|
|
if self.global_rank == 0 and commit_info.save_latest:
|
|
with open(os.path.join(commit_info.save_dir, 'latest'), 'w') as fd:
|
|
fd.write(commit_info.tag)
|
|
|
|
dist.barrier()
|
|
|
|
def _get_non_moe_state_dict(self, full_state_dict):
|
|
"""Remove expert-param keys from state dict, keeping all non-expert params.
|
|
|
|
Handles both native MoE (deepspeed_moe.experts.*) and AutoEP (experts.w1/w2/w3).
|
|
Preserves: router weights, shared_experts, any legacy/manually-built
|
|
expert_bias keys, all non-MoE params.
|
|
"""
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
|
|
expert_param_keys = set()
|
|
|
|
for n_module, module in self.module.named_modules():
|
|
module_prefix = f"{n_module}." if n_module else ""
|
|
if isinstance(module, MoE):
|
|
# Native MoE: remove keys with 'expert' except gate, scoped to this module
|
|
for key in full_state_dict.keys():
|
|
if key.startswith(module_prefix) and 'expert' in key and 'moe.gate.wg.weight' not in key:
|
|
expert_param_keys.add(key)
|
|
elif _AutoEPMoELayer is not None and isinstance(module, _AutoEPMoELayer):
|
|
# AutoEP: remove only the fused expert weight keys (w1, w2, w3)
|
|
experts_prefix = f"{module_prefix}experts."
|
|
for key in full_state_dict.keys():
|
|
if key.startswith(experts_prefix) and key[len(experts_prefix):] in ('w1', 'w2', 'w3'):
|
|
expert_param_keys.add(key)
|
|
|
|
for key in expert_param_keys:
|
|
full_state_dict.pop(key)
|
|
|
|
return full_state_dict
|
|
|
|
def _common_checkpoint_state(self, module_state_dict, zero_optimizer_state, save_frozen_param):
|
|
return dict(module=module_state_dict,
|
|
buffer_names=self._get_buffer_names(),
|
|
optimizer=self.optimizer.state_dict() if self.optimizer and not zero_optimizer_state else None,
|
|
param_shapes=self._get_zero_param_shapes() if self.optimizer and zero_optimizer_state else None,
|
|
frozen_param_shapes=self._get_zero_frozen_param_attributes(self._get_param_shape_func)
|
|
if save_frozen_param else None,
|
|
shared_params=self._get_shared_params() if self.optimizer and zero_optimizer_state else None,
|
|
frozen_param_fragments=self._get_zero_frozen_param_attributes(self._get_param_fragment_func)
|
|
if save_frozen_param else None,
|
|
lr_scheduler=self.lr_scheduler.state_dict() if self.lr_scheduler is not None else None,
|
|
data_sampler=self.training_dataloader.data_sampler.state_dict() if
|
|
(self.training_dataloader is not None and self.curriculum_learning_enabled()) else None,
|
|
random_ltd=self.random_ltd_scheduler.state_dict() if self.random_ltd_enabled() else None,
|
|
sparse_tensor_module_names=self.sparse_tensor_module_names,
|
|
skipped_steps=self.skipped_steps,
|
|
global_steps=self.global_steps,
|
|
global_samples=self.global_samples,
|
|
dp_world_size=self.seq_dp_world_size,
|
|
mp_world_size=self.mp_world_size,
|
|
ds_config=self.config,
|
|
ds_version=version)
|
|
|
|
def _save_moe_checkpoint(self, save_dir, tag, client_state={}, exclude_frozen_parameters=False):
|
|
save_path = self._get_ckpt_name(save_dir, tag)
|
|
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
_AutoEPMoELayer = None
|
|
|
|
folding_spec = getattr(self, "_autoep_folding_spec", None)
|
|
folded_autoep_tp = folding_spec is not None and folding_spec.tp_size > 1
|
|
|
|
def folding_metadata(*,
|
|
family,
|
|
ep_rank,
|
|
zero_partition_group,
|
|
zero_partition_rank,
|
|
zero_partition_count,
|
|
param_families=None):
|
|
if not folded_autoep_tp:
|
|
return None
|
|
return DeepSpeedEngine._make_autoep_folding_metadata(folding_spec,
|
|
family=family,
|
|
ep_rank=ep_rank,
|
|
zero_partition_group=zero_partition_group,
|
|
zero_partition_rank=zero_partition_rank,
|
|
zero_partition_count=zero_partition_count,
|
|
param_families=param_families)
|
|
|
|
def autoep_expert_writer() -> bool:
|
|
if folded_autoep_tp:
|
|
return groups._get_data_parallel_rank() < folding_spec.ep_size
|
|
return self.checkpoint_engine.is_data_parallel_writer(exp_dp_rank)
|
|
|
|
# A hack to save the checkpointing directory. Pipeline parallelism overrides
|
|
# module_state_dict() and uses this path to save the model. module_state_dict()
|
|
# then instead just returns None.
|
|
|
|
# Using layer_#_export_# to save the model's expert state_dict
|
|
autoep_layer_info = []
|
|
autoep_group_names = set()
|
|
moe_layer_id = 0
|
|
found_native_moe = False
|
|
found_autoep = False
|
|
for n_module, module in self.module.named_modules():
|
|
if isinstance(module, MoE): # and deepspeed.comm.get_rank() == 0:
|
|
found_native_moe = True
|
|
if self.zero_optimization_partition_weights() and found_autoep:
|
|
raise RuntimeError("AutoEP with ZeRO Stage 3 checkpointing does not support models that also "
|
|
"contain native DeepSpeed MoE layers.")
|
|
group_name = module.expert_group_name
|
|
num_local_experts = module.num_local_experts
|
|
expp_rank = groups._get_expert_parallel_rank(group_name)
|
|
exp_dp_rank = groups._get_expert_data_parallel_rank(group_name)
|
|
# print(expp_rank, exp_dp_rank)
|
|
# if exp_dp_rank != 0:
|
|
if not self.checkpoint_engine.is_data_parallel_writer(exp_dp_rank):
|
|
moe_layer_id += 1
|
|
continue
|
|
|
|
# get all moe parameters
|
|
moe_state_dict = {}
|
|
for n, p in module.state_dict().items():
|
|
if 'expert' in n and 'moe.gate.wg.weight' not in n:
|
|
moe_state_dict[n_module + '.' + n] = p
|
|
moe_str_prefix = '.deepspeed_moe.experts.deepspeed_experts.'
|
|
# print(moe_state_dict.keys()) # until now, everything is fine. So the bug happens at next few lines
|
|
# Reorder the moe name rank, so that each checkpoint only has one expert
|
|
experts_state_dict = defaultdict(dict)
|
|
for key in list(moe_state_dict.keys()):
|
|
m = re.match(f".*{moe_str_prefix}([0-9]+).*", key)
|
|
|
|
local_expert_id = None
|
|
if not m:
|
|
logger.warning(f'No expert found in key {key}.')
|
|
else:
|
|
local_expert_id = m.group(1)
|
|
|
|
global_expert_id = expp_rank * \
|
|
num_local_experts + int(local_expert_id)
|
|
expert_key = key.replace(f'{moe_str_prefix}{local_expert_id}',
|
|
f'{moe_str_prefix}{global_expert_id}')
|
|
# truncating extra tensor (shared) storage
|
|
truncated = moe_state_dict.pop(key).clone().detach()
|
|
experts_state_dict[str(global_expert_id)][expert_key] = truncated
|
|
|
|
# let save the moe parameters
|
|
for global_expert_id, expert_state_dict in experts_state_dict.items():
|
|
# save the moe parameters
|
|
moe_save_path = self._get_expert_ckpt_name(save_dir, moe_layer_id, global_expert_id, tag, self.mpu)
|
|
if self.random_ltd_enabled():
|
|
expert_state_dict = remove_random_ltd_state_dict(expert_state_dict)
|
|
saveable_state_dict = expert_state_dict
|
|
if self.checkpoint_engine.preserves_storage_sharing():
|
|
saveable_state_dict = clone_tensors_for_torch_save(expert_state_dict)
|
|
self.checkpoint_engine.save(saveable_state_dict, moe_save_path)
|
|
moe_layer_id += 1
|
|
|
|
elif _AutoEPMoELayer is not None and isinstance(module, _AutoEPMoELayer):
|
|
found_autoep = True
|
|
if self.zero_optimization_partition_weights() and found_native_moe:
|
|
raise RuntimeError("AutoEP with ZeRO Stage 3 checkpointing does not support models that also "
|
|
"contain native DeepSpeed MoE layers.")
|
|
if self.zero_optimization_partition_weights() and self.zero_nvme_offload_optimizer():
|
|
raise RuntimeError("AutoEP with ZeRO Stage 3 checkpointing does not support NVMe optimizer "
|
|
"swapping yet because expert state is restored from ZeRO optimizer shards.")
|
|
group_name = module.ep_group_name
|
|
num_local_experts = module.num_local_experts
|
|
expp_rank = groups._get_expert_parallel_rank(group_name)
|
|
exp_dp_rank = groups._get_expert_data_parallel_rank(group_name)
|
|
module_prefix = f"{n_module}." if n_module else ""
|
|
expert_params = [getattr(module.experts, wname) for wname in ('w1', 'w2', 'w3')]
|
|
if self.zero_optimization_partition_weights():
|
|
frozen_expert_names = [
|
|
f"{module_prefix}experts.{wname}" for wname, param in zip(('w1', 'w2', 'w3'), expert_params)
|
|
if not param.requires_grad
|
|
]
|
|
if frozen_expert_names:
|
|
raise RuntimeError("AutoEP with ZeRO Stage 3 checkpointing does not support frozen expert "
|
|
"parameters yet because frozen fragments are not stored in ZeRO optimizer "
|
|
f"shards: {frozen_expert_names}")
|
|
|
|
# Collect metadata on ALL ranks (before writer guard)
|
|
autoep_layer_info.append({
|
|
'moe_layer_id':
|
|
moe_layer_id,
|
|
'module_path':
|
|
n_module,
|
|
'num_experts':
|
|
module.num_experts,
|
|
'num_local_experts':
|
|
num_local_experts,
|
|
'ep_size':
|
|
module.ep_size,
|
|
'expert_key_prefix':
|
|
f"{module_prefix}experts",
|
|
AUTOEP_ZERO3_EXPERT_STATE_FORMAT_KEY:
|
|
AUTOEP_ZERO3_PARTITIONED_EXPERT_STATE_FORMAT
|
|
if self.zero_optimization_partition_weights() else 'per_expert_files',
|
|
AUTOEP_ZERO3_EXPERT_STATE_FORMAT_VERSION_KEY:
|
|
AUTOEP_ZERO3_EXPERT_STATE_FORMAT_VERSION if self.zero_optimization_partition_weights() else None,
|
|
'ep_group_name':
|
|
group_name,
|
|
'ep_rank':
|
|
expp_rank,
|
|
'expert_data_parallel_rank':
|
|
exp_dp_rank,
|
|
'expert_data_parallel_world_size':
|
|
groups._get_expert_data_parallel_world_size(group_name),
|
|
'global_expert_start':
|
|
expp_rank * num_local_experts,
|
|
'global_expert_end':
|
|
expp_rank * num_local_experts + num_local_experts,
|
|
})
|
|
autoep_group_names.add(group_name)
|
|
if len(autoep_group_names) > 1:
|
|
raise RuntimeError(f"AutoEP checkpointing requires a single EP group size, but found "
|
|
f"multiple groups: {sorted(autoep_group_names)}. "
|
|
f"All AutoEPMoELayer instances must use the same ep_size.")
|
|
|
|
# Gate file writes behind writer guard. Folded AutoEP+AutoTP needs
|
|
# one expert shard per (TP rank, EP rank), not only mp_rank_00.
|
|
if self.zero_optimization_partition_weights():
|
|
moe_layer_id += 1
|
|
continue
|
|
|
|
with deepspeed.zero.GatheredParameters(expert_params):
|
|
if autoep_expert_writer():
|
|
# Slice fused 3D tensors into per-expert state dicts.
|
|
for local_expert_id in range(num_local_experts):
|
|
global_expert_id = expp_rank * num_local_experts + local_expert_id
|
|
expert_state_dict = {}
|
|
for wname in ('w1', 'w2', 'w3'):
|
|
fused_key = f"{module_prefix}experts.{wname}"
|
|
param = getattr(module.experts, wname)
|
|
expert_state_dict[f"{fused_key}.{global_expert_id}"] = (
|
|
param[local_expert_id].clone().detach())
|
|
if folded_autoep_tp:
|
|
expert_state_dict[FOLDING_METADATA_KEY] = folding_metadata(
|
|
family="routed_expert",
|
|
ep_rank=expp_rank,
|
|
zero_partition_group="edp",
|
|
zero_partition_rank=exp_dp_rank,
|
|
zero_partition_count=folding_spec.edp_size,
|
|
)
|
|
|
|
moe_save_path = self._get_expert_ckpt_name(save_dir, moe_layer_id, global_expert_id, tag,
|
|
self.mpu)
|
|
saveable = expert_state_dict
|
|
if self.checkpoint_engine.preserves_storage_sharing():
|
|
saveable = clone_tensors_for_torch_save(expert_state_dict)
|
|
self.checkpoint_engine.save(saveable, moe_save_path)
|
|
|
|
moe_layer_id += 1
|
|
|
|
self._curr_ckpt_path = os.path.join(save_dir, tag)
|
|
|
|
largest_group_name = groups._get_max_expert_size_name()
|
|
expp_rank = groups._get_expert_parallel_rank(largest_group_name)
|
|
exp_dp_rank = groups._get_expert_data_parallel_rank(largest_group_name)
|
|
is_expert_dp_writer = self.checkpoint_engine.is_data_parallel_writer(exp_dp_rank)
|
|
expert_checkpoint_writer = (groups._get_data_parallel_rank() < folding_spec.ep_size
|
|
if folded_autoep_tp else is_expert_dp_writer)
|
|
|
|
# Non-ZeRO AutoEP keeps per-expert optimizer files. ZeRO-3 AutoEP
|
|
# restores experts from ZeRO optimizer shards, so every ZeRO partition
|
|
# rank continues to write its model-state file below instead.
|
|
if expert_checkpoint_writer and not self.zero_optimization_partition_weights():
|
|
optimizer_state = {
|
|
'optimizer': self.optimizer.state_dict() if self.optimizer and not self.zero_optimization() else None
|
|
}
|
|
if folded_autoep_tp:
|
|
optimizer_state[FOLDING_METADATA_KEY] = folding_metadata(family="routed_expert",
|
|
ep_rank=expp_rank,
|
|
zero_partition_group="edp",
|
|
zero_partition_rank=exp_dp_rank,
|
|
zero_partition_count=folding_spec.edp_size)
|
|
# TODO: why use BufferedWriter not the path
|
|
file_path = self._get_optimizer_ckpt_name(save_dir, tag, expp_rank)
|
|
saveable_state_dict = optimizer_state
|
|
if self.checkpoint_engine.preserves_storage_sharing():
|
|
saveable_state_dict = clone_tensors_for_torch_save(optimizer_state)
|
|
self.checkpoint_engine.save(saveable_state_dict, file_path)
|
|
elif not self.zero_optimization_partition_weights():
|
|
return
|
|
|
|
# Load flow uses below saved file for model parameters, RNG and more
|
|
if self.zero_optimization_partition_weights() or groups._get_data_parallel_rank() == 0:
|
|
# Get non-moe parameters
|
|
# Classes DeepSpeedEngine and PipelineEngine have different behavior for method module_state_dict.
|
|
# DeepSpeedEngine returns the state dict, where PipelineEngine saves the state dict and returns None.
|
|
# We need to get the state dict, therefore, call to DeepSpeedEngine (base class for PipelineEngine)
|
|
model_state_dict = self._get_non_moe_state_dict(
|
|
DeepSpeedEngine.module_state_dict(self, exclude_frozen_parameters=exclude_frozen_parameters))
|
|
|
|
zero_optimizer_state = self.zero_optimization()
|
|
save_frozen_param = self.zero_optimization_partition_gradients() and not exclude_frozen_parameters
|
|
zero_param_shapes = self._get_zero_param_shapes() if self.optimizer and zero_optimizer_state else None
|
|
if autoep_layer_info and self.zero_optimization_partition_weights():
|
|
DeepSpeedEngine._validate_autoep_zero3_partitioned_metadata(autoep_layer_info, model=self.module)
|
|
expert_param_names = DeepSpeedEngine._autoep_expert_parameter_names(autoep_layer_info, self.module)
|
|
zero_shape_names = {
|
|
name
|
|
for param_group_shapes in (zero_param_shapes or [])
|
|
for name in param_group_shapes.keys()
|
|
}
|
|
missing_expert_shapes = expert_param_names - zero_shape_names
|
|
if missing_expert_shapes:
|
|
raise RuntimeError("AutoEP ZeRO-3 checkpoint metadata references expert parameters that are "
|
|
f"missing from ZeRO param_shapes: {sorted(missing_expert_shapes)}")
|
|
universal_checkpoint_info = getattr(self.module, UNIVERSAL_CHECKPOINT_INFO, None)
|
|
if universal_checkpoint_info is not None:
|
|
universal_checkpoint_info = dict(universal_checkpoint_info)
|
|
elif autoep_layer_info:
|
|
universal_checkpoint_info = {}
|
|
if autoep_layer_info:
|
|
universal_checkpoint_info.setdefault(UNIVERSAL_CHECKPOINT_VERSION_KEY,
|
|
UNIVERSAL_CHECKPOINT_VERSION_VALUE)
|
|
universal_checkpoint_info[EXPERT_PARAMETER_PATTERNS] = [r'.*\.experts\.w[123]$']
|
|
universal_checkpoint_info['ds_autoep_layers'] = autoep_layer_info
|
|
|
|
state = self._common_checkpoint_state(model_state_dict, zero_optimizer_state, save_frozen_param)
|
|
state['num_experts'] = self.num_experts
|
|
state['ds_autoep_layers'] = autoep_layer_info if autoep_layer_info else None
|
|
if universal_checkpoint_info is not None:
|
|
state[UNIVERSAL_CHECKPOINT_INFO] = universal_checkpoint_info
|
|
if folded_autoep_tp:
|
|
ep_group_name = f"ep_size_{folding_spec.ep_size}"
|
|
state[FOLDING_METADATA_KEY] = folding_metadata(
|
|
family="dense",
|
|
ep_rank=groups._get_expert_parallel_rank(ep_group_name),
|
|
zero_partition_group="dense_dp",
|
|
zero_partition_rank=groups._get_data_parallel_rank(),
|
|
zero_partition_count=folding_spec.dp_size,
|
|
param_families=DeepSpeedEngine._autoep_non_expert_param_families(model_state_dict))
|
|
# Check for reserved-key collisions with client_state
|
|
reserved_keys = {'ds_autoep_layers', 'autoep_layers', UNIVERSAL_CHECKPOINT_INFO, FOLDING_METADATA_KEY}
|
|
collisions = reserved_keys.intersection(client_state.keys())
|
|
if collisions:
|
|
raise KeyError(f"client_state contains reserved checkpoint keys: {sorted(collisions)}. "
|
|
f"These keys are used internally by DeepSpeed for AutoEP metadata.")
|
|
state.update(client_state)
|
|
logger.info(f'Saving model checkpoint: {save_path}')
|
|
saveable_state_dict = state
|
|
if self.checkpoint_engine.preserves_storage_sharing():
|
|
saveable_state_dict = clone_tensors_for_torch_save(state)
|
|
self.checkpoint_engine.save(saveable_state_dict, save_path)
|
|
|
|
def _create_checkpoint_file(self, save_dir, tag, zero_checkpoint):
|
|
name_function = (self._get_zero_ckpt_name if zero_checkpoint else self._get_ckpt_name)
|
|
try:
|
|
checkpoint_name = name_function(save_dir, tag)
|
|
path = os.path.dirname(checkpoint_name)
|
|
self.checkpoint_engine.makedirs(path, exist_ok=True)
|
|
except Exception:
|
|
logger.error(f"Failed saving model checkpoint to {save_dir} with tag {tag}")
|
|
return False
|
|
|
|
return True
|
|
|
|
def _create_zero_checkpoint_files(self, save_dir, tag):
|
|
success = True
|
|
# zero checkpoint files are created sequentially
|
|
for rank in range(dist.get_world_size(self.optimizer.dp_process_group)):
|
|
if rank == self.global_rank:
|
|
success = self._create_checkpoint_file(save_dir, tag, True)
|
|
|
|
return success
|
|
|
|
def _save_checkpoint(self, save_dir, tag, client_state={}, exclude_frozen_parameters=False):
|
|
|
|
save_path = self._get_ckpt_name(save_dir, tag)
|
|
|
|
zero_optimizer_state = self.zero_optimization()
|
|
|
|
save_frozen_param = self.zero_optimization_partition_gradients() and not exclude_frozen_parameters
|
|
|
|
# A hack to save the checkpointing directory. Pipeline parallelism overrides
|
|
# module_state_dict() and uses this path to save the model. module_state_dict()
|
|
# then instead just returns None. The module_state_dict() implementation in
|
|
# PipelineEngine expects the save path to be set in self._curr_ckpt_path.
|
|
self._curr_ckpt_path = os.path.join(save_dir, tag)
|
|
module = self.module_state_dict(exclude_frozen_parameters=exclude_frozen_parameters)
|
|
self._curr_ckpt_path = None
|
|
|
|
state = self._common_checkpoint_state(module, zero_optimizer_state, save_frozen_param)
|
|
autotp_uc_info = getattr(self.module, UNIVERSAL_CHECKPOINT_INFO, None)
|
|
if autotp_uc_info is not None:
|
|
state[UNIVERSAL_CHECKPOINT_INFO] = autotp_uc_info
|
|
state.update(client_state)
|
|
log_dist(message=f'Saving model checkpoint: {save_path}', ranks=[0])
|
|
|
|
if self.save_non_zero_checkpoint:
|
|
self.checkpoint_engine.save(state_dict=state, path=save_path)
|
|
|
|
def _get_buffer_names(self):
|
|
buffer_names = []
|
|
|
|
# we save buffer names so that we could extract later the real buffers from the saved
|
|
# state_dict["module"] in the non-zero checkpoint - the buffers are already there but they
|
|
# are intermixed with param placeholders
|
|
|
|
# have to traverse the tree to be able to skip non-persistent buffers
|
|
def get_layer_named_buffers(module, prefix=""):
|
|
for name, buf in module.named_buffers(recurse=False):
|
|
if buf is not None and name not in module._non_persistent_buffers_set:
|
|
buffer_names.append(prefix + name)
|
|
|
|
for name, child in module.named_children():
|
|
if child is not None:
|
|
get_layer_named_buffers(child, prefix + name + ".")
|
|
|
|
get_layer_named_buffers(self.module, prefix="")
|
|
|
|
return buffer_names
|
|
|
|
def _get_param_shape_func(self, param):
|
|
return param.ds_shape if hasattr(param, 'ds_id') else param.shape
|
|
|
|
def _get_param_fragment_func(self, param):
|
|
return param.ds_tensor.detach().cpu() if hasattr(param, 'ds_id') else param.detach().cpu()
|
|
|
|
def _get_zero_frozen_param_attributes(self, attr_func):
|
|
frozen_param_fragments = OrderedDict()
|
|
|
|
for param in self.module.parameters():
|
|
if param.requires_grad:
|
|
continue
|
|
if param not in self.param_names:
|
|
raise ValueError(f"failed to find frozen {param} in named params")
|
|
name = self.param_names[param]
|
|
frozen_param_fragments[name] = attr_func(param)
|
|
|
|
return frozen_param_fragments
|
|
|
|
def _get_zero_param_shapes(self):
|
|
"""Returns a dict of name to shape mapping, only for the flattened fp32 weights saved by the
|
|
optimizer. the names are exactly as in state_dict. The order is absolutely important, since
|
|
the saved data is just flattened data with no identifiers and requires reconstruction in the
|
|
same order it was saved.
|
|
We can't rely on self.module.named_parameters() to get the saved tensors, as some params
|
|
will be missing and others unsaved and then it'd be impossible to reconstruct state_dict
|
|
from the flattened weights.
|
|
optimizer.bit16_groups seems to be the easiest to use as it's in all zeroX versions.
|
|
"""
|
|
param_group_shapes = []
|
|
cnt = 0
|
|
numel = 0
|
|
|
|
# zero2 started using a round_robin_bit16_groups which is a shuffled version of bit16_groups -
|
|
# if we don't use it, we get parameters ordered incorrectly
|
|
if hasattr(self.optimizer, "round_robin_bit16_groups"):
|
|
bit16_groups = self.optimizer.round_robin_bit16_groups
|
|
elif self.bfloat16_enabled() and hasattr(self.optimizer, "bf16_groups"):
|
|
bit16_groups = self.optimizer.bf16_groups
|
|
else:
|
|
bit16_groups = self.optimizer.bit16_groups if self.zero_optimization_stage(
|
|
) == 2 else self.optimizer.fp16_groups
|
|
|
|
for bit16_group in bit16_groups:
|
|
param_shapes = OrderedDict()
|
|
for param in bit16_group:
|
|
cnt += 1
|
|
numel += param.ds_numel if hasattr(param, "ds_numel") else param.numel()
|
|
shape = param.ds_shape if hasattr(param, "ds_shape") else param.shape
|
|
if param not in self.param_names:
|
|
raise ValueError("failed to find optimizer param in named params")
|
|
name = self.param_names[param]
|
|
param_shapes[name] = shape
|
|
|
|
# uncomment to debug zero_to_fp32.py problems
|
|
# if self.global_rank == 0: print(f"saving param {name} {shape} (numel={shape.numel()})")
|
|
param_group_shapes.append(param_shapes)
|
|
# if self.global_rank == 0: print(f"Total saved {numel} numels in {cnt} params")
|
|
|
|
return param_group_shapes
|
|
|
|
def _get_shared_params(self):
|
|
"""
|
|
Returns a dict of shared params, which can later be used to reconstruct the original state dict,
|
|
e.g. in `zero_to_fp32`. Each dict entry is a pair of param names, where the key is the name
|
|
of the variable that isn't stored and the value is the actual param holding data.
|
|
"""
|
|
shared_index = {}
|
|
shared_params_by_full_name = {}
|
|
|
|
is_zero3_model = (self.zero_optimization_partition_weights()
|
|
and any(hasattr(param, "ds_id") for param in self.module.parameters()))
|
|
|
|
def get_layer_state_dict(module, prefix=""):
|
|
# handle params
|
|
for name, param in module.named_parameters(recurse=False):
|
|
if param is None or (is_zero3_model and not hasattr(param, "ds_id")):
|
|
continue
|
|
key = prefix + name
|
|
|
|
# When weights are manged by stage 3, we can't rely on param.data_ptr() as it will be reused
|
|
# as weights get gathered and reduced, but param.ds_id is unique across all zero weights
|
|
# (and shared params will have the same param.ds_id)
|
|
param_id = param.ds_id if is_zero3_model else param.data_ptr()
|
|
|
|
if param_id in shared_index:
|
|
# shared weights
|
|
#print(f"`{key}` is shared with `{shared_index[param_id]}`")
|
|
shared_params_by_full_name[key] = shared_index[param_id]
|
|
else:
|
|
shared_index[param_id] = key
|
|
|
|
for name, child in module.named_children():
|
|
if child is not None:
|
|
get_layer_state_dict(child, prefix + name + ".")
|
|
|
|
if dist.get_rank() == 0:
|
|
get_layer_state_dict(self.module, prefix="")
|
|
|
|
return shared_params_by_full_name
|
|
|
|
def _copy_recovery_script(self, save_path):
|
|
base_dir = os.path.dirname(os.path.dirname(__file__))
|
|
script = "zero_to_fp32.py"
|
|
src = os.path.join(base_dir, "utils", script)
|
|
dst = os.path.join(save_path, script)
|
|
#logger.info(f"creating recovery script {dst}")
|
|
copyfile(src, dst)
|
|
self._change_recovery_script_permissions(dst)
|
|
|
|
def _change_recovery_script_permissions(self, dst):
|
|
# make executable (safeguard for file shares - Azure as example)
|
|
try:
|
|
os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC)
|
|
except (FileNotFoundError, PermissionError) as e:
|
|
#this message is used in unit test TestZeRONonDistributed
|
|
logger.info(
|
|
f'Warning: Could not change permissions for {dst} due to error: {e}. Continuing without changing permissions.'
|
|
)
|
|
|
|
def _save_zero_checkpoint(self, save_path, tag):
|
|
zero_checkpoint_name = self._get_zero_ckpt_name(save_path, tag)
|
|
zero_sd = dict(optimizer_state_dict=self.optimizer.state_dict(), ds_config=self.config, ds_version=version)
|
|
folding_spec = getattr(self, "_autoep_folding_spec", None)
|
|
if folding_spec is not None and folding_spec.tp_size > 1:
|
|
ep_group_name = f"ep_size_{folding_spec.ep_size}"
|
|
zero_sd[FOLDING_METADATA_KEY] = DeepSpeedEngine._make_autoep_folding_metadata(
|
|
folding_spec,
|
|
family="zero_optimizer_state",
|
|
ep_rank=groups._get_expert_parallel_rank(ep_group_name),
|
|
zero_partition_group="per_family",
|
|
zero_partition_rank=dist.get_rank(group=self.optimizer.dp_process_group),
|
|
zero_partition_count=dist.get_world_size(group=self.optimizer.dp_process_group),
|
|
param_families=self._autoep_zero_optimizer_param_families(),
|
|
)
|
|
self.checkpoint_engine.save(zero_sd, zero_checkpoint_name)
|
|
|
|
if self.global_rank == 0:
|
|
self._copy_recovery_script(save_path)
|
|
ckpt_type = 'zero' if self.zero_optimization() else 'bf16_zero'
|
|
#logger.info(f'{ckpt_type} checkpoint saved {zero_checkpoint_name}')
|
|
|
|
def _replace_module_consolidated_state_dict(self):
|
|
"""
|
|
Get a full non-partitioned state_dict with fp16 weights on cpu.
|
|
Important: this function must be called on all ranks and not just rank 0.
|
|
This is similar to nn.Module.state_dict (modelled after _save_to_state_dict)
|
|
This method is used for tensor parallel training.
|
|
|
|
Returns:
|
|
OrderedDict: The consolidated state dictionary if the current process rank is 0, otherwise None.
|
|
"""
|
|
#TODO: If we use both Zero3 and tensor parallel simultaneously
|
|
# we need to consolidate the gather mechanisms of both.
|
|
state_dict = OrderedDict() if dist.get_rank() == 0 else None
|
|
|
|
def get_layer_state_dict(module, prefix=""):
|
|
with GatherReplacedLayerParams(list(module.parameters(recurse=False)), module, enabled=True):
|
|
for name, param in module.named_parameters(recurse=False):
|
|
if param is None:
|
|
continue
|
|
key = prefix + name
|
|
if (dist.get_rank() == 0):
|
|
state_dict[key] = param.detach().cpu()
|
|
# print(key,module, param.detach().cpu().shape)
|
|
|
|
for name, child in module.named_children():
|
|
if child is not None:
|
|
get_layer_state_dict(child, prefix + name + ".")
|
|
|
|
get_layer_state_dict(self.module, prefix="")
|
|
|
|
# ensure that all GPU communication tasks are completed before the process exits
|
|
get_accelerator().synchronize()
|
|
return state_dict
|
|
|
|
def _consolidated_16bit_state_dict(self, exclude_frozen_parameters=False):
|
|
"""
|
|
Consolidate the 16-bit state dictionary.
|
|
"""
|
|
if self.zero_optimization_stage() == ZeroStageEnum.weights:
|
|
return self._zero3_consolidated_16bit_state_dict(exclude_frozen_parameters)
|
|
elif self.autotp_size() > 1:
|
|
return self._replace_module_consolidated_state_dict()
|
|
|
|
raise ValueError("consolidated_16bit_state_dict is only applicable to cases where weights are partitioned, "
|
|
"including Zero Stage 3 and tensor parallelism.")
|
|
|
|
def _has_autoep_layers(self):
|
|
try:
|
|
from deepspeed.module_inject.auto_ep_layer import AutoEPMoELayer as _AutoEPMoELayer
|
|
except ImportError:
|
|
return False
|
|
return any(isinstance(module, _AutoEPMoELayer) for _, module in self.module.named_modules())
|
|
|
|
def _raise_if_autoep_zero3_consolidated_export(self, operation):
|
|
if self.zero_optimization_partition_weights() and self._has_autoep_layers():
|
|
raise NotImplementedError(f"{operation} is not supported for AutoEP with ZeRO Stage 3 checkpoint "
|
|
"partitions. AutoEP expert parameters are partitioned over expert replica "
|
|
"groups, so global-DP consolidation would produce incomplete expert tensors. "
|
|
"Use ds_to_universal.py for an expert-aware checkpoint conversion.")
|
|
|
|
def _zero3_consolidated_16bit_state_dict(self, exclude_frozen_parameters=False):
|
|
"""
|
|
Get a full non-partitioned state_dict with fp16 weights on cpu.
|
|
Important: this function must be called on all ranks and not just rank 0.
|
|
This is similar to nn.Module.state_dict (modelled after _save_to_state_dict), but:
|
|
1. consolidates the weights from different partitions on gpu0
|
|
2. works on one layer at a time to require as little gpu0 memory as possible, by
|
|
moving the already consolidated weights to cpu
|
|
3. takes care to keep the shared params shared when gradually copying the params to cpu
|
|
Returns:
|
|
a consolidated fp16 ``state_dict`` on cpu on rank 0, ``None`` on other ranks
|
|
"""
|
|
if not self.zero_optimization_partition_weights():
|
|
raise ValueError("this function requires ZeRO-3 mode")
|
|
self._raise_if_autoep_zero3_consolidated_export("_zero3_consolidated_16bit_state_dict")
|
|
|
|
state_dict = OrderedDict() if dist.get_rank() == 0 else None
|
|
shared_params = {}
|
|
|
|
def get_layer_state_dict(module, prefix=""):
|
|
# gather one layer at a time to be memory-efficient
|
|
# must use modifier_rank=0 to release GPU memory after each layer gathered
|
|
#see_memory_usage("before GatheredParameters", force=True)
|
|
with deepspeed.zero.GatheredParameters(list(module.parameters(recurse=False)), modifier_rank=0):
|
|
if dist.get_rank() == 0:
|
|
# handle params
|
|
for name, param in module.named_parameters(recurse=False):
|
|
if param is None or (exclude_frozen_parameters and not param.requires_grad):
|
|
continue
|
|
key = prefix + name
|
|
# can't rely on param.data_ptr() as it will be reused as weights gets
|
|
# gathered and reduced, but param.ds_id is unique across all zero weights
|
|
# (and shared params will have the same param.ds_id)
|
|
if param.ds_id in shared_params:
|
|
# shared weights
|
|
#print(f"`{key}` is shared with `{shared_params[param.ds_id]}`")
|
|
state_dict[key] = state_dict[shared_params[param.ds_id]]
|
|
else:
|
|
state_dict[key] = param.detach().cpu()
|
|
shared_params[param.ds_id] = key
|
|
#print(f"param {param.ds_id} {param.shape} {key} ")
|
|
|
|
# now buffers - not sure if need to take care of potentially shared weights here
|
|
for name, buf in module.named_buffers(recurse=False):
|
|
if (buf is not None and name not in module._non_persistent_buffers_set):
|
|
state_dict[prefix + name] = buf.detach().cpu()
|
|
#see_memory_usage("after GatheredParameters", force=True)
|
|
|
|
for name, child in module.named_children():
|
|
if child is not None:
|
|
get_layer_state_dict(child, prefix + name + ".")
|
|
|
|
# Prepare for checkpoint save by ensuring all parameters are partitioned
|
|
if self._optimizer_has_ckpt_event_prologue():
|
|
self.optimizer.checkpoint_event_prologue()
|
|
|
|
see_memory_usage("before get_layer_state_dict", force=False)
|
|
get_layer_state_dict(self.module, prefix="")
|
|
see_memory_usage("after get_layer_state_dict", force=False)
|
|
|
|
if self._optimizer_has_ckpt_event_epilogue():
|
|
self.optimizer.checkpoint_event_epilogue()
|
|
|
|
return state_dict
|
|
|
|
def save_fp16_model(self, save_dir, save_filename="pytorch_model.bin"):
|
|
"""has been renamed to save_16bit_model, keeping this around for backwards
|
|
compatibility"""
|
|
return self.save_16bit_model(save_dir, save_filename)
|
|
|
|
def save_16bit_model(self, save_dir, save_filename="pytorch_model.bin", exclude_frozen_parameters=False):
|
|
"""
|
|
Save 16bit model weights
|
|
|
|
This method saves the 16bit model weights at the desired destination.
|
|
|
|
Arguments:
|
|
save_dir: Required. Directory for saving the model
|
|
save_filename: Optional. Filename to save to. Defaults to ``pytorch_model.bin``
|
|
exclude_frozen_parameters: Optional. Exclude frozen parameters from checkpointed state.
|
|
|
|
Returns:
|
|
``True`` when a model has been saved, ``False`` otherwise. It will not be saved if
|
|
stage3_gather_16bit_weights_on_model_save is ``False``.
|
|
|
|
Important: all processes must call this method and not just the process with rank 0. It is
|
|
because the processes need to work in sync to gather the weights. This method will hang
|
|
waiting to synchronize with other processes if it's called just for the process with rank 0.
|
|
|
|
"""
|
|
|
|
path = os.path.join(save_dir, save_filename)
|
|
|
|
if self.zero_optimization_partition_weights():
|
|
self._raise_if_autoep_zero3_consolidated_export("save_16bit_model")
|
|
if self.zero_gather_16bit_weights_on_model_save():
|
|
# consolidation is expensive in time and memory and therefore isn't a default
|
|
state_dict = self._zero3_consolidated_16bit_state_dict(
|
|
exclude_frozen_parameters=exclude_frozen_parameters)
|
|
else:
|
|
# the model will be bogus if not consolidated so don't confuse the user by saving it
|
|
logger.info(
|
|
f"Did not save the model {path} because stage3_gather_16bit_weights_on_model_save is False")
|
|
return False
|
|
else:
|
|
state_dict = self.module_state_dict(exclude_frozen_parameters=exclude_frozen_parameters)
|
|
|
|
tag = f"global_step{self.global_steps}"
|
|
tag = str(tag)
|
|
commit_info = CheckpointCommitInfo(tag=tag, save_dir=save_dir, save_latest=False)
|
|
self.checkpoint_engine.create(commit_info)
|
|
|
|
if dist.get_rank() == 0:
|
|
self.checkpoint_engine.makedirs(save_dir, exist_ok=True)
|
|
logger.info(f"Saving model weights to {path}, tag: {tag}")
|
|
self.checkpoint_engine.save(state_dict, path)
|
|
|
|
self.checkpoint_engine.commit(commit_info)
|
|
|
|
return True
|
|
|
|
def empty_partition_cache(self):
|
|
"""
|
|
Release GPU memory consumed by offloaded model parameters.
|
|
"""
|
|
if hasattr(self.optimizer, 'empty_partition_cache'):
|
|
self.optimizer.empty_partition_cache()
|
|
gc.collect()
|
|
get_accelerator().empty_cache()
|
|
|
|
def get_autosp_backend(self, compile_kwargs):
|
|
if self.compile_autosp() and self.zero_optimization_stage() not in [
|
|
ZeroStageEnum.disabled, ZeroStageEnum.optimizer_states
|
|
]:
|
|
logger.info(
|
|
f"Currently AutoSP does not compose with ZeRO stage 2 and 3. Falling back to the torch compiler.")
|
|
return None
|
|
|
|
compile_config = self._config.compile_config
|
|
compile_kwargs['fullgraph'] = True
|
|
return init_autosp(self._config)
|
|
|
|
def get_deepcompile_backend(self, backend, compile_kwargs, schedule):
|
|
if self.zero_optimization_stage() != ZeroStageEnum.optimizer_states \
|
|
and self.zero_optimization_stage() != ZeroStageEnum.weights \
|
|
and self.zero_optimization_stage() != ZeroStageEnum.gradients:
|
|
logger.info(
|
|
f"Currently DeepCompile supports ZeRO stage 1, 2, or 3 only, but ZeRO stage is set to {self.zero_optimization_stage()}. Falling back to the torch compiler."
|
|
)
|
|
return None
|
|
|
|
compile_config = self._config.compile_config
|
|
if (("zero_optimization" in self.config and "offload_optimizer" in self.config["zero_optimization"]
|
|
and "offload_param" in self.config["zero_optimization"])
|
|
and self._config.zero_config.offload_param.device == "cpu"
|
|
and self._config.zero_config.offload_optimizer.device == "cpu"):
|
|
compile_config.offload_parameters = True
|
|
if self.zero_optimization_stage() == ZeroStageEnum.optimizer_states:
|
|
return init_z1(self, backend, compile_config, compile_kwargs, schedule)
|
|
elif self.zero_optimization_stage() == ZeroStageEnum.gradients:
|
|
return init_z1(self, backend, compile_config, compile_kwargs, schedule, use_z2=True)
|
|
elif self.zero_optimization_stage() == ZeroStageEnum.weights:
|
|
return init_z3(self, backend, compile_config, compile_kwargs, schedule)
|
|
return None
|
|
|
|
def get_deepspeed_compile_backend(self, backend, compile_kwargs, schedule):
|
|
resolved_backend = None
|
|
|
|
if schedule is not None:
|
|
|
|
def passes_name_to_fn(passes):
|
|
for p in passes:
|
|
assert callable(p) or p in opt_passes, f"Unknown pass {p}"
|
|
return [p if callable(p) else opt_passes[p] for p in passes]
|
|
|
|
schedule = [(step, passes_name_to_fn(passes)) for step, passes in schedule]
|
|
|
|
assert backend in ['inductor', 'eager'], f"Backend {backend} is not supported for DeepCompile."
|
|
|
|
if self.compile_autosp():
|
|
resolved_backend = self.get_autosp_backend(compile_kwargs)
|
|
else:
|
|
resolved_backend = self.get_deepcompile_backend(backend, compile_kwargs, schedule)
|
|
|
|
return resolved_backend, schedule
|
|
|
|
def compile(self,
|
|
backend=get_accelerator().get_compile_backend(),
|
|
compile_kwargs={},
|
|
schedule=None,
|
|
compiled_autograd_enabled=False) -> None:
|
|
"""Compile the module using the specified backend and kwargs.
|
|
If a compiler_fn is set, it will be used instead of torch.compile().
|
|
"""
|
|
# Avoid graph breaks
|
|
deepspeed.utils.nvtx.enable_nvtx = False
|
|
|
|
if not is_compile_supported():
|
|
raise RuntimeError("compile is not supported in your version of PyTorch.")
|
|
|
|
if self.is_compiled:
|
|
return
|
|
|
|
if 'backend' in compile_kwargs:
|
|
logger.warning("The `backend` in `compile_kwargs` will be overridden. Use the `backend` argument instead.")
|
|
|
|
logger.info(f"Compiling deepcompile={self.is_deepcompile_enabled()} backend={backend}")
|
|
|
|
resolved_backend = None
|
|
if self.is_deepcompile_enabled():
|
|
resolved_backend, schedule = self.get_deepspeed_compile_backend(backend, compile_kwargs, schedule)
|
|
|
|
is_deepspeed_compile_backend = resolved_backend is not None
|
|
|
|
# default to torch.compiler backend if deepspeed config validation fails
|
|
backend = resolved_backend or backend
|
|
|
|
# Hook state must align with whether DeepCompile is active.
|
|
self._set_deepcompile_active(is_deepspeed_compile_backend)
|
|
|
|
# create new dict to avoid modifying original dict
|
|
try:
|
|
self.module.compile(**{**compile_kwargs, 'backend': backend})
|
|
except Exception:
|
|
if is_deepspeed_compile_backend:
|
|
# Restore default hooks if compilation fails before completing.
|
|
self._set_deepcompile_active(False)
|
|
raise
|
|
|
|
self._is_compiled = True
|
|
self._compile_kwargs = compile_kwargs
|
|
if compiled_autograd_enabled:
|
|
if not self._deepcompile_active:
|
|
self._is_compiled_autograd_enabled = compiled_autograd_enabled
|
|
else:
|
|
logger.warning("Compiled autograd is not compatible with DeepCompile, disabling compiled autograd.")
|
|
self._is_compiled_autograd_enabled = False
|
|
|
|
def _set_deepcompile_active(self, active: bool) -> None:
|
|
"""Toggle DeepCompile runtime state and manage forward hooks accordingly."""
|
|
if self._deepcompile_active == active:
|
|
return
|
|
|
|
if active:
|
|
if self.module_forward_pre_hook is not None:
|
|
self.module_forward_pre_hook.remove()
|
|
self.module_forward_pre_hook = None
|
|
if self.module_forward_post_hook is not None:
|
|
self.module_forward_post_hook.remove()
|
|
self.module_forward_post_hook = None
|
|
else:
|
|
if self.module_forward_pre_hook is None:
|
|
self.module_forward_pre_hook = self._create_module_forward_pre_hook()
|
|
if self.module_forward_post_hook is None:
|
|
self.module_forward_post_hook = self._create_module_forward_post_hook()
|
|
|
|
self._deepcompile_active = active
|
|
|
|
def get_compile_time(self):
|
|
from deepspeed.compile.backend import opt_pass_times
|
|
return opt_pass_times
|
|
|
|
def register_compile_pass(self, pass_name: str, pass_fn: Callable) -> None:
|
|
register_compile_pass(pass_name, pass_fn)
|
|
|
|
def is_deepcompile_enabled(self) -> bool:
|
|
return self._config.compile_config.deepcompile
|
|
|
|
def is_deepcompile_active(self) -> bool:
|
|
return getattr(self, "_deepcompile_active", False)
|
|
|
|
@property
|
|
def is_compiled(self) -> bool:
|
|
return self._is_compiled
|
|
|
|
def _refine_include_states(self, include: Container[OffloadStateTypeEnum]) -> Container[OffloadStateTypeEnum]:
|
|
if include is None:
|
|
include = list(OffloadStateTypeEnum)
|
|
|
|
if self.zero_use_cpu_optimizer():
|
|
exclude_states = [OffloadStateTypeEnum.hp_params, OffloadStateTypeEnum.optim_states]
|
|
if self.zero_optimization_partition_weights():
|
|
exclude_states.append(OffloadStateTypeEnum.lp_grads)
|
|
include = [x for x in include if x not in exclude_states]
|
|
|
|
return include
|
|
|
|
def offload_states(self,
|
|
include: Container[OffloadStateTypeEnum] = None,
|
|
device: OffloadDeviceEnum = OffloadDeviceEnum.cpu,
|
|
pin_memory: bool = True,
|
|
non_blocking: bool = False) -> None:
|
|
"""Offload the engine's states to the specified device.
|
|
|
|
Arguments:
|
|
include: Optional. The set of states to offload. If not provided, all states are offloaded.
|
|
device: Optional. The device to move the ZeRO optimizer buffers to. Currently only `OffloadDeviceEnum.cpu` is supported.
|
|
pin_memory: Optional. Whether to pin the memory of the offloaded states.
|
|
non_blocking: Optional. Whether to offload the states asynchronously.
|
|
"""
|
|
include = self._refine_include_states(include)
|
|
param_offload_config = self.zero_offload_param()
|
|
assert param_offload_config is None or param_offload_config.device == OffloadDeviceEnum.none, "Moving states across devices is not supported for offloaded parameters."
|
|
|
|
assert not isinstance(
|
|
self.optimizer,
|
|
DeepSpeedZeRoOffload), "Moving states across devices is not supported without an optimizer."
|
|
|
|
if device == OffloadDeviceEnum.none:
|
|
logger.warning("No device specified for offloading states.")
|
|
return
|
|
|
|
if device == OffloadDeviceEnum.nvme:
|
|
raise ValueError("NVMe offload is not supported for offloading states.")
|
|
|
|
self.optimizer.offload_states(include=include, device=device, pin_memory=pin_memory, non_blocking=non_blocking)
|
|
|
|
def reload_states(self, non_blocking: bool = False) -> None:
|
|
"""Reload the engine states to the original device.
|
|
|
|
Arguments:
|
|
non_blocking: Optional. Whether to offload the states asynchronously.
|
|
"""
|
|
assert not isinstance(
|
|
self.optimizer,
|
|
DeepSpeedZeRoOffload), "Moving states across devices is not supported without an optimizer."
|
|
|
|
self.optimizer.reload_states(non_blocking=non_blocking)
|