一句话总结: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  (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,而GPT4估计超过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, W. (2026). 第 30 章:Mixture of Experts - 稀疏激活的秘密. In Transformer 架构:从直觉到实现. https://waylandz.com/llm-transformer-book/第30章-Mixture-of-Experts-稀疏激活的秘密
@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/第30章-Mixture-of-Experts-稀疏激活的秘密}
}