vLLM:PagedAttention 如何让 LLM 推理吞吐提升 24 倍

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 显存。这导致了三个后果:

  1. 显存碎片化------预分配的内存不能共享,A100 80GB 只能同时服务约 4-5 个请求
  2. 利用率低------大部分预留的 KV Cache 实际没被使用(请求通常达不到最大长度)
  3. 无法动态调度------预留后不能调整,新请求进不来即使显存有空

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 的三个效果:

  1. 零浪费------请求需要多少 Token 就申请几个 Block,不预留
  2. 高并发------显存被精细分配,A100 80GB 从同时服务 5 个请求变成 50+ 个
  3. 内存共享------多个请求生成同一个 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 的两个限制

  1. 不支持 LoRA 热切换(早期版本)------vLLM 最初设计是"加载一个模型,服务所有请求"。如果不同请求需要不同的 LoRA adapter,早期版本不友好。新版本已部分支持,但不如 TGI 成熟。

  2. 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 更合适?

相关推荐
向宜xy15 小时前
“试试这套 SDD 规范驱动工作流”---我认真研究了“AI乱改代码”的解决方案,然后问了三个问题
c·ai编程
众人皆醒我独醉15 小时前
TGI:HuggingFace 的官方推理服务——不止 PagedAttention,更懂模型生态
面试·llm·ai编程
windliang15 小时前
Claude Code 源码分析(三):一次模型回答如何流进 Agent
前端·算法·ai编程
张彦峰ZYF16 小时前
全球开源大模型生态-从开放权重到开放智能系统:发展、进展、主力模型成就与方向分析
人工智能·开源·llm·agent
小虎AI生活16 小时前
workbuddy 获客自动化,一个人就是一支数字人视频团队
ai编程
神奇小汤圆16 小时前
面试官突然问:“数仓哪一层最适合做RAG?”他一下被问住了…
面试
一只旭宝17 小时前
C++手写shared_ptr共享智能指针|原子引用计数、强弱引用控制块、赋值重载底层深度剖析
开发语言·c++·面试
神奇小汤圆17 小时前
工业级 RAG 为什么需要 Elasticsearch?一篇讲清索引、度量、过滤与 Milvus 选型
面试