PagedAttention:操作系统级 KV 缓存虚拟内存管理
PagedAttention:操作系统级 KV 缓存虚拟内存管理
引言
在大型语言模型(LLM)的推理过程中,KV 缓存(Key-Value Cache) 是用来存储历史 token 的注意力键值对,避免每次生成新 token 时重复计算。然而,随着序列长度增长,KV 缓存的内存占用呈线性膨胀,且传统框架采用连续预分配方式,导致严重的内存碎片和显存浪费。PagedAttention 借鉴操作系统的分页内存管理思想,将 KV 缓存划分为固定大小的块,按需映射物理内存,实现了接近零碎片的显存利用与灵活的内存共享。本教程将带你从问题出发,深入理解 PagedAttention 的设计原理与实现细节。
一、LLM 推理的内存挑战
1.1 KV 缓存的作用
在自回归生成中,模型每生成一个新 token,就会计算当前输入序列的 Key 和 Value 张量,并与过去的 KV 拼接,用于注意力计算。把先前所有 token 的 K、V 保存下来,就形成了 KV 缓存:
Attention(Q, K, V) = softmax(QK^T / √d) V
如果不缓存,每次都需要对越来越长的完整序列重新计算 K、V,计算量随生成步数平方增长。缓存后每一步只需计算新 token 的 K、V,然后追加到缓存中。
1.2 连续内存分配带来的问题
传统推理引擎(如 Hugging Face Transformers、FasterTransformer 早期版本)为每个序列预先分配一块能容纳 最大可能长度 的连续 KV 缓存空间。这带来两大缺陷:
- 碎片化严重:不同请求可能先后到达,预分配导致已释放的空间无法被新请求利用,内部和外部碎片都很高,实际批处理大小受限。
- 无法共享:即使多个请求具有相同的前缀(如系统提示),仍然各自存储独立的 KV 缓存,造成显存冗余。
示例:假设最大长度为 2048,每个请求分配 2GB 连续显存。当一批请求结束或长度变化时,这些大块内存难以动态复用,导致显存利用率通常低于 30%。
二、PagedAttention 的核心思想
2.1 操作系统虚拟内存的灵感
操作系统将物理内存分成固定大小的页(Page),进程使用虚拟地址,通过页表映射到不连续的物理页。这带来了:
- 消除外部碎片;
- 按需分配物理内存;
- 多进程共享同一物理页(如动态库)。
PagedAttention 将同样的思路应用于 KV 缓存:把序列的 KV 缓存划分为固定大小的 KV 块(Block),每个块存储固定数量 token 的 K、V。序列的 KV 缓存由多个逻辑块组成,这些块在显存中可以不连续。
2.2 三个关键抽象
| 概念 | 操作系统 | PagedAttention |
|---|---|---|
| 基本单位 | 物理页(Page) | KV 块(Block),每个块含固定 token 数的 K、V |
| 映射机制 | 虚拟页 → 物理页帧 | 逻辑块 → 物理块 |
| 分配策略 | 按需调页,延迟分配 | 仅当序列增长时分配新块,无需预分配最大长度 |
| 共享机制 | Copy-on-write / 共享映射 | 相同前缀的序列共享同一组物理块 |
2.3 统一的块表(Block Table)
每个请求维护一张块表,记录其逻辑块序号到物理块地址的映射。计算注意力时,通过块表找到物理块,再按 token 偏移量索引。这与操作系统的页表解析过程非常相似。
三、PagedAttention 工作流程详解
3.1 块尺寸选择
块大小(block_size)是一个重要的可调参数。比如 block_size = 16,表示每个块存储 16 个 token 的 K 和 V。选择依据:
- 太大:内部碎片增加(最后一个块可能未填满);
- 太小:块数量增多,块表变大,并行调度复杂度上升。
- 通常取值为 8 ~ 64,需结合 GPU 线程束大小和内存事务特性调优。
3.2 请求的生命周期
3.2.1 预填充阶段(Prefill)
用户提示(prompt)一次性送入模型。PagedAttention 会为其分配足够数量的物理块来容纳所有初始 token。如果提示长度不是 block_size 的整数倍,最后一个块仅部分填充,其余位置保留 padding。
此阶段需要计算整个提示的 KV 并写入分配的块中。由于操作是并行完成的,效率很高。
3.2.2 生成阶段(Decoding)
每生成一个新 token:
- 计算该 token 的 K、V;
- 检验当前序列最后一块是否已满:
- 如果已满,向块管理器申请一个新物理块;
- 更新块表,追加映射;
- 将新 K、V 写入块的对应位置;
- 执行注意力计算,需遍历块表中的所有逻辑块。
注意力计算需要访问所有块的 K、V,此时利用 GPU 并行性,一次性加载块表,从非连续物理地址聚集数据。
3.3 内存管理与碎片消除
块管理器维护空闲物理块列表,分配策略类似于操作系统物理内存分配:
- 当请求完成或退出,其占用块被回收,重新加入空闲池;
- 任何空闲块都可以分配给任何新请求,不受原先连续约束;
- 内部碎片仅存在于每个序列的最后一个块,整体显存利用率可达 99% 以上。
3.4 前缀共享(Prefix Sharing)
多个请求可能共享相同的前缀(例如,同一个系统提示:“You are a helpful assistant.”)。传统方式每个请求各自存储这部分 KV,造成 N 倍冗余。
PagedAttention 通过引用计数实现物理块共享:
- 系统维护一个前缀块池,存储常见前缀的 KV 缓存;
- 当请求命中前缀,其初始块表中的逻辑块直接映射到共享的物理块;
- 这些物理块被标记为只读,当请求后续需要写入(例如生成产生了分歧),采用 Copy-on-Write 策略:在写入前复制该块,并更新该请求的块表映射到新块,原共享块引用计数减一。
这极大地减少了冗余,尤其对多轮对话和批量类似请求的场景显存节省可达数十倍。
四、GPU 内核实现要点
4.1 分块注意力计算
由于 K、V 物理上不连续,注意力内核必须能处理块表访问模式。典型实现采用专门编写的 CUDA Kernel:
- 每个线程块负责一个注意力头的部分计算;
- 从块表中读取该序列所有块地址,循环加载每个块的 K、V;
- 使用寄存器/共享内存缓存当前 token 的 Q,逐步完成 softmax 收缩。
vLLM 等框架的 PagedAttention 内核利用 FlashAttention 风格的分块 softmax 算法,进一步优化 IO。
4.2 块访问的合并优化
块地址不连续,但每个块内部是连续的显存区域。内核可以在块内使用向量化加载,且块大小对齐 GPU 缓存行(128 字节),最大化访存效率。
4.3 动态块调度
批处理时,多个请求的序列长度不同,所需的块数量也不同。PagedAttention 允许动态分配,配合 continuous batching,新的请求可以立即复用刚释放的块,实现更高吞吐。
五、与操作系统的类比总结
- 虚拟地址 = 逻辑 token 位置:通过(块索引, 块内偏移)定位序列中的任意 token。
- 页表 = 块表:记录逻辑块到物理块的映射,并支持按需调入(分配新块)。
- 页面置换:在显存使用接近容量时,可采用交换(offloading)策略将不活跃的块移出到 CPU 内存,类似 OS 的页面换出。
- Copy-on-Write:用于前缀共享,保证多个序列独立修改时不相互影响。
六、与竞品方案对比
| 特点 | 连续预分配 | PagedAttention |
|---|---|---|
| 内存碎片 | 高(内外碎片) | 极低(仅块内未使用) |
| 复用性 | 差,无法按需调整 | 极好,动态分配回收 |
| 共享能力 | 无硬件支持 | 原生支持块级 COW 共享 |
| 实现复杂度 | 简单 | 需要自定义块管理器和内核 |
使用 PagedAttention 的推理引擎(如 vLLM)在实际用例中将 batch size 可提升 3~5 倍,吞吐量提升 2~4 倍,同时显存占用大幅降低。
七、快速上手指南(基于 vLLM)
如果你希望立即体验 PagedAttention,通过 vLLM 只需几行代码:
from vllm import LLM, SamplingParams
# 模型会自动使用 PagedAttention 管理 KV 缓存
llm = LLM(model="meta-llama/Llama-2-7b-hf", block_size=16)
prompts = ["Explain quantum computing in simple terms."]
sampling_params = SamplingParams(temperature=0.8, max_tokens=200)
outputs = llm.generate(prompts, sampling_params)
print(outputs[0].outputs[0].text)
vLLM 内部为你完成了所有分页管理、前缀共享和高效内核调度,无需额外配置。
八、总结与展望
PagedAttention 将操作系统的成熟设计范式引入 LLM 推理,实现了 KV 缓存细粒度的动态管理,解决了困扰推理系统的内存碎片和共享难题。这不仅提高了单 GPU 的吞吐,也让长上下文、大批量服务成为可能。
未来方向包括:
- 分层块管理:利用 GPU 显存、CPU 内存和 NVMe 形成三级存储,自动 swap;
- 自适应块大小:根据序列特性动态调整块尺寸,进一步减少碎片;
- 分布式跨 GPU 共享:在多 GPU 推理中利用块表实现全局 KV 缓存池。
参考资料
- vLLM 官方文档
- Efficient Memory Management for Large Language Model Serving with PagedAttention (SOSP '23)