vLLM 高性能推理部署指南 / 10 - 性能调优
10 - 性能调优
通过系统化的调优策略,最大化 vLLM 推理引擎的性能。
10.1 性能指标体系
10.1.1 核心指标
| 指标 | 英文 | 含义 | 优化方向 |
|---|---|---|---|
| 首 Token 延迟 | TTFT (Time to First Token) | 从请求到收到第一个 token 的时间 | 越低越好 |
| 每 Token 延迟 | TPOT (Time Per Output Token) | 生成每个 token 的平均时间 | 越低越好 |
| 端到端延迟 | E2E Latency | 整个请求的完成时间 | 越低越好 |
| 吞吐量 | Throughput | 每秒处理的 token 数 | 越高越好 |
| GPU 利用率 | GPU Utilization | GPU 计算单元的使用率 | 越高越好 |
| 队列等待时间 | Queue Time | 请求在队列中等待的时间 | 越低越好 |
10.1.2 指标关系
吞吐量 vs 延迟(典型关系):
延迟 ↑
│ ╱
│ ╱
│╱ ← 延迟随负载增加
├──────────────── → 负载
吞吐量 ↑
│ ┌─── 平台期
│ ╱
│ ╱
│ ╱
│ ╱
└─────────────── → 负载
关键:找到吞吐量最高且延迟可接受的工作点
10.2 基准测试工具
10.2.1 使用内置 Benchmark
# vLLM 自带的基准测试工具
python -m vllm.entrypoints.openai.api_server_benchmark \
--model Qwen/Qwen2.5-7B-Instruct \
--dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
--num-prompts 1000 \
--request-rate 10 \
--backend openai-chat
10.2.2 自定义基准测试
# benchmark.py
"""vLLM 性能基准测试"""
import time
import json
import statistics
import concurrent.futures
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
def benchmark_throughput(prompts: list[str], model: str, max_tokens: int = 128):
"""测试吞吐量"""
ttfts = [] # Time to First Token
e2e_latencies = [] # End-to-end latency
total_tokens = 0
def send_request(prompt: str) -> dict:
start = time.time()
first_token_time = None
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
stream=True,
)
tokens = []
for chunk in stream:
if first_token_time is None:
first_token_time = time.time()
if chunk.choices and chunk.choices[0].delta.content:
tokens.append(chunk.choices[0].delta.content)
end = time.time()
return {
"ttft": first_token_time - start if first_token_time else None,
"e2e": end - start,
"tokens": len(tokens),
}
# 并发发送所有请求
start_time = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
futures = [executor.submit(send_request, p) for p in prompts]
results = [f.result() for f in concurrent.futures.as_completed(futures)]
total_time = time.time() - start_time
# 统计
for r in results:
if r["ttft"]:
ttfts.append(r["ttft"])
e2e_latencies.append(r["e2e"])
total_tokens += r["tokens"]
print(f"=== 基准测试结果 ===")
print(f"请求数: {len(prompts)}")
print(f"总耗时: {total_time:.2f}s")
print(f"总 tokens: {total_tokens}")
print(f"吞吐量: {total_tokens / total_time:.1f} tokens/s")
print(f"TTFT - avg: {statistics.mean(ttfts)*1000:.0f}ms, "
f"p50: {statistics.median(ttfts)*1000:.0f}ms, "
f"p99: {sorted(ttfts)[int(len(ttfts)*0.99)]*1000:.0f}ms")
print(f"E2E - avg: {statistics.mean(e2e_latencies)*1000:.0f}ms, "
f"p50: {statistics.median(e2e_latencies)*1000:.0f}ms, "
f"p99: {sorted(e2e_latencies)[int(len(e2e_latencies)*0.99)]*1000:.0f}ms")
if __name__ == "__main__":
# 准备测试数据(模拟不同长度的 prompt)
prompts = [
f"用 {50 + i*10} 个字解释人工智能的未来发展趋势。"
for i in range(100)
]
benchmark_throughput(prompts, "qwen-7b")
10.2.3 ShareGPT 数据集测试
# benchmark_sharegpt.py
"""使用 ShareGPT 数据集进行基准测试"""
import json
def load_sharegpt(path: str, max_prompts: int = 1000):
"""加载 ShareGPT 数据集"""
with open(path) as f:
data = json.load(f)
prompts = []
for conv in data["conversations"][:max_prompts]:
for turn in conv["conversations"]:
if turn["from"] == "human":
prompts.append(turn["value"])
break
return prompts
prompts = load_sharegpt("ShareGPT_V3_unfiltered_cleaned_split.json")
benchmark_throughput(prompts, "qwen-7b")
10.3 关键参数调优
10.3.1 GPU 显存利用率
# gpu_memory_utilization 调优
# 太低(0.7):浪费显存,KV Cache 空间少
llm = LLM(model="model", gpu_memory_utilization=0.7)
# KV Cache 块数: ~3000 → 并发数低
# 推荐(0.9):平衡安全和性能
llm = LLM(model="model", gpu_memory_utilization=0.9)
# KV Cache 块数: ~4500 → 并发数高
# 极限(0.95):最大 KV Cache,有 OOM 风险
llm = LLM(model="model", gpu_memory_utilization=0.95)
# KV Cache 块数: ~5000 → 最大并发,但有风险
10.3.2 最大序列长度
# max_model_len 调优
# 全长度(32K):每个请求分配更多 KV Cache 块
llm = LLM(model="model", max_model_len=32768)
# 并发数: ~30
# 中等长度(8K):平衡
llm = LLM(model="model", max_model_len=8192)
# 并发数: ~120
# 短长度(2K):最大化并发
llm = LLM(model="model", max_model_len=2048)
# 并发数: ~500
10.3.3 批处理参数
llm = LLM(
model="model",
# 最大并发序列数
# 增大 → 更多并发,但每步计算更慢
max_num_seqs=256,
# 最大批处理 token 数
# 增大 → 更高吞吐,但每步更慢
max_num_batched_tokens=8192,
# Swap 空间(GB)
# 增大 → 更多可 swap 的序列,但 CPU 内存占用增加
swap_space=4,
)
10.3.4 参数调优矩阵
| 场景 | gpu_memory_utilization | max_model_len | max_num_seqs | max_num_batched_tokens |
|---|---|---|---|---|
| 高吞吐离线 | 0.95 | 2048 | 512 | 16384 |
| 平衡在线 | 0.90 | 4096 | 128 | 8192 |
| 低延迟实时 | 0.85 | 2048 | 32 | 4096 |
| 长上下文 | 0.92 | 32768 | 16 | 8192 |
10.4 缓存策略
10.4.1 前缀缓存(Prefix Caching)
# 启用前缀缓存(对共享 system prompt 场景效果显著)
vllm serve model --enable-prefix-caching
效果示例:
场景:100 个请求共享相同的系统提示词(500 tokens)
无前缀缓存:
Prefill 总 tokens = 500 × 100 = 50,000
有前缀缓存:
首次 Prefill = 500 + 用户问题
后续 Prefill = 0(缓存命中)+ 用户问题
Prefill 总 tokens ≈ 500 + 用户问题 × 100 ≈ 5,000
TTFT 改善: 90%↓
10.4.2 CUDA Graph 优化
# CUDA Graph 默认启用,可显著减少 kernel launch 开销
# 如遇兼容性问题,可禁用
vllm serve model --enforce-eager # 禁用 CUDA Graph(性能下降 10-30%)
10.4.3 KV Cache 量化
# FP8 KV Cache(需要 H100/H200)
vllm serve model --kv-cache-dtype fp8
# 效果:KV Cache 内存减半,可支持约 2x 的并发数
# 代价:极小的精度损失
10.5 模型优化
10.5.1 数据类型选择
# dtype 对性能的影响
llm = LLM(model="model", dtype="auto") # 自动选择
llm = LLM(model="model", dtype="float16") # FP16
llm = LLM(model="model", dtype="bfloat16") # BF16(A100/H100 推荐)
| dtype | A100 | H100 | RTX 4090 | 显存 |
|---|---|---|---|---|
| float16 | ✅ 快 | ✅ 快 | ✅ 快 | 2x |
| bfloat16 | ✅ 最快 | ✅ 最快 | ✅ | 2x |
| float32 | ✅ 慢 | ✅ 慢 | ✅ 慢 | 4x |
10.5.2 量化权衡
# 量化方案性能对比
# FP16(基线)
llm = LLM(model="Qwen2.5-7B", dtype="float16")
# 吞吐量: 100%, 精度: 最高
# AWQ 4-bit
llm = LLM(model="Qwen2.5-7B-AWQ", quantization="awq")
# 吞吐量: 90-110%, 精度: 较高, 显存: -75%
# FP8
llm = LLM(model="Qwen2.5-7B", quantization="fp8")
# 吞吐量: 95-100%, 精度: 极高, 显存: -50%
10.6 系统级优化
10.6.1 操作系统优化
# 1. 关闭透明大页(THP)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 2. 调整文件描述符限制
ulimit -n 65535
# 3. 调整共享内存
sudo mount -o remount,size=32G /dev/shm
# 4. GPU 持久化模式
sudo nvidia-smi -pm 1
# 5. 设置 CPU 性能模式
sudo cpupower frequency-set -g performance
# 6. 绑定 NUMA 节点
numactl --cpunodebind=0 --membind=0 vllm serve model
10.6.2 NVIDIA 驱动优化
# 设置计算模式
sudo nvidia-smi -c EXCLUSIVE_PROCESS
# 设置功率限制(平衡性能和功耗)
sudo nvidia-smi -pl 350 # 350W for A100
# 启用 MIG(Multi-Instance GPU,仅 A100/H100)
# 将 1 个 A100 切分为多个独立实例
sudo nvidia-smi mig -cgi 9,9,9,9 -C
10.6.3 Python 环境优化
# 设置线程数
export OMP_NUM_THREADS=8
export MKL_NUM_THREADS=8
# 禁用 Python GC(减少 GC 暂停)
export PYTHONDONTWRITEBYTECODE=1
# 使用 UVLoop(更快的事件循环)
pip install uvloop
10.7 吞吐量优化清单
10.7.1 快速优化
□ 使用 BF16(A100/H100)
□ 启用前缀缓存(如果有共享 prompt)
□ 使用量化模型(AWQ/FP8)
□ 减小 max_model_len 到实际需要的长度
□ 增大 gpu_memory_utilization 到 0.9-0.95
□ 使用 NVLink 连接的 GPU 进行张量并行
□ 启用 Chunked Prefill(长 prompt 场景)
10.7.2 深度优化
□ 调整 max_num_seqs 和 max_num_batched_tokens
□ 使用 FP8 KV Cache
□ 调整 swap_space
□ 系统级 NUMA 绑定
□ 使用 CUDA Graph(默认启用)
□ 调整 NCCL 通信参数(分布式场景)
□ 使用 Ray 进行多节点调度
10.8 延迟优化
10.8.1 TTFT 优化
# 减少首 token 延迟
# 1. 启用 Chunked Prefill
llm = LLM(
model="model",
enable_chunked_prefill=True,
max_num_batched_tokens=512, # 较小的 chunk
)
# 2. 减小 prompt 长度(使用更简洁的系统提示)
# 3. 使用前缀缓存(避免重复 prefill)
# 4. 限制 max_num_seqs(减少批大小对延迟的影响)
10.8.2 TPOT 优化
# 减少每 token 延迟
# 1. 减少批大小(降低每步计算量)
# 2. 使用量化(减少计算量)
# 3. 使用张量并行(分摊计算)
10.9 性能对比参考
10.9.1 典型吞吐量数据
| 模型 | GPU | TP | 吞吐量 (tok/s) | 并发数 |
|---|---|---|---|---|
| Qwen2.5-7B | 1x A100 | 1 | 1,500-2,000 | 50 |
| Qwen2.5-14B | 1x A100 | 1 | 800-1,200 | 30 |
| Qwen2.5-72B | 4x A100 | 4 | 500-800 | 20 |
| LLaMA-3.1-70B | 4x A100 | 4 | 400-700 | 20 |
| LLaMA-3.1-70B-AWQ | 2x A100 | 2 | 300-500 | 40 |
| LLaMA-3.1-405B | 8x H100 | 8 | 200-400 | 10 |
注:以上数据为参考值,实际性能取决于 prompt 长度、生成长度、并发数等因素。
10.10 注意事项
过犹不及:
gpu_memory_utilization设得过高(如 0.98)可能导致 OOM。建议留 5-10% 的余量。
基准测试:不要凭经验猜测,用实际工作负载进行基准测试。不同 prompt 分布的性能差异很大。
监控先行:在调优之前先建立监控(参见第 11 章),否则无法量化改善效果。
逐步调优:一次只改变一个参数,观察效果后再继续。
10.11 扩展阅读
上一章:09 - 分布式推理 | 下一章:11 - 监控与可观测性