推理:生成为什么慢、怎么变快
主线定位
点任一已学环节,看这一步的输入 → 输出形状、参数量,以及它解决了什么问题(数字取自 Qwen/Qwen3.8-27B 真实 config)。
前十二章已经把上面这条流水线从头到尾搭好、训好。这一章不再往里加新环节,而是问:同一条流水线,拿来一个字一个字地生成时,时间都花在哪?
贯穿例句这一章当成一次请求:提示「小明把苹果给了」用锚点 Qwen3.8 的真 tokenizer 切成 4 个 token(小明 · 把 · 苹果 · 给了),要生成的下一个 token 是「小王」。在服务端,它会走完下面这一圈;每一格点开,都是锚点上的真实量:
C3 第 8 课已经讲过 KV cache 为什么能省计算、第 9、10 课讲了 GQA 与 MLA 怎么让每个 token 少存一点,C9 讲过一个参数几字节,C11 揭晓了锚点另外 48 层 GDN 用的固定大小状态、以及 MTP 作为训练目标。本章不重复这些,只引用它们的结论。
本章问题链
每一课只回答一个问题;上一课的失败,逼出下一课的问题。
| 课 | 这一课的问题 | 朴素方案在哪个真数字上失败 | 一句话 |
|---|---|---|---|
| 第 1 课 | 同一个模型,读 prompt 很快、逐字生成很慢,差在哪? | 按算力估每秒近 2 万个 token;可每个 token 都要读一遍 51.25 GB 权重,H100 上最多约 65 个,算力只用了 0.34% | 生成慢不是因为算得多,而是每个 token 都要把整个模型从显存读一遍 |
| 岔路 | 概率分布怎么变成一个字?温度、top-k、top-p、min-p 各截掉了什么? | 贪心:我们的模型 47% 的 4-gram 在重复;高温 top-p 0.9 保留 1,108 个 token | 温度改形状,截断决定尾巴里多少 token 还有机会;min-p 跟着最高概率自适应 |
| 第 2 课 | 成百上千个请求一起解码,KV cache 在显存里怎么放、怎么排? | 静态批处理槽位利用率 58.7%;按最大长度预留,锚点一张卡只放得下 1 个请求 | 连续批处理让请求随到随走,PagedAttention 把 KV 切成小块按需发放 |
| 第 3 课 | 一次前向验好几个 token,凭什么输出分布一点都不变? | 「相同才收」只收 0.73%;「拒绝后从 p 重采」把分布拉偏 0.166 | 按 min(1, p/q) 接受、拒绝时从残差重采,输出分布与目标模型逐字相同 |
| 第 4 课 | 把权重从 16 位压到 4 位,为什么能变快,又会丢什么? | 整个矩阵一个缩放直接四舍五入到 3 位:验证 loss +0.268 | 解码快慢看每个 token 读多少字节;更细的缩放和误差补偿把舍入的代价压小 |
| 第 5 课 | 服务快不快,该看哪几个数? | 只看吞吐:batch 开到 58,总吞吐 × 35,每个用户两个 token 之间却从 15.5 ms 拖到 25.2 ms | 快有好几种,互相拉扯;看分位数、按目标算 goodput,token/s 先对齐 tokenizer |
| 实验课 | 在我们自己的模型上把推理的账都对一遍 | — | 每个结论都带上条件:哪个模型、哪台机器、测了几次、和谁比 |
演进地图
- 解决了和训练完全一样,结果一定对。卡在贯穿例句的 4 个 token 提示再生成 1,000 个:K/V 要算 503,500 次,其中绝大多数是上一步刚算过的(C3 第 8 课)。
- 解决了旧 token 的 K、V 存下来,同样 1,000 个 token 只算 1,003 次;锚点每个 token 存 64 KiB。卡在每生成 1 个 token 仍要把 51.25 GB 权重读一遍:H100 上最多 65 token/s,算力只用了 0.34%(第 1 课,屋顶线上界)。
- 解决了权重读一遍,B 个请求各出一个 token:算术强度约随 B 增长。卡在静态批处理要等一批里最长的那个:验证集 64 个真实段落当回复,B = 8 时槽位利用率只有 58.7%(第 2 课)。
- 解决了每一步结束就让完成的离开、排队的进来:同一批长度 93.1%;Orca 在 175B 上同延迟吞吐为 FasterTransformer 的 36.9 倍(自报)。卡在请求随到随走,KV 却还按最大长度连续预留:vLLM 测得现有系统只有 20.4%–38.2% 的 KV 显存存着真 token。
- 解决了16 token 一块、块表映射:KV 显存利用率 96.3%,同等延迟吞吐 2–4 倍(vLLM 自报);锚点 4K 上下文时一张 H100 能放 58 个请求。卡在单个用户仍是每个 token 一次完整前向:batch 1 时 99% 以上的算力闲着。
- 解决了按 min(1, p/q) 接受、残差重采,输出分布不变;我们的模型 α = 0.567,k = 4 时每次目标前向产出 1.96 个 token(真跑)。卡在草稿要单独训练、单独部署,还要和目标对得上:接受率太低时反而更慢(第 3 课)。
- 解决了DeepSeek-V3 的 MTP 模块当草稿,第二个 token 接受率 85%–90%,1.8 倍 TPS(自报);锚点 config 自带 1 层 MTP。卡在权重本身还是每个 2 字节,字节没少——batch 1 的解码时间仍由 51.2 GB 决定。
- 解决了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 课,推演)。
- 解决了同时看首 token 时间、每 token 间隔与满足 SLO 的请求率;DistServe 同 SLO 下多服务 7.4 倍请求(自报)。卡在快的账算清了;量化、投机之后「好」还剩多少?分数能信吗?(C14)
- 解决了top-p 按累计概率自适应、min-p 跟着最高概率自适应:我们的模型在 T = 1.3 时 top-p 0.9 保留 664 个 token,min-p 0.1 只留 34 个。卡在哪种截断「更好」只能靠生成质量的实验判断,没有证明(岔路课)。
- 解决了锚点 16 层 GQA 每 token 64 KiB(同样的层若用 MHA 是 384 KiB);另外 48 层 GDN 只存固定大小的状态。卡在固定状态每步要整块读写:锚点每个请求 151 MB(FP32),短上下文时比 KV 还大(第 1 课)。
三层交互
| 层 | 本章里的组件 | 在哪一课 |
|---|---|---|
| 概念动画 | 推理循环累计图;批处理泳道的逐步播放;一次真实投机采样的逐轮回放 | 全章、第 2 课、第 3 课 |
| 算法模拟器(真算法、小数据) | 屋顶线定位(prefill 与 decode 落点、逐项字节账、换卡换模型);静态 / 连续批处理调度;连续预分配 vs 分页的显存池与块表;并行采样的共享块与写时复制;投机采样的分布叠放与 1 万 / 10 万次蒙特卡洛;温度与 top-k / top-p / min-p 截断;一行真实权重的四舍五入量化;SmoothQuant 的缩放;延迟–吞吐前沿与分块 prefill 时间线(推演) | 第 1–5 课、岔路 |
| 真模型 / 真数据 | 我们项目的 3 个模型(3.7M 目标 + 0.33M / 1.07M 草稿)离线真跑的投机采样记账与分布一致性检验、22 种量化方案的真实验证 loss、4 个提示下 2,048 维的真实 logits;项目实测的有 / 无 KV cache 速度、温度 × top-p 的熵与重复率;锚点与对照模型的官方 config;vLLM、Leviathan、DeepSeek-V3、Kimi K3、DeepSeek-V4 报告原文里的数字 | 第 1、3、4 课、岔路、实验课 |
项目推进:让我们的模型跑得又快又对
对应 04-实践项目/MILESTONES.md 的「C13 推理(KV cache、采样)」。分工照旧:代码 AI 来写,决策你来做。minillm/model.py 已经有 KVCache 与 generate()(temperature / top_k / top_p、use_cache),测试断言增量推理与整段前向的 logits 最大差 1.8 × 10−7、带与不带 cache 的贪心生成逐 token 相同;experiments/exp_c13_inference.py 导出本章实验课读取的 c13-inference.json。本章另有一份章内预计算(c13-inference/gen_c13_precomputed.py),用同一批 checkpoint 离线跑了投机采样与量化。
你要拍板的四件事
- 生成时开不开 KV cache?用「生成到 256 个 token 时有 / 无 cache 的速度比」回答,并解释为什么这个比值在我们的小模型上远小于第 1 课的推算(实验课)。
- 默认采样设置选哪一组?引用「温度 0 / 0.7 / 1.0 / 1.3 × top-p 0.9 / 1.0」的重复率表和保留个数;要不要换成 min-p(岔路课、实验课)?
- 要不要配投机采样?草稿用 d = 64 还是 d = 128 的模型、每轮猜几个?依据是真跑的「每次目标前向产出」与按参数量估的草稿代价(第 3 课)。
- 权重量化到几位、用什么方法?INT8 还是 INT4,直接四舍五入还是 GPTQ(第 4 课)。
验收标准(全部可测):
- 有 / 无 KV cache 在 32、64、128、240 个新 token 下的速度表,
same_output全为 true(项目实测已有,实验课回放)。 - 温度 × top-p 的重复率表齐全,选定的设置要引用表里的数(贪心时 4-gram 重复率 47%,实验课)。
- 贪心下,投机采样的输出与目标模型自己贪心解码逐 token 相同:章内预计算 16 组全部相同(第 3 课)。
- 量化方案的验证 loss 涨幅要小于噪声底线 0.020(C14:同配置 5 次重跑的 3σ,极差 0.016;ref_baseline 与 baseline 这一对差 0.0142)才算在训练随机性以内:INT8 各方案、INT4 GPTQ(+0.0083)与 INT4 每 128 个一组(+0.0109)达标,INT4 整个矩阵一个缩放(+0.0315)不达标(第 4 课)。
坑与带走
| 坑 | 症状 | 后果 | 怎么办 |
|---|---|---|---|
| 不用 KV cache | 每生成一个 token 都把整段重算一遍 | 生成越长越慢,计算随长度平方增长 | 必开;缓存与重算逐位相同(C3 第 8 课) |
| 以为生成慢是算力不够 | 换了算力更强的卡,batch 1 的解码几乎没快 | 买错硬件 | batch 1 看带宽:A100 → H100 算力 3.17 倍,解码只快 1.64 倍(第 1 课) |
| 以为 KV cache 免费 | 长上下文、并发一多就 OOM | 服务不了几个人 | 锚点每 token 64 KiB、每请求另有 151 MB(= 144 MiB)状态;算清容量再定 batch(第 1、2 课) |
| 每个请求按最大长度预留 KV | 显存「用满」了,实际存的 token 很少 | 同一张卡只放得下极少的请求 | 分页按需分配(第 2 课) |
| 手写推理循环、静态批处理 | 短请求写完了还占着槽位 | 吞吐低、平均延迟高 | 用连续批处理 + 分页的推理引擎(vLLM、SGLang,第 2 课) |
| 以为 PagedAttention 让注意力算得更快 | 单请求延迟没有改善 | 期望落空 | 它省的是显存,赢在能放更大的 batch;分页内核本身慢 20%–26%(第 2 课) |
| 以为投机解码有损 | 不敢在生产上开 | 白白放弃 1.5–3 倍 | 按 min(1, p/q) 接受、残差重采,输出分布与目标相同(第 3 课) |
| 草稿模型选得太大 | 接受率高了,速度反而没涨 | 比不投机还慢 | 看理论加速 E ÷ (k·c + 1),不只看接受率(第 3 课) |
| 量化后不测质量 | 悄悄掉点 | 线上效果变差 | 量化必须配评测;涨幅和重跑噪声比(第 4 课、C14) |
| 只看吞吐 | 平均很好看,用户却说卡 | 体验差 | 分开看 TTFT、TPOT 与吞吐,看 p99,按目标算 goodput(第 5 课) |
| 跨 tokenizer 比 token/s | 「我们家更快」的结论不成立 | 选型失误 | 先换算成字 / 秒(第 5 课) |
| 用 bbycroft 的 3D 可视化学 KV cache | 找不到缓存 | 概念空白 | 它每步重走整个流程;用 C3 第 8 课的步进器 |
这一章你应该带走的(各课「一句话」):
- 生成慢不是因为算得多,而是每个 token 都要把整个模型从显存读一遍;读 prompt 时一次读权重能算成百上千个 token,逐字生成时只算 1 个。
- 温度改分布的形状,截断决定尾巴里多少 token 还有机会;top-p 看累计概率,高温时会放进一长串,min-p 跟着最高概率自适应。
- 连续批处理让请求随到随走,PagedAttention 把 KV 切成小块按需发放;两者合起来,同一张卡能同时服务的请求多了几倍。
- 草稿猜、目标一次验完;按 min(1, p/q) 接受、拒绝时从残差重采,输出分布与目标模型逐字相同,省的只是目标模型的前向次数。
- 解码快慢看每个 token 要读多少字节;量化把权重从 16 位压到 8 位、4 位,读得少就快,代价是舍入误差——更细的缩放和误差补偿把它压小,激活里的离群值另有专门的办法。
- 快有好几种:首 token 要快、之后每个 token 要稳、整机吞吐要高;它们互相拉扯,要看分位数、按 SLO 算 goodput,token/s 还要先对齐 tokenizer。
- 每个结论都带上条件:哪个模型、哪台机器、测了几次、和谁比。
提问记录:这一章还没有真实提问。学习中有疑问,记到 05-问答记录/C13-L<课号>.md(岔路课是 C13-B1.md);有价值的问题会展开成新的岔路课。
概念索引(本章登场):prefill · decode · 算术强度 · 屋顶线 · 带宽受限 / 算力受限 · GEMM / GEMV · 静态批处理 · 连续批处理(迭代级调度)· PagedAttention · 块表 · 内部 / 外部碎片 · 写时复制 · 前缀缓存 · 投机解码 · 接受率 α · 残差分布 · MTP 自投机 · EAGLE-3 / DSpark · RTN · 按组缩放 · GPTQ · W8A8 · SmoothQuant · 离群特征 · TTFT · TPOT · goodput · 分块 prefill · P/D 分离 · 温度 · top-k · top-p · min-p。
深入这一章
每条都在 2026-09-26 实际打开核对过(arXiv API、官方页面、B 站 view API):写清它讲清了什么、对应哪一课、为什么推荐。外部链接需联网;论文有本地 PDF 的直接打开。
🎮 想更直观 10–30 分钟
🔬 想吃透原理 1–3 小时
📄 想读原文 按需
排除了什么(也是筛选标准):① 知乎四篇(PagedAttention 图解、投机采样推导、GPTQ 原理、2026 推理优化全景)——本次核实全部返回 403,无法确认内容,旧版课程收录的撤下;② Lilian Weng《Large Transformer Model Inference Optimization》——量化、剪枝、蒸馏写得好,但不涉及投机解码与服务调度,不作这两部分的资料;③ Maarten 的量化指南不作 AWQ 的出处(全文没有 AWQ);④ 五道口纳什视频的配套代码仓库是私有付费的,不链;⑤ 旧版 M6 的「vLLM 减少 55% 显存、2.2 倍吞吐」当成 PagedAttention 一般收益的写法(口径错,第 2 课已改);⑥ 旧版投机解码组件用预置猜测 + 贪心匹配来撑「输出分布完全不变」(说法对、演示撑不起,已换成真模型的分布叠放与蒙特卡洛);⑦ 旧版温度组件的 5 个 logits 是编的(已换成我们模型的真实 logits);⑧ MoE 路由可视化(moe-viz)移到 C11;⑨ 各类 INT4 / INT8 SEO 聚合文(同质化转载,数字查不到一手来源)。