前三篇我们讲了为什么要量化、量化背后的数学,以及 PTQ、QAT 两条主要路线。

这一篇,我们把视角拉到最底层:

  • FP32、FP16、BF16 的 3216 到底代表什么?
  • 同样是 16 位,为什么 BF16 比 FP16 更适合训练?
  • 同样是 8 位,FP8 和 INT8 为什么完全不是一回事?
  • INT4 和 NF4 都只有 4 位,为什么使用场景却不一样?
  • 4-bit 模型,真的只占 FP16 的四分之一显存吗?

说到底,所有这些问题都指向同一件事:

一个数字的有限 bit,究竟拿来表示范围、精度,还是量化索引?

理解这一点,后面的格式选型就不再需要死记硬背。


目录

  • 一、一个数字在内存里长什么样
  • 二、整数和浮点,是两种完全不同的表示思路
  • 三、逐个拆解:每种格式的 bit 怎么排
  • 四、一张全景表:范围、精度和显存如何权衡
  • 五、上手实测:亲眼看见数字被“压缩”
  • 六、实际选型:训练、推理和微调分别怎么选
  • 七、常见误区与避坑
  • 八、小结

一、一个数字在内存里长什么样

我们平时写:

x = 0.85

看起来,计算机里好像真的存了一个“0.85”。

但计算机并不认识十进制小数。内存里最终只能存一串 01。所谓数据格式,就是一套约定:

看到这串 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 的四分之一。

格式首先决定的是“每个权重的理论存储成本”,而不是整个推理系统的全部显存。


二、整数和浮点,是两种完全不同的表示思路

这些格式大体可以分成三类:

  1. 浮点格式:FP32、FP16、BF16、FP8;
  2. 整数量化格式:INT8、INT4;
  3. 非均匀码本格式: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 是量化后的整数;
  • 是反量化后的近似值;
  • 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,仍然可能因为除零、指数爆炸、异常梯度等原因产生 infNaN。BF16 提高了安全边界,但不是数值稳定的万能保险。


3.4 INT8:成熟、稳妥的整数量化档位

INT8 = 8 bit = 256 种编码

有符号 INT8 的原始整数范围通常是:

[-128, 127]

为了让正负两侧完全对称,对称量化中也经常只使用:

[-127, 127]

需要区分两个概念:

  • INT8 的整数编码范围是固定的;
  • 它能覆盖的真实浮点范围,由 scalezero-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 的真正优势通常来自三者结合:

  1. 浮点式非均匀刻度;
  2. 合适的 scaling 策略;
  3. 新硬件上的原生 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、激活值和临时空间。


六、实际选型:训练、推理和微调分别怎么选

选格式之前,先问三个问题:

  1. 你是在存权重,还是在做矩阵计算?
  2. 你最在意的是精度、显存还是吞吐?
  3. 你的硬件和框架有没有原生高效 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,并且需要更谨慎地监控 infNaN


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,才能知道它到底快不快。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐