Transformers documentation

量化概念

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

量化概念

量化后的数值只能取一个较小集合中的值,例如 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 是原始单位中一个整数步长的大小。 S=valmax−valminqmax−qmin S = \frac{val_{max} - val_{min}}{q_{max} - q_{min}}

  • 零点 ( Z )是表示 0.0. Z=qmin−round(valminS) Z = q_{min} - round\left(\frac{val_{min}}{S}\right)

值 x 通过除以缩放因子并平移零点,量化为整数 q 。 q=round(xS+Z) q = round\left(\frac{x}{S} + Z\right)

反量化会逆转该映射,并返回 x. 与 x 的近似值之间的差值即为舍入误差。 x≈S⋅(q−Z) x \approx S \cdot (q - Z)

在上面的示例中,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 mantissa

FP8 矩阵乘法需要硬件支持,例如计算能力为 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 格式无法采用每个张量一个缩放因子的方式。它会将缩放因子附加到每个小的数值块上,并将这些缩放因子作为格式的一部分进行存储。

格式块大小按块缩放额外缩放
MXFP432 个值8 位 2 的幂(E8M0)无
NVFP416 个值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 负责加载训练好的检查点。

资源

Update on GitHub