# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import math from collections import defaultdict from dataclasses import dataclass, replace from enum import Enum, auto from typing import TYPE_CHECKING import numpy as np import paddle import paddle.distributed as dist from paddle.distributed.fleet.utils.log_util import logger from .metadata import LocalTensorIndex, LocalTensorMetadata from .sharded_weight import ( ShardedWeight, ) from .utils import ( compute_local_shape_and_global_offset, get_target_tensor, slice_tensor, ) if TYPE_CHECKING: from paddle.distributed.collective import Group from .reshard_comm import AbstractCommunicator PATH_TO_CHECKPOINT_FILES: dict[str, tuple[list, list]] = {} @dataclass(frozen=True) class ReadItem: """ A communication operation for a Tensor between ranks. Attributes: tensor_name (str): Name of the tensor. src_global_offset (tuple[int]): Global offset in the source tensor. dst_global_offset (tuple[int] | None): Global offset in the destination tensor. dst_rank (list[int]): Destination ranks. src_rank (int): Source rank. dst_local_offset (tuple[int]): Local offset in the destination tensor partition. src_local_offset (tuple[int]): Local offset in the source tensor partition. slice_shape (tuple[int]): Shape of the slice to transfer. file_name (str): The name of the file from which the source tensor is read on the source rank. dtype (str): Data type of the tensor. """ tensor_name: str src_global_offset: tuple[int] dst_global_offset: tuple[int] | None dst_rank: tuple[int] src_rank: int dst_local_offset: tuple[int] src_local_offset: tuple[int] slice_shape: tuple[int] file_name: str dtype: str comm_group: Group | None = None @dataclass(frozen=True) class ExtendReadItem(ReadItem): global_shape: tuple[int] | None = None class OperationType(Enum): GLOBAL_BROADCAST = auto() BROADCAST_ALLGATHER = auto() class AllGatherType(Enum): WITH_PADDING = auto() NO_PADDING = auto() INTERNAL_PADDING_TENSOR_NAME = "__internal_padding_tensor_name__" def get_load_infos(metadata_list, local_load_files, process_group, use_dist): load_info = {} cur_rank = paddle.distributed.get_rank() for metadata in metadata_list: for local_tensor_index, file_name in metadata.storage_metadata.items(): if file_name in local_load_files: load_info[local_tensor_index] = ( cur_rank, file_name, ) load_info_list = [] if use_dist: paddle.distributed.all_gather_object( load_info_list, load_info, process_group ) else: load_info_list.append(load_info) load_infos = {} for load_info in load_info_list: for local_tensor_index, (rank, file_name) in load_info.items(): assert local_tensor_index not in load_infos load_infos[local_tensor_index] = (rank, file_name) return load_infos def compute_overlap( cur_chunk_metadata: LocalTensorMetadata, storage_local_tensor_metadata: LocalTensorMetadata, ): cur_offsets = [] storage_offsets = [] lengths = [] for cur_len, cur_offset, storage_len, storage_offset in zip( cur_chunk_metadata.local_shape, cur_chunk_metadata.global_offset, storage_local_tensor_metadata.local_shape, storage_local_tensor_metadata.global_offset, ): begin_offset = max(cur_offset, storage_offset) end_offset = min(cur_offset + cur_len, storage_offset + storage_len) if begin_offset == cur_offset: cur_offsets.append(0) storage_offsets.append(begin_offset - storage_offset) elif begin_offset == storage_offset: cur_offsets.append(begin_offset - cur_offset) storage_offsets.append(0) else: raise ValueError( f"Invalid begin_offset:{begin_offset}, cur_offset:{cur_offset}, storage_offset:{storage_offset}" ) lengths.append(end_offset - begin_offset) assert lengths[-1] >= 0, ( f"Invalid length:{lengths[-1]}, end_offset:{end_offset}, begin_offset:{begin_offset}" ) return cur_offsets, storage_offsets, lengths def not_overlap( cur_chunk_metadata: LocalTensorMetadata, storage_local_tensor_metadata: LocalTensorMetadata, ): for cur_len, cur_offset, storage_len, storage_offset in zip( cur_chunk_metadata.local_shape, cur_chunk_metadata.global_offset, storage_local_tensor_metadata.local_shape, storage_local_tensor_metadata.global_offset, ): if ( cur_offset >= (storage_offset + storage_len) or (cur_offset + cur_len) <= storage_offset ): return True return False def build_storage_state_dict_metadata(metadata_list): counts = {} for md in metadata_list: items = md.state_dict_metadata.items() for k, lst in items: counts[k] = counts.get(k, 0) + len(lst) result = {k: [None] * n for k, n in counts.items()} offset = dict.fromkeys(counts, 0) for md in metadata_list: items = md.state_dict_metadata.items() for k, lst in items: o = offset[k] n = len(lst) result[k][o : o + n] = lst offset[k] = o + n return result def get_read_items( metadata_list, state_dict, process_group, use_dist, load_infos ): storage_state_dict_metadata = {} storage_state_dict_metadata = build_storage_state_dict_metadata( metadata_list ) read_items = [] global_shape = None for tensor_key, val in state_dict.items(): tensor_name = None if isinstance(val, paddle.Tensor): if val.is_dist(): # when val is scalar, the shape is [] ( local_shape, global_offset, ) = ( compute_local_shape_and_global_offset( val.shape, val.process_mesh, val.placements, ) if len(val.shape) > 0 else ((), ()) ) global_shape = tuple(val.shape) if local_shape is None or global_offset is None: continue else: local_shape = tuple(val.shape) global_offset = ( tuple([0] * len(val.shape)) if len(val.shape) > 0 else () ) global_shape = local_shape dtype = str(val.dtype).split(".")[1] tensor_name = tensor_key elif isinstance(val, ShardedWeight): local_shape, global_offset = ( (val.local_shape, val.global_offset) if len(val.global_shape) > 0 else ((), ()) ) dtype = str(val.local_tensor.dtype).split(".")[1] tensor_name = ( tensor_key[0] if isinstance(tensor_key, tuple) else tensor_key ) else: raise ValueError( f"Only support paddle.Tensor., val type:{type(val)}" ) cur_chunk_metadata = LocalTensorMetadata( global_offset, local_shape, dtype, global_shape ) for storage_local_tensor_metadata in storage_state_dict_metadata[ tensor_name ]: if not_overlap(cur_chunk_metadata, storage_local_tensor_metadata): continue cur_offsets, storage_offsets, lengths = compute_overlap( cur_chunk_metadata, storage_local_tensor_metadata ) storage_local_tensor_index = LocalTensorIndex( tensor_name, tuple(storage_local_tensor_metadata.global_offset), local_shape=tuple(storage_local_tensor_metadata.local_shape), ) src_rank, file_name = load_infos[storage_local_tensor_index] read_items.append( ReadItem( tensor_name=tensor_name, src_global_offset=tuple( storage_local_tensor_metadata.global_offset ), dst_global_offset=global_offset, dst_rank=(paddle.distributed.get_rank(),), src_rank=src_rank, dst_local_offset=tuple(cur_offsets), src_local_offset=tuple(storage_offsets), slice_shape=tuple(lengths), file_name=file_name, dtype=storage_local_tensor_metadata.dtype, ), ) global_read_items = [] tmp = [] if use_dist: paddle.distributed.all_gather_object(tmp, read_items, process_group) else: tmp.append(read_items) for items in tmp: for item in items: global_read_items.append(item) return global_read_items class StateDictResharder: def __init__( self, target_state_dict, source_state_dict, metadata_list, communicator: AbstractCommunicator, process_group=None, offload=False, use_dist=True, ): self.target_state_dict = target_state_dict self.source_state_dict = source_state_dict self.metadata_list = metadata_list self.communicator = communicator self.process_group = process_group self.offload = offload self.use_dist = use_dist def preprocess(self): if self.offload: for file_name, state_dict in self.source_state_dict.items(): self.source_state_dict[file_name] = { k: paddle.to_tensor(v, place=paddle.CPUPlace()) if isinstance(v, np.ndarray) else v for k, v in state_dict.items() } local_load_files = list(self.source_state_dict.keys()) load_infos = get_load_infos( self.metadata_list, local_load_files, self.process_group, self.use_dist, ) read_items = get_read_items( self.metadata_list, self.target_state_dict, self.process_group, self.use_dist, load_infos, ) processed_target_state_dict = { k: v.local_tensor if isinstance(v, ShardedWeight) else v for k, v in self.target_state_dict.items() } has_tuple_key = any( isinstance(k, tuple) for k in processed_target_state_dict ) has_non_tuple_key = any( not isinstance(k, tuple) for k in processed_target_state_dict ) assert not (has_tuple_key and has_non_tuple_key), ( "target_state_dict contains a mix of tuple and non-tuple keys." ) return processed_target_state_dict, read_items def local_reshard(self, read_items, processed_target_state_dict): for read_item in read_items: src_tensor = self.source_state_dict[read_item.file_name][ read_item.tensor_name ] src_chunk_tensor = slice_tensor( src_tensor, read_item.src_local_offset, read_item.slice_shape ).contiguous() dst_tensor = get_target_tensor( processed_target_state_dict, read_item ) dst_chunk_tensor = slice_tensor( dst_tensor, read_item.dst_local_offset, read_item.slice_shape ) if src_chunk_tensor.place != dst_chunk_tensor.place: src_chunk_tensor = src_chunk_tensor.to(dst_chunk_tensor.place) paddle.assign(src_chunk_tensor, dst_chunk_tensor) def reshard(self): cur_rank = paddle.distributed.get_rank() processed_target_state_dict, read_items = self.preprocess() logger.info( f"ReadItem generation completed, with a total of {len(read_items)}." ) if not read_items: return processed_target_state_dict context = { 'rank': cur_rank, 'process_group': self.process_group, } state = { 'source_state_dict': self.source_state_dict, 'target_state_dict': processed_target_state_dict, } if self.use_dist: self.communicator.communicate(read_items, state, context) else: self.local_reshard(read_items, processed_target_state_dict) del self.source_state_dict return processed_target_state_dict def assign_sharded_weight(src, dst): assert src.global_shape == dst.global_shape, ( "Global shapes must be the same" ) ndim = len(src.global_shape) starts, ends = [], [] dst_starts, dst_ends = [], [] dest_tensor = dst.local_tensor if not dest_tensor._is_initialized(): buffer = paddle.zeros_like(dest_tensor) buffer._share_buffer_to(dest_tensor) for i in range(ndim): src_begin = src.global_offset[i] src_end = src_begin + src.local_shape[i] dst_begin = dst.global_offset[i] dst_end = dst_begin + dst.local_shape[i] overlap_begin = max(src_begin, dst_begin) overlap_end = min(src_end, dst_end) if overlap_end <= overlap_begin: return starts.append(overlap_begin - src_begin) ends.append(overlap_end - src_begin) dst_starts.append(overlap_begin - dst_begin) dst_ends.append(overlap_end - dst_begin) src_slice = paddle.slice( src.local_tensor, axes=list(range(ndim)), starts=starts, ends=ends ) dst_slice = paddle.slice( dst.local_tensor, axes=list(range(ndim)), starts=dst_starts, ends=dst_ends, ) paddle.assign(src_slice, dst_slice) class ThreeDCommGroupStateResharder: def __init__( self, target_state_dict, source_state_dict, metadata_list, h_group, v_group, p_group, memory_growth_threshold: int = 8 * (2**30), # 8GB offload=False, ): self.target_state_dict = target_state_dict self.source_state_dict = source_state_dict assert len(metadata_list) == 1, "Only support one metadata now!" self.metadata = metadata_list[0] self.h_group = h_group self.v_group = v_group for group, name in [ (self.h_group, "horizontal"), (self.v_group, "vertical"), ]: assert group.nranks > 1, ( f"The number of ranks in the {name} communication group must be greater than 1, " f"but actually it is {group.nranks}. Please check this communication group: {group}!" ) self.p_group = p_group self.using_2d_comm_group = (not self.p_group) or ( self.p_group.nranks == 1 ) self.memory_growth_threshold = memory_growth_threshold self.offload = offload self.using_tuple_key = True self.preprocess() def preprocess(self): if self.offload: for file_name, state_dict in self.source_state_dict.items(): self.source_state_dict[file_name] = { k: paddle.to_tensor(v, place=paddle.CPUPlace()) if isinstance(v, np.ndarray) else v for k, v in state_dict.items() } for file_name, state_dict in self.source_state_dict.items(): for tensor_name, tensor in state_dict.items(): if tensor.dtype == paddle.float32: state_dict[tensor_name] = tensor.cuda().pin_memory() else: state_dict[tensor_name] = tensor.cuda() self.local_load_files = list(self.source_state_dict.keys()) has_tuple_key = any( isinstance(k, tuple) for k in self.target_state_dict ) has_non_tuple_key = any( not isinstance(k, tuple) for k in self.target_state_dict ) assert not (has_tuple_key and has_non_tuple_key), ( "target_state_dict contains a mix of tuple and non-tuple keys." ) assert all( isinstance(v, ShardedWeight) for _, v in self.target_state_dict.items() ), "All sharded weights must be ShardedWeight type." self.using_tuple_key = has_tuple_key self.grouped_target_state_dict = defaultdict(list) for key, sharded_weight in self.target_state_dict.items(): if self.using_tuple_key: self.grouped_target_state_dict[key[0]].append(sharded_weight) else: self.grouped_target_state_dict[key].append(sharded_weight) self.cur_rank = paddle.distributed.get_rank() self._build_cross_section_topology() self.get_read_items() self.schedule_read_items() self.aggregate_global_read_items() def all_gather_cross_section_fn(self, info): h_group = self.h_group v_group = self.v_group h_obj_list = [] paddle.distributed.all_gather_object(h_obj_list, info, h_group) v_obj_list = [] paddle.distributed.all_gather_object(v_obj_list, h_obj_list, v_group) gathered_info = [x for sublist in v_obj_list for x in sublist] return gathered_info def _build_cross_section_topology(self): h_ranks = [] self.topology = [] paddle.distributed.all_gather_object( h_ranks, self.cur_rank, self.h_group ) paddle.distributed.all_gather_object( self.topology, h_ranks, self.v_group ) if not self.using_2d_comm_group: p_ranks = [] paddle.distributed.all_gather_object( p_ranks, self.cur_rank, self.p_group ) else: p_ranks = [self.cur_rank] self.parallel_index = {rank: i for i, rank in enumerate(p_ranks)} self.p_ranks = p_ranks self.cur_parallel_index = self.parallel_index[self.cur_rank] self.vertical_ranks = [set(col) for col in zip(*self.topology)] self.horizontal_index = { rank: i for i, ranks in enumerate(self.vertical_ranks) for rank in ranks } self.vertical_index = { rank: i for i, row in enumerate(self.topology) for rank in row } self.cur_horizontal_index = self.horizontal_index[self.cur_rank] self.h_group_size = self.h_group.nranks self.v_group_size = self.v_group.nranks # NOTE(xingmingyyj) : maybe not need this function def dedup_read_items(self, global_read_items): group = defaultdict(list) for item in global_read_items: key = (item.tensor_name, item.src_global_offset, item.slice_shape) group[key].append(item) result = [] for key, items in group.items(): min_item = min(items, key=lambda x: x.src_rank) result.append(min_item) return result def get_read_items( self, all_gather_args=None, ): current_rank = paddle.distributed.get_rank() state_dict_metadata = self.metadata.state_dict_metadata storage_metadata = self.metadata.storage_metadata shard_infos = {} for local_tensor_index, file_name in storage_metadata.items(): tensor_key = local_tensor_index.tensor_key local_tensor_metadata = state_dict_metadata[tensor_key] assert len(local_tensor_metadata) != 0, ( f"No metadata found for tensor with name {tensor_key} in file {file_name}" ) global_shape = local_tensor_metadata[0].global_shape key = (tensor_key, file_name) shard_info = ( global_shape, local_tensor_index.local_shape, local_tensor_index.global_offset, ) shard_infos[key] = shard_info local_read_plan = [] for read_file, state_dict in self.source_state_dict.items(): for tensor_name, tensor in state_dict.items(): global_shape, local_shape, global_offset = shard_infos[ (tensor_name, read_file) ] dtype = str(tensor.dtype).split(".")[1] assert tuple(tensor.shape) == tuple(local_shape), ( f"Shape mismatch in tensor name {tensor_name} in file {read_file}, expected shape {local_shape}, but got {tuple(tensor.shape)}" ) common_attrs = { "tensor_name": tensor_name, "src_rank": current_rank, "src_global_offset": tuple(global_offset), "dst_global_offset": tuple(global_offset), "src_local_offset": (0,) * len(local_shape), "dst_local_offset": (0,) * len(local_shape), "slice_shape": tuple(local_shape), "global_shape": tuple(global_shape), "file_name": read_file, "dtype": dtype, "dst_rank": None, "comm_group": None, } local_read_plan.append(ExtendReadItem(**common_attrs)) gathered_plans_per_rank = self.all_gather_cross_section_fn( local_read_plan ) global_read_plan_per_section = [ item for plan in gathered_plans_per_rank for item in plan ] self.read_items = self.dedup_read_items(global_read_plan_per_section) def schedule_read_items(self): vertical_ranks = self.vertical_ranks global_broadcast_read_items = [] bucket_read_items = defaultdict(list) for item in self.read_items: cur_dtype = item.dtype cur_shape = item.slice_shape element_size = paddle.core.size_of_dtype(getattr(paddle, cur_dtype)) memory_growth = ( element_size * math.prod(cur_shape) * len(vertical_ranks) ) if memory_growth > self.memory_growth_threshold: global_broadcast_read_items.append(item) continue else: key = (cur_shape, cur_dtype) bucket_read_items[key].append(item) bucket_read_items_t = sorted( bucket_read_items.items(), key=lambda x: ( x[0][0], x[0][1], ), ) bucket_read_items = dict(bucket_read_items_t) order_rules = lambda read_item: ( read_item.tensor_name, read_item.src_rank, read_item.src_global_offset, read_item.dst_rank, read_item.dst_local_offset, read_item.dst_global_offset if read_item.dst_global_offset is not None else (), read_item.src_local_offset, read_item.slice_shape, read_item.file_name, read_item.dtype, ) for k, v in bucket_read_items.items(): bucket_read_items[k] = sorted(v, key=order_rules) batch_read_items = [] for (cur_shape, cur_dtype), items in list(bucket_read_items.items()): if len(items) < self.h_group_size: continue while len(items) >= self.h_group_size: cur_batch_read_items = [None] * len(vertical_ranks) cnt = 0 used_indices = set() for i, item in enumerate(items): if i in used_indices: continue src_rank = item.src_rank h_index = self.horizontal_index[src_rank] if cur_batch_read_items[h_index] is None: cur_batch_read_items[h_index] = item used_indices.add(i) cnt += 1 if cnt == len(vertical_ranks): break if all(i is not None for i in cur_batch_read_items): batch_read_items.append( (cur_batch_read_items, AllGatherType.NO_PADDING) ) items = [ item for i, item in enumerate(items) if i not in used_indices ] bucket_read_items[(cur_shape, cur_dtype)] = items else: break while len(bucket_read_items) != 0: cur_batch_read_items = [None] * len(vertical_ranks) cur_batch_dtype = None used_indices = defaultdict(set) cnt = 0 for (cur_shape, cur_dtype), items in bucket_read_items.items(): cur_batch_dtype = cur_dtype break for (cur_shape, cur_dtype), items in bucket_read_items.items(): if cur_dtype != cur_batch_dtype: continue for i, item in enumerate(items): src_rank = item.src_rank h_index = self.horizontal_index[src_rank] if cur_batch_read_items[h_index] is None: cur_batch_read_items[h_index] = item used_indices[(cur_shape, cur_dtype)].add(i) cnt += 1 if cnt == len(vertical_ranks): break need_remove = [] for key, items in list(bucket_read_items.items()): remaining_items = [ item for i, item in enumerate(items) if i not in used_indices[key] ] if len(remaining_items) == 0: need_remove.append(key) else: bucket_read_items[key] = remaining_items for key in need_remove: del bucket_read_items[key] for i, item in enumerate(cur_batch_read_items): if item is None: src_rank = min(vertical_ranks[i]) common_attrs = { "tensor_name": INTERNAL_PADDING_TENSOR_NAME, "src_rank": src_rank, "src_global_offset": (0,), "dst_global_offset": (0,), "src_local_offset": (0,), "dst_local_offset": (0,), "slice_shape": (1,), "global_shape": (1,), "file_name": "padding_vfile", "dtype": cur_batch_dtype, "comm_group": None, } padding_read_item = ExtendReadItem( dst_rank=None, **common_attrs ) cur_batch_read_items[i] = padding_read_item batch_read_items.append( (cur_batch_read_items, AllGatherType.WITH_PADDING) ) self.global_broadcast_read_items = global_broadcast_read_items self.batch_read_items = batch_read_items def aggregate_global_read_items(self): if self.using_2d_comm_group: self.aggregated_global_broadcast_read_items = ( self.global_broadcast_read_items ) self.aggregated_batch_read_items = [ [batch_items] for batch_items in self.batch_read_items ] return aggregated_global_broadcast_read_items = [] aggregated_batch_read_items = [] dist.all_gather_object( aggregated_global_broadcast_read_items, self.global_broadcast_read_items, self.p_group, ) dist.all_gather_object( aggregated_batch_read_items, self.batch_read_items, self.p_group, ) self.aggregated_global_broadcast_read_items = [ item for sublist in aggregated_global_broadcast_read_items for item in sublist ] self.aggregated_batch_read_items = [] # [[[batch1],[batch2],,,,],] max_tasks = max( [len(sublist) for sublist in aggregated_batch_read_items] ) for i in range(max_tasks): task_batches = [] for batch_read_items in aggregated_batch_read_items: if len(batch_read_items) != 0: task_batches.append(batch_read_items.pop(0)) else: task_batches.append(([], None)) self.aggregated_batch_read_items.append(task_batches) def _process_one_batch_broadcast_in_section(self, batch_items): """Performs V-Broadcast + H-AllGather for one batch of items.""" read_items, allgather_type = batch_items if len(read_items) == 0: return [] read_item = read_items[self.cur_horizontal_index] if self.cur_rank == read_item.src_rank: buffer = ( paddle.empty(read_item.slice_shape, read_item.dtype) if read_item.tensor_name == INTERNAL_PADDING_TENSOR_NAME else self.source_state_dict[read_item.file_name][ read_item.tensor_name ] ) if not isinstance(buffer.place, paddle.CUDAPlace): buffer = buffer.cuda() else: buffer = paddle.empty(read_item.slice_shape, dtype=read_item.dtype) paddle.distributed.broadcast( buffer, src=read_item.src_rank, group=self.v_group ) tensor_list = [] if allgather_type == AllGatherType.WITH_PADDING: max_numel = max(math.prod(item.slice_shape) for item in read_items) if math.prod(buffer.shape) == max_numel: buffer = buffer.reshape( [ max_numel, ] ) else: numel = buffer.numel() padded_buffer = paddle.zeros([max_numel], dtype=buffer.dtype) padded_buffer[:numel] = paddle.reshape(buffer, [-1]) buffer._clear() buffer = padded_buffer paddle.distributed.all_gather( tensor_list, buffer, group=self.h_group ) unpadded_tensor_list = [] for idx, padded_tensor in enumerate(tensor_list): read_item = read_items[idx] numel = math.prod(read_item.slice_shape) unpadded_tensor = ( padded_tensor[:numel].clone().reshape(read_item.slice_shape) ) unpadded_tensor_list.append(unpadded_tensor) padded_tensor._clear() tensor_list = unpadded_tensor_list else: paddle.distributed.all_gather( tensor_list, buffer, group=self.h_group ) # NOTE(xingmingyyj) Release the GPU memory occupied by source_state_dict in advance. buffer._clear() return tensor_list def broadcast_cross_p_group_and_assign(self, tensor_list, task_batches): batch_read_items, allgather_type = task_batches[self.cur_parallel_index] need_remove_indices = set() for idx, read_item in enumerate(batch_read_items): if read_item.tensor_name == INTERNAL_PADDING_TENSOR_NAME: need_remove_indices.add(idx) for idx in sorted(need_remove_indices, reverse=True): del tensor_list[idx] filtered_read_items = [] for idx, (batch_read_items, allgather_type) in enumerate(task_batches): src_rank = self.p_ranks[idx] for read_item in batch_read_items: if read_item.tensor_name != INTERNAL_PADDING_TENSOR_NAME: replcaed_read_item = replace(read_item, src_rank=src_rank) filtered_read_items.append(replcaed_read_item) cnt = 0 for idx, read_item in enumerate(filtered_read_items): if not self.using_2d_comm_group: if read_item.src_rank == self.cur_rank: buffer = tensor_list[cnt] cnt += 1 else: buffer = paddle.empty( read_item.slice_shape, dtype=read_item.dtype ) paddle.distributed.broadcast( buffer, src=read_item.src_rank, group=self.p_group ) else: buffer = tensor_list[cnt] cnt += 1 received_sharded_weight = ShardedWeight( key=read_item.tensor_name, local_tensor=buffer, local_shape=read_item.slice_shape, global_shape=read_item.global_shape, global_offset=read_item.src_global_offset, ) for target_sharded_weight in self.grouped_target_state_dict[ read_item.tensor_name ]: if not target_sharded_weight.local_tensor._is_initialized(): buffer_t = paddle.zeros_like( target_sharded_weight.local_tensor ) buffer_t._share_buffer_to( target_sharded_weight.local_tensor ) src_tensor = received_sharded_weight.local_tensor tgt_place = target_sharded_weight.local_tensor.place if src_tensor.place != tgt_place: src_tensor = src_tensor.to(tgt_place) received_sharded_weight.local_tensor = src_tensor assign_sharded_weight( src=received_sharded_weight, dst=target_sharded_weight, ) buffer._clear() del received_sharded_weight def broadcast_cross_global_group_and_assign(self): global_broadcast_read_items = ( self.aggregated_global_broadcast_read_items ) total_items = len(global_broadcast_read_items) for idx, read_item in enumerate(global_broadcast_read_items, start=1): if idx % 10 == 0 or idx == total_items: logger.info( f"Broadcasting item {idx}/{total_items}: {read_item.tensor_name}" ) if self.cur_rank == read_item.src_rank: buffer = self.source_state_dict[read_item.file_name][ read_item.tensor_name ] if not isinstance(buffer.place, paddle.CUDAPlace): buffer = buffer.cuda() else: buffer = paddle.empty( read_item.slice_shape, dtype=read_item.dtype ) # NOTE(xingmingyyj): using global group to broadcast paddle.distributed.broadcast( buffer, src=read_item.src_rank, group=None ) received_sharded_weight = ShardedWeight( key=read_item.tensor_name, local_tensor=buffer, local_shape=read_item.slice_shape, global_shape=read_item.global_shape, global_offset=read_item.src_global_offset, ) for target_sharded_weight in self.grouped_target_state_dict[ read_item.tensor_name ]: if not target_sharded_weight.local_tensor._is_initialized(): buffer_t = paddle.zeros_like( target_sharded_weight.local_tensor ) buffer_t._share_buffer_to( target_sharded_weight.local_tensor ) assign_sharded_weight( src=received_sharded_weight, dst=target_sharded_weight, ) buffer._clear() del received_sharded_weight def reshard(self): total = len(self.aggregated_batch_read_items) logger.info( "[ThreeDCommGroupStateResharder] Begin resharding using batch broadcasting..." ) for idx, task_batches in enumerate( self.aggregated_batch_read_items, start=1 ): tensor_list = self._process_one_batch_broadcast_in_section( task_batches[self.cur_parallel_index] ) self.broadcast_cross_p_group_and_assign(tensor_list, task_batches) if idx % 10 == 0 or idx == total: logger.info( f"Resharding batches: {idx}/{total} ({idx * 100 // total}%)" ) logger.info( "[ThreeDCommGroupStateResharder] End resharding using batch broadcasting..." ) logger.info( "[ThreeDCommGroupStateResharder] Begin resharding using global broadcasting..." ) self.broadcast_cross_global_group_and_assign() logger.info( "[ThreeDCommGroupStateResharder] End resharding using global broadcasting..." ) logger.info("[ThreeDCommGroupStateResharder] Resharding finished.")