# Copyright (c) Meta Platforms, Inc. and affiliates.# All rights reserved.## This source code is licensed under the BSD-style license found in the# LICENSE file in the root directory of this source tree.from__future__importannotationsfromcollections.abcimportMappingimporttorchfromtorchimportTensor
[docs]defreshard_tensor(key:str,source_splits:list[list[Tensor]],source_shard_sizes:tuple[int,int],target_shard_sizes:tuple[int,int],target_shard_ranks:tuple[int,int],shard_dims:Mapping[str,int],)->Tensor:""" Reshards a parameter tensor from a distributed source configuration to a target configuration. This function is meant for authors of new :class:`ModelCheckpointLoader` implementations. It handles the complex task of resharding tensors when loading checkpoints from one distributed configuration (e.g. 4-way tensor parallelism) to a different target configuration (e.g. 8-way tensor parallelism). It efficiently concatenates and slices tensors to produce the correct shards for the target rank. The resharding process involves: 1. Determining if the tensor requires tensor parallelism based on specified shard dimensions. 2. For tensor parallel tensors, concatenating source shards and re-slicing for the target configuration in a memory-efficient way. 3. For replicated tensors, concatenating data parallel splits. ``key`` specifies the name of the parameter to retrieve its sharding information from ``shard_dims``. See :func:`~fairseq2.nn.get_sharding_dims` for more information. ``source_splits`` is a 2D list structure ``[tp_idx][dp_idx]`` containing the source tensor shards. The outer list specifies tensor parallel shards and inner lists specify data parallel shards. ``source_shard_sizes`` and ``target_shard_sizes`` specify the distributed source and target configurations respectively in the form of ``(tp_size, dp_size)``. ``target_shard_ranks`` specifies the ranks of the current process in the target configuration in the form of ``(tp_rank, dp_rank)``. ``shard_dims`` specifies the mapping from parameter names to dimensions along which parameters should be sharded for tensor parallelism. Omitted for replicated tensors. See :func:`~fairseq2.nn.get_sharding_dims` for more information. Returns the resharded tensor for the target rank and configuration. .. code:: python :caption: Resharding from 2-way TP to 4-way TP param_name = "model.weight" # 2 TP shards with 1 DP shard each. source_splits = [[tensor_tp0_dp0], [tensor_tp1_dp0]] source_shard_sizes = (2, 1) # 2-way TP, 1-way DP target_shard_sizes = (4, 1) # 4-way TP, 1-way DP target_shard_ranks = (2, 0) # Want shard for TP rank 2 # For a tensor with TP dim=0, this will concatenate the 2 source shards # and slice out the portion corresponding to TP rank 2 in 4-way setup resharded = reshard_tensor( param_name, source_splits, source_shard_sizes, target_shard_sizes, target_shard_ranks, shard_dims={param_name: 0}, ) .. note:: This function deletes intermediate tensors during the resharding process to minimize peak memory usage. """source_tp_size,source_dp_size=source_shard_sizestarget_tp_size,target_dp_size=target_shard_sizestarget_tp_rank,target_dp_rank=target_shard_ranks# If the source and target tensor parallel sizes match, we can directly# return the unsharded data parallel tensor.ifsource_tp_size==target_tp_size:source_dp_splits=source_splits[target_tp_rank]ifsource_dp_size==1:returnsource_dp_splits[0]returntorch.cat(source_dp_splits,dim=0)tp_dim=shard_dims.get(key,None)# We assume that non-tensor parallel parameters are always replicated.iftp_dimisNone:source_dp_splits=source_splits[0]ifsource_dp_size==1:returnsource_dp_splits[0]returntorch.cat(source_dp_splits,dim=0)tp_splits=[]# Unshard the tensor over the source tensor parallel dimension.forsource_dp_splitsinsource_splits:ifsource_dp_size==1:tp_split=source_dp_splits[0]else:tp_split=torch.cat(source_dp_splits,dim=0)tp_splits.append(tp_split)# Reshard the tensor over the target parallel dimension.# Source shard dimensionssource_tp_dim_size=tp_splits[0].size(tp_dim)# Total unsharded dimension sizetp_dim_size=source_tp_dim_size*source_tp_size# Target shard dimensiontarget_tp_dim_size=tp_dim_size//target_tp_size# Slice boundaries for this target rank.first_target_idx=target_tp_rank*target_tp_dim_sizelast_target_idx=target_tp_rank*target_tp_dim_size+target_tp_dim_size-1# Determine which source ranks contain the target slice.first_source_tp_shard_idx=first_target_idx//source_tp_dim_sizelast_source_tp_shard_idx=last_target_idx//source_tp_dim_size# Starting index of the first relevant source shardfirst_source_idx=first_source_tp_shard_idx*source_tp_dim_size# Collect sub-slices from relevant source shards.tp_sub_splits=[]foridxinrange(first_source_tp_shard_idx,last_source_tp_shard_idx+1):tp_sub_splits.append(tp_splits[idx])deltp_splitstensor=torch.cat(tp_sub_splits,dim=tp_dim)deltp_sub_splits# Extract exact slice needed for this target rank.returntensor.narrow(dim=tp_dim,start=first_target_idx-first_source_idx,length=target_tp_dim_size)