【MoE】【DeepSeekMoE-v1】【1】彻底搞懂核心代码与公式

前言

由于近期需要改DeepSeekMoE的结构,因此没办法,还是要手搓源码,顺便记录一下笔记,会对一些结构的形状推导、函数用法以及论文公式推导(包括这些公式对应哪些代码)进行详细解释

待讲完DeepSeekMoE核心代码之后,如果有机会的话会讲一下怎么修改结构并进行训练的(不过这部分出教程概率不高,因为对模型架构底层源码的理解要求太高了。。)

由于本文对每一行代码剖析的特别细致,难免篇幅过长,且本文结合了很多大佬的精华,即便没有任何基础的AI初学者也能够看懂!

本文仅讲解MoEGate内容,后续内容按照篇幅大小进行划分,至少一篇都会讲一个重要的部分

其实原本想把所有内容都囊括进来的,但可能是讲的太细,导致光是讲Gate就花了快5k字,无奈只能分章节。

DeepSeekMoE核心代码地址

来自于huggingface/modules/transformers_modules/deepseek-moe-16b-base/modeling_deepseek.py,可以使用下面的代码进行学习:modeling_deepseek.py · deepseek-ai/deepseek-moe-16b-base at main

由于该代码还有很多关于分布式的内容,鉴于篇幅所限,且本文会讲的特别细致(可能涉及演算步骤),而源代码长达1600行,如果都进行解读可能会导致篇幅超过5w字(尤其是注意力那部分超级麻烦),因此本文仅对DeepSeekMoE的核心架构代码进行解读,但即便如此,也已经超过5k字了,其中删减了很多冗余的内容,尽可能让文章更加简洁易懂。

代码结构

configuration_deepseek.py

configuration_deepseek.py 是 DeepSeek 模型的配置类文件,定义了模型的所有超参数和配置选项。它基于 Hugging Face 的 PretrainedConfig 类,通过 DeepseekConfig 类为模型提供了灵活的配置能力。主要作用包括:

  • 参数定义:包括词汇表大小 (vocab_size)、隐藏层大小 (hidden_size)、注意力头数 (num_attention_heads)、MoE 层相关参数(如 n_routed_experts 和 num_experts_per_tok)等。
  • 模型实例化支持:通过配置对象实例化 DeepSeek 模型,确保模型结构和行为符合预期。
  • 灵活性:允许用户根据任务需求调整模型架构,例如是否使用 MoE 层、RoPE 缩放策略等。
modeling_deepseek.py

modeling_deepseek.py 是 DeepSeek 模型的核心实现文件,包含模型的具体架构和前向传播逻辑。它基于 PyTorch 和 Hugging Face 的 Transformers 库,实现了从嵌入层到输出层的完整 Transformer 模型。主要作用包括:

  • 模型组件实现:包括归一化层、注意力机制、MLP、MoE 层等核心模块。
  • 模型构建:根据 DeepseekConfig 中的参数构建完整的模型结构,支持因果语言建模和序列分类任务。
  • 功能支持:支持多种注意力实现(如 Flash Attention、SDPA)、缓存机制和梯度检查点等特性。
两者关系
  • configuration_deepseek.py 提供蓝图:它定义了模型的超参数和架构细节,作为输入传递给 modeling_deepseek.py。
  • modeling_deepseek.py 实现具体逻辑:根据配置类中的参数,实例化和运行模型,确保配置与实现一致。
  • 依赖性:modeling_deepseek.py 中的类(如 DeepseekModel、DeepseekMoE 等)直接引用 DeepseekConfig 对象来初始化参数和行为。

导入模型及检查配置

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig

model_name = "/mnt/public/weights/deepseek-moe-16b-chat"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True)
model.generation_config = GenerationConfig.from_pretrained(model_name)
model.generation_config.pad_token_id = model.generation_config.eos_token_id

# 打印模型配置
print("Number of experts per token:", model.config.num_experts_per_tok)
print("n_routed_experts:", model.config.n_routed_experts)
print("scoring_func:", model.config.scoring_func)
print("aux_loss_alpha:", model.config.aux_loss_alpha)
print("seq_aux:", model.config.seq_aux)
print("norm_topk_prob:", model.config.norm_topk_prob)
print("hidden_size:", model.config.hidden_size)

text = "who are you"
inputs = tokenizer(text, return_tensors="pt")
outputs = model.generate(**inputs.to(model.device), max_new_tokens=100)

result = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(result)

image.png

可以看到这里的Number of experts per token为6,与configuration_deepseek.py中的默认值是不一致的,如果需要进行配置查看可以修改上述代码。

Preliminaries :Mixture-of-Experts for Transformers

本节会讲一下MoE最基本的假设和公式,该部分是后续公式推导的基础,是MoE的基础,DeepSeekMoE是在该公式的基础上进行的。

——同时为了本文论证的有效性,会附上论文对应公式截图进行讲解

arxiv.org/pdf/2401.06066:DeepSeekMoE v1主要提出在MoE基础上添加专家细粒度切分和共享专家、专家级和设备级负载均衡优化,本文的MoEGate暂时还没涉及该论文的创新点,但用到了其基础假设即Preliminaries

Transformers

image.png

这个公式是在没有使用MoE架构的时候,即使用Transformers中自注意力机制,里面使用稠密FFN时的数学表达式。

变量说明在截图中英文部分已经说的很清楚了,此处为了讲解就不再赘述了

注意这个公式缺少了层归一化的操作,实际上是会有层归一化的,此处只是为了简便书写

arxiv.org/pdf/2101.03961 Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity 中告诉了层归一化的公式说明:

image.png

其实公式1跟下一小节中的公式5是差不多的,只是符号不一样,然后公式2跟下面的公式3一样,即 T 个专家的加权输出。只是下面讲的更加细

image.png

注意:公式 2 中的门值 pi(x)pi​(x) 允许路由器的可微性。不过该文居然没有讲解怎么证明可微性的,有点可惜,感觉这个证明十分重要

基础MoE架构公式-代码对应关系

image.png

注意这个公式缺少了层归一化的操作,实际上是会有层归一化的,此处只是为了简便书写

可以看到Transformers和MoE在htlhtl​ 方面是不一样的,而MoE多了gi,tgi,t​ ,使计算htlhtl​时更加稀疏了。

这三个公式共同构成了MoE (Mixture of Experts) 的核心机制:

  1. 计算每个token与专家的相似度(公式5)
  2. 基于相似度选择top-k个专家(公式4)
  3. 将选中的专家输出加权组合(公式3)

这样可以让每个token动态地选择最相关的专家进行处理,提高模型的效率和性能。

由于本文对MoEGate的代码进行了详细的解释,而这一部分则是MoE公式的基石部分

代码在本文的MoEGate章节中,本文会用该代码所在行数和内容来指代,不懂的话可以翻阅下文的代码找到对应位置

注意到,对应的数学变量如下:

  • 这里的htlhtl​ 其实是第ll层中第tt 个token经过MoE架构的输出,而utlutl​ 则是该token。

  • gi,tgi,t​是该 utlutl​ token 前K个专家亲和度分数最高的值

  • si,tsi,t​ 是第i个专家与该token的亲和度分数

公式5

image.png

logits = F.linear(hidden_states, self.weight, None)
if self.scoring_func == 'softmax':
    scores = logits.softmax(dim=-1)

解释:这段代码实现了公式5中的相似度计算。通过线性变换计算某个token utlutl​ (即hidden_states)与专家权重 eileil​ 的内积,然后通过softmax函数得到最终的相似度分数si,tsi,t​。这里的 self.weight 对应公式中的 eileil​。

公式4

image.png

对应下文MoEGate代码中的第27-33行

logits = F.linear(hidden_states, self.weight, None)
if self.scoring_func == 'softmax':
    scores = logits.softmax(dim=-1)
topk_weight, topk_idx = torch.topk(scores, k=self.top_k, dim=-1, sorted=False)

解释:这段代码实现了公式4中的门控机制。首先计算每个token与每个专家的相似度得分,然后通过softmax归一化得到 sj,tsj,t​。接着使用 topk 操作选择最高的K个专家,对应公式中的 Topk({sj,t∣1≤j≤N},K)Topk({sj,t​∣1≤j≤N},K),即上面第4行就是si,tsi,t​ 。

如果不在top-k中,则权重为0:该代码在DeepSeekMoE类中,首先会经MoEGate的forward函数传递topk_idx, topk_weight (对应下面MoEGate类第62行代码)给DeepSeekMoE类:

        return topk_idx, topk_weight, aux_loss  # 返回专家索引、权重和辅助损失

然后在DeepseekMoE类中进行接收:

topk_idx, topk_weight, aux_loss = self.gate(hidden_states)  # 通过门控获取专家索引、权重和辅助损失 topk_idx, topk_weight的形状为 [bsz * seq_len, topk] ,aux_loss为一个标量

topk_idx和topk_weight被用作索引专家,并进行专家加权融合

aux_loss则被用于添加到AddAuxiliaryLoss中,使辅助负载均衡损失能够通过后向传播进行更新,使其成为可优化的参数

y = AddAuxiliaryLoss.apply(y, aux_loss)  # 添加辅助损失到计算图
公式3

image.png

该代码在DeepSeekMoE类中,因为该公式是整合所有MoE层的结果,所以在MoEGate中没有体现,DeepSeekMoE类会在下一节详细讲解

训练模式

下面代码计算了FFNi(utl)FFNi​(utl​) 这一项:

y = torch.empty_like(hidden_states)
for i, expert in enumerate(self.experts):
    y[flat_topk_idx == i] = expert(hidden_states[flat_topk_idx == i])

下面代码计算了∑i=1N(gi,tFFNi(utl))∑i=1N​(gi,t​FFNi​(utl​)) 这一项:

y = (y.view(*topk_weight.shape, -1) * topk_weight.unsqueeze(-1)).sum(dim=1)
推理模式

下面代码计算了∑i=1N(gi,tFFNi(utl))∑i=1N​(gi,t​FFNi​(utl​)) 这一项。

y = self.moe_infer(hidden_states, flat_topk_idx, topk_weight.view(-1, 1)).view(*orig_shape)

解释:这段代码实现了公式3中的混合专家计算。其中 htlhtl​ 是通过对每个选中的专家 FFNiFFNi​ 进行计算,然后与对应的门控权重 gi,tgi,t​ 相乘并求和得到。最后加上输入 uiluil​ 完成残差连接。 下图也说明了将htlhtl​ 和gi,tgi,t​ 相乘的存在:

来自:arxiv.org/pdf/2101.03961 Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity

image.png

辅助负载均衡损失损失公式-代码对应

arxiv.org/pdf/2101.03961:DeepSeekMoE仍然保留了Switch Transformers中的辅助损失部分,本文同样会讲解该部分的代码和公式

动态buffer/专家容量

image.png

可以看到,动态专家容量计算公式如下:

点击【MoE】【DeepSeekMoE-v1】【1】彻底搞懂核心代码与公式查看全文

内容概要:《机器人综合基础实践教程》(入门篇、提高篇)涵盖了机器人基础构建、编程控制、传感器应用等多个方面。教程从机械零件简介入手,逐步介绍主控板和编程环境的配置,随后通过一系列实验引导读者动手实践,包括驱动轮模块、双轮万向车、红外启动小车、带传动模块、履带机器人、红绿灯等实验。这些实验不仅帮助读者理解基本原理,还涉及高级应用如蓝牙电子温度计、语音识别、双轮小车平衡、蓝牙排爆机器人和WiFi视频排爆等。教程旨在培养读者的空间构型能力、编程技巧和综合调试能力,为机器人技术的实际应用打下坚实基础。 适用人群:具备一定编程基础和技术兴趣的学生、教师及爱好者,特别是对机器人技术感兴趣的初学者和中级学习者。 使用场景及目标:①帮助学生理解机器人基本原理,掌握机械零件组装和编程控制;②通过实际操作,提升编程和调试技能;③为机器人竞赛、项目开发和创新实践提供理论和实践指导;④培养创新思维和解决实际问题的能力。 其他说明:教程不仅提供详细的实验步骤和代码示例,还配有丰富的参考资料和光盘课件,确保学习者能够全面理解和掌握知识点。此外,教程强调实践操作的重要性,鼓励学习者通过动手实验加深理解,培养独立思考和解决问题的能力。
### DeepSeek-V3 和 DeepSeek-V1 的区别 #### 架构改进 DeepSeek-V3 基本架构依然基于 Transformer 框架,但在多个方面进行了优化和增强。相比之下,DeepSeek-V1 可能采用较为基础的 Transformer 结构,而 DeepSeek-V3 引入了 MLA (Multi-Level Aggregation) 和 MoE (Mixture of Experts),这些组件已经在 DeepSeek-V2 中得到验证并进一步发展[^1]。 #### 负载均衡策略 特别值得注意的是,在负载平衡方面,DeepSeek-V3 新增了一种无辅助损失的负载平衡策略来缓解因确保负载平衡而导致的性能下降问题。这种策略有助于提升系统的整体效率和稳定性。而在早期版本如 DeepSeek-V1 中可能并未涉及此类高级负载管理机制。 #### 接口兼容性和API支持 对于外部应用而言,从 V1 到 V3 的转变不仅体现在内部算法上的进步上;在对外服务层面也有所体现。例如,DeepSeek API 已经完全适配 OpenAI 兼容的标准接口形式,并允许开发者通过简单的配置调整即可接入最新的 DeepSeek-V3 模型实例。这意味着即使是在原有基础上构建的应用程序也能轻松迁移到新版本而不必担心接口不一致带来的麻烦[^2]。 #### 专家系统进化 另外一个重要变化在于前馈神经网络部分的设计理念更新——即所谓的 MixFFN 或者说混合专家模块。相较于之前版本里相对简单粗放式的处理方式,现在采取更加精细且高效的专家分割方法以及共享专家隔离技术,从而显著提高了各子模型间的协作效能资源利用率[^3]。 综上所述,从 DeepSeek-V1 发展到 DeepSeek-V3 不仅仅是功能特性的增加或改良,更重要的是整个体系结构和技术路线都经历了深刻的变革和发展,使得新版能够在保持良好向后兼容性的同时提供更为强大稳定的服务能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值