PagedAttention:KV Cache 的分页革命
PagedAttention通过分页管理KV Cache,显著提升显存利用率。传统KV Cache在解码阶段持续增长,导致显存不足(如LLaMA-13B在Batch=8时需25.6GB)。PagedAttention借鉴操作系统分页机制,将Cache切分为固定大小的Block,物理不连续但逻辑连续,使显存利用率从40%提升至95%。CANN Runtime 8.0原生支持该技术,通过动态分配Blo
解码阶段每生成一个 Token 都要把 KV Cache 的增长一段。序列越长,KV Cache 越大。当 Batch=8、序列长度 4096、LLaMA-13B 时——KV Cache 约 22GB。显存不够用了。
PagedAttention 借鉴操作系统的分页内存管理——把 KV Cache 切成固定大小的 Block(页),物理上不连续但逻辑上连续。不同 request 的 Cache 碎片可以被打包到同一物理页上,显存利用率从 40% 提升到 95%。
为什么 KV Cache 会爆显存
KV Cache 在解码阶段的每次迭代中都会增长——当前 Token 的 K/V 追加到缓存末尾。Batch 给固定大小时 Cache 总量需要预分配到最大值。
LLaMA-13B:40 层、40 头、隐藏维度 5120。单 request 在 n=4096 的 KV Cache:
- 每层 K:
[40, 4096, 128]× 2 bytes = 40MB - 每层 V:
[40, 4096, 128]× 2 bytes = 40MB - 40 层:40 × 80MB = 3.2GB
Batch=8 时 Cache = 25.6GB。模型参数 ~26GB。总计 ~52GB——远超单张卡 32GB 的显存。
PagedAttention 为什么出现
标准 Attention 的 KV Cache 是连续分配的——在 Prefill 阶段一次性分配 max_length 的空间。如果实际输出只有 100 个 Token,分配了 4096 容量的空间浪费了 97%。
PagedAttention 的思路是按需分配——每次只分配一个 Block(如 16 或 32 个 Token 的容量),用完再分配下一个。Block 在物理上不连续,通过页表映射到逻辑上的连续地址空间。
物理显存:
Block 0 (page 3) | Block 1 (page 7) | Block 2 (page 1) | Block 3 (page 8)
页表(Block ID → Physical Page ID):
Block 0 → Page 3
Block 1 → Page 7
Block 2 → Page 1
Block 3 → Page 8
逻辑视角:
[Block 0] → [Block 1] → [Block 2] → [Block 3](连续)
Runtime 如何管理 KV Block
CANN Runtime 在 8.0 后原生支持 Paged KV Cache。管理流程:
- 初始化。 在模型加载时分配一个 KV Block Pool——从显存中划出一片区域,切成固定大小的 Block
- 分配。 解码阶段每步需要新的 Cache 空间时,从 Pool 中取一个空闲 Block
- 页表更新。 把新 Block 的物理地址加入页表,更新逻辑到物理的映射
- 释放。 推理结束后 Block 返回 Pool
# Paged KV Cache 初始化
cache_config = {
"block_size": 16, # 每个 Block 16 个 Token
"max_blocks": 100000, # Pool 总 Block 数
"enable_paged": True, # 开启分页
}
Block 的大小可以配置。Block 越大,页表项越少(查询开销低),但内部碎片浪费更多。实测中 block_size=16 或 32 是平衡点。
大模型长文本推理场景
PagedAttention 在长文本推理中的显存优势:
| 场景 | 标准 Cache | Paged Cache | 节省 |
|---|---|---|---|
| 短对话 (n=128) | 3.2GB | 0.1GB | -97% |
| 中等长度 (n=1024) | 3.2GB | 0.8GB | -75% |
| 长文本 (n=4096) | 3.2GB | 3.2GB | 0% |
| 8×批处理 (n=1024) | 25.6GB | 6.4GB | -75% |
短序列和批处理场景的节省最明显。8 批短对话的 Cache 从 25.6GB 降到 6.4GB——一张卡可以装下更多并发请求,服务吞吐翻倍。
Paged KV Cache 的页表管理
Paged KV Cache 的页表存储在 NPU 显存中。每张卡的页表大小 = 页表项数 × 页表项大小。
Block 大小=16 Token 时,max_length=4096 的序列需要 256 个 Block。页表项 = 256 项。每项 8 字节——页表总大小 2KB。40 层 × 40 头 × 2(K+V)= 3200 个页表——总页表大小约 6.4MB。相比于 KV Cache 的几 GB,页表开销完全可忽略。
Runtime 对页表的访问是硬件加速的——Paged Attention 的 DMA 从页表查找物理地址只占 Attention 计算时间的 0.5% 以下。
Block 大小的选择
Block 大小的选择影响 Paged KV Cache 的效率:
| Block 大小 | 页表项数 | 内部碎片 | 显存利用率 |
|---|---|---|---|
| 8 | 512 | 4% | 96% |
| 16 | 256 | 6% | 94% |
| 32 | 128 | 10% | 90% |
| 64 | 64 | 18% | 82% |
Block 越小显存利用率越高(碎片少),但页表项数多。CANN 8.0 的默认 Block 大小是 16——在碎片率和页表大小之间取了平衡。
PagedAttention:KV Cache 的分页革命
解码阶段每生成一个 Token 都要把 KV Cache 的增长一段。序列越长,KV Cache 越大。当 Batch=8、序列长度 4096、LLaMA-13B 时——KV Cache 约 22GB。显存不够用了。
PagedAttention 借鉴操作系统的分页内存管理——把 KV Cache 切成固定大小的 Block(页),物理上不连续但逻辑上连续。不同 request 的 Cache 碎片可以被打包到同一物理页上,显存利用率从 40% 提升到 95%。
为什么 KV Cache 会爆显存
KV Cache 在解码阶段的每次迭代中都会增长——当前 Token 的 K/V 追加到缓存末尾。Batch 给固定大小时 Cache 总量需要预分配到最大值。
LLaMA-13B:40 层、40 头、隐藏维度 5120。单 request 在 n=4096 的 KV Cache:
- 每层 K:
[40, 4096, 128]× 2 bytes = 40MB - 每层 V:
[40, 4096, 128]× 2 bytes = 40MB - 40 层:40 × 80MB = 3.2GB
Batch=8 时 Cache = 25.6GB。模型参数 ~26GB。总计 ~52GB——远超单张卡 32GB 的显存。
PagedAttention 为什么出现
标准 Attention 的 KV Cache 是连续分配的——在 Prefill 阶段一次性分配 max_length 的空间。如果实际输出只有 100 个 Token,分配了 4096 容量的空间浪费了 97%。
PagedAttention 的思路是按需分配——每次只分配一个 Block(如 16 或 32 个 Token 的容量),用完再分配下一个。Block 在物理上不连续,通过页表映射到逻辑上的连续地址空间。
物理显存:
Block 0 (page 3) | Block 1 (page 7) | Block 2 (page 1) | Block 3 (page 8)
页表(Block ID → Physical Page ID):
Block 0 → Page 3
Block 1 → Page 7
Block 2 → Page 1
Block 3 → Page 8
逻辑视角:
[Block 0] → [Block 1] → [Block 2] → [Block 3](连续)
Runtime 如何管理 KV Block
CANN Runtime 在 8.0 后原生支持 Paged KV Cache。管理流程:
- 初始化。 在模型加载时分配一个 KV Block Pool——从显存中划出一片区域,切成固定大小的 Block
- 分配。 解码阶段每步需要新的 Cache 空间时,从 Pool 中取一个空闲 Block
- 页表更新。 把新 Block 的物理地址加入页表,更新逻辑到物理的映射
- 释放。 推理结束后 Block 返回 Pool
# Paged KV Cache 初始化
cache_config = {
"block_size": 16, # 每个 Block 16 个 Token
"max_blocks": 100000, # Pool 总 Block 数
"enable_paged": True, # 开启分页
}
Block 的大小可以配置。Block 越大,页表项越少(查询开销低),但内部碎片浪费更多。实测中 block_size=16 或 32 是平衡点。
大模型长文本推理场景
PagedAttention 在长文本推理中的显存优势:
| 场景 | 标准 Cache | Paged Cache | 节省 |
|---|---|---|---|
| 短对话 (n=128) | 3.2GB | 0.1GB | -97% |
| 中等长度 (n=1024) | 3.2GB | 0.8GB | -75% |
| 长文本 (n=4096) | 3.2GB | 3.2GB | 0% |
| 8×批处理 (n=1024) | 25.6GB | 6.4GB | -75% |
短序列和批处理场景的节省最明显。8 批短对话的 Cache 从 25.6GB 降到 6.4GB——一张卡可以装下更多并发请求,服务吞吐翻倍。
Paged KV Cache 的页表管理
Paged KV Cache 的页表存储在 NPU 显存中。每张卡的页表大小 = 页表项数 × 页表项大小。
Block 大小=16 Token 时,max_length=4096 的序列需要 256 个 Block。页表项 = 256 项。每项 8 字节——页表总大小 2KB。40 层 × 40 头 × 2(K+V)= 3200 个页表——总页表大小约 6.4MB。相比于 KV Cache 的几 GB,页表开销完全可忽略。
Runtime 对页表的访问是硬件加速的——Paged Attention 的 DMA 从页表查找物理地址只占 Attention 计算时间的 0.5% 以下。
Block 大小的选择
Block 大小的选择影响 Paged KV Cache 的效率:
| Block 大小 | 页表项数 | 内部碎片 | 显存利用率 |
|---|---|---|---|
| 8 | 512 | 4% | 96% |
| 16 | 256 | 6% | 94% |
| 32 | 128 | 10% | 90% |
| 64 | 64 | 18% | 82% |
Block 越小显存利用率越高(碎片少),但页表项数多。CANN 8.0 的默认 Block 大小是 16——在碎片率和页表大小之间取了平衡。
参考仓库
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)