Transformers documentation

量化概念

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
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