GOCLAWLLM ENGINEERING
GoClaw 首页

9. 推理:从生成到性能优化

工程进阶3~5 小时
学习目标
  1. 区分 Prefill 与 Decode
  2. 证明 KV Cache 的正确性与内存代价
  3. 设计量化、采样和吞吐基准
前置知识
  • 自回归生成
  • 注意力张量形状

本章产物缓存误差、延迟曲线、容量估算和完整基准环境记录。

9.1 Prefill 与 Decode

输入 2,000 token,生成 100 token:

Prefill 更容易利用矩阵乘法的并行度;Decode 每一步都要读取大量权重和历史 KV,常常受内存带宽限制。

必须区分:

TTFT:首 token 延迟
TPOT:后续每个输出 token 的平均时间
PP:prompt processing token/s
TG:token generation token/s
吞吐:整个服务每秒处理的请求或 token

9.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。它解释了:

以 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 / temperature

Top-k:只保留最高 k 个。 Top-p:保留累计概率达到 p 的最小集合。 Repetition/Presence penalty:降低已出现 token 的偏好。

性能比较常使用 deterministic 设置保证可重复;质量使用模型卡推荐的采样配置。不能一边改变采样,一边把输出差异全部归因于模型权重。

9.5 量化

权重理论大小:

bytes ≈ parameter_count × bits_per_parameter / 8

实际还包含 scale、zero point、分组元数据和对齐。

量化收益来自减少内存容量和带宽,但反量化内核也有成本。更低 bit 不必然更快,最终要在目标硬件上测量质量和性能。

9.6 Continuous Batching 与 Paged KV

传统静态 batch 要等一组请求一起完成,短请求会被长请求拖住。Continuous Batching 在解码过程中动态加入和移除请求,提高设备利用率。

Paged KV Cache 把缓存按块管理,减少为最大长度预留整段连续空间产生的浪费,并支持更灵活的请求调度。vLLM 的核心价值之一就是高吞吐服务调度,而不仅是“另一个 generate API”。

9.7 Flash Attention

标准实现若把完整注意力矩阵反复写入和读出高带宽内存,成本很大。Flash Attention 通过分块计算,在片上更快存储中完成更多步骤,减少中间数据搬运。

关键认识:

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,按两阶段执行。

第一阶段只验证正确性:

  1. 对同一输入分别走“每步重算全部历史”和“追加 K/V”两条路径。
  2. 使用 deterministic 设置,比较每一步最后位置的 logits。
  3. 报告 max_abs_error,使用明确的 atol/rtol,不要比较随机采样文本。
  4. 分别测试 batch 1 与 batch 2,防止缓存只在单请求时正确。

第二阶段才测性能:

固定条件变量记录
模型、dtype、设备、生成长度prompt 32/128/512 tokenprefill、decode、总耗时
模型、prompt、生成长度cache on/off每 token 延迟、峰值内存
层数、head dim、序列长度KV Head 数理论 KV bytes

报告中的性能表必须注明:是否包含模型加载和 warmup、计时是否同步设备、输入与输出 token 数、batch、精度和硬件。否则两个 token/s 数字不可比较。

常见故障:

验收标准:先证明缓存没有改变模型语义,再讨论它在指定硬件和长度下是否更快。


本章依据

原理性结论以原始论文、官方文档或公开教材为依据。论文中的实验结果只适用于其声明的模型、数据、硬件和评估设置。

  1. 自回归生成中的 KV Cache 更新、形状和位置处理。

  2. 减少 KV Head 对解码效率和模型质量的影响。

  3. 基于 IO 感知和分块计算的精确注意力算法。

  4. Paged KV Cache、内存碎片和 vLLM 服务调度。

  5. 保持目标分布的投机解码与并行验证。