DeepSeek推出的最新推理模型,以500万美元的训练成本,比肩数亿美元成本的OpenAI o1,离不开各种优化策略,除了之前提到的“知识蒸馏”以外,还包括今天的主角MoE。
在机器学习和深度学习领域,模型的设计和优化一直是研究的核心。近年来,一种名为Mixture of Experts (MoE) 的模型架构逐渐引起了广泛关注。MoE模型通过结合多个“专家”模型的优势,能够在处理复杂任务时表现出色。本文将详细介绍MoE模型的基本概念、工作原理、优势以及应用场景。
Mixture of Experts (MoE),中文译为“专家混合模型”,是一种由多个子模型(称为“专家”)组成的集成模型。每个专家模型通常专注于处理输入数据的某一部分或某一特定特征。MoE模型的核心思想是通过一个门控机制(Gating Network)来决定每个输入数据应该由哪个或哪些专家模型来处理。
MoE是前馈神经网络的一种替代方案。在前馈神经网络中,对于某个问题,需要全部的权重参与计算,而通过MoE,可以根据问题的不同,只让部分权重参与计算,从而实现高效的推理。
DeepSeek致力于MoE的研究,自2024年1月的DeepSeekMoE模型开始,到2025年初的DeepSeek R1模型,都有MoE的身影。
下图是DeepSeekMoE中的MoE。
DeepSeekMoE论文:https://arxiv.org/pdf/2401.06066
下图是DeepSeekV3中的MoE。
我们在DeepSeek的官方代码中,也可以看到MoE的代码实现。
MoE代码实现:
DeepSeek-V3/inference/model.py at main · deepseek-ai/DeepSeek-V3 · GitHub
DeepSeekV3论文:
DeepSeek-V3/DeepSeek_V3.pdf at main · deepseek-ai/DeepSeek-V3 · GitHub
MoE模型的工作流程可以分为以下几个步骤:
数学上,MoE模型的输出可以表示为:
其中:
前文提到DeepSeek V3中已经给出了MoE的代码实现,但里面涉及到许多变量,难以阅读,我对官方的代码进行了总结,提取出里面核心的思想,简化后的模型代码实现如下:
- import torch
- from torch import nn
- from torch.nn import functional as F
-
-
- class Expert(nn.Module):
- """
- 定义一个专家模块,用于在 MoE 中进行特征变换。
- 每个专家模块包含两层全连接网络,中间使用 ReLU 激活函数。
- """
- def __init__(self, dim, inter_dim):
- super().__init__()
- self.w1 = nn.Linear(dim, inter_dim) # 第一层全连接层,输入维度为 dim,中间维度为 inter_dim
- self.w2 = nn.Linear(inter_dim, dim) # 第二层全连接层,输出维度为 dim
-
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """
- 前向传播函数,实现专家模块的功能。
- :param x: 输入张量,形状为 [batch_size, dim]
- :return: 输出张量,形状为 [batch_size, dim]
- """
- return self.w2(F.relu(self.w1(x))) # 先通过第一层全连接层,再通过 ReLU 激活函数,最后通过第二层全连接层
-
-
- class MoE(nn.Module):
- """
- 定义一个混合专家(MoE)模块。
- MoE 模块包含多个专家模块和一个门控网络,用于根据输入动态选择专家。
- """
- def __init__(self, dim, inter_dim, n_expert, top_k):
- super().__init__()
- self.top_k = top_k # 每次激活的专家数量
- self.n_expert = n_expert # 总专家数量
-
- self.gate = nn.Linear(dim, n_expert) # 门控网络,输入维度为 dim,输出维度为 n_expert
- self.experts = nn.ModuleList([Expert(dim, inter_dim) for _ in range(n_expert)]) # 创建 n_expert 个专家模块
-
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """
- 前向传播函数,实现 MoE 模块的功能。
- :param x: 输入张量,形状为 [batch_size, dim]
- :return: 输出张量,形状为 [batch_size, dim]
- """
- # 门控网络计算每个专家的权重
- gate_scores = self.gate(x) # [batch_size, num_experts],计算每个样本对每个专家的得分
- gate_weights = F.softmax(gate_scores, dim=-1) # 归一化为概率分布,形状为 [batch_size, num_experts]
-
- # 选择 top-k 专家
- top_k_weights, top_k_indices = torch.topk(gate_weights, self.top_k, dim=-1) # [batch_size, top_k],选择 top-k 专家的权重和索引
- top_k_weights = top_k_weights / top_k_weights.sum(dim=-1, keepdim=True) # 重新归一化,确保 top-k 权重和为 1
-
- # 初始化输出
- output = torch.zeros_like(x) # [batch_size, output_dim],初始化输出张量
-
- # 只激活 top-k 专家
- for i in range(self.top_k):
- expert_idx = top_k_indices[:, i] # 当前批次的第 i 个专家索引,形状为 [batch_size]
- expert_mask = F.one_hot(expert_idx, num_classes=self.n_expert).float() # [batch_size, num_experts],创建 one-hot 掩码
- expert_output = torch.stack([expert(x) for expert in self.experts], dim=1) # [batch_size, num_experts, output_dim],计算所有专家的输出
- expert_output = torch.sum(expert_output * expert_mask.unsqueeze(-1), dim=1) # [batch_size, output_dim],根据掩码选择对应专家的输出
- output += top_k_weights[:, i].unsqueeze(-1) * expert_output # 加权求和,将当前专家的输出加到总输出中
-
- return output
-
- # 示例用法
- if __name__ == "__main__":
- # 定义模型参数
- input_dim = 32 # 输入维度
- num_experts = 4 # 专家数量
- top_k = 2 # 每次激活 2 个专家
- batch_size = 8 # 批量大小
-
- # 创建 MoE 模型
- moe = MoE(input_dim, 16, num_experts, top_k=top_k)
-
- # 随机生成输入数据
- x = torch.randn(batch_size, input_dim)
-
- # 前向传播
- output = moe(x)
- print("Output shape:", output.shape) # 应为 [batch_size, input_dim]