跳转至

PagedAttention

导言

PagedAttention 是 vLLM 高吞吐推理的核心内存管理机制。它没有改变 Attention 的数学公式,也没有消灭 KV Cache,而是把每条请求持续增长的 KV Cache 切成固定大小的 Block,通过 Block Table 将逻辑连续的 Token 映射到不连续的 GPU 物理块。

它解决的核心矛盾是:LLM 服务需要保留大量、长度未知且生命周期不同的 KV Cache,但 GPU 显存有限,传统连续分配容易产生预留浪费和碎片。 更高的显存利用率允许 vLLM 同时容纳更多请求,进而扩大 Batch、提高吞吐并降低高负载下的排队延迟。

一句话结论:PagedAttention 主要让 KV Cache 放得更紧、分得更灵活、共享得更容易;它通常不是让单次 Attention 计算变少,而是让同一块 GPU 能同时服务更多请求。

先看直观解释

可以把 GPU 显存想成一个仓库。每个推理请求都在不断产生 KV Cache,但系统事先不知道它最终会生成多少 Token。

传统连续分配通常面临两种选择:

  1. 按最大长度预留。 请求可能只生成几百个 Token,却提前占下可容纳几千个 Token 的连续空间,大量显存长期闲置。
  2. 随长度动态扩容。 请求增长后可能找不到紧邻空间,只能重新分配、复制,或者被散落的小空洞阻塞。

PagedAttention 把仓库划成固定大小的格子。请求需要更多空间时再领取新格子;这些格子不必相邻,只需用一张表记录它们的顺序。

假设一个 KV Block 容纳 16 个 Token,请求实际使用 600 个 Token:

  • 按 4096 Token 上限预留,需要保留 4096 个 Token 的空间;
  • 按 Block 分配,只需 \(\lceil 600/16 \rceil=38\) 个 Block,即 608 个 Token 的空间;
  • 额外浪费只出现在最后一个未填满的 Block 中。

类比的边界

KV Block 不是普通文件块,Attention Kernel 仍要按每层、KV Head、Token 和 Head Dimension 读取高维 K/V 张量。PagedAttention 也不是操作系统替 vLLM 处理透明缺页;Block 的分配、映射和回收由推理引擎显式管理。

再看真实机制

KV Cache 为什么难管理

自回归推理分为两个主要阶段:

  1. Prefill 处理 Prompt,为各层、各 Token 生成 K/V。
  2. Decode 每轮生成新 Token,追加它的 K/V,并让新 Query 读取历史 K/V。

设模型层数为 \(L\),KV Head 数为 \(H_{kv}\),每个 Head 的维度为 \(d\),每个元素占 \(s\) 字节。忽略对齐和元数据后,每个 Token 的 KV Cache 大小约为:

\[ M_{token}=2LH_{kv}ds \]

其中系数 2 分别对应 Key 和 Value。单个请求长度从 \(t\) 增加到 \(t+1\) 时,所有层都要追加新的 K/V;大量请求又会在不同时间到达、增长和结束。因此,KV Cache 同时具有 体积大、长度动态、生命周期不一致 三个特征。

Block Table 如何工作

PagedAttention 把一条请求逻辑连续的 KV Cache 划分为 Logical Blocks,再从全局显存池分配 Physical Blocks:

请求的逻辑顺序:  L0  ->  L1  ->  L2  ->  L3
Block Table:     P7     P2     P9     P4
GPU 物理位置:    P2  P4  P7  P9  ...

Attention Kernel 读取 Block Table,把逻辑块编号转换为物理 Block ID。于是请求看到的 KV Cache 仍按 Token 顺序连续,物理显存却不需要连续。

当序列继续生成时,系统只需在当前 Block 写入新 K/V;Block 填满后再分配一个空闲物理块并更新映射。请求结束后,其不再被引用的 Block 可以返回空闲池,供其他请求复用。

下图把连续预留与分页映射放在同一张图中,重点观察 KV Cache 的物理布局如何逐步影响并发容量和系统吞吐

PagedAttention 连续预留与分页 KV Cache 对比

根据本次会话内容整理的自绘示意图:左侧是连续预留引起的浪费和碎片,中间是 Logical Block 经 Block Table 映射到非连续 Physical Block,右侧是显存效率向并发和吞吐的传导。

图中的关键不是 Block Table 本身节省计算,而是它解除了“逻辑连续必须等于物理连续”的约束。vLLM 因此能够按需分配 KV Block,并把节省下来的显存用于容纳更多活跃请求。

PagedAttention 的优势

降低显存浪费

按需分配避免按最大序列长度预留完整 KV Cache。固定大小的 Block 也消除了“必须找到一整块连续大空间”的要求;内部碎片通常只剩每条序列最后一个未填满的 Block。

原始论文将这种目标概括为 near-zero waste。这是相对传统预留和连续分配的内存管理结论,不表示 KV Cache 本身不再占显存。

提高并发与吞吐

显存能容纳的活跃请求数近似受下面的容量关系限制:

\[ N_{active}\lesssim\frac{M_{GPU}-M_{weights}-M_{runtime}}{\operatorname{avg}(M_{KV/request})} \]

PagedAttention 减少分母中的无效预留和碎片后,调度器可以保留更多活跃请求,并通过 Continuous Batching 在每轮 Decode 中组成更大的有效 Batch。GPU 利用率和系统吞吐因此提高。

吞吐提升的因果链

更少显存浪费 → 更多驻留请求 → 更大的动态 Batch → 更高系统吞吐。 PagedAttention 本身不减少标准注意力需要读取的历史 K/V,也不保证单请求延迟按同样比例下降。

原始 vLLM 论文在其测试工作负载中,相较当时的 FasterTransformer 和 Orca 报告了约 2–4 倍吞吐提升。这个数字来自 PagedAttention、Block Manager、调度与执行 Kernel 的整体协同,不能解释成分页寻址单独让某个 Attention Kernel 快了 2–4 倍。

支持块级共享

相同前缀、Parallel Sampling 和 Beam Search 可能拥有相同的历史 K/V。块式布局允许多个逻辑序列引用同一批 Physical Blocks;只有分支真正产生不同内容时,才通过 Copy-on-Write 分配新块。

自动 Prefix Caching 也可以按完整 KV Block 建立 Hash,在后续请求命中相同前缀时复用已计算的 K/V。分页不是前缀缓存的充分条件,但提供了自然的共享和引用计数粒度。

改善调度与回收

Block 是统一的容量单位,调度器可以围绕空闲块数量进行接纳、抢占、回收和缓存淘汰。相较管理大量不同尺寸的连续分配,容量判断更明确,也不容易出现总空闲显存尚可、却找不到足够大连续区域的问题。

不用 PagedAttention 会怎样

“不用 PagedAttention”至少包含三种不同情况,不能混为一谈。

直接从 vLLM 删除

vLLM 的 KV Cache Manager、Scheduler、Block Table 和 Attention Backend 是配套设计。若直接删除 PagedAttention 而不提供替代实现:

  • Scheduler 分配的 Block 无法被 Kernel 正确寻址;
  • KV Cache 的追加、回收和复用失去对应布局;
  • Prefix Caching、共享和抢占恢复等能力失去底层契约;
  • 系统无法正常执行,而不只是性能下降。

因此,PagedAttention 不是一个可以独立关闭的显示选项。要移除它,必须同时替换 KV Cache 分配器、地址映射和兼容的 Attention Kernel。

替换成连续 KV Cache

如果使用正确实现的连续 KV Cache,模型仍能得到相同结果,但高并发服务通常会出现以下问题:

  1. 提前预留浪费。 按最大长度分配时,短请求占用大量未使用空间。
  2. 动态扩容困难。 按实际长度增长时,扩容可能需要重分配和数据复制。
  3. 外部碎片增加。 不同长度请求频繁到达和结束后,空闲空间被切成小块。
  4. 并发容量下降。 相同显存只能驻留更少请求,Continuous Batching 的 Batch 规模受限。
  5. 吞吐下降、排队增加。 高负载下,更多请求必须等待 KV 空间,尾延迟更容易恶化。
  6. 共享成本上升。 相同前缀或生成分支更容易复制整段连续 KV Cache。
  7. OOM 更难预测。 即使空闲容量总和足够,也可能没有满足要求的连续区域。

不过,PagedAttention 不是动态 KV Cache 管理的唯一可能方案。例如 vAttention 使用 GPU 虚拟内存机制维持连续虚拟地址,并按需映射物理显存,说明“不采用 PagedAttention”不等于“只能低效推理”。

完全关闭 KV Cache

这比不用 PagedAttention 更严重。没有 KV Cache 时,每生成一个 Token,都要重新处理已经计算过的前缀,反复生成历史位置的 K/V。显存中的持久 KV 状态减少了,但重复计算会使 Decode 显著变慢。

两者的职责应明确区分:

机制 解决的问题 缺失后的主要后果
KV Cache 避免重复计算历史 Token 的 K/V 每轮重新计算前缀,Decode 极慢
PagedAttention 高效分配、寻址、回收和共享 KV Cache 显存浪费与碎片增加,并发和吞吐下降

适用边界与常见误解

低并发场景收益有限

如果只有一个短请求,序列长度固定且显存充足,连续 KV Cache 已经足够简单高效。此时 PagedAttention 扩大并发容量的优势无从体现,Block Table 间接寻址还会增加实现复杂度。

不改变模型精度

PagedAttention 改变的是 KV Cache 的物理布局和访问路径。只要 Kernel 实现正确,逻辑 Token 顺序和注意力计算保持不变,因此不会因为分页本身产生近似误差。

不等于所有 vLLM 优化

Continuous Batching、Chunked Prefill、Prefix Caching、Speculative Decoding 和 KV Cache Quantization 是不同机制。它们可以建立在分页式 KV 管理之上并相互协作,但不能都归因于 PagedAttention。

不会降低单请求的理论读取量

标准 Decode Attention 仍需要让当前 Query 读取可见历史 K/V。PagedAttention 优化的是这些数据的管理和系统容量,并没有自动把长上下文注意力从稠密计算变为稀疏计算。

类比与术语对应

直观说法 专业术语
GPU 仓库 GPU HBM / 显存
请求不断产生的货物 持续增长的 KV Cache
固定大小的格子 Physical KV Block
请求内部的第几个格子 Logical Block
记录格子顺序的地址表 Block Table
最后一个格子未装满 Internal Fragmentation
空闲空间散落成小洞 External Fragmentation
多个请求共用已有格子 Block Sharing / Reference Counting
修改后才复制 Copy-on-Write
同时服务更多请求 Larger Active Batch / Higher Concurrency

自检问题

  1. PagedAttention 主要节省模型参数显存,还是 KV Cache 的无效占用?为什么?
  2. 两个请求的最大长度都是 4096,但最终分别使用 100 和 3000 个 Token。按最大长度连续预留与按 Block 分配会有什么差异?
  3. 只有一个短请求、长度固定且显存充足时,PagedAttention 是否仍必然显著加快推理?为什么?

参考文献

  1. Efficient Memory Management for Large Language Model Serving with PagedAttention, SOSP 2023.
  2. vLLM Paged Attention Kernel Design.
  3. vLLM Automatic Prefix Caching.
  4. vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention, ASPLOS 2025.

评论