FP8 正在成为深度学习硬件和模型部署的一个关键变量。你可能会注意到,无论是英伟达全新的 Ada(4090)还是 Hopper(H100)架构,它们的 Tensor Core 都原生支持了 FP8 计算。这不仅仅是新增一种数据类型那么简单,它背后牵扯到硬件利用率、显存带宽、推理部署效率等一系列问题。
简单来说,FP8 的硬件支持意味着更快的矩阵运算、更少的显存搬运,以及对动态范围的更好利用。H100 的第四代 Tensor Core 支持的 FP8 格式组合相当灵活,包括 E4M3×E4M3、E5M2×E5M2,以及两者的混合形式,并且最终累加结果可以落到 FP32 或 FP16 上。同时,硬件层面也集成了浮点格式之间的高效转换电路,这些设计上的便利性是 INT8 量化所不具备的,因为 INT8 与浮点格式的转换往往需要额外的乘法和加法开销。


2. FP8 的两种常见格式
既然提到了 E4M3 和 E5M2,不妨先简单回顾一下浮点数的底层表示方式,这对理解后面量化中的精度问题很有帮助。
IEEE 754 浮点数
一个标准的浮点数由三部分组成:符号位(sign)、指数位(exponent)和小数位(mantissa)。

根据指数位的值,浮点数可以分为规格化数、非规格化数以及特殊值(无穷和 NAN)。

规格化数的计算公式如下:

在 Tensor Core 中,常见的两种 FP8 格式就是 E5M2 和 E4M3,它们分别对应不同的指数位和小数位分配。

E5M2
E5M2 完全遵循 IEEE 754 标准,其 bias 值为 15(2^(5-1)-1)。
以规格化数为例,例如二进制序列 0 | 01101 | 01:

根据这个公式,很容易计算出 E5M2 的规格化最大值和最小值:

最大值结果是 2^(30-15) * (1 + 1/2 + 1/4) = 57344。

而当指数位和小数位、符号位全为 0 时,则表示数值 0。

同样可以推出非规格化的最大值和最小值:

当指数位全为 1 时,则表示特殊值。小数位为 0 时代表无穷(Infinity),小数位非 0 时代表 NaN。

E4M3
E4M3 则不完全遵循 IEEE 754 标准。它的一个关键区别在于:当指数位全为 1 时,只要小数位不全为 1,一样可以用来表示规格化数;只有当指数位和小数位同时为全 1 时,才表示 NaN。也就是说,E4M3 没有无穷大的表示能力。它的 bias 值为 7。
综合计算方式可以参考下图:

通过公式可以算出 E4M3 的最大值:(2^(14-7)) * (1 + 1/2 + 1/4) = 2^8 * 1.75 = 448。
这里可以顺带总结一个规律:浮点数在数轴上的分布并不是均匀的。你可以把它看成是 2 的幂之间的一组离散样本。比如在 E5M2 中,2 和 4 之间有 4 个样本点,4 和 8 之间同样只有 4 个样本点。而 E4M3 在同样的区间内则有 8 个样本点。这意味着浮点量化的误差会随着绝对数值的增大而增大。

3. 如何转换成 FP8
假设模型的权重全部分布在 [-2, 2] 这个范围内,并且原始数据是 FP32。如果我们想直接转成 FP8,操作其实很简单,一个 round 函数就够了。
举个例子,FP8 E5M2 在 [1, 2] 区间内的可表示数值为 [1.0, 1.25, 1.5, 1.75]。如果一个 FP32 的数是 1.4445,那么量化后的结果就是 round(1.4445) = 1.5。
问题在于,FP8 E4M3 的表示范围只有 [-448, 448],这个范围比 FP32 小得多。要让一个值域更大的张量也能被 FP8 表示,就不得不引入一个缩放因子(scale factor)。核心思路是,通过一个缩放因子将原始的张量值“挤入” FP8 的表示区间内。

所以,把一个 FP32 的数转成 FP8,本质上就两步:
- Unscaled FP32 = FP32 / scale
- FP8 = Convert(Unscaled FP32)
第一步的目的是让缩放后的数值尽可能落在 FP8 的表示范围内。比如有一个 FP32 数组 [1000.0, 23.0, 123.123],最简单的方式就是用 minmax 策略:所有数除以 (1000.0 / 448),得到 unscaled 数组 [448, 10.304, 55.104]。第二步就是之前说的 rounding 操作,因为 10.304 和 55.104 都无法被 FP8 精确表示。
如何找到合适的 scaling factor
这里提供两种比较直观的方法。
第一种来自 Nvidia 的方案。它会先在一个局部窗口(window)内寻找张量的局部最大值,然后在这个局部最大值的基础上加上一个 margin,作为最终的 scaling factor。这种做法可以在一定程度上减少寻找全局最大值带来的内存开销。

第二种来自 PPQ 开源库。根据其作者张志在知乎文章中的发现,FP8 对 scale 的选择其实并不敏感。因此他们直接预设了一组候选 scale,比如 [0.0078125, 0.03125, 0.125, 1.0, 4.0, 16.0, 64.0],然后通过计算 MSE 损失来挑选其中表现最好的那个。
4. FP8 矩阵乘法的实际过程
在实际的硬件中,FP8 矩阵运算的宏观流程大致如下:

5. FP8 的性能表现
GEMM 性能
在 H100 上,FP8 矩阵乘法的性能提升幅度相当可观。

深度学习训练性能
对于 GPT 模型,训练速度的提升同样明显。

图像分类
在图像分类任务上,大部分模型在使用 FP8 训练后,精度几乎没有明显下降。

不仅如此,FP8 的训练过程曲线也与 FP16 高度相似,这意味着在实践中,FP8 可以作为一个可靠的替代格式来使用。

