时长 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 数值格式
| 格式 | 比特 | 可表示的值 | 典型用途 |
|---|---|---|---|
| FP32 | 32 | 浮点,范围 | 主权重、优化器状态 |
| BF16 / FP16 | 16 | 浮点(Day 4 §3.1) | 训练与推理的默认精度 |
| FP8(E4M3 / E5M2) | 8 | 浮点,最大值约 448 / 57344 | H100 起的训练与推理 |
| INT8 | 8 | 整数 −127…127(乘一个 scale) | 权重和激活量化 |
| INT4 / NF4 | 4 | 16 个码值(乘一个 scale) | 权重量化 |
简写约定:W4A16 表示权重 4 bit、激活 16 bit(只量化权重,计算时把权重反量化回 16 bit);W8A8 表示权重和激活都 8 bit(可以走整数矩阵乘)。这两类的加速原理不同(§5)。
§2 量化的基本操作
2.1 仿射量化
把实数 映射到 位整数:
是缩放因子(scale), 是零点。
- 对称量化(absmax):,。码值关于 0 对称。
- 非对称量化:把 映射到 ,多存一个零点。
误差从哪来。 舍入误差在 上近似均匀分布,方差 。每多 1 bit, 减半,误差标准差减半(约 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 管多少个数
由这组数的最大值决定。这组数的范围越大,普通值分到的精度越少。 第 2 组(256×1024 的矩阵,不同行的尺度差异很大):
| 逐张量 | 逐输出通道 | 分组 | |
|---|---|---|---|
| 8 bit | 0.101 | 0.009 | 0.007 |
| 4 bit | 0.489 | 0.153 | 0.120 |
4 bit 下几乎必须分组。代价是要多存 scale:每个 scale 16 bit 的话,逐通道每参数额外 0.016 bit, 额外 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 倍的激活:
| 做法 | 输出相对误差 |
|---|---|
| 激活逐张量 INT8 | 0.109(普通通道 62.8% 的值被量成 0) |
| 激活逐 token INT8 | 0.052 |
| LLM.int8 混合分解 | 0.002 |
LLM.int8 的办法:既然离群值集中在少数固定的列上,就把这些列拆出来用 16 bit 算,其余照常 INT8,最后相加。代价是额外的一小块高精度矩阵乘和拆分开销。论文报告用这种方法把 175B 模型量化到 8 bit 而几乎不掉点。
3.2 SmoothQuant:把难度从激活搬到权重
观察:激活难量化(有离群列),权重好量化(分布平缓)。能不能把一部分难度转移过去?
等价变换:对输入的第 维,激活除以 、权重的第 行乘以 ,矩阵乘的结果完全不变:
取 , 控制转移多少。第 4 组(激活逐张量、权重逐通道,都 INT8):
| 0(不转移) | 0.25 | 0.5 | 0.75 | |
|---|---|---|---|---|
| 输出相对误差 | 0.127 | 0.044 | 0.018 | 0.021 |
太小,激活仍然难量化;太大,权重被放大的列变得难量化。论文对多数模型取 0.5。好处:激活也能用 INT8,推理可以走真正的整数矩阵乘(W8A8),在计算密集的场景也加速。缩放因子可以融进前一层(LayerNorm 或上一个线性层),推理时零开销。
3.3 GPTQ:用二阶信息补偿误差
问题重新表述。量化一个线性层,我们真正关心的不是 ,而是输出的误差 , 是校准数据上这一层的输入。
直觉。量化了一个权重,产生了误差;能不能调整还没量化的那些权重,把这个误差对输出的影响抵消掉? 如果输入特征之间有相关性,一个权重的误差可以由相关特征上的权重部分补偿。
推导思路(来自 Optimal Brain Surgeon,Hassibi & Stork 1993)。对输出矩阵的每一行,误差是二次型 , 是 Hessian(Week 0 Day 2 §1.4 的牛顿法里见过它的角色)。把第 个权重从 量化成 后,其余权重的最优调整是
GPTQ(Frantar et al. 2022)的工程贡献是:所有行按同样的列顺序处理(于是 只需算一次),用 Cholesky 分解保证数值稳定,再批量更新以提高 GPU 利用率。论文报告 175B 模型在单卡上约 4 小时完成量化。
w2day5_checks.py 第 5 组给出了一个完整的 numpy 实现(逐列量化,每量化一列就用逆 Hessian 的 Cholesky 因子把误差补偿到后面的列),在输入特征相关的线性层上对比四舍五入(RTN):
| 权重误差 RTN / GPTQ | 输出误差 RTN / GPTQ | |
|---|---|---|
| 4 bit | 0.121 / 0.184 | 0.118 / 0.085 |
| 3 bit | 0.278 / 0.444 | 0.280 / 0.199 |
这张表最值得记住的一点:GPTQ 的权重误差比 RTN 还大,输出误差却小得多。它不追求每个权重都准,而是追求乘上真实输入之后准。这是”按真正的目标优化”的一个好例子。
3.4 AWQ:保护重要的权重
观察(Lin et al. 2023):权重的重要性由它乘的激活有多大决定。激活大的输入通道,对应权重列上的一点误差会被放大。
做法:量化前把这些列放大 倍(推理时输入除以 ,融进前一层),它们在量化网格上相对就更精细;其余列相对变粗,但它们乘的是小激活,影响小。, 在校准数据上网格搜索。
第 6 组(4 bit、分组 128):
| 做法 | 输出相对误差 |
|---|---|
| RTN | 0.117 |
| AWQ | 0.097 |
| AWQ | 0.112 |
| AWQ | 0.158 |
太大,被相对压粗的列误差反而占了上风,所以要搜索。AWQ 比 GPTQ 简单:只需要激活的统计量,不需要逆 Hessian,也不改变权重之间的相对关系,对校准数据的依赖更小。
3.5 四个方法的对照
| 方法 | 解决什么 | 思路 | 结果格式 |
|---|---|---|---|
| LLM.int8 | 激活离群值 | 离群列拆出来用 16 bit | W8A8(混合) |
| SmoothQuant | 激活难量化 | 等价缩放,把难度搬到权重 | W8A8 |
| GPTQ | 权重低比特误差大 | 用校准数据的 Hessian 补偿误差 | W4A16、W3A16 |
| AWQ | 权重低比特误差大 | 放大重要输入通道对应的权重 | W4A16 |
§4 NF4 与双重量化
4.1 NF4:按正态分布放码值
均匀 INT4 的 16 个码值是等间距的。但训练好的权重近似正态分布:靠近 0 的值很多,远处的很少。等间距的码值在 0 附近太稀、在两端太密。
信息论上的最优做法:让每个码值”负责”同样多的权重,即按分布的分位点放码值。QLoRA(Dettmers et al. 2023)的 NF4:取标准正态的分位点,归一化到 。为了让 0 能被精确表示(大量权重接近 0,量化后仍需要精确的 0,比如 padding 或被剪枝的权重),正负两侧不对称:8 个正值、7 个负值,加上 0。第 7 组按这个方法构造出的 16 个码值:
注意 0 附近的间隔(约 0.08–0.09)比两端(0.28–0.30)小得多。对正态权重、每 64 个一块做 absmax 缩放:NF4 相对误差 0.092,均匀 INT4 0.108。
4.2 分块
NF4 在每 64 个权重的块内做 absmax 缩放(§2.2 的分组)。这样每块的分布都被归一化到 ,码本对所有块通用。
4.3 双重量化
每 64 个权重一个 FP32 的 absmax,每参数额外 bit,对 4 bit 的模型来说是 12.5% 的开销。双重量化:把这些 absmax 常数本身再量化成 8 bit(每 256 个一组,每组一个 FP32 常数)。第 8 组:
省下 0.373 bit/参数,65B 模型约 2.8 GiB。NF4 + 双重量化 + 分页优化器 = QLoRA 的三件套(分页优化器在 Day 6 讲)。
§5 量化为什么能加速
第 9 组(batch 1,A100 带宽约 2.04 TB/s):
| 模型 | 精度 | 权重大小 | 读一遍的时间下限 | token/s 上限 |
|---|---|---|---|---|
| 7B | BF16 | 13.0 GiB | 6.9 ms | 146 |
| 7B | INT4 | 3.3 GiB | 1.7 ms | 583 |
| 70B | BF16 | 130.4 GiB | 68.7 ms | 15 |
| 70B | INT4 | 32.6 GiB | 17.2 ms | 58 |
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 个种子):
| 比特 | 全精度 | PTQ | QAT(1000 步) |
|---|---|---|---|
| 2 | 0.008–0.012 | 0.060–0.078 | 0.023–0.031 |
| 3 | 0.008–0.012 | 0.016–0.022 | 0.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 的基础;推理速度不是它的强项 |
| GGUF | llama.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 实操
- 跑通
w2day5_checks.py,10 组结论都看懂。第 5 组的 GPTQ 实现逐行对照 §3.3 读一遍。 - 推导(1h):(a) 均匀量化误差的方差 ,每多 1 bit 误差减半;(b) SmoothQuant 的等价变换;(c) 从分位点构造 NF4 的 16 个码值;(d) 双重量化的 0.127 bit。
- 在 mini-GPT 上比较量化方案(2h):对 Week 1 训好的 mini-GPT 的所有线性层做伪量化:RTN 逐通道 8/4/3 bit、分组 128 的 4/3 bit、NF4、GPTQ 4/3 bit,比较验证集 loss。
- 在开放模型上(1.5h,需要 GPU 做 bitsandbytes 部分):
Qwen/Qwen2.5-0.5B在 WikiText-2 上的困惑度:bf16 / 伪量化 RTN-4bit / NF4 / bitsandbytes 8-bit / 4-bit,同时记录显存占用。 - 读论文:GPTQ 的第 3 节(算法)与 AWQ 的第 3 节(观察与方法)。
参考实现:code/w2d5_quant.py。
§C 自测
动机与直觉层
- 量化省的是什么?为什么只量化权重也能加速解码?什么时候它帮不上忙?
- 对称和非对称量化各适合什么数据?
- 为什么 4 bit 几乎必须分组?分组的代价是什么?
- 大模型的离群值是什么现象?为什么它对量化致命?
- SmoothQuant 的”把难度搬到权重上”是什么意思?为什么这个变换是等价的?
- GPTQ 为什么权重误差更大、输出误差却更小?
- AWQ 的”重要权重”是由什么决定的?
- NF4 为什么比均匀 INT4 更适合权重?为什么正负两侧不对称?
- PTQ 与 QAT 的区别?STE 在做什么?QAT 的收益主要从哪来?
推导与计算层
- 写出仿射量化的公式。推出均匀量化误差的方差。
- 写出 GPTQ / OBS 的权重更新公式,说明 Hessian 从哪来。
- 计算双重量化前后每参数的额外比特数。
- 70B 模型 4 bit 时权重多大?A100 上 batch 1 解码的吞吐上限是多少?
工程层
BitsAndBytesConfig里bnb_4bit_compute_dtype不设会怎样?- GPTQ / AWQ / GGUF / bitsandbytes 各自适合什么场景?
- 怎样验证一个量化方案的质量?为什么不能只看困惑度?
出关标准
- 能说清量化省显存、加速解码的原理,以及它什么时候不加速
- 能写出仿射量化公式,比较对称 / 非对称与不同粒度
- 能解释离群值现象,说出 LLM.int8 与 SmoothQuant 的思路
- 能写出 GPTQ 的更新公式,解释”权重误差大、输出误差小”
- 能说出 AWQ 的思路与超参搜索
- 能从分位点构造 NF4,算出双重量化的开销
- 能区分 PTQ 与 QAT,说清 STE
- 能回答 GPTQ / AWQ / GGUF / bitsandbytes 的定位
- 在 mini-GPT 上完成一次量化方案对比