Transformers 文档

量化概念

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

量化概念

量化通过使用低精度数据类型(如 8 位整数或 int8)而非标准 32 位浮点数 (float32) 来表示模型权重和/或激活,从而减少大型机器学习模型(例如 Transformers 库中的模型)的内存占用和计算成本。

降低模型精度带来多项显著优势

  • 更小的模型尺寸:低精度数据类型需要更少的存储空间。例如,一个 int8 模型大约比其 float32 版本小 4 倍。
  • 更快的推理:在兼容硬件上(CPU 和 GPU 通常具有 int8 操作的专用指令),对低精度数据类型(特别是整数)进行操作可以显著加快速度。这会降低延迟。
  • 更低的能耗:更快的计算和更小的内存传输通常意味着更低的功耗。

量化的主要权衡是*效率*与*准确性*。降低精度可以节省资源,但不可避免地会引入小错误(量化噪声)。目标是使用适当的方案(仿射/对称)、粒度(每张量/通道)和技术(PTQ/QAT)来最大限度地减少此错误,以便模型在其目标任务上的性能尽可能少地下降。

以下部分将介绍量化方案、粒度和技术。

量化方案

核心思想是将原始 float32 权重和激活中的值范围映射到 int8 所表示的更小范围(通常为[128,127][-128, 127]).

本节介绍一些量化技术的工作原理。

quant_visual

仿射量化

最常见的方法是仿射量化。对于给定的 float32 张量(如层的权重),它找到最小值valminval_{min}和最大值valmaxval_{max}值。此范围[valmin,valmax][val_{min}, val_{max}]映射到 int8 范围[qmin,qmax][q_{min}, q_{max}],通常为[128,127][-128, 127].

有两种主要方法进行此映射:*对称*和*非对称*。对称和非对称量化之间的选择决定了 float32 范围如何映射到 int8 范围。

  • 对称:此方法假设原始 float32 范围围绕零对称([a,a][ -a, a ])。此范围对称映射到 int8 范围,例如,[127,127][-127, 127]。一个关键特征是 float32 值0.00.0直接映射到 int8 值00。这只需要一个参数,即**比例(SS)**,来定义映射。它可以简化计算,但如果原始数据分布并非自然地以零为中心,则准确性可能会降低。
  • 非对称(仿射):此方法不假设数据以零为中心。它将 float32 的精确范围[valmin,valmax][val_{min}, val_{max}]映射到完整的 int8 范围,例如[128,127][-128, 127]。这需要两个参数:一个**比例(SS)**和一个**零点(ZZ)**。

比例(SS):一个正的 float32 数,表示 float32 范围和 int8 范围之间的比率。S=valmaxvalminqmaxqmin S = \frac{val_{max} - val_{min}}{q_{max} - q_{min}}

零点(ZZ):一个 int8 值,对应于 float32 值0.00.0. Z=qminround(valminS) Z = q_{min} - round\left(\frac{val_{min}}{S}\right)

在对称量化中,Z 通常固定为 0。

有了这些参数,float32 值,xx。可以使用以下公式量化为 int8(qq)。q=round(xS+Z) q = round\left(\frac{x}{S} + Z\right)

int8 值,qq,可以通过以下公式反量化回近似 float32。xS(qZ) x \approx S \cdot (q - Z)

dequant

在推理过程中,像矩阵乘法这样的计算是使用 int8 值(qq)执行的,结果在传递给下一层之前,会反量化回 float32(通常在内部使用 int32 等更高精度累加类型)。

int4 和权重打包

weight packing

int4 量化进一步减小了模型大小和内存使用(与 int8 相比减少了一半)。同样的仿射或对称量化原则适用,将 float32 范围映射到 int4 可表示的 16 个可能值([8,7][-8, 7]对于有符号 int4)。

int4 量化的一个关键方面是**权重打包**。由于大多数硬件无法在内存中原生处理 4 位数据类型,因此通常将两个 int4 值打包成一个 int8 字节用于存储和传输。例如,第一个值可能占用低 4 位,第二个值占用字节的高 4 位(`packed_byte = (val1 & 0x0F) | (val2 << 4)`)。

即使没有原生 int4 计算,int4 仍然有益,因为主要好处来自内存带宽的减少。将打包的 int4 权重(存储为 int8)从内存(RAM 或 VRAM)加载到计算单元的速度是加载 int8 权重的两倍。对于大型模型,内存访问通常是一个显著的瓶颈。更快的传输速度带来的加速可以抵消即时解包和反量化的计算开销,从而实现整体更快的推理,尤其是在内存受限的场景中。

然而,int4 量化通常会导致比 int8 更大的精度损失。对于 int4 来说,通常需要像 GPTQAWQ 这样的高级量化技术才能获得良好的性能。

FP8 量化 (A8W8)

fp8
一种较新的数据类型,8位浮点数(FP8),提供了另一种降低精度的方法,同时在某些情况下比 int8 保持更高的准确性。FP8 保留了浮点结构(符号、指数、尾数),但使用了更少的位。

FP8 有两种常见变体。

  • E4M3:1 个符号位,4 个指数位,3 个尾数位。提供更高的精度(更多尾数位)但动态范围更小(更少指数位)。
  • E5M2:1 个符号位,5 个指数位,2 个尾数位。提供更宽的动态范围但精度较低。

FP8 用于 *A8W8* 量化方案,该方案将激活 (A) 和权重 (W) 都量化为 8 位精度。

虽然 int8 得到广泛支持,但高效的 FP8 计算需要较新 GPU(如 NVIDIA H100/H200/B100 和 AMD Instinct MI300 系列)中特有的硬件功能。如果没有原生的硬件加速,FP8 的优势可能无法完全实现。

Transformers 通过特定的后端支持 FP8,例如 FBGEMMFineGrainedFP8compressed-tensors。当使用适当的硬件和配置时,这些后端处理底层的 FP8 转换和计算。

粒度

量化参数(SSZZ)可以通过两种方式计算。

  • 每张量:整个张量使用一组SSZZ。更简单,但如果张量内数据值差异很大,则精度较低。
  • 每通道(或每组/块):每个通道或组使用单独的SSZZ。精度更高,性能更好,但成本是略微增加复杂性和内存。
Granularities

量化技术

量化技术主要有两种类型。

  • 训练后量化 (PTQ):在模型完全训练*后*应用量化。
  • 量化感知训练 (QAT):在训练*期间*通过插入“伪量化”操作来模拟量化效果,这些操作模拟量化的舍入误差。这使得模型能够适应量化,通常会带来更好的准确性,尤其是在较低位宽下。

Transformers 中的量化

Transformers 集成了多个量化后端,例如 bitsandbytes、torchao、compressed-tensors 等(有关更多后端,请参阅量化概述)。

所有后端都统一在 `HfQuantizer` API 和相关的 `QuantizationConfig` 类下。您可以通过实现自定义 `HfQuantizer` 和 `QuantizationConfig` 来集成您自己的自定义量化后端,如贡献指南中所示。

Transformers 中量化的典型工作流程是:

  1. 选择适合您的硬件和用例的量化方法(请参阅概述选择量化方法指南以帮助您)。
  2. 从 Hugging Face Hub 加载预量化模型,或者加载 float32/float16/bfloat16 模型并使用 `QuantizationConfig` 应用特定量化方法。

下面的示例演示了加载一个 8B 参数模型并使用 bitsandbytes 将其量化为 4 位。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

model_id = "meta-llama/Llama-3.1-8B-Instruct"

quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16
)

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=quantization_config,
    torch_dtype=torch.bfloat16,
    device_map="auto"
)

资源

要更深入地探讨量化和相关性能优化概念,请查看以下资源。

< > 在 GitHub 上更新