第 14 章 推理优化
第 14 章 推理优化
学习目标
- 理解推理与训练的计算/内存差异(memory-bound 的本质);
- 掌握 KV Cache 管理:PagedAttention、前缀缓存、分块预填充;
- 理解连续批处理如何打破静态批处理的天花板;
- 掌握投机解码、并行采样等生成加速;
- 掌握主流推理引擎(vLLM/TGI/TensorRT-LLM/ONNX Runtime/TensorRT)的定位;
- 建立延迟、吞吐、成本三角的权衡心智。
14.1 推理 vs 训练:为什么「部署时」和「训练时」是两套优化
推理(Inference)与训练的根本差异不是模型不同,而是瓶颈不同:
| 维度 | 训练 | 推理 |
|---|---|---|
| 计算目标 | 算梯度更新参数 | 逐 token 生成 |
| 瓶颈 | 算力(compute-bound) | 带宽(memory-bound) |
| 数据形状 | 大 batch,形状固定 | 小 batch,变长 |
| 关键资源 | FLOPs、优化器显存 | 权重带宽、KV Cache 显存 |
| 优化方向 | 计算高效 | 少读内存、多复用 |
memory-bound 的量化理解(第 4 章的带宽账):Decode 阶段每生成一个 token,GPU 要把整个权重从 HBM 读一遍,但只算很少的 FLOPs。以 7B 模型为例:读 14GB 权重 × 2 字节 ≈ 14GB 数据,而计算只有约 GFLOPs——计算:数据比极低,带宽才是天花板。这解释了三个现象:
- 量化(第 13 章)为什么直接提速:权重从 2B/参数降到 1B/0.5B,读的数据变少;
- batch 越大,memory-bound 越被摊薄:同一个权重被多个请求共享读,计算占比上升——大 batch 是推理吞吐的命根子;
- KV Cache 为什么是显存第一瓶颈:它随并发与上下文线性增长(第 4 章)。
%%{init: {"themeVariables": {"primaryTextColor": "#000000", "textColor": "#000000", "labelColor": "#000000", "nodeTextColor": "#000000", "labelTextColor": "#000000", "scaleLabelColor": "#000000"}}}%%
flowchart LR
A[Prefill 阶段
算输入所有 token
compute-bound] --> B[生成首个 token]
B --> C[Decode 阶段
逐 token 生成
memory-bound]
C --> D[每步读全量权重
+ 复用 KV Cache]推理两阶段本质不同:Prefill(一次性处理输入,compute-bound,延迟敏感)与 Decode(逐 token,memory-bound,吞吐敏感)。所有推理引擎的优化都是「Prefill 更省算、Decode 更省带宽」。
14.2 KV Cache 的管理艺术
14.2.1 PagedAttention:分页显存
KV Cache 的问题(第 4 章):碎片化。传统做法为每个请求预分配 max_seq_len 的连续显存——长上下文时预留浪费(内部碎片),并发时大量 KV 占满显存(外部碎片)。PagedAttention(vLLM 的核心创新)借鉴操作系统虚拟内存的分页思想:KV Cache 按固定大小块(block)分配,物理上不连续,逻辑连续:
- 内部碎片大减:只分配实际用到的块;
- 共享 KV:多请求共享相同前缀(如系统提示),块级引用计数实现前缀复用;
- 吞吐提升 2-4 倍(vLLM 论文数据):同样的显存能塞下更多并发。
14.2.2 前缀缓存(Prefix Caching)
多轮对话、RAG 请求通常共享长前缀(系统提示 + 历史对话 + 检索内容)。前缀缓存把「算过的 KV 直接复用」而非重算:命中前缀的请求跳过 Prefill,首 token 延迟大降。工程注意:前缀要有精确哈希匹配(任何 token 差异都失效),且要与权限隔离(缓存不能跨租户泄密)。
14.2.3 分块预填充(Chunked Prefill)
长输入的 Prefill 会独占 GPU 算力,把 Decode 挤到阻塞。Chunked Prefill 把长 Prefill 切成小块,穿插在 Decode 之间调度,平滑延迟尖刺、提升整体吞吐。这是 2024 年后推理引擎的标配(vLLM、TGI、TensorRT-LLM 都有)。
14.3 连续批处理(Continuous Batching)
传统批处理的痛点:一个 batch 要等最慢的请求生成完才释放(生成速度由最长的输出决定,短的被拖死)。连续批处理让「生成完的请求立即退出、新请求立即加入」——batch 动态进出:
%%{init: {"themeVariables": {"primaryTextColor": "#000000", "textColor": "#000000", "labelColor": "#000000", "nodeTextColor": "#000000", "labelTextColor": "#000000", "scaleLabelColor": "#000000"}}}%%
flowchart LR
A[请求1 生成完
退出] --> B[新请求5 加入]
B --> C[batch 动态重排]
C --> D[GPU 始终满载]效果:吞吐提升 10-20 倍(相对静态批处理,Orca 论文数据)。它把「推理吞吐」从「最慢请求」的束缚中解放出来——这是 LLM Serving 和传统模型 Serving 最本质的差别之一。
14.4 投机解码(Speculative Decoding)与并行采样
Decode 是 memory-bound 的串行过程,投机解码的洞察:用一个小草稿模型快速猜多个 token,大模型一次验证。
%%{init: {"themeVariables": {"primaryTextColor": "#000000", "textColor": "#000000", "labelColor": "#000000", "nodeTextColor": "#000000", "labelTextColor": "#000000", "scaleLabelColor": "#000000"}}}%%
flowchart LR
A[草稿模型
小模型快猜 K 个] --> B[大模型一次验证
并行打分 K 个]
B --> C[接受一致前缀
拒绝处回退]
C -->|接受率高| D[单 token 延迟大降]- 数学保证:采样分布与直接大模型完全一致(拒绝采样校正),不损质量;
- 收益:草稿模型与目标分布接近时,延迟降 2-3 倍;
- 变体:草稿可以是同一模型的浅层(self-speculative)、n-gram 草稿、或 EAGLE 类自回归草稿。
并行采样(Parallel Sampling / Best-of-N):同一 prompt 采多个答案,用打分器挑最好的——Agent 与代码生成场景的推理时扩展(第 26 章会用)。代价是 N 倍计算,与 KV 前缀共享(14.2)天然互补。
14.5 算子融合、图优化与编译
推理侧的「算子优化」与训练同源(第 8 章 FlashAttention 已在用),额外要点:
- 算子融合:attention + 归一化 + MLP 的 kernel 融合,省 HBM 往返(FlashAttention、FlashDecoding 是注意力算子的事实标准);
- 图优化:CUDA Graph 消除 kernel 启动开销(小 batch 下收益显著);
- 编译:TensorRT/TensorRT-LLM 做离线引擎构建(AOT),ONNX Runtime 的图优化;PyTorch 系用
torch.compile; - FlashDecoding:长序列 Decode 时把 KV 分块并行扫描,提升长上下文生成吞吐。
14.6 推理引擎全景:vLLM、TGI、TensorRT-LLM 等
| 引擎 | 特点 | 定位 |
|---|---|---|
| vLLM | PagedAttention + 连续批处理,Python 生态 | 开源社区事实标准,通用 LLM 服务 |
| TGI(HuggingFace) | 连续批处理 + 量化支持 | HF 生态一体,部署简单 |
| TensorRT-LLM(NVIDIA) | 编译优化 + 量化 + 多卡并行 | 性能天花板,N 卡生产 |
| ONNX Runtime / TensorRT | 通用图优化引擎 | 跨框架、小模型、传统模型 |
| OpenVINO | Intel 系优化 | Intel 硬件、端侧 |
| llama.cpp | 纯 CPU/端侧 + GGUF | 端侧与桌面 |
选型逻辑:通用服务默认 vLLM;追求极致性能、深度量化、多机并行用 TensorRT-LLM;端侧用 llama.cpp;传统模型服务用 Triton/ONNX(第 15 章)。引擎的「生态红利」不可小觑——vLLM 的社区插件、监控、多模态支持最全。
14.7 延迟、吞吐、成本三角
推理优化的所有决策都落在这个三角:
%%{init: {"themeVariables": {"primaryTextColor": "#000000", "textColor": "#000000", "labelColor": "#000000", "nodeTextColor": "#000000", "labelTextColor": "#000000", "scaleLabelColor": "#000000"}}}%%
flowchart LR
A[延迟 Latency
首 token/每 token] --- B[吞吐 Throughput
token/s 请求/s]
B --- C[成本 Cost
GPU 数/每 token 单价]
C --- A- 延迟:首 token(Prefill)+ 每 token(Decode)两条线分别度量(第 21 章 SLO 用);
- 吞吐:tokens/s(引擎视角)、requests/s(服务视角)、以及「有效吞吐」(排除补 token 的浪费);
- 成本:单卡能跑多大模型、多少并发 → 每百万 token 单价。
三角的经典矛盾:低延迟(小 batch)↔ 高吞吐(大 batch)互斥;高吞吐 ↔ 低延迟都以成本为代价。工程答案是分级服务:低延迟 SLA 用小模型/专用资源,高吞吐批处理用大 batch 共享资源——第 17 章 LLM 服务专题会系统展开。
本章要点回顾
- 推理是 memory-bound:Decode 每步读全量权重,batch 越大摊薄越狠——大 batch 是吞吐命根子。
- Prefill(compute-bound)与 Decode(memory-bound)两阶段要分别优化。
- PagedAttention 用分页消灭 KV 碎片,前缀缓存复用共享 KV,分块预填充平滑长输入尖刺。
- 连续批处理让请求动态进出,吞吐比静态批提升一个数量级。
- 投机解码用小模型猜、大模型验,延迟降 2-3 倍且分布不变;并行采样用 N 倍计算换质量。
- 引擎选型:通用 vLLM、极致 TensorRT-LLM、端侧 llama.cpp。
- 延迟-吞吐-成本三角无免费午餐,分级服务是现实答案。
习题
- 7B 模型 Decode 为什么是 memory-bound?用「每 token 读 14GB 权重 vs 14 GFLOPs 计算」算带宽需求,对比 H100 的 3.35TB/s 带宽。
- PagedAttention 为什么能提升吞吐 2-4 倍?从内部碎片、外部碎片、前缀共享三个角度解释。
- 投机解码在什么情况下收益最大?什么情况下反而变慢(草稿接受率低时)?
- 你的服务要求 p95 首 token < 500ms,但吞吐不达标。给出 3 个可调杠杆并排序(提示:分块预填充、前缀缓存、小模型)。
- 对比 vLLM 与 TensorRT-LLM:什么场景选谁?给出决策树。
延伸阅读
- Kwon et al., Efficient Memory Management for Large Language Model Serving with PagedAttention(vLLM), 2023
- Yu et al., Orca: A Distributed Serving System for Transformer-Based Generative Models, 2022(连续批处理)
- Leviathan et al., Fast Inference from Transformers via Speculative Decoding, 2023
- Dao et al., FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness, 2022
- Dao et al., FlashDecoding++: Faster Large Language Model Inference on GPUs, 2023
- vLLM / TGI / TensorRT-LLM 官方文档
下一章预告
第 5 篇进入「对外提供服务」:第 15 章模型服务基础(Serving 是什么、同步/异步/流式、REST/gRPC/WebSocket、在线/离线/近线),第 16 章服务架构(Triton/KServe/Ray Serve、网关、扩缩容、灰度),第 17 章 LLM 服务专题(多租户、Token 成本、RAG/Agent 服务)。