解读DeepseekV3
本年度还剩几天,Deepseek就发布了这么值得惊喜的产品,我觉得是真正做AI,也喜欢AI同学,对这个魔幻的2024年12月,一定是未来多少年想起都能回忆起这波澜壮阔的岁月。
我见过的最省的GPT4o,Claude,Gemini2级别的模型,而且是国内唯一有机会上桌的,其实海外目前和这三家有资格掰手腕的也只有它自己,可以负责任的说其他的模型不配上桌吃菜(Gemini上个月都没资格)
其实性能好就不说了,DeepseekR1就还不错,国内讲道理是第一家做inference timing scaling law的模型,阿里是第二家,其他的就不太值得谈论了,其实国外也只有Gpt和google有资格,没想到年底还能出个DeepseekV3,在传统scaling law这边还能玩出花活儿,实属不易。
废话不多说,挑干货唠
FP8混合精度训练框架,并首次验证其在超大规模模型上的有效性
它模型下载下来看起来还能行啊,也就不到700G,那正常671B的参数,根本不可能这么点显存占用,因该1.4T左右,这是因为它原生就FP8出的,这大概是我知道的第一个原生之直接出FP8的模型,Meta说很早就用FP8训练了,那Llama3啥的也默认都BF16的,有些特定的模型FP8 GPTQ/AWQ过,像它这么激进的应该是第一个。
省钱
只有2.664M GPU小时的经济成本,完成了对14.8万亿tokens的DeepSeek-V3预训练,预训练后的进一步阶段仅需要0.1M GPU小时,因为FP8吗,自然就省算力,也省显存,还省通信,要不也不能就这么2048张卡就打发了,预训练阶段,DeepSeek-V3在每万亿tokens上的训练仅需180K H800 GPU小时,即以2048 H800 GPUs集群仅需3.7天的时间。就能训1万亿的token。因此。预训练在不到两个月内完成,总体使用GPU时间为2664K小时。加上上下文扩展需要的119K GPU小时以及后续训练需要的5K GPU小时,总计2788K GPU小时,约合$5.576M。如果算上其他精细调整部分,总成本尚不足$5.576M,看到这心里真的在滴血啊,比起动辄几百亿人民币都听不出来个响的公司,它简直是白莲花了。(当然它家本身不缺钱,做成现在这种各种优化能力,也是之前多少钱砸出来的经验)省钱因为FP8这是主要理由,但是光靠FP8是不够的,我们一会接着讲。
code math很不错
这个肯定是蒸馏别的高级模型的数据了,这个没什么可避讳的,Google的gemni2还蒸馏了Athropic的Claude3.5, 现在这也正常,蒸馏出来只是一方面,关键得训到Parameter里才是硬道理,而且为了增加V3的性能,Deepseek团队,还蒸馏了自己的R1的reason能力作为语料和隐式COT给V3灌进去。(主要是后训练阶段)
MLA (多头潜注意力,Multi-HeadLatentAttention)
这个也是老活儿了,DeepseekV2里就有,但是真的好用,啥叫多头潜注意力呢?
现规定几个变量啊
- d: 嵌入维度(Embedding dimension),表示词向量的维度。
- nh: 注意力头的数量(Number of attention heads)。
- dh: 每个注意力头的维度(Dimension per head)。
- ht: 第t个token在某个注意力层的输入(Attention input for the t-th token)。
MLA 的核心思路就是:
- 先对 token 的特征进行一个“低秩”或“小维度”的压缩(称为 latent vector),再通过少量的变换将它还原/扩展到各头所需要的 Key、Value 空间。
- 一部分必要的信息(如旋转位置编码 RoPE)的矩阵则保持单独处理,让网络依然能保留时序、位置信息。
我们看看它怎么搞的啊。首先定义一个矩阵潜注意力W^{DKV}
你就理解成一个下投影的矩阵,维度是 d x d_c,d_c是非常小的。
原始是啥呢?原始肯定是dh*nh啊,也就是每个头的hidden_size,也可以说embedding size,这俩玩意等价的然后*多少个头,也就是等于d(这块看不懂的,看我以前transformer的课程还有怎么计算显存和算力的课程 https://mp.weixin.qq.com/s/TJJcSRP9ppxua-n9lkmu2w?token=2126311288&lang=en_US)
dh*nh其实可以理解近似为hidden_size,也就是d,因为你多个头就直接对接了模型的宽度么,那你这俩至少得4096吧(咱哪怕以llama来计算)
然后我们搞下面这么个式子,也就是把你的第t个token在某个hidden_layer的输出你经过我刚才说的下投影矩阵给一压缩,那就变得非常小了
c_t^{KV} = W^{DKV} h_t
h_t原始维度是啥?是d啊,比如4096,我经过一个d*dc的下投影矩阵
d矩阵乘于d*d_c, 一出来就变d_c维的向量c_t^{KV} 了,比如d_c是400,那就小10倍,就是这么个压缩
c_t^{KV} 是一个低维度的向量,是压缩后的 keys 和 values 的 latent 向量。d_c 表示压缩后的维度,远小于 d_h * n_h,所以你kv对就小了呗,因为小了,所以占显存也少了,推理的时候kv_cache也少,推的也快,这下看懂了吧
当然你肯定还得逆向把你压缩的回复到原来的维度,那就乘一个上矩阵,要不也推不了么,可以简单认为存的时候存这玩意c_t^{KV} (不占空间),用的时候还得矩阵乘一个上矩阵来还原,这块就不解释了,
rope也一样,要被算出来一个key,最后rope的key和压缩的key,一起合成一个key,因为要带着位置编码,这快我也不细讲了,看明白我刚才讲的,可以自己去看论文,逻辑是一样的,微信公众号写数学公式太遭罪了,好累,能少写就少写点。
刚才讲的是对于推理的时候优化,训练的时候把Q也顺便给优化了,道理和刚才的kv一样,就并不赘述了。
路由专家和共享专家结合的MOE
路由专家 (Routed Experts) 和共享专家 (Shared Experts) 在 DeepSeekMoE 架构中扮演着不同的角色,它们的主要区别在于如何被激活和利用。
路由专家 (Routed Experts):
- 选择性激活: 路由专家是按需激活的。对于每个输入的 token,只有一部分路由专家会被选中并参与计算。这个选择过程由一个门控机制 (gating mechanism) 决定,例如 DeepSeekMoE 中使用的基于亲和度分数的 Top-K 选择。
- 专精化: 每个路由专家都被设计为擅长处理特定类型的输入或学习特定的特征。通过选择性激活,模型能够将不同的输入路由到最合适的专家进行处理,从而实现专精化处理。
- 稀疏性: 由于只有一部分路由专家被激活,所以计算是稀疏的,可以提高模型的效率。
- 负载均衡: 门控机制的目标之一是实现专家之间的负载均衡,确保不同的专家在不同的输入上被激活,避免某些专家过载。
DeepSeekMoE 使用 稀疏门控 (sparse gating) 的机制来选择激活哪些路由专家。只有与当前 token 最匹配(亲和度分数最高)的
Kr 个路由专家才会被激活并产生非零的门控值。其他路由专家的门控值为 0,意味着它们不参与当前 token 的处理。
共享专家 (Shared Experts):
- 全局参与: 共享专家始终参与所有输入的处理。无论输入是什么,所有共享专家都会贡献它们的力量。
- 通用知识: 共享专家旨在学习对所有输入都有用的通用知识或基础特征。它们可以捕捉到数据中普遍存在的模式。
- 促进泛化: 由于共享专家对所有输入都有贡献,它们有助于模型学习更鲁棒和泛化的表示,减少过拟合的风险。
- 提高稳定性: 共享专家可以为模型的预测提供一个更稳定的基础,尤其是在路由机制不够完美或者某些输入不适合任何特定的路由专家时。
那你拿到的最终的outputs是啥呢?
就是路由专家和共享专家的值乘以他们的权重,然后加权,所以输入也相当是一个有重点,有泛化的输出。
Auxiliary-Loss-Free Load Balancing(辅助损失无关的负载均衡)
这个设计很巧妙,MOE专家就多对把,那多服务节点系统无论是什么都跑不出要做负载均衡,MOE也一样,纯轮训肯定是开玩笑。
传统怎么做的呢?
依赖于辅助损失 以避免不平衡的负载。然而,过大的辅助损失会损害模型性能,辅助损失是什么呢?
训练机器学习模型时,除了主要的损失函数(用于衡量模型在主要任务上的性能)之外,额外引入的损失函数。 你可以把它理解为一个次要的、额外的优化目标。
在MOE领域辅助损失主要干这些事:
- 衡量负载不均衡程度: 辅助损失通常会定义一个指标来衡量当前专家负载的均衡程度。常见的指标包括:
- 专家被选择的频率差异: 计算每个专家被路由到的输入数量,并衡量这些数量之间的差异。差异越大,不均衡程度越高。
- 每个专家的平均负载: 衡量每个专家处理的输入的复杂度或计算量,并确保这些平均负载相对均匀。
- 作为惩罚项添加到总损失中: 将衡量到的负载不均衡程度转化为一个损失值,并将其添加到模型的主要任务损失中。这样,在训练过程中,模型不仅要努力完成主要任务,还要努力减少负载不均衡带来的额外损失。
- 鼓励更均匀的路由决策: 通过反向传播,负载均衡辅助损失会影响模型中门控网络的参数。门控网络负责决定将哪些输入路由到哪些专家。辅助损失会促使门控网络学习更均匀的路由策略,避免过度集中到某些专家。
传统都是这么做的,但是如上文我们提到的,你怎么定义辅助损失?如果定义的有问题,会影响模型
Deepseek就不用辅助损失了,那则呢么实现的呢?
偏差项: 为每个路由专家引入一个偏差项,将其添加到相应的亲缘度得分中,以确定最终选择的专家。
动态调整偏差项: 在训练过程中,模型会持续监控每个专家的负载情况。如果某个专家过载,则降低其偏差项;如果某个专家负载不足,则增加其偏差项。
消除辅助损失: 通过这种动态调整偏差项的方法,DeepSeek-V3 能够在不使用辅助损失函数的情况下保持专家负载均衡,从而最大程度地减少了对模型性能的负面影响。
在无辅助损失的前提下呢,它又扩展了一下Complementary Sequence-Wise Auxiliary Loss(补充性的序列级辅助损失)
尽管 DeepSeek-V3 主要依靠无辅助损失策略来实现负载均衡,但为了防止任何单个序列中出现极端不平衡的情况,它还使用了一种补充性的序列级平衡损失。该损失函数鼓励每个序列上的专家负载均衡,并使用一个极小的平衡因子来确保其对模型性能的影响最小化。
节点受限路由:
为了进一步降低训练成本,DeepSeek-V3 还采用了节点受限路由机制,限制每个令牌最多只能发送到四个节点进行处理。这种机制有效地减少了跨节点通信的开销,并使模型能够实现更高的训练效率。
更高的训练效率: 通过采用更细粒度的专家和节点受限路由,DeepSeek-V3 能够更高效地利用计算资源,并降低训练成本。
你们以前玩过hadoop,不有就近计算么,和那玩意差不多,就是降低通信导致的计算代偿,也没啥好解释的。
另外就是MTP了,也是今天讲的最后一个吧
这张图展示了 DeepSeek-V3 中使用的 Multi-Token Prediction (MTP) 的实现方式。我们可以把它分解开来理解:
核心思想:
MTP 的核心思想是在训练过程中,模型不仅预测下一个 token (像传统的语言模型一样),还同时预测序列中后续的几个 token。这可以为模型提供更丰富的训练信号,帮助它更好地理解上下文和长距离依赖关系。
图中的组成部分:
Input Tokens (输入 Tokens): 图的底部显示了输入到模型中的 token 序列,例如 t₁, t₂, t₃, t₄。
Target Tokens (目标 Tokens): 图的顶部显示了模型需要预测的目标 token 序列。注意,不同的模块对应的目标 token 是不同的。
Main Model (主模型): 这部分代表了模型的核心结构,通常是一个 Transformer 模型的堆叠。
(Next Token Prediction) (下一个 Token 预测): 说明主模型的任务是预测序列中的下一个 token。
Embedding Layer (嵌入层): 将输入的 token 转化为向量表示。
Transformer Block × L (Transformer 模块 × L): L 代表 Transformer 模块的层数,是模型进行上下文理解和特征提取的核心部分。
Output Head (输出头): 将 Transformer 模块的输出转化为预测下一个 token 的概率分布。
Cross-Entropy Loss (交叉熵损失): 计算主模型预测结果与实际下一个 token 之间的损失,这是训练主模型的标准损失函数。
MTP Module 1 (MTP 模块 1): 这是第一个 MTP 模块。
(Next² Token Prediction) (下第二个 Token 预测): 说明这个模块的任务是预测输入序列中下第二个 token。例如,当输入为 t₂, t₃, t₄, t₅ 时,它预测 t₆。
Embedding Layer (嵌入层): 与主模型共享 (Shared) 嵌入层,这可以节省参数并学习通用的 token 表示。
Transformer Block (Transformer 模块): 与主模型类似,用于处理输入并提取特征。
Linear Projection (线性投影) 和 RMSNorm (RMS 归一化): 这些是 Transformer 模块内部的常见组件,用于调整维度和稳定训练。这没啥可讲的。
Concatenation (拼接): 这里将 Embedding Layer 的输出与 Transformer Block 的输出拼接起来,为预测提供更丰富的上下文信息。
Output Head (输出头): 将 Transformer 模块的输出转化为预测下第二个 token 的概率分布。
它输出那肯定也是交叉熵损失,计算 MTP 模块 1 预测结果与实际下第二个 token 之间的损失。
MTP Module 2 (MTP 模块 2): 这是第二个 MTP 模块。(Next³ Token Prediction) (下第三个 Token 预测): 说明这个模块的任务是预测输入序列中下第三个 token。例如,当输入为 t₃, t₄, t₅, t₆ 时,它预测 t₇。
结构与 MTP Module 1 类似: 它也共享嵌入层,并包含 Transformer Block、线性投影、RMS 归一化和输出头,不讲了和前面一样。
以此类推可以有更多的 MTP 模块,每个模块预测序列中更远的后续 token。
Shared (共享): 虚线箭头标明了不同模块之间共享的组件,例如嵌入层。
工作流程和意义:
输入: 一个 token 序列被输入到模型中。
主模型预测: 主模型基于输入预测下一个 token。
MTP 模块预测: 同时,不同的 MTP 模块基于稍微偏移的输入分别预测下第二个、下第三个以及更远的 token。
损失计算: 每个模块都计算其预测结果与实际目标 token 之间的交叉熵损失。
联合训练: 所有模块(主模型和 MTP 模块)的损失会被联合起来进行反向传播,更新模型的参数。
MTP 的优势:
更丰富的训练信号: 模型不仅学习预测下一个 token,还学习预测更远的未来 token,这有助于模型理解更长期的依赖关系和上下文信息。(上下文感知能力的提升)
潜在的加速收敛: 通过提供额外的预测任务,模型可能更快地学习到有效的表征。
提高生成质量: 学习预测多个未来 token 可能有助于生成更连贯和有意义的文本,这个对续写任务是很有用的,当然有人测试,它文科任务一般,估计是语料的问题。对code也同样有用,尤其是多行代码(它的code不错)
剩下什么强化PP流水线降低Bubble时间啥的我就不讲了,大家自己下去看看论文吧
最后补一下训练方式,准确说是4阶段:
2阶段预训练,第一阶段32k
第二阶段补到128k,扩展上下文
SFT和RLHF的后训练
训练方式中规中矩,传统scaling law打法
(本文完)
确实最近挺粉deepseek,所以写了这老长的文章
一般模型没让我使这么大劲过。。。
其实你们看下他用的技术,都不是多颠覆或者猎奇,都是非常实用的,它也一直在探索,然后工程化合理的,所以能用不到600万美金就训这么个东西出来,还开源了
第一 有钱真好
第二 还是有做正确且困难的事情
(本文这才真的完)