CS336 Lecture_04
这节课讲解的是 MoE 架构(混合专家模型,Mixture of Experts)
MoE
Structure
MoE 包含若干被称为"专家"的子组件,这些专家在网络中是稀疏激活的
稠密模型:几乎所有的参数都会对输入数据进行处理的模型
稀疏模型:对于不同任务,仅调用部分参数计算
MoE 与非 MoE 结构的关键区别在 FFN 部分,在标准 transformer 中,FFN 是一个大型稠密模块,而在 MoE 中,这个 FFN 被复制或拆分成多个更小的 FFN 副本,并且引入一个路由器机制,在每次前向传播或推理时,仅动态选择其中一部分专家进行激活
这样做的好处是,如果保持激活专家的计算量与原始稠密 FFN 相当,那么模型在前向传播时的计数成本不变,却拥有了更多的参数总量

实验证明,在相同计算量(FLOPs)情况下,MoE 比稠密模型表现得更好,并且性能和专家数量成正比
但是这些专家并不是没有代价的,我们需要为这些专家存储内存,在进行并行计算时,还需要将数据"路由"到各个独立的专家那里

当含有专家时,在专家层面有一种非常自然的并行化方式,我们会有多个不同的前馈块,并且可以把这些专家中的每一个放到不同的设备上
而且因为专家是稀疏激活的,我们所要做的就是拿起 token 并将其路由到相应的设备,计算就会在该设备上进行
这是一个很自然的切分点,可以将模型分片到不同设备上,这被称为"专家并行"(expert parallelism)

Routing function
路由函数:如何进行路由,或者本质上是如何将 tokens 匹配到专家
方法1:词元选择(Token Choice)
这种方式下,每个词元都有对于不同专家的路由偏好,然后会为每个词元选择 Top-K 个专家
优点:我们可以定义一个评分函数,使得这个分数代表每个 token 被每个专家处理得有多好,并且词元选择会将它路由到最适合它的专家
方法2:专家选择(Expert Choice)
这种方式下,每个专家会对词元有一个排序偏好,然后会为每个专家选择 Top-K 个词元
这种方式的一个很大好处是,它能在专家之间实现均衡
优点:每个专家都能获得完全相同数目的 token,如果把不同的专家放在不同的设备上,就能获得平衡的利用率
方式3:全局分配(Global Assignment)
通过解决某种复杂的优化问题,来确保专家和词元之间的映射在某种程度上是均衡的
(几乎所有 MoE 模型都选择了方式1,Top-K 中的 K 通常取 2)
每个 token 如何知道哪个专家适合自己:用嵌入向量 X 乘以一个矩阵 W,然后只需要取一个 sigmoid 或类似的函数,那就是分数(路由器其实非常轻量)

Top-K 路由是大多数 MoE 中采用的方式
工作原理:残差流输入 x 进入一个路由器(进行一个线性内积运算,然后是一个 softmax),选择激活度最高的前 K 个专家,然后这些输出会被"门控"(根据不同的实现方式,可能会根据这个路由器权重对输出加权,也可能不加权),输出加权平均值或者只是一个简单的求和(取决于 MoE 的实现方式)
有很多结果表明,实际上根本不需要一个智能路由器,而直接在最底层使用一个哈希函数,将这些 x 映射到专家上,即使只是做哈希,完全没有语义信息,基于哈希的 MoE 仍然能带来性能提升

Top-K 路由公式(u 是残差流输入,e 是路由器参数)
当我们对每个专家的输出进行加权平均后,把它加回原始的残差流,并返回结果

Expert of DeepSeek
DeepSeek 在 MoE 上的创新:细粒度专家(fine-grained expert)和共享专家(shared expert)
(1)细粒度专家:
最初提出的 MoE 架构就是拿稠密架构,然后把专家复制过去,这种情况下,如果 K=2,那么激活参数将是原始稠密模型的两倍。拥有很多专家是好事,但是我又不想为拥有很多专家付出参数成本,DeepSeek 的做法就是把专家切分成更小的块(MLP 先投影再切分)
(2)共享专家:
也许拥有一些能够捕捉共享结构的 MLP 会有帮助,也许有些处理是无论我们处理哪个 token 总是需要进行的,这种时候分散参数到各个专家是不合适的,而我们只需一两个共享的专家,他们的工作就是处理所有这些所需的共享处理

MoE 中的一些超参数(比率代表的是模型中的每个专家被切分了多少,相对于只有标准的稠密配置)

Training
训练时,我们不能启用所有专家,因为需要支付所有专家的全部 FLOPs 计算成本,所以我们需要训练时的稀疏性,但稀疏的门控决策不可微分(离散,无法反向传播)
所以要找到可以进行微分,反向传播,从而更新参数的方法
方法1:RL for MoEs(强化学习)
效果还不如哈希函数,而且很复杂,几乎不使用

方法2:Stochastic approximations(随机近似)
比如这里在进行 得到输入向量与门控权重的亲和度
后,加入正态分布噪声 ,然后通过一个可学习的尺度参数
控制噪声强度
引入噪声后,每个专家可能随机得到一些没有预料到的 token,这将导致专家的专业化程度较低但可能更全面(但是随机化也意味着无法获得很多专业化,效率没那么高)
这类似于 Bandit 算法(多臂老虎机问题),如果拉一个臂却看不到其它臂的情况,就无法有效分配资源,如果随机拉一些其它的臂,就有了足够的数据做优化,所以这种"抖动"在思想上非常类似于 epsilon-greedy 探索策略
这种方法牺牲了部分效率,现在也渐渐被淘汰

方法3:Load Balancing Loss(负载均衡损失)
假如采用 Top-2 路由策略而忽略了所有其他约束条件,一个大问题就是最终一直只选一个专家,那个专家什么都擅长,而其他专家都很糟糕,这就陷入了局部最优(local minimum),所有 token 都被路由到同一个专家
Switch Transformer 提出的负载均衡损失,就是今天几乎所有 MoE 系统的标准配置,它的核心公式可以写成向量内积: 表示分配给专家 i 的实际 token 比例,而
是路由器给专家 i 的期望分配概率,通过最小化这个内积和,把分布向均衡方向推进
反向传播时, 被当做一个常数系数,如果某个专家 i 的
很大,说明它处理很多任务了,那么 Loss 对
的惩罚就很大,模型会降低给它的分配概率
(所以最初可能想要平衡的基本单元是批次,希望每个批次都能均匀地分配给专家)
为什么最小化 就能让分别变得均匀:基于柯西-施瓦茨不等式
举个例子:
情况 A(极度不均衡):专家 1 干了全部活,,
,而专家 2 什么都没干,
,
,则
=
情况 A(完美均衡):专家 1 干了全部活,,
,而专家 2 什么都没干,
,
,则
=

DeepSeek v2
第一个和 Switch Transformer 一样,平衡单元都是批次
第二个是在专家之间进行平衡,考虑到专家分片部署在不同的 GPU 设备上的负载均衡问题,通过度量不同设备上的 token 分配,来优化路由器函数
损失函数形式是都是点积,但不是汇总哪些 token 去了哪些专家,而是衡量哪些 token 去了哪些设备( 改不了,现在是针对设备组而不是每个专家来衡量的)

DeepSeek v3
他们取消了针对每个专家的平衡项,取而代之的是路由 softmax 分数 + 一个动态调整因子 ,这个因子通过在线学习不断更新,如果某个专家分配的 token 过少,就提高它的
,反之降低
这种"无辅助损失平衡"让模型能够自适应调整分配比例,避免显示的正则项
不过在论文的细节里可以看到,他们最终还是加回了一种"互补序列级辅助损失",用于保证单序列内部的平衡,说明完全摆脱辅助损失并不现实

System parallelism
解决了专家路由和训练平衡的问题后,我们还需转向另一个重要的方面:系统并行性
MoE 模型的最大优势在于其稀疏性,但是也带来了复杂的通信与调度问题,一个批次的输入序列往往会被分配到不同的专家,而这些专家又分布在不同的 GPU 上,这就导致了跨设备的 all-to-all 通信(如果通信优化不足,路由带来的稀疏计算优势就会被昂贵的 all-to-all 通信抵消)

为此,DeepSeek v3 在并行性设计中引入了"异构专家并行",即在不同的设备上部署不同数量和类型的专家,从而实现更高的灵活性;他们还设计了"流水并行中的分组 all-to-all",通过将批次分割成小块并逐步路由,减少了单次通信的压力
在硬件层面,他们还引入了 NVLink 拓扑感知的专家分布,使得同一组设备内部通信优先,跨机通信尽量减少,从而显著降低延迟

Stability
MoE 有这样一个特性:如果尝试微调它们,有时它们就会崩溃,所以需要稳定 MoE 的训练过程
因为路由器函数的计算通常是通过 softmax 完成的,而 softmax 几乎总是数值稳定性的隐患所在,为了避免问题,MoE 的路由计算通常使用 float32 精度,而不是更快的 float16
此外,还引入了一种额外的正则化方法 Z-loss(上节课内容),Z-loss 的定义是对 softmax 归一化因子的和()取对数,然后平方,并把它作为额外的损失项加入目标函数,目的是把归一化因子的值约束在 1 附近,从而避免指数项在训练中失控

如果移除了 Z-loss,会看到验证损失出现巨大的尖峰,模型会在几次迭代中有点不稳定

Fine-tuning
微调也是 MoE 的一大难点,在稀疏模型上应用微调时,出现了严重的过拟合现象,训练损失和验证损失差距大幅拉大,远超稠密模型的正常范围
也就是说 MoE 模型在微调过程中更容易记忆训练数据,而无法泛化
这一问题在 RLHF(基于人类反馈的强化学习)阶段更明显,因为此时训练数据规模较小,而模型参数规模极大,稀疏激活导致的"过度专业化"会进一步加剧过拟合

Upcycling
升级循环:工具包里一个颇为实用的技巧
核心思路:从一个稠密模型出发,把其中的 MLP 模块复制多份,必要时加一点扰动,然后再引入一个全新初始化的路由器,这样就用一个已经训练好的稠密模型初始化了一个 MoE 模型

DeepSeek MoE v1-v2-v3
V1 是一个 160 亿参数,其中 28 亿参数激活的模型,采用"2 个共享专家 + 64 个细粒度专家"的配置,每次激活 4-6 个专家,路由机制为标准的 Top-K,并在训练中增加辅助平衡损失来保证专家和设备之间的负载均衡

V2 的规模已经扩展到 2360 亿参数,其中 210 亿激活,架构图几乎和 V1 一致,唯一的区别是激活专家的数量
为了应对细粒度专家带来的高通信成本,提出了一个巧妙的优化:先在设备层面选择 Top-M,然后在每个设备内为每个 token 选出 Top-K 专家
同时,他们还增加了通信平衡损失,不仅考虑输入端的通信负载,也平衡输出端的开销

V3 参数规模为 6710 亿,其中 370 亿是激活参数,架构依旧延续最初的设计,不过在通信、分布式训练和效率优化方面引入了更多机制
一个关键的观察是,即便激活参数较少,但是 V3 在 MMLU 等指标上依然表现优异,证明了"激活参数"才是决定训练和推理成本的关键指标

V3 在注意力部分有一个巧妙的优化,叫做 MLA(multi-head latent attention,多头隐式注意力),他们没有减少头数,而是将这些头投影到一个更低维的空间,节省了 KV cache,因为只需存储低维的 c(输入 h -> 低维 c -> 投影回 q,k)

而且 FLOPs 运算上,没有引入任何额外的矩阵乘法(把新的矩阵乘法合并到了原有的乘法中)
虽然这样和 RoPE 有些不匹配,但是他们仍有一个解决方案,基本上是在未压缩的维度上应用 RoPE(这点没那么重要)

最后一点是他们在损失函数中做了一个小的修改,叫做 MTP,在那里他们并行地预测多个 token
通常下有输入时,将它们左移一位,这样就在预测未来一个 token
但是在做出预测前,可以取出隐藏状态,把它传递给一个非常轻量的单层 transformer,那个模型就可以预测未来一个 token,所以现在模型在预测未来两个 token

更多推荐
所有评论(0)