vLLM 深度实战:当 PagedAttention 终结 GPU 显存浪费——从推理引擎原理到生产级高并发部署的完全指南(2026)
字数:约 12000 字 | 适用人群:有 Python 和深度学习基础,需要部署大模型推理服务的工程师
目录
- 背景介绍:大模型推理的「显存墙」之痛
- 核心概念:vLLM 技术架构全景
- 深度剖析:PagedAttention 原理与实现
- 架构分析:连续批处理与分布式推理
- 代码实战:从零搭建 vLLM 推理服务
- 性能优化:量化、前缀缓存与推测解码
- 生产部署:Docker、K8s 与监控体系
- 推理框架对比:vLLM vs TGI vs TensorRT-LLM vs Ollama
- 总结与展望
1. 背景介绍:大模型推理的「显存墙」之痛
1.1 推理部署的三座大山
2026 年,大模型已经从「训练时代」全面进入「推理时代」。但把模型跑起来和把模型高效地跑起来,完全是两回事。
你在公司里准备上线一个大模型 API 服务,老板问你:「咱们这个服务能扛多少并发?每个请求多少延迟?」你一算:
- 7B 模型,FP16 权重就要 14GB 显存
- 13B 模型,直接 26GB,一张 A100 40GB 装不下
- 70B 模型,至少需要两张 A100 80GB 做张量并行
这还只是装进去。真正跑起来,还要考虑:
| 痛点 | 具体问题 | 后果 |
|---|---|---|
| 显存碎片化 | KV Cache 长短不一,预分配浪费严重 | 吞吐量直接腰斩 |
| 静态批处理 | 一个批次所有请求必须等最长的那个完成 | GPU 利用率低下 |
| 重复计算 | 相同前缀的请求各自算各自的 | 算力白白浪费 |
传统方案(Hugging Face Transformers 直接跑)在每个问题上都做得不够好。这就是 vLLM 要解决的核心问题。
1.2 vLLM 是什么?
vLLM 是 UC Berkeley 天空计算实验室(Sky Computing Lab)开发的开源 LLM 推理与服务库,2023 年开源后迅速成为业界标准。截至 2026 年 6 月,GitHub Star 数已突破 85,000,贡献者超过 2,000 人。
一句话定义:
vLLM 是一个专为高吞吐、低延迟 LLM 推理服务设计的开源引擎,核心创新是 PagedAttention——把操作系统的虚拟内存分页思想引入 KV Cache 管理。
1.3 为什么 2026 年还要看 vLLM?
很多人会说:「vLLM 出来这么久了,还有必要学吗?」
有必要,原因有三个:
- 生态已成事实标准:OpenAI、Anthropic、Google 的许多内部推理服务都基于 vLLM;Hugging Face 的 TGI 也在吸收 vLLM 的设计思路
- v0.11~v0.17 的重大升级:连续批处理重构、多模态支持、PD 分离架构,每一次版本升级都带来质的飞跃
- MoE 模型成为主流:DeepSeek-V3、Kimi-K2 等 MoE 架构对推理引擎提出新要求,vLLM 是目前对 MoE 支持最完善的框架之一
2. 核心概念:vLLM 技术架构全景
2.1 整体架构
请求进来
│
▼
┌─────────────────────────────┐
│ API Server │ ← OpenAI 兼容接口
│ (FastAPI + Uvicorn) │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ Scheduler (调度器) │ ← 连续批处理的核心
│ - 请求排队 │
│ - Block 管理 (PagedAttention)│
│ - 前缀缓存匹配 │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ Model Executor │ ← 模型执行层
│ - 单卡 / 张量并行 │
│ - 流水线并行 │
│ - CUDA/HIP 图执行 │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ KV Cache Manager │ ← PagedAttention 存储层
│ - Block 内存池 │
│ - 共享前缀 Block │
│ - 换入/换出 (CPU swap) │
└─────────────────────────────┘
2.2 核心技术清单
| 技术 | 解决问题 | 效果 |
|---|---|---|
| PagedAttention | KV Cache 内存碎片 | 显存利用率从 ~60% 提升到 ~90% |
| Continuous Batching | 静态批处理等待时间长 | 吞吐量提升 2-4 倍 |
| Prefix Caching | 相同前缀重复计算 | 首 Token 延迟降低 90% |
| Speculative Decoding | 自回归生成慢 | 推理速度提升 1.5-3 倍 |
| Tensor Parallelism | 单卡显存不够 | 支持 70B+ 模型推理 |
| Quantization (AWQ/FP8) | 模型太大 | 7B 模型只需 4GB 显存 |
3. 深度剖析:PagedAttention 原理与实现
3.1 传统 KV Cache 管理的痛点
在 standard Transformer 推理中,每生成一个新 token,都需要访问历史所有 token 的 Key 和 Value 矩阵(KV Cache)。传统做法是在请求开始时预分配一块连续显存:
# 传统做法:预分配 MAX_SEQ_LEN 的 KV Cache
kv_cache = torch.zeros(
(batch_size, num_layers, 2, num_heads, MAX_SEQ_LEN, head_dim),
device="cuda"
)
问题显而易见:
- 如果
MAX_SEQ_LEN=2048,但实际请求平均只有 200 token,浪费 90% 显存 - 不同请求长度不同,预分配只能按最长的来,碎片严重
- KV Cache 不能跨请求共享,相同系统提示词每次都要重新计算
3.2 PagedAttention 的核心思想
vLLM 的 PagedAttention 把操作系统的虚拟内存分页思想搬到了 GPU 显存管理上:
逻辑视图(请求视角) 物理视图(GPU 显存视角)
┌───────────────┐ ┌─────┬─────┬─────┬─────┐
│ 请求A: tokens │ │ B0 │ B1 │ B2 │ B3 │
│ [t1,t2,...,tN]│ ├─────┼─────┼─────┼─────┤
└───────┬───────┘ │ B4 │ B5 │ B6 │ B7 │
│ ├─────┼─────┼─────┼─────┤
▼ │ B8 │ B9 │ ... │ │
Page Table └─────┴─────┴─────┴─────┘
┌───────────────┐
│ Block 0 → B3 │ ← 映射表
│ Block 1 → B7 │
│ Block 2 → B1 │
└───────────────┘
每个 Block 固定存储 16 个 token 的 KV Cache。请求的说有 token 被分成多个 Block,通过 Page Table 映射到物理显存中的不连续 Block。
关键优势:
- 按需分配:来一个 token 才分配一个 Block,不浪费
- 内存共享:相同前缀的请求共享 Block(这就是 Prefix Caching 的基础)
- 碎片可控:浪费只发生在每个请求最后一个不完整的 Block,通常 < 5%
3.3 PagedAttention 代码级理解
vLLM 源码中核心的数据结构(简化版):
# Block 大小通常为 16 个 token
BLOCK_SIZE = 16
class Block:
def __init__(self, block_id: int, physical_token_ids: torch.Tensor,
attn_bias: torch.Tensor):
self.block_id = block_id
# 实际存储的 KV Cache,形状: [2, num_heads, block_size, head_dim]
self.kv_cache = torch.zeros(2, num_heads, BLOCK_SIZE, head_dim)
self.ref_count = 0 # 引用计数,支持共享
self.last_accessed = time.time()
class PagedAttention:
def __init__(self, num_blocks: int):
self.free_blocks: List[int] = list(range(num_blocks))
self.blocks: Dict[int, Block] = {}
def allocate_block(self) -> int:
"""从空闲池中分配一个 Block"""
if not self.free_blocks:
self.evict_to_cpu() # 换出到 CPU
block_id = self.free_blocks.pop()
self.blocks[block_id] = Block(block_id)
return block_id
def compute(self, query: Tensor, key: Tensor, value: Tensor,
block_tables: List[List[int]]) -> Tensor:
"""
核心计算:PagedAttention 前向传播
query: [batch, num_heads, head_dim]
block_tables: 每个请求的 Block 映射表
"""
output = []
for i in range(batch):
# 根据该请求的 Page Table 收集 KV
kv = self.gather_kv(block_tables[i])
# 标准 Attention 计算
attn_output = scaled_dot_product_attention(query[i], kv[0], kv[1])
output.append(attn_output)
return torch.stack(output)
源码位置:
vllm/attention/ops/paged_attention.py,建议直接读 CUDA kernel 实现,有 FlashAttention 集成。
4. 架构分析:连续批处理与分布式推理
4.1 连续批处理(Continuous Batching)
传统静态批处理的流程:
Batch 1: [Req1(10 tok), Req2(50 tok), Req3(20 tok)]
↓ 所有请求必须一起完成
等待 Req2 完成后,Batch 1 才释放
总耗时 = max(10, 50, 20) = 50 步
vLLM 的连续批处理:
Step 1: Batch = [Req1, Req2, Req3]
Step 5: Req1 完成 → 立即移除,插入 Req4
Step 10: Req3 完成 → 立即移除,插入 Req5
Step 20: Req4 完成 → 立即移除,插入 Req6
Step 50: Req2 完成 → Batch 清空
总耗时 = 50 步,但处理了 6 个请求而非 3 个!
核心调度逻辑(简化):
class Scheduler:
def schedule(self) -> List[SequenceGroup]:
"""每一步动态决定哪些请求参与计算"""
running = []
# 1. 继续正在运行的请求
for seq_group in self.running:
if not seq_group.is_finished():
running.append(seq_group)
# 2. 从等待队列中补充新请求(有显存就加)
while self.waiting and self.has_free_blocks():
seq_group = self.waiting.pop(0)
self._allocate_blocks(seq_group)
running.append(seq_group)
# 3. 回收已完成的请求的 Block
for seq_group in self.running:
if seq_group.is_finished():
self._free_blocks(seq_group)
self.running = running
return running
4.2 分布式推理:张量并行 vs 流水线并行
张量并行(Tensor Parallelism):把每一层的计算切分到多张卡上
# 启动 vLLM 服务,使用 4 卡张量并行
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--dtype bfloat16
适合:单层太大放不进单卡(如 70B 模型)
流水线并行(Pipeline Parallelism):把不同层放到不同卡上
vllm serve meta-llama/Llama-3.1-405B \
--tensor-parallel-size 8 \
--pipeline-parallel-size 4 \
--dtype bfloat16
适合:超大模型(405B 级别)
4.3 PD 分离架构(Prefill-Decode Disaggregation)
这是 vLLM 2026 年最重磅的架构升级之一。
问题:Prefill 阶段(处理所有输入 token)是 compute-bound(计算密集型),Decode 阶段(逐个生成 token)是 memory-bound(显存带宽瓶颈)。把这两种特性的负载混在同一张卡上,GPU 利用率不高。
PD 分离解决方案:
Prefill 节点(高算力卡) Decode 节点(高显存卡)
┌─────────────────────┐ ┌─────────────────────┐
│ 专门处理输入 token │ KV Cache │ 专门做逐 token 生成 │
│ 计算密集型 │─────────→│ 显存带宽密集型 │
│ (A100/H100) │ 传输 │ (A100/H100) │
└─────────────────────┘ └─────────────────────┘
启动方式(vLLM v0.17+):
# Prefill 节点
vllm serve model --role prefill --data-parallel-size 2
# Decode 节点
vllm serve model --role decode --data-parallel-size 4
5. 代码实战:从零搭建 vLLM 推理服务
5.1 环境准备
# Python 3.10+ 推荐
conda create -n vllm python=3.10 -y
conda activate vllm
# CUDA 12.1+ 必需
# 安装 vLLM(包含 CUDA 12.1 预编译版本)
pip install vllm
# 验证安装
python -c "import vllm; print(vllm.__version__)"
# 预期输出: 0.17.1 或更高
5.2 最快上手:启动 OpenAI 兼容 API 服务
# 下载并启动 7B 模型(会自动从 Hugging Face 下载)
vllm serve Qwen/Qwen3-7B \
--host 0.0.0.0 \
--port 8000 \
--dtype bfloat16 \
--api-key "your-key" # 可选
# 服务启动后,直接用 OpenAI SDK 调用
客户端调用示例:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="your-key" # 或随便填,如果服务端没设 key
)
response = client.chat.completions.create(
model="Qwen/Qwen3-7B",
messages=[
{"role": "user", "content": "用 Python 写一个快速排序"}
],
temperature=0.7,
max_tokens=1024
)
print(response.choices[0].message.content)
5.3 程序化调用:LLM 类直接使用
from vllm import LLM, SamplingParams
# 初始化模型(单卡)
llm = LLM(
model="Qwen/Qwen3-7B",
dtype="bfloat16",
gpu_memory_utilization=0.9, # 用 90% 显存
max_model_len=4096, # 最大上下文长度
)
# 采样参数
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.95,
max_tokens=1024,
repetition_penalty=1.1,
)
# 批量推理
prompts = [
"解释一下 PagedAttention 的原理",
"用 Rust 写一个 HTTP 服务器",
"2026 年 AI 推理引擎的发展趋势是什么?",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(f"Prompt: {output.prompt}")
print(f"Output: {output.outputs[0].text}")
print("-" * 50)
5.4 多卡张量并行实战
# 4 卡运行 70B 模型
llm = LLM(
model="meta-llama/Llama-3.1-70B-Instruct",
tensor_parallel_size=4, # 4 卡张量并行
dtype="bfloat16",
gpu_memory_utilization=0.95,
max_model_len=8192,
)
# 用 Ray 做分布式调度(vLLM 自动处理)
# 无需手动写 Ray 代码,vLLM 内部已集成
5.5 启用 Prefix Caching(共享系统提示词)
llm = LLM(
model="Qwen/Qwen3-7B",
enable_prefix_caching=True, # 启用前缀缓存
# 相同前缀的请求会自动共享 KV Cache
)
# 第一次请求:计算并缓存系统提示词的 KV
prompts = [
"<system>你是一个Python专家</system>\n用户:写一个快速排序",
"<system>你是一个Python专家</system>\n用户:解释装饰器",
# 第二条请求会复用第一条的系统提示词 KV Cache!
]
6. 性能优化:量化、前缀缓存与推测解码
6.1 量化方案选型
vLLM 支持多种量化方案,适用场景不同:
| 量化方案 | 精度损失 | 速度 | 显存节省 | 推荐场景 |
|---|---|---|---|---|
| FP8(E4M3) | 极小 | 最快 | 50% | H100/A100 生产首选 |
| AWQ(4-bit) | 小 | 快 | 75% | 7B/13B 模型部署 |
| GPTQ(4-bit) | 小 | 中 | 75% | 与 AWQ 类似,更成熟 |
| INT8(smoothquant) | 极小 | 中 | 50% | 对精度要求高的场景 |
| GGUF(Q4_K_M) | 中 | 慢 | 75% | CPU/Mac 推理 |
FP8 量化实战(需要 H100 或 Ada Lovelace 架构 GPU):
# vLLM 自动支持 FP8,无需手动量化
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--dtype fp8 \
--tensor-parallel-size 2
AWQ 4-bit 量化实战:
# 先用量化工具将模型转为 AWQ 格式
pip install autoawq
# 量化(只需做一次)
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model = AutoAWQForCausalLM.from_pretrained("Qwen/Qwen3-7B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-7B")
# 量化并保存
model.quantize(tokenizer, quant_config={"zero_point": True, "q_group_size": 128})
model.save_quantized("Qwen/Qwen3-7B-AWQ")
# 用 vLLM 加载量化后的模型
vllm serve Qwen/Qwen3-7B-AWQ \
--quantization awq \
--dtype bfloat16
6.2 推测解码(Speculative Decoding)
原理:用小模型「草稿」多个 token,再用大模型一次性验证,减少大模型调用次数。
# 用 7B 模型做草稿,13B 模型做验证
vllm serve meta-llama/Llama-3.1-13B-Instruct \
--speculative-model meta-llama/Llama-3.1-7B-Instruct \
--num-speculative-tokens 5 \
--speculative-draft-tensor-parallel-size 1
效果:在代码生成任务上,速度提升 2-3 倍。
6.3 关键性能参数调优
llm = LLM(
model="Qwen/Qwen3-7B",
# ---- 显存管理 ----
gpu_memory_utilization=0.90, # 显存利用率,推荐 0.85-0.95
swap_space=4, # CPU swap 空间(GB),防止 OOM
# ---- 批处理 ----
max_num_seqs=256, # 最大并发序列数
max_num_batched_tokens=8192, # 每步最大 token 数
# ---- KV Cache ----
block_size=16, # Block 大小,默认 16
enable_chunked_prefill=True, # 分块 Prefill,降低首 Token 延迟
max_num_on_the_fly=0, # 动态分块 Prefill 的最大并发
# ---- 调度 ----
disable_sliding_window=True, # 禁用滑动窗口(长上下文模型)
preemption_mode="recompute", # 显存不足时:recompute 或 swap
)
6.4 性能压测
用 vllm bench 工具做基准测试:
# 压测吞吐量
vllm bench throughput \
--model Qwen/Qwen3-7B \
--dataset sharegpt \
--num-prompts 1000 \
--request-rate 16
# 压测延迟
vllm bench latency \
--model Qwen/Qwen3-7B \
--dataset sharegpt \
--num-prompts 100
7. 生产部署:Docker、K8s 与监控体系
7.1 Docker 部署(推荐)
官方镜像:vllm/vllm-openai:latest(包含 CUDA 12.1 和所有依赖)
# Dockerfile
FROM vllm/vllm-openai:latest
# 预下载模型(构建时)
RUN python -c "from transformers import AutoModel; \
AutoModel.from_pretrained('Qwen/Qwen3-7B')"
EXPOSE 8000
CMD ["vllm", "serve", "Qwen/Qwen3-7B", \
"--host", "0.0.0.0", \
"--port", "8000", \
"--dtype", "bfloat16"]
# 构建
docker build -t my-vllm:latest .
# 运行(需要 --gpus all)
docker run --gpus all -p 8000:8000 \
-v ~/.cache/huggingface:/root/.cache/huggingface \
my-vllm:latest
7.2 Kubernetes 部署
# vllm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-inference
spec:
replicas: 2
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command:
- vllm
- serve
- Qwen/Qwen3-7B
- --tensor-parallel-size
- "2"
- --dtype
- bfloat16
resources:
limits:
nvidia.com/gpu: 2
memory: 64Gi
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm
ports:
- port: 8000
targetPort: 8000
type: LoadBalancer
7.3 监控体系
vLLM 暴露 Prometheus 指标(通过 --enable-metrics 启用):
vllm serve Qwen/Qwen3-7B \
--enable-metrics \
--metrics-port 9000
关键指标:
| 指标名 | 含义 | 告警阈值 |
|---|---|---|
vllm:num_requests_running | 当前运行请求数 | > max_num_seqs * 0.9 |
vllm:gpu_cache_usage_perc | KV Cache 显存使用率 | > 95% |
vllm:time_to_first_token | 首 Token 延迟 | > 1s |
vllm:time_per_output_token | 每 Token 延迟 | > 100ms |
vllm:request_throughput | 请求吞吐量 | 越低越好(相对) |
Grafana 仪表盘 JSON 可直接从 vLLM GitHub 仓库的 examples/monitoring/ 获取。
8. 推理框架对比:vLLM vs TGI vs TensorRT-LLM vs Ollama
8.1 综合对比表
| 维度 | vLLM | TGI (Text Generation Inference) | TensorRT-LLM | Ollama |
|---|---|---|---|---|
| 开发者 | UC Berkeley | Hugging Face | NVIDIA | Ollama Team |
| 吞吐量 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 易用性 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 模型支持 | 200+ | 100+ | NVIDIA 优化模型 | GGUF 格式 |
| 硬件支持 | NVIDIA/AMD/Intel | NVIDIA/AMD | NVIDIA 仅 | 全平台 |
| 量化支持 | FP8/AWQ/GPTQ/INT8 | INT8/FP8 | PTQ/INT4/FP8 | GGUF 量化 |
| Prefix Caching | ✅ | ✅ | ✅ | ❌ |
| Speculative Decoding | ✅ | ❌ | ✅ | ❌ |
| PD 分离 | ✅ (v0.17+) | ❌ | ✅ | ❌ |
| 适合场景 | 生产级高并发 | Hugging Face 生态 | NVIDIA 纯环境 | 本地开发/测试 |
8.2 选型建议
如果是生产部署,需要高并发:
→ 选 vLLM(首选)或 TensorRT-LLM(NVIDIA 环境)
如果用 Hugging Face 生态,模型都在 HF Hub:
→ 选 TGI,集成最顺畅
如果只是在本地跑跑模型,不想折腾:
→ 选 Ollama,一行命令搞定
如果需要支持 AMD/Intel GPU:
→ 选 vLLM(支持最全面)
9. 总结与展望
9.1 核心要点回顾
- PagedAttention 是 vLLM 的灵魂:把显存利用率从 ~60% 提升到 ~90%,是推理吞吐量提升的关键
- 连续批处理让 GPU 不闲着:动态调度请求,吞吐量提升 2-4 倍
- Prefix Caching 是省钱利器:相同系统提示词的请求共享 KV Cache,首 Token 延迟暴降
- 量化是必选项:FP8 几乎无损,AWQ 4-bit 是 7B/13B 模型部署的最佳平衡点
- PD 分离是未来方向:Prefill 和 Decode 分离部署,GPU 利用率再上一个台阶
9.2 vLLM 2026 年路线图
根据 vLLM 官方 Roadmap 和社区讨论:
- v0.18~v1.0:稳定 API,支持更多硬件(AMD MI300、Intel Gaudi)
- 多模态推理优化:图像、视频、音频统一推理管道
- Disaggregated Prefill-Decode 成熟:成为生产部署的默认模式
- 与模型编译器深度集成:如 TorchCompile + vLLM
9.3 实战建议
对于刚开始用 vLLM 的团队:先跑通单卡 7B 模型,再用
--tensor-parallel-size扩展到多卡。不要一上来就搞最复杂的配置。对于已经在用的团队:重点关注 Prefix Caching 和分块 Prefill,这两个特性对降低延迟效果最明显。
对于选型中的团队:vLLM 是目前综合最优的选择,除非你有非常特殊的硬件或生态约束。
参考资源
- vLLM 官方文档:https://docs.vllm.ai/
- vLLM GitHub:https://github.com/vllm-project/vllm
- PagedAttention 论文:Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023
- vLLM 中文社区:https://vllm.hyper.ai/
- 作者微博/GitHub:欢迎关注获取更多 AI 推理实战内容
本文写于 2026 年 6 月,基于 vLLM v0.17.1。如有技术更新,请以官方文档为准。
如果你觉得这篇文章有帮助,欢迎在 程序员茄子 上点赞收藏,或在评论区分享你的 vLLM 使用经验!