仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
作者:Reiase原文:https ://zhuanlan.zhihu.com/p/26649914395
DeepSeekV3的发布引起了对FP8训练的广泛关注,业界也出现了大量文章解析How的问题——DeepSeek是怎么进行FP8训练的,与传统方案有哪些不同。但是目前鲜有文章对Why问题进行深入探讨,为何DeepSeek的方案能够取得成功。本文尝试对FP8训练所面临的挑战进行深入解析,并尝试猜测DeepSeek团队设计其FP方案的背后原理。
本文实验准备了很久,刚好赶上DeepGEMM开源:
FP8是一种遵循IEEE754规范[^ieee754]的8位浮点数格式,由Nvidia在2022年发布的H100GPU中首次引入。在此之前,Nvidia硬件上浮点数格式的发展历程如下1[1]:
•2016年:P100GPU首次引入FP16数据格式,直接开启了深度学习混合精度训练的技术路线;
•2017年:V100GPU首次引入TensorCore,用于加速FP16矩阵乘法运算;
•2020年:A100GPU首次引入TF32数据格式,可通过TensorCore加速;引入bfloat16数据格式,提供比FP16更宽的动态范围(当下BF16已经成为LLM训练的主流方案);
•2022年:H100GPU首次引入FP8数据格式;
FP8被Nvidia给予厚望,认为其成功的延续了CEO提出的Huang’sLaw2[2],即10年间GPU硬件算力提升1000倍。在过去的10年间,新型数值表达的引入了16倍算力提升,是诸多技术中贡献最大者,GPU架构与复杂指令集紧随其后带来了12.5倍提升,而制程进步带来的收益非常有限,仅2.5倍3[3]。
IEEE754是目前广为使用的浮点数规范,定义了浮点数的bitwise表达与量化方式。浮点数的二进制表达分为三部分:
•符号位(sign)
•指数位(exponent)
•尾数位(mantissa)
常见的浮点数格式的二进制表达如下图所示:
随着浮点数位数从16位进一步降低到8位,动态范围不足的问题逐渐显现。因此Nvidia、Arm和Intel在FP8规范中设计了两种浮点数类型[^fp8]:E4M3和E5M2
E4M3
E5M2
format(s/e/m)
1:4:3
1:5:2
Exponentbias
7
15
Infinities
N/A
S.11111.00
NaN
S.1111.111
S.11111.{01,10,11}
Zeros
S.0000.000
S.00000.00
Maxnormal
S.1111.110==448
S.11110.11=57.344
Minnormal
S.0001.0000=
S.00001.00=
Maxsubnorm
S.0000.111=
S.00000.11=
Minsubnorm
S.0000.001=
S.00000.01=
浮点数都会分配一些二进制表达来表示特殊值NaN和,IEEE754规范约定使用指数位全1的二进制表达来表示这些特殊值。对于E4M3格式来说,若严格遵循IEEE754规范,会8个二进制表达。因此在定义E4M3规范时对这些二进制表达进行了额外开发,仅在指数位尾数位同时全为1时才表示NaN,全为0的时候表示。
H100的TensorCore提供3倍A100FP16性能,若启用FP8算力能够再次翻倍。
权重和梯度均使用高精度存储,FP8仅用来进行矩阵乘运算。在矩阵乘之前需要通过cast操作将高精度的权重和激活转换为FP8格式。矩阵乘的输出仍为高精度,不影响bias操作与激活操作。在前向计算中使用E4M3进行运算,在反向过程中使用E5M2进行计算。
TransformerEngine通过融合算子来降低模型训练时的显存开销。对比BF16训练,FP8训练能够带来30%端到端性能提升,但因为权重和激活仍然使用高精度存储,无法带来额外的显存节约,反而会因为checkpoint中存储额外的scaling值,导致5%的额外显存占用。
综合评价一下TransformerEngine的FP8方案,FP8训练有三部分理论收益:
•计算性能翻倍;
•显存开销减半;
•通信吞吐减半;
TransformerEngine所使用的FP8方案实际上只拿到了30%的计算性能收益。但是代价是什么呢:
•精度风险:很多实验发现不小于2%的lossdiff;
•计算流变复杂:在传统同步/异步范式之外设计支持延迟缩放的新型时序控制逻辑;
•内存格式变复杂:需要同时支持E4M3和E5M2两种格式,并确保每块儿显存的格式选择正确;
DeepSeek团队使用了完全不同的FP8训练方案,与TransformerEngine的主要不同之处在于:
•权重(weight)使用FP8存储;
•全部采用E4M3浮点格式;
•使用块级量化(block-wisescaling),而不是张量级量化(tensor-wisescaling);
整体架构如下:
masterweight、权重梯度使用FP32,激活梯度、优化器状态使用BF16。这些高精度数据会被切分到不同的DPrank上,因此对整体显存开销影响可以控制得比较好。
低精度优化器状态:AdamW优化器的一阶动量与二阶动量使用BF16存储来降低显存压力,但是masterweight与maingrad仍然使用FP32存储;
低精度激活值:针对不同的激活值使用了不同的精度
Attention之后的Linear层,由于attention的梯度计算对精度敏感,这些激活使用了E5M6数据类型。并且为了避免引入额外的量化误差,会使用幂次缩放因子;
MoE中SwiGLU的输入:引入recompute策略,使用FP8格式缓存输入;
低精度通信:MoE的通信是训练过程中最主要的瓶颈之一。为了降低这部分开销,将MoE前向up-projection操作与反向的down-projection操作前的激活/激活梯度进行FP8量化,之后再进行dispatch逻辑,量化过程使用幂次缩放因子。对于combine模块,使用BF16来保证训练精度。
FP8两种数据类型,E5M2保留动态范围但是缩减了尾数精度,E4M3多保留了一位尾数精度但是牺牲了动态范围。不管那一种都会加剧训练过程中的上溢和下溢问题。好在LLM模型训练时,权重与激活的动态性研究发现大多数数值分布集中在0附近,但会带有明显的少数outlier。集中在0附近意味着我们可以通过缩放因子(scalingfactor)对数值进行缩放,从而更好的利用有限的动态范围。但outlier的存在导致很难在整个tensor层面选取出适当的缩放因子。
为了平衡数值的整体分布与少数outlier的分布,可以引入分块量化策略:将数据分成1x128或者128x128的block,并对每个block选取一个缩放因子。对于大多数block可以选择较大的缩放因子来更好的利用动态范围,而对于存在outlier的block可以使用较小的缩放因子来避免出现上溢。
使用块粒度量化后的矩阵乘运算如下图所示:
高精度Accumulation:NvidiaH800GPU的TensorCore在执行FP8矩阵计算时,累积精度被限制在大约14位,远小于FP32精度。当LLM训练的权重矩阵规模与输入规模变大时,这个问题会越来越显著。
DeepSeek团队的测试中,大小为4096的矩阵乘运算因为累积精度问题出现了最大2%的相对误差。为了解决这个问题,引入了分级累加:使用TensorCore默认的累加精度进行计算,当累积一定次数后,再将这个部分累加结果搬运到CUDACore上进行FP32累加。
E4M3与在线量化:引入细粒度量化后,不再需要同时维护E4M3和E5M2两种精度,因此DeepSeek团队只使用E4M3数据格式。同时,为了维护好分块量化的缩放因子,并简化框架,使用算子内部的在线量化代替TransformerEngine中的延迟量化。
DeepSeek团队在FP8精度上对算子和框架作了大幅度的优化,这些优化需要对模型框架、训练过程的动态性以及硬件的实现细节都有充分的了解。整体方案在计算、显存和通信上都有不小的收益:
计算:DeepSeek方案与TransformerEngine方案都能加速Linear相关的三次矩阵计算(前向,权重反向和激活反向),因此能够拿到的收益应该与TransformerEngine的30%类似。少了一次权重的cast,但是多出了块量化与高精度累加操作;
存储:使用16位优化器状态,这部分显存开销降低一半。Attention后的激活使用12bit存储,对比BF16降低25%。SwiGLU部分激活通过recompute降低4倍,通过FP8再降低一半;
通信:使用FP8将通信数据量降低一倍;
DeepSeek团队已经分析了影响精度的一个主要因素——矩阵规模。在此基础上,我们进一步探讨影响精度的其他因素。算子精度涉及各种不同的数据类型、矩阵形状和运算类型(attention,matmul等),导致精度分析问题变得极为复杂。为了简化精度分析,我们通过对模型中的各种运算进行分析,发现大多数计算可以拆解成向量内积操作。因此,通过研究向量内积操作的精度,可以对算子精度进行全面分析。以LLM模型为例,主要运算如下:
•Attention计算Score包含Q和K的内积计算;
•Attention计算根据Score加权V,本质是score向量与V不同channel向量的内积计算;
•矩阵计算可以拆解成很多向量内积计算:
所以,研究FP8对LLM精度的影响,首先需要研究其对向量内积的数值精度的影响。经过实验,发现影响这个数值精度的主要因素有两个:
•向量长度的影响,向量长度越长,意味着LLM中权重矩阵规模越大;
•向量相关性的影响,LLM中的Attention计算会涉及大量强相关的向量内积;
先通过一个简单的实验观察下向量长度(矩阵规模)对误差的影响,在实验中构造满足分布的随机向量[x_i]。将两个float64向量的内积作为对照组;将两个向量量化为FP8E4M3格式在计算向量内积作为实验组。实验引入tensor-wisescalingfactor来模拟TransformerEngine的行为,测试三组不同的向量长度下,scalingfactor对量化误差的影响。重复实验200次,绘制量化误差的直方分布如下:
上图横轴为量化误差,纵轴为样本计数。随着向量长度的增大,内积计算中的量化误差呈现上升趋势。这可以归因于内积运算中,各项的量化误差在累加过程中不断叠加,导致整体误差明显增加。scalingfactor能够一定程度上改善量化误差的分布,量化误差分布更集中在零附近。为了更合理的评估量化误差的大小,可以引入信噪比概念4[4]:
其中:
•分子表示真实信号的能量;
•分母则代表量化误差(噪声)的能量;
通过信噪比,我们可以快速确定信号比噪声能量大多少,比如20dB代表着,信号能量比噪声能量大100倍,40db代表,信号能量比噪声能量大10000倍。
如何解读信噪比数据
1.正值SNR:说明信号能量大于噪声能量,较高的SNR表示量化误差对整体计算影响较小,数值更接近高精度计算的结果;2.SNR接近0:信号与噪声能量接近平衡,表示量化误差已经达到与信号能量相当的规模,此时运算结果可能开始出现较大偏差;3.负值SNR:意味着累积的量化噪声能量超过了信号能量,信号在噪声中“被淹没”;
通过信噪比来评估TransformerEngine中tensor-wisescalingfactor的作用:
在不进行scaling的情况下(scaling=1),内积计算的信噪比在在0附近,意味着信号与噪声能量差不多,计算误差极大。而引入scaling后,可显著改善信噪比的分布,大量样本的向量内积信噪比显著高于零,具有比较高的计算精度,但仍有大量case信噪比小于0,即信号被噪声淹没。从这个实验可以看出,TransformerEngine的tensor-wisescaling方案能够改善向量内积计算的信噪比。接下来我们来分析DeepSeekV3的FP8方案在精度方面有哪些不同,是否能够带来更多收益。
高精度累加是DeepSeekV3的FP8方案中的重要一环,是指使用较高的精度(32bit)进行向量内积的累加操作。受限于NvidiaGPU硬件的限制,TensorCore默认累加使用14bit累加器。为了提高累加精度,DeepSeekV3使用了分chunk的累加方案:在chunk内部使用tensorcore自带的低精度进行累加,在chunk间使用高精度累加,实验结果如下
实验使用tensor-wisescaling系数64,chunk=none为不使用分chunk的高精度累加,等价于TransformerEngine的方案,可以作为对比参考。在引入分chunk高精度累加后,整体的信噪比分布有了较大的改善,chunk越小,改善幅度越大。但是仍然存在大量case,信噪比小于零。我们需要进一步优化FP8量化方案。
Block-wiseScaling是DeepSeekV3对FP8方案的另一个主要优化,是指对向量进行分块,并对每一个分块单独计算最优的缩放因子。对不同的分块儿chunksize进行实验,结果如下:
这里chunk=none为不分chunk,整个tensor计算一个最优的scaling系数,即退化为tensor-wisescaling。对比完全没有动态scaling的结果,信噪比得到了大幅改进。另外两组实验分别在大小为512和128的chunk内计算最优的scaling系数,并进行缩放后在做内积计算。可以发现,通过引入chunk内scaling系数,可以大幅改进信噪比。chunk越细,信噪比越好。引入Block-wise动态Scaling后,基本能够避免信噪比小于零(即信号被噪声淹没的情况)的情况。
最后,我们看下DeepSeek团队是如何一步步达成V3中的FP8量化方案的:
上图图展示了如何通过一步步优化,可以看到通过高精度累加,分块动态scaling与精心选择chunk大小,DeepSeekV3的FP8方案较为完美的控制住了向量内积运算的信噪比。若单独分析每一项优化,其带来的提升从直方图上来看并不是特别的突出和显著。整体信噪比对比Nvidia的TransformerEngine方案具备优势,但没有特别显著的差异。
向量内积时影响结果的一个重要因素是两个向量的相关性,这里我们先看下当两个输入向量的相关性不同时,量化误差是否存在差别:
实验测试了三个不同的向量长度下(128、1024和4096),向量相关性对FP8内积计算误差的影响。可以发现,不管是哪一个向量长度,相关性越大,计算误差越大。而向量越长,相关性的影响也越大。为了更好的进行分析,我们仍然使用信噪比来度量误差。下图为直方图,横轴为信噪比,纵轴为样本计数:
从上图可以看到,向量相关性对增强时,量化误差出现比较大的变化,会从比较发散的分布变成更加集中的分布。这有两方面的影响:一方面改进了最坏情况,信噪比小于0的情况在相关性增大时逐渐消失了;另一方面,信噪比的上限也出现了明显的下降。通过上述实验我们发现向量相关性对向量内积的量化误差具有很大影响,接下来我们分析DeepSeekV3的FP8优化在这种情况下的作用。
我们测试长度为4096的向量在不同向量相关性情况下,内积的量化误差受分块高精度累加的影响如下图所示:
从实验结果可以看出,在存在向量相关性的情况下,分块scaling策略能够显著提高信噪比。向量相关性越强,提升的效果越显著。
这些发现为LLM训练中的FP8优化提供了重要参考:在处理Attention这样的高相关性计算时,应该优先考虑采用合适大小的分块策略,而不是简单地调整全局scalingfactor。
我们讨论了TransformerEngine与DeepSeekV3使用的两种不同的FP8训练方案,并通过向量内积的量化噪声分析,对比了两者在随机向量与相关向量下的表现,以解释DeepSeek的FP8训练方案成功的原因。在完全随机向量的情况下,通过一步步引入优化,逐步提高内积计算的信噪比。尽管无法确定DeepSeek团队在设计其FP8方案时是否使用了类似的量化分析手段,但这种分析方法也许可以帮助我们进一步改进FP8或者其他低精度方案。
当进一步分析相关向量的内积信噪比时,迎来了一个关键的顿悟时刻(AhaMoment)。对比TransformerEngine,DeepSeek的方案虽有所优势,但还不够惊艳。然而,当考虑向量相关性后,观察到了非常明显的信噪比提升。这种显著的提升并非偶然,而是DeepSeek团队精心设计与验证的结果。毕竟,在真实任务上验证完整的FP8方案需要巨额的投入,很难想象DeepSeek团队会直接在模型上进行端到端训练来优化方案的消融实验。关于FP8的完整故事应该是这样的:
•影响模型精度的主要因素是矩阵规模与向量相关性;
•TransformerEngine通过引入tensor-wisescaling,比较好的解决了大Tensor矩阵乘法的精度问题,但是忽视了LLM训练过程中的数值分布层面的动力学特征(TrainingDynamic)。在训练过程中,权重内部会逐渐呈现出相关性,导致矩阵运算频繁面临高相关性的情况。因此,TransformerEngine的FP8方案一直存在训练误差,未能被LLM厂商广泛采用。;
本文实验代码可见:
推荐阅读
用极小模型复现R1思维链的失败感悟
关于DeepSeekV3/R1Decoding吞吐极限的估计
从DeepSeekR1论文深入理解推理型大模型
复现DeepSeekZero的RL调参经验
DeepseekR1Zero复现全过程记录
开源社区DeepSeekR1最新复现进展汇总
DeepSeek-R1技术报告速读
进入大模型技术群,备注:进群。
添加好友:baobaogpt,记得备注