09. 推理与部署
模型训练完之后的事:怎么把 token 一个个吐出来(解码策略),以及怎么让它吐得又快又省(推理系统优化)。
架构层面的 KV Cache、MHA/MQA/GQA 见 06. LLM 基础,这里讲它们在服务侧怎么被用起来。
1. Prefill 与 Decode:两个完全不同的阶段
自回归推理天然分成两段,它们的性格截然相反:
- Prefill(预填充):把整个 prompt 一次性喂进去,并行算出所有位置的 K/V,最后产出第一个 token。几百上千个 token 同时参与矩阵乘法,GPU 算力被打满,属于计算密集(compute-bound)。
- Decode(解码):之后每一步只输入一个新 token,算它的 \(Q_t\),与缓存里的全部 K/V 做注意力,吐出下一个 token。计算量只有一个 token,却要把整份模型权重和整份 KV Cache从显存搬进计算核心,属于访存密集(memory-bound),算力大量闲置。
| 阶段 | 一次处理的 token 数 | 瓶颈 | 对应指标 |
|---|---|---|---|
| Prefill | 整个 prompt | 算力 FLOPs | TTFT(首 token 延迟) |
| Decode | 1 | 显存带宽 | TPOT(每 token 延迟)、吞吐 |
这个分裂是后面几乎所有优化的根源:
- decode 要搬的数据太多 → 想办法把数据变小:GQA/MQA、KV 量化、权重量化。
- decode 的算力在闲置 → 想办法把闲置算力用起来:continuous batching、speculative decoding。
- 两个阶段性格差太远 → 大规模服务干脆把它们拆到不同机器上跑(PD 分离)。
顺带澄清一个常见混淆:延迟和吞吐不是一回事,而且经常互换。批开得越大,吞吐越高,但排队和批内等待会让单个请求的延迟变差。选型时要先问清楚优化的是 TTFT、TPOT 还是总吞吐。
2. 解码策略
模型每一步输出的是词表上的一个概率分布,解码策略决定怎么从这个分布里挑出一个 token。
Greedy Search
\[ y_t=\arg\max_y p(y\mid y_{<t},x) \]
每步都挑当前概率最大的那个。快、确定、可复现;缺点是每一步只顾眼前,容易陷进重复循环,开放生成时也偏平庸。
一个容易忽略的点:贪心不等于最优。整句概率最高的序列,未必每一步都取最大值——第一步让一点,后面可能赚回来更多。这正是 beam search 存在的理由。
适合抽取式任务、分类式输出、部分代码补全。
Temperature
在 softmax 之前先把 logits 除以一个温度 \(T\):
\[ p_i=\frac{\exp(z_i/T)}{\sum_j\exp(z_j/T)} \]
- \(T<1\):分数差被放大,分布更尖,模型更保守(\(T\to0\) 就退化成 greedy)。
- \(T=1\):原始分布。
- \(T>1\):分数被压平,长尾 token 更容易被采到,更有创意,也更容易胡说。
注意它调的是分布形状,不是「随机不随机」这个开关——\(T=0.7\) 依然是随机采样,只是采到冷门 token 的机会小了。
Top-K Sampling
只保留概率最高的 \(K\) 个 token,重新归一化后采样。
- 优点:直接砍掉长尾里的低质量 token。
- 缺点:\(K\) 是固定的,不适应分布的尖锐程度。某一步前 5 个 token 已覆盖 99% 概率(此时 \(K=50\) 会放进 45 个垃圾),另一步前 5 个只覆盖 40%(此时 \(K=5\) 又砍掉了合理选项)。
Top-P / Nucleus Sampling
选取累计概率刚好达到 \(p\) 的最小 token 集合,再归一化采样。
- 优点:候选集大小动态变化——分布尖的时候只留几个,分布平的时候自动放宽,正好补上 Top-K 的短板。
- 缺点:仍有随机性,也不解决事实错误。
实践中 top-p(0.9 左右)配 temperature 是最常见的组合,两者叠加使用。
Beam Search
每一步保留累计 log probability 最高的 \(B\) 条候选序列,最后从中选一条。相比 greedy 更接近「整句概率最高」的序列。
- 优点:翻译、语音识别这类目标相对确定的任务上常有效。
- 缺点:成本随 beam size 线性增长;开放生成里偏好安全、短、重复的文本(高概率 ≠ 高质量);需要配长度归一化,否则天然偏好短句。
- 现代 LLM 的对话/推理场景基本不用 beam,多样性和质量的性价比不如采样。
怎么选
| 场景 | 建议 |
|---|---|
| 数学、代码、抽取、评测 | \(T=0\)(greedy),要可复现 |
| 翻译、摘要 | beam,或低温采样 |
| 对话、创意写作 | \(T\approx0.7\sim1.0\) 配 top-p \(\approx0.9\) |
| 要多样候选再筛 | 高温采样 + Best-of-N / self-consistency |
其他常见参数
- repetition / frequency penalty:对已出现的 token 降权,抑制复读;调过头会破坏术语和格式的一致性(代码、专有名词尤其敏感)。
- constrained decoding:用 grammar、JSON Schema、正则或状态机在解码时屏蔽非法 token,保证输出结构合法。比「求模型输出 JSON」可靠得多。
- logit bias:直接对特定 token 加减分,用来强制或禁止某些词。
3. 用算力换正确率:Best-of-N 与 Self-Consistency
这两个都属于 test-time scaling:不改模型权重,只在推理时多花算力换正确率。
Best-of-N(Rejection Sampling)
采样 \(N\) 条候选回答,用一个打分器(reward model 或 verifier)挑出得分最高的一条。
- 需要一个靠谱的打分器,效果上限由它决定。
- \(N\) 越大收益越明显,但会开始钻打分器的漏洞——RM 分数继续涨而人工质量下降,就是过优化信号(见 08. 对齐与 RLHF)。
- 成本随 \(N\) 线性增长。
Self-Consistency(多数投票)
针对有唯一答案的推理题:用较高温度采样 \(N\) 条不同的思维链,只对最终答案做多数投票,中间过程不管。
它成立的直觉是:错法千奇百怪,对法殊途同归——错误答案会散落在各处,正确答案会反复出现。
- 不需要额外的 RM,这是它相对 Best-of-N 最大的优势。
- 只适用于答案能归一化比较的任务(数值、选项、可执行代码),开放式写作用不了。
- 温度不能太低,否则 \(N\) 条轨迹几乎一样,投票失去意义。
对比
| 方法 | 需要打分器 | 适用范围 | 主要风险 |
|---|---|---|---|
| Best-of-N | 需要 RM/verifier | 开放生成、对齐 | reward hacking |
| Self-Consistency | 不需要 | 答案唯一的推理题 | 答案不可比就没法用 |
这两者和 RL 训练是一条线的两端:推理时多花算力,还是训练时把能力压进权重。实践中它们常被串起来用——先用 Best-of-N / self-consistency 造出高质量轨迹,再蒸馏回模型(见 07. 训练与系统),把「推理时的贵」变成「权重里的便宜」。
4. KV Cache 的显存账与优化
先算账
\[ \text{KV Cache}=2\times n_{\text{layers}}\times H_{KV}\times d_{\text{head}}\times L\times \text{bytes} \]
逐项读:开头的 2 是 K 和 V 各一份;每层都要存;\(H_{KV}\) 是 KV 头数(GQA 就是在这一项上省钱);\(L\) 是当前序列长度;bytes 是精度(fp16 = 2)。最后还要乘以并发请求数。
代入 Llama-3-8B(32 层、8 个 KV 头、\(d_{\text{head}}=128\)、fp16):
\[ 2\times32\times8\times128\times2\ \text{B}=128\ \text{KB / token} \]
8k 上下文就是 1 GB——而这只是一条请求。并发 32 条就要 32 GB,比模型权重本身(约 16 GB)还大。这就是为什么长上下文和高并发一起上就 OOM。
于是优化沿三条路走:
① 让每个 token 的 KV 更小 —— MQA/GQA/MLA,直接砍 \(H_{KV}\)(见 06. LLM 基础)。
② 降精度 —— 把 KV Cache 量化到 int8/fp8,显存和带宽同时减半。KV 对量化比权重更敏感一些,通常保留 head 维度上的分组 scale。
③ 别浪费已分配的空间 —— PagedAttention。
PagedAttention
问题出在内存碎片:传统实现要为每条请求预留一整块连续显存,而请求最终生成多长事先不知道,只能按最大长度(比如 4096)预留。实际只生成了 200 个 token,剩下的全浪费。实测有效利用率经常只有 20–40%。
做法直接借了操作系统虚拟内存的思路:把 KV Cache 切成固定大小的 block(比如 16 个 token 一块),用一张 block table 把逻辑位置映射到物理块。物理上不必连续,用多少分多少。
额外的好处比省碎片更值钱:同一个 prompt 的多条采样(Best-of-N、beam、多轮对话共享的系统提示)可以共享前缀的物理块,写时复制。这也是 prefix caching 的基础——相同前缀只 prefill 一次。
5. Continuous Batching
静态批处理的毛病:一批请求要等最长的那条生成完才整批返回,先结束的槽位一直空转。而 LLM 请求的输出长度差异极大——10 个 token 的「好的」和 2000 个 token 的长文混在一批里,前者要陪跑到底。
Continuous batching(也叫 in-flight batching):以迭代为调度粒度,任何一条请求生成完就立刻退出,把槽位让给排队中的新请求,不必等整批对齐。
它为什么这么有效,回到第 1 节的结论:decode 阶段是访存密集、算力闲置的。多塞几条序列进来,权重只需要搬一次却服务了更多请求,几乎是白赚的吞吐,通常有数倍提升。
代价和注意事项:
- 批越大,KV Cache 压力越大。吞吐的天花板最终由显存决定,要和 PagedAttention 一起看。
- prefill 和 decode 混在同一批里会互相拖累:一条长 prompt 的 prefill 会卡住其他请求的 decode,表现为 TTFT 和 TPOT 抖动。缓解办法是 chunked prefill(把长 prefill 切块,插进 decode 的空隙)或PD 分离(prefill 和 decode 跑在不同实例上)。
6. Speculative Decoding
思路:decode 阶段算力大量闲置,那就让一个小模型先猜,大模型批量验。
流程三步:
- 草稿:用一个便宜的 draft 模型(或 n-gram、模型自身的浅层)一口气猜出 \(k\) 个 token。
- 验证:把这 \(k\) 个 token 拼成序列送进大模型,一次前向同时算出这 \(k+1\) 个位置的分布。注意这是 prefill 式的并行计算,耗时和只算 1 个 token 几乎一样——因为瓶颈本来就在搬权重,不在算。
- 接受/回退:从头逐个比对,接受与大模型分布一致的前缀;第一个不一致的位置用大模型的分布重采一个,后面的草稿全丢掉。
最关键的一点:经过拒绝采样的修正,输出分布与直接用大模型采样完全一致。它是无损加速,不是近似。
- 收益取决于接受率:draft 和目标模型越像、内容越「好猜」(代码、格式化文本、有大量复制的改写任务),加速越明显,通常 2–3×。
- 代价是要多维护一个 draft 模型,且 batch 很大时算力已经打满,收益会缩水——它花的正是那部分闲置算力。
- 常见变体:Medusa(给模型加几个头,一次预测多个后续位置,省掉独立 draft 模型)、EAGLE(在特征层而非 token 层做草稿,接受率更高)、prompt lookup / n-gram(直接从上下文里抄一段当草稿,摘要、改写、RAG 场景特别有效,零额外模型)。
7. Quantization
把权重(有时还有激活和 KV Cache)从 fp16 降到 int8/int4。目的有两个:省显存,以及——因为 decode 是访存密集的——要搬的字节少了就是快。
常见路线
- PTQ(Post-Training Quantization):拿训练好的模型直接量化,只需少量校准数据,几分钟到几小时搞定。GPTQ、AWQ 属于这一类,是部署主流。
- QAT(Quantization-Aware Training):训练时就模拟量化误差,质量更好,但要重训,成本高,通常只在小模型或极低比特时才值得。
按量化对象还分:W4A16 / W8A16(只量化权重,激活保持高精度)最常见,直接治 decode 的带宽瓶颈;W8A8 / FP8(权重激活都量化)在支持的新硬件上还能同时吃到算力红利,对 prefill 也有效。
为什么压到 4 bit 还能用?
关键在离群值(outlier):LLM 的激活里,少数几个维度的数值远大于其他维度。如果按整体最大最小值统一量化,绝大多数正常值会被挤进极少数几个格子里,信息全丢。
所以现代方法都在处理这件事:
- 分组量化:每 64/128 个权重共享一组 scale,而不是整层一个。
- 离群通道保高精度:LLM.int8() 把少数异常通道单独用 fp16 算。
- 按重要性调缩放:AWQ 观察到「重要权重」由激活分布决定,量化前先按激活尺度缩放,保护那部分权重。
代价
- 质量下降先暴露在长尾上:平均 benchmark 可能看不出差别,但长上下文、多步数学推理、罕见语言会先掉。评估不能只看一个平均分。
- int4 以下(2–3 bit)目前一般需要额外训练或特殊结构才可用。
- 量化格式必须有配套的高效 kernel,否则显存省下来了、速度反而更慢——量化只有在 kernel 支持时才是加速。
8. 一张表串起来
每个优化手段治的都是第 1 节里那两个瓶颈之一:
| 手段 | 治的是什么 |
|---|---|
| GQA / MQA / MLA | decode 的带宽(KV 搬太多) |
| KV 量化、PagedAttention | 显存容量与碎片 |
| 权重量化(W4A16) | 权重搬运带宽 + 显存 |
| Continuous batching | decode 的算力闲置 |
| Speculative decoding | decode 的算力闲置 |
| Chunked prefill / PD 分离 | prefill 与 decode 互相干扰 |
| FlashAttention | prefill 的 \(O(n^2)\) 显存与访存 |
| Best-of-N / self-consistency | 反过来——主动多花算力换正确率 |
目录
| 章节 |
|---|
| 00. 机器学习核心概念 |
| 01. 基础与神经网络机制 |
| 02. 模型评估与指标 |
| 04. 经典机器学习 |
| 05. NLP、RNN 与词向量 |
| 06. LLM 基础 |
| 07. 训练与系统 |
| 08. 对齐与 RLHF |
| 09. 推理与部署 |
| 12. ML Coding |
| 参考资料 |