
漫画DeepSeekMoE--借助Excel理解它:从原理到代码实现 精华
在深度学习的广阔天地里,我们时常追求模型的高效与精准。精兵简政,这一理念在专家混合架构MoE中得到了淋漓尽致的体现。MoE,即Mixture of Experts,专家混合架构,它的核心思想在于将复杂的任务拆解为多个子任务,每个子任务由专门的“专家”模型负责处理。
这种架构的优势在于,它可以根据输入数据的不同特点,动态地选择最合适的专家进行处理,从而在保证模型性能的同时,降低了计算复杂度和资源消耗。正如古人所言,“多兵不如善用兵”,MoE正是通过优化资源配置,实现了表达力的大幅增强。
在接下来的内容中,我们将深入探讨MoE的具体实现原理、应用场景以及未来的发展趋势,以期在这一领域有更深入的理解和探索。
原来这就是专家混合架构
让我们继续借助第2章中的案例,深入了解DeepSeek所带来的创新之处。
在春秋末年,吴国的杰出将领孙武先生负责训练和指挥军队。军营中搭建了一座名为“点将台”的高台。每天黎明时分,全军列队。随着号角的响起,数万名士兵整齐划一地排列,静候孙将军挑选将领,带领他们奔赴战场。
而这,正是后人口中的“稀疏点将制”——如今我们在人工智能中称之为MoE。这套制度背后,蕴含着孙武对兵力调度的极致智慧,也正好道出了MoE模型为何强大又高效的原理。
将多兵不如善用兵:表达力大幅增强
孙武的军营中,将领如云,人才济济。营外,是经历过百战的精兵强将——左侧有擅长夜袭的田将军,右侧有精通水战的吕都尉,更有深谙兵法、洞察敌情的钟谋士。每位将领都各自统领着万人的军队,他们都是能够独当一面的杰出人才。然而,孙武从不一次性派遣所有将领。他深知:兵力众多并不总是优势,战场上的关键在于“用人得当”。因此,每当敌情发生突变,他便登上点将台,从众多将军中挑选出最适合的两位来应对敌军。在出战之日,虽然看起来只有两支队伍奔赴前线,但实际上,这是整个将军团队智慧的体现。
这正体现了MoE模型的运作原理:尽管整个网络由众多“专家”组成,每个专家的参数数量可达到数亿,但在进行推理时,通常只激活其中两个或少数几个专家。以一个普通的 MLP 层为例,其参数量为100M;而在 MoE 层中,即便有10个专家,每个专家的参数量也是100M,总计参数量达到 1B;然而,在实际操作中,每次仅启用2个专家,因此实际的计算成本保持在200M,大大提高了用兵的效率。
孙武布兵如 MoE 布网:整军备战,局部出击,智取胜于力拼。
稀而不弱:高效稀疏调度,轻装上阵
孙武实施“点将制”,这不仅体现了他的智慧,更源于他需要迅速调配兵力、轻装上阵。他深知兵贵神速,多余的兵力只会拖慢进军的步伐。每当调动军队出征,他仅需向军令官轻声下令:“点将谋士、田将军。”军令如山,其他人员保持不动,唯独这两员大将迅速整顿部队。
这与在 MoE 模型中引入的稀疏前向传播极为相似:通过门控机制,仅激活 Top-k 个专家,而其他专家则保持静默状态。这样,每次只需计算一小部分子网络,从而节省了内存和计算资源;同时避免了重复学习,提升了专家的特化程度。这正是稀疏机制的核心所在:确保专家仅在必要时参与计算。
调兵无上限:专家可扩展,模型更灵活**
随着敌军战术的不断演变,孙武不再满足于现有的十位将领。他开始着手扩充军队,吸纳更多的专家型将军。然而,这位智者并未安排所有将领同时进行训练或出征。他深知,在战斗中每次只需两位最合适的将军出战(参见下图),即使军营中拥有百将千帅,其目的也是为了在面对各种不同的战场时,能够有更多的选择和更精确的调度。
这正是 MoE 模型可扩展性的体现:在模型训练或性能不佳时,可以通过新增专家(子网络)来扩充“知识容量”;但每次计算仍然保持稀疏,仅激活最相关的 Top-k 专家,不增加实际计算负担;随着专家数量的增加,模型能适应更多任务场景,迁移性更强。孙武并非盲目扩军,而是精准备战;MoE 亦非盲目堆叠参数,而是理性稀疏计算的艺术。
点将有术:门控网络如统帅
尽管将军众多,但真正能够洞悉敌情、善于用人者,正是站在点将台上的孙武本人。
在 MoE 模型中,门控网络(Gating Network)扮演着至关重要的角色。每当输入一句话或一个样本,它便负责判断当前最适合处理该输入的专家是哪一位,并激活相应的子网络。例如:输入一句诗词 → 选择文学类专家;输入一段代码 → 激活程序专家;输入医学记录 → 调用医学专家进行处理。门控网络所学习的,不仅仅是“谁能完成任务”,更是“谁完成得最为出色”。这与孙武多年征战后练就的“点将直觉”如出一辙。
MoE(专家混合架构)就像孙武兵法中的用兵之道,是一种兼顾力量与智慧的策略工具:
- 兵多将广:模型参数多,具备强大的表达能力;
- 点将得当:通过稀疏激活,精准调度专家,降低计算成本;
- 弹性扩军:可根据任务灵活扩展或裁剪专家,适应性强;
- 门控统帅:由门控机制挑选最合适的专家,高效又精准。
MoE 的关键不在于“人多”,而在于“选对人、用对人”,让模型从“全员出动”进化为“精兵出击”,大幅提升了效率与智能表现。
从撒豆成兵到撒豆成精:DeepSeek MoE的数学推演
让我们通过结合Excel表格和DeepSeek MoE的公式,共同探索机器学习领域中一个非常重要的创新——DeepSeek MoE。下图是从传统的MoE架构,到Deepseek MoE架构的演变。
在人工智能领域,随着模型规模的不断膨胀和复杂性的增加,管理这些“知识专家”变得异常棘手。
若每次任务都激活所有专家,不仅会消耗大量电力,还会导致模型运行缓慢,造成资源的浪费。
幸运的是,一些智慧的科学家们创造了Mixture of Experts(MoE),它宛如一位指挥官,仅挑选出最适宜的少数专家参与每一次的任务。
然而,DeepSeek团队意识到,传统的MoE仍有提升空间。因此,他们提出了一个升级版——DeepSeekMoE。这个创新设计融合了两大核心策略:
- 细粒度专家分割(图b)
- 共享专家隔离(图c)
这两大策略,旨在实现一个共同目标:
使每位专家更加专业,整个系统运行更高效、更节约、更智能。
细粒度专家分割
想象一下,如果你要组建一个救援小组。传统方法是,每个人都是"全能战士":又要能开船、又要能攀岩、又要懂医疗……虽然厉害,但一旦出任务,总有人做得不好,因为术业有专攻。更聪明的方法是:把任务细分,组建小而专的队伍。 比如有人专攻救火,有人专攻山地搜救,有人专攻医疗急救。这样一来,每个人只专注在自己最擅长的领域,整体效率大大提高!
在 DeepSeekMoE 中,科学家们也采用了类似的办法。他们做了两件事:
一是把每个原本的大型FFN模块,切成_m_个更小的专家;二是为了保持整体运算量不变,每次选择的小专家数量也同步乘_m_。
举个具体例子:假设以前有16个大专家,每次从中选择2个来工作,组合方式只有120种。
现在每个专家被切成4个小专家,一共有64个小专家,每次选择8个,组合方式一下子飙升到惊人的44亿种(准确是4,426,165,368种)。
这种变化带来了巨大的好处:
- 输入的信息可以被更细致地分配到最适合的小专家;
- 每位小专家负责更窄、更专精的领域;
- 整个模型变得更聪明、响应更快。
关键点提醒:虽然专家变小了,数量变多了,但总的参数量和计算量是一样的哦,不用担心模型负担加重!
共享专家隔离
还有一个问题:在现实任务中,有些基本知识是大家都会用的。比如,不管做什么任务,基本的沟通能力、常识判断都必不可少。如果让每个小专家都自己去重新学习这些基础知识,那就太浪费了。所以 DeepSeekMoE 引入了第二个绝招——共享专家隔离。
具体来说:预留出_Ks_个专家,专门作为“公共知识库”;所有输入数据,不管走不走路由器,都必须经过这批共享专家;剩下的动态专家,才由路由器根据需要动态选择。
这样设计带来了双重好处:
- 公共知识集中处理,每位小专家不用重复存储常识,大大减少了参数冗余;
- 非共享专家可以专心做细分领域,比如有人专门研究生物学,有人专门研究物理学,各自深耕。
注意:因为共享专家是必选的,所以为了保持整体计算量稳定,动态选择的小专家数量会相应减少 _Ks_个。
它们的数学公式就如下所表示:
wpsoffice
我们可以把它们投影到我们的图中:
DeepSeekMOE
接下来尝试在Excel中实现它们,参见下图。
接下来再初始化一下路由权重,参见下图:
其中,_E_1 代表的是共享专家,_E_2~_E_4代表的是路由专家。
我们计算路由的公式是这样的:
/Users/i/Library/Containers/com.kingsoft.wpsoffice.mac/Data/tmp/wpsoffice.hBKTQpwpsoffice
想象一下,你经营着一个聪明的AI事务所,里面有许多聪明的“专家”——每位专家擅长不同的领域。每天,系统会接收到一些“任务”,每个任务由多个特征组成。你要做的,是判断哪些专家对哪个任务最感兴趣,从而派出最合适的人来处理问题。
我们首先有一组输入任务,它们由一个包含 6 个 Token(也可以理解为词或片段)的矩阵组成。每个 Token 拥有 5 个特征。换句话说,你眼前有一个 5 行 × 6 列的表格,每一列代表一个任务点,每一行是它的某个描述维度。
另一方面,AI事务所的专家团体(比如 E1、E2、E3 和 E4)每位都各有专长。他们的专长可以被表达成一个“兴趣向量”——也就是他们关注每种特征的程度。
这里用了 Excel 的一个函数来评估专家与任务的匹配度:
=MMULT(P152:T154, V141#)
这行公式的含义是:让系统把每一位专家的兴趣向量,与每个任务的特征做一次点积运算,得出一个“匹配打分表”。它就像是每位专家在看每个任务时会说:“这个任务我多感兴趣。”
在数学上,它可以被写成:
S = E · X
其中:
E 表示专家矩阵,每一行是一位专家对五个特征的偏好;
X 表示输入矩阵,每一列是一项任务的五个特征;
S 就是输出结果,也就是专家对每个任务的打分表。
接下来再用SoftMax对它们进行进一步的计算,参见下图:
Softmax 是深度学习和 AI 模型中非常常用的一个“变魔法”的函数,它的主要作用可以用一句话概括:
把一堆“分数”变成“比例”,让它们代表“谁说话声音更大”。
用简单例子说:
假设你有三个专家对一个任务的打分是:
专家 A:5
专家 B:2
专家 C:1
这只是相对的“分数”,不能直接用作权重(因为它们可能太大、太小、甚至为负数)。
这时候,Softmax 会这样做:
把所有分数指数化(变正、放大差距)
再除以总和,让它们变成 0~1 之间的比例
且所有比例加起来刚好等于 1(像概率)
比如,softmax 后结果可能是:
A:0.84
B:0.11
C:0.05
接下来对专家得分进行排序,也就是对我们上面的值按列进行排序,选择Top 3的专家。下图呈现了通过排序(SORT(...,,-1))得到的每列专家打分的降序排列结果,这一过程常用于混合专家模型(MoE)中进行Top-K路由选择。
其中Excle的公式如下:
=SORT(AC151:AC154,,-1)
图表内容揭示了一个4行×6列的打分矩阵,其中每列代表一个Token,每行代表一个专家。该矩阵列出了每个Token对应所有专家的亲和力得分,并且每列得分均按从高到低排序。
为何要进行此类排序?在MoE模型中,我们并非让所有专家同时“发言”,而是让每个Token仅选择得分最高的前K个专家参与处理。这一过程被称为:
Top-K Routing:每个Token路由至最匹配的K个专家。
此排序矩阵的作用是什么?你使用的是Excel的函数:=SORT(...,,-1),意味着对每列进行降序排列。
排序后的矩阵便于快速判断哪些专家进入了Top-K。
举个例子:Token5(第 5 列),原始打分(排序前)可能是:
专家 分数
E1 1.000000000
E2 0.867739153
E3 0.521375896
E4 0.407783590
排序结果是:
1.000000
0.867739
0.521376
0.407784
所以 Top-3 专家就是:E1、E2、E3
这里,我们同时对得分用 Softmax 做归一化,然后才进行的排序。接下来再对选出来的专家,进行运算:
得到最终的 Gate 权重
wpsoffice
对应的Excel公式:
=IF(AC151:AH154 >= AK153:AP153, AC151:AH154, 0)
下图是……
这张图非常清晰地展示了 Mixture of Experts 中的 Top-K 路由过程,并通过 Excel 函数 =IF(...) 实现了“打分保留 / 剔除”的门控逻辑。下面我来解释这段公式和操作背后的含义。
这段公式的意思是:
“对于每个专家对每个 Token 的打分,如果它进入了 Top-3(即分数 ≥ 该 Token 第 3 高的分数),那么保留原始值;否则设为 0。”
对应的数学表达形式,这是我们之前讲过的 “门控函数” 的选择形式:
wpsoffice
其中:
s:原始亲和力得分(专家打分)
g:门控值(控制专家输出权重)
红色区域(AK153:AP153):每列 Top-3 分数底线 每个 Token 的第三高分,用于筛选
黄色门控区域,满足条件的得分保留,否则为 0。
为什么这么做?这个过程实现了 Top-K 路由的门控过滤机制:
它不是直接根据“谁排第几名”来选专家,而是对每列找出 Top-K 的最小分数,然后保留所有大于等于这个分数的专家,这样做的好处是便于向下使用 Softmax(因为保留的是原始 score)。下图是……
这里我们可以看到因为专家1是共享专家,所以所有Token都参与了运算。
其他专家灰色的区域都没有参加计算,这样就大大节约了计算资源。
混沌初开,众神归位:DeepSeek MoE架构的代码实现
DeepSeek MoE架构的代码实现基于上述的逻辑和机制,将复杂的模型运算高效地组织起来。代码的核心在于如何精准地控制专家的选择和计算资源的分配。在DeepSeek中,专家们被组织成一个多层的结构,每一层都负责不同的任务,但都遵循着Top-K路由的门控过滤原则。
代码实现的关键步骤包括:首先,定义每个专家的计算函数和参数;其次,通过矩阵运算确定每个Token对应的专家得分;然后,根据Top-K路由机制,筛选出得分高于或等于每列Top-K最小分数的专家;最后,对这些筛选出的专家进行Softmax运算,以确定每个专家在最终决策中的权重。
DeepSeek MoE架构的代码实现不仅优化了模型的性能,还大大提高了计算效率。通过精准地控制专家的选择和计算资源的分配,DeepSeek能够在保证模型精度的同时,显著降低计算成本和时间。这使得DeepSeek在处理大规模数据和复杂任务时具有显著的优势。
下面来看看笔者的实现代码:
引入需要的库,示例代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
定义单个前馈专家,实现代码如下:
class FeedForwardExpert(nn.Module):
def __init__(self, d_model):
super().__init__()
self.net = nn.Sequential(
nn.Linear(d_model, d_model),
nn.ReLU(),
nn.Linear(d_model, d_model),
) # 简单两层 MLP,含 ReLU
def forward(self, x):
return self.net(x) # 前向传播
定义 DeepSeekGroupedMoE 模块
class DeepSeekGroupedMoE(nn.Module):
def __init__(self, d_model, num_groups, experts_per_group, top_k):
super().__init__()
self.d_model = d_model
self.num_groups = num_groups # 分几组
self.experts_per_group = experts_per_group # 每组几个专家
self.total_experts = num_groups * experts_per_group
self.top_k = top_k # 每个 Token 在组内选几个专家
# 所有专家(按组摊平)
self.experts = nn.ModuleList([
FeedForwardExpert(d_model) for _ in range(self.total_experts)
])
# 一个共享专家(所有 Token 都经过)
self.shared_expert = FeedForwardExpert(d_model)
# 路由器部分
self.group_router = nn.Linear(d_model, num_groups) # 负责选择组
self.intra_router = nn.ModuleList([
nn.Linear(d_model, experts_per_group) for _ in range(num_groups) # 每组内部的路由器
])
前向传播逻辑
def forward(self, x): # 输入 x: [B, T, D]
B, T, D = x.shape
x_flat = x.view(-1, D) # [B*T, D] 扁平化为 Token 维度
# 步骤1: 分组路由(决定每个 Token 属于哪个 group)
group_logits = self.group_router(x_flat) # [B*T, num_groups]
group_idx = group_logits.argmax(dim=-1) # 每个 Token 属于哪个组 [B*T]
# 步骤2: 在组内选择 top-K 个专家
output = torch.zeros_like(x_flat) # 初始化输出 [B*T, D]
for g in range(self.num_groups): # 遍历每个组
mask = (group_idx == g) # 选择属于第 g 组的 Token
if mask.sum() == 0:
continue # 当前组没有 Token,跳过
x_g = x_flat[mask] # 提取该组 Token,形状 [N_g, D]
intra_scores = self.intra_router[g](x_g) # 对每个 Token 给组内专家打分 [N_g, experts_per_group]
# 选择 top-k 个专家
topk_vals, topk_idx = torch.topk(intra_scores, self.top_k, dim=-1)
out_g = torch.zeros_like(x_g) # 初始化当前组输出
for i in range(self.top_k): # 遍历每个 top-k 专家
expert_ids = g * self.experts_per_group + topk_idx[:, i] # 计算全局 expert ID
gate = topk_vals[:, i].unsqueeze(-1) # 获得门控分数 [N_g, 1]
# 对每个 Token 单独调用对应专家
expert_out = torch.stack([
self.experts[expert_id](x_g[j]) # 每个 Token 分别走自己的专家
for j, expert_id in enumerate(expert_ids)
])
out_g += gate * expert_out # 按照 gate 加权相加
output[mask] = out_g # 将该组 Token 的输出插回原位置
#步骤 3: 共享专家输出(所有 Token 都过一遍)
shared_out = self.shared_expert(x_flat) # 所有 Token 共享专家 [B*T, D]
final = output + shared_out # 最终输出为两者相加 [B*T, D]
return final.view(B, T, D) # reshape 回原始形状
在上面的代码清单中,我们对代码进行了尽量详细的注释,相信大家应该可以看得懂。
不过,为了更深入地理解这个模块,我们还是简单总结一下它的工作流程:
首先,输入的序列 x 会被展平成二维的形式 x_flat,这样可以对每个 Token 进行独立的处理。然后,这些 Token 会根据一定的规则(例如通过路由网络)被分配到不同的专家组中。每个专家组会对分配给自己的 Token 进行处理,并输出一个专家输出 expert_out。
接着,这些专家输出会根据 gate(门控机制)进行加权相加,得到加权后的输出 out_g。这个加权的过程实际上是在选择性地融合不同专家的知识,让模型能够更好地处理输入序列。
然后,我们将加权后的输出 out_g 插回到原始序列的对应位置上,得到完整的输出 output。这个步骤确保了即使某些 Token 被分配到了不同的专家组,它们的输出仍然能够按照原始序列的顺序进行组合。
此外,为了让所有 Token 都能够从专家网络中获益,我们还设计了一个共享的专家网络 self.shared_expert。这个网络会对所有 Token 进行处理,并输出一个共享的专家输出 shared_out。这个输出会与前面的 output 相加,得到最终的输出 final。
因此,DeepSeekMoE 模块实际上是一个结合了条件计算和知识蒸馏思想的混合专家系统。它通过对输入序列进行细粒度的分组和处理,以及通过门控机制和共享专家网络进行知识的融合和提炼,从而实现了对复杂任务的高效和准确的建模。
本文转载自 AI大模型世界,作者:roclv
