vLLM 架构总览
vLLM 由 UC Berkeley 团队开发,核心创新是 PagedAttention——一种受操作系统虚拟内存启发的注意力机制。其架构层次:
┌─────────────────────────────────────────────┐
│ OpenAI Compatible API │
│ (Streaming / Function Calling) │
├─────────────────────────────────────────────┤
│ Request Scheduler │
│ (Continuous Batching + Priority Queue) │
├──────────────┬──────────────────────────────┤
│ PagedAttention│ ParallelWorker │
│ KV Cache Mgr │ (Tensor Pipeline Parallel)│
├──────────────┴──────────────────────────────┤
│ GPU Workers (CUDA) │
│ FlashAttention / FlashInfer Backend │
└─────────────────────────────────────────────┘
PagedAttention 原理
核心问题
传统 LLM 推理中,KV Cache 按最大序列长度预分配连续显存。这导致:
- 内部碎片:实际序列 < 最大序列,浪费 60-80% 显存
- 外部碎片:频繁分配/释放产生碎片
- 无法共享:相同前缀的请求各自维护 KV Cache
PagedAttention 方案
借鉴 OS 的分页机制,将 KV Cache 分割为固定大小的 block(通常每 block 存 16 个 token 的 KV):
逻辑视图 (Token Sequence):
[BOS] [Hello] [world] [How] [are] [you] [?]
│ │ │ │ │ │ │
物理块映射 (Block Table): ┌──────┐
Block 0 → GPU 0x1000 ←───── │ B0 │ [BOS Hello world How]
Block 1 → GPU 0x2000 ←───── │ B1 │ [are you ? _ _ _ ...]
└──────┘
关键优势:
| 指标 | 传统 Attention | PagedAttention |
|---|---|---|
| KV Cache 利用率 | 20-40% | 95%+ |
| 最大并发序列 | 受限于预分配 | 受限于总显存 |
| 前缀共享 | ❌ | ✅ 自动 |
| 内存碎片 | 严重 | 几乎无 |
代码层面理解
# vLLM 内部 KV Cache 管理(简化示意)
class PagedAttentionKVCache:
def __init__(self, num_blocks, block_size=16, num_heads=32, head_size=128):
self.block_size = block_size
# 预分配所有 block 的显存池
self.kv_pool = torch.empty(
num_blocks, block_size, 2, num_heads, head_size,
device='cuda', dtype=torch.float16
)
self.free_blocks = list(range(num_blocks))
self.block_tables = {} # seq_id → [block_indices]
def allocate(self, seq_id, num_tokens):
blocks_needed = (num_tokens + self.block_size - 1) // self.block_size
self.block_tables[seq_id] = []
for _ in range(blocks_needed):
if not self.free_blocks:
raise OOMError("KV Cache exhausted")
block_idx = self.free_blocks.pop(0)
self.block_tables[seq_id].append(block_idx)
def append_kv(self, seq_id, keys, values):
# 写入对应物理块,无需连续
table = self.block_tables[seq_id]
last_block = table[-1]
# ... write to physical block
Continuous Batching
静态 Batching 的问题
时间 → T0 T1 T2 T3 T4 T5
Req A: [gen] [gen] [gen] [done] ---- ----
Req B: [gen] [gen] [gen] [gen] [gen] [done]
Req C: [gen] [gen] [done] ---- ---- ----
↑ GPU 在 A、C 完成后空等 B
Continuous Batching 机制
时间 → T0 T1 T2 T3 T4 T5
Req A: [gen] [gen] [gen] [done]
Req B: [gen] [gen] [gen] [gen] [gen] [done]
Req C: [gen] [gen] [done]
Req D: [gen] [gen] [gen] [done] ← A 完成后立即插入
Req E: [gen] [gen] [done] ← C 完成后插入
↑ GPU 持续满载
# vLLM Scheduler 核心逻辑(简化)
class Scheduler:
def __init__(self, max_num_seqs=256, max_tokens_per_batch=8192):
self.waiting: list[SeqGroup] = []
self.running: list[SeqGroup] = []
self.max_num_seqs = max_num_seqs
def schedule(self) -> ScheduledBatch:
scheduled = []
total_tokens = 0
# 1. 优先推进 running 序列(decode step)
for seq in self.running:
if total_tokens + 1 <= self.max_tokens_per_batch:
scheduled.append(seq)
total_tokens += 1
# 2. 空位填充 waiting 序列(prefill step)
while self.waiting and len(scheduled) < self.max_num_seqs:
seq = self.waiting.pop(0)
if total_tokens + seq.prompt_len <= self.max_tokens_per_batch:
scheduled.append(seq)
total_tokens += seq.prompt_len
else:
self.waiting.insert(0, seq)
break
# 3. 移除已完成序列,腾出位置
self.running = [s for s in scheduled if not s.is_finished()]
return ScheduledBatch(scheduled)
量化支持
vLLM 支持多种量化方案,各有取舍:
| 方案 | 精度损失 | 显存节省 | 推理加速 | 支持情况 |
|---|---|---|---|---|
| GPTQ | 中 | 4x | 1.5-2x | ✅ 成熟 |
| AWQ | 低 | 4x | 1.5-2x | ✅ 成熟 |
| GGUF | 低-中 | 2-8x | 1.2-1.8x | ⚠️ 实验性 |
| FP8 (NVIDIA) | 极低 | 2x | 1.5-2x | ✅ H100/L40S |
| BitsAndBytes | 中 | 2-4x | 无加速 | ⚠️ 纯兼容 |
GPTQ 部署
from vllm import LLM, SamplingParams
# 直接加载 GPTQ 量化模型
llm = LLM(
model="TheBloke/Llama-2-13B-GPTQ",
quantization="gptq",
dtype="float16",
gpu_memory_utilization=0.9,
max_model_len=4096,
enforce_eager=False, # 使用 CUDA Graph 加速
)
sampling = SamplingParams(temperature=0.7, max_tokens=512)
outputs = llm.generate(["Explain PagedAttention in 3 sentences."], sampling)
AWQ 部署
# 启动 OpenAI 兼容 API 服务器
python -m vllm.entrypoints.openai.api_server \
--model TheBloke/Mistral-7B-Instruct-v0.2-AWQ \
--quantization awq \
--dtype float16 \
--gpu-memory-utilization 0.9 \
--max-model-len 8192 \
--port 8000 \
--enable-prefix-caching \
--enforce-eager
FP8 部署(H100/L40S)
# FP8 精度,H100 上最佳吞吐
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--quantization fp8 \
--kv-cache-dtype fp8 \
--gpu-memory-utilization 0.92 \
--tensor-parallel-size 4
分布式推理
Tensor Parallelism
# 4 卡张量并行(推荐用于 70B+ 模型)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.9 \
--max-model-len 32768
# 2 节点 × 4 卡(需要 MPI 或 Ray)
# 节点 0
VLLM_HOST_IP=10.0.0.1 python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-405B-Instruct \
--tensor-parallel-size 8 \
--pipeline-parallel-size 1 \
--distributed-executor-backend ray
Pipeline Parallelism
# 张量并行 + 流水线并行混合
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-405B-Instruct \
--tensor-parallel-size 4 \
--pipeline-parallel-size 2 \
--gpu-memory-utilization 0.9
| 并行策略 | 适用场景 | 通信开销 | 推荐配置 |
|---|---|---|---|
| TP only | 单节点多卡 | 高(AllReduce) | 70B → TP=4 |
| PP only | 多节点 | 低(P2P) | 超大模型 |
| TP + PP | 超大模型 | 中等 | 405B → TP=8, PP=2 |
性能对比
吞吐量基准(Llama 3.1 8B,A100 40G)
| 引擎 | 并发数 | 吞吐 (tok/s) | P99 延迟 (ms) | 显存效率 |
|---|---|---|---|---|
| vLLM 0.6 | 64 | 4200 | 350 | 96% |
| TGI 2.3 | 64 | 3100 | 520 | 78% |
| Ollama 0.5 | 1 | 85 | 12 | 60% |
| llama.cpp | 1 | 72 | 14 | N/A |
Prefix Caching 加速
# 启用前缀缓存(vLLM 0.4+)
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--enable-prefix-caching \
--gpu-memory-utilization 0.9
| 场景 | 无 Prefix Cache | 有 Prefix Cache | 加速比 |
|---|---|---|---|
| 系统提示 2K + 用户 100 tok | 180ms | 45ms | 4x |
| 多轮对话(10 轮) | 1200ms | 280ms | 4.3x |
| RAG(相同文档检索) | 800ms | 120ms | 6.7x |
生产部署配置
Docker Compose
version: "3.9"
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=0,1,2,3
- HF_TOKEN=${HF_TOKEN}
volumes:
- ./models:/root/.cache/huggingface
ports:
- "8000:8000"
command:
- --model=meta-llama/Llama-3.1-70B-Instruct
- --tensor-parallel-size=4
- --gpu-memory-utilization=0.9
- --max-model-len=32768
- --enable-prefix-caching
- --uvicorn-log-level=info
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 5
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
关键启动参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--gpu-memory-utilization | 0.9 | 显存使用上限 |
--max-model-len | 模型默认 | 最大上下文长度 |
--max-num-seqs | 256 | 最大并发序列数 |
--enforce-eager | False | 关闭 CUDA Graph |
--enable-prefix-caching | False | 前缀缓存 |
--swap-space | 4 (GB) | CPU 侧 swap 空间 |
--block-size | 16 | KV Cache block 大小 |
监控与调优
# 使用 vLLM 内置 Prometheus 指标
# /metrics 端点自动暴露
关键指标:
| 指标 | 含义 | 告警阈值 |
|---|---|---|
vllm:num_requests_running | 正在处理的请求数 | > max_num_seqs × 0.9 |
vllm:num_requests_waiting | 等待队列长度 | > 0 持续 5min |
vllm:gpu_cache_usage_perc | KV Cache 使用率 | > 0.95 |
vllm:time_to_first_token_seconds | TTFT | > 500ms |
vllm:time_per_output_token_seconds | TPOT | > 50ms |
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
