目录
- 引言
- vLLM 安装与配置
- PagedAttention 实战
- 并发请求测试
- 性能 Benchmark
- 参数调优
- 生产部署最佳实践
- 常见问题排查
引言
vLLM 是目前最流行、最易用的 LLM 推理引擎,以其出色的性能和简洁的 API 赢得了广泛采用。自 2023 年发布以来,vLLM 迅速成为开源社区的首选,GitHub 星标数快速增长,被众多企业和研究机构采用。
掌握 vLLM 部署与性能测试是 LLM 生产化的关键技能。无论你是在创业公司搭建第一个 AI 服务,还是在大企业优化现有系统,vLLM 都是一个值得掌握的工具。它的学习曲线平缓,文档完善,社区活跃,遇到问题很容易找到解决方案。
- 如何快速部署 vLLM? pip 安装即可使用
- PagedAttention 实际效果如何? 显存效率提升 10x+
- 如何测试并发性能? 基准测试工具详解
- 如何调优获得最佳性能? 关键参数配置指南
- 生产部署需要注意什么? 最佳实践总结
这些问题都指向一个核心主题:vLLM 部署与性能测试。
vLLM 的核心优势
┌─────────────────────────────────────────────────┐
│ vLLM 的核心优势 │
├─────────────────────────────────────────────────┤
│ │
│ 性能卓越: │
│ ├── 吞吐量:比 HuggingFace 高 10-20x │
│ ├── 显存效率:PagedAttention 提升 10-20x │
│ ├── 延迟:与原生推理相当 │
│ └── 并发:支持 10x 更多并发请求 │
│ │
│ 易用性高: │
│ ├── 安装简单:pip install vllm │
│ ├── API 兼容:OpenAI API 无缝切换 │
│ ├── 模型丰富:支持 50+ 主流模型 │
│ └── 文档完善:快速上手 │
│ │
│ 生产就绪: │
│ ├── 分布式:多 GPU 张量并行 │
│ ├── 量化:AWQ/GPTQ 支持 │
│ ├── 监控:Prometheus 指标 │
│ └── 高可用:多实例负载均衡 │
│ │
└─────────────────────────────────────────────────┘
vLLM 安装与配置
了解了 vLLM 的优势后,让我们开始实际部署。vLLM 的安装非常简便,支持多种部署方式:pip 直接安装、Docker 容器部署、以及源码编译。对于大多数用户,我们推荐 pip 安装或 Docker 部署,这两种方式最简单快捷。
在开始安装之前,请确保你的系统满足基本要求:Linux 操作系统(Ubuntu 20.04+ 推荐),Python 3.8+,CUDA 12.1+(GPU 部署),以及足够的显存(7B 模型至少需要 16GB 显存)。
基础安装
下面的安装脚本展示了完整的 pip 安装流程。我们推荐使用 Python 虚拟环境,这样可以避免与其他 Python 项目产生依赖冲突。脚本首先创建虚拟环境,然后安装 vLLM 及其依赖,最后验证安装是否成功。
安装过程中需要注意几点:CUDA 版本需要与你的 NVIDIA 驱动兼容,建议使用 CUDA 12.1 或更高版本。如果你的 GPU 较老,可能需要使用特定版本的 vLLM。安装完成后,可以通过导入 vLLM 模块并检查 GPU 状态来验证安装是否成功。
echo "=========================================="
echo " vLLM 安装"
echo "=========================================="
# 1. 创建虚拟环境
echo ""
echo "[1/4] 创建 Python 虚拟环境..."
python3 -m venv /opt/vllm-env
source /opt/vllm-env/bin/activate
# 2. 安装 vLLM
echo ""
echo "[2/4] 安装 vLLM..."
# CUDA 12.1+
pip install vllm
# 或者指定版本
# pip install vllm==0.4.0
# 3. 安装依赖
echo ""
echo "[3/4] 安装依赖..."
pip install openai requests torch torchvision
# 4. 验证安装
echo ""
echo "[4/4] 验证安装..."
python3 -c "
import vllm
import torch
print(f'vLLM 版本:{vllm.__version__}')
print(f'PyTorch 版本:{torch.__version__}')
print(f'CUDA 版本:{torch.version.cuda}')
print(f'GPU 可用:{torch.cuda.is_available()}')
if torch.cuda.is_available():
print(f'GPU 型号:{torch.cuda.get_device_name(0)}')
print(f'GPU 显存:{torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB')
"
echo ""
echo "=========================================="
echo " vLLM 安装完成"
echo "=========================================="
echo ""
echo "激活环境:source /opt/vllm-env/bin/activate"
Docker 部署
对于生产环境,我们更推荐使用 Docker 部署。Docker 提供了更好的隔离性和可重复性,可以避免环境依赖问题,也便于版本管理和回滚。
Docker 部署的关键配置包括:GPU 数量(通过--gpus 参数指定)、模型名称(从 HuggingFace 加载)、端口映射(默认 8000)、以及共享内存大小(--shm-size,建议 16G 以上)。容器启动后,vLLM 会自动下载模型并启动服务。
使用 Docker 部署的好处还包括:可以轻松扩展到多容器部署,便于与 Kubernetes 等编排工具集成,以及方便的环境迁移。下面的脚本展示了完整的 Docker 部署流程。
echo "=========================================="
echo " vLLM Docker 部署"
echo "=========================================="
# 配置
MODEL_NAME=${MODEL_NAME:-"meta-llama/Llama-2-7b-chat-hf"}
GPU_COUNT=${GPU_COUNT:-1}
PORT=${PORT:-8000}
echo ""
echo "部署配置:"
echo " 模型:$MODEL_NAME"
echo " GPU 数量:$GPU_COUNT"
echo " 端口:$PORT"
echo ""
# 运行容器
docker run --runtime nvidia --gpus $GPU_COUNT \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p $PORT:8000 \
--name vllm-server \
--shm-size 16G \
vllm/vllm-openai:latest \
--model $MODEL_NAME \
--tensor-parallel-size $GPU_COUNT \
--max-num-seqs 256 \
--gpu-memory-utilization 0.9
echo ""
echo "=========================================="
echo " vLLM 服务已启动"
echo "=========================================="
echo ""
echo "API 端点:http://localhost:$PORT/v1"
echo "健康检查:curl http://localhost:$PORT/health"
echo "停止服务:docker stop vllm-server"
服务启动配置
无论是 pip 安装还是 Docker 部署,最终都需要启动 vLLM 的 API 服务。服务启动时有很多可配置参数,理解这些参数的含义对于优化性能至关重要。
模型配置方面,需要指定模型名称(从 HuggingFace 加载)、主机地址和端口。性能配置包括:张量并行数(多 GPU 时使用)、最大并发请求数(max-num-seqs)、最大序列长度、以及 GPU 显存利用率。量化配置是可选的,可以指定 AWQ、GPTQ 或 FP8 等量化方法。
关键参数的选择需要根据你的硬件和需求来决定。例如,max-num-seqs 决定了最大并发请求数,调高可以提升吞吐量但会增加延迟。gpu-memory-utilization 决定了 vLLM 可以使用多少比例的显存,通常设置为 0.9 左右,留出一些余量避免 OOM。
echo "=========================================="
echo " 启动 vLLM API 服务"
echo "=========================================="
# 模型配置
MODEL=${MODEL:-"Qwen/Qwen2.5-7B-Instruct"}
HOST=${HOST:-"0.0.0.0"}
PORT=${PORT:-8000}
# 性能配置
TENSOR_PARALLEL=${TENSOR_PARALLEL:-1} # GPU 数量
MAX_NUM_SEQS=${MAX_NUM_SEQS:-256} # 最大并发请求
MAX_MODEL_LEN=${MAX_MODEL_LEN:-4096} # 最大序列长度
GPU_MEM_UTIL=${GPU_MEM_UTIL:-0.9} # GPU 显存利用率
# 量化配置 (可选)
QUANTIZATION=${QUANTIZATION:-""} # awq/gptq/fp8
echo ""
echo "启动配置:"
echo " 模型:$MODEL"
echo " 主机:$HOST:$PORT"
echo " 张量并行:$TENSOR_PARALLEL"
echo " 最大并发:$MAX_NUM_SEQS"
echo " 最大长度:$MAX_MODEL_LEN"
echo " 显存利用:$GPU_MEM_UTIL"
echo ""
# 启动服务
python3 -m vllm.entrypoints.openai.api_server \
--model $MODEL \
--host $HOST \
--port $PORT \
--tensor-parallel-size $TENSOR_PARALLEL \
--max-num-seqs $MAX_NUM_SEQS \
--max-model-len $MAX_MODEL_LEN \
--gpu-memory-utilization $GPU_MEM_UTIL \
${QUANTIZATION:+--quantization $QUANTIZATION} \
--served-model-name $(basename $MODEL) \
--enable-chunked-prefill \
--disable-log-requests
echo ""
echo "=========================================="
配置文件示例
# vllm_config.yaml - vLLM 配置文件
# 模型配置
model:
name: "Qwen/Qwen2.5-7B-Instruct"
trust_remote_code: true
revision: "main"
# 性能配置
performance:
tensor_parallel_size: 1 # GPU 数量
max_num_seqs: 256 # 最大并发请求
max_model_len: 4096 # 最大序列长度
gpu_memory_utilization: 0.9 # GPU 显存利用率
swap_space: 4 # CPU 交换空间 (GB)
# 批处理配置
batching:
enable_chunked_prefill: true # 启用分块预填充
max_num_batched_tokens: 2560 # 最大批处理 tokens
# 量化配置
quantization:
enabled: false
method: "awq" # awq/gptq/fp8
# API 配置
api:
host: "0.0.0.0"
port: 8000
api_key: "" # API 密钥 (可选)
# 日志配置
logging:
level: "INFO"
disable_log_requests: false
# 监控配置
monitoring:
enable_metrics: true
metrics_port: 9090
PagedAttention 实战
PagedAttention 是 vLLM 的核心技术,但它的效果到底如何?让我们通过实际测试来验证。下面的代码展示了如何测试 PagedAttention 的显存效率,以及如何可视化 Block 分配情况。
理解 PagedAttention 的实际效果对于调优和故障排查都非常有帮助。通过监控显存使用情况,你可以更好地配置参数,避免 OOM 问题,同时最大化并发能力。
显存效率测试
显存效率测试脚本首先创建一个 LLM 实例,然后测试不同并发数下的显存占用情况。通过观察显存随并发数的增长趋势,你可以了解 PagedAttention 的实际效果。
测试的关键指标包括:总显存、已分配显存、已预留显存、以及利用率。理想情况下,随着并发数增加,显存应该线性增长,而不是指数增长,这证明 PagedAttention 在高效管理显存。
每请求显存占用是另一个重要指标。通过计算每个请求平均占用的显存,你可以评估当前配置是否合理,以及是否还有优化空间。
import torch
from vllm import LLM, SamplingParams
def test_memory_efficiency():
"""测试 PagedAttention 显存效率"""
print("="*60)
print("PagedAttention 显存效率测试")
print("="*60)
# 创建 LLM 实例
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.9,
max_num_seqs=256,
max_model_len=4096
)
# 获取显存信息
total_memory = torch.cuda.get_device_properties(0).total_memory
allocated_memory = torch.cuda.memory_allocated()
reserved_memory = torch.cuda.memory_reserved()
print(f"\nGPU 显存信息:")
print(f" 总显存:{total_memory / 1e9:.2f} GB")
print(f" 已分配:{allocated_memory / 1e9:.2f} GB")
print(f" 已预留:{reserved_memory / 1e9:.2f} GB")
print(f" 利用率:{reserved_memory / total_memory * 100:.1f}%")
# 测试不同并发数的显存占用
print(f"\n并发请求显存占用测试:")
print("-"*60)
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=100,
top_p=0.9
)
prompts = [
"请介绍一下人工智能。",
"什么是机器学习?",
"深度学习和机器学习有什么区别?",
] * 10 # 30 个请求
for num_requests in [1, 5, 10, 20, 30]:
# 清理显存
torch.cuda.empty_cache()
# 运行推理
current_prompts = prompts[:num_requests]
outputs = llm.generate(current_prompts, sampling_params)
# 获取显存
allocated = torch.cuda.memory_allocated()
per_request = allocated / num_requests
print(f" {num_requests:2d} 并发:{allocated/1e9:.2f} GB "
f"(每请求:{per_request/1e6:.1f} MB)")
print("="*60)
if __name__ == "__main__":
test_memory_efficiency()
Block Table 可视化
#!/usr/bin/env python3
# visualize_block_table.py - Block Table 可视化
from vllm import LLM, SamplingParams
def visualize_block_allocation():
"""可视化 Block 分配"""
print("="*60)
print("PagedAttention Block 分配可视化")
print("="*60)
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.5, # 限制显存便于观察
max_num_seqs=10,
max_model_len=1024,
block_size=16 # 每个 block 16 tokens
)
# 获取缓存配置
cache_config = llm.llm_engine.cache_config
num_gpu_blocks = cache_config.num_gpu_blocks
num_cpu_blocks = cache_config.num_cpu_blocks
block_size = cache_config.block_size
print(f"\n缓存配置:")
print(f" Block 大小:{block_size} tokens")
print(f" GPU Blocks: {num_gpu_blocks}")
print(f" CPU Blocks: {num_cpu_blocks}")
print(f" 总缓存容量:{num_gpu_blocks * block_size} tokens")
# 运行一些请求
sampling_params = SamplingParams(
temperature=0,
max_tokens=50
)
prompts = [
f"请用 50 个字解释第{i}个概念。"
for i in range(5)
]
print(f"\n运行 {len(prompts)} 个请求...")
outputs = llm.generate(prompts, sampling_params)
# 获取调度器状态
scheduler = llm.llm_engine.scheduler
print(f"\n调度器状态:")
print(f" 等待队列:{len(scheduler.waiting)}")
print(f" 运行中:{len(scheduler.running)}")
print(f" 交换中:{len(scheduler.swapped)}")
print("\n" + "="*60)
if __name__ == "__main__":
visualize_block_allocation()
并发请求测试
部署好 vLLM 后,下一步是测试其性能表现。并发请求测试是评估推理服务性能的关键环节,它可以帮助你了解服务在不同负载下的表现,发现性能瓶颈,并为容量规划提供依据。
基准测试主要关注几个核心指标:延迟(从发送请求到收到响应的时间)、吞吐量(单位时间内处理的 token 数)、以及并发能力(同时处理的请求数)。通过测试不同并发度下的这些指标,你可以找到最佳配置点。
基准测试工具
下面的基准测试工具使用 Python 的 asyncio 库实现并发请求发送,通过 OpenAI API 兼容接口与 vLLM 通信。测试器支持自定义并发度、请求数量、以及生成 token 数。
测试流程是:首先准备测试提示,然后以指定并发度发送请求,记录每个请求的延迟和生成 token 数,最后统计各项指标。通过逐步增加并发度,可以观察到吞吐量如何随并发度变化,以及延迟如何增长。
关键指标包括:平均延迟、P95 延迟(95% 请求的延迟上限)、P99 延迟、以及吞吐量(tokens/s)。这些指标对于评估服务性能和 SLA 承诺都非常重要。
import asyncio
import time
import statistics
from typing import List, Tuple
from openai import AsyncOpenAI
import aiohttp
class VLLMBenchmark:
"""vLLM 基准测试器"""
def __init__(self, base_url: str = "http://localhost:8000"):
self.client = AsyncOpenAI(
base_url=base_url,
api_key="not-needed"
)
self.results = []
async def send_request(self, prompt: str, max_tokens: int = 100) -> dict:
"""发送单个请求"""
start_time = time.perf_counter()
response = await self.client.chat.completions.create(
model="default",
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
temperature=0.7
)
end_time = time.perf_counter()
# 计算指标
latency = end_time - start_time
output_tokens = len(response.choices[0].message.content.split())
tokens_per_sec = output_tokens / latency if latency > 0 else 0
return {
'latency_ms': latency * 1000,
'output_tokens': output_tokens,
'tokens_per_sec': tokens_per_sec,
'prompt_len': len(prompt),
}
async def benchmark_concurrent(
self,
prompts: List[str],
concurrency: int,
max_tokens: int = 100
) -> dict:
"""并发基准测试"""
print(f"\n并发测试:{concurrency} 并发,{len(prompts)} 请求")
print("-"*60)
# 创建信号量控制并发
semaphore = asyncio.Semaphore(concurrency)
async def limited_request(prompt):
async with semaphore:
return await self.send_request(prompt, max_tokens)
# 运行测试
start_time = time.perf_counter()
tasks = [limited_request(prompt) for prompt in prompts]
results = await asyncio.gather(*tasks)
total_time = time.perf_counter() - start_time
# 统计结果
latencies = [r['latency_ms'] for r in results]
tokens = [r['output_tokens'] for r in results]
stats = {
'concurrency': concurrency,
'total_requests': len(prompts),
'total_time_sec': total_time,
'requests_per_sec': len(prompts) / total_time,
'avg_latency_ms': statistics.mean(latencies),
'median_latency_ms': statistics.median(latencies),
'p95_latency_ms': sorted(latencies)[int(len(latencies) * 0.95)],
'p99_latency_ms': sorted(latencies)[int(len(latencies) * 0.99)],
'avg_tokens': statistics.mean(tokens),
'total_tokens': sum(tokens),
'throughput_tokens_per_sec': sum(tokens) / total_time,
}
self.results.append(stats)
return stats
def print_report(self):
"""打印测试报告"""
print("\n" + "="*80)
print("vLLM 基准测试报告")
print("="*80)
print()
print(f"{'并发数':<10} {'请求数':<10} {'QPS':<12} "
f"{'P50 延迟':<12} {'P95 延迟':<12} {'P99 延迟':<12} {'吞吐 (tok/s)':<12}")
print("-"*80)
for stats in self.results:
print(f"{stats['concurrency']:<10} {stats['total_requests']:<10} "
f"{stats['requests_per_sec']:<12.1f} "
f"{stats['median_latency_ms']:<12.1f} "
f"{stats['p95_latency_ms']:<12.1f} "
f"{stats['p99_latency_ms']:<12.1f} "
f"{stats['throughput_tokens_per_sec']:<12.1f}")
print("="*80)
async def main():
benchmark = VLLMBenchmark("http://localhost:8000")
# 准备测试数据
prompts = [
"请介绍一下人工智能的基本概念。",
"机器学习有哪些主要类型?",
"深度学习与传统机器学习有什么区别?",
"什么是神经网络?",
"Transformer 架构的核心思想是什么?",
] * 20 # 100 个请求
# 测试不同并发度
for concurrency in [1, 2, 4, 8, 16, 32, 64]:
stats = await benchmark.benchmark_concurrent(
prompts,
concurrency=concurrency,
max_tokens=100
)
benchmark.print_report()
if __name__ == "__main__":
asyncio.run(main())
负载测试脚本
#!/bin/bash
# vllm_load_test.sh - vLLM 负载测试
echo "=========================================="
echo " vLLM 负载测试"
echo "=========================================="
BASE_URL=${BASE_URL:-"http://localhost:8000"}
NUM_REQUESTS=${NUM_REQUESTS:-100}
CONCURRENCY=${CONCURRENCY:-32}
echo ""
echo "测试配置:"
echo " 服务地址:$BASE_URL"
echo " 请求数量:$NUM_REQUESTS"
echo " 并发度:$CONCURRENCY"
echo ""
# 使用 locust 进行负载测试 (需要安装:pip install locust)
cat << 'EOF' > locustfile.py
from locust import HttpUser, task, between
import random
class VLLMUser(HttpUser):
wait_time = between(0.1, 0.5)
@task
def chat_completion(self):
prompts = [
"请介绍一下人工智能。",
"什么是机器学习?",
"深度学习有什么应用?",
"Transformer 是什么?",
"大语言模型有哪些?",
]
self.client.post(
"/v1/chat/completions",
json={
"model": "default",
"messages": [{"role": "user", "content": random.choice(prompts)}],
"max_tokens": 100
},
name="/v1/chat/completions"
)
EOF
echo "启动 Locust 负载测试..."
locust -f locustfile.py \
--host $BASE_URL \
--headless \
--users $CONCURRENCY \
--spawn-rate 2 \
--run-time 60s \
--html report.html
echo ""
echo "=========================================="
echo " 负载测试完成"
echo "=========================================="
echo ""
echo "报告:report.html"
性能 Benchmark
基准测试工具帮助你测试并发性能,而完整的性能 Benchmark 则提供更全面的性能评估。这包括吞吐量测试(不同 batch size 下的性能)和延迟测试(不同输入长度下的表现)。
性能测试的目的是建立性能基线,了解你的配置能达到的最佳性能,以及在不同负载下的表现。这对于容量规划、SLA 承诺、以及性能调优都至关重要。
完整性能测试
吞吐量测试通过改变 batch size 来观察性能变化。通常,随着 batch size 增加,吞吐量会提升,但延迟也会增加。找到吞吐量和延迟的平衡点是调优的关键。
延迟测试则关注不同输入长度下的表现。输入越长,首 token 延迟通常越高,因为需要处理更多上下文。了解这种关系对于设置合理的超时和预期管理很重要。
测试结果应该与官方参考值对比,如果性能明显低于预期,可能存在配置问题或硬件瓶颈,需要进一步排查。
import time
import torch
from vllm import LLM, SamplingParams
from vllm.engine.arg_utils import EngineArgs
def test_throughput():
"""吞吐量测试"""
print("="*70)
print("vLLM 吞吐量测试")
print("="*70)
# 创建 LLM 实例
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.9,
max_num_seqs=256,
max_model_len=2048,
tensor_parallel_size=1
)
# 测试不同 batch size
batch_sizes = [1, 4, 8, 16, 32, 64, 128, 256]
print(f"\n{'Batch Size':<12} {'Tokens/s':<15} {'Latency (ms)':<15} {'GPU Mem (GB)':<15}")
print("-"*70)
for batch_size in batch_sizes:
prompts = ["介绍一下人工智能。"] * batch_size
sampling_params = SamplingParams(
temperature=0,
max_tokens=100,
top_p=1.0
)
# 运行
start = time.perf_counter()
outputs = llm.generate(prompts, sampling_params)
elapsed = time.perf_counter() - start
# 计算指标
total_tokens = sum(len(out.outputs[0].text.split()) for out in outputs)
tokens_per_sec = total_tokens / elapsed
avg_latency = elapsed * 1000 / batch_size
gpu_mem = torch.cuda.memory_allocated() / 1e9
print(f"{batch_size:<12} {tokens_per_sec:<15.1f} "
f"{avg_latency:<15.1f} {gpu_mem:<15.2f}")
print("="*70)
def test_latency():
"""延迟测试"""
print("\n" + "="*70)
print("vLLM 延迟测试")
print("="*70)
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.9,
max_num_seqs=1
)
# 测试不同输入长度
input_lengths = [32, 64, 128, 256, 512, 1024]
print(f"\n{'输入长度':<12} {'首 token 延迟':<15} {'总延迟':<15} {'解码速度':<15}")
print("-"*70)
for input_len in input_lengths:
prompt = "你好," * (input_len // 2)
sampling_params = SamplingParams(
temperature=0,
max_tokens=100
)
# 运行
start = time.perf_counter()
outputs = llm.generate(prompt, sampling_params)
elapsed = time.perf_counter() - start
# 计算指标
output_tokens = len(outputs[0].outputs[0].text.split())
first_token_latency = outputs[0].metrics.first_token_time * 1000
decode_tokens_per_sec = output_tokens / (elapsed - outputs[0].metrics.first_token_time)
print(f"{input_len:<12} {first_token_latency:<15.1f} "
f"{elapsed*1000:<15.1f} {decode_tokens_per_sec:<15.1f}")
print("="*70)
if __name__ == "__main__":
test_throughput()
test_latency()
性能参考值
┌────────────────────────────────────────────────────────────────────┐
│ vLLM 性能参考 (Qwen2.5-7B, A100) │
├──────────────────┬─────────────┬─────────────┬─────────────────────┤
│ 配置 │ 吞吐量 │ 延迟 │ 显存占用 │
│ │ (tok/s) │ (ms) │ (GB) │
├──────────────────┼─────────────┼─────────────┼─────────────────────┤
│ batch=1 │ 80-100 │ 50-70 │ 14-16 │
│ batch=8 │ 400-500 │ 80-100 │ 16-18 │
│ batch=32 │ 900-1100 │ 120-150 │ 18-20 │
│ batch=128 │ 1400-1600 │ 200-250 │ 22-26 │
│ batch=256 │ 1600-1800 │ 300-400 │ 28-32 │
└──────────────────┴─────────────┴─────────────┴─────────────────────┘
注:测试条件 A100 80GB,FP16,max_tokens=100,实际性能受配置影响
参数调优
vLLM 提供了丰富的配置参数,理解这些参数的含义和影响是性能调优的关键。正确的参数配置可以让你的服务性能提升数倍,而错误的配置则可能导致性能低下甚至 OOM。
参数调优的核心思路是:根据你的应用场景(高吞吐还是低延迟)、硬件条件(显存大小、GPU 数量)、以及业务需求(最大并发、最长序列)来选择合适的参数组合。
关键参数说明
显存相关参数是最需要关注的。gpu_memory_utilization 决定了 vLLM 可以使用多少比例的 GPU 显存,通常设置为 0.9 左右。swap_space 是 CPU 交换空间大小,当显存不足时可以使用,但会降低速度。max_model_len 是最大序列长度,调高可以处理更长文本,但会增加显存占用。
批处理相关参数影响吞吐量和延迟的平衡。max_num_seqs 是最大并发请求数,调高提升吞吐量但增加延迟。max_num_batched_tokens 是最大批处理 token 数,同样影响吞吐和延迟的平衡。enable_chunked_prefill 启用分块预填充,提升显存效率但可能略微增加延迟。
并行相关参数用于多 GPU 部署。tensor_parallel_size 是 GPU 张量并行数,用于将大模型拆分到多张 GPU 上。pipeline_parallel_size 是流水线并行数,适合超大模型。
├─────────────────────────────────────────────────┤
│ │
│ 显存相关: │
│ ├── gpu_memory_utilization (0.5-0.95) │
│ │ └── GPU 显存使用比例,默认 0.9 │
│ │ └── 调高:支持更多并发,但可能 OOM │
│ │ └── 调低:更稳定,但并发能力下降 │
│ │ │
│ ├── swap_space (0-8) │
│ │ └── CPU 交换空间大小 (GB) │
│ │ └── 调高:支持更长序列,但速度变慢 │
│ │ └── 调低:速度更快,但可能 OOM │
│ │ │
│ └── max_model_len (1024-32768) │
│ └── 最大序列长度 │
│ └── 调高:支持长文本,但显存占用增加 │
│ └── 调低:节省显存,但无法处理长文本 │
│ │
│ 批处理相关: │
│ ├── max_num_seqs (1-256) │
│ │ └── 最大并发请求数 │
│ │ └── 调高:吞吐量提升,但延迟增加 │
│ │ └── 调低:延迟降低,但吞吐量下降 │
│ │ │
│ ├── max_num_batched_tokens (1024-8192) │
│ │ └── 最大批处理 tokens 数 │
│ │ └── 调高:吞吐量提升 │
│ │ └── 调低:延迟降低 │
│ │ │
│ └── enable_chunked_prefill (true/false) │
│ └── 启用分块预填充 │
│ └── true:显存效率更高 │
│ └── false:延迟更低 │
│ │
│ 并行相关: │
│ ├── tensor_parallel_size (1-8) │
│ │ └── GPU 张量并行数 │
│ │ └── 调高:支持更大模型 │
│ │ └── 调低:单 GPU 部署 │
│ │ │
│ └── pipeline_parallel_size (1-4) │
│ └── GPU 流水线并行数 │
│ └── 调高:适合超大模型 │
│ └── 调低:延迟更低 │
│ │
└─────────────────────────────────────────────────┘
调优建议
理解了各参数的含义后,让我们看看针对不同场景的推荐配置。调优的核心是根据场景需求权衡吞吐量和延迟。
高吞吐场景(如离线处理、批量生成)的目标是最大化吞吐量,延迟是次要考虑。推荐配置:gpu_memory_utilization 设为 0.95(充分利用显存),max_num_seqs 设为 256(最大并发),max_num_batched_tokens 设为 4096(大批处理),enable_chunked_prefill 启用(提升显存效率)。
低延迟场景(如实时对话、交互式应用)的目标是最小化延迟,吞吐量是次要考虑。推荐配置:gpu_memory_utilization 设为 0.8(留有余量),max_num_seqs 设为 32(限制并发),max_num_batched_tokens 设为 2048(小批处理),enable_chunked_prefill 禁用(降低延迟)。
平衡场景(如通用 API 服务)需要兼顾吞吐量和延迟。推荐配置:gpu_memory_utilization 设为 0.9,max_num_seqs 设为 128,max_num_batched_tokens 设为 2560,enable_chunked_prefill 启用。
长文本场景(如文档处理、长文分析)需要支持长序列。推荐配置:gpu_memory_utilization 设为 0.85(为长序列留显存),max_model_len 设为 8192 或 16384,swap_space 设为 8(防止 OOM),max_num_seqs 设为 64(限制并发)。
多 GPU 场景(大规模部署)需要优化多卡效率。推荐配置:tensor_parallel_size 设为 GPU 数量,gpu_memory_utilization 设为 0.9,max_num_seqs 设为 256。
├─────────────────────────────────────────────────┤
│ │
│ 高吞吐场景 (离线处理): │
│ ├── gpu_memory_utilization: 0.95 │
│ ├── max_num_seqs: 256 │
│ ├── max_num_batched_tokens: 4096 │
│ ├── enable_chunked_prefill: true │
│ └── 目标:最大化吞吐量,延迟次要 │
│ │
│ 低延迟场景 (实时对话): │
│ ├── gpu_memory_utilization: 0.8 │
│ ├── max_num_seqs: 32 │
│ ├── max_num_batched_tokens: 2048 │
│ ├── enable_chunked_prefill: false │
│ └── 目标:最小化延迟,吞吐次要 │
│ │
│ 平衡场景 (通用 API 服务): │
│ ├── gpu_memory_utilization: 0.9 │
│ ├── max_num_seqs: 128 │
│ ├── max_num_batched_tokens: 2560 │
│ ├── enable_chunked_prefill: true │
│ └── 目标:平衡延迟和吞吐 │
│ │
│ 长文本场景 (文档处理): │
│ ├── gpu_memory_utilization: 0.85 │
│ ├── max_model_len: 8192 或 16384 │
│ ├── swap_space: 8 │
│ ├── max_num_seqs: 64 │
│ └── 目标:支持长序列,保证稳定性 │
│ │
│ 多 GPU 场景 (大规模部署): │
│ ├── tensor_parallel_size: GPU 数量 │
│ ├── gpu_memory_utilization: 0.9 │
│ ├── max_num_seqs: 256 │
│ └── 目标:最大化多 GPU 效率 │
│ │
└─────────────────────────────────────────────────┘
生产部署最佳实践
掌握了 vLLM 的部署和调优后,让我们看看如何在生产环境中部署。生产部署与测试环境不同,需要考虑高可用、监控、日志、安全等多个方面。
高可用部署是生产环境的基本要求。通过部署多个 vLLM 实例,配合负载均衡器,可以实现故障自动切换,确保服务持续可用。下面的 Docker Compose 配置展示了如何部署主备双实例,配合 Nginx 负载均衡。
高可用部署
生产环境部署的关键组件包括:vLLM 主实例和备用实例(自动故障切换)、Nginx 负载均衡器(分发请求到健康实例)、Prometheus(收集性能指标)、以及 Grafana(可视化监控)。
健康检查配置确保负载均衡器可以检测到实例故障并自动切换到备用实例。重启策略(restart: unless-stopped)确保容器崩溃后自动重启。监控配置帮助你在问题发生前发现异常,如显存使用率过高、延迟增长等。
version: '3.8'
services:
vllm-primary:
image: vllm/vllm-openai:latest
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-7B-Instruct
--tensor-parallel-size 1
--max-num-seqs 128
--gpu-memory-utilization 0.9
ports:
- "8001:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
vllm-backup:
image: vllm/vllm-openai:latest
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-7B-Instruct
--tensor-parallel-size 1
--max-num-seqs 128
--gpu-memory-utilization 0.9
ports:
- "8002:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- vllm-primary
- vllm-backup
restart: unless-stopped
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
restart: unless-stopped
volumes:
grafana-data:
监控配置
# prometheus.yml - Prometheus 监控配置
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'vllm'
static_configs:
- targets: ['vllm-primary:8000', 'vllm-backup:8000']
metrics_path: '/metrics'
日志收集
#!/usr/bin/env python3
# vllm_logging.py - vLLM 日志配置
import logging
import json
from datetime import datetime
class RequestLogger:
"""请求日志记录器"""
def __init__(self, log_file='vllm_requests.log'):
self.log_file = log_file
self.logger = logging.getLogger('vllm')
self.logger.setLevel(logging.INFO)
handler = logging.FileHandler(log_file)
formatter = logging.Formatter(
'%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
def log_request(self, prompt, response, latency_ms, tokens):
"""记录请求日志"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'prompt': prompt[:100], # 截断长 prompt
'response_length': len(response),
'latency_ms': round(latency_ms, 2),
'tokens': tokens
}
self.logger.info(json.dumps(log_entry))
# 使用示例
# logger = RequestLogger()
# logger.log_request(prompt, response, latency, tokens)
常见问题排查
在实际使用过程中,你可能会遇到各种问题。下面的常见问题排查指南可以帮助你快速定位和解决问题。
OOM 问题
OOM(Out Of Memory)是最常见的问题之一。当显存不足时,vLLM 会报错并终止。解决 OOM 问题有几个常用方法。
降低 gpu_memory_utilization 可以减少 vLLM 使用的显存比例,留出更多余量。减少 max_num_seqs 可以降低并发请求数,从而减少显存占用。缩短 max_model_len 可以减少每个请求的显存需求。增加 swap_space 可以在显存不足时使用 CPU 内存,虽然会降低速度但可以避免 OOM。如果问题持续,可以使用 nvidia-smi 监控显存使用情况,查找是否有显存泄漏。
# 1. 降低显存利用率
--gpu-memory-utilization 0.8
# 2. 减少并发请求
--max-num-seqs 64
# 3. 缩短最大长度
--max-model-len 2048
# 4. 增加交换空间
--swap-space 8
# 5. 检查显存泄漏
nvidia-smi dmon -s pucvmet -d 1 -c 10
性能低下
如果 vLLM 的吞吐量远低于预期,可能存在性能瓶颈。排查步骤如下。
首先检查 GPU 利用率,使用 nvidia-smi dmon 命令。如果 GPU 利用率低,可能是 CPU 瓶颈或批处理配置不当。增加 max_num_seqs 可以提升并发度,从而提高 GPU 利用率。启用 enable_chunked_prefill 可以提升显存效率,间接提升吞吐量。检查 CPU 瓶颈,使用 top 命令查看 CPU 使用率。如果 CPU 是瓶颈,考虑减少批处理大小或升级 CPU。对于 Hopper GPU,可以使用 --kv-cache-dtype fp8 启用 FP8 KV Cache,进一步提升性能。
# 2. 增加并发
--max-num-seqs 256
# 3. 启用分块预填充
--enable-chunked-prefill
# 4. 检查 CPU 瓶颈
top -bn1 | grep "Cpu(s)"
# 5. 使用 TensorRT 优化
--kv-cache-dtype fp8 # Hopper GPU
连接问题
如果无法连接 vLLM API 服务,按以下步骤排查。
首先检查服务状态,使用 curl 访问健康检查端点。如果服务未响应,检查端口是否监听,使用 netstat 命令。检查防火墙规则,确保端口没有被阻止。查看日志文件,查找错误信息。如果问题无法解决,尝试重启服务。
2. 检查端口
netstat -tlnp | grep 8000
# 3. 检查防火墙
sudo iptables -L -n
# 4. 查看日志
tail -f vllm_requests.log
# 5. 重启服务
docker restart vllm-server
总结
今天学到的内容
- ✅ vLLM 安装与配置:pip、Docker、服务启动
- ✅ PagedAttention 实战:显存效率测试、Block Table
- ✅ 并发请求测试:基准测试工具、负载测试
- ✅ 性能 Benchmark:吞吐量、延迟测试
- ✅ 参数调优:关键参数、调优建议
- ✅ 生产部署:高可用、监控、日志
- ✅ 问题排查:OOM、性能、连接问题
下一步
明天我们将学习 Day 16 - TensorRT-LLM 部署与优化,深入了解:
- TensorRT-LLM 环境搭建
- 模型优化与编译
- 多 GPU 推理
- 性能实测