4、PagedAttention核心思想:借鉴操作系统虚拟内存分页机制,将KV Cache划分为固定大小的块(Block)

在大语言模型推理过程中,KV Cache 的显存占用是制约吞吐量和并发数的核心瓶颈。传统实现中,KV Cache 以连续内存方式为每个请求预留完整空间,导致严重的内部碎片和外部碎片。PagedAttention 的突破性思路,是将操作系统虚拟内存的分页机制引入 GPU 显存管理:把连续的 KV Cache 逻辑空间切分为固定大小的"页"(Block),通过块表(Block Table)建立逻辑页到物理块的映射,从而让显存分配像操作系统管理内存一样灵活、高效。本章将深入拆解这一核心思想的原理、实现路径与工程实践。

PagedAttention 分页映射机制 逻辑 KV Cache 空间 (连续虚拟地址) Logical Block 0 Logical Block 1 Logical Block 2 Logical Block 3(未分配) 块表(Block Table) 逻辑块 → 物理块 LB0 → PB7 LB1 → PB3 LB2 → PB9 LB3 → 未映射 GPU 物理显存 (非连续块) PB0 PB1 PB2 PB3 PB4 PB5 PB6 PB7 PB8 PB9 PB10 PB11 传统连续分配 vs PagedAttention 分页分配 • 传统方式:为每个请求预留 max_seq_len × hidden_size × 2 的连续显存,实际使用率可能不足 40% • PagedAttention:按需分配固定 Block(如 16 token/块),物理块可离散存放,显存利用率接近 100% • 块表维护逻辑到物理的映射,Attention 计算时通过块表索引访问 KV,支持并行解码与 Beam Search 典型 Block 配置参数(以 Llama-7B 为例) • Block Size(token 数):16 或 32,影响块表大小与内存碎片粒度 • 每 Block 显存占用 = Block_Size × num_layers × 2 × head_dim × num_kv_heads × dtype_size • 例:Block_Size=16, 32层, 8 KV头, head_dim=128, FP16 → 16×32×2×128×8×2 = 4,194,304 字节 ≈ 4MB/Block • 块表条目数 = 总物理块数,每条目记录物理块地址 + 引用计数(用于 Copy-on-Write)

4.1 核心原理:从连续内存到分页管理的范式转换

传统 KV Cache 实现中,推理引擎为每个序列预先分配一段连续显存,大小由 max_seq_len × hidden_size × num_layers × 2 × dtype_size 决定。这种"一次性预留"策略存在两个致命问题:其一,实际生成 token 数远小于最大长度时,预留空间大量闲置,造成内部碎片;其二,不同请求的生命周期不同,频繁分配释放导致显存空洞,即外部碎片。以 7B 模型为例,若最大序列长度 2048,单请求 KV Cache 预留约 1GB 显存,但实际平均生成 512 token 时,利用率仅 25%。

PagedAttention 彻底改变了这一格局。它将 KV Cache 的逻辑地址空间划分为固定大小的 Block,每个 Block 可容纳固定数量的 token 对应的 Key 和 Value 张量。物理显存中,Block 可以存放在任意位置,无需连续。每个序列维护一张块表(Block Table),记录其逻辑 Block 到物理 Block 的映射关系。Attention 计算时,GPU kernel 通过块表间接寻址,按块读取 KV 数据参与计算。

核心类比:操作系统虚拟内存中,进程看到的是连续虚拟地址空间,实际物理页可离散分布,通过页表完成映射。PagedAttention 中,模型看到的是连续的 KV 序列,实际 Block 可散落显存各处,通过块表完成映射。两者本质相同——用间接层换取分配灵活性和利用率。

4.2 块表结构与寻址机制

块表是 PagedAttention 的核心数据结构。每个序列(Sequence)拥有独立的块表,块表条目数等于该序列当前占用的逻辑 Block 数。每个条目包含两个关键字段:物理块地址(指向 GPU 显存中的实际 Block)和引用计数(用于支持 Copy-on-Write 优化)。

Attention 计算时,给定 token 位置 pos,首先计算逻辑块号 logical_block = pos / block_size,块内偏移 offset = pos % block_size。然后通过块表查询物理块地址 physical_block = block_table[logical_block],最终在物理块的 offset 位置读取 KV 数据。整个过程涉及两次内存访问(块表 + 数据),但 GPU 的 L2 Cache 能有效缓存块表热点数据,性能开销可忽略。

参数 典型值 说明
Block Size(token 数) 16 / 32 越小碎片越少,但块表越大;越大块表越小,但内部碎片增加
块表条目大小 8 字节 4 字节物理地址 + 4 字节引用计数
块表总大小 序列数 × 最大块数 × 8B 通常小于 1MB,可常驻 GPU L2 Cache
物理块总数 显存容量 / 单块大小 例如 80GB 显存,单块 4MB → 约 20000 块

4.3 分配与释放流程:按需分配与引用计数

PagedAttention 的显存分配遵循"按需分配、用完即还"原则。具体流程如下:

  1. 初始化:推理引擎启动时,将可用显存划分为固定大小的物理块池(Block Pool),维护空闲块链表。
  2. 请求到达:为序列创建空块表,初始不分配任何物理块。
  3. Prefill 阶段:根据输入 prompt 的 token 数,计算所需块数 ceil(prompt_len / block_size),从空闲链表中取出对应数量的物理块,填入块表。
  4. Decode 阶段:每生成一个新 token,检查当前最后一个逻辑块是否已满。若已满,则从空闲链表分配一个新物理块,追加到块表末尾。
  5. 序列结束:释放该序列所有物理块,归还空闲链表,清除块表。

引用计数机制则服务于 Beam Search 和并行采样场景。当多个序列共享同一个前缀时(如 Beam Search 中多个候选序列共享已生成的前缀),无需为每个序列复制前缀的 KV Cache,而是让它们的块表条目指向相同的物理块,并将引用计数加 1。当某个序列不再需要该前缀时,引用计数减 1;仅当计数归零时,物理块才真正释放。这种 Copy-on-Write 策略极大节省了显存。

工程经验:Block Size 的选择需要权衡。过小(如 8)会导致块表过大且 Attention kernel 的块内并行度不足;过大(如 64)则内部碎片增多。实践中,16 或 32 是多数场景的甜点值。对于长序列推理(如 32K 上下文),建议使用 32 以减少块表开销。

4.4 案例拆解:多请求并发下的显存节省效果

我们以一个具体场景对比传统方案与 PagedAttention 的显存表现。假设 GPU 为 A100-80GB,模型为 Llama-7B(FP16),单层 KV Cache 大小为 2 × 128 × 8 × 2B = 4KB/token,32 层共 128KB/token。设置最大序列长度 2048,Block Size 为 16。

场景设定:同时处理 64 个请求,每个请求实际生成长度分布为:32 个请求生成 256 token,16 个请求生成 512 token,16 个请求生成 1024 token。所有请求的 prompt 长度均为 128 token。

传统连续分配:每个请求预留 2048 token 的 KV 空间,即 2048 × 128KB = 256MB。64 个请求共需 64 × 256MB = 16GB。实际使用量:32×(128+256) + 16×(128+512) + 16×(128+1024) = 32×384 + 16×640 + 16×1152 = 12288 + 10240 + 18432 = 40960 token,对应 40960 × 128KB = 5GB。显存利用率仅 31.25%。

PagedAttention 分页分配:每个请求按实际 token 数分配 Block。总 token 数 40960,Block Size 16,共需 40960 / 16 = 2560 个 Block。每个 Block 大小 16 × 128KB = 2MB。总显存占用 2560 × 2MB = 5GB。与传统方案相比,节省了 11GB 显存,相当于多容纳 43 个同等规模的请求。

指标 传统连续分配 PagedAttention 提升
预留显存 16 GB 5 GB 节省 68.75%
显存利用率 31.25% ~100% 3.2 倍
可并发请求数(80GB) 约 312 个 约 1000 个 3.2 倍
块表额外开销 无 2560 × 8B ≈ 20KB 可忽略

实际部署中,vLLM 通过 PagedAttention 将 Llama-7B 的推理吞吐量提升了 2-4 倍,核心原因就是显存利用率的大幅提升使得 batch size 可以成倍增加。值得注意的是,块表本身也占用显存,但相比节省的空间,这部分开销微乎其微。

4.5 常见误区与工程复盘

误区一:认为 Block Size 越小越好。虽然小 Block 减少内部碎片,但会导致块表膨胀、Attention kernel 的块内并行度下降,反而降低计算效率。实测中,Block Size 从 16 降到 8,吞吐量可能下降 10-15%。
误区二:忽略 Copy-on-Write 的引用计数管理。在 Beam Search 中,若引用计数更新不及时,可能导致物理块被提前释放,造成数据竞争和推理错误。必须使用原子操作保证计数一致性。
误区三:认为 PagedAttention 只适用于自回归解码。实际上,PagedAttention 同样适用于 Prefix Caching(前缀缓存)、Prompt 共享等场景。只要存在 KV Cache 的复用,分页机制都能带来收益。

复盘经验:在将 PagedAttention 集成到生产系统时,建议按以下步骤验证:首先,用 profiling 工具统计实际 KV Cache 使用量分布,确定合理的 Block Size;其次,在单卡环境对比传统方案与 PagedAttention 的吞吐量和延迟;最后,在分布式环境(如 Tensor Parallel)中验证块表同步机制的正确性。特别要注意,当使用张量并行时,每个 GPU 持有部分 KV Head,块表需要在各 GPU 上保持一致,这要求块表更新操作具备跨设备同步能力。

PagedAttention 的分页思想不仅解决了显存碎片问题,更开启了 KV Cache 管理的新范式——将显存视为可精细调度的资源池,而非一次性分配的固定空间。这一设计为后续的 Prefix Caching、自动混合精度、显存交换等高级特性奠定了基础,是 vLLM 高性能推理的核心基石。