Files
paddlepaddle--paddle/python/paddle/distributed/flex_checkpoint/dcp/resharder.py
T
2026-07-13 12:40:42 +08:00

1038 lines
37 KiB
Python

# 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.")