第 2 周 · Day 5:量化

Transformers and Large Language Models · Week 2 · Day 5。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

时长 7 小时:§1 数值格式(0.5h)· §2 量化的基本操作(1h)· §3 大模型量化的难点与四个方法(2h)· §4 NF4 与双重量化(0.5h)· §5 为什么能加速(0.5h)· §6 PTQ 与 QAT(0.5h)· §7 格式地图 · §A–§C(1.5h)

对应正课 Lecture 4 的量化部分。学习计划点名要会的:FP32 / FP16 / BF16 / INT8 / INT4 的取舍、BF16 为什么在训练里胜出(Day 4 §3)、PTQ vs QAT、GPTQ / AWQ / GGUF 各自的定位。

配套代码 w2day5_checks.py(numpy):对称 vs 非对称、粒度、离群通道与 LLM.int8、SmoothQuant、GPTQ 的完整实现与 RTN 的对比、AWQ 的缩放搜索、NF4 码本的构造与误差、双重量化、比特数与解码速度、QAT vs PTQ

§B 参考实现(code/ 目录):w2d5_quant.py(在 mini-GPT 和一个小型开放模型上比较 RTN / 分组 / NF4 / GPTQ 在不同比特下的困惑度;bitsandbytes 的 8-bit 与 4-bit 加载)。

今天的目标:能说清量化省的是什么、为什么能加速解码;能解释大模型量化为什么难(离群值);能说出 LLM.int8、SmoothQuant、GPTQ、AWQ 各自用什么思路解决它;能从分位点构造出 NF4。


§0 今天的主线

量化 = 用更少的比特表示权重(和激活)。为什么要做?

  • 显存:70B 模型 bf16 约 130 GiB,4-bit 约 33 GiB,能放进一张卡(Day 4 的显存账)。
  • 速度:batch 1 的解码是访存密集的(Week 1 Day 4 §1.5),时间约等于把权重读一遍的时间,比特数减半,速度上限翻倍(§5)。
  • 微调门槛:4-bit 基座 + LoRA = QLoRA,16 GB 的卡能微调 7B(Day 6)。

代价是精度损失。今天的全部内容都在回答:怎样用尽量少的比特,丢尽量少的精度。

今天要用到的旧结论:浮点格式与 BF16(Day 4 §3);roofline 与解码的访存瓶颈(Week 1 Day 4 §1.5);GPU 解码时读权重的字节数(Day 2 §2.3);最小二乘与 Hessian(Week 0 Day 2 §1.4 的牛顿法)。


§1 数值格式

格式比特可表示的值典型用途
FP3232浮点,范围 ±3.4×1038\pm3.4\times10^{38}主权重、优化器状态
BF16 / FP1616浮点(Day 4 §3.1)训练与推理的默认精度
FP8(E4M3 / E5M2)8浮点,最大值约 448 / 57344H100 起的训练与推理
INT88整数 −127…127(乘一个 scale)权重和激活量化
INT4 / NF4416 个码值(乘一个 scale)权重量化

简写约定:W4A16 表示权重 4 bit、激活 16 bit(只量化权重,计算时把权重反量化回 16 bit);W8A8 表示权重和激活都 8 bit(可以走整数矩阵乘)。这两类的加速原理不同(§5)。


§2 量化的基本操作

2.1 仿射量化

把实数 xx 映射到 bb 位整数:

q=clip(round(xs)+z, qmin⁡, qmax⁡),x^=s (q−z)q=\mathrm{clip}\Big(\mathrm{round}\Big(\frac xs\Big)+z,\ q_{\min},\ q_{\max}\Big),\qquad\hat x=s\,(q-z)

ss 是缩放因子(scale),zz 是零点。

  • 对称量化(absmax):z=0z=0,s=max⁡∣x∣/(2b−1−1)s=\max|x|/(2^{b-1}-1)。码值关于 0 对称。
  • 非对称量化:把 [min⁡x,max⁡x][\min x,\max x] 映射到 [0,2b−1][0,2^b-1],多存一个零点。

误差从哪来。 舍入误差在 [−s/2,s/2][-s/2,s/2] 上近似均匀分布,方差 s2/12s^2/12。每多 1 bit,ss 减半,误差标准差减半(约 6 dB)。所以 8 bit 通常几乎无损,4 bit 开始有明显损失,3 bit 以下损失很大。

什么时候用哪个。 w2day5_checks.py 第 1 组:数据全为正(ReLU、SiLU 之后的激活)时,对称量化把一半码值(负数)浪费了。4 bit 下对称的相对误差 0.178,非对称 0.084,差了约一倍,相当于少用 1 bit。数据关于 0 对称时两者差不多(0.180 vs 0.165)。权重大致关于 0 对称,通常用对称量化;激活常常不对称,用非对称。

2.2 粒度:一个 scale 管多少个数

ss 由这组数的最大值决定。这组数的范围越大,普通值分到的精度越少。 第 2 组(256×1024 的矩阵,不同行的尺度差异很大):

逐张量逐输出通道分组 g=128g=128
8 bit0.1010.0090.007
4 bit0.4890.1530.120

4 bit 下几乎必须分组。代价是要多存 scale:每个 scale 16 bit 的话,逐通道每参数额外 0.016 bit,g=128g=128 额外 0.125 bit。所以常说的”4-bit 模型”实际是每参数 4.1–4.5 bit。

2.3 量化什么

  • 权重:静态,可以离线花时间精心量化(GPTQ、AWQ)。
  • 激活:每次推理都不同,只能在线量化,而且有离群值(§3.1)。
  • KV Cache:长上下文时是显存大头(Week 1 Day 4 §1.4),量化到 8 bit 甚至 4 bit 能直接增加可支持的上下文或并发。

§3 大模型量化的难点,以及四个方法

3.1 离群值与 LLM.int8

现象。Dettmers et al.(2022)发现:模型超过约 6.7B 参数后,隐藏状态里会出现少数几个维度的数值远大于其他维度(大几十倍),而且这些维度在所有层、所有 token 上都是同一批。

为什么致命。逐张量量化时,scale 由最大值决定,离群值把 scale 撑大,其余所有普通值都挤进 0 附近的几个码值。第 3 组构造了 1024 维里有 6 个维度(0.6%)是其余 60 倍的激活:

做法输出相对误差
激活逐张量 INT80.109(普通通道 62.8% 的值被量成 0)
激活逐 token INT80.052
LLM.int8 混合分解0.002

LLM.int8 的办法:既然离群值集中在少数固定的列上,就把这些列拆出来用 16 bit 算,其余照常 INT8,最后相加。代价是额外的一小块高精度矩阵乘和拆分开销。论文报告用这种方法把 175B 模型量化到 8 bit 而几乎不掉点。

3.2 SmoothQuant:把难度从激活搬到权重

观察:激活难量化(有离群列),权重好量化(分布平缓)。能不能把一部分难度转移过去?

等价变换:对输入的第 jj 维,激活除以 sjs_j、权重的第 jj 行乘以 sjs_j,矩阵乘的结果完全不变:

Y=XW=(X diag(s)−1)(diag(s) W)Y=XW=(X\,\mathrm{diag}(s)^{-1})(\mathrm{diag}(s)\,W)

取 sj=max⁡∣X:,j∣α/max⁡∣Wj,:∣1−αs_j=\max|X_{:,j}|^\alpha/\max|W_{j,:}|^{1-\alpha},α\alpha 控制转移多少。第 4 组(激活逐张量、权重逐通道,都 INT8):

α\alpha0(不转移)0.250.50.75
输出相对误差0.1270.0440.0180.021

α\alpha 太小,激活仍然难量化;太大,权重被放大的列变得难量化。论文对多数模型取 0.5。好处:激活也能用 INT8,推理可以走真正的整数矩阵乘(W8A8),在计算密集的场景也加速。缩放因子可以融进前一层(LayerNorm 或上一个线性层),推理时零开销。

3.3 GPTQ:用二阶信息补偿误差

问题重新表述。量化一个线性层,我们真正关心的不是 ∥W−W^∥\|W-\hat W\|,而是输出的误差 ∥XW⊤−XW^⊤∥2\|XW^\top-X\hat W^\top\|^2,XX 是校准数据上这一层的输入。

直觉。量化了一个权重,产生了误差;能不能调整还没量化的那些权重,把这个误差对输出的影响抵消掉? 如果输入特征之间有相关性,一个权重的误差可以由相关特征上的权重部分补偿。

推导思路(来自 Optimal Brain Surgeon,Hassibi & Stork 1993)。对输出矩阵的每一行,误差是二次型 δ⊤Hδ\delta^\top H\delta,H=2X⊤XH=2X^\top X 是 Hessian(Week 0 Day 2 §1.4 的牛顿法里见过它的角色)。把第 qq 个权重从 wqw_q 量化成 quant(wq)\mathrm{quant}(w_q) 后,其余权重的最优调整是

δ=−wq−quant(wq)[H−1]qq H:,q−1\delta=-\frac{w_q-\mathrm{quant}(w_q)}{[H^{-1}]_{qq}}\,H^{-1}_{:,q}

GPTQ(Frantar et al. 2022)的工程贡献是:所有行按同样的列顺序处理(于是 H−1H^{-1} 只需算一次),用 Cholesky 分解保证数值稳定,再批量更新以提高 GPU 利用率。论文报告 175B 模型在单卡上约 4 小时完成量化。

w2day5_checks.py 第 5 组给出了一个完整的 numpy 实现(逐列量化,每量化一列就用逆 Hessian 的 Cholesky 因子把误差补偿到后面的列),在输入特征相关的线性层上对比四舍五入(RTN):

权重误差 RTN / GPTQ输出误差 RTN / GPTQ
4 bit0.121 / 0.1840.118 / 0.085
3 bit0.278 / 0.4440.280 / 0.199

这张表最值得记住的一点:GPTQ 的权重误差比 RTN 还大,输出误差却小得多。它不追求每个权重都准,而是追求乘上真实输入之后准。这是”按真正的目标优化”的一个好例子。

3.4 AWQ:保护重要的权重

观察(Lin et al. 2023):权重的重要性由它乘的激活有多大决定。激活大的输入通道,对应权重列上的一点误差会被放大。

做法:量化前把这些列放大 sjs_j 倍(推理时输入除以 sjs_j,融进前一层),它们在量化网格上相对就更精细;其余列相对变粗,但它们乘的是小激活,影响小。sj=∣X:,j∣‾αs_j=\overline{|X_{:,j}|}^\alpha,α\alpha 在校准数据上网格搜索。

第 6 组(4 bit、分组 128):

做法输出相对误差
RTN0.117
AWQ α=0.25\alpha=0.250.097
AWQ α=0.50\alpha=0.500.112
AWQ α=0.75\alpha=0.750.158

α\alpha 太大,被相对压粗的列误差反而占了上风,所以要搜索。AWQ 比 GPTQ 简单:只需要激活的统计量,不需要逆 Hessian,也不改变权重之间的相对关系,对校准数据的依赖更小。

3.5 四个方法的对照

方法解决什么思路结果格式
LLM.int8激活离群值离群列拆出来用 16 bitW8A8(混合)
SmoothQuant激活难量化等价缩放,把难度搬到权重W8A8
GPTQ权重低比特误差大用校准数据的 Hessian 补偿误差W4A16、W3A16
AWQ权重低比特误差大放大重要输入通道对应的权重W4A16

§4 NF4 与双重量化

4.1 NF4:按正态分布放码值

均匀 INT4 的 16 个码值是等间距的。但训练好的权重近似正态分布:靠近 0 的值很多,远处的很少。等间距的码值在 0 附近太稀、在两端太密。

信息论上的最优做法:让每个码值”负责”同样多的权重,即按分布的分位点放码值。QLoRA(Dettmers et al. 2023)的 NF4:取标准正态的分位点,归一化到 [−1,1][-1,1]。为了让 0 能被精确表示(大量权重接近 0,量化后仍需要精确的 0,比如 padding 或被剪枝的权重),正负两侧不对称:8 个正值、7 个负值,加上 0。第 7 组按这个方法构造出的 16 个码值:

[−1.0, −0.696, −0.525, −0.395, −0.284, −0.185, −0.091, 0, 0.080, 0.161, 0.246, 0.338, 0.441, 0.563, 0.723, 1.0][-1.0,\ -0.696,\ -0.525,\ -0.395,\ -0.284,\ -0.185,\ -0.091,\ 0,\ 0.080,\ 0.161,\ 0.246,\ 0.338,\ 0.441,\ 0.563,\ 0.723,\ 1.0]

注意 0 附近的间隔(约 0.08–0.09)比两端(0.28–0.30)小得多。对正态权重、每 64 个一块做 absmax 缩放:NF4 相对误差 0.092,均匀 INT4 0.108。

4.2 分块

NF4 在每 64 个权重的块内做 absmax 缩放(§2.2 的分组)。这样每块的分布都被归一化到 [−1,1][-1,1],码本对所有块通用。

4.3 双重量化

每 64 个权重一个 FP32 的 absmax,每参数额外 32/64=0.532/64=0.5 bit,对 4 bit 的模型来说是 12.5% 的开销。双重量化:把这些 absmax 常数本身再量化成 8 bit(每 256 个一组,每组一个 FP32 常数)。第 8 组:

864+3264×256=0.127 bit/参数\frac8{64}+\frac{32}{64\times256}=0.127\ \text{bit/参数}

省下 0.373 bit/参数,65B 模型约 2.8 GiB。NF4 + 双重量化 + 分页优化器 = QLoRA 的三件套(分页优化器在 Day 6 讲)。


§5 量化为什么能加速

第 9 组(batch 1,A100 带宽约 2.04 TB/s):

模型精度权重大小读一遍的时间下限token/s 上限
7BBF1613.0 GiB6.9 ms146
7BINT43.3 GiB1.7 ms583
70BBF16130.4 GiB68.7 ms15
70BINT432.6 GiB17.2 ms58

batch 1 的解码时间约等于读权重的时间,比特数减少 4 倍,速度上限提高 4 倍。所以 W4A16(只量化权重)也能加速解码:计算仍然在 16 bit 里做(权重读进来之后在寄存器里反量化),省的是读显存。

但它不是万能的:

  • prefill 和大 batch 是计算密集的(Week 1 Day 4 §1.3),瓶颈不在读权重,W4A16 基本帮不上忙,甚至因为反量化开销而变慢。要在这些场景加速,需要激活也量化(W8A8、FP8),走整数或 FP8 矩阵乘。
  • 需要专门的内核:如果实现只是”先把整个权重反量化回 16 bit 存进显存再乘”,读的字节数一点没少。真正的加速来自在计算内核里即时反量化。

回答学习计划的自测题:“BF16 为什么在训练里打败了 FP16”见 Day 4 §3;“量化加速的是什么”:batch 1 解码时加速的是访存,大 batch 时要靠低比特计算。


§6 PTQ 与 QAT

PTQ(训练后量化):模型训完之后直接量化,最多用一点校准数据(GPTQ、AWQ 都是 PTQ)。便宜、快,对 8 bit 和精心处理的 4 bit 通常够用。

QAT(量化感知训练):训练时前向就用量化后的权重,让模型学会适应量化误差。难点是 round 的导数几乎处处为 0,梯度过不去。直通估计器(STE):前向用量化值,反向假装 round 是恒等函数,让梯度直接穿过。

第 10 组(两层网络,量化第一层权重,3 个种子):

比特全精度PTQQAT(1000 步)
20.008–0.0120.060–0.0780.023–0.031
30.008–0.0120.016–0.0220.013–0.018

QAT 的收益主要来自其他参数学会补偿量化误差(这里是全精度的第二层)。比特越低,差距越大。代价是要训练,而且要有训练数据和流程。实践中:8 bit 和 4 bit 大多用 PTQ;2–3 bit 的极端量化、或者对质量要求很高时用 QAT。


§7 格式地图

学习计划问:“GPTQ / AWQ / GGUF 各自的定位?”

名字是什么主要场景
GPTQ一种 PTQ 算法(§3.3),也指用它量化后的权重格式GPU 推理,4 bit 权重
AWQ一种 PTQ 算法(§3.4),同上GPU 推理,4 bit 权重;vLLM 等推理框架常用
bitsandbytes(LLM.int8、NF4)加载时即时量化的库训练友好:QLoRA 的基础;推理速度不是它的强项
GGUFllama.cpp 的模型文件格式,含多种分组量化方案(如 Q4_K_M、Q8_0)CPU、Apple Silicon、本地推理
FP8数值格式(§1)H100 起的训练与推理,W8A8
SmoothQuant让激活可以 INT8 的变换(§3.2)需要 W8A8 的高吞吐推理服务

选择的逻辑:要在 GPU 上高吞吐推理,用 AWQ / GPTQ(或 FP8);要在笔记本上跑,用 GGUF;要微调一个大模型又显存不够,用 bitsandbytes 的 NF4(QLoRA,Day 6)。


§A 量化:API 用法与边界

A.1 bitsandbytes:加载时量化

from transformers import AutoModelForCausalLM, BitsAndBytesConfig
bnb = BitsAndBytesConfig(load_in_4bit=True,
                         bnb_4bit_quant_type="nf4",            # 或 "fp4"
                         bnb_4bit_use_double_quant=True,       # 双重量化(§4.3)
                         bnb_4bit_compute_dtype=torch.bfloat16)  # ★ 计算时反量化成什么
model = AutoModelForCausalLM.from_pretrained(name, quantization_config=bnb, device_map="auto")
print(model.get_memory_footprint() / 2**30)
  • bnb_4bit_compute_dtype 默认是 float32,会很慢;设成 bf16(显卡支持的话)。
  • bitsandbytes 主要支持 CUDA;其他后端的支持在逐步增加,但不要默认它能在 Mac 上跑。
  • 8 bit 用 load_in_8bit=True,走 LLM.int8 的混合分解(离群阈值 llm_int8_threshold 默认 6.0)。
  • 量化后的模型不能直接全量训练(整数权重没有梯度),只能在上面加 LoRA(Day 6)。训练前通常要调用 peft.prepare_model_for_kbit_training(model)。

A.2 加载别人量化好的模型

GPTQ、AWQ 量化好的模型在 Hugging Face 上很多,from_pretrained 会读取其中的量化配置自动加载(需要装对应的后端库)。加载前看模型卡:比特数、分组大小、用什么校准数据量化的。

A.3 验证量化质量

  • 困惑度:在一个标准语料(如 WikiText-2)上比较量化前后的困惑度。它敏感、便宜,是第一道检查。
  • 下游任务:困惑度只增加一点,某些任务(尤其是数学、代码)也可能掉得明显。要在你关心的任务上测。
  • 别只看平均:量化误差可能集中在特定类型的输入上。

A.4 自己写”伪量化”做实验

研究量化效果时,常用伪量化(fake quantization):量化再立刻反量化回浮点,模型的其余部分不变。它不省显存也不加速,但能准确模拟量化带来的精度影响,适合比较不同方案(参考代码就是这么做的)。


§B 实操

  1. 跑通 w2day5_checks.py,10 组结论都看懂。第 5 组的 GPTQ 实现逐行对照 §3.3 读一遍。
  2. 推导(1h):(a) 均匀量化误差的方差 s2/12s^2/12,每多 1 bit 误差减半;(b) SmoothQuant 的等价变换;(c) 从分位点构造 NF4 的 16 个码值;(d) 双重量化的 0.127 bit。
  3. 在 mini-GPT 上比较量化方案(2h):对 Week 1 训好的 mini-GPT 的所有线性层做伪量化:RTN 逐通道 8/4/3 bit、分组 128 的 4/3 bit、NF4、GPTQ 4/3 bit,比较验证集 loss。
  4. 在开放模型上(1.5h,需要 GPU 做 bitsandbytes 部分):Qwen/Qwen2.5-0.5B 在 WikiText-2 上的困惑度:bf16 / 伪量化 RTN-4bit / NF4 / bitsandbytes 8-bit / 4-bit,同时记录显存占用。
  5. 读论文:GPTQ 的第 3 节(算法)与 AWQ 的第 3 节(观察与方法)。

参考实现:code/w2d5_quant.py。


§C 自测

动机与直觉层

  1. 量化省的是什么?为什么只量化权重也能加速解码?什么时候它帮不上忙?
  2. 对称和非对称量化各适合什么数据?
  3. 为什么 4 bit 几乎必须分组?分组的代价是什么?
  4. 大模型的离群值是什么现象?为什么它对量化致命?
  5. SmoothQuant 的”把难度搬到权重上”是什么意思?为什么这个变换是等价的?
  6. GPTQ 为什么权重误差更大、输出误差却更小?
  7. AWQ 的”重要权重”是由什么决定的?
  8. NF4 为什么比均匀 INT4 更适合权重?为什么正负两侧不对称?
  9. PTQ 与 QAT 的区别?STE 在做什么?QAT 的收益主要从哪来?

推导与计算层

  1. 写出仿射量化的公式。推出均匀量化误差的方差。
  2. 写出 GPTQ / OBS 的权重更新公式,说明 Hessian 从哪来。
  3. 计算双重量化前后每参数的额外比特数。
  4. 70B 模型 4 bit 时权重多大?A100 上 batch 1 解码的吞吐上限是多少?

工程层

  1. BitsAndBytesConfig 里 bnb_4bit_compute_dtype 不设会怎样?
  2. GPTQ / AWQ / GGUF / bitsandbytes 各自适合什么场景?
  3. 怎样验证一个量化方案的质量?为什么不能只看困惑度?

出关标准

  • 能说清量化省显存、加速解码的原理,以及它什么时候不加速
  • 能写出仿射量化公式,比较对称 / 非对称与不同粒度
  • 能解释离群值现象,说出 LLM.int8 与 SmoothQuant 的思路
  • 能写出 GPTQ 的更新公式,解释”权重误差大、输出误差小”
  • 能说出 AWQ 的思路与超参搜索
  • 能从分位点构造 NF4,算出双重量化的开销
  • 能区分 PTQ 与 QAT,说清 STE
  • 能回答 GPTQ / AWQ / GGUF / bitsandbytes 的定位
  • 在 mini-GPT 上完成一次量化方案对比

疑问清单

← 返回课程目录