vLLM 性能基石
vLLM 的核心性能优势来自三大技术:
| 技术 | 解决的问题 | 性能提升 |
|---|---|---|
| PagedAttention | KV Cache 内存碎片 | 2-4x 吞吐 |
| 连续批处理 | 静态批处理浪费 | 3-8x 吞吐 |
| 高效注意力 | 标准 Attention 慢 | 2x 推理速度 |
PagedAttention 原理
传统 KV Cache 为每个请求预分配连续内存,导致大量碎片:
传统方式:
[请求A: ████░░░░] [请求B: ██░░░░░░] [请求C: ██████░░]
预分配8块, 用4块 预分配8块, 用2块 预分配8块, 用6块
浪费4块 浪费6块 浪费2块
总利用率: 12/24 = 50%
PagedAttention:
[块0:A1] [块1:A2] [块2:B1] [块3:C1] [块4:C2] [块5:A3] [块6:C3] [块7:空闲]
按需分配, 几乎无浪费
总利用率: 7/8 = 87.5%
KV Cache 被分成固定大小的 block(通常 16 token/block),通过页表映射,类似操作系统的虚拟内存。
from vllm import LLM, SamplingParams
# PagedAttention 默认启用, 通过参数调整
llm = LLM(
model="meta-llama/Llama-3-8B",
# PagedAttention 相关
block_size=16, # KV Cache block 大小 (token数)
swap_space=4, # CPU swap 空间 (GB)
gpu_memory_utilization=0.9, # GPU 显存利用率上限
)
block_size 选择
| block_size | 内存利用率 | 查找开销 | 推荐 |
|---|---|---|---|
| 8 | 最高 | 高 | 短序列 |
| 16 | 高 | 低 | 默认推荐 |
| 32 | 中 | 最低 | 长序列 |
连续批处理
传统批处理等待所有请求完成才能释放,连续批处理在请求完成后立即加入新请求:
传统静态批处理:
时间 →
Req A: [生成中████████████] ← 慢请求拖累全队
Req B: [生成████] ← 完成但要等 A
Req C: [████████████████████] ← 完成但要等 A
批次完成后才能接新请求
连续批处理 (Continuous Batching):
Req A: [生成中████████████]
Req B: [生成████]→完成→新Req D加入
Req C: [████████████████████]→完成→新Req E加入
↑ 空位立即填充, 吞吐最大化
# vLLM 服务端连续批处理 (API 模式)
# 启动 API server
# vllm serve meta-llama/Llama-3-8B \
# --host 0.0.0.0 --port 8000 \
# --max-num-seqs 256 \ # 最大并发序列数
# --max-num-batched-tokens 8192 # 单批次最大 token
from vllm import LLM, SamplingParams
# 离线批量推理也支持连续批处理
llm = LLM(
model="meta-llama/Llama-3-8B",
max_num_seqs=256, # 最大并发序列
max_num_batched_tokens=8192, # 单批次最大 token 数
enable_chunked_prefill=True, # 分块 prefill, 减少 tail latency
)
prompts = [f"请写一首关于秋天的诗,第{i}首" for i in range(1000)]
sampling = SamplingParams(
temperature=0.7,
max_tokens=256,
)
# vLLM 自动用连续批处理
outputs = llm.generate(prompts, sampling)
关键参数调优
| 参数 | 作用 | 推荐值 |
|---|---|---|
| max_num_seqs | 最大并发数 | 128-256 (A100) |
| max_num_batched_tokens | 批次 token 上限 | 4096-8192 |
| enable_chunked_prefill | 分块 prefill | True (推荐) |
张量并行
大模型单卡放不下,需要将模型切分到多卡:
张量并行 (TP=2):
GPU 0: 负责 attention 的前半部分 + FFN 的前半部分
GPU 1: 负责 attention 的后半部分 + FFN 的后半部分
每层都需要 AllReduce 通信
# 方式1: Python API
llm = LLM(
model="meta-llama/Llama-3-70B",
tensor_parallel_size=4, # 4卡张量并行
pipeline_parallel_size=1, # 流水线并行度
dtype="bfloat16",
)
# 方式2: API Server
# vllm serve meta-llama/Llama-3-70B \
# --tensor-parallel-size 4 \
# --pipeline-parallel-size 1
TP vs PP 选择
| 方式 | 通信量 | 显存均衡 | 适用场景 |
|---|---|---|---|
| 张量并行 (TP) | 高(每层AllReduce) | 好 | 单机多卡 |
| 流水线并行 (PP) | 低(仅层间传递) | 一般 | 多机部署 |
| TP + PP | 中 | 好 | 超大模型多机 |
# 8卡部署 70B 模型: TP=4, PP=2
llm = LLM(
model="meta-llama/Llama-3-70B",
tensor_parallel_size=4,
pipeline_parallel_size=2,
dtype="bfloat16",
)
流水线并行
PP=2 示例:
GPU 0: Layer 0-31 → 中间结果发送到 GPU 1
GPU 1: Layer 32-63 → 接收并完成推理
# vLLM 的流水线并行
llm = LLM(
model="meta-llama/Llama-3-70B",
pipeline_parallel_size=2,
tensor_parallel_size=2, # TP + PP 组合
dtype="bfloat16",
)
# 多机部署 (每台机器4卡, 2台共8卡)
# 节点0:
# vllm serve meta-llama/Llama-3-70B \
# --tensor-parallel-size 4 \
# --pipeline-parallel-size 2 \
# --distributed-executor-backend ray
# 节点1: 同样命令 + Ray join
量化推理
# AWQ 量化 (推荐, 精度损失最小)
llm = LLM(
model="TheBloke/Llama-3-70B-AWQ",
quantization="awq",
dtype="float16",
)
# GPTQ 量化
llm = LLM(
model="TheBloke/Llama-3-70B-GPTQ",
quantization="gptq",
dtype="float16",
)
# FP8 量化 (H100 专属, 极致性能)
llm = LLM(
model="meta-llama/Llama-3-70B",
quantization="fp8",
dtype="bfloat16",
)
量化方案对比
| 方案 | 显存 (70B) | 精度损失 | 推理速度 | 硬件要求 |
|---|---|---|---|---|
| BF16 | 140GB | 0% | 1x | A100 80G×2 |
| FP8 | 70GB | <1% | 1.5x | H100 |
| AWQ | 40GB | ~2% | 1.3x | 通用GPU |
| GPTQ | 40GB | ~3% | 1.2x | 通用GPU |
LoRA 动态加载
vLLM 支持同时服务多个 LoRA adapter,动态切换:
from vllm import LLM, SamplingParams
from vllm.lora.request import LoRARequest
# 基座模型
llm = LLM(
model="meta-llama/Llama-3-8B",
enable_lora=True,
max_loras=4, # 同时加载的 LoRA 数
max_lora_rank=64, # 最大 LoRA rank
max_cpu_loras=16, # CPU 缓存的 LoRA 数
)
# 加载 LoRA adapters
# 语法: LoRARequest(lora_name, lora_id, lora_path)
lora_code = LoRARequest("code_assistant", 1, "./loras/code-v1")
lora_translate = LoRARequest("translator", 2, "./loras/translate-v1")
lora_chat = LoRARequest("chatbot", 3, "./loras/chat-v1")
# 不同请求用不同 LoRA
outputs = llm.generate(
["写一个 Python 排序函数", "Translate: Hello world", "你好"],
SamplingParams(temperature=0.7, max_tokens=256),
[
lora_code, # 第一个请求用 code LoRA
lora_translate, # 第二个用 translate LoRA
lora_chat, # 第三个用 chat LoRA
],
)
# API Server 模式
# vllm serve meta-llama/Llama-3-8B \
# --enable-lora \
# --lora-modules code=./loras/code-v1 translate=./loras/translate-v1 \
# --max-loras 4 --max-lora-rank 64
# 请求时指定 LoRA
# curl http://localhost:8000/v1/completions \
# -H "Content-Type: application/json" \
# -d '{"model": "code", "prompt": "写排序函数", "max_tokens": 256}'
监控与调优
Prometheus 指标
vLLM 内置 Prometheus 指标导出:
# 启动时自动暴露 /metrics 端点
# vllm serve meta-llama/Llama-3-8B \
# --host 0.0.0.0 --port 8000
关键监控指标:
| 指标 | 含义 | 告警阈值 |
|---|---|---|
| vllm:num_requests_running | 正在处理的请求 | 持续=max_num_seqs |
| vllm:num_requests_waiting | 排队请求 | >50 |
| vllm:gpu_cache_usage | KV Cache 使用率 | >90% |
| vllm:time_to_first_token | 首 token 延迟 | >500ms |
| vllm:time_per_output_token | 生成延迟 | >50ms/token |
| vllm:request_success_total | 成功请求数 | - |
性能调优检查清单
# 推荐生产配置 (A100 80G × 4)
PROD_CONFIG = {
# 模型
"model": "meta-llama/Llama-3-70B",
"dtype": "bfloat16",
# 并行
"tensor_parallel_size": 4,
# 批处理
"max_num_seqs": 128,
"max_num_batched_tokens": 8192,
"enable_chunked_prefill": True,
# KV Cache
"gpu_memory_utilization": 0.9,
"swap_space": 4,
"block_size": 16,
# 量化 (可选, 省2x显存)
# "quantization": "fp8",
# LoRA (可选)
# "enable_lora": True,
# "max_loras": 4,
# 超时
"enforce_eager": False, # False=用CUDA Graph, True=省编译时间
}
常见性能问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 首 token 延迟高 | prefill 计算量大 | 开启 chunked_prefill |
| 吞吐量低 | 批次太小 | 增大 max_num_seqs |
| OOM | KV Cache 不够 | 降 gpu_memory_utilization 或用量化 |
| 排队过多 | 并发超限 | 加机器或增 max_num_seqs |
| 长请求超时 | 超过 max_tokens | 调大 max_model_len |
实战建议
- 先量后调:用 vLLM 自带 benchmark 脚本测基线性能
- gpu_memory_utilization 不要拉满:留 5-10% 给系统和其他进程
- CUDA Graph 默认开:enforce_eager=False 有 20-30% 加速
- 量化优先用 AWQ:通用性最好,精度损失最小
- LoRA 多任务首选 vLLM:动态加载无额外延迟,比手动切换高效百倍
- 监控必有:没有监控的 vLLM 是黑盒,排障靠猜
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
