01

MoE 架构全景

moe_layer.py MoELayer BaseMoELayer

在标准 Transformer 中,每个 token 都经过同一个 MLP 层。Mixture of Experts(MoE) 的核心思想是:用多个独立的 expert MLP 替换单一 MLP,每个 token 只被路由到其中 top-k 个 expert 进行计算。这样模型参数量可以大幅增加(例如 256 个 expert),但每个 token 的实际计算量只等价于 k 个 expert,实现了参数量与计算量的解耦

Megatron-LM 的 MoE 实现位于 megatron/core/transformer/moe/ 目录,核心文件包括:

  • moe_layer.py — MoELayer 主类,编排整个前向流程
  • router.py — TopKRouter,决定 token 到 expert 的路由
  • token_dispatcher.py — AlltoAll / AllGather / Flex 三种 token 分发策略
  • experts.py — GroupedMLP / TEGroupedMLP / SequentialMLP 三种 expert 实现
  • shared_experts.py — SharedExpertMLP,所有 token 共享的"常驻 expert"
  • moe_utils.py — 辅助函数(aux loss、sinkhorn、permute/unpermute 等)

MoELayer 的前向流程由五个阶段组成,定义在 MoELayer.forward()custom_forward 内部函数中:

shared_experts_compute(hidden_states)

Shared expert 使用原始 hidden_states(路由之前)进行前向计算。这一步在 route 之前执行,确保 shared expert 看到的是未经修改的输入。

route(hidden_states) → probs, routing_map

Router 计算每个 token 到各 expert 的路由概率。输出 probs(权重)和 routing_map(bool 掩码,标记哪些 expert 被选中)。

preprocess → dispatch → routed_experts_compute

preprocess:Token Dispatcher 的预处理(排列 token、计算 split 元信息)。dispatch:通过 All-to-All 通信将 token 发送到对应 EP rank。routed_experts_compute:各 rank 上的 local expert 处理接收到的 token。

combine(output, shared_expert_output)

通过反向 All-to-All 收集 expert 输出,unpermute 恢复原始 token 顺序,最后加上 shared expert 的输出。

moe_layer.py — MoELayer.forward() 核心流程 L312-366
def forward(self, hidden_states: torch.Tensor):
    def custom_forward(hidden_states):
        # ① Shared expert 先于路由执行(使用原始 hidden_states)
        shared_expert_output = self.shared_experts_compute(hidden_states)

        # ② Route:计算路由概率与选择掩码
        probs, routing_map = self.route(hidden_states)

        # ③ Preprocess:token dropping / padding 等前处理
        hidden_states, probs, residual = self.preprocess(
            hidden_states, probs, routing_map
        )

        # ④ Dispatch:All-to-All 分发 token 到对应 EP rank
        dispatched_input, probs = self.dispatch(hidden_states, probs)

        # ⑤ Compute:本 rank 上的 local experts 计算
        output, mlp_bias = self.routed_experts_compute(
            dispatched_input, probs, residual
        )

        # ⑥ Combine:All-to-All 收集 + 加权合并 + 叠加 shared expert
        output = self.combine(output, shared_expert_output)
        return output, mlp_bias

    if self.moe_layer_recompute:
        outputs = tensor_parallel.checkpoint(custom_forward, False, hidden_states)
    else:
        outputs = custom_forward(hidden_states)
    return outputs
flowchart TD input["hidden_states [S/TP, B, H]"] input --> shared["SharedExpertMLP\n(可选, 在原始输入上计算)"] input --> router["TopKRouter"] router -->|"probs, routing_map"| prep["preprocess\n(permute tokens)"] prep --> dispatch["All-to-All dispatch\n(跨 EP rank 分发)"] dispatch --> compute["local experts compute\n(GroupedGEMM / Sequential)"] compute --> combine_a2a["All-to-All combine\n(跨 EP rank 收集)"] combine_a2a --> unp["unpermute\n(恢复原始 token 顺序)"] shared --> add(("+")) unp --> add add --> output["output [S/TP, B, H]"] style input fill:#161b22,stroke:#58a6ff,color:#58a6ff,stroke-width:2px style output fill:#161b22,stroke:#58a6ff,color:#58a6ff,stroke-width:2px style shared fill:#161b22,stroke:#d2a8ff,color:#d2a8ff,stroke-width:2px style router fill:#161b22,stroke:#3fb950,color:#3fb950,stroke-width:2px style dispatch fill:#161b22,stroke:#f0883e,color:#f0883e,stroke-width:2px style compute fill:#161b22,stroke:#3fb950,color:#e6edf3 style combine_a2a fill:#161b22,stroke:#f0883e,color:#f0883e,stroke-width:2px style add fill:#0d1117,stroke:#58a6ff,color:#58a6ff
MoELayer 替换 MLP
在 Megatron 的 Layer Spec 机制中,MoE 层通过将 TransformerLayerSubmodules.mlp 的 spec 从普通 MLP 替换为 MoELayer 来启用。MoE 层在 TransformerLayer 中的位置与 MLP 完全相同:位于第二个 LayerNorm 之后,接收 [S/TP, B, H] 的输入,输出相同形状的张量。
moe_layer.py — MoELayer.__init__() 四大组件初始化 L108-179
class MoELayer(BaseMoELayer):
    def __init__(self, config, submodules, layer_number, pg_collection):
        super().__init__(config=config, ...)

        # ---- 1. Router ----
        self.router = TopKRouter(config=self.config, pg_collection=pg_collection)

        # ---- 2. Token Dispatcher ----
        if config.moe_token_dispatcher_type == "alltoall":
            self.token_dispatcher = MoEAlltoAllTokenDispatcher(...)
        elif config.moe_token_dispatcher_type == "allgather":
            self.token_dispatcher = MoEAllGatherTokenDispatcher(...)
        elif config.moe_token_dispatcher_type == "flex":
            self.token_dispatcher = MoEFlexTokenDispatcher(...)

        # ---- 3. Routed Experts ----
        self.experts = build_module(submodules.experts, self.num_local_experts, ...)

        # ---- 4. Shared Experts (可选) ----
        if self.use_shared_expert:
            self.shared_experts = build_module(submodules.shared_experts, ...)
            if self.shared_expert_overlap:
                self.token_dispatcher.set_shared_experts(self.shared_experts)
02

Expert Parallelism

EP Expert TP Expert DP

Expert Parallelism(EP) 是 MoE 模型特有的并行策略:将 expert 分配到不同 GPU 上,每个 GPU 只持有总 expert 数的一个子集。例如 64 个 expert 分到 8 个 EP rank 上,每个 rank 持有 8 个 local expert。

与 TP/PP/DP 不同,EP 不切分单个 expert 的参数,而是将不同的 expert 放到不同 rank。由于每个 token 只被路由到 top-k 个 expert,而这些 expert 可能分布在不同 rank 上,因此需要 All-to-All 通信来跨 rank 交换 token。

EP Rank 0 EP Rank 1 EP Rank 2 EP Rank 3
E0 E1
E2 E3 num_local_experts = 4
E4 E5
E6 E7 num_local_experts = 4
E8 E9
E10 E11 num_local_experts = 4
E12 E13
E14 E15 num_local_experts = 4
EP=4, 16 experts · 每个 rank 持有 16 / 4 = 4 个 local expert · All-to-All 通信在 EP group 内进行

Megatron 的 MoE 并行涉及三个独立的通信组:

通信组配置用途
EP group expert_model_parallel_size expert 分布的维度,All-to-All token 交换发生在此组内
Expert TP group expert_tensor_parallel_size 对单个 expert 的 MLP 做 TP 切分(当 expert FFN 太大时使用),与 Attention 的 TP 组独立
Expert DP group 自动计算 Expert 级别的数据并行,持有相同 expert 副本的 rank 组成此组,用于梯度同步
EP 与 TP 的区别
TP 是将单个大矩阵按列/行切分到多个 rank,每个 rank 持有同一层参数的一部分,通过 AllReduce 聚合。EP 是将不同的 expert 放到不同 rank,每个 rank 持有完整但不同的 expert,通过 All-to-All 交换 token。两者可以组合使用:先用 EP 将 expert 分到不同 rank,再用 Expert TP 对每个 expert 内部做 TP 切分。
moe_layer.py — BaseMoELayer.__init__() EP 初始化 L54-84
class BaseMoELayer(MegatronModule, ABC):
    def __init__(self, config, layer_number, pg_collection):
        super().__init__(config)
        self.ep_group = pg_collection.ep
        ep_size = utils.get_pg_size(self.ep_group)
        ep_rank = utils.get_pg_rank(self.ep_group)

        # 每个 EP rank 持有的 local expert 数量
        assert self.config.num_moe_experts % ep_size == 0
        self.num_local_experts = self.config.num_moe_experts // ep_size

        # 本 rank 负责的 expert 索引范围
        local_expert_indices_offset = ep_rank * self.num_local_experts
        self.local_expert_indices = [
            local_expert_indices_offset + i
            for i in range(self.num_local_experts)
        ]
EP Rank 0 EP Rank 1 EP Rank 2 EP Rank 3
Expert TP=0 GPU 0 GPU 2 GPU 4 GPU 6
Expert TP=1 GPU 1 GPU 3 GPU 5 GPU 7
Experts E0 E1TP=2 切分 E2 E3TP=2 切分 E4 E5TP=2 切分 E6 E7TP=2 切分
EP group: {GPU0, GPU2, GPU4, GPU6} 和 {GPU1, GPU3, GPU5, GPU7}
Expert TP group: {GPU0, GPU1}, {GPU2, GPU3}, {GPU4, GPU5}, {GPU6, GPU7}
03

TopKRouter 路由机制

router.py TopKRouter softmax / sigmoid

TopKRouter 是 Megatron MoE 唯一的 Router 实现,负责将每个 token 路由到 top-k 个 expert。Router 的核心是一个线性门控网络(gating network):一个形状为 [E, H] 的权重矩阵,将 hidden_states [num_tokens, H] 投影到 [num_tokens, E] 的 logits 空间,其中 E 为 expert 总数。

Router 的完整前向流程如下:

① Input Jitter(可选)

为输入添加均匀噪声 input * U(1-eps, 1+eps),增强训练稳定性(参考 ST-MoE)。通过 moe_input_jitter_eps 控制。

② Gating(线性投影)

logits = hidden_states @ W_gate.T,输出形状 [num_tokens, num_experts]。Gate 权重默认使用 FP32 精度(self.weight.fp32 = True)。

③ Z-Loss(可选)

鼓励 logits 保持较小值以增强训练稳定性:\(\text{z\_loss} = \text{coeff} \cdot \text{mean}(\text{logsumexp}(\text{logits})^2)\)。来自 ST-MoE 论文。

④ Top-K 选择 + Score Function

通过 topk_routing_with_score_function() 计算路由。支持两种 score function:softmax(传统方式)和 sigmoid(DeepSeek-V3 风格)。sigmoid 模式下,expert_bias 作为参数传入,在 top-k 选择时加到 logits 上。输出 probs [num_tokens, E]routing_map [num_tokens, E](bool 掩码)。

⑤ _apply_expert_bias(更新 token 计数 buffer)

统计本步各专家收到的 token 数,写入 local_tokens_per_expert buffer,用于下一步更新 expert_bias(不影响当前步路由)。仅在 moe_router_enable_expert_bias=Truetorch.is_grad_enabled() 时执行。

moe_utils.py — topk_routing_with_score_function() sigmoid 分支 L528-630
def topk_routing_with_score_function(
    logits, topk, use_pre_softmax=False, num_groups=None,
    group_topk=None, scaling_factor=None, score_function="softmax",
    expert_bias=None, fused=False,
):
    if score_function == "sigmoid":
        scores = torch.sigmoid(logits.float()).type_as(logits)
        if expert_bias is not None:
            # expert_bias 在 top-k 选择时加到 scores 上
            scores_for_routing = scores + expert_bias
            _, top_indices = compute_topk(scores_for_routing, topk, ...)
            # 但 probs 仍然使用原始 scores(不含 bias)
            scores = torch.gather(scores, dim=1, index=top_indices)
        else:
            scores, top_indices = compute_topk(scores, topk, ...)

        if topk > 1:
            probs = scores / (scores.sum(dim=-1, keepdim=True) + 1e-20)
        else:
            probs = scores

    routing_probs = torch.zeros_like(logits).scatter(1, top_indices, probs)
    routing_map = torch.zeros_like(logits).int().scatter(1, top_indices, 1).bool()
    return routing_probs, routing_map
Softmax vs Sigmoid 路由
Softmax(传统方式):所有 expert 的概率和为 1,形成零和博弈。支持 pre_softmax(先 softmax 再 top-k)和 post_softmax(先 top-k 再 softmax)两种模式。Sigmoid(DeepSeek-V3 风格):每个 expert 独立计算分数,不受其他 expert 约束。当 top-k > 1 时,对选中的 expert 做归一化(scores / sum)。sigmoid 模式支持 expert_bias 来动态调整路由偏好。

Group TopK:当配置了 moe_router_num_groupsmoe_router_group_topk 时,expert 被分成若干组,先在组间选 top-k_group 个组,再在选中的组内选 top-k 个 expert。这是 DeepSeek-V3 使用的分组路由策略。

04

负载均衡策略

Aux Loss Sinkhorn Expert Bias

MoE 训练的核心挑战之一是负载不均衡:如果 router 总是把 token 发给少数几个 expert,其余 expert 的参数就浪费了。Megatron 提供了多种负载均衡策略,通过 moe_router_load_balancing_type 配置。

策略核心思想配置
Aux Loss Switch Transformer 的辅助损失,在 micro-batch 级别鼓励 token 均匀分配。loss = E * Σ f_i * P_i,其中 f_i 是 expert i 的 token 占比,P_i 是平均路由概率。 moe_router_load_balancing_type="aux_loss"
moe_aux_loss_coeff=0.01
Seq Aux Loss 序列级别辅助损失:将 batch 维度 reshape 到 expert 维度,对每个序列独立计算 aux loss 后取平均。粒度更细。 moe_router_load_balancing_type="seq_aux_loss"
Global Aux Loss 全局级别辅助损失:跨所有 DP rank 累积 token 分布(使用滑动平均),在全局维度上鼓励均衡。参考 DeepSeek-V3 论文。 moe_router_load_balancing_type="global_aux_loss"
Sinkhorn 使用 Sinkhorn 迭代(双随机矩阵归一化)使 token-expert 分配矩阵接近均匀,无需额外损失项。 moe_router_load_balancing_type="sinkhorn"
Expert Bias 为每个 expert 维护一个可训练的 bias(DeepSeek-V3 风格),在 top-k 选择时加到 logits 上引导路由。bias 根据 token 分布的不均衡程度动态更新。 moe_router_enable_expert_bias=True
None 不使用任何负载均衡(仅用于测试/基准)。 moe_router_load_balancing_type="none"

多策略组合moe_router_load_balancing_type 可以接受列表,同时启用多种策略。例如 ["aux_loss", "global_aux_loss"] 会在同一个前向中依次应用两种 aux loss。对应的 moe_aux_loss_coeff 也需要是列表。

Aux Loss 的数学公式
Switch Transformer 辅助损失: $$L_{\text{aux}} = E \cdot \text{coeff} \cdot \sum_{i=1}^{E} f_i \cdot P_i$$ 其中 \(f_i = \frac{1}{T \cdot k} \sum_{x \in B} \mathbb{1}[\text{routing\_map}(x, i)]\) 是 expert i 收到的 token 比例,\(P_i = \frac{1}{T} \sum_{x \in B} \text{probs}(x, i)\) 是分配给 expert i 的平均路由概率。当所有 expert 均匀分配时,\(f_i = 1/E\),\(P_i = k/E\),loss 达到最小值 \(k \cdot \text{coeff}\)。
router.py — _apply_aux_loss() 核心逻辑 L283-319
def _apply_aux_loss(self, probs, scores_for_aux_loss, routing_map):
    tokens_per_expert = routing_map.sum(dim=0)  # [num_experts]

    # EP > 1: 跨 tp_ep 组 reduce,获取完整 EP 组的 token 分布
    # EP = 1: 跨 tp_cp 组 reduce
    if self.ep_group.size() > 1:
        reduce_group = self.tp_ep_group     # expert_tp x EP
    else:
        reduce_group = self.tp_cp_group     # TP x CP

    tokens_per_expert = reduce_from_tensor_model_parallel_region(
        tokens_per_expert, reduce_group
    )

    aux_loss = switch_load_balancing_loss_func(
        probs=scores_for_aux_loss,
        tokens_per_expert=tokens_per_expert,
        total_num_tokens=num_tokens * reduce_group.size(),
        topk=self.topk,
        num_experts=self.config.num_moe_experts,
        moe_aux_loss_coeff=aux_loss_coeff,
    )
    # 通过 MoEAuxLossAutoScaler 将 aux loss 附加到 probs 的梯度图上
    probs = MoEAuxLossAutoScaler.apply(probs, aux_loss)
    return probs
MoEAuxLossAutoScaler:巧妙的梯度注入
Aux loss 不能直接加到主 loss 上(因为 MoE 层输出的是 hidden states 而非 loss)。Megatron 使用 MoEAuxLossAutoScaler(一个自定义 autograd.Function)将 aux loss 的梯度注入到 probs 张量上。前向时 probs 不变,反向时 aux loss 的梯度会流经 probs 传播到 router 的 gate 权重上,从而实现对 router 的正则化。
05

Token Dispatcher

token_dispatcher.py AlltoAll AllGather Flex (DeepEP)

Token Dispatcher 负责将 token 从"按 rank 组织"转换为"按 expert 组织",以及反向操作。这是 MoE 层最复杂的部分,因为它需要协调跨设备的通信和本地的 permute/unpermute 操作。

Megatron 提供三种 Token Dispatcher 实现:

Dispatcher通信方式适用场景配置
MoEAlltoAllTokenDispatcher EP 维度 All-to-All + TP 维度 AllGather/ReduceScatter EP > 1 的标准场景,通信量最优 moe_token_dispatcher_type="alltoall"
MoEAllGatherTokenDispatcher TP*EP 维度 AllGather + ReduceScatter EP == 1 或小规模场景,实现简单 moe_token_dispatcher_type="allgather"
MoEFlexTokenDispatcher DeepEP / HybridEP 融合 kernel 高性能场景,permute + All-to-All 融合为一步 moe_token_dispatcher_type="flex"

Token Dispatcher 的接口被拆分为六个阶段,将通信操作(可能在专用 stream 上异步执行)和本地计算操作分离,以实现最大化的通信-计算 overlap:

flowchart TD dp["dispatch_preprocess\n本地: reshape, metadata, permute"] td["token_dispatch\n通信: All-to-All EP 发送 token"] dpost["dispatch_postprocess\n本地: AllGather TP, sort by expert"] ec["Expert Compute\nGroupedGEMM"] cp["combine_preprocess\n本地: unsort, ReduceScatter TP"] tc["token_combine\n通信: All-to-All EP 收集结果"] cpost["combine_postprocess\n本地: unpermute, reshape, 加 shared expert"] dp --> td --> dpost --> ec --> cp --> tc --> cpost style dp fill:#161b22,stroke:#3fb950,color:#3fb950,stroke-width:2px style td fill:#161b22,stroke:#f0883e,color:#f0883e,stroke-width:2px style dpost fill:#161b22,stroke:#3fb950,color:#3fb950,stroke-width:2px style ec fill:#161b22,stroke:#d2a8ff,color:#d2a8ff,stroke-width:2px style cp fill:#161b22,stroke:#3fb950,color:#3fb950,stroke-width:2px style tc fill:#161b22,stroke:#f0883e,color:#f0883e,stroke-width:2px style cpost fill:#161b22,stroke:#3fb950,color:#3fb950,stroke-width:2px

AlltoAll Dispatcher 详解

MoEAlltoAllTokenDispatcher 是最常用的实现。它的核心思路是:

  1. Permute 1:将 token 按照 routing_map 重排,使发往同一 EP rank 的 token 排列在一起(为 All-to-All 准备)
  2. All-to-All (EP):各 EP rank 交换 token。Rank i 发送 input_splits[j] 个 token 给 Rank j,接收 output_splits[j] 个 token
  3. AllGather (TP):如果使用了 Expert TP,需要在 TP 组内 AllGather 以获取完整的 token
  4. Sort by Expert:将接收到的 token 按 local expert 排序(expert 0 的 token 排在前面,expert 1 的排在后面...),生成 tokens_per_expert 告知 GroupedGEMM 每个 expert 处理多少 token
token_dispatcher.py — MoEAlltoAllTokenDispatcher.token_dispatch() L648-674
def token_dispatch(self, permutated_local_input_tokens, permuted_probs):
    """All-to-All 通信:在 EP group 内交换 token"""
    # input_splits: [ep_size] 本 rank 发给各 EP rank 的 token 数
    # output_splits: [ep_size] 本 rank 从各 EP rank 接收的 token 数
    global_input_tokens = all_to_all(
        self.ep_group,
        permutated_local_input_tokens,
        self.output_splits,    # 接收 split
        self.input_splits      # 发送 split
    )
    global_probs = all_to_all(
        self.ep_group,
        permuted_probs,
        self.output_splits,
        self.input_splits
    )
    return global_input_tokens, global_probs
AlltoAll Dispatcher 数据流 — EP=2, 每 rank 4 个 token, 2 个 local expert
EP Rank 0EP Rank 1
初始 tokens + 路由 [A,B,C,D]
A→E0 B→E2 C→E1 D→E0
初始 tokens + 路由 [E,F,G,H]
E→E1 F→E0 G→E3 H→E2
▼ Permute 1 (按目标 EP rank 分组) ▼
EP Rank 0EP Rank 1
→EP0 | →EP1 [A,D | B,C] →EP0 | →EP1 [F | E,G,H]
▼ All-to-All 交换 ▼
EP Rank 0EP Rank 1
收到 (→ E0, E1) [A,D] + [F] = [A,D,F] 收到 (→ E2, E3) [B,C] + [E,G,H] = [B,C,E,G,H]
▼ Sort by Expert ▼
EP Rank 0EP Rank 1
E0=[A,D,F] E1=[] tokens_per_expert=[3, 0] E2=[B,H] E3=[G] tokens_per_expert=[2, 1]
DtoH 同步优化
AlltoAll Dispatcher 的一个关键优化是延迟 GPU-CPU 数据传输。input_splitsoutput_splits 等元信息需要从 GPU 拷贝到 CPU(因为 NCCL All-to-All API 需要 CPU 上的 split sizes),但这个 DtoH 拷贝会触发 GPU-CPU 同步。Dispatcher 通过 cuda_dtoh_pointcuda_sync_point 机制,将 DtoH 操作放到独立的 CUDA stream 上执行,并尽可能延迟同步点,以最大化 GPU 利用率。
06

Expert 计算

experts.py GroupedMLP TEGroupedMLP SequentialMLP

Expert 计算是 MoE 层中实际处理 token 的部分。每个 expert 本质上是一个标准 MLP(fc1 + activation + fc2),但由于单个 rank 上有多个 local expert,如何高效地执行它们是一个关键问题。

Megatron 提供三种 expert 实现:

实现计算方式优势限制
GroupedMLP 使用 grouped_gemm 库,将所有 local expert 的矩阵乘法打包成一个调用 GPU 利用率最高,减少 kernel launch 开销 不支持 bias;需要安装 grouped_gemm 库
TEGroupedMLP 使用 TransformerEngine 的 GroupedLinear 支持 FP8/FP4 训练、融合 kernel、bias 需要 TransformerEngine ≥ 2.x
SequentialMLP 创建 num_local_experts 个独立 MLP 实例,顺序执行 实现简单,兼容性好 GPU 利用率低(多次小 kernel 调用)

三种实现的输入输出接口完全相同:

  • 输入permuted_local_hidden_states [total_tokens, H](所有 local expert 的 token 拼接在一起),tokens_per_expert [num_local_experts](每个 expert 分到的 token 数),permuted_probs [total_tokens](路由权重)
  • 输出fc2_output [total_tokens, H](expert 计算结果,已乘以路由权重)

GroupedGEMM 原理

GroupedMLP 的核心是 Grouped GEMM(Grouped General Matrix Multiplication)。传统方式下,N 个 expert 需要 N 次独立的矩阵乘法调用,每次处理不同数量的 token。Grouped GEMM 将这 N 个矩阵乘法打包成一次 GPU kernel 调用,极大提升了 GPU 的 SM 利用率。

Sequential (SequentialMLP)
Expert 0[t0,t1,t2] @ W0kernel
Expert 1[t3] @ W1kernel
Expert 2[t4,t5] @ W2kernel
Expert 3[t6,t7,t8] @ W3kernel
4 次 kernel launch · GPU 利用率低
Grouped (GroupedMLP)
gmm 单次调用 gmm([t0..t8],
    [W0,W1,W2,W3],
    tpe=[3,1,2,3])
kernel
1 次 kernel launch · GPU 利用率高
CUTLASS grouped GEMM 实现
experts.py — GroupedMLP.forward() 核心逻辑 L234-293
def forward(self, permuted_local_hidden_states, tokens_per_expert, permuted_probs):
    # 将权重 reshape 为 [num_local_experts, H, ffn_hidden]
    w1 = self.weight1.view(self.num_local_experts, self.config.hidden_size, -1)
    w2 = self.weight2.view(self.num_local_experts, -1, self.config.hidden_size)

    # GroupedGEMM: fc1
    # input: [total_tokens, H], w1: [num_experts, H, ffn_hidden*2]
    fc1_output = gg.ops.gmm(
        permuted_local_hidden_states, w1, tokens_per_expert, trans_b=False
    )

    # SwiGLU activation + 路由权重
    intermediate = self.activation_func_with_probs(
        fc1_output, permuted_probs.unsqueeze(-1)
    )

    # GroupedGEMM: fc2
    # input: [total_tokens, ffn_hidden], w2: [num_experts, ffn_hidden, H]
    fc2_output = gg.ops.gmm(
        intermediate, w2, tokens_per_expert, trans_b=False
    )
    return fc2_output, None
权重组织方式
GroupedMLP 将所有 local expert 的权重存储在两个大矩阵中:weight1 [H, num_local_experts * ffn_hidden * 2](fc1,包含 SwiGLU 的 gate 和 value)和 weight2 [num_local_experts * ffn_hidden, H](fc2)。在前向时通过 view 操作将其 reshape 为 [num_local_experts, ...] 的形状供 Grouped GEMM 使用。这种紧凑存储避免了为每个 expert 创建独立 Parameter 的开销。
07

Shared Experts

shared_experts.py SharedExpertMLP DeepSeek-V3

Shared Expert 是一种所有 token 都会经过的"常驻 expert",与 routed expert(只处理被路由到的 token)互补。Shared expert 的输出直接加到 routed expert 的输出上,确保每个 token 至少获得一些"通用知识"的处理。这一设计来自 DeepSeek-MoE / DeepSeek-V3 架构。

SharedExpertMLP 继承自标准 MLP,额外支持:

  • Gate Mechanism:可选的 sigmoid 门控,学习一个标量系数来调节 shared expert 的输出贡献。gate_score = sigmoid(hidden_states @ gate_weight.T),然后 output = output * gate_score
  • 独立的 FFN 大小:通过 moe_shared_expert_intermediate_size 控制,通常比 routed expert 的 FFN 更大。
  • Overlap 模式:可与 Token Dispatcher 的通信操作重叠执行(详见下节)。
shared_experts.py — SharedExpertMLP.forward() L208-220
class SharedExpertMLP(MLP):
    def forward(self, hidden_states):
        # 标准 MLP 前向:fc1 -> activation -> fc2
        if self.activation_recompute:
            output, _ = self._forward_with_activation_recompute(hidden_states)
        else:
            output, _ = super().forward(hidden_states)

        # 可选:sigmoid gate 调节输出
        if self.use_shared_expert_gate:
            logits = F.linear(hidden_states, self.gate_weight)
            gate_score = torch.sigmoid(logits)
            output = output * gate_score

        return output
flowchart TD input["hidden_states"] input --> shared["SharedExpertMLP"] input --> routed["TopKRouter + Dispatch\n+ Routed Experts + Combine"] shared -->|"× gate_score (可选 sigmoid)"| shared_out["shared_output"] routed --> routed_out["routed_output"] shared_out --> add(("+")) routed_out --> add add --> output["final output"] style input fill:#161b22,stroke:#58a6ff,color:#58a6ff,stroke-width:2px style output fill:#161b22,stroke:#58a6ff,color:#58a6ff,stroke-width:2px style shared fill:#161b22,stroke:#d2a8ff,color:#d2a8ff,stroke-width:2px style routed fill:#161b22,stroke:#3fb950,color:#3fb950,stroke-width:2px style add fill:#0d1117,stroke:#58a6ff,color:#58a6ff
Shared Expert 的 TP
Shared expert 使用 Attention 层的 TP 组(pg_collection.tp),而非 Expert TP 组。这是因为 shared expert 处理所有 token,其并行方式与普通 MLP 相同。Routed expert 则使用 Expert TP 组(pg_collection.expt_tp),后者可以与 Attention TP 有不同的大小。
08

通信-计算 Overlap

Overlap SharedExpert + Dispatcher CUDA Stream

MoE 层的主要性能瓶颈是 All-to-All 通信。当 EP 较大时,跨节点的 All-to-All 延迟可能显著影响训练吞吐。Megatron 通过将 Shared Expert 计算与 Token Dispatcher 通信重叠来隐藏通信延迟。

moe_shared_expert_overlap=True 时,SharedExpertMLP 的前向被拆分为多个细粒度函数,穿插在 AlltoAll Dispatcher 的各阶段之间,在独立的 CUDA stream 上执行:

Shared Expert Overlap 时序图 Main Stream (Routed Expert Path) Side Stream (Shared Expert) ─────────────────────────────── ───────────────────────────── dispatch_preprocess() └─> shared_experts.pre_forward_comm() ──> AllGather (SP) + gate_score (在 side stream 启动) | v token_dispatch() [A2A EP] linear_fc1_forward_and_act() └─> 等待 A2A 完成 fc1 -> SwiGLU activation | dispatch_postprocess() v └─> AllGather (TP) (等待 main stream A2A 完成) └─> shared_experts.linear_fc1_forward() | Expert Compute (GroupedGEMM) | v combine_preprocess() | └─> ReduceScatter (TP) | └─> shared_experts.linear_fc2_forward() ──> fc2 计算 | token_combine() [A2A EP] v └─> 等待 A2A 完成 post_forward_comm() ReduceScatter (SP) combine_postprocess() | └─> unpermute v └─> shared_experts.get_output() ──────> 同步 side stream └─> output += shared_expert_output

关键实现细节:

  • Shared expert 的 linear_fc1linear_fc2 的 TP 通信(AllGather/ReduceScatter)被拆出来由 pre_forward_commpost_forward_comm 显式执行。Linear 模块内部的 TP 通信被禁用(linear.parallel_mode = None)。
  • 所有 shared expert 操作都在 SharedExpertMLP.stream(一个独立的 CUDA stream)上执行。最终通过 torch.cuda.current_stream().wait_stream(self.stream) 同步。
  • 使用 _set_sequence_nr() 控制反向传播的调度顺序,确保 shared expert 的反向在正确的时机执行。
Overlap 的收益
Shared expert 的计算量通常与一个 routed expert 相当(甚至更大,因为它处理所有 token)。通过将其与 All-to-All 通信重叠,可以几乎完全隐藏 shared expert 的计算时间,前提是 All-to-All 的延迟足够长(即 EP 足够大或跨节点通信)。在小 EP 或节点内通信的场景下,overlap 的收益较小。
09

配置参数速查表

TransformerConfig 速查

基础配置

参数默认值说明
num_moe_experts-Expert 总数(如 64、128、256)
moe_router_topk-每个 token 路由到的 expert 数量(如 1、2、8)
moe_ffn_hidden_size=ffn_hidden_size每个 routed expert 的 FFN 隐藏层维度
expert_model_parallel_size1Expert Parallelism 大小(EP size),num_moe_experts 必须能被整除
expert_tensor_parallel_size1Expert 内部的 Tensor Parallelism 大小

路由配置

参数默认值说明
moe_router_load_balancing_type"aux_loss"负载均衡策略:aux_loss / seq_aux_loss / global_aux_loss / sinkhorn / none,可用列表组合多种
moe_aux_loss_coeff0.0Aux loss 系数,配合 load_balancing_type 使用
moe_z_loss_coeffNoneZ-loss 系数,鼓励 logits 保持小值
moe_router_score_function"softmax"打分函数:softmaxsigmoid
moe_router_pre_softmaxFalse是否在 top-k 之前做 softmax(仅 softmax 模式)
moe_router_enable_expert_biasFalse启用 expert bias(DeepSeek-V3 风格动态负载均衡)
moe_router_num_groupsNone分组路由的组数(DeepSeek-V3 风格)
moe_router_group_topkNone分组路由中每个 token 选择的组数
moe_router_topk_scaling_factorNone路由概率的缩放因子
moe_input_jitter_epsNone输入 jitter 噪声幅度
moe_router_dtypeNone路由计算的精度:fp32 / fp64

Token Dispatcher 配置

参数默认值说明
moe_token_dispatcher_type"allgather"Dispatcher 类型:alltoall / allgather / flex
moe_expert_capacity_factorNoneExpert 容量因子,设置后启用 token dropping
moe_pad_expert_input_to_capacityFalse是否 pad 输入到固定容量(drop and pad 模式)
moe_token_drop_policy"probs"Token dropping 策略:probs(丢低概率)或 position"(丢后面的)
moe_permute_fusionFalse使用融合 permute kernel
moe_flex_dispatcher_backend-Flex dispatcher 后端:deepep / hybridep

Expert 与 Shared Expert 配置

参数默认值说明
moe_grouped_gemmFalse使用 GroupedMLP(grouped_gemm 库)替代 SequentialMLP
moe_apply_probs_on_inputFalse将路由权重乘到输入(而非输出),仅 topk=1 时可用
moe_shared_expert_intermediate_sizeNoneShared expert 的 FFN 隐藏层维度,设置后启用 shared expert
moe_shared_expert_gateFalse是否为 shared expert 添加 sigmoid gate
moe_shared_expert_overlapFalse将 shared expert 计算与 dispatcher 通信重叠
相关文档