Files
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

330 lines
13 KiB
Python

# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
# Copyright (c) Microsoft Corporation.
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
# Copyright (C) 2024 THL A29 Limited, a Tencent company. 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 copy
import paddle
import paddle.distributed as dist
import paddle.nn.functional as F
try:
from paddle.distributed.auto_parallel.local_layer import LocalLayer
except:
class LocalLayer(object):
"""
A dummy class for LocalLayer, used when the actual class
cannot be imported.
"""
pass
from paddle import nn
from .auto_utils import einsum, get_mesh
from .moe_gate_auto import PretrainedMoEGate
def dispatching(x, dispatch_mask, scatter_index, num_experts, capacity):
"""
Rearranges the input tensor `x` based on gate results, truncates it according to the specified capacity, and performs padding.
Args:
x (Tensor)[Seq, Dim]: The input tensor.
dispatch_mask (List[Tensor[Seq, 1], Tensor[Seq, 1]]): A list of dispatch masks.
scatter_index (Union[List[Tensor[Seq,], Tensor[Seq]], Tensor[Seq, 2]]): A list or tensor representing scatter indices.
num_experts (int): The number of experts.
capacity (int): The capacity size.
Returns:
Tensor [Expert*Capacity, Dim]: The output tensor after dispatching.
"""
output = None
orig_dtype = x.dtype
if isinstance(scatter_index, paddle.Tensor):
scatter_index = scatter_index.unbind(1)
for i_scatter_index, i_dispatch_mask in zip(scatter_index, dispatch_mask):
init_output = paddle.zeros([num_experts * capacity, x.shape[-1]], dtype="float32")
updates = x * i_dispatch_mask.cast(x.dtype)
if output is None:
output = paddle.scatter(
init_output,
i_scatter_index,
updates,
overwrite=False,
)
else:
output = output + paddle.scatter(
init_output,
i_scatter_index,
updates,
overwrite=False,
)
if output.dtype != orig_dtype:
output = output.cast(orig_dtype)
return output
def combining(x, combine_weights, scatter_index):
"""
Performs combination and aggregation operations on the input matrix.
Args:
x: Tensor[num_experts * capacity, dim] - The input matrix to be processed, where the last dimension represents the number of features.
combine_weights: Union[List[Tensor[seq, 1], Tensor[seq, 1]], Tensor[seq, 2, 1]] - A list or tensor containing combination weights for each feature.
scatter_index: Union[List[Tensor[seq], Tensor[seq]], Tensor[seq, 2]] - A tuple of indices indicating which elements are to be aggregated, where the first element is the row index and the second element is the column index.
Returns:
Tensor: The output matrix after combination and aggregation, with a shape of [n, dim * num_features], where n is the number of samples in the input matrix.
"""
dim = x.shape[-1]
if isinstance(scatter_index, (list, tuple)):
scatter_index = paddle.concat([i.unsqueeze([-1]) for i in scatter_index], -1)
scatter_index = scatter_index.reshape([-1])
num_k = len(combine_weights) if isinstance(combine_weights, (list, tuple)) else combine_weights.shape[-1]
x = paddle.gather(x, scatter_index).reshape([-1, num_k, dim]) # [seq,2,dim]
if isinstance(combine_weights, (list, tuple)):
combine_weights = paddle.concat(combine_weights, -1).unsqueeze([1])
return paddle.matmul(combine_weights, x).squeeze(1) # [seq,1,2] @ [seq,2,dim] -> [seq,1,dim]
class LocalGatePart1(LocalLayer):
def __init__(self, config, gate: PretrainedMoEGate, ipp=None):
mesh = get_mesh(ipp)
out_dist_attrs = [
(mesh, [dist.Shard(0)]), # reshaped_input [b*s, h]
(mesh, [dist.Shard(0)]), # scores [b*s, e]
(mesh, [dist.Partial(dist.ReduceType.kRedMax)]), # expert_counts [e]
(mesh, [dist.Partial(dist.ReduceType.kRedAvg)]), # l_aux, scalar
(mesh, [dist.Partial(dist.ReduceType.kRedAvg)]), # l_zloss, scalar
]
grad_dist_attrs = [
None,
(mesh, [dist.Partial(dist.ReduceType.kRedAvg)]), # gate_weights.grad
(mesh, [dist.Partial(dist.ReduceType.kRedAvg)]), # e_score_correction_bias.grad
]
super().__init__(out_dist_attrs, grad_dist_attrs)
self.config = config
self.gate = gate
def forward(self, hidden_state, gate_weight, e_score_correction_bias, used_token=None):
# Implement Algorithm 2 from GShard paper.
batch_size, seq_len, d_model = hidden_state.shape
reshaped_input = hidden_state.reshape([-1, d_model])
# compute gating score
logits = F.linear(hidden_state, gate_weight, None)
with paddle.amp.auto_cast(False):
scores = self.gate.gate_score_func(logits=logits)
scores = scores.cast(paddle.get_default_dtype())
exp_counts, l_aux, l_zloss = self.gate.topkgating_part1(scores, e_score_correction_bias)
reshaped_scores = scores.reshape([-1, scores.shape[-1]])
return reshaped_input, reshaped_scores, exp_counts, l_aux, l_zloss
class LocalGateAndDispatch(LocalLayer):
def __init__(self, gate: PretrainedMoEGate, ipp=None):
mesh = get_mesh(ipp)
out_dist_attrs = [
(mesh, [dist.Shard(1)]), # dispatched_input [e,c,h]
(mesh, [dist.Shard(0)]), # combine_weights [s,e,c]
]
grad_dist_attrs = [
None,
None,
]
super().__init__(out_dist_attrs, grad_dist_attrs)
self.gate = gate
def forward(self, reshaped_input, scores):
combine_weights, dispatch_mask = self.gate.topkgating_part2(scores)
dispatched_input = einsum("sec,sm->ecm", paddle.cast(dispatch_mask, reshaped_input.dtype), reshaped_input)
return dispatched_input, combine_weights
class LocalCombine(LocalLayer):
def __init__(self, ipp=None):
self.mesh = get_mesh(ipp)
out_dist_attrs = [(self.mesh, [dist.Shard(0)])]
grad_dist_attrs = [None, None]
super().__init__(out_dist_attrs, grad_dist_attrs)
def forward(self, combine_weights, expert_output, dtype="float32", out_shape=None):
combined_output = einsum("sec,ecm->sm", combine_weights.cast(dtype), expert_output)
if out_shape is not None:
if dist.get_rank() in self.mesh.process_ids:
out_shape = dist.auto_parallel.moe_utils._cal_local_shape(
out_shape, self.out_dist_attrs[0][0], self.out_dist_attrs[0][1]
)
combined_output = combined_output.reshape(out_shape)
return combined_output
class MoELayer(nn.Layer):
def __init__(
self,
config,
moe_num_experts: int,
expert_class: nn.Layer,
expert_kwargs: dict,
gate: PretrainedMoEGate,
capacity: int = 1.0,
moe_group: str = "data",
all_to_all_dropout=0.0,
ipp: int = None,
):
super().__init__()
self.config = config
self.moe_num_experts = moe_num_experts
self.capacity = capacity
self.ipp = ipp
self.all_to_all_dropout = all_to_all_dropout
self.enable_recompute = False
self.experts = nn.LayerList([])
for i in range(self.moe_num_experts):
self.experts.append(expert_class(**expert_kwargs))
self.expert_parallel_degree, self.moe_num_experts_per_device = self._parse_moe_expert_parallel(
self.moe_num_experts, config
)
self._redistribute_experts(self.experts, config.moe_group)
self.moe_group = None
self.gate = gate
self.gate.group = self.moe_group
self.is_dummy_moe = True
self._post_init()
self.local_gate_part1 = LocalGatePart1(config, gate, ipp)
self.local_gate_and_dispatch = LocalGateAndDispatch(gate, ipp)
self.local_combine = LocalCombine(ipp)
def _redistribute_experts(self, experts, moe_group: str):
if moe_group != "None":
index = 0 if moe_group == "dp" else 1
self.moe_mesh_dim = index
ep_sub_meshes = dist.auto_parallel.api.split_mesh(get_mesh(self.ipp), index)
for i, expert in enumerate(experts):
ep_group_id = i // self.moe_num_experts_per_device
experts[i].redistribute_expert(ep_sub_meshes[ep_group_id], [dist.Replicate(), dist.Replicate()])
def _parse_moe_expert_parallel(self, moe_num_experts, config):
assert config.moe_group in ["dp", "mp", "None"], f"moe_group={config.moe_group} not in ['dp', 'mp', 'None']"
if config.moe_group == "None":
expert_parallel_degree = 1
else:
expert_parallel_degree = dist.fleet.auto.get_mesh().get_dim_size(config.moe_group)
assert (
moe_num_experts >= expert_parallel_degree
), f"expert moe_num_experts={moe_num_experts} >= moe_world_size={expert_parallel_degree}"
assert (
moe_num_experts % expert_parallel_degree == 0
), f"expert moe_num_experts={moe_num_experts} % moe_world_size={expert_parallel_degree} == 0"
moe_num_experts_per_device = moe_num_experts // expert_parallel_degree
return expert_parallel_degree, moe_num_experts_per_device
def _post_init(self):
for p in self.gate.parameters():
p.is_gate = True
for k in self.experts:
if k is not None:
for p in k.parameters():
p.expert = not self.is_dummy_moe
p.no_sync = not self.is_dummy_moe
# logger.info(f"expert param={p.name}, no-sync={p.no_sync}")
def expert_forward(self, dispatched_input):
sub_mesh_tensors = dist.auto_parallel.api.moe_sub_mesh_tensors(
dispatched_input, get_mesh(self.ipp), self.moe_mesh_dim, dispatched_input.placements
)
chunks = paddle.utils.flatten([t.unbind(1) for t in sub_mesh_tensors])
# try to simplify the code below
ep_group_outputs = []
expert_outputs = []
for i, (chunk, expert) in enumerate(zip(chunks, self.experts)):
chunk = chunk.contiguous()
expert_outputs += [expert(chunk)]
if (i + 1) % self.moe_num_experts_per_device == 0:
ep_group_outputs += [paddle.stack(expert_outputs, axis=1)]
expert_outputs = []
expert_output = dist.auto_parallel.api.moe_global_mesh_tensor(
ep_group_outputs, get_mesh(self.ipp), dispatched_input.placements, self.moe_mesh_dim
)
return expert_output
def forward(
self,
hidden_state: paddle.Tensor,
used_token: paddle.Tensor = None,
):
"""_summary_
Args:
input (_type_): _description_
used_token
Returns:
_type_: _description_
"""
# Implement Algorithm 2 from GShard paper.
batch_size, seq_len, d_model = hidden_state.shape
reshaped_input, gate_scores, exp_counts, l_aux, l_zloss = self.local_gate_part1(
hidden_state, self.gate.weight, self.gate.e_score_correction_bias, used_token=used_token
)
if self.gate.drop_tokens is False:
capacity = paddle.max(exp_counts)
capacity = dist.reshard(capacity, get_mesh(), [dist.Replicate()])
self.gate.capacity = int(capacity)
dispatched_input, combine_weights = self.local_gate_and_dispatch(reshaped_input, gate_scores)
ori_dispatched_placements = copy.deepcopy(dispatched_input.placements)
ep_placements = copy.deepcopy(dispatched_input.placements)
ep_placements[self.moe_mesh_dim] = dist.Shard(0)
dispatched_input = dist.reshard(dispatched_input, get_mesh(self.ipp), ep_placements)
# Re-shape after all-to-all: ecm -> gecm
dispatched_input = dispatched_input.reshape(
[self.expert_parallel_degree, self.moe_num_experts_per_device, -1, d_model]
)
expert_output = self.expert_forward(dispatched_input)
# Re-shape before drop_tokens: gecm -> ecm
expert_output = expert_output.reshape(
[self.expert_parallel_degree * self.moe_num_experts_per_device, -1, d_model]
)
expert_output = dist.reshard(expert_output, get_mesh(self.ipp), ori_dispatched_placements)
combined_output = self.local_combine(
combine_weights, expert_output, dtype=hidden_state[0].dtype, out_shape=hidden_state.shape
)
return combined_output, l_aux, l_zloss