LLM 推理服务系列 · 第 1 篇
2023 年 6 月,UC Berkeley 的团队发布 vLLM,直接把 LLM 推理吞吐提升了 24 倍(相比 HuggingFace Transformers 默认实现)。一篇论文一个开源项目,半年内 GitHub 冲到 40K+ stars,成了 LLM 推理服务的默认选择。
这个系列第一篇拆 vLLM------因为它对 LLM 推理做了最根本的工程革新:PagedAttention。理解了这个,TGI、Triton 的优化思路你都能举一反三。
问题:LLM 推理真正慢在哪
推理过程分两步:
Prefill(预填充):输入 Prompt 的所有 Token 并行计算 → 产生 KV Cache
Decode(解码): 逐个生成新 Token,每步只做一次 Attention → 反复利用 KV Cache
Prefill 是计算密集型------GPU 算力打满。Decode 是内存密集型------每生成一个 Token,需要从显存里读出所有历史 Token 的 KV Cache 做 Attention。KV Cache 在 Decode 阶段才是真正的瓶颈:
yaml
一个请求:Prompt 1000 Token + 生成 500 Token
→ KV Cache 大小 ≈ 2 × 96层 × 128头 × 128维 × 1500 Token × 2 字节(FP16)
→ ≈ 11.8 GB
普通做法为每个请求预留最大序列长度(比如 2048)的 KV Cache------一个请求还没开始生成,就已经吃掉了约 16 GB 显存。这导致了三个后果:
- 显存碎片化------预分配的内存不能共享,A100 80GB 只能同时服务约 4-5 个请求
- 利用率低------大部分预留的 KV Cache 实际没被使用(请求通常达不到最大长度)
- 无法动态调度------预留后不能调整,新请求进不来即使显存有空
PagedAttention:把 KV Cache 按"页"管理
vLLM 的核心洞察:KV Cache 的管理问题跟操作系统的虚拟内存管理是一回事。 操作系统把物理内存分成固定大小的 Page,进程按需分配------vLLM 把 KV Cache 也按固定大小分块。
css
传统方式:每个请求预分配一整块连续 KV Cache
============================================
[请求A: KV Cache 全部预分配,即使只用 30%]
[请求B: KV Cache 全部预分配,即使只用 50%]
[请求C: 等不到了------显存被 A 和 B 占满了]
============================================
vLLM PagedAttention:
============================================
[A 的 Block 1][B 的 Block 1][A 的 Block 2][空闲 Block]
[B 的 Block 2][A 的 Block 3][B 的 Block 3][空闲 Block]
[空闲 Block ][空闲 Block ][C 的 Block 1][空闲 Block]
============================================
请求 A、B、C 的 KV Cache 交错存储在物理 Block 中
每个请求按需申请 Block,用完释放
PagedAttention 的三个效果:
- 零浪费------请求需要多少 Token 就申请几个 Block,不预留
- 高并发------显存被精细分配,A100 80GB 从同时服务 5 个请求变成 50+ 个
- 内存共享------多个请求生成同一个 Prompt 前缀(如 system prompt 相同)时,Block 可以被共享引用(Copy-on-Write 语义)
Continuous Batching:不等慢请求
传统推理服务用 Static Batching------攒一批请求一起算,但这批里有一个慢的(生成 500 Token),其他快的(生成 20 Token)也得等着。GPU 在等慢请求生成完的空隙------空闲。
vLLM 的 Continuous Batching:
less
时间线:
传统 Static Batching:
[Batch: A B C D]→等最慢的→[Batch: A B C D]→等最慢的→...
即使 C 早就生成完了,也得等 A
vLLM Continuous Batching:
[A B C D] → C 完成了 → [A B D E] → B完成了 → [A D E F]
↑ E 立即加入 ↑ F 立即加入
当一个请求生成完 EOS Token 后------它立即退出 batch------新请求立即加入。GPU 没有空闲的 batch slot。这跟 PagedAttention 配合起来:旧请求退出 → 释放 Block → 新请求立即使用。
架构总览
scss
HTTP/gRPC API
│
▼
┌────────────────┐
│ Scheduler │ ← 决定哪些请求进入当前 batch
│ (Continuous │ 基于显存可用 Block 数做准入控制
│ Batching) │
└────────────────┘
│
▼
┌────────────────┐
│ Block Manager │ ← 管理 KV Cache Block 的分配/释放/共享
│ (PagedAttention)│ 类似操作系统的虚拟内存管理器
└────────────────┘
│
▼
┌────────────────┐
│ GPU Workers │ ← 执行 Attention + FFN,读写 KV Cache Block
│ (CUDA Kernels) │
└────────────────┘
5 行代码启动一个兼容 OpenAI API 的服务
python
# 安装:pip install vllm
# 启动:
# python -m vllm.entrypoints.openai.api_server \
# --model meta-llama/Llama-3.1-8B-Instruct \
# --tensor-parallel-size 1 \
# --max-model-len 4096
# 调用------跟调用 OpenAI API 完全一样:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed"
)
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": "解释 PagedAttention"}],
temperature=0.7,
max_tokens=512
)
关键参数说明:
| 参数 | 作用 | 建议值 |
|---|---|---|
--tensor-parallel-size |
张量并行度(跨 GPU 切分模型层) | = GPU 数量 |
--max-model-len |
最大上下文长度 | 按需设置,越小显存越省 |
--gpu-memory-utilization |
允许使用的显存比例 | 0.90(留 10% 给 KV Cache 碎片) |
--max-num-seqs |
同时处理的最大请求数 | 越大并发越高但单个请求变慢 |
vLLM 的两个限制
-
不支持 LoRA 热切换(早期版本)------vLLM 最初设计是"加载一个模型,服务所有请求"。如果不同请求需要不同的 LoRA adapter,早期版本不友好。新版本已部分支持,但不如 TGI 成熟。
-
Prefill 阶段没有做 Chunked Prefill------超长 Prompt(>10K Token)的 Prefill 会一次吃掉大量计算时间,block 住 Decode 请求。TGI 和 TensorRT-LLM 做了 Chunked Prefill------把超长的 Prefill 切成小块,跟 Decode 交替执行。
什么时候选 vLLM
| 场景 | vLLM 适合? |
|---|---|
| 高并发 LLM API 服务 | 首选------PagedAttention 并发优势碾压 |
| 需要 OpenAI 兼容 API | 一行代码,完全兼容 |
| 单模型、多请求 | 最佳场景 |
| 需要 LoRA 热切换(多租户) | 可以但不最佳,考虑 TGI |
| 量化模型服务 | 支持 AWQ、GPTQ、FP8 等 |
| 超长 Prompt(>10K)服务 | Chunked Prefill 缺失,考虑 TGI 或 TensorRT-LLM |
一句话总结
vLLM 的核心创新是 PagedAttention------把操作系统的虚拟内存分页思想搬到 KV Cache 管理上,让显存利用率从 ~30% 跳到 ~95%。配合 Continuous Batching------请求随到随算、算完就走------吞吐相比原生 HuggingFace 提升 14-24 倍。它是当前最广泛使用的 LLM 推理引擎------不是因为算法多新颖------是因为把"显存管理"这个工程问题解决到了极致。
下一篇:TGI(Text Generation Inference)------HuggingFace 的官方推理服务。同样是 PagedAttention,TGI 哪里做得跟 vLLM 不同?为什么 LoRA 热切换和多模态推理选 TGI 更合适?