第9章:多节点推理优化
vLLM / TensorRT-LLM / SGLang / Dynamo 选型决策树 + speculative decoding + PD 解耦 + KV cache + continuous batching + 长上下文 / 多模型同卡——把 Goodput 方法论扩展到推理侧
Ch8 把 Goodput 在训练侧讲透——这一章是模块零的最后一章,把同样的方法论扩展到推理侧。推理工程师每天面对的是另一组挑战:TTFT vs TPOT 的取舍、长 prompt prefill 爆炸、多请求 batching 的复杂调度、PD 解耦的取舍、speculative decoding 的工程现实。这一章给一份可直接照着选的推理决策树,并解释每条选择背后的工程逻辑。
📑 目录
- 1. 推理工作负载的关键指标
- 2. 推理引擎选型决策树
- 3. Continuous Batching 与 PagedAttention
- 4. KV-Cache 优化:Prefix / Cross-node / Quantization
- 5. PD 解耦推理
- 6. Speculative Decoding
- 7. 长上下文(>1M tokens)的特殊挑战
- 8. 多模型同卡服务
- 9. 推理 Goodput 路径总结
- 自我检验清单
- 参考资料
1. 推理工作负载的关键指标
1.1 三个核心指标
| 指标 | 含义 | 用户感受 |
|---|---|---|
| TTFT (Time To First Token) | 从请求发出到第一个 token 的延迟 | ”AI 在多久后开始回应我” |
| TPOT (Time Per Output Token) | 后续 token 的平均生成间隔 | ”回应得有多流畅” |
| Throughput | 系统每秒能产出多少 token | 服务能承多少并发 |
1.2 TTFT vs TPOT 的取舍
它们之间存在结构性冲突:
- 想 TPOT 低:batch 越大越好(GPU 利用率高)
- 想 TTFT 低:batch 越小越好(避免排队 + prefill 不被插入)
- 想 throughput 高:batch 大 + prefill / decode 并行
不同业务对这三个的权重不同:
| 业务 | 关键指标 | 容忍度 |
|---|---|---|
| 客服对话 | TTFT < 1s | TPOT 50-100ms 可接受 |
| Code Agent / Copilot | TTFT < 200ms | TPOT 30-60ms |
| 后台批处理 | Throughput 最大化 | TTFT / TPOT 不重要 |
| 长上下文 RAG | TTFT 与 prompt 长度强相关 | 接受 prefill 慢 |
1.3 推理 Goodput 的定义
把训练 Ch8 的 Goodput 概念翻译到推理侧:
不满足 SLA 的请求不计入分母——这点和”原始 throughput”完全不同。
2. 推理引擎选型决策树
2.1 主流引擎能力对照
| 引擎 | 强项 | 弱项 | 推荐场景 |
|---|---|---|---|
| vLLM | PagedAttention / 全开源 / 社区活跃 | 极致 latency 不如 TRT-LLM | 通用 LLM 服务 |
| TensorRT-LLM | 极致优化(NVIDIA 自己写)/ FP8 | 闭源 / 集成复杂 | 极致 latency / FP8 |
| SGLang | RadixAttention prefix cache / 快 | 较新,生态不如 vLLM | 工具调用密集 |
| NVIDIA Dynamo | PD 解耦 / 多模型混合 | 较新 | 大规模生产 |
| llama.cpp / MLC | CPU / mobile / 边缘 | 不适合大规模并发 | 端侧 |
2.2 选型决策树
推理 stack 选型
│
├─ 业务规模?
│ ├─ 小(< 1000 QPS)→ vLLM 默认即可
│ ├─ 中(1k-10k QPS)→ vLLM + 调优 / SGLang
│ └─ 大(10k+ QPS)→ Dynamo / TRT-LLM
│
├─ 硬件?
│ ├─ H100 + 想用 FP8 → TRT-LLM
│ ├─ A100 → vLLM 或 SGLang
│ ├─ 国产 GPU → 各厂商定制(华为 MindIE / 寒武纪 etc)
│ └─ 边缘 / 端侧 → llama.cpp / MLC / Apple MLX
│
├─ 关键 SLA?
│ ├─ TTFT 极致 → TRT-LLM(CUDA Graph + FP8)
│ ├─ throughput 极致 → vLLM (continuous batching)
│ ├─ 长上下文 → vLLM + chunked prefill
│ └─ 工具调用密集 → SGLang (prefix cache)
│
└─ 团队工程能力?
├─ 强 → 自定义 + 各家组合
├─ 中 → vLLM + 上层胶水
└─ 弱 → 直接调商业 API
2.3 选型的实战经验
经验 1:先用 vLLM 跑起来
99% 团队的第一选择应该是 vLLM——开源、文档好、性能基线已经很高、坑相对少。等到性能跑满 vLLM 极限再考虑其他。
经验 2:TRT-LLM 的优势在 H100
H100 + FP8 + CUDA Graph 这一套在 TRT-LLM 上才能拿全——A100 上 vLLM 已经接近 TRT-LLM。
经验 3:SGLang 在工具调用场景显著更快
Agent / RAG 这种多轮工具调用 + 大量 prefix 复用的场景,SGLang 的 RadixAttention 能拿到 vLLM 之上的 30-50% 加速。
3. Continuous Batching 与 PagedAttention
3.1 静态 batch vs continuous batch
静态 batch(早期实现):
- 等够 N 个请求 → 凑成一个 batch → 一起跑
- 问题:第 1 个请求要等到第 N 个到达
- 长短请求差距大时尾部请求被快请求拖慢
Continuous batching(vLLM 等现代引擎):
- 每个 step 动态加入新请求 / 移除完成的请求
- 没有”凑齐”的等待
- 长短请求互相不阻塞
3.2 PagedAttention:解决 KV-Cache 碎片
KV cache 的传统实现:每个请求预分配最大长度的 contiguous 显存——严重碎片化:
- 短请求只用了 100 token,剩下 31900 token 浪费
- 长请求时已经分配的小块凑不出 32K
vLLM 的 PagedAttention:
- 把 KV cache 切成固定大小的”页”(block,typically 16 token)
- 请求按需分配页(不连续也行)
- 类似 OS 的虚拟内存机制
效果:
- KV 内存利用率从 ~50% 提到 ~90%+
- 同样显存能服务更多并发请求
3.3 Chunked Prefill
长 prompt 的 prefill 是 ——一个 32K prompt 的 prefill 可能要 1-2 秒。如果在这期间 decode 阶段被阻塞,TPOT 会暴增。
Chunked prefill:
- 把 32K prompt 拆成 4-8 个 chunk
- 每个 chunk 的 prefill 和正在 decode 的请求交错进行
- TPOT 不再被 long prompt 卡死
vLLM 0.6+、SGLang 都已默认开启。
4. KV-Cache 优化:Prefix / Cross-node / Quantization
4.1 Prefix Cache:复用相同前缀
很多场景下不同请求共享相同 prefix:
- 同一 system prompt 的多个用户请求
- Agent 多轮对话的历史部分
- RAG 的同一文档检索
把 prefix 的 KV cache 缓存下来,新请求复用:
请求 1: [system_prompt] [user_msg_1] → KV: cache_sys + cache_msg1
请求 2: [system_prompt] [user_msg_2] → KV: cache_sys (reuse!) + new
vLLM Prefix Caching、SGLang RadixAttention 都做了这件事——典型场景能拿到 5-30% 的总 prefill 时间节省。
4.2 Cross-node KV-Cache 共享
PD 解耦推理(§5 详细)的核心:Prefill 节点和 Decode 节点不同——KV cache 必须跨节点传输。
- 普通 TCP:太慢(KV cache 几十 MB / token)
- RDMA / NVLink:µs 级
- 鲲鹏 UB:见模块十六 Ch4
代表实现:Mooncake、NIXL、LMCache。
4.3 KV-Cache Quantization
把 KV cache 从 FP16 / BF16 降到 INT8 / FP8:
- 显存节省:50%
- 精度损失:通常 <1% perplexity 上升
- 实现:vLLM 0.6+ 支持 fp8 KV cache
工程经验:长上下文场景特别值得开——KV cache 是长上下文显存压力的主要来源。
5. PD 解耦推理
5.1 为什么要解耦
LLM 推理两阶段差距巨大:
| 阶段 | 计算特征 | GPU 利用率 | 优化重点 |
|---|---|---|---|
| Prefill | 大 batch matmul | 高(compute bound) | TFLOPS |
| Decode | 每步 1 token,small batch | 低(memory bound) | HBM bandwidth |
把它们放在同一张卡上跑:
- 长 prompt 的 prefill 让卡进入 compute bound
- 这期间在跑的 decode 请求 TPOT 暴增
- 一个长请求拖累所有短请求
5.2 PD 解耦的设计
┌─────────────────┐
请求 → 调度器 → ─────────► │ Prefill Pool │
│ (大 batch / │
│ compute bound) │
└────────┬────────┘
│ KV cache 跨节点传输
▼
┌─────────────────┐
│ Decode Pool │
│ (持续 batch / │
│ memory bound) │
└────────┬────────┘
│
▼
用户响应
5.3 何时值得做 PD 解耦
需要做的信号:
- 长 prompt(>4K)占比高
- TPOT SLA 紧(< 30ms)
- 集群规模大(>16 GPU)
不需要做的信号:
- 大部分请求 < 1K prompt
- 集群小(< 8 GPU)—— 解耦本身的协调成本超过收益
5.4 实现选项
- NVIDIA Dynamo:原生支持 PD 解耦
- Mooncake:开源,专注 PD 解耦 + KV cache pool
- vLLM 自定义:vLLM 有 disagg-prefill 实验性支持
6. Speculative Decoding
6.1 原理
Speculative decoding 的核心想法:
- 用一个**小模型(draft model)**快速生成 K 个 token
- 用大模型一次 forward 验证这 K 个 token
- 接受能匹配的部分 → 实际 throughput 上升
关键工程理由:大模型的 forward 是 memory-bound 的——一次 forward 算 1 个 token 和算 K 个 token 的 wall time 差不多。所以”一次验证 K 个”几乎免费。
6.2 几种 draft 策略
| 策略 | draft 来源 | 接受率 | 实际加速 |
|---|---|---|---|
| Small model draft | 小一两个量级的模型 | 0.6-0.8 | 1.5-2.5x |
| Medusa | 同模型加 K 个 head | 0.5-0.7 | 1.3-2x |
| EAGLE | 学习一个轻量 next-K predictor | 0.7-0.85 | 2-3x |
| Lookahead | 缓存最近见过的 n-gram | 0.4-0.6 | 1.2-1.5x |
6.3 工程现实
- Speculative decoding 在 TPOT 紧 的场景才值得开——throughput 极致场景没收益
- 接受率高度依赖 draft 模型质量——一个差 draft 反而拖慢
- 对工具调用 / 结构化输出特别有用(next K token 高度可预测)
6.4 在 vLLM / TRT-LLM 上启用
# vLLM 0.6+
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-3-70B",
speculative_model="meta-llama/Llama-3-8B",
num_speculative_tokens=5,
)
TRT-LLM 通过 --speculative_decoding_mode flag。
7. 长上下文(>1M tokens)的特殊挑战
7.1 三个工程瓶颈
瓶颈 1:Prefill 时间
1M token prefill ≈ ops 量级——单 H100 即使 FP8 也要几分钟。
对策:
- Sequence Parallelism + Ring Attention(多卡分担)
- 工程上把长上下文请求路由到 prefill 集群
瓶颈 2:KV cache 显存
1M token × 2 (K+V) × 8 (heads) × 128 (head dim) × 2 byte (fp16) ≈ 4 GB / layer × 80 layers ≈ 320 GB——单卡装不下。
对策:
- KV cache 量化(INT8 / FP8)
- KV cache 跨节点池(鲲鹏 UB / NIXL / Mooncake)
- Sliding window attention(牺牲历史 tokens 复用)
瓶颈 3:Attention
每 token 要 attend 全 1M token —— ops 仅 attention。
对策:
- FlashAttention 系列(kernel 优化)
- Sparse attention / sliding window
- StreamingLLM 风格(保留 sink + 最近 window)
7.2 1M token 真实 wall clock
按 H100 + FP8 + Ring Attention 当前最优:
- Prefill:~1-2 分钟 / 1M token
- Decode:~30-50ms / token
- 1M prompt + 100 token output 总耗时 ~2-3 分钟
这是 GPT-4 / Claude / DeepSeek 这一档商业模型的真实数字。
8. 多模型同卡服务
8.1 三种隔离模式
| 模式 | 隔离粒度 | 性能 isolation | 实现 |
|---|---|---|---|
| MIG (Multi-Instance GPU) | SM / 显存物理分割 | 强 | A100 / H100 硬件支持 |
| MPS (Multi-Process Service) | 时间共享 | 弱 | CUDA MPS daemon |
| Time-slicing | 软件层调度 | 弱 | k8s GPU device plugin |
8.2 选型
- 不同租户严格隔离(云服务、SLA 强)→ MIG
- 同租户多模型混跑 → MPS / time-slicing
- 极致密度(小模型 + 共享)→ time-slicing + custom scheduler
8.3 一个工程坑
MIG 的 instance 数量配置在boot 时固定——不能动态改。所以 MIG 适合稳定负载,不适合突发负载。
9. 推理 Goodput 路径总结
把上面所有内容整合成”推理 Goodput 优化的工程化路径”:
9.1 阶段 1:从基线到合理(Goodput → 50%+)
- ✅ 用 vLLM 起步(不要从头写)
- ✅ Continuous batching 默认开
- ✅ PagedAttention 默认开
- ✅ Chunked prefill(如果有长 prompt)
- ✅ Prefix caching(如果有共享前缀)
9.2 阶段 2:精细调优(Goodput → 65-75%)
- ✅ KV cache fp8 量化
- ✅ 模型本身 fp8(H100+)
- ✅ Speculative decoding(如果 TPOT 紧)
- ✅ 选对 batch size(throughput vs latency 平衡)
- ✅ Continuous batching 调度参数(max_num_batched_tokens / max_num_seqs)
9.3 阶段 3:架构级优化(Goodput → 80%+)
- ✅ PD 解耦(长 prompt 占比高时)
- ✅ Cross-node KV cache 共享
- ✅ 自定义 kernel(特定算子)
- ✅ 多模型 MIG 提升集群利用率
- ✅ 故障预测 + 主动迁移
9.4 与训练 Goodput 的对照
| 维度 | 训练 (Ch8) | 推理 (Ch9) |
|---|---|---|
| 主要矛盾 | 通信 + 故障 | 长短请求混跑 + KV cache |
| 核心优化技术 | 3D 并行 / ZeRO / FP8 | continuous batch / PagedAttention / spec decode |
| Goodput 顶级水平 | 75%+ | 80%+ |
| 工程成熟度 | Megatron / DeepSpeed | vLLM / TRT-LLM / Dynamo |
| 公司核心竞争力 | 训练 stack 是顶级 lab 护城河 | 推理 stack 是大模型公司护城河 |
🎯 自我检验清单
- TTFT 和 TPOT 之间为什么存在结构性冲突?不同业务(客服 / Code / 批处理)各自更看重哪个?
- PagedAttention 解决的是什么具体问题?为什么 KV cache 内存利用率从 50% 能提到 90%+?
- 解释 PD 解耦的工程动机——为什么”prefill 是 compute bound + decode 是 memory bound”使得它们值得分开?
- Speculative decoding 的”一次 forward 验证 K 个 token 几乎免费”这件事的硬件原因是什么?
- 1M 上下文推理的三个工程瓶颈分别是什么?各自的对策是什么?
📚 参考资料
- vLLM:github.com/vllm-project/vllm
- NVIDIA TensorRT-LLM:github.com/NVIDIA/TensorRT-LLM
- SGLang:github.com/sgl-project/sglang
- NVIDIA Dynamo:developer.nvidia.com/blog/introducing-nvidia-dynamo
- Mooncake:github.com/kvcache-ai/Mooncake
- PagedAttention 论文:arxiv.org/abs/2309.06180
- EAGLE / Medusa(spec decoding):相关 GitHub 与论文
- 本系列模块四 推理优化(详细推理引擎对比)
🌟 模块零至此完整。从 Ch1 的 Goodput 方法论,到 Ch5-7 的 CUDA / PyTorch 工具链,到 Ch8 的训练 Goodput,再到 Ch9 的推理 Goodput——一份贯穿单 kernel 到万卡集群、训练到推理的性能工程方法论就此收口。
把这 12 章作为长期参照——当你下次面对”为什么我的训练 / 推理慢""怎么才能让 Goodput 上去”这类问题时,回到这里找答案。