import numpy as np import onnx import os import torch import warnings import tempfile from enum import Enum from . import _checkpoint_storage, _utils ################################################################################ # Experimental Checkpoint APIs ################################################################################ def experimental_state_dict(ort_trainer, include_optimizer_state=True): warnings.warn( "experimental_state_dict() will be deprecated soon. " "Please use ORTTrainer.state_dict() instead.", DeprecationWarning, ) if not ort_trainer._training_session: warnings.warn( "ONNX Runtime training session is not initialized yet. " "Please run train_step or eval_step at least once before calling state_dict()." ) return ort_trainer._state_dict # extract trained weights session_state = ort_trainer._training_session.get_state() torch_state = {} for name in session_state: torch_state[name] = torch.from_numpy(session_state[name]) # extract untrained weights and buffer for n in ort_trainer._onnx_model.graph.initializer: if n.name not in torch_state and n.name in ort_trainer.options.utils.frozen_weights: torch_state[n.name] = torch.from_numpy(np.array(onnx.numpy_helper.to_array(n))) # Need to remove redundant (optimizer) initializers to map back to original torch state names if not include_optimizer_state and ort_trainer._torch_state_dict_keys: return {key: torch_state[key] for key in ort_trainer._torch_state_dict_keys if key in torch_state} return torch_state def experimental_load_state_dict(ort_trainer, state_dict, strict=False): warnings.warn( "experimental_load_state_dict() will be deprecated soon. " "Please use ORTTrainer.load_state_dict() instead.", DeprecationWarning, ) # Note: It may happen ONNX model has not yet been initialized # In this case we cache a reference to desired state and delay the restore until after initialization # Unexpected behavior will result if the user changes the reference before initialization if not ort_trainer._training_session: ort_trainer._state_dict = state_dict ort_trainer._load_state_dict_strict = strict return # Update onnx model from loaded state dict cur_initializers_names = [n.name for n in ort_trainer._onnx_model.graph.initializer] new_initializers = {} for name in state_dict: if name in cur_initializers_names: new_initializers[name] = state_dict[name].numpy() elif strict: raise RuntimeError("Checkpoint tensor: {} is not present in the model.".format(name)) ort_trainer._update_onnx_model_initializers(new_initializers) # create new session based on updated onnx model ort_trainer._state_dict = None ort_trainer._init_session() # load training state session_state = {name: state_dict[name].numpy() for name in state_dict} ort_trainer._training_session.load_state(session_state, strict) def experimental_save_checkpoint( ort_trainer, checkpoint_dir, checkpoint_prefix="ORT_checkpoint", checkpoint_state_dict=None, include_optimizer_state=True, ): warnings.warn( "experimental_save_checkpoint() will be deprecated soon. " "Please use ORTTrainer.save_checkpoint() instead.", DeprecationWarning, ) if checkpoint_state_dict is None: checkpoint_state_dict = {"model": experimental_state_dict(ort_trainer, include_optimizer_state)} else: checkpoint_state_dict.update({"model": experimental_state_dict(ort_trainer, include_optimizer_state)}) assert os.path.exists(checkpoint_dir), f"checkpoint_dir ({checkpoint_dir}) directory doesn't exist" checkpoint_name = _get_checkpoint_name( checkpoint_prefix, ort_trainer.options.distributed.deepspeed_zero_optimization.stage, ort_trainer.options.distributed.world_rank, ort_trainer.options.distributed.world_size, ) checkpoint_file = os.path.join(checkpoint_dir, checkpoint_name) if os.path.exists(checkpoint_file): msg = f"{checkpoint_file} already exists, overwriting." warnings.warn(msg) torch.save(checkpoint_state_dict, checkpoint_file) def experimental_load_checkpoint(ort_trainer, checkpoint_dir, checkpoint_prefix="ORT_checkpoint", strict=False): warnings.warn( "experimental_load_checkpoint() will be deprecated soon. " "Please use ORTTrainer.load_checkpoint() instead.", DeprecationWarning, ) checkpoint_files = _list_checkpoint_files(checkpoint_dir, checkpoint_prefix) is_partitioned = False if len(checkpoint_files) > 1: msg = ( f"Found more than one file with prefix {checkpoint_prefix} in directory {checkpoint_dir}." " Attempting to load ZeRO checkpoint." ) warnings.warn(msg) is_partitioned = True if (not ort_trainer.options.distributed.deepspeed_zero_optimization.stage) and is_partitioned: return _load_multi_checkpoint(ort_trainer, checkpoint_dir, checkpoint_prefix, strict) else: return _load_single_checkpoint(ort_trainer, checkpoint_dir, checkpoint_prefix, is_partitioned, strict) class _AGGREGATION_MODE(Enum): Zero = 0 Megatron = 1 def _order_paths(paths, D_groups, H_groups): """Reorders the given paths in order of aggregation of ranks for D and H parallellism respectively and returns the ordered dict""" trainer_options_path_tuples = [] world_rank = _utils.state_dict_trainer_options_world_rank_key() for path in paths: trainer_options_path_tuples.append( (_checkpoint_storage.load(path, key=_utils.state_dict_trainer_options_key()), path) ) # sort paths according to rank sorted_paths = [ path for _, path in sorted( trainer_options_path_tuples, key=lambda trainer_options_path_pair: trainer_options_path_pair[0][world_rank] ) ] ordered_paths = dict() ordered_paths["D"] = [[sorted_paths[i] for i in D_groups[group_id]] for group_id in range(len(D_groups))] ordered_paths["H"] = [[sorted_paths[i] for i in H_groups[group_id]] for group_id in range(len(H_groups))] return ordered_paths def _add_or_update_sharded_key( state_key, state_value, state_sub_dict, model_state_key, state_partition_info, sharded_states_original_dims, mode ): """Add or update the record for the sharded state_key in the state_sub_dict""" # record the original dimension for this state original_dim = _utils.state_dict_original_dimension_key() sharded_states_original_dims[model_state_key] = state_partition_info[original_dim] axis = 0 if mode == _AGGREGATION_MODE.Megatron and state_partition_info["megatron_row_partition"] == 0: axis = -1 if state_key in state_sub_dict: # state_dict already contains a record for this state # since this state is sharded, concatenate the state value to # the record in the state_dict state_sub_dict[state_key] = np.concatenate((state_sub_dict[state_key], state_value), axis) else: # create a new entry for this state in the state_dict state_sub_dict[state_key] = state_value def _add_or_validate_unsharded_key(state_key, state_value, state_sub_dict, mismatch_error_string): """Add or validate the record for the unsharded state_key in the state_sub_dict""" if state_key in state_sub_dict: # state_dict already contains a record for this unsharded state. # assert that all values are the same for this previously loaded state assert (state_sub_dict[state_key] == state_value).all(), mismatch_error_string else: # create a new entry for this state in the state_sub_dict state_sub_dict[state_key] = state_value def _aggregate_model_states( rank_state_dict, sharded_states_original_dims, state_dict, mixed_precision_enabled, mode=_AGGREGATION_MODE.Zero ): """Aggregates all model states from the rank_state_dict into state_dict""" model = _utils.state_dict_model_key() full_precision = _utils.state_dict_full_precision_key() partition_info = _utils.state_dict_partition_info_key() # if there are no model states in the rank_state_dict, no model aggregation is needed if model not in rank_state_dict: return if model not in state_dict: state_dict[model] = {} if full_precision not in state_dict[model]: state_dict[model][full_precision] = {} # iterate over all model state keys for model_state_key, model_state_value in rank_state_dict[model][full_precision].items(): # ZERO: full precision model states are sharded only when they exist in the partition_info subdict and mixed # precision training was enabled. for full precision training, full precision model states are not sharded # MEGATRON : full precision model states are sharded when they exist in the partition_info subdict if (model_state_key in rank_state_dict[partition_info]) and ( mode == _AGGREGATION_MODE.Megatron or mixed_precision_enabled ): # this model state is sharded _add_or_update_sharded_key( model_state_key, model_state_value, state_dict[model][full_precision], model_state_key, rank_state_dict[partition_info][model_state_key], sharded_states_original_dims, mode, ) else: # this model state is not sharded since a record for it does not exist in the partition_info subdict _add_or_validate_unsharded_key( model_state_key, model_state_value, state_dict[model][full_precision], "Value mismatch for model state {}".format(model_state_key), ) def _aggregate_optimizer_states(rank_state_dict, sharded_states_original_dims, state_dict, mode=_AGGREGATION_MODE.Zero): """Aggregates all optimizer states from the rank_state_dict into state_dict""" optimizer = _utils.state_dict_optimizer_key() partition_info = _utils.state_dict_partition_info_key() sharded_optimizer_keys = _utils.state_dict_sharded_optimizer_keys() # if there are no optimizer states in the rank_state_dict, no optimizer aggregation is needed if optimizer not in rank_state_dict: return if optimizer not in state_dict: state_dict[optimizer] = {} # iterate over all optimizer state keys for model_state_key, optimizer_dict in rank_state_dict[optimizer].items(): for optimizer_key, optimizer_value in optimizer_dict.items(): if model_state_key not in state_dict[optimizer]: state_dict[optimizer][model_state_key] = {} if optimizer_key in sharded_optimizer_keys and model_state_key in rank_state_dict[partition_info]: # this optimizer state is sharded since a record exists in the partition_info subdict _add_or_update_sharded_key( optimizer_key, optimizer_value, state_dict[optimizer][model_state_key], model_state_key, rank_state_dict[partition_info][model_state_key], sharded_states_original_dims, mode, ) else: # this optimizer state is not sharded since a record for it does not exist in the partition_info subdict # or this optimizer key is not one of the sharded optimizer keys _add_or_validate_unsharded_key( optimizer_key, optimizer_value, state_dict[optimizer][model_state_key], "Value mismatch for model state {} and optimizer state {}".format(model_state_key, optimizer_key), ) def _reshape_states(sharded_states_original_dims, state_dict, mixed_precision_enabled): """Reshape model and optimizer states in the state_dict according to dimensions in sharded_states_original_dims""" model = _utils.state_dict_model_key() full_precision = _utils.state_dict_full_precision_key() optimizer = _utils.state_dict_optimizer_key() sharded_optimizer_keys = _utils.state_dict_sharded_optimizer_keys() for sharded_state_key, original_dim in sharded_states_original_dims.items(): # reshape model states to original_dim only when mixed precision is enabled if mixed_precision_enabled and (model in state_dict): state_dict[model][full_precision][sharded_state_key] = state_dict[model][full_precision][ sharded_state_key ].reshape(original_dim) # reshape optimizer states to original_dim if optimizer in state_dict: for optimizer_key, optimizer_value in state_dict[optimizer][sharded_state_key].items(): if optimizer_key in sharded_optimizer_keys: state_dict[optimizer][sharded_state_key][optimizer_key] = optimizer_value.reshape(original_dim) def _aggregate_trainer_options(rank_state_dict, state_dict, partial_aggregation): """Extracts trainer options from rank_state_dict and loads them accordingly on state_dict""" trainer_options = _utils.state_dict_trainer_options_key() state_dict[trainer_options] = {} mixed_precision = _utils.state_dict_trainer_options_mixed_precision_key() zero_stage = _utils.state_dict_trainer_options_zero_stage_key() world_rank = _utils.state_dict_trainer_options_world_rank_key() world_size = _utils.state_dict_trainer_options_world_size_key() optimizer_name = _utils.state_dict_trainer_options_optimizer_name_key() D_size = _utils.state_dict_trainer_options_data_parallel_size_key() H_size = _utils.state_dict_trainer_options_horizontal_parallel_size_key() state_dict[trainer_options][mixed_precision] = rank_state_dict[trainer_options][mixed_precision] state_dict[trainer_options][zero_stage] = 0 state_dict[trainer_options][world_rank] = rank_state_dict[trainer_options][world_rank] if partial_aggregation else 0 state_dict[trainer_options][world_size] = 1 state_dict[trainer_options][optimizer_name] = rank_state_dict[trainer_options][optimizer_name] state_dict[trainer_options][D_size] = 1 state_dict[trainer_options][H_size] = 1 def _aggregate_megatron_partition_info(rank_state_dict, state_dict): """Extracts partition_info from rank_state_dict and loads on state_dict for megatron-partitioned weights""" partition_info = _utils.state_dict_partition_info_key() if partition_info not in state_dict: state_dict[partition_info] = {} rank_partition_info = rank_state_dict[partition_info] for model_state_key, partition_info_dict in rank_partition_info.items(): if model_state_key not in state_dict[partition_info]: # add partition info only if weight is megatron partitioned if partition_info_dict["megatron_row_partition"] >= 0: state_dict[partition_info][model_state_key] = partition_info_dict def _to_pytorch_format(state_dict): """Convert ORT state dictionary schema (hierarchical structure) to PyTorch state dictionary schema (flat structure)""" pytorch_state_dict = {} for model_state_key, model_state_value in state_dict[_utils.state_dict_model_key()][ _utils.state_dict_full_precision_key() ].items(): # convert numpy array to a torch tensor pytorch_state_dict[model_state_key] = torch.tensor(model_state_value) return pytorch_state_dict def _get_parallellism_groups(data_parallel_size, horizontal_parallel_size, world_size): """Returns the D and H groups for the given sizes""" num_data_groups = world_size // data_parallel_size data_groups = [] for data_group_id in range(num_data_groups): data_group_ranks = [] for r in range(data_parallel_size): data_group_ranks.append(data_group_id + horizontal_parallel_size * r) data_groups.append(data_group_ranks) num_horizontal_groups = world_size // horizontal_parallel_size horizontal_groups = [] for hori_group_id in range(num_horizontal_groups): hori_group_ranks = [] for r in range(horizontal_parallel_size): hori_group_ranks.append(hori_group_id * horizontal_parallel_size + r) horizontal_groups.append(hori_group_ranks) return data_groups, horizontal_groups def _aggregate_over_ranks( ordered_paths, ranks, sharded_states_original_dims=None, mode=_AGGREGATION_MODE.Zero, partial_aggregation=False, pytorch_format=True, ): """Aggregate checkpoint files over set of ranks and return a single state dictionary Args: ordered_paths: list of paths in the order in which they must be aggregated ranks: list of ranks that are to be aggregated sharded_states_original_dims: dict containing the original dims for sharded states that are persisted over multiple calls to _aggregate_over_ranks() mode: mode of aggregation: Zero or Megatron partial_aggregation: boolean flag to indicate whether to produce a partially aggregated state which can be further aggregated over pytorch_format: boolean flag to select either ONNX Runtime or PyTorch state schema of the returned state_dict Returns: state_dict that can be loaded into an ORTTrainer or into a PyTorch model """ state_dict = {} if sharded_states_original_dims is None: sharded_states_original_dims = dict() world_rank = _utils.state_dict_trainer_options_world_rank_key() mixed_precision = _utils.state_dict_trainer_options_mixed_precision_key() zero_stage = _utils.state_dict_trainer_options_zero_stage_key() world_size = _utils.state_dict_trainer_options_world_size_key() optimizer_name = _utils.state_dict_trainer_options_optimizer_name_key() loaded_mixed_precision = None loaded_world_size = None loaded_zero_stage = None loaded_optimizer_name = None for i, path in enumerate(ordered_paths): rank_state_dict = _checkpoint_storage.load(path) assert _utils.state_dict_partition_info_key() in rank_state_dict, "Missing information: partition_info" assert _utils.state_dict_trainer_options_key() in rank_state_dict, "Missing information: trainer_options" assert ( ranks[i] == rank_state_dict[_utils.state_dict_trainer_options_key()][world_rank] ), "Unexpected rank in file at path {}. Expected {}, got {}".format( path, rank, rank_state_dict[_utils.state_dict_trainer_options_key()][world_rank] ) if loaded_mixed_precision is None: loaded_mixed_precision = rank_state_dict[_utils.state_dict_trainer_options_key()][mixed_precision] else: assert ( loaded_mixed_precision == rank_state_dict[_utils.state_dict_trainer_options_key()][mixed_precision] ), "Mixed precision state mismatch among checkpoint files. File: {}".format(path) if loaded_world_size is None: loaded_world_size = rank_state_dict[_utils.state_dict_trainer_options_key()][world_size] else: assert ( loaded_world_size == rank_state_dict[_utils.state_dict_trainer_options_key()][world_size] ), "World size state mismatch among checkpoint files. File: {}".format(path) if loaded_zero_stage is None: loaded_zero_stage = rank_state_dict[_utils.state_dict_trainer_options_key()][zero_stage] else: assert ( loaded_zero_stage == rank_state_dict[_utils.state_dict_trainer_options_key()][zero_stage] ), "Zero stage mismatch among checkpoint files. File: {}".format(path) if loaded_optimizer_name is None: loaded_optimizer_name = rank_state_dict[_utils.state_dict_trainer_options_key()][optimizer_name] else: assert ( loaded_optimizer_name == rank_state_dict[_utils.state_dict_trainer_options_key()][optimizer_name] ), "Optimizer name mismatch among checkpoint files. File: {}".format(path) # aggregate all model states _aggregate_model_states(rank_state_dict, sharded_states_original_dims, state_dict, loaded_mixed_precision, mode) if not pytorch_format: # aggregate all optimizer states if pytorch_format is False _aggregate_optimizer_states(rank_state_dict, sharded_states_original_dims, state_dict, mode) # for D+H aggregation scenario, the first pass of aggregation(partial aggregation) is over D groups # to aggregate over Zero, and another pass to aggregate Megatron partitioned # states. Preserve the relevant partition info only for weights that are megatron partitioned for # a partial aggregation call if partial_aggregation: _aggregate_megatron_partition_info(rank_state_dict, state_dict) # entry for trainer_options in the state_dict to perform other sanity checks if _utils.state_dict_trainer_options_key() not in state_dict: _aggregate_trainer_options(rank_state_dict, state_dict, partial_aggregation) # entry for user_dict in the state_dict if not already present if ( _utils.state_dict_user_dict_key() not in state_dict and _utils.state_dict_user_dict_key() in rank_state_dict ): state_dict[_utils.state_dict_user_dict_key()] = rank_state_dict[_utils.state_dict_user_dict_key()] # for a partial aggregation scenario, we might not have the entire tensor aggregated yet, thus skip reshape if not partial_aggregation: # reshape all the sharded tensors based on the original dimensions stored in sharded_states_original_dims _reshape_states(sharded_states_original_dims, state_dict, loaded_mixed_precision) # return a flat structure for PyTorch model in case pytorch_format is True # else return the hierarchical structure for ORTTrainer return _to_pytorch_format(state_dict) if pytorch_format else state_dict def _aggregate_over_D_H(ordered_paths, D_groups, H_groups, pytorch_format): """Aggregate checkpoint files and return a single state dictionary for the D+H (Zero+Megatron) partitioning strategy. For D+H aggregation scenario, the first pass of aggregation(partial aggregation) is over D groups to aggregate over Zero, and another pass over the previously aggregated states to aggregate Megatron partitioned states. """ sharded_states_original_dims = {} aggregate_data_checkpoint_files = [] # combine for Zero over data groups and save to temp file with tempfile.TemporaryDirectory() as save_dir: for group_id, d_group in enumerate(D_groups): aggregate_state_dict = _aggregate_over_ranks( ordered_paths["D"][group_id], d_group, sharded_states_original_dims, partial_aggregation=True, pytorch_format=False, ) filename = "ort.data_group." + str(group_id) + ".ort.pt" filepath = os.path.join(save_dir, filename) _checkpoint_storage.save(aggregate_state_dict, filepath) aggregate_data_checkpoint_files.append(filepath) assert len(aggregate_data_checkpoint_files) > 0 # combine for megatron: aggregate_state = _aggregate_over_ranks( aggregate_data_checkpoint_files, H_groups[0], sharded_states_original_dims, mode=_AGGREGATION_MODE.Megatron, pytorch_format=pytorch_format, ) return aggregate_state def aggregate_checkpoints(paths, pytorch_format=True): """Aggregate checkpoint files and return a single state dictionary Aggregates checkpoint files specified by paths and loads them one at a time, merging them into a single state dictionary. The checkpoint files represented by paths must be saved through ORTTrainer.save_checkpoint() function. The schema of the state_dict returned will be in the same as the one returned by ORTTrainer.state_dict() Args: paths: list of more than one file represented as strings where the checkpoint is saved pytorch_format: boolean flag to select either ONNX Runtime or PyTorch state schema of the returned state_dict Returns: state_dict that can be loaded into an ORTTrainer or into a PyTorch model """ loaded_trainer_options = _checkpoint_storage.load(paths[0], key=_utils.state_dict_trainer_options_key()) D_size = _utils.state_dict_trainer_options_data_parallel_size_key() H_size = _utils.state_dict_trainer_options_horizontal_parallel_size_key() world_size = _utils.state_dict_trainer_options_world_size_key() D_size = loaded_trainer_options[D_size] H_size = loaded_trainer_options[H_size] world_size = loaded_trainer_options[world_size] D_groups, H_groups = _get_parallellism_groups(D_size, H_size, world_size) combine_zero = loaded_trainer_options[_utils.state_dict_trainer_options_zero_stage_key()] > 0 combine_megatron = len(H_groups[0]) > 1 # order the paths in the order of groups in which they must be aggregated according to # data-parallel groups and H-parallel groups obtained # eg: {'D': [[path_0, path_2],[path_1, path_3]], 'H': [[path_0, path_1],[path_2, path_3]]} ordered_paths = _order_paths(paths, D_groups, H_groups) aggregate_state = None if combine_zero and combine_megatron: aggregate_state = _aggregate_over_D_H(ordered_paths, D_groups, H_groups, pytorch_format) elif combine_zero: aggregate_state = _aggregate_over_ranks( ordered_paths["D"][0], D_groups[0], mode=_AGGREGATION_MODE.Zero, pytorch_format=pytorch_format ) elif combine_megatron: aggregate_state = _aggregate_over_ranks( ordered_paths["H"][0], H_groups[0], mode=_AGGREGATION_MODE.Megatron, pytorch_format=pytorch_format ) return aggregate_state ################################################################################ # Helper functions ################################################################################ def _load_single_checkpoint(ort_trainer, checkpoint_dir, checkpoint_prefix, is_partitioned, strict): checkpoint_name = _get_checkpoint_name( checkpoint_prefix, is_partitioned, ort_trainer.options.distributed.world_rank, ort_trainer.options.distributed.world_size, ) checkpoint_file = os.path.join(checkpoint_dir, checkpoint_name) if is_partitioned: assert_msg = ( f"Couldn't find checkpoint file {checkpoint_file}." " Optimizer partitioning is enabled using ZeRO. Please make sure the checkpoint file exists " f"for rank {ort_trainer.options.distributed.world_rank} of {ort_trainer.options.distributed.world_size}" ) else: assert_msg = f"Couldn't find checkpoint file {checkpoint_file}." assert os.path.exists(checkpoint_file), assert_msg checkpoint_state = torch.load(checkpoint_file, map_location="cpu") experimental_load_state_dict(ort_trainer, checkpoint_state["model"], strict=strict) del checkpoint_state["model"] return checkpoint_state def _load_multi_checkpoint(ort_trainer, checkpoint_dir, checkpoint_prefix, strict): checkpoint_files = _list_checkpoint_files(checkpoint_dir, checkpoint_prefix) ckpt_agg = _CombineZeroCheckpoint(checkpoint_files) aggregate_state_dict = ckpt_agg.aggregate_checkpoints() experimental_load_state_dict(ort_trainer, aggregate_state_dict, strict=strict) # aggregate other keys in the state_dict. # Values will be overwritten for matching keys among workers all_checkpoint_states = dict() for checkpoint_file in checkpoint_files: checkpoint_state = torch.load(checkpoint_file, map_location="cpu") del checkpoint_state["model"] all_checkpoint_states.update(checkpoint_state) return all_checkpoint_states def _list_checkpoint_files(checkpoint_dir, checkpoint_prefix, extension=".ort.pt"): ckpt_file_names = [f for f in os.listdir(checkpoint_dir) if f.startswith(checkpoint_prefix)] ckpt_file_names = [f for f in ckpt_file_names if f.endswith(extension)] ckpt_file_names = [os.path.join(checkpoint_dir, f) for f in ckpt_file_names] assert len(ckpt_file_names) > 0, f"No checkpoint found with prefix '{checkpoint_prefix}' at '{checkpoint_dir}'" return ckpt_file_names def _get_checkpoint_name(prefix, is_partitioned, world_rank=None, world_size=None): SINGLE_CHECKPOINT_FILENAME = "{prefix}.ort.pt" MULTIPLE_CHECKPOINT_FILENAME = "{prefix}.ZeRO.{world_rank}.{world_size}.ort.pt" if is_partitioned: filename = MULTIPLE_CHECKPOINT_FILENAME.format( prefix=prefix, world_rank=world_rank, world_size=(world_size - 1) ) else: filename = SINGLE_CHECKPOINT_FILENAME.format(prefix=prefix) return filename def _split_state_dict(state_dict): optimizer_keys = ["Moment_1_", "Moment_2_", "Update_Count_", "Step"] split_sd = {"optimizer": {}, "fp32_param": {}, "fp16_param": {}} for k, v in state_dict.items(): mode = "fp32_param" for optim_key in optimizer_keys: if k.startswith(optim_key): mode = "optimizer" break if k.endswith("_fp16"): mode = "fp16_param" split_sd[mode][k] = v return split_sd class _CombineZeroCheckpoint(object): def __init__(self, checkpoint_files, clean_state_dict=None): assert len(checkpoint_files) > 0, "No checkpoint files passed" self.checkpoint_files = checkpoint_files self.clean_state_dict = clean_state_dict self.world_size = int(self.checkpoint_files[0].split("ZeRO")[1].split(".")[2]) + 1 assert len(self.checkpoint_files) == self.world_size, f"Could not find {self.world_size} files" self.weight_shape_map = dict() self.sharded_params = set() def _split_name(self, name): name_split = name.split("_view_") view_num = None if len(name_split) > 1: view_num = int(name_split[1]) optimizer_key = "" mp_suffix = "" if name_split[0].startswith("Moment_1"): optimizer_key = "Moment_1_" elif name_split[0].startswith("Moment_2"): optimizer_key = "Moment_2_" elif name_split[0].startswith("Update_Count"): optimizer_key = "Update_Count_" elif name_split[0].endswith("_fp16"): mp_suffix = "_fp16" param_name = name_split[0] if optimizer_key != "": param_name = param_name.split(optimizer_key)[1] param_name = param_name.split("_fp16")[0] return param_name, optimizer_key, view_num, mp_suffix def _update_weight_statistics(self, name, value): if name not in self.weight_shape_map: self.weight_shape_map[name] = value.size() # original shape of tensor def _reshape_tensor(self, key): value = self.aggregate_state_dict[key] weight_name, _, _, _ = self._split_name(key) set_size = self.weight_shape_map[weight_name] self.aggregate_state_dict[key] = value.reshape(set_size) def _aggregate(self, param_dict): for k, v in param_dict.items(): weight_name, optimizer_key, view_num, mp_suffix = self._split_name(k) if view_num is not None: # parameter is sharded param_name = optimizer_key + weight_name + mp_suffix if param_name in self.aggregate_state_dict and optimizer_key not in ["Update_Count_"]: self.sharded_params.add(param_name) # Found a previous shard of the param, concatenate shards ordered by ranks self.aggregate_state_dict[param_name] = torch.cat((self.aggregate_state_dict[param_name], v)) else: self.aggregate_state_dict[param_name] = v else: if k in self.aggregate_state_dict: assert (self.aggregate_state_dict[k] == v).all(), "Unsharded params must have the same value" else: self.aggregate_state_dict[k] = v self._update_weight_statistics(weight_name, v) def aggregate_checkpoints(self): warnings.warn( "_CombineZeroCheckpoint.aggregate_checkpoints() will be deprecated soon. " "Please use aggregate_checkpoints() instead.", DeprecationWarning, ) checkpoint_prefix = self.checkpoint_files[0].split(".ZeRO")[0] self.aggregate_state_dict = dict() for i in range(self.world_size): checkpoint_name = _get_checkpoint_name(checkpoint_prefix, True, i, self.world_size) rank_state_dict = torch.load(checkpoint_name, map_location=torch.device("cpu")) if "model" in rank_state_dict: rank_state_dict = rank_state_dict["model"] if self.clean_state_dict: rank_state_dict = self.clean_state_dict(rank_state_dict) rank_state_dict = _split_state_dict(rank_state_dict) self._aggregate(rank_state_dict["fp16_param"]) self._aggregate(rank_state_dict["fp32_param"]) self._aggregate(rank_state_dict["optimizer"]) for k in self.sharded_params: self._reshape_tensor(k) return self.aggregate_state_dict