完整文档索引见 llms.txt。 在任意 URL 后追加 `.md` 即可查看该页面的 Markdown 版本。
PagedAttention
PagedAttention 是一种在 LLM 推理中管理 KV 缓存的高内存效率方法。它在服务中的主要收益并不来自更快的注意力内核,而是来自服务引擎分配和管理 KV 缓存内存的方式。注意力内核实现了其中一部分机制(它们通过查找表读取 KV 块),但真正的优势发生在服务层,这也是为什么本页位于“推理优化”章节中。若想了解内核层面的注意力效率,请参见 FlashAttention。
注意力与 KV 缓存
注意力是 Transformer 用来评估 token 之间相关强度的机制。对于每个 token,模型会计算三个向量:
- Query (Q):当前 token 正在寻找什么
- Key (K):每个 token 提供了什么用于匹配
- Value (V):每个 token 贡献的内容
模型将 query 与 key 进行比较以生成注意力分数,使用 softmax 对其归一化,然后利用得到的权重对 value 做加权求和。
在自回归生成过程中,每个新 token 都需要所有先前 token 的 key 和 value。服务引擎不会在每一步都重新计算它们,而是会将它们存储到 KV 缓存中。该缓存会随序列长度增长,并且在并发请求场景下会消耗大量 GPU 内存。PagedAttention 要解决的问题,正是引擎如何分配这些内存。
更多信息请参见注意力机制。
为什么连续的 KV 缓存分配会浪费内存
通常,KV 缓存会占据大量内存,因为它被存储为一个巨大的连续块。这可能导致内存碎片或空间浪费,因为即使你无法将它填满,也需要预留一个大块区域。
更具体地说,早期的服务引擎往往会把 KV 缓存分配为一个面向最坏情况大小的连续张量。一个简化后的形状 是:
2 × num_layers × num_heads × head_dim × max_seq_len
这种分配会针对每个活跃请求发生,且还未计入批大小和每个元素的字节数。它很简单,但默认假设每个请求都会使用最大序列长度。真实流量是变化的:一个请求可能生成很短的回答,另一个可能维持一段很长的对话,还有一个可能会提前停止。如果每个请求都为
max_seq_len 预留内存,那么被保留的 GPU 内存中会有很大一部分处于未使用状态。
其结果是,有效批大小更低、内存碎片更多、并发请求数更少。
PagedAttention 是如何工作的?
PagedAttention 会把这块大的连续区域拆成更小的块,有点像书中的页面。换句话说,KV 缓存会以非连续块的形式存储。随后,它使用一个查找表来跟踪这些块。LLM 只加载自己需要的块,而不是一次性把所有内容都加载进来。
这样可以节省内存,并让整个过程更高效。必要时,它甚至允许不同输出之间共享同样的块。
原始的 PagedAttention 论文报告称,在没有 PagedAttention 的情况下,分配出去的 KV 缓存内存中只有 20.4%-38.2% 被用于存储真实 token 状态,其余部分都因碎片化而浪费。相比之下,PagedAttention 将 KV 缓存的内存浪费几乎降到了零。
这就是为什么 PagedAttention 的意义不止于单个注意力内核。它为服务引擎提供了更好的 KV 缓存内存分配器,从而让 continuous batching、前缀缓存 和 KV 缓存卸载 等技术更容易组合使用。
PagedAttention 最早由 vLLM 实现。此后,Hugging Face TGI 和 TensorRT-LLM 等其他项目也已采用并实现了 PagedAttention。