type
Post
status
Published
date
Mar 4, 2026
slug
deepseek-001
summary
tags
LLM
AI
category
DeepSeek
icon
password
- DeepSeek内容基础配置
‼️只支持linux、脚本无法在Win或Mac上跑通
‼️Mini模型暂定2B、可选择双卡4090、1T硬盘或4卡4090、2T硬盘
核心库版本 ↓
1 DeepSeekv3架构图解与基本参数配置
根据DeepSeekv3开源架构以及技术报告,我们绘制了DeepSeekv3基本架构图——

整体架构明显是一个类llama的decoder-only架构、具体架构板块如下——
- 1. 输入嵌入层:模型以 ParallelEmbedding(并行嵌入层)作为起点,将输入的 token 映射到高维向量空间。
- 2. 旋转位置编码 (Rotary Positional Encodings, RoPE):使用 RoPE 为输入嵌入添加位置信息,使模型能够有效地捕捉序列中 token 的顺序关系。
- 3. RMSNorm 层归一化:每个注意力层和前馈层前均使用 RMSNorm(均方根归一化)进行规范化。
- 4. 多头注意力机制 (Multi-Head Attention) 与 KV 缓存:这是带有KV缓存加速的多头注意力机制、提供两种缓存策略:
- Naive Cache(简单缓存):适合较短序列或常规场景。
- Absorb Cache(密集缓存):优化内存使用和推理效率,特别适用于长序列处理。
- 5. 前馈网络 (FFN) 和混合专家模型 (MoE):在注意力模块之后,模型在以下两种模块之间进行 互斥选择:
- FFN(前馈网络):使用 SiLU(Sigmoid Linear Unit) 作为激活函数,结合并行计算实现高效特征转换。
- MoE(专家混合模型):通过门控机制动态路由输入到部分专家网络。这种设计在不增加显著计算成本的前提下,提升了模型的参数效率和灵活性。
- 6. 堆叠的 Transformer Blocks:模型由多个 Transformer Blocks 堆叠而成(图中标注为 *N)。每个 Transformer 块包含注意力模块、RMSNorm 层以及 FFN 或 MoE。
- 7. 输出层:堆叠的 Transformer 块输出经过、使用RMSNorm 再次进行规范化、ColumnParallelLinear(列并行线性层)将特征映射到词汇表的概率分布、Softmax 层计算最终的概率分布。
在开源时,DeepSeekv3提供了三种不同规模的模型参数,但只提供了671B模型的权重——
参数名称 | 含义描述 | 16B | 236B | 671B |
vocab_size | 词汇表大小,支持的最大 token 数量 | 102400 | 102400 | 129280 |
dim | 词向量维度,每个 token 的表示维度 | 2048 | 5120 | 7168 |
inter_dim | FFN 的中间层维度 | 10944 | 12288 | 18432 |
moe_inter_dim | MoE 模型中专家的中间层维度 | 1408 | 1536 | 2048 |
n_layers | Transformer 块的层数 | 27 | 60 | 61 |
n_dense_layers | 使用 FFN 的 Transformer 块数量 | 1 | 1 | 3 |
n_heads | 注意力头的数量 | 16 | 128 | 128 |
n_routed_experts | MoE 模型中所有专家的总数量 | 64 | 160 | 256 |
n_shared_experts | MoE 模型中共享专家的数量 | 2 | 2 | 1 |
n_activated_experts | 每个输入激活的专家数量 | 6 | 6 | 8 |
n_expert_groups | 专家分组的数量 | - | 8 | 8 |
n_limited_groups | 路由到的分组数量限制 | - | 3 | 4 |
route_scale | 专家路由得分的缩放因子 | 1.0 | 16.0 | 2.5 |
q_lora_rank | Q 投影的低秩分解维度 | 0 | 1536 | 1536 |
kv_lora_rank | KV 投影的低秩分解维度 | 512 | 512 | 512 |
qk_nope_head_dim | 无位置嵌入的 Query/Key 头维度 | 128 | 128 | 128 |
qk_rope_head_dim | 使用旋转位置编码的 Query/Key 头维度 | 64 | 64 | 64 |
v_head_dim | Value 投影的头维度 | 128 | 128 | 128 |
mscale | RoPE 编码的缩放因子,用于扩展序列长度 | 0.707 | - | - |
score_func | 专家路由的打分函数类型(如 softmax/sigmoid) | - | - | sigmoid |
dtype | 数据类型(如 bf16、fp8) | - | - | fp8 |
在整个模型代码的最初进行参数配置可以使关键超参数显而易见,方便开发者快速了解模型的整体设置,而无需额外打开配置文件。这种方式对于调试或学习模型代码尤为重要。一些参数(如
world_size、rank 等分布式相关配置)需要与具体代码逻辑或硬件环境动态结合,因此直接在代码中定义更加直观,也能避免额外的动态加载过程。‼️本次课程中我们讲解的是
deepseekv3_model.py模型脚本、在原本开源的脚本基础上2 MLA潜在注意力机制与低秩KV缓存
潜在注意力机制(Multi-head Latent Attention)是在原始多头注意力机制的KV缓存上改进得到的全新注意力机制。KV缓存是在传统注意力机制的推理过程中用于推理加速的一种机制,它的核心哲学是用内存换效率,它会保存推理时需要的中间变量、从而为推理过程加速。
2.1 经典的KV缓存
在经典注意力机制中,我们所使用的预测方式是自回归预测——
seq = 蒹葭苍苍
t=0、将"蒹葭苍苍"输入解码器、输出新token0。
t=1、将"蒹葭苍苍token0"输入解码器、输出新的token1。
t=2、将"蒹葭苍苍token0token1"输入解码器、输出新的token2。
……
这是一个按顺序执行的循环流程、必须完成上一步之后才能执行下一步,因此是一个很缓慢的过程。大语言模型正式通过这样“一个字、一个字地输出”的流程为我们逐步生成一段话。
在每次将一个序列输入解码器时,这个序列都需要经过注意力机制的拆解、并最终输出全新的token。注意力机制是通过计算单词之间两两相关性来理解信息的计算方式,其中的公式是——
其中:
- :查询矩阵,构成相关性计算中的主动发起乘法的词向量
- :键矩阵,构成相关性计算中的被乘的词向量
- :值矩阵,用于携带单词本身的语义信息
- :词向量的维度
- 是缩放因子,用于稳定梯度
如公式所示,每个序列进入注意力机制后,都会有自己的Q、K、V矩阵,其中Q与K矩阵一般呈现如下结构——
其中, 表示 Query 矩阵中第 行、第 列的元素。同理,一个(n,d)结构的K矩阵如下—
假设现在Q是4行3列、K.T是3行4列,二者相乘、我们会得到——
加上前瞻掩码、使用更简化的写法,你会发现脚标是这样构成的:
现在让我们回到自回归的流程中——
- t=0、将"蒹葭苍苍"输入解码器、输出新token0。
先将序列seq经过线性层转化为序列的Q、K、V,然后求解——
- t=1、将"蒹葭苍苍token0"输入解码器、输出新的token1。
先将序列seq经过线性层转化为序列的Q、K、V(相比上次带了一个新的token1),然后求解——
- t=2、将"蒹葭苍苍token0token1"输入解码器、输出新的token2。
先将序列seq经过线性层转化为序列的Q、K、V(相比上次带了一个新的token2),然后求解——
你发现了吗?事实上在每次推理时,QK.T矩阵只是比上一次多了一行,而这全新的一行是由新token 的Q和历史的K组成的!因此不难发现,在进行推理时、与其每次都使用线性层对完整的序列进行转化、不如直接保留历史的K和V供后续的计算。保存历史的K和V之后、在每个时间步中就只需要为K和V增加最新的Token即可、无需再重新进行完整的线性计算。
如果没有 KV 缓存,我们每次都要计算:
- Q, K, V
从
X 线性变换得到:- 为QK加上位置编码
3. 计算完整的 QK.T
如果缓存了 K/V,我们不需要每次都计算 K/V 了,而是:
- 仅仅计算Q、以及新的token的K和V
其中仅包含当前新加入的 token(通常是 1 个 token),因此和计算成本比全部的K和V矩阵的计算成本小得多。
- 为QK加上位置编码
- 只需要对Knew做位置编码,之前缓存的K矩阵是带位置编码的。
- 拼接已有的 KV 缓存:
- 和 直接从缓存中取出,无需重新计算。
- 计算完整的 QK.T
因此、对传统的KV缓存来说,我们的代码有如下流程 ↓ (以llama为例子)
- 进一步探讨:为什么只缓存KV不缓存Q?
在进行推理时,由于我们只需要不断预测下一个token、因此我们事实上只需要QK.T矩阵的最后一行。
如果我们让注意力机制转变为让最新的一行输出、而忽略其他历史信息,因此实际上我们可以只计算——
- 新token的Q, K, V——
从 `X` 线性变换得到:
其中仅包含当前新加入的 token(通常是 1 个 token)。
- 为QK加上位置编码
- 只需要对做位置编码,之前缓存的K矩阵是带位置编码的。
- 拼接已有的 KV 缓存
- 和 直接从缓存中取出,无需重新计算。
- 计算单一token的QK.T
在我们的推理流程中,我们也会去实现类似的代码 ↓
2.2 低秩KV缓存与代码实现
KV缓存是典型的“牺牲内存加速运算”、对于不得不使用循环的推理流程来说确实是一个可靠的方案,然而它对内存的占用也是不可忽略的。DeepSeek所提出的“潜在注意力机制”就是尝试降低KV缓存所需的内存、同时也不损失太多精度的方案。
有哪些降低所需存储空间的基本方法呢?对于一个矩阵来说,要降低其所需的存储空间,要么就要缩小其尺寸压缩其信息、要么就降低其精度同时压缩其信息。对于潜在注意力机制而言,它选择的是“缩小尺寸、压缩信息”的方案,简单来说就是“先缩小再放大、中间损失忽略不计”的基本逻辑。
首先我们来看DeepSeek对K矩阵和V矩阵执行的操作——
- 1. 使用低秩投影(Low-rank Projection)让原始的X序列矩阵变成小尺寸矩阵C、将带有原始信息的小尺寸矩阵存储起来。这个方案不仅将原本需要保存的K、V两个矩阵转化为了一个携带原始信息的矩阵C,还降低了需要存储的矩阵的维度,因此大幅降低存储所需的内存。
- 2. 在原始的大尺寸X矩阵上计算位置编码、并将该大尺寸位置编码存储起来,但由于位置编码只需要“薄薄的表单”就可以存储,因此几乎不占用内存。
- 3. 在需要计算的时候、再从小尺寸矩阵C分别投影回大尺寸矩阵K和V,将大尺寸K结合位置编码后、基于大尺寸的K和V进行注意力计算,这样即可以保留足够的信息量、又可以存储较小的信息。
这个过程就是低秩KV缓存,具体来看 ↓

从数学公式来看,就是如下流程——

在这个过程中,低秩转换实际上就是线性投影,将原本
(seq_len, 2048)结构的矩阵通过线性层投影为(seq_len, 512)维的矩阵C就可以存储起来。同时,用于保存位置编码的矩阵可能只有(seq_len, 64)尺寸大小,这样需要保存的矩阵就比原来需要的 2 x (seq_len, 2048)要小得多了,比原来节省了75%的存储成本。并且,由于C变小、因此GPU带宽压也会变得更小,在数据交换过程中变得更加容易。对于Q矩阵,DeepSeek也支持完成低秩处理——
- 1. 使用低秩投影(Low-rank Projection)让原始的X序列矩阵变成小尺寸矩阵C。不过并不保存任何的中间变量。
- 2. 在原始的大尺寸X矩阵上计算位置编码,但同样的,不保存任何变量。
- 3. 在需要计算的时候、再从小尺寸矩阵C投影回大尺寸的Q,将大尺寸的Q结合位置编码、再用大尺寸的Q进行注意力计算。
不难发现,这个过程其实和K的处理过程一模一样,因此其数学过程也是 ↓

在训练过程中、如果我们对Q也执行低秩转换、可以帮助我们减少少许训练中所需的激活值内存。一般来说,激活值(Activation)是指向前传播中计算出的中间结果,这些指在反向传播时被用于梯度计算,这些激活值包括了——
- 线性层的中间输出
- 注意力计算时的
Q、K、V矩阵
- softmax 计算出的注意力分数
- 反向传播需要用到的梯度信息
对于大模型来说,激活值占用了训练过程中相当大的一部分 GPU 内存,特别是在注意力层(self-attention)里,
Q、K、V 矩阵和注意力分数矩阵(大小为 (batch, heads, seq_len, seq_len))都需要存储,导致内存消耗极大。不过在推理过程中,Q做低秩缓存没有太大的意义,因此推理过程中一般没有Q的低秩缓存。
- 具体的代码实现

在这个过程中,位置编码链路和低秩矩阵压缩链路看起来是两条数据流,但实际代码在实现DeepSeek却只使用了一个线性层。我们是规定好低秩压缩的维度、以及用于承载旋转位置编码的矩阵所需要的维度后,使用一个线性层一次性将原始数据X(或中间值h)转换为所需的矩阵,再将两个矩阵分开做各自的运算。
3 DeepSeekMOE
门控是一种经典的深度学习机制。许多时候,我们使用线性层来“解读”原始信息,在原始信息上乘以一个常数、以常数的大小控制原始信息向下一层的流通,这被称为“门控机制”,控制常数的层被称之为“门”。
当我们有多个线性层对信息进行不同的“解读”,同时又赋予每个信息对应的门时,这样的模型被称为混合专家模型——
当我们使用多个门同时控制信息的流通时,这些多个门构成的结构被称为是路由器。如图所示,混合专家模型 (MoE) 是一种动态路由策略,通过为不同的输入选择不同的子模型(专家模型)进行计算。相比于传统的全连接前馈网络(FFN),MoE 在每次前向传播时只激活部分专家模型,从而实现参数高效和计算高效。
MoE 已经广泛应用于大规模自然语言处理模型(如 GShard、Switch Transformer 等),并在计算机视觉等领域表现出色。其核心优势在于:模型具有超大参数量,但每次推理或训练时,只使用部分参数来进行计算。这是为什么针对DeepSeek这样的模型,我们会有全量参数和激活参数的区别。


3.1 经典MOE模型速览
在混合专家模型的世界中、我们可以针对一个序列、一张表单或任意单独的一段信息设置一扇“门”(一个权重),我们也可以针对每一个token设置一个权重,大部分用于大模型的MOE使用的是token-level的路由设置。当我们将文字序列输入MOE时,常规的数据流如下所示 ↓

当上述流程转换为数学公式时,我们则可以有如下的表示 ↓
假设:
- 假设输入向量为 , 是 token
t经过注意力机制后的隐藏状态(图中的数据X是完整的携带所有token的矩阵,则是一个token的向量)
- 共有
N个专家,每个专家都是一个带有门控的前馈网络(SwiGLU FFN),也就是至少会含有3个线性层。
- 门控网络(路由器)是一个线性层,其结构是(dim, n_experts)。
- 只有
K个专家会被选择(Top-K 机制)。
根据上述假设,混合专家模型的一般流程为:
- 1. 通过门控计算专家的权重分数
- 将输入门控网络,通过门控网络(Gating Network)计算 token 对每个专家的偏好:
- 其中 是门控网络的权重矩阵,i则代表特定的专家。
- 2. 选出 Top-K 最高分的专家
- 选择分数最高的
K个专家,并将其他专家的权重通过掩码设为0: - 这样可以确保每个 token 只经过
K个专家。
- 3. 计算被选中的专家输出
- 令 表示第
i个专家的前馈网络: - 这里 是 被输入专家
i的输出结果。 - 作为权重调整不同专家的贡献。
- 作为当前层 MoE 的输出,将传递到 Transformer 的下一层。
3.2 DeepSeekMOE的基本流程
DeepSeekMoE(DeepSeek Mixture of Experts)是一种改进的 MoE(Mixture of Experts)架构,它从GShared等已经优化过的MoE架构上改造而来,又做出了许多细致的优化。从今天的眼光来看,DeepSeekMoE流程与常规MoE的区别包括以下的3点——
1. 引入了共享专家(Shared Experts)机制:为模型增加了固定必须要通过的专家、以确保MoE模型的学习能力。
2. 进行了更细粒度的专家划分(Fine-Grained Experts Segmentation):具体地来说——
• 将每个专家的FFN隐藏层维度减少为原始大小的1/m倍,实现更小、更细粒度的专家 • 专家变得更小后,每个专家的计算能力会大幅下降,因此为了补偿专家能力的下降、提出了“分组”技术,以前由TopK机制选择K个专家,现在则是选择K组专家,允许每组中存在m个小专家,一组专家被赋予一个权重 • 这样就大幅增加了专家数量、但涉及的总参数量和计算量不变,相当于将大的专家拆分成更多的小专家,同时也允许更多的小专家被激活,这样保证了计算成本一致,同时也增强了模型在处理多样化知识时的能力。 • 出自论文《DeepSeekMOE:Towards Utimate Expert Specialization in MoE language Models》[https://arxiv.org/abs/2401.06066]
3. 将原始的softmax函数修改为Sigmoid函数:让各个专家之间的计算变得独立、更便于进行并行计算,同时让专家之间的概率不再相互影响之后,softmax带来的概率可能是更接近“1高多0”的情况,而sigmoid带来的概率会更加显著(有效的专家与无效的专家之间的差异更加明显),因此更有可能进一步让更多的专家同时被激活,避免了 Softmax 归一化导致的极端情况(即过度偏向某几个专家)。

当这些改变加在数学流程上时,DeepSeekMoE流程可以呈现为——
假设:
- 输入向量 为 token
t经过注意力机制后的隐藏状态。
- 每个专家都是一个带有门控的前馈网络(SwiGLU FFN),也就是至少会含有3个线性层。
- 门控网络(路由器)是一个线性层,其结构是(dim, n_experts)。
- 只有
K个专家会被选择(Top-K 机制)。
=========
1. 计算 经过共享专家(Shared Experts)的输出
- 普通 MoE 只有动态选择的专家routed_experts,但 DeepSeekMoE 额外增加 个共享专家()。
- 所有 token 都必须经过这 个共享专家,确保基本特征转换。
在任意token输入所有共享专家后、无论有多少个共享专家,都对所有共享专家的结果进行加和,形成当前token在共享专家上的输出结果 ↓
- 这里 表示第
i个共享专家,它们是固定启用的,不依赖 token 选择。
=========
2. 通过门控计算专家与输入向量的亲合度分数
- 将输入门控网络,通过门控网络(Gating Network)计算 token 对每个专家的亲合度分数。不同于普通 MoE 采用 Softmax 计算专家分配权重,DeepSeekMoE 采用 Sigmoid 计算专家匹配度——
- 其中 是 专家
i的中心向量(centroid vector),用于衡量 与专家i的匹配度。不难发现,事实上的功能等同于门控网络的权重向量,实际我们在实现这一步骤计算时,也是使用线性层进行转换。
=========
3. 选出 个路由专家
- 选择分数最高的 个专家,其余专家的得分设为
0。
- 这确保每个 token 最多只经过 个专家,减少计算量。
=========
4. 归一化选中的专家权重
选中的 个专家需要 进行归一化,以确保不同 token 选择的专家权重总和为 1:
- 归一化后的 作为最终的专家权重。
=========
5. 计算 经过路由专家(Routed Experts)的输出 只计算 被选中的 个专家:FFN(r)i(ut)
- 这里 是 路由专家
i,只有 选出的专家会执行前馈计算。
=========
6. 路由专家(Routed Experts)结果与路由专家权重进行加权求和 只计算 被选中的 个专家:
- 作为该专家的权重,与专家输出的结果进行加权求和。
=========
7. 计算最终输出 综合共享专家和路由专家的计算结果:
- 第一项 :架构中的残差链接。
- 第二项 :所有 token 共享专家的计算结果。
- 第三项 :根据 选择的路由专家计算结果。
不记得残差连接?如下所示 ↓

所以你不难发现,DeepSeekv3中的MoE实际的数学路程与论文中所呈现的顺序相反——

3.3 免负载均衡的DeepSeekMOE
DeepSeekMoE是DeepSeek公司一项重要的创新、但DeepSeekv3架构所使用的是DeepSeekMoE基础上改进的架构,即免负载均衡的DeepSeekMoE。
负载不均衡是传统的混合专家(MoE)模型在处理专家负载时面临着一个重要问题,由于 MoE 模型根据输入的特征选择不同的专家进行计算,通常会出现某些专家频繁被激活,而其他专家几乎不参与计算的情况。这种不均衡的负载会导致部分专家的计算资源被过度消耗,而其他专家则未被充分利用,进而影响整体计算效率和模型性能。为了缓解这一问题,许多传统方法采用辅助损失(auxiliary loss)来强制平衡各个专家的负载,通过增加额外的损失项来惩罚负载不均衡。然而,过大的辅助损失往往会干扰模型的学习过程,导致模型性能下降。尽管这些方法可以在一定程度上减轻负载不均衡,但它们仍然面临着调整损失权重的难题,且可能影响模型的效果。
为了在负载平衡和模型性能之间取得更好的折中,DeepSeekv3开创了一种无辅助损失的负载平衡策略。具体来说,他们为每个专家引入了一个偏置项 ,并将其加到相应的亲和度得分(也就是我们之前总提到的每个专家的权重) 上来帮助确定专家是否被激活。
经过上述计算后,如果亲合度得分在 top-K 所规定的范围内,那该专家被激活,而且具体的激活值为,否则激活值为 0。

除此之外,在论文中还特别提到,DeepSeekv3会在训练过程中动态调整每个专家的偏置项 bi,以保证负载平衡:
- 如果某个专家的负载过重(即该专家被激活过多),偏置项会减小,这样该专家的亲和度得分会降低,减少其被激活的概率。
- 如果某个专家的负载过轻,偏置项会增加,增加该专家被激活的概率。
- 这个动态调整过程通过一个超参数 γ 来控制偏置项的更新速度。
但事实上,在后来的架构实现中、DeepSeekv3采取的策略是:如果用户加载的是671B的DeepSeekv3模型、则直接让bias作为模型超参数、跟着损失函数一起更新、如果用户加载的是比671B更小的模型、则直接不设置bias选项。但无论如何,在开源代码中都没有设置用于更新bias的参数γ。在后续的开源的config中、也没有对γ进行设置。
【头脑风暴】DeepSeek为什么不对这部分进行开源?如果我们要自定义gamma相关的代码、最难的点在哪里?
以下是头脑风暴的伪代码——
3.4 补充辅助损失
尽管 DeepSeek-V3 主要依赖于无辅助损失的负载平衡策略,但为了防止在单个序列中出现极端的负载不平衡,模型还是引入了序列级别的负载平衡损失。这意味着,对于每一个输入序列,模型会对该序列中的每个专家的负载进行平衡、并且该流程会影响DeepSeekv3模型的预训练过程。
任何负载均衡方向的辅助损失都会从两个不同的角度衡量专家i的负载情况,分别是——
- (论文中的第 20 个公式): 这是专家 i 在整个序列中的负载平衡指标,它是所有时间步 t 上归一化激活值的平均值。该指标用于衡量每个专家的负载是否平衡。
其中,(第 19 式) 是对专家负载的归一化处理,将每个专家在特定时间步 t 的激活值 除以该时间步所有专家的总激活值。这是为了避免某个专家的过高激活值影响到负载的判断。
- (论文中的第 18 个公式): 对每个专家 i,计算其负载平衡指标。具体来说,首先计算专家 i 在每个时间步中的激活情况(即0,1表示,使用
Top-k路由选择,表示每个时间步选择激活最强的 k 个专家)。然后,根据这些激活情况,计算专家 i 在序列中的激活比例。
最终的辅助损失为——
- (第 17 式): 这是负载平衡的总损失,定义为所有专家负载平衡的加权和。这里的加权系数由 平衡因子 α 控制,该因子通常是一个非常小的值,确保负载平衡不会主导训练过程。
其中,平衡因子 α 是一个超参数,在 DeepSeek-V3 中将赋予一个非常小的值;1(⋅) 表示指示函数;而 T 表示序列中的标记数量。序列级别的负载平衡损失鼓励每个序列中的专家负载保持平衡。当负载不均衡时,这个辅助损失将变得更大。
【深度】为什么这里让两个因子相乘就能够代表负载的均衡?
3.5 DeepSeekMoE的专家并行
专家并行机制是 Mixture-of-Experts (MoE) 模型在大规模分布式计算中的关键技术,旨在通过将模型的多个专家分布在不同的计算节点或设备上,来实现高效的计算和负载平衡。在大规模 MoE 模型中,模型的参数量非常庞大,且每个输入数据仅激活部分专家。如果每个设备都计算所有专家,计算量和内存消耗将急剧增加。专家并行机制的目标是将专家分配到多个设备上,只有特定的设备负责处理某些专家,从而达到:
- 负载均衡:不同的设备处理不同的专家,避免某个设备负载过重。
- 计算效率:通过并行计算,提高计算效率。
- 内存管理:每个设备只需要加载和处理自己负责的专家,降低内存消耗。
核心问题1——如何分配专家到不同的设备呢?
- 本地专家:在分布式设置下,每张GPU只负责一部分专家。这些专家被称为 本地专家。例如,假设有 1000 个专家,如果系统有 10 个进程(即
world_size = 10),每个节点将负责 100 个专家。这种方式通过分配专家来确保每个设备的计算负载均衡。
- 专家的起始和结束索引:
- 每个设备负责一部分专家,这部分专家通过两个索引进行标识:
experts_start_idx和experts_end_idx。 experts_start_idx表示当前设备上负责的第一位专家的索引,experts_end_idx表示最后一位专家的索引。
例如,如果
n_routed_experts = 1000 且 world_size = 10,则每张GPU负责 100 个专家。假设GPU的 rank = 0,则该GPU负责的专家索引为 0 到 99(即从第 1 个专家到第 100 个专家)。- 局部专家的路由计算:每个设备仅会参与计算它所负责的专家的输出。路由机制会确保每个输入数据仅由选定的 top-k 专家 来处理。因此,专家并行机制 基于数据路由而不需要每个设备计算所有专家,从而有效减少了计算量。
从代码的角度来说,每张GPU只加载和计算它所负责的 本地专家。这通过以下代码实现:
self.experts_start_idx = rank * self.n_local_experts
self.experts_end_idx = self.experts_start_idx + self.n_local_expertsrank表示当前GPU的标识符(例如,在分布式训练中,rank表示当前节点的编号)。
n_local_experts是每张GPU上负责的专家数量。通过rank和n_local_experts计算当前节点负责的专家范围。
核心问题2——如何分配不同的token到不同的专家上呢?
在前向传播过程中,每张GPU只负责计算它所负责的专家的输出,其中
self.experts是专家的列表,x是输入的数据,weights是经过topk筛选出的权重。counts[i]记录了每个专家的激活次数,如果某个专家没有被激活,则跳过该专家。
- 只计算当前GPU所负责的专家(由
self.experts_start_idx和self.experts_end_idx确定的范围内)。
- 我们会从所有的序列中索引出当前专家负责的token,然后将token输入到指定的专家中进行计算。
torch.where 用于根据选定的专家索引来选择输入数据的相应部分。具体的代码片段是:indices是一个张量,表示每个输入样本对应的 专家索引。
i是当前专家的索引。
indices == i生成一个布尔条件张量,表示哪些样本被路由到了专家i。
torch.where(indices == i)返回两个张量:idx:表示满足条件indices == i的样本的索引。top:这些满足条件的样本的位置索引。
在这段代码中,
torch.where 用于 筛选出 路由到特定专家 i 的输入数据样本,并将它们的索引存储在 idx 和 top 变量中。在分布式训练中,每个节点只计算它所负责的部分专家的输出。为了确保各个节点计算的结果一致,我们需要对结果进行 同步:
3.6 DeepSeekMoE的全套实现代码
在我们借助代码实现DeepSeekMOE的过程中,我们同样采取大模型脚本编写中惯例的模块化代码编写方式、先编写MoE所需的各个模块、再写一个串联起所有元素的类用来实现DeepSeekMoE的全局。
- 分布式SwiGLU FFN作为共享专家层

📎 参考文章
- 一些引用
- 引用文章
欢迎您在底部评论区留言,一起交流~
Loading...

