9. 推理:从生成到性能优化
- 区分 Prefill 与 Decode
- 证明 KV Cache 的正确性与内存代价
- 设计量化、采样和吞吐基准
- 自回归生成
- 注意力张量形状
本章产物缓存误差、延迟曲线、容量估算和完整基准环境记录。
9.1 Prefill 与 Decode
输入 2,000 token,生成 100 token:
- Prefill:并行处理输入,建立各层 KV Cache。
- Decode:每一步生成一个 token,串行重复 100 次。
Prefill 更容易利用矩阵乘法的并行度;Decode 每一步都要读取大量权重和历史 KV,常常受内存带宽限制。
必须区分:
TTFT:首 token 延迟
TPOT:后续每个输出 token 的平均时间
PP:prompt processing token/s
TG:token generation token/s
吞吐:整个服务每秒处理的请求或 token9.2 自回归生成
while 未结束:
logits = model(history)
next = sample(logits[-1])
history.append(next)训练时可以并行预测每个位置,因为正确历史来自训练数据;推理时未来 token 尚不存在,只能逐步生成。这是生成延迟的根本来源。
9.3 KV Cache
无缓存时,生成第 t 个 token 会重新计算全部历史位置的 Q、K、V。历史 token 的 K/V 在后续步骤中不会变化,因此可以按层保存;当前步骤只计算新 token 的投影,再把它追加到缓存。Query 只服务于当前生成位置,没有跨步骤复用价值。
Prefill 一次处理完整输入并建立初始缓存;Decode 每次读取历史缓存并追加一个位置。缓存改变计算路径,但不应改变生成语义。实现测试必须比较缓存开启前后的 logits,而不能只比较采样后的文本。
粗略内存:
KV bytes
≈ 2 × layers × kv_heads × head_dim
× sequence_length × bytes_per_element × batch前面的 2 表示 K 和 V。它解释了:
- 上下文扩大 4 倍,缓存大约扩大 4 倍。
- 并发扩大 4 倍,缓存总量也会显著扩大。
- 权重 4-bit 不代表 KV Cache 自动 4-bit。
- GQA/MQA 减少 KV 头,可直接减少缓存。
以 32 层、32 个 KV Head、Head Dimension 128、FP16、4096 token、batch 1 为例:
2 × 32 × 32 × 128 × 4096 × 2 bytes
≈ 2 GiB如果模型使用 8 个 KV Head 的 GQA,同一配置约为 512 MiB。该估算未计入分配器对齐、页表和运行时工作区,但足以用于容量规划。
生产系统还需要区分缓存分配策略:
| 策略 | 主要特征 | 适用场景 |
|---|---|---|
| Dynamic Cache | 随序列增长分配 | 交互式开发、长度不可预测 |
| Static Cache | 提前分配最大容量 | 固定上限、便于图编译 |
| Paged Cache | 按固定大小的块管理 | 高并发、连续批处理 |
| Sliding Window | 只保留最近窗口 | 模型本身支持局部注意力 |
| Quantized Cache | 低比特保存 K/V | 长上下文、显存受限 |
| Prefix Cache | 多请求复用公共前缀 | 固定系统提示、共享文档前缀 |
缓存优化的首要约束是正确性:位置编码、RoPE 偏移、因果遮罩、batch 重排和请求结束后的页回收都必须经过独立测试。
配套实验:实现并测量 KV Cache Notebook。实验包含缓存前后数值核对、延迟曲线、显存公式以及 MHA/GQA/MQA 对比。
9.4 Sampling
Greedy:
next = argmax(logits)Temperature:
scaled_logits = logits / temperatureTop-k:只保留最高 k 个。 Top-p:保留累计概率达到 p 的最小集合。 Repetition/Presence penalty:降低已出现 token 的偏好。
性能比较常使用 deterministic 设置保证可重复;质量使用模型卡推荐的采样配置。不能一边改变采样,一边把输出差异全部归因于模型权重。
9.5 量化
权重理论大小:
bytes ≈ parameter_count × bits_per_parameter / 8实际还包含 scale、zero point、分组元数据和对齐。
- Weight-only quantization:权重低比特,激活较高精度。
- W8A8:权重和激活 8-bit。
- GPTQ/AWQ:常见的训练后权重量化路线。
- GGUF Q4_K_M 等:llama.cpp 生态常用格式。
量化收益来自减少内存容量和带宽,但反量化内核也有成本。更低 bit 不必然更快,最终要在目标硬件上测量质量和性能。
9.6 Continuous Batching 与 Paged KV
传统静态 batch 要等一组请求一起完成,短请求会被长请求拖住。Continuous Batching 在解码过程中动态加入和移除请求,提高设备利用率。
Paged KV Cache 把缓存按块管理,减少为最大长度预留整段连续空间产生的浪费,并支持更灵活的请求调度。vLLM 的核心价值之一就是高吞吐服务调度,而不仅是“另一个 generate API”。
9.7 Flash Attention
标准实现若把完整注意力矩阵反复写入和读出高带宽内存,成本很大。Flash Attention 通过分块计算,在片上更快存储中完成更多步骤,减少中间数据搬运。
关键认识:
- 数学上仍是精确 attention(允许正常的浮点舍入差异)。
- 收益随序列长度、硬件和内核变化。
- 它解决的是 IO/内存访问问题,不是简单减少参数。
9.8 投机解码
小型 draft model 先提出多个 token,大模型一次验证:
草稿命中率高
→ 减少大模型串行 forward 次数
→ 可能加速若草稿模型太慢或接受率太低,收益会消失。Leviathan 等人提出的接受—拒绝校正算法在其假设下保持目标模型的采样分布;并非所有工程化“草稿后验证”变体都自动具有这一性质,因此实现必须核对所用算法,而不能直接接受看起来合理的草稿。
9.9 Mac 上的推理路线
# MLX
python -m pip install mlx-lm
mlx_lm.generate \
--model mlx-community/Qwen3-8B-4bit \
--prompt "解释 Prefill 和 Decode。 /no_think" \
--max-tokens 160# llama.cpp
brew install llama.cpp
llama-cli \
-hf Qwen/Qwen3-8B-GGUF:Q4_K_M \
-ngl 99 \
-c 8192 \
-n 160 \
-p "解释 KV Cache。 /no_think"M4 Pro 推理实验资料提供了更细的基准测试步骤;本章重点解释这些指标在 LLM 全生命周期中的位置。
9.10 动手:KV Cache 正确性与性能分开验收
实验 05|KV Cache 资源:CPU;时间:约 2 分钟;产物:logits 最大误差、序列长度—延迟曲线和缓存容量估算。
打开KV Cache Notebook,按两阶段执行。
第一阶段只验证正确性:
- 对同一输入分别走“每步重算全部历史”和“追加 K/V”两条路径。
- 使用 deterministic 设置,比较每一步最后位置的 logits。
- 报告
max_abs_error,使用明确的atol/rtol,不要比较随机采样文本。 - 分别测试 batch 1 与 batch 2,防止缓存只在单请求时正确。
第二阶段才测性能:
| 固定条件 | 变量 | 记录 |
|---|---|---|
| 模型、dtype、设备、生成长度 | prompt 32/128/512 token | prefill、decode、总耗时 |
| 模型、prompt、生成长度 | cache on/off | 每 token 延迟、峰值内存 |
| 层数、head dim、序列长度 | KV Head 数 | 理论 KV bytes |
报告中的性能表必须注明:是否包含模型加载和 warmup、计时是否同步设备、输入与输出 token 数、batch、精度和硬件。否则两个 token/s 数字不可比较。
常见故障:
- 第一个 token 一致、后续不一致:检查 cache position 和 RoPE 偏移。
- batch 1 正确、batch 变动后错误:检查请求重排和 padding mask。
- cache 更慢:小序列中 Python 调度和拼接开销可能超过节省的计算。
- 估算内存与实测不同:检查分配器、预留容量、工作区和元素 dtype。
验收标准:先证明缓存没有改变模型语义,再讨论它在指定硬件和长度下是否更快。
本章依据
原理性结论以原始论文、官方文档或公开教材为依据。论文中的实验结果只适用于其声明的模型、数据、硬件和评估设置。
自回归生成中的 KV Cache 更新、形状和位置处理。
减少 KV Head 对解码效率和模型质量的影响。
基于 IO 感知和分块计算的精确注意力算法。
Paged KV Cache、内存碎片和 vLLM 服务调度。
保持目标分布的投机解码与并行验证。