LLM2023· SOSP 2023· CLASSIC

Efficient Memory Management for LLM Serving with PagedAttention (vLLM)

Kwon et al.

借鉴操作系统虚拟内存——把 KV Cache 分成固定大小的块,大幅减少内存碎片,吞吐提升 2-4×。

arXiv:2309.06180
#inference#systems#serving#kv-cache

核心贡献

  • 01诊断问题:传统 KV Cache 为每个请求预留最大长度连续空间 → 大量内部碎片 + 外部碎片
  • 02PagedAttention:KV Cache 分成固定 block(如 16 tokens),按需分配
  • 03Copy-on-Write 支持并行采样、beam search,多个序列共享前缀 block
  • 04比 HuggingFace Transformers 吞吐高 2-4×,比 TGI 高 1.5-2.5×
  • 05成为开源 LLM serving 事实标准

痛点

LLM serving 的吞吐瓶颈往往不是算力,是 KV Cache 内存

传统方案:每个请求预留 max_seq_len × d 的连续显存。问题:

  • 内部碎片:实际输出长度 < 预留 → 浪费
  • 外部碎片:不同长度请求动态分配/释放 → 无法利用零散空间
  • 实测显存利用率只有 20%-40%

PagedAttention

借鉴操作系统分页内存管理

  1. KV Cache 划成固定大小的物理块(block,如 16 个 token 的 KV)
  2. 每个请求维护一个逻辑块表(block table),映射逻辑位置到物理块
  3. 按需分配——只有实际用到的 token 才占用物理块

架构

text
1Logical blocks: [b0 b1 b2 b3 b4 ...] per request
2 ↓ block table
3Physical blocks: [PB5 PB2 PB8 PB1 PB3] GPU memory pool

每次 attention 计算时,通过 block table 把逻辑位置翻译成物理地址。CUDA kernel 一次内核完成这个间接寻址(否则 Python 层翻译太慢)。

Copy-on-Write

多个序列共享相同前缀时(如 beam search、并行采样、系统 prompt),物理块可共享。仅在某序列产生分歧时才复制。

Continuous Batching

传统 batching 必须等 batch 里所有请求都结束才开始下一批(最慢的拖累所有请求)。PagedAttention 使 continuous batching 成为可能:

  • 请求可以随时加入
  • 完成的请求立即释放物理块
  • 计算效率提升数倍

结果

  • A100 80GB 上,LLaMA-13B 吞吐从 ~20 req/s 升到 ~60 req/s
  • 显存利用率提高到 90%+
面试视角

面试考点

"传统 KV Cache 的问题?" 内部碎片(预留过大)+ 外部碎片(动态分配产生的空隙)。显存利用率仅 20%-40%。

"PagedAttention 的核心思想?" 把连续显存访问变成分页的间接访问——像操作系统做虚拟内存一样。block size 通常 16,平衡细粒度和 CUDA 访存效率。

"Continuous Batching 和 Static Batching 的区别?"

  • Static: batch 固定,必须等最慢请求结束
  • Continuous: 请求随时加入/退出,GPU 永远满载
  • vLLM 是最早让 Continuous Batching 好用的系统

"vLLM vs TGI vs TensorRT-LLM?"

  • vLLM: 开源、易用、Python 生态
  • TGI (HuggingFace): 生态集成好
  • TensorRT-LLM: NVIDIA 官方,性能极致但闭源
  • 新秀:SGLang(用 RadixAttention 优化前缀共享)

"KV Cache 量化和 PagedAttention 能组合吗?" 可以——vLLM 支持 INT8 KV Cache。进一步压缩内存换吞吐。但注意精度损失对长上下文尤其敏感。

"Prefix Caching 是什么?" vLLM 的扩展——显式缓存 system prompt 等固定前缀的 KV,避免重复计算。SGLang 的 RadixAttention 更系统地做这个。

"Decoding 的瓶颈?" Prefill(处理 prompt)是 compute-bound;Decoding(生成每个 token)是 memory-bound——需要读全部历史 KV Cache 计算 attention。减少 KV 内存 = 提吞吐。

相关论文