Skip to content

필사 모드: Mixture of Experts (MoE) 架构论文深度解析:从 GShard 到 DeepSeek-MoE

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
Mixture of Experts Architecture

引言

大规模语言模型(LLM)的性能提升,传统上一直与参数量的增加直接挂钩。然而,Dense 模型中参数翻倍,训练和推理的计算量(FLOPs)也会随之成比例增加。Mixture of Experts(MoE) 架构正是突破这一局限的核心策略。它让模型的总参数量大幅增加,但对每个输入 token,只激活全部参数中的少数专家(Expert),从而将计算成本维持在一定水平。

MoE 的最初构想可以追溯到 1991 年 Jacobs 等人的论文,但直到 2017 年 Shazeer 等人在 "Outrageously Large Neural Networks" 中提出 Sparsely-Gated MoE 之后,它才开始具备现代形态。此后,Google 的 GShard(2020)实现了 600B 参数规模的分布式 MoE 训练,Switch Transformer(2021)用单一专家路由把效率推向极致。Mistral AI 的 Mixtral 8x7B(2024)为开源 MoE 模型树立了新标准,DeepSeek 的 DeepSeek-MoE(2024)则通过细粒度专家分割(Fine-grained Expert Segmentation)与共享专家隔离(Shared Expert Isolation),将专家特化推向极致。

本文将分析各篇论文的核心贡献,并结合代码比较路由策略、负载均衡与训练稳定性技巧,同时讨论 MoE 训练中实际会遇到的不稳定性问题与调试策略。

MoE 架构基础:Sparse Gating 与 Expert 选择

门控网络的原理

MoE 层的核心是 门控网络(Gating Network)。给定输入 token x,门控网络会输出针对 N 个专家各自的概率分布。

G(x)=softmax(Wgx+ϵ)G(x) = \text{softmax}(W_g \cdot x + \epsilon)

这里 W_g 是可学习的门控权重矩阵,噪声项则在训练初期帮助专家探索。MoE 层的最终输出,是由所选 Top-k 专家的加权和计算得出的。

y=iTop-kG(x)iEi(x)y = \sum_{i \in \text{Top-k}} G(x)_i \cdot E_i(x)

E_i(x) 是第 i 个专家网络(通常为 FFN)的输出。

import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicMoEGating(nn.Module):
    """Sparsely-Gated MoE 的基础门控网络"""
    def __init__(self, d_model: int, num_experts: int, top_k: int = 2,
                 noise_std: float = 1.0):
        super().__init__()
        self.top_k = top_k
        self.noise_std = noise_std
        self.gate = nn.Linear(d_model, num_experts, bias=False)
        self.noise_gate = nn.Linear(d_model, num_experts, bias=False)

    def forward(self, x: torch.Tensor):
        # x: (batch_size, seq_len, d_model)
        logits = self.gate(x)

        # 训练时加入噪声(促进探索)
        if self.training:
            noise = F.softplus(self.noise_gate(x))
            logits = logits + torch.randn_like(logits) * noise * self.noise_std

        # 选择 Top-k 专家
        top_k_logits, top_k_indices = torch.topk(logits, self.top_k, dim=-1)
        top_k_gates = F.softmax(top_k_logits, dim=-1)

        return top_k_gates, top_k_indices

Sparse Activation 与计算效率

Dense 模型中,所有参数在每个 token 上都会被激活,而 Sparse MoE 中只有 Top-k 专家会被激活。由于 N 个专家中只有 k 个参与运算,模型容量可以扩大 N 倍,而计算量则维持在 k 倍的水平。

举例来说,Mixtral 8x7B 在 8 个专家中激活 2 个。总参数量约为 47B,但激活参数量约为 13B,能以与 Dense 13B 模型相近的推理成本,取得高得多的性能。

Capacity Factor 与 token 丢弃

每个专家能处理的 token 数上限,由 容量因子(Capacity Factor, CF) 来控制。在理想的均匀分配下,每个专家会处理 T/N 个 token,乘以 CF 后得到实际容量。

Expert Capacity=CF×TN\text{Expert Capacity} = \text{CF} \times \frac{T}{N}

CF 为 1.0 时,假设是完全均匀分配。实际中常用 1.25 到 1.5 之间的值。CF 太低,token 会被丢弃(溢出)导致信息损失;CF 太高,则会浪费内存。

class MoELayer(nn.Module):
    """基础 Sparse MoE 层(含 Capacity Factor)"""
    def __init__(self, d_model: int, d_ff: int, num_experts: int,
                 top_k: int = 2, capacity_factor: float = 1.25):
        super().__init__()
        self.num_experts = num_experts
        self.top_k = top_k
        self.capacity_factor = capacity_factor
        self.gate = BasicMoEGating(d_model, num_experts, top_k)

        # 每个专家都是独立的 FFN
        self.experts = nn.ModuleList([
            nn.Sequential(
                nn.Linear(d_model, d_ff),
                nn.GELU(),
                nn.Linear(d_ff, d_model)
            ) for _ in range(num_experts)
        ])

    def forward(self, x: torch.Tensor):
        B, T, D = x.shape
        gates, indices = self.gate(x)
        expert_capacity = int(self.capacity_factor * T / self.num_experts)

        output = torch.zeros_like(x)
        tokens_dropped = 0

        for i in range(self.num_experts):
            # 分配给第 i 个专家的 token 掩码
            expert_mask = (indices == i).any(dim=-1)  # (B, T)
            # 丢弃超出容量的 token
            token_count = expert_mask.sum(dim=-1)
            for b in range(B):
                if token_count[b] > expert_capacity:
                    overflow = token_count[b] - expert_capacity
                    tokens_dropped += overflow.item()

            if not expert_mask.any():
                continue

            expert_input = x[expert_mask]
            expert_output = self.experts[i](expert_input)
            gate_values = gates[indices == i]
            output[expert_mask] += gate_values.unsqueeze(-1) * expert_output

        return output, tokens_dropped

GShard:大规模分布式 MoE 训练

论文概述

GShard(Lepikhin 等,2020)是 Google 发表的论文,利用 MoE 在 2048 个 TPU v3 上,用 4 天时间训练出了 600B 参数规模的多语言机器翻译模型。其核心贡献有三点。

  1. Top-2 门控与随机路由:第一个专家以 Top-1 方式确定性地选出,第二个专家则按门控概率成比例地随机选出
  2. 本地分组分发(Local Group Dispatching):把 token 分成 G 个本地组,并行独立处理,从而降低通信成本
  3. 自动分片(Auto-sharding):借助 XLA 编译器的自动分区,抽象掉分布式训练的复杂性

GShard 的随机路由

GShard 的路由策略颇为独特。Top-1 专家以确定性方式选出门控值最高的专家,而第二个专家则按其余专家的门控概率成比例地随机采样。这种方式让利用率较低的专家也有机会被分配到 token,从而促进专家间的负载均衡与探索(exploration)。

class GShardRouter(nn.Module):
    """GShard Top-2 路由器(含随机路由)"""
    def __init__(self, d_model: int, num_experts: int,
                 capacity_factor: float = 2.0):
        super().__init__()
        self.num_experts = num_experts
        self.capacity_factor = capacity_factor
        self.gate = nn.Linear(d_model, num_experts, bias=False)

    def forward(self, x: torch.Tensor):
        # x: (batch_size, seq_len, d_model)
        B, T, D = x.shape
        logits = self.gate(x)
        probs = F.softmax(logits, dim=-1)

        # Top-1:确定性选择
        top1_probs, top1_indices = probs.max(dim=-1)

        # Top-2:概率性选择(从其余专家中采样)
        mask = torch.zeros_like(probs)
        mask.scatter_(-1, top1_indices.unsqueeze(-1), 1.0)
        remaining_probs = probs * (1 - mask)
        remaining_probs = remaining_probs / (remaining_probs.sum(dim=-1, keepdim=True) + 1e-8)

        # 按概率采样第二个专家
        top2_indices = torch.multinomial(
            remaining_probs.view(-1, self.num_experts), 1
        ).view(B, T)
        top2_probs = probs.gather(-1, top2_indices.unsqueeze(-1)).squeeze(-1)

        # 重新归一化门控值
        total = top1_probs + top2_probs
        top1_gates = top1_probs / total
        top2_gates = top2_probs / total

        return (top1_gates, top1_indices, top2_gates, top2_indices)

本地分组分发

GShard 把整个 batch 的 token 均匀切分为 G 个本地组。每组包含 S = N/G 个 token,各组内独立完成专家分发。这样可以把门控运算加速 O(G) 倍,每个专家的本地容量则设为 C = 2N/(G x E)。

Switch Transformer:单一 Expert 路由的效率

论文概述

Switch Transformer(Fedus 等,2021)是 Google Brain 发表的论文,颠覆了关于 MoE 路由的常识。此前普遍认为必须激活 Top-2 以上的专家训练才会稳定,而 Switch Transformer 证明了仅用 Top-1 单一专家路由 也能取得优异性能。

Top-1 路由的三大优势

  1. 减少路由器运算:只需选择一个专家,门控运算随之简化
  2. 提升 batch 效率:由于每个 token 只分配给一个专家,在相同 Capacity Factor 下可以把 batch 大小翻倍
  3. 降低通信成本:分布式训练时,token 只需发送到一个专家所在设备

Switch Transformer 的路由器实现

class SwitchRouter(nn.Module):
    """Switch Transformer 路由器(Top-1 单一专家)"""
    def __init__(self, d_model: int, num_experts: int,
                 capacity_factor: float = 1.25):
        super().__init__()
        self.num_experts = num_experts
        self.capacity_factor = capacity_factor
        self.gate = nn.Linear(d_model, num_experts, bias=False)

    def forward(self, x: torch.Tensor):
        B, T, D = x.shape
        logits = self.gate(x)
        probs = F.softmax(logits, dim=-1)

        # Top-1 选择(单一专家)
        gate_values, expert_indices = probs.max(dim=-1)  # (B, T)

        # 基于 Capacity 的 token 丢弃
        capacity = int(self.capacity_factor * T / self.num_experts)

        # 追踪各专家分配到的 token 数
        expert_counts = torch.zeros(B, self.num_experts, device=x.device)
        drop_mask = torch.zeros(B, T, dtype=torch.bool, device=x.device)

        for t in range(T):
            for b in range(B):
                eid = expert_indices[b, t].item()
                if expert_counts[b, eid] >= capacity:
                    drop_mask[b, t] = True  # 丢弃超出容量的 token
                else:
                    expert_counts[b, eid] += 1

        # 把被丢弃 token 的门控值设为 0
        gate_values = gate_values * (~drop_mask).float()

        return gate_values, expert_indices, drop_mask

Simplified Load Balancing Loss

Switch Transformer 提出了一种简化的负载均衡辅助损失(auxiliary loss)。

Lbalance=αNi=1NfiPiL_{\text{balance}} = \alpha \cdot N \sum_{i=1}^{N} f_i \cdot P_i

这里 N 是专家数,f_i 是分配给第 i 个专家的 token 比例,P_i 是第 i 个专家的平均路由器概率,alpha 是超参数(论文中推荐为 0.01)。该损失会促使 f_iP_i 都趋近于 1/N,也就是让专家们均匀地获得 token。

def switch_load_balancing_loss(router_probs: torch.Tensor,
                                expert_indices: torch.Tensor,
                                num_experts: int,
                                alpha: float = 0.01) -> torch.Tensor:
    """Switch Transformer 的 Simplified Load Balancing Loss

    Args:
        router_probs: 路由器概率 (B, T, num_experts)
        expert_indices: 所选专家索引 (B, T)
        num_experts: 专家数
        alpha: 均衡系数
    """
    # f_i:分配给各专家的 token 比例
    one_hot = F.one_hot(expert_indices, num_experts).float()
    f = one_hot.mean(dim=[0, 1])  # (num_experts,)

    # P_i:各专家的平均路由器概率
    P = router_probs.mean(dim=[0, 1])  # (num_experts,)

    # 均衡损失:alpha * N * sum(f_i * P_i)
    loss = alpha * num_experts * (f * P).sum()
    return loss

bfloat16 混合精度训练

Switch Transformer 的另一项重要贡献,是用 bfloat16 混合精度训练大规模 Sparse 模型的技巧。由于路由器的 softmax 运算在数值上可能不稳定,因此只让路由器部分保持 float32,其余部分用 bfloat16 处理。这样既节省了内存,又保证了训练稳定性。

Mixtral 8x7B:开源 MoE 的创新

论文概述

Mixtral 8x7B(Jiang 等,2024)是 Mistral AI 发布的 Sparse MoE 语言模型。它基于与 Mistral 7B 相同的架构,但把每层的 FFN(前馈网络)替换成 8 个专家,并采用 Top-2 路由。

架构细节

  • 总参数量:约 46.7B(8 个专家 x 7B FFN + 共享 Attention)
  • 激活参数量:约 12.9B(每个 token 激活 2 个专家)
  • 上下文长度:32K token
  • 注意力机制:Grouped Query Attention(GQA)+ Sliding Window Attention(SWA)

Mixtral 的 Top-2 路由

与 GShard 不同,Mixtral 的 Top-2 路由是确定性的。对每个 token,选出门控值最高的 2 个专家,再用归一化后的门控值对这两个专家的输出做加权和。

y=iTop-2egijTop-2egjEi(x)y = \sum_{i \in \text{Top-2}} \frac{e^{g_i}}{\sum_{j \in \text{Top-2}} e^{g_j}} \cdot E_i(x)

专家特化模式分析

Mixtral 论文中一个有趣的发现是专家特化模式。研究者分析了 The Pile 验证集多个领域中各专家的激活频率,结果显示专家的特化更多依据 句法模式(syntactic pattern),而非主题(topic)。特定专家并不是只专精于代码、数学或自然语言中的某一种,而是根据 token 序列的结构特征被反复选中。

基准测试性能

Mixtral 8x7B 仅凭 13B 激活参数,就在大多数基准测试中达到了与 LLaMA 2 70B 相当甚至更优的性能,在数学与代码生成基准测试中尤其突出,在部分领域甚至达到 GPT-3.5 的水平。

DeepSeek-MoE:Fine-grained Expert Segmentation

论文概述

DeepSeek-MoE(Dai 等,2024)为了在 MoE 架构中把专家特化推向极致,提出了两项核心策略。

  1. Fine-grained Expert Segmentation:把专家细分为更小的单元,使灵活组合成为可能
  2. Shared Expert Isolation:把部分专家隔离为共享专家,集中承担公共知识,减少路由专家之间的重复

Fine-grained Expert Segmentation 的原理

如果说传统 MoE 是从 N 个专家中激活 K 个,那么 DeepSeek-MoE 则在相同参数预算下,把专家数分割为 m 倍(mN 个),并激活 m 倍数量的专家(mK 个)。具体做法是把每个专家 FFN 的中间维度(intermediate hidden dimension)除以 m。

总参数量和计算量保持不变,但由于可以实现更精细的专家组合,知识得以被更精确地分解并学习,每个专家也能达到更高水平的特化。

Shared Expert Isolation

DeepSeek-MoE 把部分专家指定为 共享专家(Shared Expert),使其对所有 token 始终保持激活。共享专家负责学习与上下文无关、普遍需要的知识(通用语言结构、基本语法等),其余路由专家则可以专注于各自的专业领域。这样一来,路由专家之间的知识重复得以减少。

class DeepSeekMoELayer(nn.Module):
    """DeepSeek-MoE 层(Fine-grained Segmentation + Shared Expert)"""
    def __init__(self, d_model: int, d_ff: int, num_routed_experts: int,
                 num_shared_experts: int, top_k: int, segmentation_factor: int = 4):
        super().__init__()
        self.num_routed = num_routed_experts
        self.num_shared = num_shared_experts
        self.top_k = top_k

        # Fine-grained:把 FFN 中间维度除以 segmentation_factor
        fine_d_ff = d_ff // segmentation_factor

        # 共享专家(始终激活)
        self.shared_experts = nn.ModuleList([
            nn.Sequential(
                nn.Linear(d_model, d_ff),
                nn.SiLU(),
                nn.Linear(d_ff, d_model)
            ) for _ in range(num_shared_experts)
        ])

        # 路由专家(细分后的尺寸)
        self.routed_experts = nn.ModuleList([
            nn.Sequential(
                nn.Linear(d_model, fine_d_ff),
                nn.SiLU(),
                nn.Linear(fine_d_ff, d_model)
            ) for _ in range(num_routed_experts)
        ])

        # 门控网络(用于路由专家)
        self.gate = nn.Linear(d_model, num_routed_experts, bias=False)

    def forward(self, x: torch.Tensor):
        B, T, D = x.shape

        # 1. 共享专家输出(应用于所有 token)
        shared_output = sum(expert(x) for expert in self.shared_experts)

        # 2. 选择路由专家并计算输出
        logits = self.gate(x)
        probs = F.softmax(logits, dim=-1)
        top_k_probs, top_k_indices = torch.topk(probs, self.top_k, dim=-1)
        top_k_gates = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)

        routed_output = torch.zeros_like(x)
        for k in range(self.top_k):
            expert_idx = top_k_indices[:, :, k]  # (B, T)
            gate_val = top_k_gates[:, :, k]       # (B, T)
            for i in range(self.num_routed):
                mask = (expert_idx == i)
                if mask.any():
                    inp = x[mask]
                    out = self.routed_experts[i](inp)
                    routed_output[mask] += gate_val[mask].unsqueeze(-1) * out

        return shared_output + routed_output

性能比较

DeepSeek-MoE 2B 用比 GShard 2.9B 少 1.5 倍的专家参数,达到了相当的性能。DeepSeek-MoE 16B 则以约 40% 的计算量,达到了与 LLaMA 2 7B 相当的性能,从实证上验证了 Fine-grained Segmentation 的效果。

MoE 模型比较分析表

Dense vs MoE 比较

特性Dense 模型Sparse MoE 模型
激活参数全部参数仅 Top-k 专家参数
计算量(FLOPs)与参数量成正比与激活参数量成正比
内存占用与参数量大小成正比需要全部参数占用内存(推理时)
训练稳定性相对稳定需要负载均衡,可能不稳定
分布式训练数据/模型并行需额外的 Expert 并行
推理延迟可预测存在路由开销
扩展效率线性增长亚线性计算增长,效率更高

MoE 变体模型比较

特性GShard(2020)Switch Transformer(2021)Mixtral 8x7B(2024)DeepSeek-MoE(2024)
路由方式Top-2(随机 2nd)Top-1Top-2(确定性)Top-K(细分)
专家数2048(分布式)128+8 per layer64 routed + 2 shared
激活专家数2126 routed + 2 shared
总参数量600B1.6T(最大)46.7B16.4B / 145B
激活参数量--12.9B2.8B / 22.2B
专家细分标准标准标准Fine-grained
共享专家
负载均衡Auxiliary loss + CFSimplified loss隐式Auxiliary loss
训练精度float32bfloat16 混合bfloat16bfloat16
主要贡献大规模分布式 MoETop-1 路由效率开源 MoE LLM专家特化极致化

路由策略与负载均衡

路由策略谱系

MoE 路由策略大致可分为三种范式。

1. Token-Choice Routing:由 token 选择专家的方式(GShard、Switch、Mixtral)

最常见的方式,把每个 token 的隐藏状态输入门控网络,选出 Top-k 专家。直观且实现简单,但可能出现负载不均衡。

2. Expert-Choice Routing:由专家选择 token 的方式(Expert Choice,2022)

每个专家自行挑选要处理的 token。由于专家容量固定,能保证完美的负载均衡,但特定 token 可能不被任何专家选中,也可能被多个专家重复选中。

3. Shared + Routed Hybrid:共享专家与路由专家并用的方式(DeepSeek-MoE)

公共知识由共享专家处理,专业知识由路由专家负责。路由专家之间的重复得以减少,专家特化随之提升。

负载均衡的重要性

MoE 训练中最常见的问题之一是 专家崩溃(Expert Collapse)。训练初期,如果某个专家的表现哪怕只是略优于其他专家,路由器就会给它分配更多 token,这又会进一步强化该专家的训练,形成 positive feedback loop。最终会陷入只有少数专家被使用、其余专家得不到训练的状态。

def compute_expert_utilization(expert_indices: torch.Tensor,
                                num_experts: int) -> dict:
    """专家利用率监控工具

    Args:
        expert_indices: 所选专家索引 (B, T) 或 (B, T, K)
        num_experts: 专家总数

    Returns:
        利用率指标字典
    """
    flat = expert_indices.flatten()
    counts = torch.bincount(flat, minlength=num_experts).float()
    total = flat.numel()

    # 各专家的 token 比例
    fractions = counts / total

    # 利用率指标
    max_fraction = fractions.max().item()
    min_fraction = fractions.min().item()
    ideal_fraction = 1.0 / num_experts

    # 不均衡比例(越接近 1 越均匀)
    balance_ratio = min_fraction / (max_fraction + 1e-8)

    # 非激活专家数
    dead_experts = (counts == 0).sum().item()

    return {
        "balance_ratio": balance_ratio,
        "max_load": max_fraction,
        "min_load": min_fraction,
        "ideal_load": ideal_fraction,
        "dead_experts": dead_experts,
        "load_std": fractions.std().item(),
    }

Auxiliary Loss 的效果与局限

Auxiliary loss(辅助损失)是防止专家崩溃最常用的技巧。但 alpha 超参数的设置颇为棘手。

  • alpha 过小时:均衡效果不足,存在专家崩溃风险
  • alpha 过大时:路由质量下降,因为会强行把 token 均匀分配给所有专家,导致专家特化被削弱
  • Switch Transformer 推荐值:alpha = 0.01
  • 实务范围:在 0.001 ~ 0.1 之间摸索

MoE 训练中的注意事项与不稳定性解决

训练不稳定性的原因

相比 Dense 模型,MoE 模型的训练往往更不稳定,主要原因如下。

1. 路由器的离散决策(Discrete Decision)

Top-k 选择本质上是不可微的离散运算。虽然通过 softmax 使其近似可微,但门控值的微小变动仍可能剧烈改变专家选择。这是训练不稳定性的根本原因之一。

2. 专家间的非对称训练

热门专家会获得更多梯度、训练得更快,而冷门专家则因梯度不足而停滞。这种不对称会随着时间推移而加剧。

3. 路由器 softmax 的数值不稳定性

在大规模 logit 值下,softmax 运算在数值上可能不稳定,在 float16 或 bfloat16 精度下这一问题会更加严重。

稳定化技巧

Router z-loss:Switch Transformer 提出的 z-loss,通过抑制路由器 logit 的规模来提升数值稳定性。

Lz=1BTb,t(logi=1Nexb,t,i)2L_z = \frac{1}{B \cdot T} \sum_{b,t} \left( \log \sum_{i=1}^{N} e^{x_{b,t,i}} \right)^2
def router_z_loss(router_logits: torch.Tensor) -> torch.Tensor:
    """Router z-loss:通过抑制路由器 logit 的规模来提升稳定性

    Args:
        router_logits: 路由器的原始 logit (B, T, num_experts)
    """
    # log-sum-exp 的平方
    log_z = torch.logsumexp(router_logits, dim=-1)  # (B, T)
    z_loss = (log_z ** 2).mean()
    return z_loss


class StableMoETrainer:
    """用于稳定 MoE 训练的 Trainer 示例"""
    def __init__(self, model, optimizer, alpha_balance=0.01,
                 alpha_z=0.001):
        self.model = model
        self.optimizer = optimizer
        self.alpha_balance = alpha_balance
        self.alpha_z = alpha_z

    def train_step(self, input_ids, labels):
        self.optimizer.zero_grad()

        # Forward pass
        logits, router_logits, expert_indices = self.model(input_ids)

        # Task loss
        task_loss = F.cross_entropy(
            logits.view(-1, logits.size(-1)), labels.view(-1)
        )

        # Load balancing loss
        router_probs = F.softmax(router_logits, dim=-1)
        balance_loss = switch_load_balancing_loss(
            router_probs, expert_indices, self.model.num_experts
        )

        # Router z-loss
        z_loss = router_z_loss(router_logits)

        # Total loss
        total_loss = task_loss + self.alpha_balance * balance_loss + self.alpha_z * z_loss

        total_loss.backward()

        # 梯度裁剪(在 MoE 训练中尤为重要)
        torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)

        self.optimizer.step()

        return {
            "task_loss": task_loss.item(),
            "balance_loss": balance_loss.item(),
            "z_loss": z_loss.item(),
            "total_loss": total_loss.item(),
        }

混合精度训练策略

Switch Transformer 揭示的一个重要经验是:在 MoE 模型中,路由器必须保持高精度(float32)。路由器的 softmax 运算若在 bfloat16 下进行,数值误差会累积,可能导致训练发散。

推荐策略如下。

  • 路由器门控:保持 float32
  • 专家 FFN:可用 bfloat16
  • Attention 层:可用 bfloat16
  • 损失计算:保持 float32

失败案例与调试

案例 1:专家崩溃(Expert Collapse)

症状:训练过程中,特定 1~2 个专家处理了超过 90% 的 token,其余专家的门控概率趋近于 0。

原因:Auxiliary loss 的 alpha 值过小,或学习率过高,导致路由器无法摆脱初始偏置。

解决方法

  • 尝试把 alpha 值从 0.01 提高到 0.1
  • 路由器权重不用 Xavier 初始化,改用更小的标准差(0.01)初始化
  • 加入 Jitter noise 以强制探索
  • 每一步都监控专家利用率,以便及早发现

案例 2:因 token 丢弃导致的性能下降

症状:在 Capacity Factor 较低的情况下,有超过 10% 的 token 被丢弃,导致 perplexity 无法改善。

原因:CF 过低,或 token 过度集中在特定专家上。

解决方法

  • 把 CF 从 1.0 提高到 1.5(在内存允许范围内)
  • 强化 Auxiliary loss,使 token 分布更均匀
  • 改变 Token dropping 策略:从基于「position」改为基于「probability」,只丢弃低概率 token

案例 3:分布式训练中的 All-to-All 通信瓶颈

症状:增加专家数量后,训练吞吐量(throughput)反而下降。

原因:在 Expert Parallelism 中,All-to-All 通信耗时超过了计算耗时。

解决方法

  • 缩小 Expert Parallelism 组的规模,增加 Data Parallelism
  • 增大每个专家的 token batch 大小,改善计算与通信的比例
  • 应用 GShard 的本地分组分发,限制通信范围

案例 4:推理时的内存暴涨

症状:MoE 模型推理时,所需 GPU 内存比 Dense 模型多出 N 倍以上。

原因:Sparse MoE 无论训练还是推理,都需要把全部专家参数加载进内存。虽然激活参数不多,但全部参数都常驻内存。

解决方法

  • Expert Offloading:把非激活专家移到 CPU/NVMe,按需加载
  • Expert Quantization:把非激活专家量化为 INT8/INT4
  • Expert Pruning:移除利用率低的专家,并重新训练路由器

调试清单

MoE 训练中必须监控的指标如下。

  1. 各专家的 token 分配比例:应保持均匀(理想情况下为 1/N)
  2. 非激活专家数:应为 0
  3. token 丢弃率:建议维持在 5% 以下
  4. 路由器熵:过低会导致专家崩溃,过高则会导致随机路由
  5. Auxiliary loss 趋势:应单调下降
  6. 各专家的梯度范数:专家间偏差过大意味着训练不均衡

结语

MoE 架构已经稳固确立为 LLM 扩展的核心策略。GShard 证明了大规模分布式 MoE 训练的可行性,Switch Transformer 验证了单一专家路由的效率。Mixtral 8x7B 为开源社区提供了实用的 MoE 模型,DeepSeek-MoE 则通过 Fine-grained Expert Segmentation 与 Shared Expert Isolation,把专家特化推向极致。

各篇论文的核心经验总结如下。

  • GShard:借助随机路由与本地分组分发,在数千台设备上训练 MoE 成为可能
  • Switch Transformer:Top-1 路由可以比 Top-2 更简单,同时依然高效;路由器的数值稳定性是关键
  • Mixtral 8x7B:即使只用少量专家(8 个),也能构建出实用的 MoE LLM
  • DeepSeek-MoE:把专家细分并隔离共享专家,可以在相同计算预算下取得更高性能

MoE 研究仍在持续演进。DeepSeek-V2 中出现了与 Multi-head Latent Attention(MLA)结合的 MoE 架构,DeepSeek-V3 则以 671B 参数中仅激活 37B 的结构,开辟了效率的新境界。未来,路由策略的改进、训练稳定性的提升、推理优化等方向预计仍将持续发展。

参考资料

현재 단락 (1/375)

大规模语言模型(LLM)的性能提升,传统上一直与参数量的增加直接挂钩。然而,Dense 模型中参数翻倍,训练和推理的计算量(FLOPs)也会随之成比例增加。**Mixture of Experts(M...

작성 글자: 0원문 글자: 16,455작성 단락: 0/375