第 19 讲 · 模型量化(一):低精度数字与基础量化方法 · Model Quantization I: Low-precision Numbers & Basic Methods

Large Language Model Systems · Lecture 19。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

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

本篇目录

对应 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

学完这一讲,你应该能够:

  1. 说出量化省了什么(显存、带宽、计算)、损失了什么;比较 INT8 / INT4 与 FP8 / FP4 的网格,知道什么数据适合哪种。
  2. 写出 absmax(对称)与零点(非对称)量化,解释误差的三个来源,以及截断范围的取舍。
  3. 解释量化粒度(逐张量、逐通道、逐组)的作用与代价,算出”每个权重实际几位”。
  4. 说明 LLM 的离群特征为什么让 INT8 激活量化失效,LLM.int8() 的混合精度分解怎样解决。
  5. 区分 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 部分:

模型FP32BF16INT8 / FP8INT4
LLaMA-3 8B32 GB16 GB8 GB4 GB
Llama-70B280 GB140 GB70 GB35 GB
DeepSeek-V32684 GB1342 GB671 GB336 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 / stdINT8FP8 E4M3INT4FP4 E2M1
均匀3.50.40%2.56%7.15%11.05%
正态7.61.04%2.64%18.90%12.84%
拉普拉斯11.61.87%2.62%32.65%20.12%
学生 t(ν = 3)47.38.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
INT83.03.03.03.03.0
INT47.93.02.93.23.1
INT322.76.36.65.64.0
INT23465154968419

粒度越细,一个大值只祸害它所在的那一小组。代价是 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.q65536+0.0
attn.v32768+0.2
ffn.w1196608+0.6
ffn.w3196608+0.1
ffn.w2196608+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 页:对每个线性层,找量化后的 Ŵ 使输出误差最小:

W^=arg⁡min⁡W^∥WX−W^X∥22\hat{W} = \arg\min_{\hat{W}} \| W X - \hat{W} X \|_2^2

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 页):一次训练一个块(学生),让它的输出等于全精度这一块(教师)的输出,两者用同样的输入:

LLKD,k=MSE(Lk⋅Lk−1⋯L1(X), L^k⋅Lk−1⋯L1(X))\mathcal{L}_{LKD,k} = \text{MSE}\big(L_k \cdot L_{k-1} \cdots L_1(X),\ \hat{L}_k \cdot L_{k-1} \cdots L_1(X)\big)

一次只需要一个块的显存。ZeroQuant 还融合了量化相关的内核,验证到 20B(GPT-NeoX),1.3B 的模型约 3 小时,已集成进 DeepSpeed(slides 第 22 页)。

4.3 比较

05 号脚本在同一个模型上比较四种方法(逐通道、对称):

位数RTNAdaQuant逐块蒸馏QAT
INT36.33.43.12.9
INT2153.819.83.94.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向量级 INT8LLM.int8()
1(不植入)3.13.03.0
303.13.03.0
1004.53.33.0
300836720553.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 算,两部分相加:

XW=∑h∈OX:,hWh,: (FP16)+∑h∉OX:,hWh,: (INT8)XW = \sum_{h \in O} X_{:,h} W_{h,:} \ (\text{FP16}) + \sum_{h \notin O} X_{:,h} W_{h,:} \ (\text{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

  1. 量化用低位数存权重 / 激活:省显存、省带宽(解码近乎线性加速)、可以用更快的低精度乘法;代价是精度。Llama-70B:140 GB → INT4 35 GB。
  2. 整数网格均匀,浮点网格对数均匀:8 位时 INT8 多数情况更好,4 位时 FP4 多数情况更好;长尾数据对两者都难。
  3. absmax(对称)与零点(非对称)量化;误差来自舍入(≈ s²/12)、截断、累积。位数越少,越值得截断(最佳范围 INT8 0.8、INT4 0.4 × max)。
  4. 粒度:逐张量 → 逐通道 → 逐组,越细越准,代价是 scale 的存储(4 位、组 128 = 4.125 位)。非对称在极低位数时帮助很大。层的敏感程度取决于输入的大小(ffn.w2 最敏感)。
  5. QAT 准但贵;PTQ 里 RTN 最简单,AdaQuant(逐层重建)、ZeroQuant(逐块蒸馏)用少量校准数据大幅挽回精度。逐层误差最小 ≠ 整个模型最好。
  6. LLM.int8():大模型的激活有系统性的离群特征,逐张量 / 逐 token 的 INT8 都会失效;把离群的特征维用 16 位单独算,其余 INT8。

7. 常见误区 · Common pitfalls

  1. “FP8 一定比 INT8 精确。” 8 位时,正态、拉普拉斯分布的数据 INT8 误差更小(§1.2)。
  2. “量化误差就是权重误差。” 要紧的是输出误差 ‖WX − ŴX‖,它取决于输入(§3.3、§4.2)。
  3. “截断一定有害。” 低位数时适度截断是最优的(§2.3)。
  4. “零点量化会让 0 变成非零数。” 零点取整后 0 精确表示(§2.1)。
  5. “逐组量化是免费的。” 每组一个 16 位 scale,组 32 时每个权重多 0.5 位(§3.1)。
  6. “每层误差最小,整个模型就最好。” 逐块蒸馏、QAT 的逐层误差更大,模型却更好(§4.3)。
  7. “逐 token 的 scale 能解决离群值。” 离群的是特征维,每个 token 都有(§5.3)。
  8. “LLM.int8() 只把很少的计算放在 16 位。” 取决于阈值和模型的激活尺度;在我们的小模型上阈值 6 留了 17% 的维(§5.4)。
  9. “QAT 和 PTQ 只是时间早晚的区别。” QAT 需要训练数据、整个模型的反向传播和优化器状态,对大模型几乎不可行(§4.3)。
  10. “4 位量化在任何模型上都会明显掉点。” 取决于模型和任务;我们的小模型 4 位逐通道无损,大模型通常也只损失很少(第 20 讲)。

8. 练习 · Exercises

  1. 一个 13B 的模型,BF16、INT8、INT4(组大小 128,scale 用 FP16)各占多少显存?
  2. 对 x = [0.5, −2.0, 1.0, 3.0] 做 absmax INT8 量化:scale、量化后的整数、反量化结果、最大误差各是多少?
  3. 对同一个 x 做 slides 的零点量化:scale、零点、量化后的整数。0 能精确表示吗?
  4. INT4 对称量化,scale = 0.1。舍入误差的均方大约是多少?如果改成 INT3、范围不变呢?
  5. 为什么位数越少,最佳的截断范围 c 越小?
  6. 一个 4096 × 4096 的权重矩阵,INT4 逐通道和逐组 64(scale 都是 FP16)各占多少字节?每个权重平均几位?
  7. 2 位对称量化(qmax = 1)只有 {−1, 0, 1} 三个值。非对称 2 位有几个值?为什么 §3.2 里非对称在 2 位时好那么多?
  8. 为什么 ffn.w2 对量化最敏感?如果把 w2 的输入先缩小 10 倍、再把 w2 的权重放大 10 倍,敏感程度会变吗(用 RTN 逐通道)?
  9. STE 是什么?没有它,QAT 能训练吗?
  10. AdaQuant 的目标和 RTN 的目标有什么不同?为什么 AdaQuant 的逐层误差更小,整个模型却不如逐块蒸馏?
  11. 一个激活矩阵 X(1000 个 token × 512 维)里第 7 维的值都在 50 左右,其余维在 ±1 以内。向量级 INT8 量化后,其余维的 scale 约是多少?大约有多少比例会被量化成 0?
  12. LLM.int8() 把 X 拆成两部分时,W 也要拆。写出拆分后的两个矩阵乘法各自的形状(X: n × d,W: d × m,离群维 k 个)。

参考答案

  1. 13B × 2 = 26 GB;13 GB;INT4:13B × 4.125 / 8 ≈ 6.7 GB。
  2. 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。
  3. 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。
  4. s²/12 = 0.01/12 ≈ 8.3 × 10⁻⁴。INT3 时 qmax 从 7 变成 3,范围不变则 scale 变成 0.1 × 7/3 ≈ 0.233,均方误差 ≈ 4.5 × 10⁻³(约 5.4 倍)。
  5. 舍入误差 ∝ scale² ∝ (c·max / qmax)²;位数少 → qmax 小 → 舍入误差大,截断少数大值换来的”所有值的 scale 变细”就更划算。
  6. 整数:4096² × 0.5 字节 = 8 MiB。逐通道 scale:4096 × 2 字节 = 8 KiB → 每权重 4.004 位;逐组 64:4096 × 64 × 2 = 512 KiB → 每权重 4.25 位。
  7. 非对称 2 位有 4 个值(0, 1, 2, 3 减去零点)。对称只用 3 个,并且两侧都按较大的一侧定 scale;非对称用满 4 个、范围贴着 [min, max]——每多一个值,2 位时的相对改善都很大。
  8. 它的输入幅度最大(±200),同样的权重误差乘上更大的输入,输出误差更大。缩放输入、反向缩放权重:逐通道 RTN 的 scale 由每行的最大值决定,权重整体放大 10 倍时 scale 也放大 10 倍,相对误差不变,输出误差也不变——敏感程度来自”输入里哪些维大”和”权重误差怎样与之配合”,单纯整体缩放改变不了。(要按列缩放才会改变,那是 SmoothQuant / AWQ 的思路,第 20 讲。)
  9. 前向用 round,反向把 round 当作恒等函数、梯度原样传过去。没有它,round 的梯度几乎处处为 0,量化前的权重收不到任何梯度,QAT 训练不了。
  10. RTN 最小化每个权重与网格点的距离;AdaQuant 最小化这一层的输出误差(考虑了输入)。AdaQuant 只看一层,忽略了块内各层误差的相互补偿;逐块蒸馏只要求整个块的输出对,给了各层配合的自由。
  11. 每行的最大值约 50,scale ≈ 50/127 ≈ 0.39;其余维 |x| < 0.2 的都会变成 0——如果它们大致均匀分布在 ±1,约 20%;如果像正态分布集中在 0 附近,比例更高。
  12. 离群部分: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 怎样处理激活的离群值,以及量化之后的推理内核长什么样。

← 返回课程目录