Skip to content

调参与性能调优

本页速览 LLM 推理调参清单与性能调优实战:max_num_seqs、max_model_len、gpu_memory_utilization、block_size、swap_space、prefix_caching、chunked_prefill、tensor_parallel_size 八大参数详解,配 nvidia-smi、Nsight Systems、PyTorch Profiler 的瓶颈诊断方法与 compute-bound vs memory-bound 的应对策略。

调参与性能调优

调参不是"试错",是"测→看→改"——先测出瓶颈在哪一层,再改对应那一层的参数。盲调 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业务需要
RAG4096平衡检索深度与显存
超长上下文(如 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_bytes

Llama-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%)

调参方向

场景推荐值理由
单实例独占 GPU0.85–0.9最大化 KV pool
多实例共享 GPU(MPS/cgroup)0.4–0.5给其他实例留空间
同卡上跑 embedder/reranker0.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.gpu70–95%< 50% 说明算力闲置(memory-bound 或调度差);> 95% 持续可能瓶颈在算力
utilization.memory50–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× 吞吐精度损失模型量化基础
上 FlashAttention1.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 cachingTTFT 大降仅多轮有效-
减小模型显存带宽大幅降精度大幅降剪枝与稀疏化

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%
- [ ] 回滚预案准备好(旧参数集)

七、延伸阅读

参考资料