Files
paddlepaddle--paddlenlp/paddlenlp/transformers/segment_parallel_utils.py
T
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

186 lines
7.4 KiB
Python

# Copyright (c) 2023 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.
# 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.
import paddle
import paddle.distributed as dist
from paddle.autograd import PyLayer
from paddle.distributed.communication.group import _get_global_group
from paddle.distributed.fleet import fleet
def split_inputs_sequence_dim(inputs, sep_rank=None, sep_degree=None):
if sep_degree is None and sep_rank is None:
_hcg = fleet.get_hybrid_communicate_group()
sep_degree = _hcg.get_sep_parallel_world_size()
sep_rank = _hcg.get_sep_parallel_rank()
assert isinstance(sep_degree, int) and isinstance(
sep_rank, int
), f"sep_degree:{type(sep_degree)} and sep_rank:{type(sep_rank)} must be int"
if sep_degree <= 1:
return inputs
def do_split_sequence_dim(data, sep_rank, sep_degree):
if data is None:
return None
assert isinstance(data, paddle.Tensor), f"data should be paddle.Tensor, but is type:{type(data)}"
assert len(data.shape) == 2, f"data dims should be 2, but shaped: {data.shape}"
sliced_data = paddle.split(data, num_or_sections=sep_degree, axis=-1)[sep_rank]
return sliced_data
if isinstance(inputs, paddle.Tensor):
return do_split_sequence_dim(inputs, sep_rank, sep_degree)
elif isinstance(inputs, dict):
res = {}
for k, tensor in inputs.items():
res[k] = do_split_sequence_dim(tensor, sep_rank, sep_degree)
elif isinstance(inputs, list):
res = []
for tensor in inputs:
res.append(do_split_sequence_dim(tensor, sep_rank, sep_degree))
raise ValueError(f"the inputs should be a list or a dict, but is type: {type(inputs)}")
return res
@paddle.no_grad()
def _reshard_qkv(x, group, split_axis=2, concat_axis=0):
# [s/sep, b, h] -> [s, b, h/sep]
# [s, b, h/sep] -> [s/sep, b, h]
group = _get_global_group() if group is None else group
nranks = dist.get_world_size(group=group)
shape = x.shape
assert len(shape) == 3, "Only support 3D tensor, but got {}".format(len(shape))
assert shape[split_axis] % nranks == 0, "Only support evenly split, but got {} % {} != 0".format(shape[2], nranks)
comm_tensor_list = paddle.split(x, nranks, axis=split_axis)
output_list = [paddle.empty_like(comm_tensor_list[0]) for _ in comm_tensor_list]
dist.alltoall(output_list, comm_tensor_list, group=group)
reshard_tensor = paddle.concat(output_list, axis=concat_axis)
return reshard_tensor
class ReshardQKV(PyLayer):
@staticmethod
def forward(ctx, x, group=None, split_axis=2, concat_axis=0):
ctx.group = _get_global_group() if group is None else group
ctx.split_axis = split_axis
ctx.concat_axis = concat_axis
res = _reshard_qkv(x, group, split_axis=ctx.split_axis, concat_axis=ctx.concat_axis)
return res
@staticmethod
def backward(ctx, dy):
res = _reshard_qkv(dy, ctx.group, split_axis=ctx.concat_axis, concat_axis=ctx.split_axis)
return res
class ReshardLayer(paddle.nn.Layer):
def __init__(self, sep_group=None) -> None:
if sep_group is None:
_hcg = fleet.get_hybrid_communicate_group()
sep_group = _hcg.get_sep_parallel_group() if sep_group is None else sep_group
self.sep_group = sep_group
self.sep_degree = dist.get_world_size(group=self.sep_group)
super(ReshardLayer, self).__init__()
def forward(
self,
x,
split_axis=1,
concat_axis=2,
):
# if x dims==3, its shape can be [s/sep, b, h] or [b, s/sep, h], the output shape can be [s, b, h/sep] or [b, s, h/sep]
# if x dims==4, its shape can be [s, b, num_head/sep, head_dim] or [b, s, num_head/sep, head_dim], the output shape can be [s/sep, b, num_head, head_dim] or [b, s/sep, num_head, head_dim]
shape = x.shape
assert len(shape) == 3 or len(shape) == 4, "Only support 3D or 4D tensor"
if len(shape) == 4:
assert shape[split_axis] % self.sep_degree == 0
shape[split_axis] = shape[split_axis] // self.sep_degree
shape[concat_axis] = shape[concat_axis] * self.sep_degree
input_data = x
if len(shape) == 3:
reshard_tensor = ReshardQKV.apply(
input_data, self.sep_group, split_axis=split_axis, concat_axis=concat_axis
)
else:
input_data = input_data.reshape([0, 0, -1])
reshard_tensor = ReshardQKV.apply(
input_data, self.sep_group, split_axis=split_axis, concat_axis=concat_axis
)
reshard_tensor.reshape_(shape)
return reshard_tensor
def sep_reshard_layer(input, split_axis, concat_axis):
# [auto_parallel] do alltoall operation to reshard input from [Shard(concat_axis)] to [Shard[split_axis]]
sep_axis = input.process_mesh.dim_names.index("sep")
mp_axis = input.process_mesh.dim_names.index("mp")
input_placements = input.placements
if input_placements[sep_axis] != dist.Shard(concat_axis):
raise ValueError(
f"Input placements for 'sep' axis should be Shard({concat_axis}), but got {input_placements[sep_axis]}"
)
input_placements[sep_axis] = dist.Shard(split_axis)
if input_placements[sep_axis] == input_placements[mp_axis]:
input_placements[sep_axis] = dist.Shard(split_axis, shard_order=0)
input_placements[mp_axis] = dist.Shard(split_axis, shard_order=1)
out = dist.reshard(input, input.process_mesh, input_placements)
return out
def auto_split_inputs_sequence_dim(inputs):
def do_split_sequence_dim(data):
if data is None:
return None
data_mesh = data.process_mesh
data_placements = data.placements
sep_axis = data_mesh.dim_names.index("sep")
# shard along sep axis
data_placements[sep_axis] = dist.Shard(1)
data = dist.reshard(data, data_mesh, data_placements)
return data
if isinstance(inputs, paddle.Tensor):
return do_split_sequence_dim(inputs)
elif isinstance(inputs, dict):
res = {}
for k, tensor in inputs.items():
res[k] = do_split_sequence_dim(tensor)
elif isinstance(inputs, list):
res = []
for tensor in inputs:
res.append(do_split_sequence_dim(tensor))
else:
raise ValueError(f"the inputs should be a tensor, list or dict, but is type: {type(inputs)}")
return res