外观
调参与性能调优
调参不是"试错",是"测→看→改"——先测出瓶颈在哪一层,再改对应那一层的参数。盲调 100 个参数,等于把瓶颈换了一个位置。
推理优化圈有个误区:"vLLM 调参就是改 max_num_seqs"。事实上 vLLM 有 8 个核心参数,每一个都对应一个性能维度,改错位置等于把瓶颈换了位置不解决问题。改 max_num_seqs 解决不了 memory-bound、改 gpu_memory_utilization 解决不了 compute-bound——这是 Roofline 模型与算力分析 的典型应用场景。
本文给出 LLM 推理调参的完整框架:先看 8 个核心参数、再走一遍性能调优 checklist、最后用 profiling 工具找瓶颈。读完本文,你应该能针对任何性能问题,定位是哪一层、改哪个参数、改完怎么验证。这是 从零部署一个推理服务 与 渐进式教程 的调参配套。
本文的边界
本文以 vLLM 为主,因为它是今天最主流的 LLM 推理引擎。SGLang / TensorRT-LLM / TGI 的参数大同小异,原理可迁移。如果你想先建立推理性能的整体框架,先读 延迟、吞吐与并发 与 显存层次与带宽墙。
一、调参总原则:先测后调
1. 调参的"测→看→改"循环
┌─────────────────────────────────────────┐
│ ① 测:跑 [推理基准测试实践](/practice/benchmarking) │
│ - TTFT、TPOT、p50/p99、显存 │
└──────────────────┬──────────────────────┘
▼
┌─────────────────────────────────────────┐
│ ② 看:profiling │
│ - nvidia-smi(GPU 利用率/显存/温度) │
│ - vLLM logs(KV cache 占用、吞吐) │
│ - Nsight Systems(算子级时间分布) │
└──────────────────┬──────────────────────┘
▼
┌─────────────────────────────────────────┐
│ ③ 改:定位瓶颈层 │
│ - compute-bound → 调 batch/量化 │
│ - memory-bound → 调 量化/KV cache │
│ - 调度问题 → 调 max_num_seqs/调度 │
└──────────────────┬──────────────────────┘
▼
┌─────────────────────────────────────────┐
│ ④ 验:再测一遍,对比前一版 │
│ - 改进有效?继续下一项 │
│ - 改进无效?回滚,换方向 │
└─────────────────────────────────────────┘这个循环不能跳步——跳过"测"和"看"直接"改",等于盲调,10 次有 9 次改错位置。详见 部署设计原则 的"模型+算子+系统三层都要查瓶颈"。
2. 调参不是一锤子买卖
每个参数都不是"调到最优就固定"——最优值随业务流量变化:
- 白天高峰:
max_num_seqs调大(吞吐优先) - 夜晚低谷:
max_num_seqs调小(延迟优先) - 大促期间:临时降
gpu_memory_utilization(多塞一个实例到 GPU)
生产部署要有"参数集"概念:高峰配置、低谷配置、故障降级配置,按场景切换。详见 模型服务化与编排 的"参数集"。
二、八大核心参数
下面 8 个参数是 vLLM 调参的全部"主战场"。每个参数都给出:含义、默认值、调参方向、典型影响、常见误区。
1. max_num_seqs(最大并发请求数)
含义:vLLM 同时在飞的请求数上限。等同于"continuous batching 的 batch size 上限"。
默认值:256
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 在线聊天(延迟优先) | 16–32 | 小 batch 保 p99 |
| 在线批处理(吞吐优先) | 64–128 | 大 batch 拉吞吐 |
| 离线批量 | 256+ | 不在乎延迟,吞吐最大化 |
| 投机解码场景 | ≤ 16 | 大 batch 投机解码反向收益 |
典型影响:从 16 → 64,吞吐通常涨 3×,但 p99 延迟可能涨 5–10×。这是 批处理与请求调度 的核心 trade-off。
常见误区:
- 设太大:32 并发灌 256 上限 → KV cache 显存爆,部分请求被换出/重算,p99 飞涨
- 设太小:64 并发灌 16 上限 → 48 个请求排队,p99 飞涨
- 不随业务调整:高峰低谷用同一个值,要么浪费要么退化
找"拐点"的方法
跑 推理基准测试实践 的"并发数 vs p99 延迟"曲线,找 p99 突然飞涨的拐点,max_num_seqs 设在拐点之前。例:
并发 1 → p99 25ms
并发 4 → p99 30ms
并发 16 → p99 50ms ← 拐点之前
并发 32 → p99 180ms ← 拐点
并发 64 → p99 1200ms ← 严重退化max_num_seqs = 16 是这个例子的最优值。
2. max_model_len(模型最大上下文长度)
含义:模型能处理的最大 token 数(input + output)。
默认值:取决于模型,Llama-2-7B 是 4096
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 短对话 | 2048 | 节省 KV cache 显存 |
| 长文档摘要 | 8192–16384 | 业务需要 |
| RAG | 4096 | 平衡检索深度与显存 |
| 超长上下文(如 Claude 200K) | 32768+ | 需大显存 + chunked prefill |
典型影响:从 4096 → 16384,KV cache 显存占用 4×,能接的并发数 1/4。
常见误区:
- 设太大"备着":8K 业务硬设 32K,4 倍 KV cache 显存白占
- 忽略与
max_num_seqs的耦合:max_model_len × max_num_seqs是 KV cache 总占用,两者不能各自独立调
KV cache 显存计算公式
KV cache 显存 = 2 × num_layers × hidden_size × num_heads × head_dim × max_model_len × max_num_seqs × dtype_bytesLlama-2-7B:32 层 × 128 hidden × 32 heads × 128 head_dim × 4096 × 32 × 2 bytes (bf16) = 8 GB
设到 max_num_seqs=64 时变成 16 GB。设到 max_model_len=16384 变成 32 GB——爆显存。
3. gpu_memory_utilization(GPU 显存使用率)
含义:vLLM 占总显存的比例(用于 KV pool)。
默认值:0.9(占 90%)
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 单实例独占 GPU | 0.85–0.9 | 最大化 KV pool |
| 多实例共享 GPU(MPS/cgroup) | 0.4–0.5 | 给其他实例留空间 |
| 同卡上跑 embedder/reranker | 0.6–0.7 | 给其他模型留空间 |
| 故障降级(重试多) | 0.5–0.6 | 留余量给临时 KV 分配 |
典型影响:从 0.9 → 0.7,KV pool 缩水 22%,能接的并发降 30%。
常见误区:
- 默认 0.9 不改:同卡上想再跑一个 embedder,OOM 启动失败
- 设到 0.95 想榨干性能:临时显存分配(kernel 中间矩阵)不够,反而崩
别让 vLLM 把 GPU 全占了
生产环境多模型共存是常态。LLM 占 60–70%,留 30–40% 给 embedder/reranker/监控。详见 常见陷阱与反模式 的"多模型共享 GPU 但没设 MPS/cgroup"。
4. block_size(KV cache 块大小)
含义:PagedAttention 的"页大小"。KV cache 被分成 block_size 大小的块管理,类似 OS 的虚拟内存分页。
默认值:16
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 默认 | 16 | 通用最优 |
| 极致延迟(小 batch) | 8 | 减少 internal fragmentation |
- 极致吞吐(大 batch) | 32 | 减少 metadata 开销 |
典型影响:从 16 → 32,metadata 开销减半但 internal fragmentation 增加。通常不调——vLLM 团队为 16 做了大量优化。
常见误区:
- 以为调大就快:block_size 不是越大越好,调到 64 反而退化(internal fragmentation 占主导)
- 改了不验证:改 block_size 后必须重跑基准对比,不能假设
详见 vLLM 与 PagedAttention 的"PagedAttention 实现"。
5. swap_space(CPU 交换空间)
含义:KV cache 满时,把部分 KV 换出到 CPU 内存的缓冲区大小(GB)。
默认值:4(GB)
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 在线服务 | 0 | 换出意味着 p99 飞涨,宁可拒绝也不换出 |
| 离线批量 | 16–32 | 偶尔换出可接受,换空间换吞吐 |
| 内存吃紧 | 0 | 物理内存不够别再吃 |
典型影响:换出 → 换入时,单请求延迟从 50ms 暴涨到 500ms+。
常见误区:
- 设太大当 fallback:以为有了 swap 就能多接并发——其实一旦开始 swap,p99 就崩
- 在线服务开 swap:换出/换入的不确定性让 p99 不可控
在线服务就该关 swap
生产在线服务如果经常触发 swap,说明 max_num_seqs 设大了——应该减小并发上限,而不是开 swap。swap 是最后兜底,不是常规手段。详见 部署设计原则 的"延迟优先 vs 吞吐优先先选一个"。
6. enable_prefix_caching(前缀缓存)
含义:相同的 prompt 前缀(如 system prompt)只计算一次 prefill,后续请求复用 KV。
默认值:vLLM 0.5+ 默认开启
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 多轮对话 / RAG(共享 system prompt) | 开 | TTFT 显著降低 |
| 单轮问答(无共享前缀) | 关 | 缓存命中率低,metadata 开销反而拖累 |
| structured output(共享 few-shot) | 开 | few-shot prefix 复用收益巨大 |
典型影响:多轮对话场景,TTFT 从 200ms 降到 50ms(prefill 直接跳过)。
常见误区:
- 不知道这个开关:多轮对话场景没开,TTFT 始终高,跑去改
max_num_seqs——根本不对症 - 单轮场景开了:缓存命中率 < 10%,反而增加 metadata 开销,TPOT 略升
7. enable_chunked_prefill(分块预填充)
含义:长 prompt 的 prefill 被切成小块,与 decode 交错执行,避免长 prompt 阻塞 decode 队列。
默认值:vLLM 0.5+ 默认开启
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 长 prompt + 在线服务 | 开 | 长 prefill 不阻塞短 decode |
| 短 prompt + 离线批量 | 关 | chunked 增加开销,没必要 |
| 极长上下文(32K+) | 开 + 调 max_num_batched_tokens | 必须 chunked 否则 OOM |
典型影响:长 prompt 场景,开 chunked 后 decode 队列 p99 从 500ms 降到 50ms。
常见误区:
- 离线批量也开:批量不在乎 decode 延迟,chunked 徒增开销
max_num_batched_tokens没调:默认值可能不适合你的场景,要单独调
8. tensor_parallel_size(张量并行度)
含义:跨多少张 GPU 做张量并行。
默认值:1(单卡)
调参方向:
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 7B 模型 | 1 | 单卡放得下,多卡浪费 |
| 13B–30B 模型 | 2 | 单卡放不下或 KV pool 不够 |
| 70B 模型 | 4–8 | 必须 |
| 175B+ 模型 | 8+ | 必须 + 加流水线并行 |
典型影响:7B 模型 TP=1 → 2,吞吐只涨 1.3×(远不到 2×),因为通信开销吃掉收益。
常见误区:
- 小模型也上 TP:7B 模型 TP=2,性能反退(通信开销 > 计算节省)
- TP 当银弹:70B 模型靠 TP=8 也撑不住,要加 PP(pipeline parallel)
详见 分布式推理(TP/PP)。
三、性能调优 checklist
调参不是从参数表里乱挑——按下面这个顺序走,能避开 90% 的盲调陷阱。
Step 1: nvidia-smi 看宏观
bash
# 持续监控(每秒刷新)
nvidia-smi --query-gpu=timestamp,name,utilization.gpu,utilization.memory,memory.used,memory.free,power.draw,temperature.gpu,clocks.sm,clocks.mem --format=csv -l 1观察四个指标:
| 指标 | 健康范围 | 异常含义 |
|---|---|---|
utilization.gpu | 70–95% | < 50% 说明算力闲置(memory-bound 或调度差);> 95% 持续可能瓶颈在算力 |
utilization.memory | 50–90% | < 30% 说明显存带宽没用满(compute-bound);> 95% 持续可能是 memory-bound |
temperature.gpu | < 80°C | > 85°C 触发热限频,性能掉 30%(详见 常见陷阱与反模式 的"没监控 GPU 温度") |
power.draw | 接近 TDP | 远低于 TDP 说明没跑满(功耗墙或调度问题) |
Step 2: vLLM logs 看微观
vLLM 启动时输出关键信息:
INFO 08-22 14:30:00 config.py:65] Initializing KV cache with ... blocks
INFO 08-22 14:30:00 config.py:78] KV cache size: 24576 tokens # ← 看 KV pool 大小
INFO 08-22 14:30:00 llm_engine.py:130] # GPU blocks: 1536 # ← 块数运行时关键日志:
# 吞吐与延迟
INFO 08-22 14:31:00 metrics.py:120] Avg prompt throughput: 250.4 tokens/s
INFO 08-22 14:31:00 metrics.py:121] Avg generation throughput: 1100.2 tokens/s
INFO 08-22 14:31:00 metrics.py:122] Running: 32 reqs (swapped: 0, finished: 28)重点看:
swapped: N—— N > 0 说明在换出,要降max_num_seqs或加显存Avg generation throughput—— 单实例的生成吞吐Running: N reqs—— 当前在飞请求数,与max_num_seqs对比看是不是灌满了
Step 3: Nsight Systems 看算子级
nvidia-smi 看不到算子级,要 Nsight Systems:
bash
# 启动 vLLM 时挂载 nsys profile
nsys profile -o vllm_profile \
--trace=cuda,nvtx,osrt \
--capture-range=cudaProfilerApi \
--export=sqlite \
vllm serve meta-llama/Llama-2-7b-chat-hf --port 8000然后跑一批请求,停止 vLLM,分析 .qdrep 文件。看:
- 算子时间分布:哪个 kernel 占时间最多?attention / matmul / quantize?
- CPU-GPU overlap:CPU 在等 GPU 时,是 CPU 空转还是 GPU 等待 CPU?
- kernel launch 开销:launch 太频繁说明 batch 太小
Step 4: PyTorch Profiler 看框架级
如果用 HuggingFace transformers 跑(非 vLLM),PyTorch Profiler 更直接:
python
from torch.profiler import profile, ProfilerActivity
with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
record_shapes=True) as prof:
out = model.generate(**inputs, max_new_tokens=64)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=20))看:
- CPU 时间 vs CUDA 时间:CPU 占比高 → 调度开销大;CUDA 占比高 → 真在算
- 算子分布:matmul / attention / elementwise 各占多少
Step 5: 找瓶颈层
综合上面四步,定位瓶颈:
| 观察到的现象 | 瓶颈层 | 应对 |
|---|---|---|
| GPU 利用率 < 50%,显存利用率 < 50% | 调度 / batch 太小 | 加 max_num_seqs,开 continuous batching |
| GPU 利用率 > 90%,显存利用率 > 90% | compute-bound | 上量化(INT8/INT4),加 tensor core 优化 |
| GPU 利用率 < 50%,显存利用率 > 90% | memory-bound | 上量化(减权重访存),加 PagedAttention |
| GPU 温度 > 85°C | 热限频 | 改散热,降 max_num_seqs |
swapped > 0 持续 | 显存不足 | 降 max_num_seqs,关 swap,上 TP |
| 长 prompt TTFT 高 | prefill 慢 | 开 chunked prefill,开 prefix caching |
| TPOT 高 | decode 慢 | 上量化,上投机解码 |
这是 Roofline 模型与算力分析 的工程落地。
四、compute-bound vs memory-bound 的应对
LLM 推理的瓶颈几乎总是这两种之一。下面分别给出完整应对策略。
1. compute-bound(算力受限)
症状:
- GPU 利用率 > 90%
- 显存利用率也高(算力喂满了,访存也满)
- 加 batch 不再涨吞吐(计算已饱和)
根因:模型本身的 FLOPs 太多,算力跟不上。
应对:
| 手段 | 收益 | 代价 | 详见 |
|---|---|---|---|
| 上量化(INT8/INT4) | 2–4× 吞吐 | 精度损失 | 模型量化基础 |
| 上 FlashAttention | 1.3–2× | 改 attention kernel | 算子融合与自定义核 |
| 上 tensor core 优化 | 1.5× | 需要新硬件 | GPU 体系结构与优化 |
| 减小模型 | 5–10× | 精度大幅下降 | 剪枝与稀疏化 |
| 蒸馏小模型 | 2–5× | 训练成本 | 知识蒸馏 |
2. memory-bound(显存带宽受限)
症状:
- GPU 利用率 30–60%
- 显存利用率 > 90%
- 加 batch 不涨吞吐(访存饱和)
- TPOT 远高于"算力 ÷ token 数"
根因:每生成一个 token,要把整个权重从 HBM 读到 SRAM——读得比算得慢。
应对:
| 手段 | 收益 | 代价 | 详见 |
|---|---|---|---|
| 上 weight-only 量化(AWQ/GPTQ) | 1.5–2× TPOT | 精度小幅损失 | 权重量化与混合精度 |
| 上 PagedAttention | 减少 KV 浪费 | 需 vLLM 等引擎 | vLLM 与 PagedAttention |
| 上投机解码(小 batch) | 2× TPOT | 多 draft 模型 | 投机解码与 Medusa/EAGLE |
| 加 prefix caching | TTFT 大降 | 仅多轮有效 | - |
| 减小模型 | 显存带宽大幅降 | 精度大幅降 | 剪枝与稀疏化 |
LLM 推理 90% 是 memory-bound
LLM decode 阶段(生成 token)几乎总是 memory-bound——每生成一个 token 要把整个权重读一遍,而算力远没用满。所以 量化的收益在 LLM 场景远大于经典模型——量化让权重访存减半,直接拉低 TPOT。这是 模型量化基础 在 LLM 场景的特殊价值。
五、调参实战案例
下面用三个真实案例展示调参流程。
案例 1:在线聊天 p99 飞涨
症状:vLLM 跑 Llama-2-7B,QPS 20 时 p99 突然从 200ms 飞涨到 1200ms。
调试流程:
Step 1: nvidia-smi → GPU 利用率 60%,显存 95%,温度 75°C(正常)
Step 2: vLLM logs → "swapped: 4 reqs"(出现换出!)
Step 3: 定位瓶颈 → KV cache 不够,部分请求被换出
Step 4: 当前配置 max_num_seqs=64, max_model_len=4096, gpu_mem_util=0.9
Step 5: 改 max_num_seqs=32(拐点之前),关 swap
Step 6: 验证 → swapped=0,p99 回到 180ms关键洞察:原配置把 max_num_seqs 设太大(64),KV pool 不够 → swap → p99 飞涨。降并发上限反而降低 p99——这是 批处理与请求调度 的反直觉案例。
案例 2:长 prompt TTFT 高
症状:vLLM 跑 Llama-2-7B,prompt 4000 tokens(RAG 场景),TTFT 1.2s。
调试流程:
Step 1: nvidia-smi → 正常
Step 2: vLLM logs → "Avg prompt throughput: 6000 tokens/s"
Step 3: 计算 → 4000 tokens / 6000 tokens/s = 666ms prefill
Step 4: 剩余 534ms 是队列等待(短请求被长 prefill 阻塞)
Step 5: 开 enable_chunked_prefill=true
Step 6: 验证 → TTFT 降到 380ms(prefill 切块后短请求不被阻塞)关键洞察:长 prompt 的 TTFT 高不在 prefill 本身,而在 prefill 阻塞了其他请求的 decode。chunked prefill 让两者交错,TTFT 大降。
案例 3:吞吐上不去
症状:vLLM 跑 Llama-2-7B,32 并发只跑到 800 tok/s,A100 上理论应该 1100+。
调试流程:
Step 1: nvidia-smi → GPU 利用率 60%(低!),显存 90%
Step 2: vLLM logs → "Running: 32 reqs",无 swap
Step 3: 定位 → GPU 利用率低 + 显存高 = memory-bound
Step 4: 检查模型 → bf16(未量化)
Step 5: 上 AWQ INT4 → 权重访存减半
Step 6: 验证 → GPU 利用率 85%,吞吐 1700 tok/s关键洞察:bf16 跑 LLM 在 7B 规模几乎必然 memory-bound——量化是首要手段。
六、调参 checklist
把所有调参纪律汇总成一张 checklist,每次部署前过一遍:
markdown
## 部署前调参 checklist
### 1. 业务场景定位
- [ ] 延迟优先 / 吞吐优先?(决定 max_num_seqs)
- [ ] prompt 长度分布?(决定 max_model_len)
- [ ] 是否多轮 / 共享 prefix?(决定 enable_prefix_caching)
### 2. 硬件预算
- [ ] 显存预算(权重 + KV pool + 临时)
- [ ] 是否多实例共享 GPU?(决定 gpu_memory_utilization)
- [ ] 散热是否到位?(影响温度)
### 3. 参数集
- [ ] max_num_seqs 设在"并发-p99"曲线拐点之前
- [ ] max_model_len × max_num_seqs < 显存预算
- [ ] gpu_memory_utilization 留 10% 给其他进程
- [ ] swap_space 在线服务设 0
- [ ] enable_prefix_caching 多轮场景开
- [ ] enable_chunked_prefill 长 prompt 场景开
### 4. 量化决策
- [ ] bf16 上线,跑业务评测集
- [ ] AWQ INT4 + 业务评测集对比
- [ ] MMLU 下降 < 1 才考虑全量上线
### 5. 监控告警
- [ ] nvidia-smi 监控温度(> 85°C 告警)
- [ ] vLLM logs 监控 swapped(> 0 告警)
- [ ] Prometheus 监控 p99(超 SLA 60% 告警)
- [ ] KV cache 命中率监控(< 50% 告警)
### 6. 灰度
- [ ] 新配置先跑 5% 流量
- [ ] 24 小时观察后扩到 100%
- [ ] 回滚预案准备好(旧参数集)七、延伸阅读
- 延迟、吞吐与并发 —— 调参对象的理论
- 显存层次与带宽墙 —— memory-bound 的根因
- Roofline 模型与算力分析 —— 瓶颈诊断方法
- 批处理与请求调度 —— max_num_seqs 的理论
- 模型量化基础 —— 量化调参的基础
- 权重量化与混合精度 —— weight-only 量化详解
- GPU 体系结构与优化 —— nvidia-smi 看什么
- 算子融合与自定义核 —— Nsight Systems 看什么
- vLLM 与 PagedAttention —— vLLM 实现深入
- 推理基准测试实践 —— 调参前测什么
- 部署设计原则 —— 调参的纪律
- 常见陷阱与反模式 —— 调参反模式集合
- 从零部署一个推理服务 —— 调参的端到端场景
参考资料
- vLLM: Configuration —— vLLM 全部参数的官方文档
- vLLM: Performance Optimization —— vLLM 性能优化指南
- NVIDIA Nsight Systems —— GPU profiling 工具
- PyTorch Profiler —— 框架级 profiler
- nvidia-smi Documentation —— GPU 监控
- NVIDIA Data Center GPU Specs —— 各型号 GPU 规格
- vLLM: PagedAttention Paper —— PagedAttention 论文
- SGLang: RadixAttention —— RadixAttention 论文
- Continuous Batching: Orca Paper —— 连续批处理论文