DeepSeek MoE:从v1到v3的演进

less than 1 minute read

Published:

MoE(Mixture-of-Expert)是现代大模型的标配结构,其将传统Transformer中的单一FFN层替换为router+多个expert的组合,在推理时为每个token动态分配特定的几个expert,可以在不显著增大推理开销的前提下大幅提升模型的参数量,使得其容纳的知识更多,从而提升模型算法性能。然而MoE的动态路由机制使得其在多卡训练场景下存在负载均衡和集合通信开销等的挑战。DeepSeek系列的模型在架构层面一直致力于在算法和系统层面优化MoE,使之具备愈发成熟的实用价值。这里简单记录DeepSeek MoE从DSv1到DSv3的主要演进。

参考文献:https://zhuanlan.zhihu.com/p/18565423596

目录

DeepSeek之前的MoE

  • GShard

    是最早将MoE应用于transformer的工作之一。

    • 交替堆叠dense block和MoE block,且在多卡情况下引入EP:将各个experts分布到不同卡上,而模型其他部分在各个卡上则是replicate的。

    • 随机路由:每个token都被路由到2个专家处,这top-2个专家中,其中一个肯定为得分最高的专家,而第二个专家是以得分作为概率随机选择的。

    • 专家容量:设定一个阈值,定义一个专家最多能处理多少tokens。如果某个token选取的top-2两个专家容量都哦达到了上线,则该token就会溢出,可以通过残差直接传递到下一层或完全丢弃。设置专家容量的意义在于,所有张量形状在编译时是静态确定的,而我们无法提前知道多少tokens会被分配给各个专家,因此需要一个固定的容量因子。专家容量也可以防止某些专家被过度偏爱。

  • ST-MoE

    ST-MoE训练时采用三种损失:

    \[L=L_{CE}+c_BL_B+c_zL_Z\]

    其中,$L_{CE}$是普通的预训练CELoss,$L_B$是负载均衡辅助损失,$L_Z$是Router Z-loss

    • 负载均衡辅助损失(Auxiliary Load Balance Loss)

      设共有$N$个experts,一个batch $\mathcal B$中有$T$个tokens,$f_i$为本batch分给expert $i$的token的比例,$P_i$为本batch分配给expert $i$的router概率分数比例(也即所有tokens给expert $i$的概率分数之和除以给所有专家的总概率分数之和):

      \[f_i=\frac{1}{T}\sum_{x\in\mathcal B}\mathbb 1_{\{x被路由到专家i\}}\] \[P_i=\frac{1}{T}\sum_{x\in\mathcal B}p_i(x)\]

      则负载均衡损失函数为向量$f,P$的点积:

      \[L_B=N\sum_{i=1}^N f_i\cdot P_i\]

      它之所以能鼓励均匀路由,是因为它在均匀分布下(也即每个专家被分配的token比例都相同、每个专家被分配到的分数也相同)最小化:

      \[\min\sum_{i=1}^N f_i\cdot P_i=\sum_{i=1}^N\frac{1}{N}\cdot\frac{1}{N}=\frac{1}{N}\]

      $L_B$中前边还会乘一项$N$,是使得expert数量$N$不同时保持均匀路由下的loss(也即loss下界)是常数$1$。

    • Router Z-loss

      主要是为了防止router中产生极大的logits,导致数值溢出和softmax饱和等问题,影响训练稳定性。因此设计Router Z-loss来鼓励router产生较小的logits值:

      \[L_Z=\frac{1}{T}\sum_{i=1}^T\left(\log\sum_{j=1}^N e^{x_j^{(i)}}\right)\]

      其中$T$是batch中的token数量,$N$是专家数量,$x_j^{(i)}$为token $i$给专家$j$产生的logits分数。这个loss将每个专家收到的logits做指数后求和然后套上log,然后再对所有token的结果做平均,可以惩罚较大的logits。

DeepSeek-v1(DeepSeek MoE)

  • 细粒度专家分割

    • 用于解决专家数量较少(如8或16个)时的知识混合问题,ds认为专家数量较少时会导致每个专家的知识涵盖都过于冗杂

    • 做法是保持参数量不变的情况下,通过进一步分割FFN中间隐含维度来将专家分割成更细的粒度,且相应地成倍地增加topk中的k的大小,这样在计算成本不变的情况下可以激活更多细粒度的专家,以实现激活专家组合的更高灵活性,且让各个专家拥有更精细化的知识

  • 共享专家隔离

    • 用于解决知识冗余问题,多个专家可能学到重叠的知识,导致专家参数存在冗余,也阻碍了专家的专业化

    • 做法是设置某些共享专家,它们始终被所有token通过,希望它们能够捕获上下文中的共同知识,从而让其他路由专家能够专注于不同方面

  • 专家级负载loss:改进

  • 设备级负载loss

    将专家分为若干组,每组专家放在一个device上,然后引入设备级(专家组级)负载均衡loss,相当于在更粗粒度的专家组级别做负载均衡,从而平衡不同device上的负载,促进设备(算力)负载均衡。

DeepSeek-v2

  • 设备受限的专家路由机制:

    • 避免同一个token激活的专家分布的devices过于离散化,使得一个token需要被分发到过多devices处,导致通讯成本增大。希望保证每个token激活的专家最多分布在M<TopK个device上,来控制通信成本。这是一种促进通信负载均衡的方法。

    • 做法是对于每个token,首先选取其得分最高的那些专家所在的M个devices(例如,得分最高的M个专家分布在不同的M个devices上,则选取这M个devices;如得分最高的M+i个专家中存在某些专家处于同一device,使得这M+i个专家总共分布于M个device上,则选取这M个devices),然后把这M个devices上的所有专家作为备选集合,在它们中选取TopK个得分最高专家,作为该token最终流向的专家。(也即:如果原始的TopK个专家分布的很离散的话,则舍弃其中得分相对较低的那些,并用得分较高的那些专家所在device上的其他专家替代(虽然它们的原始得分不如舍弃的那些专家,但好在它们聚集程度更高))

  • 通信负载均衡loss:

    • 通过设备受限的专家路由机制可以在通信发送端确保分发不过于离散化,减少多扇出的通信量,但在接收侧还有可能出现集中几个设备的专家被激活的问题,导致通信拥堵。

    • 通信负载均衡loss在通信接收端保持接收平衡,鼓励每个device接收等量的tokens。它和设备受限的专家路由机制都是为了促进通信负载均衡。

  • Device级的token丢弃策略

    为了进一步促进设备负载均衡,当batch每经过一个MoE层时,首先算出每个设备平均接收token的数量C,然后对于那些实际接收token数量Td超过平均值C的device,选取这Td个token中给其赋予分数最低的Td-C个tokens丢掉,使得实际通过专家的最多只有C个tokens。(被丢掉的token的hidden state会通过残差来直接传递到下一层)。

DeepSeek-v3

  • 修改门控计算方式:Softmax->Sigmoid:

    dsv3的总专家数量相比于v2进一步大幅增加:

    | | 路由专家总数 | 激活专家数 | 模型总参数 | 激活参数 |

    | —- | ———— | ———- | ———- | ——– |

    | Dsv2 | 160 | 6 | 67B | 21B |

    | Dsv3 | 256 | 8 | 671B | 37B |

    由于softmax存在归一化要求,因此当专家总数过多时,会导致各个专家归一化得分之间的差异变小,也即使得它们的区分度不高。而sigmoid则是直接对每个专家计算出一个[0,1]之间的分数,各个专家的分数都是独立的,并不随专家总数变化而变化,这使得专家得分的值域更大。因此,在路由专家总数较大的情况下,可以采用值域更宽的sigmoid函数来计算专家的得分,提高专家区分度.

  • 无辅助损失负载均衡(Auxiliary-Loss-Free load balancing):

    v1和v2中具有的专家级设备级通信级的负载均衡loss,它们主要是在系统层促进负载均衡,对于模型算法效果没什么帮助,甚至辅助loss种类过多(导致某些loss太大)时还会对主模型算法性能产生损害。为了减轻多辅助loss对主模型的影响,v3中把专家级、设备级、通信级的负载均衡loss都舍弃了(同时也舍弃了设备级token丢弃策略,保留了设备受限的路由机制),通过引入一个可动态调节的bias做负载均衡。

    具体而言:

    • v2及以前的MoE是通过专家的门控权重$s_{i,t}$(token $t$给予专家$i$的得分)来选取token $t$流向的TopK个专家

    • v3进一步对每个专家$i$维护一个可动态调节(可学习)的bias参数 $b_i$,使得token $t$选取TopK个专家的门控权重变为$s_{i,t}+b_i$。

      在训练过程中,每个batch都会统计每个expert收到的token数$c_i$,如果发现某个expert出现过载(具体而言,统计所有expert收到token数量的均值$c$,则收到token数量超过$c$的就算过载专家),则把它的bias值减去一个常数(bias update rate)$u$,从而抑制其门控权重,对于负载不足的专家则将其bias值加上一个$u$,从而提升其门控权重:

      \[e_i=\bar{c}-c_i\]
\[b_i\leftarrow b_i+u*sign(e_i)\]
  • sequence粒度的负载均衡loss

    v3额外添加了一个sequence粒度的负载均衡损失,希望每个sequence中的token都能够均匀地激活各个专家。

    其和v1的专家级辅助loss的区别在于作用粒度不一样:专家级辅助loss作用于整个batch上,而sequence粒度辅助loss作用于单条样本sequence上。其目的在于更细粒度地对于专家激活进行平衡。

总结:

alt text