原文:https://zhuanlan.zhihu.com/p/619431625
1. Background
硬件的支持
许多硬件厂商的芯片开始支持 FP8 的计算,如英伟达最新的两种架构 Ada (4090) 和 Hopper (H100)。它们的 Tensor Core 计算单元都开始支持 FP8 的计算,如图所示:
在 H100 的第四代 Tensor Core 中,支持任意的 FP8 格式矩阵的乘法 (E4M3xE4M3, E5M2xE5M2, E4M3xE5M2, E5M2xE4M3)
然后会进行累加到 FP32 和 FP16 的数据格式之中
同时也支持浮点格式之间的互相转换,如下图
FP8 好处
-
FP8 Tensor Cores 比 16-bit Tensor Cores 快
-
减少 memory movement
-
如果模型已经在 FP8 中进行,部署更加方便
-
FP8 拥有更宽的动态范围
-
FP8 到 FP16/FP32/BF16 之间的转换电路,可以设计得更简单直接,而不需要像INT8/UINT8到FP的转化需要乘法和加法的开销。
2. FP8 TYPES
先简单回顾一下浮点数的表示方式:
IEEE 754
-
浮点数会分为符号位(sign), 指数位 (exponent), 和小数位 (Mantissa)
float32 的表示方法
在
Tensor Cores
中,根据指数位和小数位的不同,支持 E5M2 和 E4M3
下面分别来看看它们的浮点数表示方法:
E5M2
E5M2 遵循上面的 IEEE 754 的浮点数格式,其中
bias =2^(e-1)-1 = 15
易得最大值和最小值
max= 2^(30-15)*(1+1/2+1/4)=577344
如果指数位和小数位都为 0,则表示 0
同时,易得非规格化的最大值最小值
E4M3
E4M3 不完全遵循 IEEE 754 的数据格式,主要不同在于当指数位全为1
时,一样可以用来表示规格化的值(当小数位不为1)
,
当且仅当指数与底数部分全为1时,其表示NaN,不能用来表示 Infinites
。其中, bias = 7
计算方式还是一样的,综合一下,可参考下图:
E4M3的最大值=(2^4-1-7)*(1+1/2+1/4)=2^8*(7/4)=448
根据表示的方式,可以把浮点数看成 2 的幂之间的
2^M
个样本的精度,比如在 E5M2 中,2 和 4 之间会有 4 个样本,4 和 8 之间也会有 4 个样本;在 E4M3 中,2 和 4 之间有 8 个样本。通过这一特性,可以容易得出浮点量化的误差会随着数值变化的增大而增大。
3. Convert to FP8
先试想一下,模型的权重如果都是 [-2, 2],它们原本的数据格式都是 FP32,如果你想把它转换成 FP8,其实只需要一个 round 函数。
eg: 易得 FP8 E5M2 在 [1,2] 之间的数为 [1, 1.25, 1.5, 1.75],fp32 的值为 1.4445,那么 fp8 = round(1.4445) = 1.5
不过,FP8 E4M3 的表示范围为 [-448, 448],明显远远小于正常的 FP32 表示的范围。在一些应用上肯定还是无法表示的,办法就是引入一个缩放因子 scale,使得原 Tensor 的值可以在 FP8 的表示范围下表示,如图:
所以对于一个 FP32 的数,要想转换成 FP8, 只需要两步:
-
Unscaled FP32 = FP32 / scale
-
FP8 = Convert(Unscaled FP32)
第一步,就是尽可能地把 FP32 的数据放到 FP8 的表示范围内。
eg: 假设有 fp32 数组 [1000.0, 23.0, 123.123],最简单的操作类似于 “minmax”,所有数除以 1000.0/448, 得到 Unscaled FP32 数组 [448, 10.304, 55.104]。
第二步,其实就是上面所说的 Rounding 了,FP8 表示不了精确的 10.304 和 55.104。具体方法可见FP8 量化-原理、实现与误差分析
寻找 scaling factor
这里提供两种较为容易理解的方法:
第一种 (from Nvidia):
假设是 Per-Tensor 的量化,就是找到整个 Tensor 中的最大值,而为了减少找全局最大值带来的 memory consumption, 这里提出了一个基于一个 window, 即找到一个局部的最大值,然后再在这个局部最大值添加一个 margin 在作为 scaling factor