本站内容由 AI 生成,可能存在错误。如发现问题,欢迎到 GitHub Issues 反馈。

PagedAttention 与 Continuous Batching

PagedAttention 与 Continuous Batching

更新于 2026-04-05

KV Cache 的内存困境

KV Cache 原理 中我们知道,推理时需要缓存每一层的 Key 和 Value 张量来避免重复计算。但传统实现有一个严重问题:为每个请求预分配 max_seq_len 的连续内存

假设 max_seq_len = 2048,但用户请求平均只用 512 个 token。这意味着 75% 的 GPU 显存被预分配但从未使用。更糟的是,当多个请求的 KV Cache 在显存中连续分配时,已完成的请求释放的内存会形成碎片(外部碎片),导致新请求可能因为找不到足够大的连续空间而无法启动。

内存分配对比:预分配 vs PagedAttention拖动滑块调整实际序列长度,观察内存浪费率变化12825651210241536tokens预分配 (max=2048)浪费 75.0%使用 25.0%PagedAttention (block=16)使用 100.0%序列长度 512 / max 2048 — PagedAttention 使用 32 个块512 slots预分配浪费 75.0% vs PagedAttention 浪费 0.0%— 节省 75.0% 内存

Kwon et al. (2023) 的实测数据显示,在真实 serving 场景中,KV Cache 占用了 GPU 显存的 60-80%,但其中超过 60% 被浪费在预分配和碎片上

操作系统的启示

这个问题和操作系统的内存管理非常相似。早期操作系统为每个进程分配连续的物理内存,导致同样的碎片问题。解决方案是虚拟内存 + 分页:每个进程看到连续的虚拟地址空间,但操作系统在背后将虚拟页映射到分散的物理页帧。

PagedAttention 将这个思想引入 KV Cache 管理:

操作系统概念PagedAttention 对应
虚拟页逻辑块(一组连续 token 的 KV)
物理页帧物理块(GPU 显存中的固定大小区域)
页表Block Table(逻辑块 → 物理块映射)
按需分配新 token 到来时才分配新物理块
页面大小Block size(通常 16 tokens)

PagedAttention 核心机制

每个请求的 KV Cache 被划分为逻辑块,每块存储固定数量的 token(如 16 个)的 Key/Value。逻辑块通过 Block Table 映射到 GPU 显存中的物理块,物理块无需连续。

Block Table:逻辑块 → 物理块映射点击 token 查看它在哪个逻辑块,映射到哪个物理块Thecatsatonthematandsleptforhours逻辑块 0逻辑块 1逻辑块 2Block Table逻辑块物理块031721物理内存 (GPU)P0P1← L2for hoursP2P3← L0The cat sat onP4P5P6P7← L1the mat and sleptP8P9物理块在 GPU 显存中无需连续 — 就像操作系统的虚拟内存分页

关键设计:

  1. 按需分配:不预分配 max_seq_len,而是每生成一个新 token,检查当前块是否还有空间。满了才分配新物理块
  2. 无外部碎片:物理块大小固定且统一,任何空闲块都可以被任何请求使用
  3. 极小内部碎片:浪费只发生在每个请求的最后一个块(未填满的部分),平均浪费 < block_size / 2 个 token
内存碎片率对比切换不同并发请求数,观察碎片率变化4 请求16 请求64 请求内部碎片预分配 max_len 导致的未使用空间40%连续分配3%PagedAttention外部碎片请求间内存间隙无法利用30%连续分配0%PagedAttention总浪费率70%3%PagedAttention 将外部碎片降为 0%,内部碎片仅来自最后一个块(< block_size tokens)

Copy-on-Write

Beam search 和 parallel sampling 场景中,多个候选序列共享相同的前缀。PagedAttention 用 Copy-on-Write (CoW) 优化:多个序列的 Block Table 可以指向同一个物理块,只在某个序列需要修改该块内容时才复制。

每个物理块维护一个 ref_count(引用计数)。当 ref_count > 1 时写入会触发复制。这和 Linux 的 fork() + CoW 机制完全一致。

初始:Beam 1 和 Beam 2 共享前缀
Prompt 阶段 — 物理块共享Beam 1Beam 2Block 02Block 12两个 beam 指向同一组物理块,ref_count = 2

Continuous Batching

有了 PagedAttention 高效管理内存后,下一步是优化请求调度。

传统 Static Batching(Orca 之前的做法):将一批请求打包在一起,等所有请求都完成才处理下一批。短请求被长请求”拖后腿”,GPU 在等待期间大量空闲。

Continuous Batching(Orca 提出的 iteration-level scheduling):在每一个 decode iteration 结束后检查,已完成的请求立即释放 slot,等待队列中的新请求立即填入。没有空闲等待,GPU 利用率最大化。

Continuous Batching 时间线3 个 GPU slot,请求动态进出,完成即释放Slot 0Slot 1Slot 201234567891011121314ABCDED 到达E 到达Prefill(深色)Decode(浅色)新请求到达请求 A/C 完成后 slot 立即被 D/E 填入 — 无空闲等待,GPU 利用率最大化

性能分析

三种批处理策略在不同并发数下的吞吐量差异:

吞吐量对比:三种批处理策略Hover 查看具体数值0255075100吞吐量 (req/s)148163264并发请求数Static BatchDynamic BatchContinuous Batch

核心结论:

  • 低并发(1-4 请求):三种策略差异不大,因为 GPU 本身没被充分利用
  • 高并发(16+ 请求):continuous batching 的优势呈指数级增长,因为它避免了长尾请求阻塞整个 batch
  • 实测数据:vLLM 在 PagedAttention + continuous batching 下,吞吐量比 HuggingFace Text Generation Inference 高 2-4 倍,比原始 transformers 高 24 倍

总结

PagedAttention 通过”虚拟内存分页”思想解决了 KV Cache 的内存浪费问题,Continuous Batching 通过”iteration-level 调度”解决了请求间的 GPU 空闲问题。两者结合使得 vLLM 能同时服务更多请求、更高效利用 GPU——这也是为什么 vLLM 成为云端 LLM serving 的事实标准。

延伸阅读