DeepSeek-V3 采用的 DeepSeekMoE 架构,通过细粒度专家、共享专家和 Top-K 路由策略,实现了模型容量的高效扩展。
每个 MoE 层包含 1 个共享专家和 256 个路由专家,每个 Token 选择 8 个路由专家,最多路由至 4 个节点。这种稀疏激活的机制,使得 DeepSeek-V3 能够在不显著增加计算成本的情况下,拥有庞大的模型容量。
MoE 的核心思想:分而治之
想象一下,一个超级英雄联盟,每个英雄都有自己独特的超能力,专门应对特定的威胁。MoE 架构就类似于此,它包含多个“专家”网络,每个专家专注于处理特定类型的输入或特征。当一个输入进来时,一个“Gate Network”会决定将输入路由到哪些最合适的专家进行处理。
在 DeepSeek-V3 中,MoE 层主要由两种类型的专家构成:
- 路由专家 (Routed Experts): 数量众多,负责处理特定类型的输入。DeepSeek-V3 的每个 MoE 层包含 256 个路由专家。
- 共享专家 (Shared Experts): 数量较少,负责处理所有输入,提供通用的特征提取。DeepSeek-V3 每个 MoE 层包含 1 个共享专家。
类比,想象一个大型公司的客服中心:
- 共享专家 就像是总机接待员,每个来电都会先经过他们。他们负责处理一些常见问题和基本信息收集,并将电话转接到更专业的部门。
- 路由专家 就像是各个专业部门的客服人员(例如,技术支持、账单查询、投诉处理)。总机接待员(Gate)会根据来电者的需求将电话路由到相应的专业部门。每个来电只会连接到少数几个最相关的专业部门。
DeepSeek-V3 的 MoE 工作流程
当一个句子输入到 DeepSeek-V3 的 MoE 层时,会经历以下步骤:
- 路由 (Routing): Gate 网络接收输入(每个词的表示),并计算每个词与各个路由专家的匹配程度(得分)。
- 选择 (Selection): 根据得分,Gate 网络为每个词选择 Top-K 个最合适的路由专家。在 DeepSeek-V3 中,每个 Token 选择 8 个路由专家。
- 专家处理 (Expert Processing): 被选中的路由专家以及共享专家会对输入进行处理,提取特征。
- 加权与聚合 (Weighting and Aggregation): 每个路由专家的输出会根据 Gate 网络给出的权重进行加权,然后与共享专家的输出进行聚合,形成 MoE 层的最终输出。
我们用个示例来理解这个过程:
假设我们有一个 MoE 层,其中包含几个不同的路由专家,每个专家擅长处理不同类型的语言信息:
- 专家 1: 擅长处理情感词汇和情感表达。
- 专家 2: 擅长处理实体指代和关系理解。
- 专家 3: 擅长处理语法结构和句法关系。
- 共享专家 (虽然不是路由专家,但也参与处理): 提供通用的特征提取。
现在,考虑以下句子:
"我今天非常高兴地收到了一份期待已久的礼物。"
当这个句子输入到 MoE 层时,Gate 模块会为每个词计算路由得分,并决定将哪些词路由到哪些专家:
- "我" (Pronoun): 可能会被路由到 专家 2 (实体指代)。
- "今天" (Time Adverb): 可能也会被路由到 专家 2 (情境理解)。
- "非常高兴地" (Adverb + Adjective): 很可能会被路由到 专家 1 (情感词汇)。
- "收到" (Verb): 可能会被路由到 专家 3 (动词,语法结构)。
- "了" (Particle): 可能也会被路由到 专家 3 (语法助词)。
- "一份期待已久的" (Adjective Phrase): 可能部分路由到 专家 1 (情感),部分路由到 专家 2 (描述性信息)。
- "礼物" (Noun): 可能会被路由到 专家 2 (实体)。
- 整个句子也会经过共享专家。
处理过程:
- 路由: 每个词根据 Gate 的决策被分配到相应的路由专家。
- 专家处理:
- 专家 1 接收到 "非常高兴地",会提取其积极的情感特征。
- 专家 2 接收到 "我"、"今天"、"礼物",会处理实体信息和它们之间的关系。
- 专家 3 接收到 "收到"、"了",会分析动词的含义和时态。
- 共享专家 会对整个句子的表示进行通用的特征提取。
- 加权和聚合: 每个专家对接收到的词进行处理后,会产生一个输出表示。这些输出表示会根据 Gate 给出的权重进行加权,然后进行聚合(例如,求和或拼接)。同时,共享专家的输出也会被加进来。
- 传递到下一层: MoE 层的最终输出表示会传递到 Transformer 模型的下一层进行进一步处理。
将同一句话的不同词路由到不同的专家是 MoE 架构的核心机制,这是 MoE 设计的核心思想,旨在让不同的专家专注于处理不同类型的输入或特征,从而提高模型的整体能力。
代码层面深入理解 MoE
让我们深入到 DeepSeek-V3 的代码实现,理解 MoE 的关键组件:
Gate 类:实现智能路由
class Gate(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.dim = args.dim
# 每个 Token 激活的专家数
self.topk = args.n_activated_experts
# 专家组数量
self.n_groups = args.n_expert_groups
# 每个 Token 路由到的专家组数量
self.topk_groups = args.n_limited_groups
# ...
def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
# 计算 Token 与每个路由专家的匹配度
scores = linear(x, self.weight)
# ...
if self.n_groups > 1: # 专家分组路由,实现负载均衡
scores = scores.view(x.size(0), self.n_groups, -1)
group_scores = scores.topk(2, dim=-1)[0].sum(dim=-1)
group_indices = group_scores.topk(self.topk_groups, dim=-1)[1]
mask = torch.zeros_like(scores[..., 0]).scatter_(1, group_indices, True)
scores = (scores * mask.unsqueeze(-1)).flatten(1)
# 选择 Top-K 个专家
indices = torch.topk(scores, self.topk, dim=-1)[1]
# 获取专家权重
weights = original_scores.gather(1, indices)
return weights.type_as(x), indices
Gate 类的 forward 方法是路由的核心。它首先计算每个 Token 与所有路由专家的得分,然后,如果启用了专家分组(n_groups > 1),则会先选择 Top-K 个专家组,再在这些组内选择最终的 Top-K 个专家。这是一种保障负载均衡的策略。
Expert 类:细粒度专家
class Expert(nn.Module):
def __init__(self, dim: int, inter_dim: int):
super().__init__()
self.w1 = Linear(dim, inter_dim)
self.w2 = Linear(inter_dim, dim)
self.w3 = Linear(dim, inter_dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.w2(F.silu(self.w1(x)) * self.w3(x))
Expert 类定义了一个简单的 MLP 结构,每个路由专家都是一个独立的 Expert 实例,负责处理被路由到它的 Token。
MoE 类:整合共享与路由专家
class MoE(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
# 共享专家
self.shared_experts = MLP(args.dim, args.n_shared_experts * args.moe_inter_dim)
# 路由专家列表
self.experts = nn.ModuleList([Expert(...) for _ in range(args.n_routed_experts)])
# 路由模块
self.gate = Gate(args)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# 获取路由权重和索引
weights, indices = self.gate(x)
y = torch.zeros_like(x)
for i in range(self.n_routed_experts):
mask = (indices == i)
if mask.any():
# 将 Token 路由到对应专家并加权
y[mask] += self.experts[i](x[mask]) * weights[mask]
# 所有 Token 通过共享专家
z = self.shared_experts(x)
return (y + z).view(shape)
MoE 类的 forward 方法完成整个 MoE 层的计算。它首先通过 Gate 获取路由决策,然后将 Token 分配给相应的路由专家进行处理,并将路由专家的输出与共享专家的输出进行合并。
创新的无额外损耗负载均衡策略
为了解决 MoE 中常见的负载不均衡问题(某些专家过载,某些专家利用不足),DeepSeek-V3 提出了一种创新的、无额外损耗的负载均衡策略。 传统的 MoE 模型通常会引入额外的辅助损失函数来平衡各个专家的负载,但这可能会对模型的主要任务性能产生负面影响。
DeepSeek-V3 的方法则更加优雅。它在 Gate 模块中引入了一个可学习的偏置项 (Bias Term):
self.bias = nn.Parameter(torch.empty(args.n_routed_experts)) if self.dim == 7168 else None
这个 bias 是一个可学习的参数,与路由专家的数量相同。在计算路由得分时,这个偏置项会被动态地加到每个路由专家的得分上:
if self.bias is not None:
scores = scores + self.bias
那么,这个偏置项是如何实现负载均衡的呢?
- 动态调整路由倾向: 通过学习这些偏置项,模型可以动态地调整对不同路由专家的偏好。如果某个专家的负载过重,其对应的偏置项可能会被学习为负值,从而降低其被选择的概率。反之,对于负载较轻的专家,其偏置项可能会被学习为正值,提高其被选择的概率。
- 无额外损耗: 关键在于,这个偏置项的调整是直接通过模型的训练目标进行优化的,而不是通过一个独立的负载均衡损失函数。这意味着,模型在努力提高主要任务性能的同时,也会自然而然地学习到一种更均衡的路由策略,而不会因为额外的负载均衡损失而影响性能。
DeepSeek-V3 还对偏置项的学习率进行了动态调整,以更好地平衡训练的早期探索和后期稳定:
- 预训练早期 (前 14.3T 个 Token): 偏置项的更新速度 (γ) 设置为 0.001,允许模型在早期快速探索不同的路由策略。
- 预训练后期 (剩余 500B 个 Token): 偏置项的更新速度 (γ) 设置为 0.0,意味着在模型训练的后期,路由策略趋于稳定,不再进行大幅调整。
为什么 MoE 如此有效?
- 模型容量扩展: MoE 允许模型拥有大量的参数(每个专家都有自己的参数),而每个输入只需激活少量专家。通过增加专家数量,可以显著扩展模型的参数量,而不会显著增加计算成本(因为每个输入只激活少数几个专家)。
- 专业化处理: 不同的专家可以学习到不同的特征,使得模型能够更精细地处理各种复杂的输入。
- 动态路由: Gate 网络能够根据输入内容动态地调整路由策略,实现更灵活的计算。
- 无损负载均衡: 通过可学习的偏置项,实现更均衡的专家利用率,提升训练效率和模型性能。
总结:分工合作,高效致胜
DeepSeek-V3 的 MoE 架构是一种精巧的设计,它借鉴了“分而治之”的思想,通过引入多个专业化的路由专家和一个通用的共享专家,实现了模型容量的高效扩展和更精细化的输入处理。
创新的无额外损耗负载均衡策略,通过动态调整可学习的偏置项,进一步提升了 MoE 架构的效率和性能。
Gate 网络作为智能的“调度员”,确保每个 Token 都能找到最合适的“专家”进行处理。
深入理解 MoE 的工作原理和代码实现,有助于我们更好地理解 DeepSeek-V3 强大能力的背后逻辑。