对应 Maps to:CMU 11-868 / 11-968 Large Language Model Systems(Spring 2026,Lei Li),Lecture 19 · Wed 3/25 · Model Quantization(slides:llmsys-19-quantization.pdf,26 页)。 阅读材料:Nagel et al. (2021) A White Paper on Neural Network Quantization;Hubara et al. (2021) AdaQuant;Yao et al. (2022) ZeroQuant;Dettmers et al. (2022) LLM.int8()。 依据说明:slides 覆盖:LLM 训练与推理的代价(Llama-70B 140 GB、DeepSeek-V3)、量化的优缺点、精度格式(BF16、FP16、INT8)、BF16 / FP16 为什么快(half2、HFMA2 与 CUDA 的半精度 API)、直接量化的三种误差、absmax 与零点量化(含代码)、QAT 与 PTQ、逐层量化的目标、AdaQuant(及 MobileNet-v2 的表)、为什么量化 LLM 难、ZeroQuant(逐层蒸馏、内核融合、规模与耗时)、LLM.int8()(离群特征、混合精度分解)。本讲义用到的城市语言模型与”超出理论下界”这把尺子、整数与浮点网格的比较、截断的扫描、粒度与层敏感性的实验、植入离群特征的方法、AdaQuant / 逐块蒸馏 / QAT 的简化实现与比较,都是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture19/(5 个脚本 +quant.py;第一次运行训练一个小模型约 40 秒,之后每个脚本 1–45 秒) 预计阅读时间:2.5 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 说出量化省了什么(显存、带宽、计算)、损失了什么;比较 INT8 / INT4 与 FP8 / FP4 的网格,知道什么数据适合哪种。
- 写出 absmax(对称)与零点(非对称)量化,解释误差的三个来源,以及截断范围的取舍。
- 解释量化粒度(逐张量、逐通道、逐组)的作用与代价,算出”每个权重实际几位”。
- 说明 LLM 的离群特征为什么让 INT8 激活量化失效,LLM.int8() 的混合精度分解怎样解决。
- 区分 QAT 与 PTQ,理解逐层量化(AdaQuant)与逐层蒸馏(ZeroQuant)的目标和代价,以及”逐层误差最小 ≠ 整个模型最好”。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 整数与浮点格式、舍入、混合精度(前置第 2 讲,其中 §5 是量化预告) | GPTQ 的二阶补偿(第 20 讲) |
| 解码是访存受限的(第 4 讲、第 9 讲) | SmoothQuant、AWQ(第 20 讲) |
| LLaMA 式结构(第 7 讲) | 量化的 KV 缓存与推理内核(第 20、24 讲) |
0.3 本讲地图 Map
§1 为什么要量化;低精度数字:显存、网格、INT 还是 FP、half2 ← 代码 01
§2 直接量化:absmax 与零点;三种误差;截断的取舍 ← 代码 02
§3 在真正的模型上量化:位数、粒度、对称与否、哪些层敏感、误差怎样累积 ← 代码 03
§4 QAT vs PTQ;逐层的方法:AdaQuant、ZeroQuant ← 代码 05
§5 LLM.int8():离群特征与混合精度分解 ← 代码 04
§6 小结 §7 常见误区 §8 练习 §9 延伸阅读 §10 下一讲预告
0.4 一把尺子
本讲和下一讲的实验都在同一个模型上做:第 7 讲的 LLaMA 式结构(RMSNorm、SwiGLU、RoPE、GQA、无偏置),d = 128、4 层、80 万参数,在第 1 讲的城市语言上训练 2000 步(quant.get_city_lm(),第一次约 40 秒,之后读缓存)。城市语言是我们自己定义的,真实分布已知,测试集交叉熵的理论下界可以精确算出来。尺子就是”测试集交叉熵超出理论下界多少毫 nat / token”:全精度模型是 3.0,量化让它涨多少,就是量化的损失。
一个提醒:这个模型小、语言简单,大部分位置几乎是确定的,所以它对量化相当”皮实”——8 位、4 位逐通道几乎无损,差别要到 3 位、2 位才明显。真实的大模型在 4 位就能看到差别。
1. 为什么要量化 · Why quantize
1.1 代价
slides 第 4 页:Llama-70B 训练用了 3930 万 H100 小时,推理要 140 GB 显存;DeepSeek-V3 推理要 400 GB 以上。01 号脚本第 1 部分:
| 模型 | FP32 | BF16 | INT8 / FP8 | INT4 |
|---|---|---|---|---|
| LLaMA-3 8B | 32 GB | 16 GB | 8 GB | 4 GB |
| Llama-70B | 280 GB | 140 GB | 70 GB | 35 GB |
| DeepSeek-V3 | 2684 GB | 1342 GB | 671 GB | 336 GB |
量化(quantization,slides 第 5 页)用低位数存参数和中间结果:
- 省显存 → 放得下更大的批、更长的 KV 缓存,甚至能把模型塞进更少的卡;
- 快:解码是访存受限的,每生成一个 token 都要把全部权重读一遍——字节数减半,速度接近翻倍;低精度的乘法在一个周期里能做更多次;
- 代价:可能损失精度。
1.2 两种网格
slides 第 6 页的几种格式(细节在前置第 2 讲):BF16(8 位指数、7 位尾数,范围和 FP32 一样、精度低)、FP16(5 位指数、10 位尾数)、INT8(−128 到 127 的整数)。量化关心的是:把一组数缩放到格式的范围里,能表示的值分布在哪里。
- 整数网格是均匀的:INT8 缩放到 [0, 1] 后 128 个值,间距处处是 1/127。
- 浮点网格是对数均匀的:每个二进位区间里值的个数相同,越靠近 0 越密。FP8 E4M3 缩放到 [0, 1] 后 127 个值,其中 79% 小于 0.1,接近 1 时间距是 INT8 的 9 倍。4 位时:INT4 是 {0, 1/7, …, 1},FP4 E2M1 是 {0, 0.083, 0.167, 0.25, 0.333, 0.5, 0.667, 1}。
哪个好?01 号脚本第 3 部分把 10 万个数逐张量缩放后量化(相对误差):
| 分布 | max / std | INT8 | FP8 E4M3 | INT4 | FP4 E2M1 |
|---|---|---|---|---|---|
| 均匀 | 3.5 | 0.40% | 2.56% | 7.15% | 11.05% |
| 正态 | 7.6 | 1.04% | 2.64% | 18.90% | 12.84% |
| 拉普拉斯 | 11.6 | 1.87% | 2.62% | 32.65% | 20.12% |
| 学生 t(ν = 3) | 47.3 | 8.22% | 2.66% | 79.47% | 65.55% |
- 8 位:INT8 在均匀、正态、拉普拉斯上都赢——8 位的整数网格已经够细,而 FP8 只有 3 位尾数,大值附近反而粗。只有长尾分布(最大值把 scale 撑大,大多数值挤在 0 附近)FP8 赢。
- 4 位:形势反过来,除了均匀分布,FP4 都赢——只有 16 个值时,集中放在数据最密的地方更划算。这是 Blackwell 加入 FP4 张量核心的原因之一。
- 4 位逐张量量化长尾数据,两种都很差(65–79%)。整数的对策是更细的粒度(§3)和单独处理离群值(§5)。
1.3 为什么 16 位更快:half2
slides 第 7–8 页:A100 及以后的 GPU 支持 BF16。一个 32 位寄存器正好装两个 16 位数(01 号脚本第 4 部分把 [1.5, −2.25] 装成 0xC0103FC0),一条 __hfma2(a, b, c) 指令同时做两个乘加(舍入到最近的偶数),吞吐是标量 FP32 的两倍;一个 warp 的 32 个线程每人读 2 个 BF16,一次正好一条 128 字节的缓存行。量化把这个思路推得更远:一个 32 位寄存器能装 4 个 INT8 或 8 个 INT4。代价是要乘回 scale 才是原来的数——反量化的开销,以及量化的误差。
2. 直接量化 · Direct quantization
2.1 absmax 与零点
slides 第 10–11 页的两段代码(quant.py 原样收录):
def absmax_quantize(X):
scale = 127 / torch.max(torch.abs(X))
X_quant = (scale * X).round()
X_dequant = X_quant / scale
return X_quant.to(torch.int8), X_dequant
def zeropoint_quantize(X):
x_range = torch.max(X) - torch.min(X)
x_range = 1 if x_range == 0 else x_range
scale = 255 / x_range
zeropoint = (-scale * torch.min(X) - 128).round()
X_quant = torch.clip((X * scale + zeropoint).round(), -128, 127)
X_dequant = (X_quant - zeropoint) / scale
return X_quant.to(torch.int8), X_dequant
02 号脚本第 1 部分在 x = [−1.2, 0.3, 2.5, −0.7, 0, 1.1, 0.05] 上一步一步展开:
- absmax(对称):[−2.5, 2.5] → [−127, 127],scale = 50.8。0 精确地对应 0;但这个向量的负数一侧只到 −1.2,[−127, −61) 那一段整数被浪费了。最大误差 0.0091。
- 零点(非对称):[−1.2, 2.5] → [−128, 127],scale = 68.9,零点 −45。用满 256 个整数,最大误差 0.0065。零点先取整,所以 x = 0 反量化后仍然正好是 0——这很重要,网络里大量的 0(ReLU 之后、填充)不能变成小的非零数。代价是多存一个零点。
2.2 误差的三个来源
slides 第 9 页:精度损失(量化噪声累积)、范围不匹配(截断)、舍入误差。02 号脚本第 2 部分:
- 舍入:误差在 [−s/2, s/2] 里近似均匀,均方误差 ≈ s²/12(26 万个正态数,实测与公式差千分之几)。INT4 的 scale 是 INT8 的 18 倍,均方误差大 329 倍。
- 截断:absmax 从不截断,但 scale 被最大值撑大(§2.3)。
- 累积:长度 d 的点积把 d 个独立的舍入误差加起来。w ~ N(0, 1/d) 时 scale 也 ∝ 1/√d,两者大体抵消,误差只缓慢增长(d 从 64 到 4096,INT8 从 0.0061 到 0.0087)。但网络有很多层,每层的误差传给下一层(§3.4)。
2.3 截断的取舍
把范围缩到最大值的 c 倍:scale 变细(舍入误差 ∝ c²),超出 c·max 的值被截断。02 号脚本第 3 部分(拉普拉斯分布的权重):最佳的 c,INT8 是 0.8,INT4 是 0.4,INT3 是 0.3。位数越少,舍入误差越大,越值得牺牲少数大值。截断不一定是坏事——但最佳的 c 取决于分布和位数,实用的方法要么在校准数据上搜索(GPTQ、AWQ 的实现里都有),要么让每个小组用自己的范围(§3),要么让步长参与训练(§4)。
2.4 对称还是非对称
02 号脚本第 4 部分看模型里的真实张量:RMSNorm 的输出、权重大致对称,两种量化差不多;SiLU 的输出几乎全是非负的(−0.28 到 14.39),非对称量化把 INT8 误差从 4.94% 降到 2.46%、INT4 从 41.56% 降到 29.44%。所以权重通常用对称量化(不用存零点,内核简单),非对称留给偏向一侧的激活(SiLU、ReLU、softmax 之后)。
3. 在真正的模型上量化 · Quantizing a real model
3.1 位数与粒度
四舍五入(RTN,round-to-nearest)是最简单的训练后量化:把每个权重取整到最近的网格点。区别只在一个 scale 管多少个数:
- 逐张量:整个矩阵一个 scale;
- 逐通道:每个输出通道(权重矩阵的一行)一个;
- 逐组:每一行再沿输入维每 g 个一组(GPTQ、AWQ 常用 g = 128)。
03 号脚本第 1 部分把所有线性层量化(超出下界,全精度 3.0):
| 位数 | 逐张量 | 逐通道 | 逐组 128 | 逐组 64 | 逐组 32 |
|---|---|---|---|---|---|
| INT8 | 3.0 | 3.0 | 3.0 | 3.0 | 3.0 |
| INT4 | 7.9 | 3.0 | 2.9 | 3.2 | 3.1 |
| INT3 | 22.7 | 6.3 | 6.6 | 5.6 | 4.0 |
| INT2 | 3465 | 154 | 96 | 84 | 19 |
粒度越细,一个大值只祸害它所在的那一小组。代价是 scale 的存储(16 位):INT4 逐通道(这里一行 128 个)是 4.125 位 / 权重,逐组 32 是 4.5 位。“4 位、组大小 128”是 4.125 位。
3.2 对称还是非对称(权重)
03 号脚本第 2 部分:INT2 逐通道,对称 153.8 → 非对称 20.1;逐组 32,19.1 → 8.7。位数越少,差别越大:一行里正负两侧的最大值不相等,对称量化按较大的一侧定 scale、而且只用 2^b − 1 个整数(2 位时只有 {−1, 0, 1} 三个值!)。GPTQ 论文用的正是”逐行的非对称最小–最大网格”(slides 第 23 页)。
3.3 哪些层最敏感
只量化一种层(INT2 逐通道),03 号脚本第 3 部分:
| 只量化 | 权重个数 | 比全精度多 |
|---|---|---|
| attn.q | 65536 | +0.0 |
| attn.v | 32768 | +0.2 |
| ffn.w1 | 196608 | +0.6 |
| ffn.w3 | 196608 | +0.1 |
| ffn.w2 | 196608 | +28.3 |
w1、w3、w2 参数一样多,敏感程度差了几十倍。原因是输入的大小:同样的权重误差乘上更大的输入,输出误差更大。w2 的输入(门 ⊙ W₃x)大到 ±200,其余层的输入是 RMSNorm 的输出,只有几。这启发了两件事:混合精度(敏感层用更多位);以及 GPTQ——既然误差取决于输入,就用输入的统计量来决定怎样取整(第 20 讲)。
3.4 误差怎样累积
03 号脚本第 4 部分:只量化第 1 层就多出 +122.5,其余三层单独量化各只有 +0.2 到 +21.2;量化前 3 层 165.5,4 层反而 153.8;4 层合计的增量(+150.8)与各层单独增量之和(+144.4)也不相等。前面层的误差改变了后面层的输入,后面层的误差可能放大它、也可能部分抵消它。所以”每一层各自把输出误差压到最小”并不保证整个网络最好——slides 第 17 页说逐层量化”仍可能导致量化误差的累积”。
4. QAT vs PTQ · Training-aware vs post-training
4.1 两条路
slides 第 14–16 页的地图:
- 训练中量化(QAT,quantization-aware training):训练(或微调)时就让前向使用量化后的权重,模型学会适应。精度好,但要昂贵的重新训练,难以扩展到几十亿参数。
- 训练后量化(PTQ,post-training quantization):训练完再量化。RTN 能扩展到任意大的模型,但低位数时精度掉得厉害;AdaQuant、BRECQ、OBQ 这类方法逐层(或逐块)地处理,保住精度;ZeroQuant、LLM.int8()、GPTQ 则追求扩展到大模型。
QAT 有一个技术问题:round 的导数几乎处处是 0,梯度传不过去。标准的办法是直通估计(STE,straight-through estimator):前向取整,反向假装它是恒等函数(quant.ste_round)。我们的实现还让步长(scale)一起训练——相当于自动找 §2.3 的最佳截断点。
4.2 逐层量化的目标
slides 第 17 页:对每个线性层,找量化后的 Ŵ 使输出误差最小:
X 是这一层在校准数据上的输入。注意目标是输出,不是权重本身:RTN 最小化的是 ‖W − Ŵ‖,而输入大的方向上的误差更要紧(§3.3)。
- AdaQuant(Hubara et al.,slides 第 18 页):给 W 加一个连续的扰动 V,量化 W + V,用梯度下降优化 V 和步长,目标就是上面的逐层输出误差。slides 第 19 页的结果:MobileNet-v2 直接量化后 top-1 只剩 1.7%,AdaQuant 拉回 52.3%(浮点 65.4%)。
- ZeroQuant 的逐层蒸馏(LKD,Yao et al.,slides 第 21 页):一次训练一个块(学生),让它的输出等于全精度这一块(教师)的输出,两者用同样的输入:
一次只需要一个块的显存。ZeroQuant 还融合了量化相关的内核,验证到 20B(GPT-NeoX),1.3B 的模型约 3 小时,已集成进 DeepSpeed(slides 第 22 页)。
4.3 比较
05 号脚本在同一个模型上比较四种方法(逐通道、对称):
| 位数 | RTN | AdaQuant | 逐块蒸馏 | QAT |
|---|---|---|---|---|
| INT3 | 6.3 | 3.4 | 3.1 | 2.9 |
| INT2 | 153.8 | 19.8 | 3.9 | 4.7 |
(全精度 3.0;⏱ 本机 AdaQuant 约 2–3 秒、逐块蒸馏约 11 秒、QAT 约 7 秒。)2 位时 RTN 基本把模型毁了;AdaQuant 只用 64 × 32 个 token 的无标签校准数据把每层单独调一调就降到 19.8;逐块蒸馏和 QAT 几乎回到全精度。
更有意思的是逐层目标(INT2,校准数据上的平均相对输出误差):RTN 41.8%、AdaQuant 16.9%、逐块蒸馏 56.2%、QAT 40.8%。AdaQuant 直接优化逐层误差,所以它最小;可整个模型反而是逐层误差更大的逐块蒸馏和 QAT 更好。逐层误差最小 ≠ 整个模型最好:一个块里几层的误差可以互相补偿,逐块蒸馏只要求整个块的输出对;QAT 只要求最终的损失低。
| 方法 | 数据 | 目标 | 反向传播的范围 | 显存 |
|---|---|---|---|---|
| RTN | 不需要 | — | 不需要 | 一个权重矩阵 |
| AdaQuant | 无标签校准数据 | 这一层的输出 | 一层 | 一层 |
| ZeroQuant 逐层蒸馏 | 无标签校准数据 | 全精度这一块的输出 | 一个块 | 一个块 |
| QAT | 训练数据 | 语言模型损失 | 整个模型 | 整个模型 + 优化器 + 激活 |
越往下越准、越贵。slides 第 20 页的问题”为什么量化 LLM 难”:精确的方法(AdaQuant、OBQ)难以扩展到几十亿参数;能扩展的方法(RTN 式的取整)在 3、4 位时精度损失大。第 20 讲的 GPTQ 同时做到了两点:不做梯度下降,用二阶信息解析地补偿误差。
5. LLM.int8():离群特征 · Outlier features
5.1 问题
前面都是只量化权重(W4A16 之类:权重 4 位,激活 16 位,乘之前反量化)。如果激活也量化成 INT8(W8A8),矩阵乘法就能用 INT8 张量核心,算得更快。可 Dettmers et al. (2022) 发现,大模型的激活里有离群特征(outlier features,slides 第 23–24 页):
- 幅度 ≥ 6.0(大多数激活只有个位数以下);
- 系统性:集中在固定的少数几个特征维,出现在 ≥ 25% 的层、≥ 6% 的序列位置;
- 在 6.7B 左右的模型里突然出现,普通的 INT8 量化在这个规模以上突然失效。
5.2 植入离群特征
我们的小模型没有自然出现这种现象。quant.plant_outliers 在不改变模型函数的前提下植入它:把每层两个 RMSNorm 的 γ 在第 3、77 维放大 c 倍,同时把读它们的线性层(q、k、v;w1、w3)的对应输入列缩小 c 倍。RMSNorm 的 γ 是逐维的乘数,两者在乘法里抵消。04 号脚本第 1 部分:c = 300 时全精度损失完全不变(2.975),但第 2 层注意力的输入在这两维上大到 594,其余维最大只有 3.8、中位数 0.55,而且 100% 的 token 的最大值都落在这两维上——和真实模型一样的”系统性”。
5.3 W8A8 的三种做法
04 号脚本第 2 部分(超出下界):
| c | 逐张量 INT8 | 向量级 INT8 | LLM.int8() |
|---|---|---|---|
| 1(不植入) | 3.1 | 3.0 | 3.0 |
| 30 | 3.1 | 3.0 | 3.0 |
| 100 | 4.5 | 3.3 | 3.0 |
| 300 | 8367 | 2055 | 3.0 |
- 向量级量化(vector-wise):X 的每一行(每个 token)一个 scale,W 的每一行(每个输出通道)一个 scale,INT8 × INT8 用 INT32 累加,最后乘回 c_x ⊗ c_w。比逐张量好,但离群值大到 300 倍时也崩了。
- 为什么逐 token 的 scale 救不了(
04号脚本第 3 部分):每一行都有那两个离群维,每一行的 scale 都被撑大,其余维的值有 73% 被量化成 0。向量级量化能对付”某些 token 特别大”,对付不了”某些特征维特别大”。如果每个特征维(每列)一个 scale,只有 1% 变成 0——可矩阵乘法 Y = X·Wᵀ 是沿特征维求和的,按列的 scale 提不到求和外面(除非挪进 W 的对应列——这正是 SmoothQuant 的想法,第 20 讲)。
5.4 混合精度分解
LLM.int8() 选了另一条路:找出 X 中有任何元素 |x| ≥ 6 的特征维(列),这几列和 W 的对应列用 16 位算,其余列用向量级 INT8 算,两部分相加:
上表最后一列:任何 c 都是 3.0。论文说大模型里超过 99.9% 的值仍用 8 位相乘。在我们的小模型上(04 号脚本第 4 部分),阈值 6 时有 17.4% 的特征维留在 16 位——因为这个模型 ffn.w2、attn.o 的输入天然就很大(ffn.w2 最大到 900),不是归一化过的;阈值调到 100,只剩 1.8%,损失仍是 3.0。阈值要按模型的激活尺度来定。工程上的代价:每个矩阵乘法要先找离群列、拆成两个乘法再相加,对小模型来说这个开销可能比省下的还多。
6. 小结 · Summary
- 量化用低位数存权重 / 激活:省显存、省带宽(解码近乎线性加速)、可以用更快的低精度乘法;代价是精度。Llama-70B:140 GB → INT4 35 GB。
- 整数网格均匀,浮点网格对数均匀:8 位时 INT8 多数情况更好,4 位时 FP4 多数情况更好;长尾数据对两者都难。
- absmax(对称)与零点(非对称)量化;误差来自舍入(≈ s²/12)、截断、累积。位数越少,越值得截断(最佳范围 INT8 0.8、INT4 0.4 × max)。
- 粒度:逐张量 → 逐通道 → 逐组,越细越准,代价是 scale 的存储(4 位、组 128 = 4.125 位)。非对称在极低位数时帮助很大。层的敏感程度取决于输入的大小(ffn.w2 最敏感)。
- QAT 准但贵;PTQ 里 RTN 最简单,AdaQuant(逐层重建)、ZeroQuant(逐块蒸馏)用少量校准数据大幅挽回精度。逐层误差最小 ≠ 整个模型最好。
- LLM.int8():大模型的激活有系统性的离群特征,逐张量 / 逐 token 的 INT8 都会失效;把离群的特征维用 16 位单独算,其余 INT8。
7. 常见误区 · Common pitfalls
- “FP8 一定比 INT8 精确。” 8 位时,正态、拉普拉斯分布的数据 INT8 误差更小(§1.2)。
- “量化误差就是权重误差。” 要紧的是输出误差 ‖WX − ŴX‖,它取决于输入(§3.3、§4.2)。
- “截断一定有害。” 低位数时适度截断是最优的(§2.3)。
- “零点量化会让 0 变成非零数。” 零点取整后 0 精确表示(§2.1)。
- “逐组量化是免费的。” 每组一个 16 位 scale,组 32 时每个权重多 0.5 位(§3.1)。
- “每层误差最小,整个模型就最好。” 逐块蒸馏、QAT 的逐层误差更大,模型却更好(§4.3)。
- “逐 token 的 scale 能解决离群值。” 离群的是特征维,每个 token 都有(§5.3)。
- “LLM.int8() 只把很少的计算放在 16 位。” 取决于阈值和模型的激活尺度;在我们的小模型上阈值 6 留了 17% 的维(§5.4)。
- “QAT 和 PTQ 只是时间早晚的区别。” QAT 需要训练数据、整个模型的反向传播和优化器状态,对大模型几乎不可行(§4.3)。
- “4 位量化在任何模型上都会明显掉点。” 取决于模型和任务;我们的小模型 4 位逐通道无损,大模型通常也只损失很少(第 20 讲)。
8. 练习 · Exercises
- 一个 13B 的模型,BF16、INT8、INT4(组大小 128,scale 用 FP16)各占多少显存?
- 对 x = [0.5, −2.0, 1.0, 3.0] 做 absmax INT8 量化:scale、量化后的整数、反量化结果、最大误差各是多少?
- 对同一个 x 做 slides 的零点量化:scale、零点、量化后的整数。0 能精确表示吗?
- INT4 对称量化,scale = 0.1。舍入误差的均方大约是多少?如果改成 INT3、范围不变呢?
- 为什么位数越少,最佳的截断范围 c 越小?
- 一个 4096 × 4096 的权重矩阵,INT4 逐通道和逐组 64(scale 都是 FP16)各占多少字节?每个权重平均几位?
- 2 位对称量化(qmax = 1)只有 {−1, 0, 1} 三个值。非对称 2 位有几个值?为什么 §3.2 里非对称在 2 位时好那么多?
- 为什么 ffn.w2 对量化最敏感?如果把 w2 的输入先缩小 10 倍、再把 w2 的权重放大 10 倍,敏感程度会变吗(用 RTN 逐通道)?
- STE 是什么?没有它,QAT 能训练吗?
- AdaQuant 的目标和 RTN 的目标有什么不同?为什么 AdaQuant 的逐层误差更小,整个模型却不如逐块蒸馏?
- 一个激活矩阵 X(1000 个 token × 512 维)里第 7 维的值都在 50 左右,其余维在 ±1 以内。向量级 INT8 量化后,其余维的 scale 约是多少?大约有多少比例会被量化成 0?
- LLM.int8() 把 X 拆成两部分时,W 也要拆。写出拆分后的两个矩阵乘法各自的形状(X: n × d,W: d × m,离群维 k 个)。
参考答案
- 13B × 2 = 26 GB;13 GB;INT4:13B × 4.125 / 8 ≈ 6.7 GB。
- scale = 127 / 3 = 42.33;x·scale = [21.2, −84.7, 42.3, 127] → [21, −85, 42, 127];反量化 [0.496, −2.008, 0.992, 3.0];最大误差约 0.008。
- scale = 255 / 5 = 51;零点 = round(−51 × (−2) − 128) = −26;x·51 − 26 = [−0.5, −128, 25, 127] → [0(−0.5 四舍五入到偶数 0), −128, 25, 127](torch 的 round 是”平局取偶”,−0.5 → −0)。0 精确表示:0·51 − 26 = −26,反量化 (−26 + 26)/51 = 0。
- s²/12 = 0.01/12 ≈ 8.3 × 10⁻⁴。INT3 时 qmax 从 7 变成 3,范围不变则 scale 变成 0.1 × 7/3 ≈ 0.233,均方误差 ≈ 4.5 × 10⁻³(约 5.4 倍)。
- 舍入误差 ∝ scale² ∝ (c·max / qmax)²;位数少 → qmax 小 → 舍入误差大,截断少数大值换来的”所有值的 scale 变细”就更划算。
- 整数:4096² × 0.5 字节 = 8 MiB。逐通道 scale:4096 × 2 字节 = 8 KiB → 每权重 4.004 位;逐组 64:4096 × 64 × 2 = 512 KiB → 每权重 4.25 位。
- 非对称 2 位有 4 个值(0, 1, 2, 3 减去零点)。对称只用 3 个,并且两侧都按较大的一侧定 scale;非对称用满 4 个、范围贴着 [min, max]——每多一个值,2 位时的相对改善都很大。
- 它的输入幅度最大(±200),同样的权重误差乘上更大的输入,输出误差更大。缩放输入、反向缩放权重:逐通道 RTN 的 scale 由每行的最大值决定,权重整体放大 10 倍时 scale 也放大 10 倍,相对误差不变,输出误差也不变——敏感程度来自”输入里哪些维大”和”权重误差怎样与之配合”,单纯整体缩放改变不了。(要按列缩放才会改变,那是 SmoothQuant / AWQ 的思路,第 20 讲。)
- 前向用 round,反向把 round 当作恒等函数、梯度原样传过去。没有它,round 的梯度几乎处处为 0,量化前的权重收不到任何梯度,QAT 训练不了。
- RTN 最小化每个权重与网格点的距离;AdaQuant 最小化这一层的输出误差(考虑了输入)。AdaQuant 只看一层,忽略了块内各层误差的相互补偿;逐块蒸馏只要求整个块的输出对,给了各层配合的自由。
- 每行的最大值约 50,scale ≈ 50/127 ≈ 0.39;其余维 |x| < 0.2 的都会变成 0——如果它们大致均匀分布在 ±1,约 20%;如果像正态分布集中在 0 附近,比例更高。
- 离群部分:X[:, O](n × k)乘 W[O, :](k × m),FP16;其余:X[:, ¬O](n × (d − k))乘 W[¬O, :]((d − k) × m),INT8。两个结果都是 n × m,相加。
9. 延伸阅读 · Further reading
- Nagel et al. (2021) A White Paper on Neural Network Quantization:量化的系统性入门(slides 第 5 页引用)。
- Jacob et al. (2018) Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference:零点量化与整数推理。
- Bengio et al. (2013):直通估计;Esser et al. (2020) LSQ:可学习的步长。
- Hubara et al. (2021) Improving Post Training Neural Quantization: Layer-wise Calibration and Integer Programming(AdaQuant)。
- Li et al. (2021) BRECQ:按块重建。
- Yao et al. (2022) ZeroQuant;Dettmers et al. (2022) LLM.int8()。
- Micikevicius et al. (2022) FP8 Formats for Deep Learning:E4M3 与 E5M2。
10. 下一讲预告 · Next lecture
这一讲看到:低位数时 RTN 不行,AdaQuant / 逐块蒸馏 / QAT 能救回来,但都要做梯度下降,规模大了就慢。第 20 讲的 GPTQ 换一个思路:量化一个权重时,解析地算出其他还没量化的权重该怎样调整来补偿它的误差——补偿的方向由输入的 Hessian H = 2XXᵀ 决定。它来自 1993 年的”最优脑外科”(OBS),经过 OBQ 的改造,再加上三个工程上的改进(固定顺序、惰性批量更新、Cholesky 分解),能在几个 GPU 小时内把 175B 的模型量化到 3–4 位。我们会从零实现它,在同一个模型上和 RTN、AdaQuant 比较,再看看 SmoothQuant、AWQ 怎样处理激活的离群值,以及量化之后的推理内核长什么样。