在第四篇中我们反复提到,模型选型只是第一步,真正把模型变成可用的 API 服务,推理框架的选择直接决定用户体验和成本结构。vLLM 是当前开源社区最主流的高吞吐推理框架之一,由 UC Berkeley 团队开发,以PagedAttention算法为核心创新,在吞吐量上相比传统方案有数量级提升。本节讲清它的核心原理、为什么快,以及如何部署。
一、为什么需要专门的推理框架?
先看一个朴素方案的问题:你用 Hugging Face Transformers 直接加载模型,写一个 FastAPI 服务,每次请求时调用 model.generate()。这在小规模测试中能跑,但一旦面对生产环境的并发请求,三个瓶颈立刻暴露:
瓶颈 1:KV-Cache 显存浪费
自回归生成时,每生成一个新 Token,模型都要重新计算之前所有 Token 的 Key 和 Value 矩阵。为避免重复计算,推理框架会把已计算的 K、V 缓存下来,这就是 KV-Cache。但传统方案中,KV-Cache 是预先分配固定长度的连续显存块——如果你的最大生成长度设为 2048,但实际请求只生成 50 个 Token,剩余 1998 个位置的显存就白白占着,造成严重的内部碎片。
瓶颈 2:显存碎片化
不同用户的请求生成长度不同,频繁的分配和释放会导致显存像老式硬盘一样碎片化——空闲显存总量足够,但找不到连续的大块空间存放新请求的 KV-Cache。这就是外部碎片。
瓶颈 3:请求串行排队
朴素方案中,显存利用率极低,导致同时能服务的请求数很少。大部分请求都在排队等待,GPU 计算单元大量空闲。
vLLM 的 PagedAttention 正是为解决这三个问题而设计的。
二、PagedAttention 的核心原理:像操作系统管理内存一样管理显存
PagedAttention 的核心洞见是:KV-Cache 不应该被当作一个巨大的连续数组,而应该被切分成固定大小的“页”(Block),像操作系统中的虚拟内存分页一样管理。
1. 传统方案 vs PagedAttention
| 维度 | 传统方案(Contiguous KV-Cache) | PagedAttention(Block-based) |
|------|-------------------------------|------------------------------|
| 内存分配 | 每个请求预分配最大生成长度的连续显存 | 按需分配,以固定大小 Block(如 16 个 Token)为单位动态扩展 |
| 碎片问题 | 频繁分配释放导致严重内外碎片 | Block 统一大小,无外部碎片,内部碎片仅限最后一个 Block |
| 显存利用率 | 通常 20%–40% | 通常可达 80%–95% |
| 批处理 | 请求生成长度差异大时难以高效 Batch | 不同长度的请求可以无缝共享 Batch,因为 Block 是独立单元 |
| 高级特性 | 难以实现 | 天然支持显存共享(Beam Search、并行采样时多个序列共享同一个前缀的 KV-Cache) |
2. 工作流程图解
当你提交一个请求给 vLLM 时,这个过程分四步:
第一步:Block 分配
系统将 KV-Cache 切分为固定大小的 Block(默认 16 个 Token 一个 Block)。当请求到来时,不预分配全部空间,只分配第一个 Block,开始生成。
第二步:按需扩展
每生成 16 个新 Token,就动态申请一个新 Block,并通过指针链表串联。新 Block 不要求与旧 Block 在物理显存上连续——通过一个 Block Table 维护逻辑顺序。
第三步:注意力计算
计算注意力时,GPU Kernel 根据 Block Table 找到分散在显存各处的 KV-Cache Block,进行分段注意力计算。这增加了少量计算开销,但换来的是显存利用率的数量级提升。
第四步:释放回池
请求完成后,所有 Block 被标记为空闲,放回公共 Block 池。新请求可以直接复用,避免了频繁的显存分配释放。
3. 显存共享:PagedAttention 的“杀手锏”
这是 PagedAttention 相对于其他优化方案最独特的优势。
场景:你使用 Beam Search(束搜索)生成多个候选序列,或者对同一个提示词做并行采样。这些序列共享完全相同的前缀,但后续分支不同。
传统方案:为每个序列复制完整的 KV-Cache(包括相同的长前缀),显存随序列数线性增长。
PagedAttention 方案:前缀部分的 KV-Cache Block 可以在多个序列间物理共享。只有当序列从某个位置分叉时,分叉后的 Block 才写上“Copy-on-Write”(写时复制)标记——实际上是在逻辑上共享,直到某个序列需要修改该 Block 时才复制。
这在大规模服务场景中极其关键:假设你有一个 2000 Token 的长提示词,用户只改最后一句(如换个问题),2000 Token 的 KV-Cache 可以完全复用,节省大量计算和显存。
三、连续批处理(Continuous Batching):榨干 GPU 每一滴算力
vLLM 的另一个关键创新是连续批处理。
传统推理服务的批处理是“静态批处理”(Static Batching):等一批请求全部生成完成后,才一起返回并接收下一批。这意味着一个生成长度为 500 的请求,会拖慢同一批里只生成 10 个 Token 的请求。
vLLM 的实现是迭代级调度(Iteration-level Scheduling):
- GPU 每执行一次前向传播(生成一个 Token),调度器就检查一次当前所有请求的状态;
- 如果一个请求已经生成了结束符(EOS),立即将其从当前 Batch 中移除,释放其 KV-Cache Block;
- 同时,从等待队列中拉一个新请求加入 Batch,分配新 Block 开始生成。
直观类比:静态批处理是“公交车模式”——必须等所有乘客都到齐才发车,中途不下客;连续批处理是“出租车拼车”——随时上下客,座位(显存)利用率拉满。
四、vLLM 能快多少?真实性能预期
以下数据来自 vLLM 官方基准测试和社区实践(A100 80GB + Llama 13B):
| 指标 | HuggingFace(无优化) | vLLM(默认配置) | 提升幅度 |
|------|----------------------|-----------------|---------|
| 单请求延迟(TTFT) | 接近 | 接近 | 持平(P40/T4 等设备生成速度基本一致) |
| 吞吐量(并发 50) | ~200 tokens/s | ~2500 tokens/s | 10–20× |
| 显存利用率 | 30%–40% | 80%–95% | 2–3× |
| 单卡最大并发请求数 | 10–20 | 100–200+ | 10×+ |
关键洞察:vLLM 不改变单个 Token 的生成速度,但大幅提升并发吞吐量。 如果你的场景是低并发的交互式应用(如单用户聊天),vLLM 的收益有限;但一旦涉及高并发 API 服务,就是质变。
五、快速部署:从零到生产级 API
1. 安装
# 推荐使用 pip 安装(CUDA 12.1 环境)
pip install vllm
# 如果使用特定 CUDA 版本,可从源码编译
# 注意:vLLM 对 CUDA 和 PyTorch 版本有强依赖,建议用 Docker
生产环境强烈建议使用官方 Docker 镜像:
docker pull vllm/vllm-openai:latest
2. 启动服务
vLLM 兼容 OpenAI API 格式,可以直接用任何 OpenAI 客户端库对接。
方式一:命令行启动(最简单)
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2-7B-Instruct \
--tensor-parallel-size 1 \
--max-model-len 4096 \
--gpu-memory-utilization 0.95
--model:HuggingFace 模型名或本地路径--tensor-parallel-size:张量并行卡数(单卡=1,两卡=2)--max-model-len:最大上下文长度(越小越省显存)--gpu-memory-utilization:显存使用比例上限(0.95 表示允许用满 95% 显存)
方式二:Docker 部署(生产推荐)
docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
vllm/vllm-openai:latest \
--model Qwen/Qwen2-7B-Instruct \
--max-model-len 4096
服务启动后,你会在终端看到类似以下日志:
INFO: Uvicorn running on http://0.0.0.0:8000
3. 调用测试
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed" # vLLM 不需要真实 API Key
)
response = client.chat.completions.create(
model="Qwen/Qwen2-7B-Instruct",
messages=[
{"role": "user", "content": "用三句话解释什么是 Transformer"}
],
max_tokens=200,
temperature=0.7
)
print(response.choices[0].message.content)
4. 生产环境关键参数调优
| 参数 | 建议值 | 说明 |
|------|--------|------|
| --gpu-memory-utilization | 0.90–0.95 | 越大显存利用率越高,但留一些余量给 CUDA Graph 等内部开销 |
| --max-num-seqs | 128–256 | 最大并发序列数;太大可能因 Block 过多导致调度开销增加 |
| --max-model-len | 按需设置 | 如果不需要长上下文,设为 2048 或 4096 可大幅降低显存占用 |
| --dtype | auto | 通常自动选择最优精度,也可手动指定 float16 或 bfloat16 |
| --enforce-eager | False(默认) | 默认使用 CUDA Graph 加速;如果遇到报错,改为 True 可降级到 eager 模式 |
| --enable-prefix-caching | True | 启用前缀缓存(Prefix Caching),对相同 System Prompt 的请求自动复用 KV-Cache |
Prefix Caching 是 vLLM 从 2024 年起支持的杀手级功能:如果你的应用有很长的 System Prompt(如 500 Token),所有请求的前 500 Token KV-Cache 会被自动缓存。首个请求后,后续请求直接跳过前缀计算,TTFT(Time to First Token)可降低 50% 以上。
六、多卡部署:当单卡装不下模型
vLLM 支持两种多卡模式:
1. 张量并行(Tensor Parallelism)
将模型的权重矩阵切分到多张卡上,适合单模型装不下的场景:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2-72B-Instruct \
--tensor-parallel-size 4 \ # 用 4 张卡
--gpu-memory-utilization 0.95
72B 模型 FP16 需要 ~144GB 显存,单张 A100 80GB 装不下,张量并行到 4 张卡即可。
2. 流水线并行(Pipeline Parallelism)
适合超大模型跨节点部署,vLLM 目前对流水线并行的支持仍在完善中,张量并行是更成熟的选择。
七、常见问题与排障
问题 1:启动时 OOM(显存溢出)
- 检查
--gpu-memory-utilization是否设太高(先试 0.85) - 减小
--max-model-len - 确保没有其他进程占用 GPU(
nvidia-smi检查)
问题 2:吞吐量远低于预期
- 增大
--max-num-seqs,让更多请求同时进入 Batch - 检查请求的生成长度分布——如果大部分请求只生成几个 Token,批量调度的收益会被稀释
- 确认已启用 Prefix Caching(
--enable-prefix-caching)
问题 3:与 HuggingFace Transformers 输出不同
- vLLM 为了性能,浮点运算顺序可能与 HuggingFace 略有差异,导致极小偏差(通常 < 1e-5)
- 如果出现显著差异,检查
--dtype是否一致,以及采样参数(temperature, top_p)是否相同
八、vLLM 的局限与替代选择
局限:
- 模型支持范围:主要支持纯文本 Decoder 模型(Llama、Qwen、Mistral 等),多模态和 Encoder-Decoder 模型的支持仍在快速迭代中;
- 单请求延迟:vLLM 优化的是吞吐量,不是单请求延迟。如果你需要极致低延迟,TensorRT-LLM 或 SGLang 可能更合适;
- 自定义模型支持复杂:高度定制化的模型结构可能需要自行实现 vLLM 的模型适配层。
替代方案速查:
| 框架 | 核心优势 | 适用场景 |
|------|---------|---------|
| vLLM | 高吞吐、PagedAttention、前缀缓存 | 高并发 API 服务(主要推荐) |
| TensorRT-LLM | NVIDIA 官方优化,极致低延迟 | 需要绑定 NVIDIA 生态的低延迟场景 |
| SGLang | RadixAttention(比 PagedAttention 更激进的前缀共享) | 同类 Prefix 场景极多的服务(如多轮对话) |
| TGI | HuggingFace 官方,生态兼容性最好 | 需要与 HF Hub 无缝集成的快速原型 |
在 23.2 节,我们将深入 TensorRT-LLM,看 NVIDIA 如何通过图编译优化将推理延迟压到极致。