低精度数值格式小结:FP8 / INT8 / FP4 / INT4 以及为什么训练更偏爱 FP8
1. 总体心智模型
在深度学习里,常见的低精度格式大致分两类:
-
浮点型(FP 系):FP16、BF16、FP8、FP4
- 有 指数位 + 尾数位
- 更像"缩小版浮点",数值分布近似 指数/对数刻度
- 适合:训练 + 高端 GPU 推理
-
整数型(INT 系):INT8、INT4
- 本体是均匀的整数格子,靠
scale(+ zero_point)映射回实数 - 数值分布是 线性刻度
- 适合:推理量化,特别是跨平台部署
- 本体是均匀的整数格子,靠
一句话:
FP8/FP4 = 小号浮点(内建动态范围)
INT8/INT4 = 量化码本(线性格子 + 手动 scale)
2. 四种格式快速对比
| 格式 | 类型 | bit 数 | 分布方式(直觉) | 典型用途 |
|---|---|---|---|---|
| FP8 | 浮点 | 8 | 指数味道(对数刻度) | 高端 GPU 上的大模型训练 + 推理 |
| INT8 | 整数 | 8 | 线性均匀格子 | 推理量化(CPU/GPU/TPU/NPU 通吃) |
| FP4 | 浮点 | 4 | 更粗的指数刻度 | Blackwell 等上的极致压缩推理 |
| INT4 | 整数 | 4 | 线性格子(只有 16 档) | LLM 权重量化、QLoRA 等 |
3. 各格式数值直觉 & 例子
3.1 INT8:线性整数 + scale
本体:
- 有符号 INT8:
q ∈ [-128, 127],一共 256 个整数格子,均匀间隔 1。
在深度学习里的用法:
- 不直接拿 -128~127 当实数,而是:
real ≈ q × scale
举例:
某层权重大致在 [-6, 6],选 scale = 0.05:
- 表示范围:
[-128×0.05, 127×0.05] ≈ [-6.4, 6.35] - 步长(两格之间):
0.05
直觉:
INT8 = "256 个等间距格子 × 一个可调的 scale"
→ 你可以用不同 scale,让这 256 格对准某一小段实数区间。
3.2 FP8:8 位浮点(E4M3 / E5M2)
以常见的 E4M3 为例(1 符号 + 4 指数 + 3 尾数):
-
仍然只有 8 bit → 256 种 bit pattern;
-
只是这些 pattern 被拆成:
- 符号位 S
- 指数 E(决定数量级)
- 尾数 M(决定该数量级里的细分)
大致特性(以 E4M3 为例):
- 最小非零正数 ≈ 0.00195
- 最小正规正数 ≈ 0.015625
- 最大有限值 ≈ 448
- 中间是按数量级分段的一堆离散点(加上一些 NaN)
关键点:
-
不是连续 [-448, 448] 都 0.001 一格,而是:
- 小数附近(比如 ~0.015625)步长 ~0.002
- 在 1 附近步长 ~0.125
- 数值越大,绝对步长越大(所有浮点都是这样,只是 FP8 更粗)
直觉:
FP8 = "256 个离散点按指数分散在很多数量级上"
→ 像是 缩小版浮点数,有较大的动态范围,但每个数量级里刻度比较稀。
3.3 INT4:4 位整数 + scale
本体:
- 有符号 INT4:
q ∈ [-8, 7],一共 16 个整数格子
量化用法:
和 INT8 类似:
real ≈ q × scale
举例:某一小组权重用 scale = 0.1:
- 表示范围:
[-0.8, 0.7] - 可用值只有 16 个:
-0.8, -0.7, ..., -0.1, 0, 0.1, ..., 0.7
常见做法:
- per-group/per-channel scale:每 64/128 个权重一组,各自有 scale;
- 专门照顾 outlier(极端值)避免拉爆整组。
直觉:
INT4 = "每组只有 16 档的小线性尺子 + scale",
但配合分组量化、outlier 技巧,权重 4bit 仍然可用 → 换来 4× 压缩。
3.4 FP4:4 位浮点
FP4 具体格式各家不完全一样,这里只用"直觉版"描述:
-
仍然是:符号 + 少量指数 + 少量尾数;
-
总体只有 16 个状态,其中:
- 若干是有效实数(分布在不同数量级上)
- 若干是 NaN / 特殊值
行为特征:
- 动态范围仍可跨几个数量级(因为有指数);
- 但每个数量级里可能只有 2~4 个点 → 非常粗。
直觉:
FP4 = "更残血的缩小版浮点",
适合拿来压那些对精度不那么敏感的权重层,换极致显存/带宽。
4. FP8 vs INT8:为什么训练更偏向 FP8?
核心总结:
INT8 ≈ 线性分布,FP8 ≈ 指数分布
现在看训练到底要啥,就明白为啥 FP8 更讨喜了。
4.1 训练的数值特点
训练过程中,下面这些东西都在变:
- 权重
- 激活
- 梯度 & 优化器状态(m, v, …)
它们有几个重要特征:
-
动态范围跨度极大
- 有的激活在 1e-1 级
- 有的梯度在 1e-5 甚至更小
- 中间值可能到几十甚至上百
-
分布随时间变化(不同 step/batch 迭代中在飘)
-
对 相对误差 很敏感:
- 更新量常常是 1e-4 × 1e-3 这种级别
- 很多"小变化"其实很关键,不能全被当成 0
总结一下:
训练需要的是:跨大范围、相对误差大致可控、对分布漂移不那么敏感的格式。
这正好是"浮点 + 指数"的优势领域。
4.2 为啥 FP8 在训练 里比 INT8 顺手?
① 动态范围:FP8 自带,INT8 完全吃 scale
-
FP8:
- 指数决定数量级,动态范围(比如 [-448, 448])写死在格式里;
- 不同层、不同时间数值变大变小时,只要没超范围,指数自动在帮你"换挡";
- 再配点 per-tensor 的 scale/amax,还能进一步稳住。
-
INT8:
-
有效范围几乎完全由
scale决定:- scale 太小:数据稍微大一点就饱和在 ±127;
- scale 太大:很多小值被量成 0。
-
训练时分布在动,你得不停算动态 scale,否则:
- 不是大值被截顶,就是小梯度归零。
-
效果:
- FP8:更像"自带可伸缩表盘",你只需要少量辅助 scaling。
- INT8:是"死死写死刻度的直尺",你必须每次手动调"缩放倍率"。
② 小梯度 & 更新噪声:INT8 特别容易把细节抹平
训练更新大概长这样:
w_{t+1} = w_t - η · g_t
- 学习率 (η) 可能是 1e-4、1e-5
- 梯度 (g_t) 很多在 1e-3 甚至更小
在 INT8 上:
- 为了不让常规激活/权重溢出,scale 通常不能太小;
- 结果是:大量 1e-4、1e-5 级别的值 乘以 1/scale 后 < 0.5,直接 round 成 0;
- 等价于:一堆本应该有的细小更新被当成"啥都没发生"。
FP8 虽然精度粗,但因为有指数:
- 在小数那段(比如 0.01~0.1)仍然有一些离散点;
- 再加上累加用 BF16/FP16,整体的训练轨迹虽然有噪声,但不会像 INT8 那样"全削平"。
③ 相对误差:浮点更贴合优化过程的"倍数世界"
训练里的很多操作都是乘法 / 归一化 / 标准化,本质上在玩比例:
- 比如 LayerNorm、Softmax、scale-attention 等,
- 你更关心的是「这个值比那个值大几倍」,而不是绝对差。
浮点的指数 + 尾数分布,是"对于同一数量级的值,相对误差大致稳定",适合这类优化。
INT8 的线性格子 + 一个 scale,很难同时兼顾:
- 小量级不全变 0
- 大量级不饱和
做得到,但要非常复杂的动态量化策略,工程成本高。
5. 实战分工(推荐)
你可以记住这么一个分工心智:
-
训练主力:
-
老/普通卡:BF16 / FP16 混合精度;
-
新一代(H100 / B100 / GB200 / TPU v7):
- FP8 + BF16/FP16 混合(权重/激活 FP8,累加 & 部分关键层 BF16/FP16)。
-
-
推理主力:
- 想简单:FP16 / BF16 推理;
- 想省钱、跨平台:INT8 / INT4 量化(CPU/GPU/TPU/NPU 都吃这一套);
- 在 NVIDIA 高端卡上冲极限:FP8 / FP4 + 少量 BF16/FP16 再结合 INT8/INT4 做极致压缩。
一句话总结:
训练 → 更喜欢 FP8 这种"对数刻度的小浮点"
推理 → 更喜欢 INT8/INT4 这种"线性刻度 + 可调 scale"的量化整数
6. 关键要点速记
- FP8/FP4 是浮点格式:有指数位,动态范围内建,适合训练中的动态数值分布
- INT8/INT4 是整数格式:线性刻度 + scale,适合推理量化和跨平台部署
- 训练偏爱 FP8 的原因:
- 自带动态范围,无需频繁调整 scale
- 对小梯度友好,不会轻易量化成 0
- 相对误差稳定,适合优化算法
- 推理偏爱 INT8/INT4 的原因:
- 硬件支持广泛(CPU/GPU/NPU 都支持)
- 量化算法成熟,工具链完善
- 存储和带宽效率高