资讯详情

资讯详情

深度学习数值格式终极指南:FP32、FP8、INT8与量化实战

我还在想怎么把这么硬核的东西讲得让人不犯困。结果发现自己越写越起劲——因为数值格式这事儿真的是越抠越有意思。从FP32一路卷到FP4和INT8表面上是一堆规格表背后其实是整个深度学习硬件和算法摊牌的过程。先说个扎心的事实大部分人用浮点数其实只知道精度越高越好。但真到了部署大模型、抠性能的时候才会发现FP32算不动、FP16存不下市场上给出的选择又乱成一团。E4M3、E5M2、E2M1、INT8这些缩写背后是硬件厂商和算法工程师联手做的妥协艺术。这篇我不打算给你贴一堆废话说如何使用而是直接从格式的本质开始拆每一位是怎么分配的、为什么会这样分、训练和推理各自该用什么、真机跑起来是什么速度。最后我还会把ONNX量化INT8的实操经验掏出来这些都是我实际跑项目时踩过的坑。1. 从FP32到FP8精度不够用之后我们到底做了些什么1.1 为什么FP32会成为负担FP32是单精度浮点数按照IEEE 754标准1位符号位、8位指数位、23位尾数位一共32位。它能表示的数值范围约从1.2×10⁻³⁸到3.4×10³⁸在CPU时代这是绝对的标配。但深度学习的计算密度越来越大问题就出现了数据和模型参数都是以十亿计的每个参数占4字节一亿参数就是400MB。现在动辄几十亿几百亿参数的大模型光是用FP32存一遍权重就需要几十GB甚至上百GB显存。训练时还需要存梯度、优化器状态那更是雪上加霜。这就是为什么几乎所有AI硬件的算力规格里FP32永远是垫底的——因为硬件厂商早就默认你不会拿它来干重活。1.2 从FP16到BF16训练时我们牺牲了尾数还是牺牲了范围为了压缩数据量FP16出现了——1位符号、5位指数、10位尾数只有FP32一半大小。当时做训练混精度Mixed Precision的思路很简单数值计算用FP32做累加和主权重但前向和反向计算用FP16存下来的主要是中间激活值和部分梯度。FP16有个明显的弱点动态范围太窄。5位指数的偏置是15能表示的最大值约65504。做反向传播时梯度值常常小到1e-5以下FP16的精度很快就不够用了会下溢成0。这个现象相信做过混精度训练的同行都有体会。BF16就是冲着这个来的。它把指数位恢复到8位和FP32一模一样所以动态范围和FP32几乎相同下溢问题大幅缓解。但代价是尾数只剩7位精度号称约3位十进制有效数字。你可以理解为BF16是一个范围优先的格式适合做大数和小数混杂的梯度场景。现在大多数AI加速卡训练都主推BF16混精度就是因为它在范围上足够安全。1.3 推理侧的压缩需求格式越攒越多训练侧搞定以后推理侧的胃口又上来了。边端设备内存小、带宽有限、推理时延要求严格于是INT8量化、FP8甚至FP4就进入视野。请注意一个核心趋势——从FP32到BF16是范围优先的妥协从FP16到INT8则是整数均匀量化路线而FP8的E4M3和E5M2则试图在浮点减半的路上继续降低成本。这就是为什么你会在YAML配置文件、TensorRT引擎规格、ONNX部署文档里越来越多地看到E4M3、E5M2、E2M1、INT8同时出现。每一种格式都对应不同的使用阶段和硬件能力。2. E4M3与E5M2的指数尾数博弈FP8的两种活法2.1 E4M3的规格和它能干什么FP8不像FP16只有一个标准。它有两种主流的位分配方式E4M3和E5M2。E4M3的意思是1位符号、4位指数、3位尾数实际上尾数1位隐式精度能表示的有效精度大约相当于4位二进制。指数位少意味着动态范围小尾数位多一点则意味着精度相对好一点。它的最大有限值是448左右最小正常值是2⁻⁶。没有独立的Inf和NaN编码解释器需要用特殊值来做容错。这种格式主要用于前向传播也就是激活值Activations和权重Weights。原因是前向传播的数值范围通常比较可控尤其是经过归一化LayerNorm/RMSNorm之后激活值的分布往往集中在某个相对稳定的区间。E4M3的精度可以保证这些数值的尾数细节被尽可能保留。我在实际测试中说句实在话如果用E4M3做权重的PTQ训练后量化配合好的校准方法精度的掉落比想象中小很多因为网络已经训练到一定程度参数分布相对稳定。2.2 E5M2的规格和它服务的场景E5M2则是1位符号、5位指数、2位尾数。最大有限值约57344最小正常值约2⁻¹⁴动态范围比E4M3大了几个量级但尾数精度很低只有约2位二进制有效精度。这就很明白了吧——E5M2是给反向传播和梯度用的。梯度数值在一层一层回传过程中会经历数量级的剧烈变化有的层梯度大有的层梯度极小如果动态范围不够小梯度的信息就全部被砸成0了。所以反向传播宁可牺牲精度也要换来足够的数值范围。除此之外E5M2还保留了Inf和NaN编码支持这在计算梯度时特别重要。因为梯度链式求导中偶尔会出现除零或者极端值的情况硬件能够原样表示这些特殊值比E4M3只能出错后由上层兜底要自然得多。2.3 两种FP8典型应用与硬件支持情况现在很多AI加速芯片比如Grace-Hopper架构、部分NPU和推理卡都支持FP8两种模式但关键是最通过融合算子Fused Kernel同时利用E4M3和E5M2的出前向用E4M3反向用E5M2。如果你只是用FP8存权重做推理E4M3足够如果涉及在线训练或微调务必确认你的框架和硬件同时支持E5M2。不同硬件对FP8的支持细节也不一样。有些芯片在整数计算单元上模拟FP8并没有原生的浮点硬件这会导致精度表现和速度都有差异。需要强调的一点FP8不是所有算子的万能解药。像Softmax这类涉及指数和分式的算子FP8精度不够容易出现结果漂移。通常量化方案中这类层会保持高精度或者用混合精度策略切回FP32/FP16。3. E2M1和INT8把数字压缩到极限的量化艺术3.1 E2M1的“数值可数性”E2M11位符号、2位指数、1位尾数一共4位。这个格式能表示的数值非常有限——用手指头数得过来如果没有隐式前导1也就只剩下几个离散值。你可能要问了这么粗的格式拿来干嘛答案是权重极端压缩和某些特定硬件的FP4推理。举个例子有的模型经过特殊的训练和量化感知训练QAT可以让权重分布集中到E2M1能够表达的几个值附近推理时从4字节降到0.5字节内存占用减少8倍对边缘部署极有吸引力。但注意E2M1不适合直接用来做通用张量存储更不适合做激活值。它的误差是非线性的没有办法通过简单的scale校准来修复大范围分布的误差。基本上FP4是一种已经做好心理准备的极限压缩。我看到很多项目用E2M1跑小模型演示效果不错但遇到大模型还是得回到FP8或者INT8。3.2 INT8的均匀量化和scale/zero-point机制INT8是8位整数从-128到127有符号每个间隔是均匀的。这是它和FP8最大的区别浮点格式在不同数量级下间隔不同而INT8在全范围内是等间隔的。均匀间隔的数学性质让它非常适合做线性映射——也就是用一个scale和一个zero-point把浮点分布映射到整数区间int8_val clamp(round(fp_val / scale) zero_point, -128, 127)这个逻辑看着简单但里面的门道很多。scale决定了区间的大小选得太大精度流失严重选得太小会溢出。zero-point则负责对齐零点否则输入中的0在整数域中可能无处安放导致后续的padding比如卷积中全零边界出现偏差。INT8的推理之所以快是因为硬件对INT8的矩阵乘做了专门优化。很多AI芯片上INT8的单位算子吞吐量是FP16的2倍是FP32的4倍。这个速度优势是大规模推理落地的重要基础。3.3 头还是尾什么场景用FN系列什么场景坚持INT8我的经验是训练和微调阶段FP8E4M3/E5M2是更自然的选择因为梯度性质决定了浮点更合适而纯推理部署场景INT8更成熟、工具链更完善。推理时选择INT8还是FP8主要看硬件。如果你的推理引擎跑在只支持INT8的NPU/CPU上量化到E4M3也是白搭算子不认。反之如果你的GPU支持FP8的tensor core整条链路可以做得更顺滑因为FP8的前向精度天然好于INT8特别是在处理分布宽动态的激活值上。在我们实测的几个Transformer模型里FP8 E4M3权重的精度损失比INT8均匀量化小一半以上尤其是在最后几层logits附近INT8量化经常需要特殊校准才能压住误差。4. 同一段算子FP16/BF16/INT8/FP8谁跑得快4.1 算力规格表的透明算账说到速度就得看硬件规格表。很多AI加速卡的算力是这么标的FP32算力有一个值FP16/BF16的Tensor Core算力约等于它的2倍INT8约等于它的4倍部分架构是8倍FP8在H100这类芯片上能到FP16的2倍甚至更多。这意味着一个简单的事实同样一次矩阵乘用INT8比用FP32能快4-8倍用FP8也比FP32快4-8倍具体取决于架构。但这里有个陷阱峰值算力是理论数据实际推起来会被内存带宽、算子融合程度、量化/反量化开销拖累。INT8计算再快如果每次都要把fp32的激活值现转成int8转换本身的代价可能吃掉一部分速度优势。所以部署时做算子融合把量化/反量化融合进前一个/后一个算子是基本操作。4.2 实测里看到的真实速度差异我在实际项目中对比过onnxruntime的FP32和INT8动态量化模型在小batch1-8下推理速度提升通常在1.5-2.5倍之间并没有达到理论4倍。原因是batch小的时候算子切换和内存搬运占比很大整数算子的优势没有完全发挥出来。而把batch提到32、64以后INT8的优势会明显放大。FP8在GPU上的表现类似。E4M3模式下由于不需要频繁做double-round前向计算噪声很小推理画面感和INT8差不多——在小batch时提升有限大batch才放得开。如果同时涉及BF16它的速度一般和FP16接近但精度比FP16稳定。我做训练的时候更愿意把主流程放在BF16上而推理则根据算子量化敏感度来决定是INT8还是FP8。4.3 算力需求怎么算别用位数直接推很多人在对比FP16、BF16、INT8、FP32、FP64时只盯着 位数减半速度就翻倍 这个直觉却忘了算力需求还涉及两个维度计算量FLOPs和内存带宽Bytes。模型体积减半比如FP32到FP16最直接节约的是内存带宽和显存占用真正的FLOPS还得看硬件单元是FP32的还是Tensor Core的。硬件的FP32单元不会因为你的数据变成FP16就自动翻倍吞吐——当然很多GPU的FP32和FP16共用一部分单元如果你的算子跑在CUDA Core而非Tensor Core上速度差异并不会太悬殊。所以我的判断顺序是先看框架有没有把算子调成tensor core/int8/fp8路径再看数据格式。格式只是前提路径才是性能关键。5. ONNX Runtime INT8量化流程实战笔记5.1 动态量化和静态量化的分岔路搜索热词里正好有.onnx量化int8这个我太熟。ONNX Runtime的量化主要有两条路线。动态量化Dynamic Quantization只在推理时对激活值做动态统计并量化权重量化离线完成。好处是无需校准数据部署简单坏处是激活值的scale是运行时候统计的会引入额外开销。对于模型权重占比大的场景比如LLM纯动态量化提升明显但对卷积网络这种激活值占大头的情况就比较一般了。静态量化Static Quantization离线用一小部分校准数据预先统计激活值的min/max或分布生成scale和zero-point推理时直接使用。这样运行时没有统计开销速度更快但你需要准备具有代表性的校准集。我推荐一个判断方法如果模型以线性/Embedding为主可以先试动态量化实现成本低、效果立竿见影如果模型卷几层卷积、Residual结构多、激活分布广静态量化更值得投入。5.2 校准数据的选取和校准方法静态量化的核心是校准。校准集不能太大也不能太小通常几百张有代表性的样本就够了。关键是覆盖面要涵盖实际部署时会遇到的亮度、对比度、噪声、类别比例等差异否则scale就会偏。ONNX Runtime里常见校准算法有MinMax、Percentile和Entropy。MinMax最简单取绝对最大值Percentile排出极端离群点Entropy基于信息论让量化前后的KL散度最小。我自己的经验默认先跑MinMax速度快精度尚可如果某一层误差特别大就针对性改成Percentile把个别的尖峰过滤掉效果往往立竿见影。每个关键对照表校准方法原理适用场景我的建议MinMax取绝对最大分布集中先跑这个Percentile剔除极值有离群点针对性微调EntropyKL散度最小宽分布激活精度优先时用5.3 量化敏感层的处理我在实操中发现就算整体精度还能看总有少数层偷偷掉点。常见问题集中在最后一层全连接或卷积输出直接对应logits对量化误差敏感BatchNorm折叠不彻底的残差分支LayerNorm/Sigmoid/GELU这类非线性激活层解法通常有三个把敏感层保留FP32混合精度对敏感层单独使用更大的量化粒度per-channel或者在QAT训练阶段做量化感知训练让权重适应量化误差。最优化的做法其实是设计模型时就考虑量化友好比如避免极端动态范围的激活函数把输出的分布压窄一点。如果模型已训练完毕退而求其次就是精度敏感层不量化。5.4 实际跑onnxruntime量化时的几个坑模型里如果有无用的Identity节点量化前建议先简化/清理图结构否则量化后图结构啰嗦推理延迟下不来动态量化模式下Gather和MatMul内的float输入会被转为int8但Embedding查表依然是浮点对大词表模型提升有限静态量化需要校准器Calibrater在CPU/GPU上跑一遍这步别省否则scale完全是乱猜不同onnxruntime版本对量化算子支持度差异大要确认所部署环境的ONNX Runtime版本和量化算子完整度6. 从精度需求倒推格式选型我的决策框架6.1 按阶段选训练、微调、推理是三张牌我自己在项目里的决策逻辑很直白——先看阶段。训练阶段主推BF16或FP16混精度梯度用BF16保范围。如果显卡不支持BF16兜底FP16但要小心下溢必要时加loss scaling。微调阶段如果你打算全参数微调还是BF16/FP32为主LoRA这类参数高效微调可以肆无忌惮地用FP8/E4M3因为只更新少量低秩矩阵误差可控。推理阶段按部署端能力走CPU上INT8是唯一的性价比之王支持FP8的GPU上权重用E4M3激活能量化就量化激活敏感到不行的就混合精度至于E2M1/FP4只建议在内存极度紧张且对精度有充分预期的场景使用。按阶段选格式核心不是比哪个格式更好而是比哪个格式在这个阶段的代价最小。6.2 按算子类型选宽动态与窄动态数值分布是选格式的另一个视角。权重矩阵经过训练后分布通常集中在0附近动态范围窄、尾部小适合INT8、E4M3这种高精度窄范围格式。激活值经过激活函数后在每一层分布差异大。如果是ReLU系输出全是非负范围可控但如果用了GELU、SiLU负半轴也有值分布宽一点容易在量化边缘出问题。这种情况下你要么选FP8的E4M3增强动态范围要么在INT8静态量化时花心思校准。梯度呢动态范围极不稳定差量级的跨度可以到1e-8~1e-1E4M3基本不可能FP16也有风险必须FP32或BF16/E5M2。这是反向传播的硬约束。6.3 误差评估的正确姿势换格式之前先定误差基线。我通常会用三个维度端到端的精度指标accuracy/rouge/BLEU等中间层输出的余弦相似度或L2差距最终logits分布的KL散度一和二能帮你定位是哪一层出了问题三能看整体分布漂移。三者并行观察量化调参会快得多。不要只盯着单个模型跑分。如果你部署的是多个模型服务还得考虑量化对全部模型的一致影响有的模型对格式转换非常敏感有的却像铁打的。敏感度不一致是正常的接受这一点才会老老实实给不同模型做单独的量化方案。6.4 一句话总结选型心得如果只让我留一句话给刚入门的读者那就是训练保范围用BF16推理保速度用INT8想在推理上再抠一点、硬件又支持的话就上FP8的E4M3。E2M1的FP4永远是压箱底的大招不是常规武器。而且你要明白一点——无论选哪个格式真正的功夫不在于认识那几个字母而在于你知道自己的数值在哪儿、误差从哪儿来、硬件吃哪一套。搞清楚了这些格式表在你手里就不是天书了。最后说个实操中的细节我每次做量化方案之前都会花半小时把自己模型的每个权重分布直方图打出来看一眼。这笔时间永远值得。因为数据分布长什么样直接决定了你该信任MinMax还是Percentile该用per-tensor还是per-channel甚至能提醒你哪个层不适合量化。格式选型本质上就是对分布的理解和妥协。还有一个小技巧想分享给跑ONNX的同行做INT8量化前先把模型的Node Fusion和常量折叠跑完再初始化量化器。很多表面上量化后精度崩了的案例其实是因为图里残留了大量冗余Casts和Transposes精度根本没崩纯粹是结构太乱影响了calibrater的统计质量。把这些边角清干净事半功倍。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →