量化概念
量化后的数值只能取一个较小集合中的值,例如 int8 有 256 个,int4 有 16 个,因此每种量化方法都会引入舍入误差。不同方法的区别在于如何防止该误差损害模型的输出。本页将解释这些差异背后的选择:数值如何映射到该小集合、哪些张量会被量化、使用哪些数据类型,以及量化发生在何时。
如果您是 Transformers 量化的新手,请从概述开始。
将值映射到更少的位
量化将高精度值的范围映射到低位类型所能表示的少量整数集合上。int8 可以表示 256 个整数,即 [-128, 127],而 int4 可以表示 16 个。
仿射量化是最常见的映射方式。它取张量的最小值和最大值,并将整数范围拉伸到该区间上。在下方的示例中,一个取值范围为 -1.8 到 2.4 的张量被映射到 int8,其中 0.0 对应 -19。
bf16 -1.8 0.0 2.4
├────────────────┼──────────────────────┤
▼ ▼ ▼
int8 -128 -19 127两个参数定义了该映射。
的 scale
S是原始单位中一个整数步长的大小。零点 (
Z)是表示0.0.
值 x 通过除以缩放因子并平移零点,量化为整数 q 。
反量化会逆转该映射,并返回 x. 与 x 的近似值之间的差值即为舍入误差。
在上面的示例中,S 约为 0.0165,Z 为 -19。数值 0.5 量化后为 11,而 11 反量化后约为 0.494,舍入误差约为 0.006。
┌─────┐ quantize ┌────┐ dequantize ┌───────┐ │ 0.5 │ ──────────▶ │ 11 │ ───────────▶ │ 0.494 │ └─────┘ └────┘ └───────┘
仿射量化有两种形式。
- 非对称量化使用上述公式。它映射精确的范围,因此当权重不以零为中心时,不会浪费可表示的值。
- 对称量化假设数值范围为
[-a, a],并将Z固定为 0,因此只需存储缩放因子。其计算开销更低,但当权重偏向零的一侧时,部分可表示的数值将处于未使用状态。
选择粒度
对整个张量使用单一缩放因子的存储开销很小,但一个较大的离群值会拉宽数值范围,导致其他所有权重可表示的数值分布更加稀疏。更小的分组可以让每个缩放因子覆盖更窄的范围。
- Per-tensor 对整个张量使用一个缩放系数和一个零点。
- Per-channel 为每个输出通道使用一个缩放因子,即权重矩阵的每一行。
- 按组或按块的方式为每组连续值(例如 128 个权重)使用一个缩放因子。每组都需要额外的存储空间来保存其缩放因子。
下图展示了一个三行权重矩阵上的三种选项。每个 s 代表一个缩放因子。
per-tensor per-channel per-group ┌────────────┐ ┌────────────┐ ┌──────┬──────┐ │ │ │ │ s0 │ s0 │ s1 │ │ │ ├────────────┤ ├──────┼──────┤ │ s │ │ │ s1 │ s2 │ s3 │ │ │ ├────────────┤ ├──────┼──────┤ │ │ │ │ s2 │ s4 │ s5 │ └────────────┘ └────────────┘ └──────┴──────┘
大多数低比特方法按组进行量化。GPTQ 默认使用 128 个权重为一组,Fine-grained FP8 默认使用 128x128 的块。Data types 中的 FP4 格式将块大小直接内置于格式本身。
量化权重或激活值
不同的方法会对不同的张量进行量化,这决定了矩阵乘法在何处执行。
- 仅权重方法以低精度存储权重,而激活值保持为 bf16 或 fp16。在每次矩阵乘法之前,内核会将权重反量化到更高精度的计算数据类型。bitsandbytes 4-bit、AWQ、GPTQ 和 HQQ 均采用这种方式。在 bitsandbytes 中,
bnb_4bit_compute_dtype用于设置该计算数据类型。 - 权重与激活方法会对激活值也进行量化,矩阵乘法直接在低精度下执行。这需要硬件支持低精度矩阵乘法,例如近期 GPU 上的 FP8。该方案通常写作 W8A8,表示 8 位权重和激活。
仅权重量化方法可以节省内存,但并不总是能提升运行速度,因为反量化会为每次矩阵乘法增加额外开销。当模型处于内存瓶颈时,它们能加速生成过程。在逐 token 生成的过程中,GPU 在每一步中大部分时间都用于从内存读取权重,而非进行计算。读取 4 位权重所传输的数据量仅为 bf16 权重的四分之一,这一节省往往能够抵消反量化带来的开销。
权重与激活方法在模型处于计算受限状态时(例如在预填充阶段或处理大批量数据时)尤为有效,因为低精度矩阵乘法本身速度更快。部分方法会在两种模式之间进行切换。NVFP4 在预填充阶段执行 4 位权重、4 位激活的矩阵乘法,而在解码阶段执行 4 位权重、16 位激活的矩阵乘法。
数据类型
数据类型决定了量化数值可以表示的取值范围。
int8 和 int4
int8 和 int4 表示等间距的整数,并使用上述的仿射映射。int4 只能表示 16 个值,因此其精度损失比 int8 更大。使用 int4 的方法通常按组进行量化,或使用校准来保持精度。
大多数硬件没有 4 位存储类型,因此两个 int4 值会被打包到一个字节中。例如,第一个值可以占用低 4 位,第二个值占用高 4 位。内核在计算之前会先解包这些值。
┌──────┐ ┌──────┐ ┌──────┬──────┐ │ val1 │ │ val2 │ │ val2 │ val1 │ │ 0101 │ │ 0011 │ → │ 0011 │ 0101 │ 1 byte └──────┘ └──────┘ └──────┴──────┘ int4 int4 high low
某些 4 位类型以不均匀的方式分布其可表示值,以匹配权重的分布情况。bitsandbytes 的 NF4 在零附近放置了更多的值,因为大多数权重都集中在该区域。
FP8
FP8 在 8 位中保留了由符号、指数和尾数组成的浮点布局。其可表示的数值在零附近更密集,在绝对值较大时更稀疏,这与权重和激活值中数值的分布范围相契合。
FP8 有两种变体。
- E4M3 拥有 4 位指数和 3 位尾数。它的精度更高,但覆盖的数值范围更小。它通常是推理阶段中权重和激活值的常用选择。
- E5M2 拥有 5 个指数位和 2 个尾数位。它以较低的精度换取了更宽的数值范围。
E4M3 ┌─┬───────┬─────┐
│S│E E E E│M M M│
└─┴───────┴─────┘
E5M2 ┌─┬─────────┬───┐
│S│E E E E E│M M│
└─┴─────────┴───┘
S sign E exponent M mantissaFP8 矩阵乘法需要硬件支持,例如计算能力为 8.9 或更高的 NVIDIA GPU(Ada、Hopper、Blackwell)、AMD Instinct MI300 以及 Intel XPU。Transformers 通过 Fine-grained FP8、FBGEMM FP8 和 compressed-tensors 支持 FP8。
带块缩放的 FP4
FP4 包含 1 个符号位、2 个指数位和 1 个尾数位(E2M1)。它仅能表示 0、0.5、1、1.5、2、3、4 和 6 的正负值。由于可表示的数值如此有限,FP4 格式无法采用每个张量一个缩放因子的方式。它会将缩放因子附加到每个小的数值块上,并将这些缩放因子作为格式的一部分进行存储。
| 格式 | 块大小 | 按块缩放 | 额外缩放 |
|---|---|---|---|
| MXFP4 | 32 个值 | 8 位 2 的幂(E8M0) | 无 |
| NVFP4 | 16 个值 | FP8 | 每个张量一个 FP32 缩放 |
MXFP4 是一种微缩放(microscaling)格式。由于 2 的幂次缩放仅会移动指数,因此应用它的开销很低,但它只能落在 2 的幂次上。NVFP4 使用更小的块,并采用 FP8 缩放因子,其取值可以位于 2 的幂次之间。这保留了更高的精度,代价是需要一个逐张量(per-tensor)缩放因子,以确保 FP8 块缩放因子保持在有效范围内。Four Over Six 和 FP-Quant 同样可以生成 NVFP4 或 MXFP4 权重。
同样的分块思想也体现在 8 位上。MXFP8 使用 2 的幂次分块缩放因子来存储 FP8 值,而 Fine-grained FP8 则负责加载它。
码本
在 4 bit 以下,某些方法不再将每个权重四舍五入到最接近的可表示值,而是将权重分组,以向量的形式进行联合量化。码本(codebook)保存一组学习得到的向量,每组权重则以其最接近向量的索引形式存储。AQLM 默认使用 8 个权重为一组,并采用 16 位索引,相当于每个权重 2 bit。VPTQ 同样使用码本。寻找合适的码本需要在校准数据上进行长时间的优化,因此这些模型会预先完成量化,并以检查点(checkpoint)的形式加载。
量化发生的时间
模型可以在训练后或训练过程中进行量化。Transformers 中的大多数方法都是在训练后进行量化。
训练后量化
训练后量化(PTQ)对已经训练好的模型进行量化,同时不改变其学到的内容。PTQ 方法的区别在于是否需要数据,这决定了该方法在文档目录中归属于哪一类。
- 在没有校准数据的情况下,缩放系数仅由权重决定,量化速度足够快,可以在模型加载过程中完成。bitsandbytes、HQQ 和 torchao 均采用这种方式。这些方法列在 加载时量化 下。
- 使用校准数据时,该方法会将一个小数据集输入模型,并利用激活值来决定如何量化每一层。在 4 位及以下精度时,校准通常能带来更高的精度,但耗时从几分钟到数小时不等。
校准方法以不同的方式利用激活值。GPTQ 逐列量化一个层,并调整其余列以补偿每一列的舍入误差。AWQ 会找到激活值最大的权重通道,并在舍入前对其进行放大,以保护这些通道。
校准运行的位置决定了你在 Transformers 中如何使用该方法。
- GPTQ 在将
dataset传递给 GPTQConfig 时,会在from_pretrained内部进行校准,因此它被列在 加载时量化 下。 - 其他校准方法,例如 AWQ、AutoRound、AQLM 和 VPTQ,在各自的库中进行校准。Transformers 加载其结果,因此它们被列在 加载预量化检查点 下。
量化感知训练
量化感知训练(QAT)在训练过程中模拟量化。前向传播按量化的方式对数值进行舍入,而梯度则更新全精度权重,从而使模型学会容忍舍入误差。在极低比特宽度下,QAT 通常能提供最佳精度,但它需要对模型进行训练。
BitNet 模型以这种方式进行训练,其 1.58 位权重参与训练循环,而 Transformers 负责加载训练好的检查点。
资源
- 使用 Hugging Face 了解量化基础
- 深入量化
- 使用 🤗 和 💗🧑🍳 烹制的量化入门
- EfficientML.ai 第 5 讲 - 量化(第一部分)
- 从第一性原理出发,让深度学习飞速运转
- 使用 PyTorch 加速生成式 AI 第二部分:LLM 优化