混合精度 FP16/BF16/FP8:减半位宽如何换来近翻倍吞吐与带宽
更新时间:2026-09-25。前置知识是浮点的三段式表示,见 浮点运算与 IEEE 754:符号、指数、尾数各自决定什么。本篇回答一个工程问题——为什么深度学习敢于把 32 位浮点砍到 16 位、8 位,砍完为什么几乎不损失模型质量。消费这些格式的硬件在 Tensor Core 张量核心,乘法器的面积账在 MAC 单元的精度设计,8 位以下的整数路线在 INT8/INT4 量化与校准。
本文要回答的问题
- FP32 在深度学习里到底浪费在哪里?为什么说它既占带宽又占算力?
- FP16、BF16、FP8(E4M3/E5M2)的位是怎么切的?动态范围和有效十进制位各是多少?
- 为什么位宽减半,Tensor Core 上的吞吐和有效带宽都接近翻倍?
- FP16 训练为什么会溢出/下溢,loss scaling 怎么救?为什么累加必须留在 FP32?
- 一套典型的训练/推理混合精度 recipe 长什么样,精度损失大概有多少?
一、FP32 的双重账单:算力与带宽
先看一个深度学习训练的典型动作:矩阵乘加 D = A·B + C。在 浮点运算与 IEEE 754 中可以看到,浮点乘法的电路成本大头在尾数相乘,而尾数乘法器的面积大致随位数的平方增长。FP32 有 24 位尾数(含隐含的 1),FP16 只有 11 位,同样的硅面积能塞下的乘法器数量相差约 (24/11)² ≈ 4.8 倍。换句话说,用 FP32 做矩阵乘,等于为「20 多年前为科学计算定的精度标准」支付四倍以上的乘法器面积。
第二张账单是带宽和存储。大模型的瓶颈通常不是算不动,而是权重和激活搬不过来:HBM 容量按字节计价,带宽按字节/秒计量。同一个 70 亿参数的模型,FP32 存储要 28 GB,FP16/BF16 只要 14 GB,FP8 只要 7 GB;同一套 HBM 和同一批 cache line,搬运 FP16 元素的数量就是 FP32 的两倍。训练时 KV cache、激活内存、优化器状态全部按同一比例缩放,位宽直接决定「单卡放不放得下」。
训练时的内存账比推理更夸张。以 Adam 优化器为例,除了参数本身,还要为每个参数保存一阶动量和二阶方差两份状态,加上梯度,训练一个模型需要的显存约为参数量的 4 份乘以单份字节数:FP32 下每参数 16 字节,BF16 参数加 FP32 主权重与优化器状态的混合配方约 8~12 字节。70 亿参数模型仅模型相关状态就从 112 GB 压到 60 GB 上下,这决定了它能否塞进一张 80 GB 的卡。激活内存同理:训练要为反向传播保存每层中间激活,长序列、大 batch 下激活往往比权重更占地方,而激活恰恰是最容易降精度的部分。
而神经网络对精度并不敏感。每层的权重和激活本质上是统计量:没有哪个输出依赖某一个权重的第 7 位十进制有效数字;批归一化、残差连接、随机梯度下降本身都在持续吸收噪声。这给了低精度可乘之机——前提是格式选得对、数值保护做得住。
二、位域对比:指数买范围,尾数买精度
所有浮点格式都是同一笔交易:总位数固定,指数位越多动态范围越大,尾数位越多相邻可表示数越密。下图按比例画出五种常用格式(外加 INT8 作为整数参照)的位域。
逐行读这张图。FP16(IEEE 半精度,也称 5e10m)只给指数 5 位,偏移量 15,尾数 10 位。BF16(Brain Floating Point,Google 为 TPU 设计)直接把 FP32 的位域拦腰截断:保留 8 位指数、只留 7 位尾数,所以它的动态范围与 FP32 完全相同,代价是有效数字少了一大截。
这种「拦腰截断」还带来一个免费的工程好处:FP32 转 BF16 只需要保留符号和指数、丢弃低 16 位尾数(配合舍入),硬件上就是接线加一个舍入器,转换几乎零成本、零延迟;而 FP32 转 FP16 需要重新对阶、调整偏移量并处理溢出,是一次完整的格式转换。训练流水线里每层都要在格式间来回,转换成本会累积,BF16 在这一点上也占了便宜。顺带一提 TF32:A100 引入的折中格式,存储仍是 FP32,但乘法只用 10 位尾数(同 FP16)配 8 位指数,让大量 FP32 矩阵乘代码无需修改就吃到 Tensor Core 红利。FP8 有两种互不替代的格式:E4M3 尾数 3 位、精度优先,E5M2 指数 5 位、范围优先。最后是 INT8:没有指数段,靠定点缩放因子换算,8 位全部用于有效载荷。
一个细节值得点出:FP8 E4M3 的最大有限值是 448。它用了一个非标准约定——指数全 1、尾数为 110 时仍是有限数(2^8 × 1.75 = 448),只有指数全 1 且尾数为 111 才编码 NaN,且没有无穷大;E5M2 则遵循 IEEE 惯例,最大有限值 57344,指数全 1 留给 Inf/NaN。这些特例是硬件和编译器必须严格对齐的部分。
三、动态范围与有效位:一张决策表
下图配套的数字表是选型时最常用的依据。「十进制有效位」由尾数位数乘 log₁₀2 估算,「最小规格化数」决定多小的数不经过非规格化通道就会归零。
| 格式 | 位分配(符/指/尾) | 最大有限值 | 最小规格化数 | 十进制有效位 | 典型场景 |
|---|---|---|---|---|---|
| FP32 | 1 / 8 / 23 | ±3.4×10³⁸ | ±1.2×10⁻³⁸ | 约 7.2 位 | 主权重副本、累加、科学计算基准 |
| FP16 | 1 / 5 / 10 | ±65504 | ±6.1×10⁻⁵ | 约 3.3 位 | 第一代混合精度训练/推理,需 loss scaling |
| BF16 | 1 / 8 / 7 | ±3.4×10³⁸ | ±1.2×10⁻³⁸ | 约 2.4 位 | 训练主力格式,范围安全、无需 loss scaling |
| FP8 E4M3 | 1 / 4 / 3 | ±448 | ±1.6×10⁻² | 约 1.2 位 | 推理、训练前向(权重与激活精度敏感) |
| FP8 E5M2 | 1 / 5 / 2 | ±57344 | ±6.1×10⁻⁵ | 约 0.9 位 | 训练反向(梯度范围大、精度不敏感) |
| INT8 | 1 符号 + 7 数值(定点) | 由缩放因子决定 | 由缩放因子决定 | 约 2~3 位 | 推理量化,乘法器最省、需校准 |
这张表解释了三个反直觉的事实。第一,FP16 的上限只有 65504——深层网络里梯度沿层数连乘,单次值突破 65504 直接变 Inf,这是它训练易溢出的根源。第二,BF16 只有 2.4 位有效数字,比 FP16 的 3.3 位「更粗糙」,但因为范围与 FP32 相同,训练反而更稳:在「算得准」和「不溢出」之间,训练用脚投票选了后者。第三,FP8 的两种格式是分工而非竞争:前向传播的权重、激活数值集中在 ±1 量级且对精度敏感,用 E4M3;反向梯度跨度大、统计上更耐噪声,用 E5M2——同一轮迭代里两种格式并存。
四、为什么减半位宽≈翻倍吞吐与带宽
收益可以从三层加总。乘法器层面,尾数乘法器面积约随位数平方缩小,再省掉宽指数比较和宽移位器,FP16 MAC 的面积约为 FP32 的四分之一,FP8 再省一半。同样的 Tensor Core 面积预算,低精度 MAC 的阵列规模直接翻倍到四倍。存储层面,寄存器文件、shared memory、HBM 全部按位寻址,FP16 一个 bank 行装的元素是 FP32 的两倍,等于带宽和容量同步翻倍。复用层面,tile 大小不变时每个元素更小,片上缓存能容纳的 K 维切片更厚,Tensor Core 张量核心 一次 MMA 的等待被更多预取填满,利用率随之抬高。
实际数字印证这笔账。A100 上 FP32 稠密算力 19.5 TFLOPS,FP16/BF16 Tensor 算力 312 TFLOPS(约 16 倍,其中既有位宽红利也有矩阵单元红利);H100 上 FP16 Tensor 约 989 TFLOPS,FP8 约 1979 TFLOPS,8 位对 16 位接近严格的 2 倍。推理侧的 INT8 路线同理,A100 INT8 标称 624 TOPS,原理与校准流程见 INT8/INT4 量化与校准。
能耗角度的账同样成立。先进工艺上,一次 MAC 的能量随电路规模缩小,而一次 HBM 读取的能量相对昂贵。位宽减半同时减少了参与翻转的晶体管数和需要搬运的字节数,每 FLOP 的能耗往往不止减半——数据中心采购大模型算力时,电费和机架功率是硬约束,这也是 FP8 在 H100 世代快速普及的直接推手。
需要注意,「翻倍」的成立有前提:工作负载必须是矩阵乘这类规则算子,K 维足够长、形状对齐,且缩放/类型转换的开销能被摊薄。一个全是逐元素小算子的网络,大量时间花在格式转换和窄向量运算上,低精度反而可能更慢。另外,Tensor Core 的峰值是按 MMA 指令满射计的:矩阵边长不对齐、batch 太小、K 维太短,都会让阵列空转,实际 MFU(模型浮点利用率)常在 30%~50%,此时决定速度的是 tiling 和调度,而不是纸面位宽。
五、FP16 的坑:溢出、下溢与 loss scaling
FP16 训练有两种典型事故。上溢:梯度值超过 65504,先变成 Inf,再经加法污染权重,训练直接发散,loss 变 NaN。下溢:大量小梯度低于 2⁻²⁴(约 6×10⁻⁸)后被舍入为零,模型表面正常但参数不再更新,精度悄悄下滑。
标准对策是 loss scaling(损失缩放)。反向传播开始前把 loss 乘一个缩放因子 S(常用 2 的幂,如 128、8192),由链式法则,所有梯度同步放大 S 倍,原本落在舍入区的小梯度被抬进 FP16 的表示区间;权重更新前再把梯度除以 S 还原,而这一步在 FP32 下完成。用 2 的幂是刻意的选择:乘除 2 的幂在浮点上只改指数、不损失尾数,缩放和还原都不引入额外舍入误差。S 的取值动态调整:连续若干步(如 2000 步)未出现 Inf/NaN 就加倍,一旦出现就跳过该步、把 S 减半再重试,框架(AMP 的 GradScaler)自动管理这套逻辑。初值一般选得很激进(如 2¹⁶=65536),让前几百步快速探测到安全上限。还有一个隐蔽问题是非规格化数(subnormal):FP16 在 2⁻¹⁴ 以下进入非规格化区间,硬件处理非规格化数常伴随吞吐骤降甚至陷入微码模拟,loss scaling 把数值抬进规格化区间同时也避开了这个性能悬崖。
第二个硬支柱是 FP32 累加。即使输入是 FP16,矩阵乘沿 K 维的部分和也必须在 FP32 累加器中相加:K 维常常有数千项,每项乘积远小于部分和,用 FP16 累加会因对阶把小乘积整个吞掉。Tensor Core 的第一代规格就规定了 FP16 输入、FP32 累加,输入精度与累加精度解耦是硬件级的数值设计。配套的做法还有 FP32 master weights:优化器保留一份 FP32 主权重,前向时现场转换成低精度,更新落在主副本上——优化器状态(如 Adam 的动量、方差)对精度很敏感,这一份不能省。BF16 因为范围与 FP32 相同,通常不需要 loss scaling,但 FP32 累加和 FP32 主权重两条纪律同样适用。
六、典型 recipe 与精度损失
下图把一套主流混合精度训练的数值流串起来。
训练侧三种主流配方:AMP(自动混合精度)FP16 常见于 V100 时代及游戏卡,配 loss scaling;BF16 是 A100 及后续数据中心训练的默认选择,大部分模型直接替换、训练曲线与 FP32 重合;FP8 训练(H100 起)需要每 tensor 或每 tile 维护缩放因子,采用延迟缩放或即时统计,并在归一化、softmax、优化器等敏感处回退 FP32/BF16,框架提供 transformer engine 之类的现成 recipe。推理侧更激进:权重离线量化为 INT8 或 FP8,激活在线或离线校准,敏感层(通常是首尾层、以及 softmax 前的 logits)保留高精度。大语言模型推理中还可以把自回归阶段持续增长的 KV cache 存成 FP8/INT8——这部分内存随上下文长度线性膨胀,在长上下文场景里它才是决定并发路数的瓶颈,降精度的收益甚至超过权重本身。框架的通行做法是提供逐层白名单:矩阵乘、卷积走低精度通路,归一化、softmax、逐点残差相加留在 FP32 或 BF16,少数对量化敏感的层单独提精度,由配置而非逐行手写控制。
FP8 配方里缩放因子的更新策略是工程核心。延迟缩放(delayed scaling)用最近若干步的统计最大值决定当前步因子,硬件只需一个标量、一条缩放指令,但遇到数值突变会反应不及;即时统计(per-tile / current scaling)在每个 tile 现场扫描最大值,更稳但多一遍读数。H100 上的做法通常是前者为主、溢出时回退重算。推理时 FP8 相对 INT8 的独特好处是保留指数段:不同通道数值分布差异大时,FP8 靠浮点自适应、不依赖逐通道校准,部署链路更短;INT8 的面积和能效优势则在边缘端更被看重,两者长期共存而非替代。
精度损失的公开报告大致落在以下区间,需注意它们随模型、任务、配方浮动,只能作为数量级参考:FP16/BF16 AMP 训练,分类准确率相对 FP32 通常下降 0~0.3 个百分点,大量报告里差异在随机种子波动以内;FP8 训练大语言模型,困惑度相对 BF16 基线的相对上升多在 0.5% 以内,下游任务指标损失约 0.2~1 个百分点;FP8/INT8 推理配合良好校准时,困惑度相对损失通常低于 0.5%,小模型或尾部分布异常的层可能例外。真正的红线信号不是最终指标,而是训练中途出现 Inf/NaN、loss 曲线抬升、或某层激活长期贴着格式上限——这些说明缩放策略失效,需要收紧 S 或让该层回退高精度。
这一篇在知识体系里的位置
- 上游基础:浮点运算与 IEEE 754 讲符号/指数/尾数的通用规则;
- 消费硬件:Tensor Core 张量核心 讲 MMA 指令与各代精度算力;
- 电路视角:MAC 单元的精度设计 讲乘法器面积与累加器的硬件取舍;
- 整数路线:INT8/INT4 量化与校准 讲无指数格式如何靠缩放因子工作。
总结
混合精度的本质是用「指数买范围、尾数买精度」:FP16 有 10 位尾数但指数只到 65504,训练需 loss scaling 防溢出下溢;BF16 截自 FP32、8 位指数换来同量程,牺牲有效位换取训练稳定;FP8 让 E4M3(上限 448、精度优先)承担前向与推理、E5M2(范围优先)承担反向梯度,并按 tensor/tile 配缩放因子;所有低精度配方都遵守「低精度乘法、FP32 累加、FP32 主权重更新」三条纪律。位宽减半使尾数乘法器面积约按平方缩小、存储与带宽按字节翻倍,叠加 Tensor Core 阵列红利,换来 H100 上 FP8 对 FP16 近 2 倍、A100 上 FP16 对 FP32 约 16 倍的算力;典型精度损失在 1 个百分点以内,但 Inf/NaN 与贴限激活是配方失效的硬信号。