大模型量化从0到1(四):FP16、INT8、INT4、FP8、NF4 到底差在哪
前三篇我们讲了为什么要量化、量化背后的数学,以及 PTQ、QAT 两条主要路线。
这一篇,我们把视角拉到最底层:
- FP32、FP16、BF16 的
32和16到底代表什么? - 同样是 16 位,为什么 BF16 比 FP16 更适合训练?
- 同样是 8 位,FP8 和 INT8 为什么完全不是一回事?
- INT4 和 NF4 都只有 4 位,为什么使用场景却不一样?
- 4-bit 模型,真的只占 FP16 的四分之一显存吗?
说到底,所有这些问题都指向同一件事:
一个数字的有限 bit,究竟拿来表示范围、精度,还是量化索引?
理解这一点,后面的格式选型就不再需要死记硬背。
目录
- 一、一个数字在内存里长什么样
- 二、整数和浮点,是两种完全不同的表示思路
- 三、逐个拆解:每种格式的 bit 怎么排
- 四、一张全景表:范围、精度和显存如何权衡
- 五、上手实测:亲眼看见数字被“压缩”
- 六、实际选型:训练、推理和微调分别怎么选
- 七、常见误区与避坑
- 八、小结
一、一个数字在内存里长什么样
我们平时写:
x = 0.85
看起来,计算机里好像真的存了一个“0.85”。
但计算机并不认识十进制小数。内存里最终只能存一串 0 和 1。所谓数据格式,就是一套约定:
看到这串 bit 时,应该怎样把它解释成一个数字?
同一个 0.85,使用不同格式存储,得到的 bit 排列不同,占用空间不同,能保留的精度也不同。
先建立最基本的量纲感:
1 bit = 1 个二进制位,只能是 0 或 1
8 bit = 1 Byte
FP32 = 32 bit = 4 Byte
FP16 = 16 bit = 2 Byte
BF16 = 16 bit = 2 Byte
INT8 = 8 bit = 1 Byte
INT4 = 4 bit = 0.5 Byte
NF4 = 4 bit = 0.5 Byte
因此,只计算纯权重时:
理论权重大小 = 参数量 × 每参数位数 ÷ 8
以一个 70 亿参数模型为例:
| 格式 | 理论权重大小 |
|---|---|
| FP32 | 28 GB |
| FP16 / BF16 | 14 GB |
| INT8 / FP8 | 7 GB |
| INT4 / NF4 | 3.5 GB |
这就是为什么从 FP16 降到 4 bit,理论权重显存能够缩小到四分之一。
但这里有一个非常重要的“理论”二字。
实际量化模型还需要存储:
- scale;
- zero-point;
- 分组信息;
- 对齐和填充;
- 量化格式元数据。
而模型运行时还需要:
- 激活值;
- KV Cache;
- 临时计算空间;
- CUDA Kernel 工作区。
所以:
4-bit 权重的理论大小是 0.5 Byte/参数,但整个模型运行时的显存,不会严格等于 FP16 的四分之一。
格式首先决定的是“每个权重的理论存储成本”,而不是整个推理系统的全部显存。
二、整数和浮点,是两种完全不同的表示思路
这些格式大体可以分成三类:
- 浮点格式:FP32、FP16、BF16、FP8;
- 整数量化格式:INT8、INT4;
- 非均匀码本格式:NF4。
先看最核心的两大家族:整数和浮点。
2.1 整数:刻度均匀的直尺
整数可以理解成一把刻度均匀的直尺。
例如,一个 INT8 存储单元一共有:
2⁸ = 256
种不同的 bit 组合。
对于有符号 INT8,原始整数编码通常是:
-128, -127, ..., 0, ..., 126, 127
但量化模型并不是只能表示这些整数。它会用一个 scale,把整数刻度映射回浮点空间:
q = round(x / scale) + zero_point
x̂ = scale × (q - zero_point)
其中:
x是原始浮点数;q是量化后的整数;x̂是反量化后的近似值;scale决定每一格有多宽;zero_point决定哪个整数对应真实的 0。
假设:
scale = 0.01
zero_point = 0
那么整数:
-2, -1, 0, 1, 2
对应的真实数值就是:
-0.02, -0.01, 0, 0.01, 0.02
相邻刻度之间始终相差 0.01。
这就是整数表示的核心特点:
在同一个 scale 下,刻度是均匀分布的。
好处是结构简单,硬件也容易高效计算。
坏处是,如果一组数据里同时存在大量小值和少数特别大的离群值,统一刻度就很难兼顾:
- scale 太小,大值会超出范围;
- scale 太大,小值会被粗糙地挤在几个刻度里。
这也是 per-channel、per-group、AWQ、GPTQ 等方法需要重点处理的问题。
2.2 浮点:刻度会随数量级变化的软尺
浮点数不是直接存储数值,而是把数字写成类似科学计数法的形式。
十进制科学计数法是:
1.23 × 10⁵
二进制浮点数则近似写成:
1.xxxxx × 2ⁿ
因此,一个典型浮点格式会把 bit 分成三段:
[符号位] [指数位] [尾数位]
对于普通的规格化二进制浮点数,其数值大致为:
值 = (-1)^符号位 × 1.尾数 × 2^(指数 - bias)
三部分分别负责:
- 符号位:决定正负;
- 指数位:决定数量级,也就是范围;
- 尾数位:决定有效数字,也就是精度。
最重要的一句话是:
指数位决定能表示多大、能表示多小;尾数位决定相邻数字能有多接近。
浮点数的刻度不是均匀的。
它在较小数值附近刻度比较密,在较大数值附近刻度比较稀。更准确地说,浮点数大致保持的是相对精度,而不是固定的小数位数。
例如 FP16:
- 在
1附近,相邻两个数的间距约为0.0009765625; - 在
1024附近,相邻两个数的间距已经变成1; - 在
2048附近,相邻两个数的间距变成2。
也就是说,FP16 可以精细地区分:
1.0000 和 1.0010
但到了几千附近,它可能已经无法区分两个只相差 0.5 的数字。
所以,“浮点精度高”并不意味着它能在所有数量级上保留相同的小数位数。
2.3 一个容易被忽略的现实:硬件决定谁更快
从编码结构上看,整数乘法通常比浮点乘法简单。
但在现代 GPU 上,不能直接得出“整数一定比浮点快”的结论。
因为真正的速度还取决于:
- GPU 是否有对应格式的原生计算单元;
- 推理框架有没有高效 Kernel;
- 数据是否需要频繁反量化;
- 算子是否受显存带宽限制;
- batch size 和矩阵尺寸是否足够大。
例如,一张 GPU 可能对 BF16 和 FP8 有非常强的 Tensor Core 支持,却没有高效的通用 INT4 计算路径。
所以:
格式决定理论可能性,硬件和 Kernel 决定实际性能。
三、逐个拆解:每种格式的 bit 怎么排
3.1 FP32:传统全精度,也是数值安全网
FP32 = 1 位符号 + 8 位指数 + 23 位尾数
总共 32 bit,也就是 4 Byte。
由于规格化浮点数最高位的 1 通常可以省略不存,FP32 实际拥有约 24 位二进制有效精度。
它的特点是:
- 最大有限值约为
3.4 × 10³⁸; 1附近的最小间距约为1.19 × 10⁻⁷;- 范围大;
- 精度高;
- 占用空间也最大。
早期深度学习训练通常大量使用 FP32。今天即使采用混合精度,FP32 也没有消失。
它仍然经常用于:
- 梯度累加;
- 优化器状态;
- master weight;
- 归一化等数值敏感算子;
- 结果对照和误差分析。
所以,把 FP32 理解成“已经被淘汰的格式”并不准确。
更准确的说法是:
模型的大规模矩阵乘法越来越少完全使用 FP32,但 FP32 仍然承担着数值稳定器的角色。
3.2 FP16:精度不错,但指数范围较窄
FP16 = 1 位符号 + 5 位指数 + 10 位尾数
总共 16 bit,也就是 2 Byte。
与 FP32 相比:
- 指数从 8 位减少到 5 位;
- 尾数从 23 位减少到 10 位;
- 存储空间减半。
FP16 在 1 附近的间距约为:
2⁻¹⁰ = 0.0009765625
这意味着它在常见数量级上的精度通常不错。
但 FP16 最大的弱点是指数位太少。
它能表示的最大有限值只有:
65504
一旦计算结果超过这个范围,就可能变成:
inf
也就是无穷大。
另一方面,过小的梯度也可能发生 underflow,被舍入成 0。
因此,FP16 混合精度训练常常要配合 loss scaling:
- 先把 loss 和梯度放大;
- 避免小梯度直接变成 0;
- 如果检测到溢出,再动态调整缩放比例。
需要注意,loss scaling 主要缓解的是小梯度下溢,并不能自动解决所有激活值或中间结果的溢出问题。
3.3 BF16:牺牲尾数精度,换取接近 FP32 的范围
BF16 = 1 位符号 + 8 位指数 + 7 位尾数
BF16 也是 16 bit、2 Byte,但它与 FP16 的取舍完全不同。
它保留了和 FP32 相同宽度的 8 位指数,只把尾数缩短到 7 位。
结果是:
- 最大有限值约为
3.39 × 10³⁸; - 数量级范围和 FP32 基本相同;
- 不容易像 FP16 那样因数值稍大就溢出;
- 但尾数精度明显低于 FP16。
BF16 在 1 附近的间距是:
2⁻⁷ = 0.0078125
相比之下,FP16 在 1 附近的间距只有:
0.0009765625
所以同样是 16 位:
- FP16 的近距离刻度更细;
- BF16 能覆盖的数量级更大。
这正好契合了大模型训练的特点:
训练过程通常更怕范围不够导致溢出,而不是每个数少保留几位有效数字。
因此,在硬件支持的情况下,BF16 往往是大模型训练更省心的默认选择。
不过,“BF16 不会溢出”也是错误的。
它只是范围接近 FP32,仍然可能因为除零、指数爆炸、异常梯度等原因产生 inf 或 NaN。BF16 提高了安全边界,但不是数值稳定的万能保险。
3.4 INT8:成熟、稳妥的整数量化档位
INT8 = 8 bit = 256 种编码
有符号 INT8 的原始整数范围通常是:
[-128, 127]
为了让正负两侧完全对称,对称量化中也经常只使用:
[-127, 127]
需要区分两个概念:
- INT8 的整数编码范围是固定的;
- 它能覆盖的真实浮点范围,由
scale和zero-point决定。
例如 scale 为 0.01 时,127 对应 1.27;scale 为 10 时,127 对应 1270。
INT8 常见的量化形式包括:
- W8A16:权重 8 位,激活仍是 16 位浮点;
- W8A8:权重和激活都量化为 8 位;
- per-tensor;
- per-channel;
- per-group。
INT8 的优点是:
- 量化级别有 256 个,精度通常较容易保住;
- 硬件和软件生态相对成熟;
- 适合作为生产量化的第一档基线。
但“INT8 一定只掉不到 1%”这类说法并不严谨。
实际精度取决于:
- 模型结构;
- 任务类型;
- 校准数据;
- 权重还是激活被量化;
- 量化粒度;
- 是否存在异常离群值。
更稳妥的结论是:
INT8 通常是风险较低的量化起点,但仍然必须在真实任务上验证。
3.5 INT4:存储极小,但只有 16 个刻度
INT4 = 4 bit = 16 种编码
有符号 4 位整数的原始范围通常是:
[-8, 7]
在对称量化中,也常使用:
[-7, 7]
这样可以让正负两侧完全对称,但相当于只用了 15 个有效整数级别。
无论具体实现如何,核心事实不变:
INT4 一共只有大约 16 个可用刻度。
这也是它的优势和弱点:
- 理论存储只有 0.5 Byte/参数;
- 相比 FP16,纯权重可缩小到四分之一;
- 但每个分组里只有十几个重建值可选,误差明显增大。
为了让 INT4 真正可用,实际系统通常还会配合:
- per-channel 或 per-group scale;
- 更小的 group size;
- 离群值处理;
- AWQ;
- GPTQ;
- SmoothQuant;
- 混合精度保留敏感层。
消费级 GPU 部署大模型时,INT4 经常是显存和质量之间很实用的折中。
但这里必须强调:
INT4 权重不等于整个计算过程都使用 INT4。
很多所谓 4-bit 模型实际是:
W4A16
也就是:
- 权重以 4 bit 打包存储;
- 计算前按块反量化;
- 激活值和矩阵乘法仍使用 FP16 或 BF16。
因此,4-bit 最确定的收益是减少权重存储和显存带宽,并不自动意味着:
- 计算量缩小四倍;
- 延迟缩小四倍;
- 吞吐提高四倍。
是否加速,仍然取决于硬件和反量化 Kernel。
3.6 FP8:把浮点的“伸缩刻度”压进 8 位
FP8 只有 8 bit,但仍然保留了符号、指数和尾数结构。
常见的两种变体是:
E4M3 = 1 位符号 + 4 位指数 + 3 位尾数
E5M2 = 1 位符号 + 5 位指数 + 2 位尾数
名字本身就是配方:
E4M3:4 位 exponent,3 位 mantissa;E5M2:5 位 exponent,2 位 mantissa。
二者取舍如下:
| 变体 | 特点 |
|---|---|
| E4M3 | 尾数多一位,精度稍好,但范围较小 |
| E5M2 | 指数多一位,范围更大,但精度更粗 |
在常见 FP8 变体中:
- E4M3 最大有限值大约为
448; - E5M2 最大有限值大约为
57344。
不同标准和硬件可能采用略有差异的 FP8 编码,因此具体范围要以实现为准。
早期常见的混合方案是:
- 前向权重和激活使用 E4M3;
- 反向梯度使用范围更大的 E5M2。
但这不是硬性规定。现代实现还可能使用:
- 全 E4M3;
- 动态 scaling;
- delayed scaling;
- per-tensor scaling;
- per-block scaling。
这也说明一个重要事实:
FP8 通常也不是简单地把张量执行一次
.to(fp8)就结束了。
它同样需要缩放策略,避免数值超过 FP8 的有限范围。
与 INT8 相比:
- INT8 的刻度在一个 scale 下均匀分布;
- FP8 的刻度随指数变化,能够在同一个块内兼顾不同数量级的值。
不过,不能简单说“FP8 的动态范围一定比 INT8 大”。
因为 INT8 的真实范围也可以通过 scale 调整,而现代 INT8 还会使用 per-channel、per-group 等更细粒度的缩放。
FP8 的真正优势通常来自三者结合:
- 浮点式非均匀刻度;
- 合适的 scaling 策略;
- 新硬件上的原生 FP8 Tensor Core。
3.7 NF4:它不是标准浮点,而是一个 4 位非均匀码本
NF4 = NormalFloat 4
NF4 也只有 4 bit,因此每个值只能存储 16 种编码。
但它和普通 INT4 的关键区别是:
- INT4 通常对应均匀刻度;
- NF4 对应 16 个预先设计好的非均匀刻度。
更准确地说,NF4 的 4 bit 存的是:
“应该查码本里的第几个值”这个索引。
其过程可以粗略理解为:
原始权重
↓
按块归一化到约 [-1, 1]
↓
从 16 个 NF4 码本值中寻找最近值
↓
存储对应的 4-bit 索引
反量化时则执行:
重建值 = NF4码本[索引] × block_scale
NF4 的码本在 0 附近更密,两端更稀。
设计出发点是:许多神经网络权重块在归一化后,呈现出零中心、近似钟形的分布。既然大量权重集中在 0 附近,就应该把更多刻度留给这里。
需要注意两个边界条件。
第一,NF4 并不是 IEEE FP4,也不是硬件可以直接执行乘法的标准浮点格式。它更接近一个专用的非均匀量化码本。
第二,“神经网络权重都严格服从正态分布”也不成立。
不同模型、层、分组方式可能出现:
- 偏态;
- 长尾;
- 多峰;
- 明显离群值。
因此:
NF4 在接近其设计分布的权重上很有优势,但并不保证对所有张量都优于 INT4。
NF4 最经典的使用场景是 QLoRA:
- 冻结的基础模型权重以 NF4 存储;
- 计算时反量化到 FP16 或 BF16;
- 只训练 LoRA 适配器;
- 还可以对 scale 再做一次 double quantization。
所以 QLoRA 里的“4-bit 训练”,并不意味着基础模型在执行原生 4-bit 浮点乘法。
四、一张全景表:范围、精度和显存如何权衡
下面这张表把最重要的信息放在一起。
其中“1 附近间距”表示从 1 到下一个可表示浮点数之间的距离。数字越小,说明该格式在 1 附近越精细。
| 格式 | bit 构成或级别 | 理论字节/参数 | 1 附近间距 | 最大有限值或范围 | 典型用途 |
|---|---|---|---|---|---|
| FP32 | 1+8+23 | 4 Byte | 1.19e-7 |
约 3.4e38 |
累加、优化器、数值敏感计算 |
| FP16 | 1+5+10 | 2 Byte | 9.77e-4 |
65504 |
推理、混合精度训练 |
| BF16 | 1+8+7 | 2 Byte | 7.8125e-3 |
约 3.39e38 |
大模型训练、稳健推理 |
| FP8 E4M3 | 1+4+3 | 1 Byte | 约 0.125 |
常见变体约 448 |
新硬件权重和激活 |
| FP8 E5M2 | 1+5+2 | 1 Byte | 约 0.25 |
常见变体约 57344 |
大范围梯度等场景 |
| INT8 | 256 个均匀整数编码 | 1 Byte | 由 scale 决定 | 由 scale 决定 | 稳妥量化、W8A8 |
| INT4 | 16 个均匀整数编码 | 0.5 Byte | 由 scale 决定 | 由 scale 决定 | 低显存权重量化 |
| NF4 | 16 个非均匀码本值 | 0.5 Byte | 由码本和 scale 决定 | 块归一化后匹配码本 | QLoRA、4-bit 权重存储 |
这张表里有四个规律。
规律一:位数首先决定理论存储大小
32 位是 4 Byte,16 位是 2 Byte,8 位是 1 Byte,4 位是 0.5 Byte。
但实际量化模型还需要额外 scale 和元数据。
规律二:同样位数,不代表数值能力相同
FP16 和 BF16 都是 16 位,但一个把更多 bit 给尾数,一个把更多 bit 给指数。
INT8 和 FP8 都是 8 位,但一个使用均匀整数刻度,一个使用浮点式非均匀刻度。
规律三:范围和精度通常需要互相交换
总 bit 数固定时:
- 指数位更多,范围更大;
- 尾数位更多,局部精度更高。
BF16 与 FP16,E4M3 与 E5M2,都是这个规律的直接体现。
规律四:格式只是量化系统的一部分
同样是 INT4,最终结果还会受到以下因素影响:
- scale 怎么选;
- group size 多大;
- 是否有 zero-point;
- 是否处理离群值;
- 是否使用 AWQ、GPTQ;
- 硬件是否有匹配的 Kernel。
所以不能只根据“INT4”三个字判断模型质量和速度。
五、上手实测:亲眼看见数字被“压缩”
5.1 FP16 和 BF16:一个精度细,一个范围大
先比较 1 附近的精度。
这里要特别注意:原始基准必须先用 FP64 保存。否则 torch.tensor() 默认先把数字转成 FP32,再比较时基准本身已经被舍入了。
import torch
# 用 FP64 保存原始基准,避免它提前被 FP32 舍入
source = torch.tensor([1.001], dtype=torch.float64)
formats = [
(torch.float32, "FP32"),
(torch.float16, "FP16"),
(torch.bfloat16, "BF16"),
]
for dtype, name in formats:
restored = source.to(dtype).to(torch.float64)
error = torch.abs(source - restored).item()
print(
f"{name:5s}: "
f"读回值 = {restored.item():.10f}, "
f"绝对误差 = {error:.2e}"
)
典型输出:
FP32 : 读回值 = 1.0010000467, 绝对误差 = 4.67e-08
FP16 : 读回值 = 1.0009765625, 绝对误差 = 2.34e-05
BF16 : 读回值 = 1.0000000000, 绝对误差 = 1.00e-03
在 1 附近,FP16 明显比 BF16 精细。
接下来再看范围:
large = torch.tensor([70000.0], dtype=torch.float64)
for dtype, name in [
(torch.float16, "FP16"),
(torch.bfloat16, "BF16"),
]:
restored = large.to(dtype).to(torch.float64)
print(f"{name:5s}: 70000 被存成了 {restored.item()}")
典型输出:
FP16 : 70000 被存成了 inf
BF16 : 70000 被存成了 70144.0
这组实验非常清楚地展示了二者的取舍:
- FP16 在常见数量级上更精细;
- BF16 的数值范围大得多;
- BF16 虽然把
70000舍入成了70144,但至少没有直接溢出。
因此不能简单地问“FP16 和 BF16 谁精度更高”。
更准确的问题应该是:
- 你更需要局部有效数字,还是更大的动态范围?
- 你的计算过程中是否容易出现大值或小梯度?
- 硬件对哪种格式支持更好?
5.2 NF4 为什么可能优于均匀 4 bit,又为什么不是永远更好
下面做一个玩具实验。
为了只比较“均匀码本”和“NF4 码本”的形状,我们让二者都使用 16 个重建值,并使用相同的 blockwise absmax scale。
这不是某个生产量化 Kernel 的完整复刻,只用于建立直觉。
import numpy as np
# 16 个均匀分布的 4-bit 重建值
UNIFORM4_LEVELS = np.linspace(-1.0, 1.0, 16)
# 近似 NF4 码本
NF4_LEVELS = np.array([
-1.0000000, -0.6961928, -0.5250731, -0.3949175,
-0.2844414, -0.1847734, -0.0910500, 0.0000000,
0.0795803, 0.1609302, 0.2461123, 0.3379152,
0.4407098, 0.5626170, 0.7229568, 1.0000000,
], dtype=np.float64)
def quantize_by_codebook(
weights: np.ndarray,
levels: np.ndarray,
block_size: int = 64,
) -> np.ndarray:
"""按块归一化,并映射到最近的码本值。"""
weights = np.asarray(weights, dtype=np.float64)
restored = np.empty_like(weights)
for start in range(0, weights.size, block_size):
block = weights[start:start + block_size]
scale = np.max(np.abs(block))
if scale == 0:
restored[start:start + block_size] = 0
continue
normalized = block / scale
# 为每个值寻找最近的码本位置
distance = np.abs(
normalized[:, None] - levels[None, :]
)
indices = np.argmin(distance, axis=1)
restored[start:start + block_size] = (
levels[indices] * scale
)
return restored
rng = np.random.default_rng(0)
datasets = {
"正态分布": rng.normal(0, 0.1, size=10_000),
"均匀分布": rng.uniform(-0.1, 0.1, size=10_000),
}
for name, weights in datasets.items():
uniform_restored = quantize_by_codebook(
weights,
UNIFORM4_LEVELS,
)
nf4_restored = quantize_by_codebook(
weights,
NF4_LEVELS,
)
uniform_mse = np.mean(
(weights - uniform_restored) ** 2
)
nf4_mse = np.mean(
(weights - nf4_restored) ** 2
)
print(f"\n{name}")
print(f"均匀 4-bit MSE: {uniform_mse:.8e}")
print(f"NF4 MSE: {nf4_mse:.8e}")
print(f"NF4 / 均匀误差: {nf4_mse / uniform_mse:.1%}")
典型输出:
正态分布
均匀 4-bit MSE: 9.62390249e-05
NF4 MSE: 8.59340420e-05
NF4 / 均匀误差: 89.3%
均匀分布
均匀 4-bit MSE: 1.39830935e-05
NF4 MSE: 2.75741740e-05
NF4 / 均匀误差: 197.2%
这个结果比“NF4 永远比 INT4 好”更值得记住:
- 对接近正态分布的数据,NF4 的非均匀刻度可能更合适;
- 对接近均匀分布的数据,均匀刻度反而明显更好。
真正的结论是:
量化码本越贴近数据分布,有限的 16 个刻度就越能用在刀刃上。
NF4 的价值不在于它拥有某种“神奇的 4 bit”,而在于它对特定权重分布做了更合适的先验设计。
5.3 不用创建大张量,也能计算理论显存
原文通过创建 1 亿参数张量计算显存,会真实分配几百 MB 内存。
其实理论存储大小直接用公式计算即可:
def ideal_weight_gib(
n_params: int,
bits_per_param: int,
) -> float:
n_bytes = n_params * bits_per_param / 8
return n_bytes / 1024**3
n_params = 7_000_000_000
formats = {
"FP32": 32,
"FP16 / BF16": 16,
"INT8 / FP8": 8,
"INT4 / NF4": 4,
}
for name, bits in formats.items():
size = ideal_weight_gib(n_params, bits)
print(f"{name:12s}: {size:.2f} GiB")
输出:
FP32 : 26.08 GiB
FP16 / BF16 : 13.04 GiB
INT8 / FP8 : 6.52 GiB
INT4 / NF4 : 3.26 GiB
这里使用的是二进制单位 GiB。
再次强调,这只是纯权重的理论值。实际量化模型还会略大一些,完整推理显存则还要加上 KV Cache、激活值和临时空间。
六、实际选型:训练、推理和微调分别怎么选
选格式之前,先问三个问题:
- 你是在存权重,还是在做矩阵计算?
- 你最在意的是精度、显存还是吞吐?
- 你的硬件和框架有没有原生高效 Kernel?
基于这三个问题,可以形成下面这套选型框架。
| 场景 | 优先尝试 | 原因 | 需要注意 |
|---|---|---|---|
| 全参数训练 | BF16 + FP32 累加 | 范围大,训练稳定性通常好 | 需要硬件支持 |
| 老硬件混合精度训练 | FP16 + 动态 loss scaling | FP16 支持可能更成熟 | 注意溢出和下溢 |
| 高精度推理基线 | BF16 或 FP16 | 不引入整数量化误差 | BF16 更稳,FP16 局部精度更细 |
| 保守量化推理 | INT8 | 级别多,通常较容易保精度 | 仍需校准和任务验证 |
| 消费级显卡压缩 | INT4 + AWQ/GPTQ | 权重体积小,生态成熟 | Kernel 和 group size 很重要 |
| QLoRA 微调 | NF4 权重 + BF16 计算 | 低显存,适合冻结基础模型 | NF4 主要是存储格式 |
| 新硬件高吞吐 | FP8 | 原生 FP8 计算能力强 | scaling 和框架支持很关键 |
6.1 训练:优先 BF16,但不要丢掉 FP32
在支持 BF16 的硬件上,常见组合不是“所有东西全部 BF16”,而是:
BF16 权重或激活
+
FP32 累加和数值敏感状态
也就是混合精度。
原因是矩阵输入使用 BF16 可以降低存储和带宽成本,而 FP32 累加能够减少长链求和中的误差。
FP16 也可以训练,但通常更依赖动态 loss scaling,并且需要更谨慎地监控 inf 和 NaN。
6.2 普通推理:先建立 BF16 或 FP16 基线
在尝试量化前,最好先得到一份 BF16 或 FP16 的质量和速度基线。
因为只有这样,才能回答:
- 量化后到底掉了多少质量?
- 延迟究竟有没有改善?
- 显存节省是否符合预期?
- 问题来自模型本身,还是量化配置?
如果模型中间值跨度较大,BF16 通常更稳。
如果硬件对 FP16 优化更好,而且模型数值范围安全,FP16 也可能表现很好。
6.3 INT8:生产量化的低风险起点
如果目标是降低显存或提高吞吐,但又不希望马上承担 4-bit 的精度风险,INT8 通常是合理的第一步。
尤其是支持高效 W8A8 Kernel 的平台,可以同时压缩:
- 权重带宽;
- 激活带宽;
- 矩阵计算成本。
但如果只是把权重存成 INT8,计算时仍然反量化到 16 位,收益重点就更偏向权重存储和读取。
6.4 INT4:显存优先,但必须看 Kernel
当模型根本塞不进显卡时,INT4 往往是最实用的选择。
常见方案包括:
- AWQ;
- GPTQ;
- group-wise quantization;
- mixed-precision layer;
- 4-bit weight-only Kernel。
对自回归大模型来说,逐 token 解码经常受到显存带宽限制。权重从 FP16 压到 INT4 后,每生成一个 token 需要读取的数据明显减少,因此可能获得速度收益。
但 prompt prefill 阶段更偏向大规模矩阵计算,是否加速会更依赖原生计算 Kernel。
所以同一个 4-bit 模型可能出现:
- 显存明显下降;
- 解码变快;
- prefill 加速不明显;
- 某些硬件上甚至因反量化开销而变慢。
这不是矛盾,而是不同阶段的性能瓶颈不同。
6.5 NF4:优先放在 QLoRA 语境里理解
NF4 最适合记成:
为低显存微调设计的 4-bit 权重存储码本。
典型组合是:
基础模型权重:NF4
矩阵计算 dtype:BF16 或 FP16
可训练参数:LoRA 适配器
如果目标是生产推理,并且平台有成熟的 AWQ、GPTQ INT4 Kernel,那么普通 INT4 方案可能比 NF4 更方便,甚至更快。
因此,不能简单地写成:
4-bit 一律优先 NF4
更准确的判断是:
- bitsandbytes、QLoRA 微调:优先考虑 NF4;
- 高性能部署推理:优先测试 AWQ、GPTQ 等 INT4 格式;
- 最终以真实质量和吞吐 benchmark 为准。
6.6 FP8:只有格式、硬件、框架同时支持才有意义
FP8 的价值来自原生执行能力。
只有同时满足以下条件时,它才可能真正发挥优势:
- 硬件有 FP8 Tensor Core;
- 框架支持 FP8 scaling;
- 算子有成熟 FP8 Kernel;
- 模型经过正确校准或训练;
- 实际 workload 能够利用这些 Kernel。
如果硬件不支持,FP8 可能需要先转换成其他格式再计算。这样即使存储少了,也不一定更快。
因此,FP8 不是简单的“8 位浮点版 INT8”,而是一套硬件、格式和缩放策略共同构成的计算方案。
七、常见误区与避坑
误区一:位数一样,格式就差不多
FP16 和 BF16 都是 16 位,但:
- FP16 尾数更多;
- BF16 指数更多。
FP8 和 INT8 都是 8 位,但:
- FP8 是浮点式非均匀刻度;
- INT8 是经过 scale 映射的均匀整数刻度。
位数只能告诉你理论体积,不能告诉你数值特性。
误区二:4-bit 模型一定只占 FP16 四分之一显存
四分之一只适用于纯权重的理论 bit 数。
实际还需要:
- scale;
- zero-point;
- 元数据;
- 对齐;
- KV Cache;
- 激活;
- 工作区。
所以完整运行显存不会严格缩小四倍。
误区三:4-bit 权重等于 4-bit 计算
很多 4-bit 模型实际是:
W4A16
权重是 4 bit,但激活和计算仍是 FP16 或 BF16。
4 bit 首先是存储格式,不一定是算术格式。
误区四:NF4 一定比 INT4 好
NF4 的优势依赖于数据分布与其码本是否匹配。
对于接近正态、零中心的权重块,它可能更合适;对于其他分布,均匀量化可能更好。
而在推理部署中,AWQ、GPTQ 等经过优化的 INT4 方法也可能超过简单 NF4 量化。
误区五:BF16 范围大,所以绝对不会出现 NaN
BF16 的指数范围接近 FP32,但仍然可能因为以下原因出现异常:
- 除零;
- 指数函数爆炸;
- 梯度爆炸;
- 不稳定的归一化;
- 错误的学习率;
- 无效输入数据。
BF16 降低了因格式范围不足而溢出的概率,但不能替代数值稳定性设计。
误区六:FP8 直接转换一下就能加速
实际 FP8 系统通常还需要:
- scale 统计;
- 动态或延迟 scaling;
- overflow 检测;
- 对应 FP8 Kernel;
- 硬件原生支持。
没有这些条件,FP8 可能既不快,也不稳定。
误区七:量化就是把 FP16 文件转成 INT4 文件
真正的量化系统至少包含:
选择格式
+
选择量化粒度
+
计算 scale / zero-point
+
处理离群值
+
校准数据
+
选择敏感层
+
匹配硬件 Kernel
+
验证任务质量
格式只是其中一层,不是全部。
八、小结
这一篇最值得带走的不是某个格式的定义,而是一套分析方法。
1. 位数决定理论存储成本
32 bit → 4 Byte
16 bit → 2 Byte
8 bit → 1 Byte
4 bit → 0.5 Byte
但实际模型还需要 scale、元数据、KV Cache 和临时空间。
2. 浮点要看指数位和尾数位
- 指数位决定范围;
- 尾数位决定局部精度;
- FP16 尾数更多、范围较窄;
- BF16 指数更多、范围接近 FP32。
3. 整数量化依赖 scale,而不是只看整数范围
INT8 的 [-128, 127] 和 INT4 的 [-8, 7] 只是编码范围。
它们最终能表示多大的浮点数,取决于 scale、zero-point 和量化粒度。
4. 4-bit 存储不等于 4-bit 算术
大量 INT4、NF4 模型仍然使用 FP16 或 BF16 做实际计算。
低 bit 最确定的收益是减少权重存储和带宽,不保证同比例加速。
5. NF4 是非均匀码本,不是通用标准浮点
它非常适合从 QLoRA 的角度理解:基础权重低比特存储,计算仍使用较高精度。
6. 最实用的选型口诀
全参数训练:BF16 + FP32 累加
高精度推理:BF16 / FP16
保守量化:INT8
低显存部署:INT4 + AWQ/GPTQ
QLoRA 微调:NF4 权重 + BF16 计算
新硬件吞吐:FP8,但必须匹配硬件和框架
最后可以把整篇浓缩成一句话:
看位数,只能知道它有多大;看指数、尾数、scale 和码本,才能知道它有多准;再看硬件和 Kernel,才能知道它到底快不快。
更多推荐




所有评论(0)