从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 40 分钟 页内实验 ×4
C13 推理:生成为什么慢、怎么变快 第 4 课

把权重从 16 位压到 4 位,为什么能变快,又会丢什么?

量化:少读几位,能快多少、丢多少
第 1 课说,batch 1 的解码时间约等于「要读的字节 ÷ 带宽」。锚点权重从 BF16 压到每 128 个一组的 INT4,要读的从 51.25 GB 降到 13.21 GB,上界从每秒 65 个 token 升到 254 个。代价是舍入误差:我们的模型整个矩阵共用一个缩放、直接四舍五入到 3 位,验证 loss 涨 0.268;每 32 个一组只涨 0.039;GPTQ 把每一列的误差摊给还没量化的列,4 位时只涨 0.0083。激活里的离群值另有专门的办法。
❓

本课一问:第 1 课说,生成慢是因为每个 token 都要把权重读一遍。那把每个权重从 16 位压到 8 位、4 位,读的字节少了,是不是就快了?快多少?代价是什么?

这一课先不碰:FP8、FP4、MX 这些浮点格式的位是怎么排的(C9 第 4 课与 岔路);训练时就带着量化(量化感知训练,C9 岔路);把大模型的能力「教」给小模型的蒸馏(C12)。这一课只讲推理时把训好的权重压小。

从已有出发

从已有出发 · 一次请求在服务端走过的路 点任一格看锚点 Qwen3.8-27B 上的真实量
decode 循环:每个 token 走一圈
旁路:

贯穿例句的那一步 decode(写出「小王」),锚点要读 51.25 GB 权重,H100 上至少 15.30 ms。第 1 课的屋顶线告诉我们:batch 1 时这一步的时间几乎只由字节决定。于是一个直接的想法:每个权重少存几位。先把字节账算清楚:

算法模拟器 · 格式 × 锚点字节 → 解码时间上界(H100 屋顶线,浏览器现算)
字节账:权重每个多少位,解码就快多少

🎯

试三件事:① B = 1 时看「相对 BF16」一列:INT8 约 1.98 倍、INT4 约 3.76 倍——都比 16 ÷ 位数略少,因为 GDN 状态与 KV 的读写不跟着权重变;② 把 B 拖到 58,INT4 只剩约 1.8 倍:每个请求自己的 KV 与状态占了大头;③ 看最后两列:INT4 让整模型从 55.6 GB 降到 14.3 GB,同一张卡能放的 4K 请求从 58 个变成 156 个——量化同时改变了第 2 课的容量账。

这里的速度来自字节,不来自算力:只量化权重时,乘法照样按 16 位算(先把 INT4 还原回来再乘)。DeepSeek-V4 报告说得很直白:它把 MoE 专家权重做成 FP4,是为了「降低部署时的显存流量、实际提速」,而在现有硬件上 FP4 × FP8 的峰值算力和 FP8 × FP8 一样 。

先自己设计:整个矩阵一个缩放,直接四舍五入

把一个权重矩阵压成 b 位整数,最直接的办法:找出整个矩阵里绝对值最大的数 m,让它对上最大的整数 2b−1 − 1;每个权重除以这个缩放、四舍五入(round-to-nearest,RTN)。拿我们项目的模型真做一遍:4 个块里全部 28 个线性层、3,162,112 个权重,量化后在整个验证集上重算 loss(不量化时 4.6405):

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案整个矩阵共用一个缩放,直接四舍五入
位数整个矩阵一个缩放验证 loss比不量化涨了
INT8±1274.6401-0.0004
INT4±74.67200.0315
INT3±34.90850.2680

改进一:缩放按小组给

对称量化:一个权重怎么变成 b 位整数
= · ; 每权重实际位数 = b +
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
我们用的是对称量化(零点固定在 0)。GPTQ、AWQ 的常见实现多用非对称量化(每组再存一个零点),道理相同。

看一行真实权重(我们模型 blocks.0.attn.q_proj 里绝对值最大的元素所在的那一行,256 个数)被压成几位、按多大的组缩放时,会变成什么样:

真模型权重 · 一行 256 个真实权重的四舍五入量化(浏览器现算)
一行真实权重:四舍五入到几位、按多大的组缩放

🎯

试三件事:① INT4、整行一个缩放:数一数被舍入成 0 的红点;② 换成每 32 个一组:只有最大那个数所在的组还粗,其余组的格子细了,相对误差下降;③ 切到 INT2:只剩 −1、0、1 三个等级,大半权重变成 0——这就是位数再往下砍会碰到的墙。

「按组给缩放」放在锚点的真实形状里:FFN 的 Wup 按输入维每 128 个一组,每个输出列每组一个缩放:

按组量化:锚点 W_up 的整数权重与缩放(config 真实维度;可切小规模)
Ŵ = Wq ⊙ expand(s)
参数(可学习)输出中间结果
边长按对数比例缩放,块边上的数字是真实值;同一维度符号的边在全图等长。
维度含义 真实规模 小规模
d 输入维 hidden_size 5120 8
f 输出维 intermediate_size 17,408 6
g 组数 = 5,120 ÷ 128 40 2
W_q 占 44.6 MB(4 位),缩放占 1.39 MB(16 位);合起来每权重 4.125 位。C9 第 4 课的 FP8 也按块缩放,但权重块是 128 × 128(1 × 128 是激活的切法)。

改进二:GPTQ——让还没量化的权重替已经量化的背锅

按组缩放让格子变细,但每个权重还是各自舍入、各自承担误差。GPTQ(2022)换了个角度:我们在乎的不是 ŵ 和 w 差多少,而是输出 X·ŵ 和 X·w 差多少。于是按列一个个量化,每量化一列,就用校准数据算出的 H = 2XᵀX,把这一列的误差按比例摊到还没量化的列上,让它们提前「改一点」来抵消 。

我们照论文的算法 1 实现了一个最直接的版本(不重排列、全部列一次处理,校准数据取训练集 16 个窗口,评估在不重叠的验证集上):

位数与缩放直接四舍五入GPTQ
INT4,每个输出行一个缩放+0.0133+0.0083
INT4,每 128 个一组+0.0109+0.0084
INT3,每个输出行一个缩放+0.0746+0.0413
INT3,每 64 个一组+0.0519+0.0298

同样的位数、同样的缩放方式,GPTQ 都更小。但要按实验课的规则读:INT4 时两者只差约 0.005,小于噪声底线(C14:同配置 5 次重跑极差 0.016,3σ ≈ 0.020),在我们这个规模上不能下结论;INT3 时差 0.02–0.03,才超出了它。规模一大,差距会大到决定成败:GPTQ 论文里 OPT-175B 在 WikiText2 上,FP16 困惑度 8.34;直接四舍五入到 4 位是 10.54,GPTQ 是 8.37;到 3 位,直接四舍五入是 7.3 × 10³(彻底坏掉),GPTQ 是 8.68(表 3) 。

🔍 GPTQ 的一步到底改了什么?

对第 j 列:先把它四舍五入,得到误差 e = wj − qj(每个输出行一个数);再用 H 的逆矩阵(先做 Cholesky 分解,数值上更稳)的第 j 行,把 −e ÷ [H⁻¹]jj 乘上 [H⁻¹]j, j+1… 加到后面各列。直觉是:如果第 j 个输入通道和第 k 个经常一起变大,把第 j 列的误差让第 k 列「预先补一点」,输出就几乎不受影响。我们的实现在 gen_c13_precomputed.py 的 gptq_quant(约 20 行)。AWQ 走的是另一条路:按激活的幅度挑出约 1% 的显著通道,把它们的权重放大、缩放缩小,舍入误差对它们的相对影响就小了(MLSys 2024 最佳论文)。

激活也要量化吗?离群值的问题

上面只压了权重:乘法时先把它还原成 16 位。如果激活也压成 INT8(W8A8),乘法本身就能用 INT8 算,大 batch、算力受限时更快。可激活有一个权重没有的麻烦:离群值。LLM.int8() 报告,模型到约 6.7B 规模时,几乎所有层里都会出现少数几个特征维,幅度可达其他维的 20 倍;每个 2,048-token 序列约有 15 万个这样的值,集中在 6 个维度里,把它们置零困惑度会变差 600%–1000% 。整张量一个缩放时,这几个大数会把其余所有值挤进同一两个格子里。

我们 3.7M 的模型有没有?逐通道看激活的最大绝对值,最不均匀的一层是 blocks.1.ffn.w2(SwiGLU 的 W2 的输入):最大 ÷ 中位数 = 6.4——远没到 LLM.int8() 说的程度。所以在我们的模型上,W8A8 用整张量一个缩放,验证 loss 只涨 0.0036;每个 token 一个缩放 +0.0003。这个规模看不到离群值的问题,下面的办法只能演示机制,不能演示收益。

SmoothQuant(2022)的办法:既然难处在激活、权重很「好压」,就把一部分难度挪过去。每个输入通道 j 选一个 sj,激活除以它、对应那一列权重乘以它:

SmoothQuant:X·W 不变,难度从激活挪给权重
= · ,
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
实现上 diag(s)⁻¹ 可以并进前一层的 RMSNorm 权重,推理时零额外开销。

等式严格成立 ;能不能让 W8A8 在大模型上不掉点,是实验结论:论文在 OPT-175B 等模型上做到 W8A8,FasterTransformer 上 1.56 倍加速、显存减半 。用我们模型那一层的真实统计拖一拖 α:

真模型统计 · SmoothQuant 的迁移强度(真实激活与权重的逐通道幅度,浏览器现算)
SmoothQuant:把激活的难度挪给权重

KV 也能压:它同样是每步要读的字节。FP8 的 KV 让第 1 课字节条里橙色那段减半;DeepSeek-V4.1 更进一步,把主 KV 存成 FP4(每条缓存条目 288 字节,FP8 版 528 字节;R3 §1.5 e 由官方缓存格式复算;只有 4 层持有 KV、其中 3 层每 2 个 token 合 1 条,连索引 key 摊下来每 token 共 890 字节,C15 第 3 课)——C3 第 10 课、C11 讲过它怎么先把 KV 压短压少,这里是再压位数。

最后把我们模型上的全部结果放在一起(绿条 = 涨幅在 C14 的噪声底线以内):

真模型实测 · 22 种量化方案的真实验证 loss(我们的 3.7M 模型,离线真算)
我们的模型:量化之后验证 loss 变了多少

⚠ 常见误解

⚠

「量化只省显存,不提速。」batch 1 的解码受带宽限制,要读的字节减半,就接近快一倍:锚点 INT8 的上界约 131 token/s,BF16 约 65。

⚠

「压到 4 位,就快 4 倍。」只有权重那部分字节变少:KV 与状态照读,缩放还要多占 0.125–0.5 位,反量化也有开销;batch 大了,每个请求自己的字节越来越多,只量化权重的收益越来越小(上面 B = 58 时约 1.8 倍)。

⚠

「4 位量化,每个权重就占 4 位。」每 g 个权重还有一个 16 位缩放:每 128 个一组是 4.125 位;MXFP4 每 32 个一个 8 位缩放是 4.25 位;NVFP4 每 16 个一个是 4.5 位(C9 岔路)。

⚠

「验证 loss 几乎没变,就是没有损失。」loss 是平均意义上的,长推理、代码、少见的语言可能掉得多得多。量化必须配任务评测——而评测数字本身该信多少,是 C14 的问题。

⚠

「激活离群值每个模型都有,所以一定要 SmoothQuant。」我们 3.7M 的模型最不均匀的一层也只有 6.4 倍,W8A8 用最粗的缩放也只涨 0.0036;LLM.int8() 说它在约 6.7B 以上才系统出现。先测自己的模型,再决定。

在演进地图上的位置

推理加速的演进:每一代都被上一代的某个真数字逼出来(点名字去那一课;完整版在章首页)
  1. 每一步把整段重新喂进模型
  2. KV cache
  3. 批处理:一次解码多个请求
  4. 连续批处理(迭代级调度)
  5. PagedAttention:KV 分块按需分配
  6. 投机解码:草稿猜、目标一次验
  7. 让模型自己当草稿:MTP 与专用草稿器
  8. 量化:每个权重少读几位
  9. 按 TTFT / TPOT / goodput 调度:分块 prefill、P/D 分离
  10. 采样:温度 → top-k → top-p → min-p
  11. 少存 KV:GQA / MLA / 线性注意力
第六代量化:每个权重少读几位LLM.int8() / GPTQ / SmoothQuant 2022 → AWQ 2023 → FP4 QAT 2026你在这里
解决了INT4(每 128 个一组)只读 13.21 GB → 上界 254 token/s;我们的模型 GPTQ INT4 验证 loss 只涨 0.0083(整张量直接四舍五入涨 0.0315)。
卡在每一招都说「更快」:batch 从 1 开到 58,总吞吐 65 → 2,304 token/s,每个用户却从每 token 15.5 ms 慢到 25.2 ms;一个 32K 的 prompt 整段 prefill 要 1.91 s(第 5 课,推演)。
数字口径:锚点 Qwen3.8-27B 的字节与时间是 H100 SXM 上的屋顶线上界(不是实测),由 anchor-model.json 现算;调度、投机、量化、采样的数字来自我们项目的 3 个小模型(章内 precomputed.json,脚本 gen_c13_precomputed.py,2026-09-26);vLLM、DeepSeek-V3 的数字出自本地 PDF,Orca、DistServe 出自论文原文,均为提出方自报。

证据三问

问本课的回答
① 它解决什么问题?解码受带宽限制:每个权重少读几位,每个 token 就快;整模型变小,同一张卡还能多放请求
② 数学上为什么?字节与时间上界是记账 ;SmoothQuant 的变换严格等价 ;GPTQ 的误差补偿来自二阶近似
③ 实验上呢?损失多少只能实测:我们的模型 INT8 各方案的变化都在 ±0.0004 以内、INT4 GPTQ +0.0083;OPT-175B 上 GPTQ 3 位 8.68 对直接四舍五入 7.3 × 10³

一句话

📌

解码快慢看每个 token 要读多少字节;量化把权重从 16 位压到 8 位、4 位,读得少就快,代价是舍入误差——更细的缩放和误差补偿把它压小,激活里的离群值另有专门的办法。

🚦

先停在这里:记住「快来自字节,代价看分组与补偿」,就可以去第 5 课。
再深一层:展开 GPTQ 的 Dig,再读 Maarten Grootendorst 的量化图解与 GPTQ 论文算法 1。

下一个缺口

四课下来,每一招都说自己「更快」:连续批处理让整机吞吐翻几倍,投机解码让单个用户每步多拿几个 token,量化让每一步更短。可它们说的「快」是同一个东西吗?batch 开大了,整机更快、每个人却更慢;来一个很长的 prompt,别人都得等。服务快不快,到底该看哪几个数? → 第 5 课:服务快不快,该看哪几个数?

你可能会问

这一课还没有收到真实提问;你的提问会记到 05-问答记录/C13-L4.md。