大模型推理部署性能调优实战手册
📍 本文是「LLM进阶:从会用到底层精通」专题的第 9/10 篇
📊 难度:高级 | ⏱️ 预计阅读:22 分钟
学习目标
🎯 学完本文后,你将能够:
- 理解 Continuous Batching 如何解决「长短不一请求的 GPU 利用率」问题
- 掌握 PagedAttention(vLLM 核心技术)的 KV Cache 内存管理原理
- 理解 Speculative Decoding 为什么能让生成速度翻倍
- 对比 vLLM 和 SGLang 在延迟/吞吐/易用性上的差异并做出选型
前置唤醒
📚 在开始之前,请确认你已经理解:
- Transformer 的自回归推理流程(一个一个 Token 生成)
- KV Cache 的作用(见第 1-2 篇相关内容)
- GPU 基本概念(显存、SM、Tensor Core)
1. 为什么推理比训练更「难」优化?
训练和推理的优化目标完全不同,这是很多人低估的一个点。
训练是「批处理」模式——你把几百万个 Token 塞进 GPU,算一个梯度,更新参数。优化目标是 throughput(每秒处理多少 Token),latency 不重要。一个训练 step 跑 10 秒还是 12 秒,只要每天能完成目标步数就行。
推理是「在线服务」模式——用户发来一个请求,200ms 内不回结果他就划走了。优化目标是一个 latency + throughput 的帕累托前沿:既要快(每个请求的延迟低),又要省(同样的硬件服务更多用户)。
这两个目标经常是矛盾的。更大的 batch → 更高的 throughput → 但每个请求在 batch 里等更久 → 更高的 latency。
更麻烦的是,推理请求的长度千差万别:有的用户问「今天天气怎么样」(5 个 token),有的用户贴了一整篇论文问总结(5000 个 token),有的请求生成 10 个 token 就结束了,有的要生成 2000 个 token。把这些长短不一的请求塞进同一个 batch,问题就来了。
✨ 一句话记住:推理优化 = 在 latency 和 throughput 之间走钢丝,同时还要应对请求长度的巨大方差。
2. Continuous Batching:不等了
2.1 Static Batching 的问题
传统的 Static Batching 逻辑很简单:等 N 个请求到达后,组成一个 batch,一起处理,等所有人都完成后一起返回。
问题是:最快的请求要等最慢的请求。
Static Batching 时间线:
请求A: ████░░░░░░░░ (生成 50 token, 1s完成)
请求B: ████████████████████████ (生成 300 token, 6s完成)
请求C: ██░░░░░░░░░░░░░ (生成 25 token, 0.5s完成)
批处理时间: ░░░░░░░░░░░░░░░░░░░░░░░░████████████████████████░░ (7s)
↑ 等待 B 和 C 到达 ↑ C 和 A 早就完成了,干等 BStatic Batching 时间线:
请求A: ████░░░░░░░░ (生成 50 token, 1s完成)
请求B: ████████████████████████ (生成 300 token, 6s完成)
请求C: ██░░░░░░░░░░░░░ (生成 25 token, 0.5s完成)
批处理时间: ░░░░░░░░░░░░░░░░░░░░░░░░████████████████████████░░ (7s)
↑ 等待 B 和 C 到达 ↑ C 和 A 早就完成了,干等 B
Continuous Batching 时间线:
请求A: ████ (完成, 立刻出队)
请求B: ████████████████████████ (还在跑)
请求C: ██ (完成, 立刻出队)
请求D: ██████████ (A完成→D立即加入)
请求E: ██████████████ (C完成→E立即加入)
时间: ████████████████████████ (持续满负荷)
↑ 队列在动态变化,GPU 几乎不空转A 和 C 在 0.5 秒和 1 秒后就完成了,但必须等 B 跑 6 秒。GPU 在中间的 5 秒里利用率只有 33%。
2.2 Continuous Batching 的核心思路
不等了。 哪个请求完成就踢出去,新请求立刻进来补位。
# Speculative Decoding 的伪代码逻辑
draft_tokens = draft_model.generate(prompt, max_tokens=5) # 小模型快速生成 5 个
target_logits = target_model.forward(prompt + draft_tokens) # 大模型一次性验证
accepted = []
for i, draft_token in enumerate(draft_tokens):
if draft_token == target_logits[i].argmax(): # 投机成功
accepted.append(draft_token)
else:
accepted.append(target_logits[i].argmax()) # 投机失败, 用大模型自己的
break # 后续候选全部作废Continuous Batching 时间线:
请求A: ████ (完成, 立刻出队)
请求B: ████████████████████████ (还在跑)
请求C: ██ (完成, 立刻出队)
请求D: ██████████ (A完成→D立即加入)
请求E: ██████████████ (C完成→E立即加入)
时间: ████████████████████████ (持续满负荷)
↑ 队列在动态变化,GPU 几乎不空转
# --- 部署 Qwen2.5-7B-Instruct with vLLM ---
# 启动服务: python -m vllm.entrypoints.openai.api_server \
# --model Qwen/Qwen2.5-7B-Instruct \
# --max-model-len 4096 \
# --gpu-memory-utilization 0.90 \
# --enable-prefix-caching \
# --max-num-seqs 64 # 最大并发请求数,利用 PagedAttention 动态管理
# --- 客户端压测脚本 (locust) ---
# pip install locust openai
from locust import HttpUser, task, between
from openai import OpenAI
class LLMUser(HttpUser):
wait_time = between(0.1, 0.5) # 模拟真实用户的思考间隔
prompts = [
"解释一下什么是递归",
"用Python写一个快速排序",
"总结Transformer架构的核心思想",
"什么是向量数据库?它和传统数据库有什么区别?",
"请用三句话概括机器学习的本质",
]
@task
def chat(self):
import random
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
response = client.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
prompt=random.choice(self.prompts),
max_tokens=100,
temperature=0.7,
)
assert len(response.choices[0].text) > 0
# 运行压测: locust -f benchmark.py --host http://localhost:8000
# 在 http://localhost:8089 查看实时 QPS/P50/P95/P99 延迟实现的难点:不同请求处于不同的生成阶段(有的刚开始 prefill,有的在 decode 第 k 个 token),需要框架在 kernel 层面支持动态拼接。
🛠️ 实战经验:我们线上从 TGI(HuggingFace 的推理框架)切到 vLLM 后,同样 4 张 A100,QPS 从 12 涨到了 45。不是因为 vLLM 的 attention kernel 更快——而是 Continuous Batching 把 GPU 利用率从 30-40% 拉到了 80-90%。大部分人的推理性能瓶颈根本不是 kernel,是调度。
💡 关键要点:Continuous Batching 是推理框架的「灵魂」——kernel 优化带来 20-30% 的提升,好的调度带来 2-3x 的提升。
3. PagedAttention:KV Cache 的虚拟内存
3.1 KV Cache 预分配是最蠢的浪费
推理时每个 Token 都要存它的 Key 和 Value,供后续生成时复用。问题是:你不知道用户的回答会有多长。
传统做法:给每个请求预分配 max_seq_len 长度的 KV Cache 空间。如果 max_seq_len 是 4096 但用户只说了 20 个字——你浪费了 99.5% 的显存。
3.2 PagedAttention 的核心思想
vLLM 团队从操作系统的虚拟内存中获得了灵感:按「页」管理 KV Cache。
这不只是省显存——它还解决了 KV Cache 碎片化的问题。传统预分配模式下,不同请求的 KV Cache 块大小不一致,释放后留下各种大小的空洞,无法被新请求利用。PagedAttention 统一了页大小,分配和回收都是整页操作,零碎片。
3.3 数字说话
假设服务 100 个并发请求,max_seq_len 为 4096,实际平均生成长度 200 token:
🛠️ 实战经验:PagedAttention 在实际场景中通常能省 50-70% 的 KV Cache 显存。省下来的显存有两个去处:(1) 提升 batch size → 提高吞吐;(2) 支撑更长的上下文 → 原来只能跑 8K 上下文的单卡现在能跑 32K。
✨ 一句话记住:PagedAttention = 把 KV Cache 当虚拟内存管——按需分页、动态回收、零碎片。
4. Speculative Decoding:用小模型加速大模型
4.1 自回归生成的本质瓶颈
LLM 每次只能生成一个 Token——这串行依赖是 GPU 并行能力最大的浪费。你有 80GB 显存的 H100、几千个 Tensor Core,却只能一次算一个 Token 的概率分布。
4.2 投机解码的原理
Speculative Decoding 用了一个巧妙的「投机+验证」策略:
Speculative Decoding 的伪代码逻辑
draft_tokens = draft_model.generate(prompt, max_tokens=5) # 小模型快速生成 5 个
target_logits = target_model.forward(prompt + draft_tokens) # 大模型一次性验证
accepted = []
for i, draft_token in enumerate(draft_tokens):
if draft_token == target_logits[i].argmax(): # 投机成功
accepted.append(draft_token)
else:
accepted.append(target_logits[i].argmax()) # 投机失败, 用大模型自己的
break # 后续候选全部作废
4.3 什么时候有效?
Speculative Decoding 的效果取决于 Draft Model 和 Target Model 的「对齐度」。如果小模型能猜中 80% 以上的 Token(比如同样架构、不同大小),加速比可达 2-3x。如果小模型和大模型分布差异太大(比如不同家族的模型),猜中率可能只有 30%,加速比趋近于 1。
🛠️ 实战经验:用 Qwen2.5-0.5B 做 Draft Model 来加速 Qwen2.5-72B,猜中率约 85%,实际加速约 2.3x。但如果用 LLaMA-3-8B 做 Draft Model 来加速 Qwen-72B,猜中率降到 55%,加速仅 1.3x——几乎不值得额外的系统复杂度。同家族、同 tokenizer 是大前提。
✨ 一句话记住:用小模型「猜」答案、大模型「验证」答案——猜对了省时,猜错了回退,永远不丢精度。
5. vLLM vs SGLang:推理框架选型对比
2025-2026 年,vLLM 和 SGLang 是 LLM 推理部署的两大主流选择。
选型建议
🛠️ 实战经验:不要同时引入两个框架——维护两套部署配置、两套监控、两套调优经验的成本远超省下的几毫秒延迟。选一个,用透。
6. 代码实践:vLLM 部署与压测
--- 部署 Qwen2.5-7B-Instruct with vLLM ---
启动服务: python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--max-model-len 4096 \
--gpu-memory-utilization 0.90 \
--enable-prefix-caching \
--max-num-seqs 64 # 最大并发请求数,利用 PagedAttention 动态管理
--- 客户端压测脚本 (locust) ---
pip install locust openai
from locust import HttpUser, task, between
from openai import OpenAI
class LLMUser(HttpUser):
wait_time = between(0.1, 0.5) # 模拟真实用户的思考间隔
prompts = [
"解释一下什么是递归",
"用Python写一个快速排序",
"总结Transformer架构的核心思想",
"什么是向量数据库?它和传统数据库有什么区别?",
"请用三句话概括机器学习的本质",
]
@task
def chat(self):
import random
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
response = client.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
prompt=random.choice(self.prompts),
max_tokens=100,
temperature=0.7,
)
assert len(response.choices[0].text) > 0
运行压测: locust -f benchmark.py --host http://localhost:8000
在 http://localhost:8089 查看实时 QPS/P50/P95/P99 延迟
💡 关键要点:
- --max-num-seqs 设为你预期的最大并发数——PagedAttention 会自动管理 KV Cache,不需要手动预分配- --enable-prefix-caching 在多轮对话和 system prompt 复用场景下能省 30-50% 的 prefill 时间- --gpu-memory-utilization 不要设为 1.0——留 5-10% 给 CUDA context 和临时分配,否则 OOM🛠️ 实战经验:我们线上压测发现 max-num-seqs=64 时显存被打到 90%,P50 延迟 120ms,P99 延迟 350ms。但当并发用户从 50 涨到 100 时,P99 跳到了 2.8 秒——排队等待时间远大于实际推理时间。解法不是加显存,而是加实例(水平扩容)。7. 常见误区
❌ 误区 1:越大 batch size 越好
对于推理,更大的 batch 确实提高 GPU 利用率,但代价是每个请求的排队延迟增加。当并发超过 GPU 能力时,P99 延迟会指数级增长。
正确做法:监控 P50 和 P99 延迟。当 P99/P50 比值超过 5 时,说明排队效应已经失控——要么降 max-num-seqs,要么水平扩容。
❌ 误区 2:Speculative Decoding 总能加速
Speculative Decoding 只在 Draft Model 猜中率高时有效。如果猜中率低于 60%,Draft Model 的开销已经超过了它省下的时间。此外,Draft Model 本身也占显存——在显存紧张的场景下,可能得不偿失。
❌ 误区 3:换推理框架就能解决所有性能问题
框架只影响 30-50% 的性能。剩下的来自:(1) 模型量化(INT8/INT4);(2) Prompt 长度控制;(3) 请求调度策略;(4) 网络和序列化开销。一个糟糕的 Prompt(5000 token 的 system prompt)能吃掉你所有优化收益。
8. 练习与思考
练习 1:基础检验题
解释 PagedAttention 如何解决 KV Cache 碎片化问题。类比操作系统内存管理中的分页机制。
<details>
<summary>查看答案与解析</summary>
传统预分配类似操作系统的「固定分区分配」——每个进程(请求)分配一整块连续内存(KV Cache),不同进程的块大小不同。进程结束后留下各种大小的空洞,新进程可能因为找不到合适大小的空洞而无法运行(即使总空闲空间足够)。
PagedAttention 类似「分页机制」——把物理内存(GPU 显存)划分为固定大小的页(如每页 16 个 token)。每个请求使用一组离散的页,页之间通过页表映射。请求结束时释放所有页,因为页大小统一,任何空闲页都可以被任何新请求使用——零外部碎片。
如果答错了,复习一下操作系统内存管理中「内部碎片 vs 外部碎片」的区别。
</details>
练习 2:应用分析题
你的服务每天有 100 万次请求、峰值 QPS 500、P99 延迟要求 < 2 秒。设计 GPU 集群规模和 vLLM 配置。
<details>
<summary>查看答案与解析</summary>
假设使用 Qwen2.5-7B,单张 A100 (80GB) 在 vLLM 下可支撑约 50 个并发请求(P99 < 2s):
--max-num-seqs 64 --gpu-memory-utilization 0.88 --enable-prefix-caching关键不是单实例性能,而是你的扩缩策略能否在流量尖刺时 30 秒内完成扩容。
</details>
练习 3:拓展思考题
MoE 模型的推理(如 DeepSeek-V3 671B/37B 活跃)有哪些额外的部署挑战?
<details>
<summary>查看思路引导</summary>
核心挑战来自 Expert 分布在多卡上:
这是当前 MoE 推理最前沿的工程挑战,DeepSeek 团队的开源推理方案是当前的最佳参考。
</details>
延伸阅读
本文总结
💡 核心收获:
- Continuous Batching + PagedAttention + Speculative Decoding 是现代 LLM 推理的「三件套」,缺一个都会在某个维度上吃亏
- Continuous Batching 解决调度效率(让 GPU 不空转),PagedAttention 解决显存效率(让 KV Cache 不浪费),Speculative Decoding 解决计算效率(让大模型不少算)
- vLLM 和 SGLang 各有侧重:通用场景选 vLLM,Agent/结构化生成选 SGLang
- 推理优化的天花板不是框架或 kernel——是你的请求调度、负载均衡、弹性扩缩策略
⚠️ 注意事项:本文聚焦单模型推理部署,未覆盖多模型混合部署(如 embedding 模型 + LLM 在同一集群)和 GPU 虚拟化(MIG/vGPU)。这些是企业级推理平台需要额外考虑的方向。
---
🔗 下一篇:技术栈讲完了——从 Transformer 基础到推理部署优化,你已建立了完整的 LLM 技术认知。最后一篇「2026 LLM技术趋势盘点与展望」将带你抬头看路,盘点这一年的关键变化和未来方向。