一句话总结:Mixture of Experts (MoE) 通过"稀疏激活"实现了一个看似矛盾的目标——用 12B 激活参数达到 70B 效果,让模型拥有海量参数但每次推理只用其中一小部分,这正是 Mixtral 8x7B 和 DeepSeek-V3 背后的核心架构思想。


30.1 MoE 的核心思想

30.1.1 一个反直觉的现象

2023 年 12 月,Mistral AI 发布了 Mixtral 8x7B。这个模型有一个让人困惑的名字——8x7B 到底是多少参数?

答案是:总参数 46.7B,但每次推理只激活 12.9B。

更让人惊讶的是,这个"12B 激活"的模型,在多数基准测试上打平甚至超越了 LLaMA 2 70B!

Mixtral 8x7B vs LLaMA 2 70B

                    Mixtral 8x7B    LLaMA 2 70B
─────────────────────────────────────────────────
总参数量              46.7B          70B
激活参数量            12.9B          70B
推理速度              6x 更快        基准
效果                  ≈ 或更好       基准

这怎么可能?用更少的计算量达到更好的效果?

30.1.2 稀疏激活 vs 密集激活

要理解 MoE,首先要理解两种计算模式的区别:

密集激活(Dense):

传统的 Transformer 是"密集"的——每次前向传播,所有参数都参与计算。

┌─────────────────────────────────────────────────────────────┐
│                    Dense Model (传统模型)                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入 Token ──▶ [ 所有参数都参与计算 ] ──▶ 输出            │
│                                                             │
│  70B 参数 = 70B 激活                                        │
│  每次推理都用全部参数                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

稀疏激活(Sparse):

MoE 是"稀疏"的——每次前向传播,只有一小部分参数参与计算。

┌─────────────────────────────────────────────────────────────┐
│                    Sparse Model (MoE 模型)                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入 Token ──▶ [ 路由器选择 ] ──▶ [ 2个专家计算 ] ──▶ 输出 │
│                     ↓                                        │
│                选择2/8个专家                                 │
│                                                             │
│  46.7B 总参数, 但只激活 12.9B                               │
│  每次推理只用部分参数                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

核心洞察:MoE 的哲学是"不是所有知识都需要同时使用"。处理数学问题时,不需要激活语言翻译的参数;处理代码时,不需要激活写诗的参数。

30.1.3 类比:专家会诊 vs 全科医生

想象你去医院看病:

全科医生模式(Dense):

你找一个"什么都会"的全科医生。他需要掌握所有科室的知识,每次问诊都要调用全部知识来判断你的病情。

  • 优点:一个医生搞定所有问题
  • 缺点:每个领域都只能达到"还行"的水平,不可能在每个领域都是专家

专家会诊模式(MoE):

你先去分诊台(Router),分诊护士根据你的症状,把你分配给最相关的 2 个专科医生(Top-2 Experts)。

  • 优点:每个专家在自己领域都是顶尖
  • 缺点:需要一个好的分诊系统
┌─────────────────────────────────────────────────────────────┐
│                     医院专家会诊系统                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                    ┌──────────┐                             │
│     患者症状  ────▶│  分诊台  │                             │
│                    │ (Router) │                             │
│                    └────┬─────┘                             │
│                         │                                   │
│            ┌────────────┼────────────┐                      │
│            ↓            ↓            ↓                      │
│       ┌────────┐  ┌────────┐  ┌────────┐                   │
│       │ 心内科 │  │ 骨科   │  │ 神经科 │  ... (8个科室)    │
│       │ Expert │  │ Expert │  │ Expert │                   │
│       └────────┘  └────────┘  └────────┘                   │
│            ↓            ↓                                   │
│            └─────┬──────┘                                   │
│                  ↓                                          │
│           最终诊断结果                                       │
│         (选择的2个专家结果加权)                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

这个类比很好地解释了 MoE 的核心:

  • 分诊台 = Router(路由器):决定把输入发给哪些专家
  • 各科室专家 = Experts:每个专家负责特定类型的输入
  • Top-K 选择:每次只咨询 K 个最相关的专家(通常 K=2)

30.1.4 MoE 的历史

MoE 不是新概念——它在 1991 年就被提出了!但真正在大模型中大规模应用是近几年的事:

MoE 发展时间线:

1991  Jacobs et al. 提出 MoE 概念
      │
2017  Shazeer et al. 首次将 MoE 用于 NLP
      │
2021  Google Switch Transformer (1.6T 参数)
      │
2022  Google GLaM (1.2T 参数)
      │
2023  Mistral Mixtral 8x7B (开源 MoE 标杆)
      │
2024  DeepSeek-V3 (671B 参数, 37B 激活)

为什么最近 MoE 突然火了?因为推理效率越来越重要。当模型越来越大,推理成本成为主要瓶颈,而 MoE 提供了一条"鱼与熊掌兼得"的路——大容量,但低推理成本。


30.2 MoE 架构详解

30.2.1 MoE 层的位置

在标准 Transformer 中,每个 Block 包含两部分:

  1. Self-Attention(注意力层)
  2. FFN(前馈网络)

MoE 的改动很简单:把 FFN 替换成 MoE 层。

┌─────────────────────────────────────────────────────────────┐
│            标准 Transformer Block vs MoE Block               │
├───────────────────────────┬─────────────────────────────────┤
│     Standard Block        │         MoE Block               │
├───────────────────────────┼─────────────────────────────────┤
│                           │                                 │
│  Input                    │  Input                          │
│    ↓                      │    ↓                            │
│  ┌─────────────────┐      │  ┌─────────────────┐            │
│  │ Self-Attention  │      │  │ Self-Attention  │            │
│  └────────┬────────┘      │  └────────┬────────┘            │
│           ↓               │           ↓                     │
│  ┌─────────────────┐      │  ┌─────────────────┐            │
│  │      FFN        │      │  │   MoE Layer     │ ← 这里变了!│
│  │  (Dense)        │      │  │  (Sparse)       │            │
│  └────────┬────────┘      │  └────────┬────────┘            │
│           ↓               │           ↓                     │
│  Output                   │  Output                         │
│                           │                                 │
└───────────────────────────┴─────────────────────────────────┘

30.2.2 MoE 层的内部结构

MoE 层包含两个关键组件:

  1. Router(路由器):决定每个 token 应该由哪些专家处理
  2. Experts(专家网络):N 个并行的 FFN,每个都有独立的参数
┌─────────────────────────────────────────────────────────────┐
│                      MoE Layer 内部结构                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Input: x (hidden_size)                                     │
│           │                                                 │
│           ↓                                                 │
│    ┌──────────────┐                                         │
│    │   Router     │  Linear(hidden_size → num_experts)     │
│    │   + Softmax  │  输出每个专家的权重                     │
│    └──────┬───────┘                                         │
│           │                                                 │
│           ↓                                                 │
│    ┌──────────────┐                                         │
│    │  Top-K Gate  │  选择权重最高的 K 个专家                │
│    └──────┬───────┘                                         │
│           │                                                 │
│     weights, indices                                        │
│           │                                                 │
│           ↓                                                 │
│    ┌─────────────────────────────────────────────────┐      │
│    │                   N 个 Expert                    │      │
│    │  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ... ┌─────┐   │      │
│    │  │ E_0 │ │ E_1 │ │ E_2 │ │ E_3 │     │ E_7 │   │      │
│    │  │(FFN)│ │(FFN)│ │(FFN)│ │(FFN)│     │(FFN)│   │      │
│    │  └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘     └──┬──┘   │      │
│    │     │       │       │       │           │       │      │
│    └─────┼───────┼───────┼───────┼───────────┼───────┘      │
│          │       │       │       │           │              │
│          ↓       ↓       ↓       ↓           ↓              │
│    ┌─────────────────────────────────────────────────┐      │
│    │        Weighted Sum (加权求和)                   │      │
│    │        只对被选中的 K 个专家求和                 │      │
│    └────────────────────────┬────────────────────────┘      │
│                             │                               │
│                             ↓                               │
│  Output: y (hidden_size)                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

30.2.3 Router(路由器)的作用

Router 是 MoE 的"大脑",负责为每个 token 选择最合适的专家。

Router 的计算过程:

# 输入: x, shape = (batch_size, seq_len, hidden_size)
# 输出: 每个 token 应该去哪些专家,以及对应的权重

# Step 1: 线性变换,得到每个专家的"分数"
router_logits = Linear(hidden_size, num_experts)(x)
# router_logits shape: (batch_size, seq_len, num_experts)

# Step 2: Softmax 得到概率分布
router_probs = softmax(router_logits, dim=-1)
# router_probs shape: (batch_size, seq_len, num_experts)
# 例如: [0.4, 0.3, 0.1, 0.05, 0.05, 0.03, 0.04, 0.03]
#       表示这个 token "更适合" Expert 0 和 Expert 1

# Step 3: Top-K 选择
top_k_probs, top_k_indices = topk(router_probs, k=2)
# top_k_probs: [0.4, 0.3]
# top_k_indices: [0, 1]  # 选择 Expert 0 和 Expert 1

# Step 4: 归一化权重
weights = top_k_probs / top_k_probs.sum()
# weights: [0.57, 0.43]

直觉理解:

Router 学会了"理解"每个 token 的特点,并把它路由到最擅长处理这类 token 的专家。

例如(概念性的,非真实数据):

Token: "def" (Python 关键字)
  → Router 分数: Expert_代码:0.8, Expert_数学:0.1, Expert_语言:0.1
  → 选择: Expert_代码 + Expert_数学

Token: "微积分" (数学术语)
  → Router 分数: Expert_代码:0.1, Expert_数学:0.7, Expert_语言:0.2
  → 选择: Expert_数学 + Expert_语言

Token: "曾经" (中文语言)
  → Router 分数: Expert_代码:0.05, Expert_数学:0.05, Expert_语言:0.9
  → 选择: Expert_语言 + (某个其他专家)

30.2.4 Expert(专家)网络

每个 Expert 就是一个标准的 FFN:

class Expert(nn.Module):
    def __init__(self, hidden_size, intermediate_size):
        super().__init__()
        # 标准 FFN 结构:上投影 → 激活 → 下投影
        self.w1 = nn.Linear(hidden_size, intermediate_size)  # 上投影
        self.w2 = nn.Linear(intermediate_size, hidden_size)  # 下投影
        self.act = nn.SiLU()  # 激活函数

    def forward(self, x):
        return self.w2(self.act(self.w1(x)))

关键点:每个 Expert 的参数是完全独立的。这就是 MoE 参数量大的原因——8 个专家就是 8 倍的 FFN 参数。

30.2.5 Top-K 选择机制

为什么选 Top-2 而不是 Top-1 或 Top-8?

Top-1(只选一个专家):

优点:计算最少
缺点:
  - 梯度只能流向一个专家,训练不稳定
  - 如果路由错误,结果会很差
  - 专家之间无法协作

Top-2(选两个专家):

优点:
  - 两个专家可以"互补"
  - 梯度可以流向两个专家,训练更稳定
  - 容错性更好
缺点:
  - 计算量是 Top-1 的两倍

Top-K(K > 2):

优点:更多专家协作,可能效果更好
缺点:
  - 稀疏性降低,计算量增加
  - 当 K = N 时,就变成了 Dense 模型

实践经验:Top-2 是最常用的选择,在效果和效率之间取得了很好的平衡。

30.2.6 负载均衡问题

MoE 有一个严重的问题:专家负载不均衡。

如果 Router 学到了"把所有 token 都发给 Expert 0",会发生什么?

不均衡的情况:

Expert 0: 处理 90% 的 token  ← 过载,成为瓶颈
Expert 1: 处理 5% 的 token
Expert 2: 处理 3% 的 token
...
Expert 7: 处理 0.1% 的 token  ← 闲置,参数浪费

这会导致两个问题:

  1. 效率问题:一个专家成为瓶颈,其他专家闲置
  2. 容量问题:大部分参数没有被充分利用

解决方案:Auxiliary Load Balancing Loss

在训练时,加入一个辅助损失函数,鼓励 Router 均匀分配 token:

# 辅助负载均衡损失
def load_balancing_loss(router_probs, expert_indices):
    # router_probs: 每个 token 对每个专家的概率
    # expert_indices: 实际选择的专家

    # 计算每个专家被选中的频率
    expert_mask = F.one_hot(expert_indices, num_experts)
    expert_fraction = expert_mask.mean(dim=0)  # 每个专家处理的 token 比例

    # 计算每个专家的平均概率
    router_fraction = router_probs.mean(dim=0)  # 每个专家的平均路由概率

    # 负载均衡损失 = 频率 * 概率 的总和
    # 如果均匀分布,这个值最小
    aux_loss = num_experts * (expert_fraction * router_fraction).sum()

    return aux_loss

直觉理解:

这个损失函数惩罚"热门专家"。如果某个专家被选中的频率很高(expert_fraction 大),同时路由概率也很高(router_fraction 大),就会受到更大的惩罚。这迫使 Router 学会更均匀地分配 token。


30.3 Mixtral 8x7B 架构

30.3.1 Mistral AI 的实现

2023 年 12 月,Mistral AI 发布了 Mixtral 8x7B(技术报告于 2024 年 1 月公开),成为开源 MoE 模型的标杆。

基本参数:

配置项值说明
总参数量46.7B所有参数的总和
激活参数量12.9B每次推理实际使用的参数
专家数量8每层有 8 个专家
激活专家数2每个 token 激活 2 个专家
隐藏维度4096与 LLaMA 2 相同
层数3232 个 Transformer Block
注意力头数32使用 GQA,8 个 KV 头
上下文长度32K支持长上下文

30.3.2 架构图

┌─────────────────────────────────────────────────────────────┐
│                  Mixtral 8x7B 架构                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Input Tokens                                               │
│       │                                                     │
│       ↓                                                     │
│  ┌─────────────────────────────┐                            │
│  │    Token Embedding          │                            │
│  │    vocab_size × 4096        │                            │
│  └────────────┬────────────────┘                            │
│               │                                             │
│               ↓                                             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                 × 32 Layers                          │    │
│  │  ┌───────────────────────────────────────────────┐  │    │
│  │  │  RMSNorm                                       │  │    │
│  │  │     ↓                                          │  │    │
│  │  │  GQA Self-Attention (32 Q heads, 8 KV heads)  │  │    │
│  │  │     ↓                                          │  │    │
│  │  │  Residual Connection                           │  │    │
│  │  │     ↓                                          │  │    │
│  │  │  RMSNorm                                       │  │    │
│  │  │     ↓                                          │  │    │
│  │  │  ┌─────────────────────────────────────────┐  │  │    │
│  │  │  │           MoE Layer                      │  │  │    │
│  │  │  │                                          │  │  │    │
│  │  │  │  Router → Top-2 Selection                │  │  │    │
│  │  │  │     ↓                                    │  │  │    │
│  │  │  │  ┌────┬────┬────┬────┬────┬────┬────┬────┐│  │  │    │
│  │  │  │  │ E0 │ E1 │ E2 │ E3 │ E4 │ E5 │ E6 │ E7 ││  │  │    │
│  │  │  │  └────┴────┴────┴────┴────┴────┴────┴────┘│  │  │    │
│  │  │  │     ↓                                    │  │  │    │
│  │  │  │  Weighted Sum (只用选中的 2 个)          │  │  │    │
│  │  │  └─────────────────────────────────────────┘  │  │    │
│  │  │     ↓                                          │  │    │
│  │  │  Residual Connection                           │  │    │
│  │  └───────────────────────────────────────────────┘  │    │
│  └─────────────────────────────────────────────────────┘    │
│               │                                             │
│               ↓                                             │
│  ┌─────────────────────────────┐                            │
│  │    RMSNorm + LM Head        │                            │
│  │    4096 × vocab_size        │                            │
│  └────────────┬────────────────┘                            │
│               │                                             │
│               ↓                                             │
│  Output Logits                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

30.3.3 参数量计算

让我们算一算为什么是 46.7B:

参数分布:

1. Embedding 层:
   vocab_size × hidden_size = 32000 × 4096 ≈ 131M

2. 每层参数:
   - Self-Attention:
     Q: 4096 × 4096 = 16.8M
     K: 4096 × 1024 = 4.2M  (GQA, 8 个 KV 头)
     V: 4096 × 1024 = 4.2M
     O: 4096 × 4096 = 16.8M
     小计: ≈ 42M

   - MoE 层 (8 个专家):
     每个专家: 4096 × 14336 × 2 = 117M (上下投影)
     门控: 4096 × 14336 = 59M (SwiGLU 的额外门控)
     8 个专家: (117M + 59M) × 8 ≈ 1.4B
     Router: 4096 × 8 ≈ 33K (可忽略)

   - RMSNorm: 4096 × 2 ≈ 8K (可忽略)

   每层总计: 42M + 1.4B ≈ 1.45B

3. 32 层总计:1.45B × 32 ≈ 46.4B

4. 加上 Embedding 和 LM Head:≈ 46.7B

激活参数量计算:

每次推理只激活 2/8 = 25% 的专家参数

非 MoE 部分: 42M × 32 ≈ 1.3B
MoE 部分 (只算 2 个专家): 1.4B × 0.25 × 32 ≈ 11.2B
加上 Embedding: ≈ 12.9B

所以:46.7B 总参数,12.9B 激活参数

30.3.4 与 LLaMA 2 70B 对比

对比项Mixtral 8x7BLLaMA 2 70B
总参数量46.7B70B
激活参数量12.9B70B
推理 FLOPS~13B 等效70B
推理速度约 6x 更快基准
显存占用~90GB (FP16)~140GB (FP16)
MMLU 分数70.6%68.9%
代码能力强 (60.7% HumanEval)较强
多语言强一般

核心发现:Mixtral 用 1/5 的计算量达到了相当甚至更好的效果。这证明了 MoE 架构的效率优势。

30.3.5 Mixtral 的 Router 行为

Mistral AI 的论文中分析了 Router 的行为,发现了一些有趣的现象:

1. 专家确实学到了不同的"专业"

虽然没有显式地告诉专家"你负责代码"或"你负责数学",但专家们自然地分化了:

Token 类型         最常被选中的专家
─────────────────────────────────
Python 代码        Expert 3, Expert 7
数学公式           Expert 1, Expert 5
中文文本           Expert 2, Expert 6
英文文本           Expert 0, Expert 4

2. 语法结构影响路由

同一个词在不同位置可能被路由到不同专家:

"The" 在句首 → Expert 0
"the" 在句中 → Expert 4

3. 相邻 token 倾向于选择不同专家

这可能是模型学到的一种"分工协作"机制。


30.4 DeepSeek-V3 架构

30.4.1 DeepSeek 的野心

2024 年底,中国 AI 公司 DeepSeek 发布了 DeepSeek-V3,在多个基准测试上达到了 GPT-4 级别的性能,但训练成本只有 5.5 百万美元!

对比一下:GPT-4 的训练成本估计在 1 亿美元以上。

DeepSeek-V3 是如何做到的?核心就是两个创新:MLA(Multi-head Latent Attention) 和 Fine-grained MoE。

30.4.2 基本配置

配置项DeepSeek-V3说明
总参数量671B非常大
激活参数量37B每次推理使用
专家数量256 + 1256 个路由专家 + 1 个共享专家
激活专家数8每个 token 激活 8 个专家
层数61比较深
隐藏维度7168较大
上下文长度128K超长上下文

30.4.3 Multi-head Latent Attention (MLA)

MLA 是 DeepSeek 的一个重要创新,用于解决 KV Cache 的内存问题。

传统 MHA 的问题:

传统 MHA:
  K cache size = batch × seq_len × num_heads × head_dim
  V cache size = batch × seq_len × num_heads × head_dim

对于 128K 上下文,这个 cache 非常大!

MLA 的解决方案:

MLA 通过低秩分解压缩 KV:

┌─────────────────────────────────────────────────────────────┐
│              MLA vs MHA 对比                                 │
├──────────────────────────────┬──────────────────────────────┤
│          MHA                 │          MLA                  │
├──────────────────────────────┼──────────────────────────────┤
│                              │                              │
│  x ──▶ W_K ──▶ K             │  x ──▶ W_DKV ──▶ c_KV        │
│        (d × d_k × h)         │        (d × d_c)    │        │
│                              │                    ↓        │
│  x ──▶ W_V ──▶ V             │              ┌────────┐      │
│        (d × d_v × h)         │              │ c_KV   │      │
│                              │              │ (压缩) │      │
│  KV Cache: O(seq × d × h)    │              └───┬────┘      │
│                              │                  ↓           │
│                              │  c_KV ──▶ W_UK ──▶ K        │
│                              │  c_KV ──▶ W_UV ──▶ V        │
│                              │                              │
│                              │  KV Cache: O(seq × d_c)     │
│                              │  d_c << d × h               │
│                              │                              │
└──────────────────────────────┴──────────────────────────────┘

直觉理解:

MLA 先把 K 和 V 压缩到一个低维的"潜在空间"(latent space),缓存这个压缩版本,需要时再解压。这大大减少了 KV Cache 的大小。

内存节省:
  传统 MHA: 每层 cache 大小 ∝ num_heads × head_dim
  MLA: 每层 cache 大小 ∝ latent_dim

  如果 latent_dim = 0.25 × (num_heads × head_dim)
  → KV Cache 减少 75%!

30.4.4 Fine-grained MoE

DeepSeek-V3 的另一个创新是细粒度 MoE。

传统 MoE 的问题:

每个专家都是完整的 FFN,参数量很大。如果想要更多专家(比如 256 个),参数量会爆炸。

Fine-grained MoE 的解决方案:

把每个 FFN 拆成更小的"专家碎片":

┌─────────────────────────────────────────────────────────────┐
│          传统 MoE vs Fine-grained MoE                        │
├──────────────────────────────┬──────────────────────────────┤
│     传统 MoE (8 个大专家)    │   Fine-grained (256个小专家)  │
├──────────────────────────────┼──────────────────────────────┤
│                              │                              │
│  ┌────────────────────────┐  │  ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─...─┐  │
│  │    Expert 0 (大)       │  │  │0│1│2│3│4│5│6│7│8│9...255│ │
│  │    完整 FFN            │  │  │ │ │ │ │ │ │ │ │ │ ... │  │
│  │    d → 4d → d          │  │  │ │ │ │ │ │ │ │ │ │ ... │  │
│  └────────────────────────┘  │  └─┴─┴─┴─┴─┴─┴─┴─┴─┴─...─┘  │
│  ┌────────────────────────┐  │                              │
│  │    Expert 1 (大)       │  │  每个小专家:                  │
│  └────────────────────────┘  │  d → 4d/32 → d               │
│  ...                         │                              │
│  ┌────────────────────────┐  │  激活 8 个小专家:            │
│  │    Expert 7 (大)       │  │  d → 8×(4d/32) → d           │
│  └────────────────────────┘  │  = d → d → d                 │
│                              │                              │
│  Top-2 选择                  │  Top-8 选择                   │
│  激活 2/8 = 25%             │  激活 8/256 ≈ 3%              │
│                              │                              │
└──────────────────────────────┴──────────────────────────────┘

优势:

  1. 更细粒度的路由:256 个小专家比 8 个大专家能做出更精确的决策
  2. 更稀疏的激活:8/256 ≈ 3% 激活率,远低于 2/8 = 25%
  3. 更好的负载均衡:更多专家意味着更容易均衡

30.4.5 共享专家

DeepSeek-V3 还有一个创新:共享专家(Shared Expert)。

┌─────────────────────────────────────────────────────────────┐
│              DeepSeek-V3 MoE 结构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Input x                                                    │
│     │                                                       │
│     ├─────────────────────────────────────────┐             │
│     │                                         │             │
│     ↓                                         ↓             │
│  ┌────────────┐                         ┌──────────────┐    │
│  │ Router     │                         │ Shared Expert│    │
│  │ Top-8      │                         │ (所有token   │    │
│  └─────┬──────┘                         │  都通过)     │    │
│        │                                └──────┬───────┘    │
│        ↓                                       │            │
│  ┌─────────────────────────────────────┐      │            │
│  │    256 个路由专家                    │      │            │
│  │    选中 8 个                         │      │            │
│  └────────────────┬────────────────────┘      │            │
│                   │                           │            │
│                   ↓                           ↓            │
│              ┌─────────────────────────────────┐           │
│              │        相加                      │           │
│              │  routed_output + shared_output  │           │
│              └─────────────────┬───────────────┘           │
│                               │                            │
│                               ↓                            │
│                          Output y                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

共享专家的作用:

  1. 捕获通用知识:有些模式是所有 token 都需要的,共享专家负责这部分
  2. 提高稳定性:即使路由决策不完美,共享专家也能提供基础能力
  3. 减少冗余:路由专家可以专注于差异化的知识

30.4.6 训练成本分析

DeepSeek-V3 的训练成本只有 5.5M,而GPT−4估计超过5.5M,而 GPT-4 估计超过100M。为什么?

成本分解:

1. 硬件效率:
   - 使用 NVIDIA H800 GPU(2048 卡集群)
   - 优化了通信和计算的 overlap
   - FP8 混合精度训练

2. 架构效率:
   - MLA 减少了 KV Cache,可以用更大的 batch
   - Fine-grained MoE 提高了参数利用率
   - 更稀疏的激活减少了计算量

3. 数据效率:
   - 高质量预训练数据
   - 多阶段课程学习

4. 工程优化:
   - 专家并行 + 数据并行 + 流水线并行
   - 高效的 all-to-all 通信

30.5 MoE 的挑战

30.5.1 训练不稳定性

MoE 训练比 Dense 模型更容易出问题:

问题 1:Router 崩溃

症状:所有 token 都被路由到同一个专家
原因:Router 收敛到局部最优
后果:其他专家参数没有被训练,模型退化成单专家

解决方案:
- 加入噪声:router_logits += noise
- 负载均衡损失:惩罚不均匀分布
- 专家 dropout:训练时随机丢弃部分专家

问题 2:训练 Loss 震荡

症状:Loss 曲线不平滑,有剧烈波动
原因:不同 batch 的专家激活模式差异大
后果:模型难以收敛

解决方案:
- 增大 batch size
- 使用梯度累积
- 降低学习率

30.5.2 负载不均衡

即使使用了负载均衡损失,专家负载仍然可能不均衡:

┌─────────────────────────────────────────────────────────────┐
│                   专家负载分布示例                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  理想情况 (均匀分布):                                        │
│  E0: ████████ 12.5%                                         │
│  E1: ████████ 12.5%                                         │
│  E2: ████████ 12.5%                                         │
│  E3: ████████ 12.5%                                         │
│  E4: ████████ 12.5%                                         │
│  E5: ████████ 12.5%                                         │
│  E6: ████████ 12.5%                                         │
│  E7: ████████ 12.5%                                         │
│                                                             │
│  实际情况 (不均衡):                                          │
│  E0: ████████████████████ 25%  ← 热门专家                    │
│  E1: ██████████████ 18%                                     │
│  E2: ██████████ 13%                                         │
│  E3: ████████ 12%                                           │
│  E4: ██████ 10%                                             │
│  E5: ██████ 10%                                             │
│  E6: ████ 7%                                                │
│  E7: ████ 5%  ← 冷门专家                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

后果:

  1. 计算资源浪费:热门专家成为瓶颈,冷门专家闲置
  2. 模型容量未充分利用:部分参数没有被有效训练

更激进的解决方案:

  1. Capacity Factor:限制每个专家最多处理多少 token
# 如果专家超载,多余的 token 会被丢弃或溢出到其他专家
capacity = (total_tokens / num_experts) * capacity_factor
# capacity_factor 通常是 1.0 到 1.5
  1. 专家并行时的负载均衡:在分布式训练中,确保每个 GPU 上的专家负载相近

30.5.3 通信开销

MoE 在分布式训练和推理时有独特的通信模式:

问题:All-to-All 通信

┌─────────────────────────────────────────────────────────────┐
│              MoE 的 All-to-All 通信                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  假设 4 个 GPU,每个 GPU 有 2 个专家:                       │
│                                                             │
│  GPU 0: Expert 0, 1    GPU 1: Expert 2, 3                   │
│  GPU 2: Expert 4, 5    GPU 3: Expert 6, 7                   │
│                                                             │
│  一个 batch 的 token 需要路由到不同专家:                    │
│                                                             │
│  Token 1 → Expert 0 (在 GPU 0)    ← 不需要通信              │
│  Token 2 → Expert 3 (在 GPU 1)    ← 需要从 GPU 0 发到 GPU 1 │
│  Token 3 → Expert 5 (在 GPU 2)    ← 需要从 GPU 0 发到 GPU 2 │
│  Token 4 → Expert 7 (在 GPU 3)    ← 需要从 GPU 0 发到 GPU 3 │
│                                                             │
│  这种"所有 GPU 都要和所有 GPU 通信"的模式叫 All-to-All      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

通信开销分析:

传统 Dense 模型(数据并行):
  通信:AllReduce 梯度
  复杂度:O(参数量)

MoE 模型(专家并行):
  通信:All-to-All(发送 token 到专家,接收结果)
  复杂度:O(batch_size × hidden_size)
  每层都要通信两次(发送和接收)

优化方案:

  1. 计算-通信重叠:在等待通信时进行其他计算
  2. 批量通信:积累多个 token 一起发送
  3. 混合并行:结合数据并行和专家并行,减少跨机器通信

30.5.4 推理效率挑战

虽然 MoE 理论上推理更快,但实际部署有挑战:

问题 1:动态路由不利于 batching

Dense 模型:
  所有 token 走相同的路径
  可以高效 batch 处理

MoE 模型:
  不同 token 可能走不同专家
  需要动态调度,batch 效率降低

问题 2:显存占用

虽然激活参数少,但所有专家参数都要加载到显存
46.7B 参数 → 需要 ~90GB 显存 (FP16)
而不是 12.9B 激活参数对应的 ~25GB

问题 3:Token 数量波动

如果一个 batch 的 token 大多被路由到同一个专家:
  - 该专家需要处理大量 token,成为瓶颈
  - 其他专家闲置
  - 整体延迟由最慢的专家决定

30.6 代码示例

30.6.1 简化的 MoE 层实现

# 代码示例:简化的 MoE 层

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

class MoELayer(nn.Module):
    def __init__(
        self,
        hidden_size: int = 4096,
        intermediate_size: int = 14336,
        num_experts: int = 8,
        top_k: int = 2,
        aux_loss_coef: float = 0.01
    ):
        super().__init__()
        self.hidden_size = hidden_size
        self.num_experts = num_experts
        self.top_k = top_k
        self.aux_loss_coef = aux_loss_coef

        # Router: 一个简单的线性层
        self.router = nn.Linear(hidden_size, num_experts, bias=False)

        # 专家网络: N 个独立的 FFN
        self.experts = nn.ModuleList([
            Expert(hidden_size, intermediate_size)
            for _ in range(num_experts)
        ])

    def forward(self, x):
        """
        x: (batch_size, seq_len, hidden_size)
        """
        batch_size, seq_len, hidden_size = x.shape

        # 1. 计算路由概率
        # (batch, seq, hidden) → (batch, seq, num_experts)
        router_logits = self.router(x)
        router_probs = F.softmax(router_logits, dim=-1)

        # 2. Top-K 选择
        # (batch, seq, top_k)
        top_k_probs, top_k_indices = torch.topk(
            router_probs, self.top_k, dim=-1
        )

        # 3. 归一化 Top-K 权重
        top_k_weights = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)

        # 4. 计算专家输出
        # 简化实现:逐个专家计算(生产环境应该用并行)
        x_flat = x.view(-1, hidden_size)  # (batch*seq, hidden)
        output = torch.zeros_like(x_flat)

        for expert_idx in range(self.num_experts):
            # 找到路由到这个专家的 token
            # 这是简化实现,实际应该用更高效的方式
            expert_mask = (top_k_indices == expert_idx).any(dim=-1)
            expert_mask_flat = expert_mask.view(-1)

            if expert_mask_flat.any():
                expert_input = x_flat[expert_mask_flat]
                expert_output = self.experts[expert_idx](expert_input)

                # 获取权重
                weights_for_expert = torch.where(
                    top_k_indices == expert_idx,
                    top_k_weights,
                    torch.zeros_like(top_k_weights)
                ).sum(dim=-1).view(-1)[expert_mask_flat]

                output[expert_mask_flat] += (
                    expert_output * weights_for_expert.unsqueeze(-1)
                )

        output = output.view(batch_size, seq_len, hidden_size)

        # 5. 计算辅助损失(用于负载均衡)
        aux_loss = self._compute_aux_loss(router_probs, top_k_indices)

        return output, aux_loss

    def _compute_aux_loss(self, router_probs, expert_indices):
        """计算负载均衡辅助损失"""
        # 每个专家被选中的频率
        expert_mask = F.one_hot(
            expert_indices, self.num_experts
        ).float()  # (batch, seq, top_k, num_experts)
        expert_fraction = expert_mask.sum(dim=2).mean(dim=(0, 1))

        # 每个专家的平均路由概率
        router_fraction = router_probs.mean(dim=(0, 1))

        # 负载均衡损失
        aux_loss = self.num_experts * (expert_fraction * router_fraction).sum()

        return aux_loss * self.aux_loss_coef


class Expert(nn.Module):
    """单个专家网络(标准 FFN)"""

    def __init__(self, hidden_size: int, intermediate_size: int):
        super().__init__()
        self.w1 = nn.Linear(hidden_size, intermediate_size, bias=False)
        self.w2 = nn.Linear(intermediate_size, hidden_size, bias=False)
        self.w3 = nn.Linear(hidden_size, intermediate_size, bias=False)
        self.act = nn.SiLU()

    def forward(self, x):
        # SwiGLU 激活
        return self.w2(self.act(self.w1(x)) * self.w3(x))

30.6.2 Router 的实现细节

# 代码示例:带噪声的 Router(用于训练稳定性)

class NoisyTopKRouter(nn.Module):
    def __init__(
        self,
        hidden_size: int,
        num_experts: int,
        top_k: int,
        noise_std: float = 0.1
    ):
        super().__init__()
        self.top_k = top_k
        self.noise_std = noise_std

        self.gate = nn.Linear(hidden_size, num_experts, bias=False)

    def forward(self, x, training=True):
        """
        x: (batch_size, seq_len, hidden_size)
        returns: weights, indices
        """
        # 计算路由 logits
        router_logits = self.gate(x)

        # 训练时加入噪声(增加探索)
        if training and self.noise_std > 0:
            noise = torch.randn_like(router_logits) * self.noise_std
            router_logits = router_logits + noise

        # Softmax 得到概率
        router_probs = F.softmax(router_logits, dim=-1)

        # Top-K 选择
        top_k_probs, top_k_indices = torch.topk(
            router_probs, self.top_k, dim=-1
        )

        # 归一化权重
        top_k_weights = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)

        return top_k_weights, top_k_indices, router_probs

30.6.3 使用 HuggingFace 的 Mixtral

# 代码示例:使用 HuggingFace 加载 Mixtral

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 加载模型和 tokenizer
model_name = "mistralai/Mixtral-8x7B-Instruct-v0.1"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map="auto",  # 自动分配到多个 GPU
    load_in_4bit=True,  # 使用 4-bit 量化节省显存
)

# 生成文本
prompt = "[INST] 解释什么是 Mixture of Experts [/INST]"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

outputs = model.generate(
    **inputs,
    max_new_tokens=500,
    temperature=0.7,
    do_sample=True,
)

response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)

30.6.4 查看 Mixtral 的专家结构

# 代码示例:探索 Mixtral 的 MoE 结构

from transformers import MixtralForCausalLM

model = MixtralForCausalLM.from_pretrained(
    "mistralai/Mixtral-8x7B-v0.1",
    torch_dtype=torch.bfloat16,
)

# 查看一个 MoE 层的结构
moe_layer = model.model.layers[0].block_sparse_moe
print(f"Router: {moe_layer.gate}")
print(f"专家数量: {len(moe_layer.experts)}")
print(f"每个专家: {moe_layer.experts[0]}")

# 输出类似:
# Router: Linear(in_features=4096, out_features=8, bias=False)
# 专家数量: 8
# 每个专家: MixtralBLockSparseTop2MLP(
#   (w1): Linear(in_features=4096, out_features=14336, bias=False)
#   (w2): Linear(in_features=14336, out_features=4096, bias=False)
#   (w3): Linear(in_features=4096, out_features=14336, bias=False)
#   (act_fn): SiLU()
# )

30.7 MoE vs Dense 模型对比

30.7.1 参数量 vs 激活参数

┌─────────────────────────────────────────────────────────────┐
│              参数量 vs 激活参数 对比                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  模型                总参数      激活参数     激活比例       │
│  ─────────────────────────────────────────────────────────  │
│  LLaMA 2 70B         70B         70B         100%          │
│  Mixtral 8x7B        46.7B       12.9B       27.6%         │
│  DeepSeek-V3         671B        37B         5.5%          │
│  GPT-4 (传言)        1.8T        ~110B       ~6%           │
│                                                             │
│  观察:MoE 模型激活参数远小于总参数                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

30.7.2 训练成本

模型估计训练成本Token 数量硬件
LLaMA 2 70B~$5M2TA100
Mixtral 8x7B~$2M (估计)未公开未公开
DeepSeek-V3$5.5M14.8T国产 GPU
GPT-4>$100M (传言)13T+A100/H100

MoE 训练成本优势:

  1. 每步计算量更少:只激活部分参数
  2. 可以用更多参数:相同计算预算下,MoE 可以有更多参数
  3. 更高效的梯度利用:每个专家只接收相关的梯度

30.7.3 推理效率

指标Dense 70BMoE 8x7B (激活 12B)
延迟 (首 token)基准~0.2x
吞吐量基准~3-4x
显存占用~140GB~90GB
Tokens/秒基准~6x

注意:MoE 的推理优势主要体现在:

  • 延迟降低:每次只算部分参数
  • 吞吐量提升:可以处理更大的 batch

但有隐藏成本:

  • 显存占用仍然大:所有专家参数都要加载
  • Batching 效率可能降低:动态路由增加调度复杂度

30.7.4 适用场景

┌─────────────────────────────────────────────────────────────┐
│              MoE vs Dense 适用场景                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  选择 Dense 模型:                                           │
│  - 推理延迟是最关键指标                                      │
│  - 显存非常有限(需要量化)                                  │
│  - 任务类型单一,不需要多样化知识                           │
│  - 需要简单的部署架构                                        │
│                                                             │
│  选择 MoE 模型:                                             │
│  - 需要大容量(多语言、多任务)                             │
│  - 高吞吐量比低延迟更重要                                   │
│  - 有足够的显存/机器资源                                    │
│  - 可以接受更复杂的部署架构                                 │
│                                                             │
│  实际例子:                                                  │
│  - ChatGPT API:高吞吐量场景 → MoE 适合                     │
│  - 手机端推理:显存受限 → Dense + 量化更合适                │
│  - 代码助手:需要多语言知识 → MoE 适合                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

30.7.5 未来趋势

MoE 正在成为大模型的标配:

  1. GPT-4 可能是 MoE:虽然 OpenAI 没有确认,但多方信息暗示 GPT-4 使用了 MoE
  2. 开源社区跟进:Mixtral 的成功让更多团队投入 MoE 研究
  3. 硬件支持增强:新一代 GPU 对 MoE 的 all-to-all 通信有更好的支持
  4. 混合架构涌现:MoE + 其他技术(MLA、Mamba)的组合

30.8 本章要点

30.8.1 核心概念回顾

概念含义
MoEMixture of Experts,通过稀疏激活实现大容量低计算
稀疏激活每次推理只使用部分参数,而非全部
Router决定每个 token 由哪些专家处理的组件
Expert独立的 FFN 网络,每个负责不同类型的输入
Top-K每个 token 激活 K 个专家(通常 K=2)
负载均衡确保所有专家被均匀使用的机制
MLAMulti-head Latent Attention,压缩 KV Cache
Fine-grained MoE更多更小的专家,更细粒度的路由

30.8.2 关键数字

Mixtral 8x7B:
  - 总参数: 46.7B
  - 激活参数: 12.9B (27.6%)
  - 专家数: 8, 激活 2

DeepSeek-V3:
  - 总参数: 671B
  - 激活参数: 37B (5.5%)
  - 专家数: 256+1, 激活 8
  - 训练成本: $5.5M

30.8.3 MoE 的优缺点

优点:
  ✓ 大容量,低推理成本
  ✓ 专家可以学习不同的"专业"
  ✓ 训练效率高

缺点:
  ✗ 训练不稳定
  ✗ 负载均衡困难
  ✗ 显存占用仍然大(需要加载所有专家)
  ✗ 分布式通信开销

30.8.4 核心公式

Router 计算:

router_logits = Linear(x)  # (hidden_size → num_experts)
router_probs = softmax(router_logits)
top_k_weights, top_k_indices = topk(router_probs, k)

MoE 输出:

output = Σ (weight_i × Expert_i(x))  # 只对选中的 K 个专家求和

负载均衡损失:

aux_loss = num_experts × Σ (expert_fraction × router_fraction)

30.8.5 核心认知

MoE 的核心思想是"专业分工":不是所有知识都需要同时使用。通过让不同的专家负责不同类型的输入,MoE 实现了参数量和计算量的解耦——模型可以拥有万亿参数来存储知识,但每次推理只使用其中一小部分。这正是 Mixtral 能用 12B 激活参数打平 70B Dense 模型的秘密。


本章交付物

学完这一章,你应该能够:

  • 解释稀疏激活和密集激活的区别
  • 描述 MoE 层的结构(Router + Experts)
  • 理解 Top-K 选择机制的工作原理
  • 解释为什么需要负载均衡损失
  • 比较 Mixtral 8x7B 和 LLaMA 2 70B 的参数配置
  • 理解 DeepSeek-V3 的 MLA 和 Fine-grained MoE
  • 分析 MoE 模型的优缺点和适用场景

延伸阅读


下一章预告

我们已经理解了 MoE 如何通过稀疏激活实现高效的大模型。但 2024 年还有一个更大的突破:推理模型(Reasoning Models)。

OpenAI 的 o1、DeepSeek 的 R1、月之暗面的 K1.5,这些模型在数学和编程任务上展现出了惊人的能力。它们不是简单地"预测下一个 token",而是学会了"思考"。

下一章,我们将探索这些推理模型的工作原理:为什么让模型"慢下来思考"反而能得到更好的答案?

引用本文 / Cite
Zhang, Wayland (2026). 第 30 章:Mixture of Experts - 稀疏激活的秘密. In Transformer 架构:从直觉到实现. https://waylandz.com/llm-transformer-book/%E7%AC%AC30%E7%AB%A0-Mixture-of-Experts-%E7%A8%80%E7%96%8F%E6%BF%80%E6%B4%BB%E7%9A%84%E7%A7%98%E5%AF%86/
@incollection{zhang2026transformer_30_-Mixture-of-Experts-,
  author = {Zhang, Wayland},
  title = {第 30 章:Mixture of Experts - 稀疏激活的秘密},
  booktitle = {Transformer 架构:从直觉到实现},
  year = {2026},
  url = {https://waylandz.com/llm-transformer-book/%E7%AC%AC30%E7%AB%A0-Mixture-of-Experts-%E7%A8%80%E7%96%8F%E6%BF%80%E6%B4%BB%E7%9A%84%E7%A7%98%E5%AF%86/}
}