Motivation
最近在搭建 VLM real quantization baseline。最初的实验环境选在 RTX PRO 6000 Blackwell 上,但在实际部署过程中发现,当前主流推理框架对 Blackwell 设备上传统整数低比特量化的支持还不够成熟,尤其是常见的 W4A8、W4A16 等 INT 量化配置,在 vLLM、SGLang、TensorRT-LLM 等推理栈中的可用性、kernel 覆盖和性能表现,都与 Hopper/Ada 等架构存在明显差异。
进一步调研后发现,Blackwell 架构的低精度推理路线正在发生变化:相比传统 INT4/INT8,硬件与软件生态开始更多地围绕低精度浮点格式展开,例如 MXFP4、NVFP4 等 micro-scaling FP4 格式。这类格式在保持 4-bit 存储与计算效率的同时,通过局部 scale、浮点指数以及更细粒度的动态范围适配,试图缓解 INT 量化中常见的离群值敏感和动态范围不足问题。
与此同时,近两年的低精度量化研究也逐渐从“如何把 INT4 做稳”转向“如何利用硬件原生支持的 FP4 格式”。例如,围绕 MXFP4/NVFP4 的推理量化、FP4 全量训练、FP4 误差分析以及格式感知的旋转/缩放方法,已经在 ICLR、ICML、NeurIPS 以及 arXiv 上陆续出现。因此,我准备写下这篇 blog,作为对低精度浮点量化格式的学习笔记,也作为后续在 Blackwell 设备上构建 VLM real quant baseline 的技术背景梳理。
Note
IEEE 754 标准浮点数
我们先来看看在内存中,浮点数是如何按照 IEEE 754 标准存储的。
本质上,浮点数可以看作是用科学计数法来表示一个数:
$$ V = (-1)^S \times M \times R^E $$其中 S 表示符号位,M 表示尾数,R 表示基数,E 表示指数。对于计算机中的二进制浮点数而言,基数 R = 2。
以 IEEE 754 单精度浮点数,也就是 float32 为例,它一共占 32 bit,具体划分如下:
- 符号位
S:1 bit - 指数字段
Exponent:8 bit - 尾数字段
Fraction:23 bit
为了在有限的位宽内尽可能扩大表示范围并提高精度,IEEE 754 对指数和尾数做了特殊规定:
- 对于规格化浮点数,尾数的整数部分总是
1,因此这个最高位的1不需要显式存储,称为隐藏位。于是,尾数字段虽然只有 23 bit,但实际可以表示 24 bit 的有效数字。 - 指数字段采用偏置(bias)表示。对于
float32,指数字段占 8 bit,存储值范围为0 ~ 255,偏置值为 127。对于规格化数,真实指数为:
需要注意的是,Exponent = 0 和 Exponent = 255 有特殊含义,分别用于表示 0、非规格化数、无穷大和 NaN。因此,规格化单精度浮点数的真实指数范围是 $-126 \sim 127$。
单精度浮点数在内存中的布局如下:
bit index
31 30 23 22 0
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌───────┬────────────────────────────┬──────────────────────────────────┐
│ S │ Exponent │ Fraction │
│ 1 bit │ 8 bits │ 23 bits │
└───────┴────────────────────────────┴──────────────────────────────────┘
│ │ │
│ │ └─ fraction bits of mantissa
│ └─ biased exponent, bias = 127
└─ sign bit: 0 = positive, 1 = negative
例如,我们想用单精度浮点数表示 25.125。
首先将它转换为二进制:
整数部分:25(D) = 11001(B)
小数部分:0.125(D) = 0.001(B)
因此:
25.125(D) = 11001.001(B)
将其写成二进制科学计数法:
11001.001(B) = 1.1001001(B) × 2^4
所以:
S = 0
M = 1.1001001
Fraction = 10010010000000000000000
Exponent = 4 + 127 = 131(D) = 10000011(B)
因此,25.125 的 IEEE 754 单精度表示为:
S | Exponent | Fraction
0 | 10000011 | 10010010000000000000000
完整的 32 bit 表示为:
01000001110010010000000000000000
对应的十六进制表示为:
0x41C90000
浮点数表示会带来一定的精度损失,主要原因是并非所有十进制小数都能被有限位宽的二进制小数精确表示。例如,0.2 转换为二进制后是一个无限循环小数:
0.2(D) = 0.0011001100110011...(B)
由于计算机的位宽有限,存储时只能保留有限位,并按照 IEEE 754 的规则进行舍入,因此会产生精度误差。
在 IEEE 754 单精度浮点数下,常用的有限数表示范围大约为:
-3.4 × 10^38 ~ 3.4 × 10^38
更准确地说,单精度浮点数能够表示的最大有限正数约为:
3.4028235 × 10^38
最小正规格化数约为:
1.17549435 × 10^-38
如果考虑非规格化数,还可以表示更接近 0 的数,但精度会进一步降低。
在如今的 LLM 训练推理过程中,更通用的浮点数格式是 FP16 与 BF16。它们与 FP32 的区别在于:
- FP16 的指数位为 5,尾数位为 10
- BF16 的指数位为 8,尾数位为 7
我们知道,在总位宽固定的情况下,尾数越多则指数越少,能够表示的范围越小,但相应精度越高。因此 FP16 比 BF16 精度更高,但表示范围更小。
正因如此,BF16 通常用在训练场景(更大的动态范围更利于反向传播中的梯度表示),而 FP16 通常用在推理场景(主要是因为目前很多主流推理框架对 FP16 的 kernel 优化做得更好)。
FP8
FP8 是 Hopper 架构设备开始支持的低精度浮点数,通常有两种表示形式:E4M3 和 E5M2(其中 E 表示指数位数,M 表示尾数位数)。
同样地,E4M3 精度更高、范围更小,而 E5M2 范围更大、精度更低。
在支持 FP8 的硬件(Hopper 架构显卡)上,已经支持 FP8 格式的矩阵乘法(E4M3×E4M3、E5M2×E5M2、E4M3×E5M2、E5M2×E4M3)。
我们接下来从精度和性能两个方面,对比分析 FP8 与 INT8 在量化上的表现。
首先分析二者在精度上的表现。
由浮点数的定义可知,浮点数可以看作是在相邻 2 的幂($2^E$)之间均匀采样的 $2^M$ 个样本。例如在 E5M2 中,2(一次幂)和 4(二次幂)之间有 4 个样本,4 和 8(三次幂)之间也有 4 个样本;而在 E4M3 中,2 和 4 之间有 8 个样本。由此可知:
- E5M2 的量化误差比 E4M3 更大;
- FP8 表示的数值是非均匀的,浮点量化的误差会随着数值的增大而增大。
这正是 FP8 与 INT8 量化的最大差别:对于 FP8 而言,越靠近 0 分布越稠密,越远离 0 分布越稀疏。
接下来从数学角度分析 FP8 与 INT8 在量化上的表现。
对于 INT8,量化可以写作:
$$ \hat x = s \cdot \text{round}\left(\frac{x}{s}\right) $$其中 $s$ 是 scale。如果使用对称 INT8,那么通常有:
$$ q \in [-127, 127] $$假设一组待量化值的最大绝对值是 $\alpha$,那么 scale 大约是:
$$ s = \frac{\alpha}{127} $$量化绝对误差满足:
$$ |x - \hat x| \leq \frac{s}{2} = \frac{\alpha}{254} $$而相对误差为:
$$ \frac{|x - \hat x|}{|x|} \leq \frac{\alpha}{254\,|x|} $$可以看到,无论从绝对误差还是相对误差来看,INT8 量化都更容易受到 outlier 的影响。
而对于 FP8,假设尾数有 $M$ bit,那么在区间 $[2^e, 2^{e+1})$ 内约有 $2^M$ 个采样点,间隔为:
$$ \Delta_e = \frac{2^e}{2^M} = 2^{e-M} $$因此量化绝对误差大约满足:
$$ |x - \hat x| \leq \frac{1}{2}\,2^{e-M} $$而相对误差近似满足:
$$ \frac{|x - \hat x|}{|x|} \leq 2^{-(M+1)} $$也就是说,虽然 FP8 的绝对误差也会随着数值变大而变大,但相对误差大致稳定。
如果是 E4M3,相对误差不会超过 $6.25\%$;而 E5M2 则不会超过 $12.5\%$。
若令 INT8 的相对量化误差等于 E4M3:
$$ \frac{\alpha}{254\,|x|} = \frac{1}{16} $$可以得到:
$$ |x| \approx 0.063\,\alpha $$也就是说,当待量化值与最大值的差距不超过约 16 倍时,INT8 的量化效果往往更好;一旦超出这个范围(即数值相对 $\alpha$ 过小),FP8 则是更优的选择。
而我们知道,权重和激活值虽然都存在 outlier,但二者的数值范围并不相同:权重的 outlier 一般是普通 token 的数十倍,而激活值的 outlier 则不一定,甚至可以达到数百倍。
因此在实际部署时,一个理想的选择是权重用 INT 量化、激活值用 FP8 量化,也就是常说的 W4AFP8(在 TensorRT-LLM 中记作 W4A8)。目前的推理框架普遍推荐这种量化方式,例如 TensorRT-LLM 中:
| QUANTIZATION METHODS | PERFORMANCE IMPROVEMENT (BATCH SIZE <= 4) | PERFORMANCE IMPROVEMENT (BATCH SIZE >= 16) | ACCURACY IMPACT | CALIBRATION TIME** |
|---|---|---|---|---|
| FP8 (W8A8) | Medium | Medium | Very Low | Minutes |
| Int8 SQ (W8A8) | Medium | Medium | Medium | Minutes |
| Int8 weight-only (W8A16) | Medium | Low | Low | Not Required |
| Int4 weight-only (W4A16) | High | Low | High | Not Required |
| Int4 AWQ (W4A16) | High | Low | Low | Tens of Minutes |
| Int4 GPTQ | High | Low | Low | Tens of Minutes |
| Int4-FP8 AWQ (W4A8) | High | Medium | Low | Tens of Minutes |
可以看到,W4AFP8 在性能提升最大的同时,还能做到较低的精度影响。
接下来从性能角度比较 FP8 与 INT8。
在正式比较之前,需要先明确一点:低精度 Tensor Core 的核心,并不是把整个矩阵乘法都维持在 8 bit 精度下完成,而是采用 mixed-precision MMA 的方式。也就是说,输入矩阵以 FP8 或 INT8 这样的低精度格式参与乘法,但乘积通常会在更高精度的累加器中累加,最后再根据需要转换为目标输出精度。
这样设计的原因在于,GEMM 中每个输出元素本质上都是一个长度为 K 的点积,需要经历大量乘加操作。如果累加阶段仍然使用过低的精度,那么许多较小的增量会因为舍入而被直接吞掉,累加误差也会随着 K 的增大迅速放大。因此,低精度矩阵乘法的“低精度”主要体现在输入和乘法阶段,而不是简单地意味着整个计算链路都只有 8 bit。
从公开硬件规格来看,在支持 FP8 的 NVIDIA GPU 上,FP8 Tensor Core 和 INT8 Tensor Core 往往具有相同的标称峰值吞吐。这说明在产品级实现中,厂商可以通过专门的数据通路设计、流水线化、tile 级并行以及 mixed-precision 累加机制,使 FP8 和 INT8 在峰值计算能力上达到相同水平。
但这并不意味着 FP8 和 INT8 的底层硬件代价完全相同。INT8 的计算本质上是整数/定点乘加,数据格式规则,乘法和累加逻辑相对简单;而 FP8 虽然同样只有 8 bit,但它包含符号位、指数位和尾数位,计算时需要处理指数对齐、尾数运算、规格化、舍入以及高精度浮点累加等逻辑。因此,从单个 MAC 单元或数据通路的实现复杂度来看,FP8 通常比 INT8 更复杂。
所以更准确的理解是:理论上,FP8 运算单元的硬件实现成本高于 INT8;但在 Hopper、Blackwell 这类专门面向 AI 负载设计的 Tensor Core 中,NVIDIA 通过专门的硬件设计把 FP8 和 INT8 的峰值吞吐做到了相同水平。也就是说,FP8 的优势并不是“硬件更简单”,而是在相近吞吐下提供了更大的动态范围,从而更适合大模型中激活值波动大、outlier 明显的场景。(简单来说:放心大胆地用 FP8 吧,NVIDIA 帮你兜底了。)
FP4 / MXFP4 / NVFP4
在 4-bit 下,想要像 FP8 那样直接使用 FP4 是不现实的,因为 FP4 能表示的数值范围实在太小了。在 OCP 标准中,FP4 一般取 E2M1,它能表示的值大概是:
$$ 0,\ \pm 0.5,\ \pm 1,\ \pm 1.5,\ \pm 2,\ \pm 3,\ \pm 4,\ \pm 6 $$也就是说,单单使用 FP4,能够表示的数值范围相当有限。因此实际应用时,通常不会只存储一个 FP4 值,还会额外存储一个 scale:
$$ \text{real num} = \text{Encoding of FP4} \times \text{scale} $$scale 的不同对应着目前两种主流的 FP4 格式(Blackwell 确实支持 FP4 这种数值格式,但没有针对 scale 的专用硬件加速)。
我们先来看 MXFP4,它的 scale 格式是每 32 个值共享一个 E8M0 scale,也就是 2 的幂形式的 scale。
接下来按照与 FP8 相同的思路分析它的量化误差(取 block 内最大绝对值为 $A$):
$$ \hat x_i = s_{mx} \cdot Q_{\text{E2M1}}\left(\frac{x_i}{s_{mx}}\right) $$一般而言理想的 scale 是 $\frac{A}{6}$;然而在 MXFP4 中 scale 必须是 E8M0 格式,因此需要将其量化为最近的 2 的幂:
$$ s_{mx} = 2^{\text{round}\left(\log_2 \frac{A}{6}\right)} $$在 TensorRT 中一般会向上取整。为了计算方便,我们定义:
$$ \rho_{mx} = \frac{s_{mx}}{A/6} $$由于 E8M0 是 2 的幂,向上取整时有:
$$ 1 \leq \rho_{mx} < 2 $$由于任意量化器的量化误差不会超过一个量化间隔,因此我们有:
$$ |e_i| \leq s_{mx} = \rho_{mx}\,\frac{A}{6} $$而 $\rho_{mx}$ 与 block 内最大值和 2 的幂的对齐程度相关:当 $A$ 恰好为 2 的幂时 $\rho_{mx}$ 接近 1,误差较小;当 $A$ 略大于某个 2 的幂时 $\rho_{mx}$ 接近 2,误差较大。
例如 $A/6 = 1.99$ 与 $A/6 = 2.01$:前者会被向上取整为 2,此时 $\rho_{mx}$ 接近 1;后者会被向上取整为 4,此时 $\rho_{mx}$ 约为 2。
若对齐得好,则有:
$$ |e_i| \leq \frac{A}{6} $$否则为:
$$ |e_i| < \frac{A}{3} $$除了绝对误差外,我们再看看它的归零阈值(即低于该值会被量化为 0):
$$ |x| < 0.25\,s_{mx} = \rho_{mx}\,\frac{A}{24} $$于是有:
$$ \frac{A}{24} \leq \text{zero threshold} \leq \frac{A}{12} $$大约是 $4.17\% A$ 到 $8.33\% A$。
接下来看看 NVFP4,它与 MXFP4 的区别在于:
- block 更小:每 16 个元素共享一个 scale;
- scale 更细:NVFP4 的 block scale 采用 FP8 E4M3,而不是 E8M0。
此外,NVFP4 还会额外使用一个全局的(per-tensor)FP32 scale 来避免 overflow。一般而言:
$$ s_{\text{global}} = \frac{\text{global}_{amax}}{6 \times 448}\\[4pt] s_{\text{block}} = \frac{\text{block}_{amax}/6}{s_{\text{global}}} $$其中 6 是 E2M1 的最大可表示值,448 是 E4M3 的最大可表示值,二者相乘保证 block scale 落在 E4M3 的表示范围内。
同样地,我们可以把 NVFP4 写作:
$$ \hat x_i = s_{\text{global}} \cdot s_{\text{block}} \cdot Q_{\text{E2M1}}\left(\frac{x_i}{s_{\text{global}} \cdot s_{\text{block}}}\right) $$令
$$ s_{nv} = s_{\text{global}} \cdot s_{\text{block}} $$那么同样地有:
$$ \rho_{nv} = \frac{s_{nv}}{A/6} $$又因为此时 scale 为 E4M3,其相邻正规格化数的比例最多约为:
$$ 1 + \frac{1}{8} = \frac{9}{8} $$因此取向上取整时:
$$ 1 \leq \rho_{nv} \leq \frac{9}{8} $$于是 NVFP4 的绝对误差上界约为:
$$ |e_i| \leq \rho_{nv}\,\frac{A}{6} \leq \frac{3}{16}A $$约 $18.75\% A$,这明显好于 MXFP4 在未对齐时的最坏情况。
而 NVFP4 的归零阈值为:
$$ \text{zero threshold} \leq 4.69\% A $$至于 INT4,可以通过与 INT8 类似的方式分析:对称量化下 $q \in [-7, 7]$,scale 为 $s = A/7$,因此绝对误差上界为 $|e_i| \leq s/2 = A/14$,归零阈值同样为 $A/14 \approx 7.14\% A$。
需要注意的是,INT4 的绝对误差上界(约 $7.14\% A$)看似比 NVFP4(约 $18.75\% A$)更小,但这是均匀分布在整个量程上的误差;而对于神经网络中占绝大多数的小数值,FP4 的误差与数值大小成比例,这些小值上的实际误差远小于 INT4 的均匀误差。再结合 FP4 对长尾异常值更友好的动态范围,可以得出结论:在 4-bit 量化中,面对神经网络这种“大量小值 + 长尾异常值”的分布,NVFP4 的综合表现最好。
而从性能上分析,虽然 MXFP4 由于采用 32 元素 block 和 E8M0 power-of-two scale,理论上具有更低的 scale 存储与缩放复杂度,但在真实的 LLM/VLM 推理中,性能并不只由单个 GEMM 的格式开销决定。NVFP4 通过 16 元素 micro-block 和 E4M3 scale 显著降低了量化误差,使更多层能够稳定进入 FP4 路径,减少混合精度 fallback 和额外补偿。因此,在当前 Blackwell + TensorRT-LLM 生态下,NVFP4 往往是更实际的高性能 FP4 选择;而 MXFP4 更适合在对精度不敏感、或有专门误差补偿方法的场景中追求更高的硬件效率。
此外,目前主流推理框架都对 NVFP4 做了专门优化,是性能—精度权衡下的最佳选择。