大模型量化:从原理到 4-bit 实战

大模型的能力随参数量增长而提升,但部署成本同样随参数量线性甚至超线性膨胀。一个 70B 参数的模型,仅权重以 FP16 存储就需要约 140 GB 显存,远超单张消费级显卡的容量。量化(Quantization)是缓解这一矛盾最成熟、最广泛使用的技术手段:它以可控的精度损失为代价,把模型权重与激活从高精度浮点(FP16/BF16/FP32)压缩到低比特整数(INT8/INT4 等),从而同时降低显存占用、提升推理吞吐。

本文系统梳理量化的动机、基础概念、四种代表性方法,以及用开源工具链以 4-bit 加载模型的实战代码。

一、为什么要量化:显存、带宽与延迟

要理解量化为什么有效,需要把「模型能不能装下」和「模型跑得快不快」拆开看。二者分别对应两个不同的瓶颈:显存容量瓶颈与内存带宽瓶颈。

1.1 显存容量:决定模型能否装下

模型权重是推理时最占显存的部分。以参数量 P 为例,不同精度下的纯权重体积约为:

FP32: 4 * P  字节
FP16 / BF16: 2 * P  字节
INT8: 1 * P  字节
INT4: 0.5 * P  字节

一个 7B 模型,FP16 下约 14 GB,INT8 约 7 GB,INT4 约 3.5 GB。量化到 INT4 后,许多原本需要多卡才能加载的模型可以被塞进单张 24 GB 的消费级显卡。需要注意的是,实际显存还包含优化器状态(训练时)、KV Cache、激活值与量化常数(scale/zero-point),因此真实占用会高于上述纯权重的下界。

1.2 内存带宽:决定推理是否够快

很多人误以为推理慢是因为「算力不够」。对自回归生成的 decode 阶段而言,每生成一个 token 都要把全部权重从显存读到计算单元,这个过程的耗时主要由「需要搬多少字节」决定,而非「能做多少次乘加」。在算力充足、带宽受限的内存墙(memory-bound)场景下,单 token 延迟可近似为:

单 token 延迟 ≈ (参数量 P * 每参数字节数) / 显存带宽

例如 A100 的 HBM 带宽约为 2 TB/s,70B 模型 FP16(140 GB)理论下限约 70 ms/token;量化到 INT8(70 GB)后约降为 35 ms/token。换言之,降低每参数比特数直接成比例地降低了带宽需求,这是量化能加速推理的根本原因。

1.3 三者的关系

显存容量决定「能不能跑」,内存带宽决定「跑多快」,而量化同时改善二者:低比特让模型更可能装下单卡,也让每次生成要搬运的数据更少。延迟的下降幅度,在内存受限场景下近似等于比特数下降的比例;在算力受限场景下,收益则取决于计算内核是否能真正利用低比特指令。这也是为什么实际加速比(如 1.5x 到 4x)会因硬件、内核实现与位宽而不同。

二、量化基础

2.1 INT8 与 INT4:位宽意味着什么

位宽直接决定可表示数值的粒度与压缩率。INT8 用 8 个比特表示 256 个整数级别,INT4 仅 16 个级别。位宽越低,压缩越好,但相邻可表示值之间的「台阶」越大,量化误差也越大。经验上 INT8 对多数任务几乎无损,INT4 是当前精度与体积之间最常用的甜点位,再往 3-bit、2-bit 推进则精度退化明显加快。

2.2 权重量化与激活量化

  • 权重量化(weight-only quantization):只把模型权重压到低比特,计算时再反量化为高精度(或在专用低比特内核中计算)。它直接省下显存,实现简单,是 GPTQ、AWQ 等方法的路线。
  • 激活量化(activation quantization):把每层前向传播的中间激活也压到低比特(如 W8A8)。这能进一步释放计算内核的吞吐,但激活中常存在极端离群值(outlier),量化难度远高于权重,这也是 SmoothQuant 等方法要解决的核心问题。

2.3 对称量化与非对称量化

量化本质是建立浮点值与整数值之间的仿射映射。最常见的是均匀量化。

对称量化假设数值关于零对称,只有一个缩放因子 scale:

量化:  q = round(x / scale)
反量化: x' = q * scale
其中 scale = max(|x|) / (2^(b-1) - 1)   # 以 INT8 为例,分母为 127

对称量化无零点(zero-point),计算轻量,适合分布大致关于零对称、无明显偏移的权重。

非对称量化额外引入零点 zero_point,把实数区间 [min, max] 映射到整数区间 [0, 2^b - 1]:

step      = (max - min) / (2^b - 1)
zero_point = round(-min / step)
量化:  q = round(x / step) + zero_point
反量化: x' = (q - zero_point) * step

非对称量化能更贴合有偏移的分布(例如 ReLU 后的激活恒非负),用少量额外存储(zero_point)换取更小的映射误差。

2.4 校准(Calibration)

scale 与 zero_point 不能凭空设定,而应根据「真实数据分布」确定。校准就是用一小批有代表性的样本(校准集,calibration set)统计激活或权重的分布,从而选出使量化误差最小的量化参数。常见策略包括:

  • 最小最大值(min-max):直接用观测到的最大最小值,简单但易被个别离群值拉大 step;
  • 百分位/裁剪(percentile clipping):用 99.9% 分位数代替最大值,抑制离群值影响;
  • 熵(KL 散度)或均方误差(MSE)最小化:在原始分布与量化后分布之间选最优截断点。

校准集的质量直接影响低比特下的精度,因此多数 PTQ(训练后量化)方法都会强调校准数据的选取。

三、代表性量化方法

下面四种方法几乎覆盖了当前开源生态的主流路线,且都已有对应的工程实现。

3.1 LLM.int8():混合精度分解与离群特征

论文:Dettmers 等人,LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale(NeurIPS 2022,arXiv:2208.07339)。

作者发现, transformer 中存在一类「系统性涌现的离群特征」:少数特征维度的值域比其他维度高出约两个数量级。若对全部特征统一做 INT8,这些离群维度会产生巨大量化误差,进而拖累整体性能。

LLM.int8() 的解法是混合精度分解:

  • 对绝大多数特征,采用 vector-wise 量化(矩阵乘法中每个内积单独计算归一化常数)压成 INT8;
  • 对少数离群特征维度,单独拆出做 FP16 矩阵乘法,其余超过 99.9% 的数值仍在 8-bit 下完成。

结果是显存约减半,且对高达 175B 参数的模型几乎无性能退化。该方法由 bitsandbytes 库实现,是后续 4-bit 推理(如 QLoRA)的基础。

3.2 GPTQ:基于二阶信息的单步权重量化

论文:Frantar 等人,GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers(ICLR 2023,arXiv:2210.17323)。

GPTQ 是一种 one-shot(单步、无需训练)的权重量化方法,核心思想是用近似的二阶信息(基于 Hessian 的误差补偿)来最小化量化带来的层输出误差。它在逐层、逐列量化权重时,把当前列的量化误差按重要性补偿到尚未量化的列上,从而以很小的精度代价把位宽压到 3 或 4 bit。

论文报告:可在约 4 个 GPU 小时内量化 175B 模型到 3-4 bit,相对 FP16 基线精度损失极小;端到端推理相对 FP16 在 A100 上约加速 3.25 倍、在 A6000 上约加速 4.5 倍。GPTQ 是权重-only 4-bit 生态的奠基性工作,社区实现以 AutoGPTQ 为代表。

3.3 SmoothQuant:把激活的难度迁移到权重

论文:Xiao 等人,SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models(ICML 2023,arXiv:2211.10438)。

SmoothQuant 的目标是做到 W8A8——权重和激活都量化到 INT8,从而充分利用低比特计算内核。它的关键观察是:权重容易量化,而激活难量化(因为激活包含离群值)。既然「难量化」源于激活的离群幅度,那么可以通过一个数学上等价的变换,把量化难度从激活「迁移」到权重上:

对某一通道:  Y = X * W
引入缩放向量 s(按通道):
   X' = X * diag(s)
   W' = W * diag(1/s)
则 X' * W' = X * W = Y   # 等价变换,结果不变

通过适当选择 s(让激活与权重的量化难度均衡),激活的离群幅度被「摊平」,权重则承担更多可量化的幅度变化。SmoothQuant 据此实现全矩阵乘法的 INT8 量化,论文报告最高约 1.56 倍加速与 2 倍显存下降,且对 OPT、BLOOM、LLaMA 等系列模型均有效。

3.4 AWQ:激活感知的权重量化

论文:Lin 等人,AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration(MLSys 2024 最佳论文,arXiv:2306.00978)。

AWQ 同样走权重-only 低比特路线,但其洞察很独特:并不是所有权重都同等重要。论文发现,只要保护约 1% 的「显著权重通道」(salient weights),就能大幅降低量化误差。关键在于——哪些通道显著,应当看激活分布而不是权重本身的大小。

做法上,AWQ 通过离线统计激活,为显著通道计算一个缩放系数,并用等价变换把这些通道「放大」,使其相对量化台阶更鲁棒;其余通道正常量化。它不依赖反向传播或重建,因此不易过拟合校准集,对指令微调模型、多模态模型也能较好泛化。AWQ 配套了 TinyChat 推理框架,在桌面与移动端 GPU 上相对 FP16 实现可获得 3 倍以上加速。

四、量化对精度与困惑度的影响

量化终究是用精度换效率,trade-off 是绕不开的话题。实践中常用困惑度(Perplexity,PPL,越低越好,多见于 WikiText2 评测)来衡量语言建模能力的变化,并用下游任务准确率验证可用性。

几个有依据的经验性结论:

  • INT8 权重量化通常几乎无损,困惑度与 FP16 非常接近;
  • INT4 权重-only 量化(GPTQ、AWQ)在良好校准下,困惑度上升通常很小,仍可保持可用甚至接近基线的能力,是当前最流行的部署配置;
  • 激进位宽(3-bit、2-bit 或更低)会带来明显的困惑度恶化与任务掉点,需要更精细的方法或阶段性量化才可控;
  • 激活量化到 4-bit(W4A4)比权重-only 4-bit 难得多,因为激活离群值更难压住,这正是 LLM.int8() 与 SmoothQuant 重点攻关的方向。

取舍的原则是:先确定可接受的精度红线(例如困惑度上升不超过某阈值、下游任务不掉点超过某比例),再据此选择能压到的最低位宽与是否做激活量化。对绝大多数部署场景,「4-bit 权重-only + 高精度计算内核」是体积、速度与精度之间性价比最高的落点。

五、实战:以 4-bit 加载大模型

下面给出三种主流、可运行的加载方式。所有示例均只做推理加载与一次生成,便于验证量化模型是否「能正常说话」。

5.1 使用 transformers + bitsandbytes(NF4)

bitsandbytes 由 LLM.int8() 团队维护,transformers 原生支持通过其 BitsAndBytesConfig 以 4-bit 加载任意兼容模型,无需预先量化好的权重文件。

所需包:torchtransformersbitsandbytesaccelerate

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

# 4-bit 量化配置:NF4 针对近似正态的权重分布,配合双重量化进一步省显存
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",            # 4-bit 正态浮点,比纯 INT4 更贴合权重分布
    bnb_4bit_compute_dtype=torch.bfloat16,  # 计算时反量化为 bf16
    bnb_4bit_use_double_quant=True,       # 对量化常数再做一次量化,再省约 0.4 bit/参数
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    quantization_config=bnb_config,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

inputs = tokenizer("量化后的模型能正常推理吗?", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=64)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

说明:bitsandbytes 的 8-bit/4-bit 推理依赖 NVIDIA CUDA 内核,主要在 CUDA GPU 上可用。

5.2 使用 auto-gptq 加载 GPTQ 模型

该方式需要模型权重本身已由 GPTQ 算法预先量化(Hugging Face Hub 上有大量 *-GPTQ 仓库)。transformers 可直接加载,底层由 auto-gptq 提供内核。

所需包:torchtransformersauto-gptq

from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "TheBloke/Llama-2-7B-GPTQ"
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_id)

inputs = tokenizer("量化后的模型能正常推理吗?", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=64)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

提示:原 AutoGPTQ 仓库已进入未维护状态,社区继任项目为 GPTQModel;新模型与问题修复建议迁移到 GPTQModel,但 API 形态相近。

5.3 使用 autoawq 加载 AWQ 模型

同理,需要模型已按 AWQ 算法量化(Hub 上常见 *-AWQ 仓库)。autoawq 提供专用加载入口。

所需包:torchtransformersautoawq

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_id = "TheBloke/Llama-2-7B-AWQ"
# from_quantized 直接加载 4-bit 权重,并融合层以提升推理效率
model = AutoAWQForCausalLM.from_quantized(model_id, fuse_layers=True)
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)

inputs = tokenizer("量化后的模型能正常推理吗?", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=64)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

三种方式如何选择:想对「任意 HF 模型」即时做 4-bit 而不准备量化权重,选 bitsandbytes;想用社区已发布的 GPTQ/AWQ 权重获得更高、更稳定的压缩与吞吐,分别选 auto-gptq / autoawq。无论哪种,都建议用一小段真实输入验证输出连贯性,再上线到生产。

小结

量化通过把权重与激活从高精度浮点压到低比特整数,同时缓解显存容量瓶颈与内存带宽瓶颈,是降低大模型部署成本最成熟的手段。基础层面要分清 INT8/INT4 的位宽取舍、权重量化与激活量化、对称/非对称量化以及校准的作用。方法层面,LLM.int8() 用混合精度分解处理离群特征,GPTQ 用二阶误差补偿做高质量的 one-shot 权重量化,SmoothQuant 通过等价变换把激活的量化难度迁移到权重以实现 W8A8,AWQ 则依据激活分布保护约 1% 显著权重通道来做 4-bit 权重-only 量化。精度上,INT8 近乎无损、INT4 是当前性价比甜点、更低位宽需谨慎。实战中可用 transformers + bitsandbytes 即时做 4-bit,或用 auto-gptq / autoawq 加载社区已量化权重,落地门槛已经很低。

参考与延伸阅读

本文累计阅读