跳转到主要内容
KV 缓存分页:以固定显存支撑 256K 上下文推理
8 分钟阅读

KV 缓存分页:以固定显存支撑 256K 上下文推理

长上下文推理的瓶颈在 KV 缓存而不在模型权重:上下文达到 256K 时,仅缓存就要占用 4.6 GiB 显存,解码速度跌至 13 token/s。本文分析这笔开销的来源,并梳理一种分页式方案——GPU 上只保留固定容量的 KV 池,冷块按需在显存与主机内存之间换入换出,全程比特级无损。

AI推理优化

长上下文的真实成本

设想一个典型场景:用户把一份六万词的合同(约八万个 token)粘贴进对话框,要求模型逐条核对风险条款。这正是长上下文推理的日常形态——上下文一旦变长,三件事同时发生:KV 缓存占用的显存线性增长,预填充的计算量平方增长,解码阶段每生成一个 token 都要读取完整缓存。

问题的核心在于 KV 缓存的体量:

KV 字节数 = 2 × 层数 × KV 头数 × 头维 × 每元素字节数 × token 数

式中的 2 指 K 与 V 各存一份。以 27B 级别的稠密模型为例,每个 token 的 KV 约 18 KiB,上下文达到 256K 时累计 4.6 GiB——这笔开销伴随每一步注意力计算,既占显存又吃带宽。

权重侧反而并不紧张。27B 模型分片到两张显卡后,每卡只承担约 360 MiB;预填充阶段每 token 的计算量约 75.4 GFLOP,对应权重读取 3.4 GB,计算强度约 22 FLOP/byte,与 RTX 3090 的算力带宽比(82.6 TFLOPS 对 931 GB/s,折算约 22.7)基本持平。换句话说,只要权重合理分片,预填充吞吐可以贴着算力上限跑。真正的成本几乎全部落在 KV 缓存上。

解码为什么慢:每一步都读一遍完整缓存

预填充的显存峰值与平方复杂度,靠分块预填充(chunked prefill,每块 128–256 token)已经是通行解法。真正的难关在解码:每个新 token 的注意力都要扫过整段历史。

按 931 GB/s 的显存带宽计算,读取 4.6 GiB 缓存约需 5 ms,对应 200 token/s 的理论上限;而朴素实现的实测值只有约 13 token/s,每步超过 30 ms。差距不在带宽,在内核效率——主流注意力内核(FlashAttention-3 一类)为不超过 128K 的规整变长序列调优,面对 256K 的长序列,单个查询要对整个 262144×3456 的键矩阵做 QK^T,内核长期运转在低效区间。

也就是说:显存放不下,带宽吃不消,内核也跑不顺。要在消费级硬件上支撑 256K 上下文,要么截断上下文,要么引入近似——滑动窗口、KV 量化、稀疏注意力都属后者,共同代价是精度受损。

分页:让 KV 缓存不再随上下文增长

换一个角度看待缓存,可以得到两个关键观察:

  1. 单步解码具有局部性。 生成下一个 token 时,注意力权重集中在少数关键块上,久远的历史多数时候只是"在场备查"。既然如此,KV 完全可以像操作系统的内存页一样按块换入换出,不必常驻显存。
  2. 换页可以做到无损。 数据移动本身不改变数值。只要按块的原始字节搬运,注意力内核的计算结果就与全量驻存完全一致。这与量化、稀疏化等有损手段是本质不同的正交维度,可以任意叠加。

据此可以构造一个固定容量的 KV 池:显存中只保留一个槽位池,容量与上下文长度无关;上下文切成 64 token 一块的分片,在主机内存与显存槽位之间按需流动。预填充同样按 128–256 token 分块,与换页共用一套机制。

内核层面"忘记"不在场的块

每个槽位附带一个有效位掩码。注意力内核处理每个查询块时先读掩码,把不在场的块从注意力中直接剔除——机制上与 FlashAttention-3 处理变长序列(varlen)同源:不参与计算的块等价于不存在。掩码方案保证内核的数值行为与全量驻存严格一致,这正是"无损"的技术含义。

分片自包含:把 RoPE 烘焙进 K 行

旋转位置编码(RoPE)只作用于 K,不作用于 V。在写入缓存前把旋转直接算进 K 行,缓存中的每个分片就成为自包含的数据单元:换入即可参与计算,既不依赖相邻分片,也不需要额外的位置元数据。这是块能够自由迁移的前提。

换页与计算重叠

换入换出走 GPU 与主机内存之间的点对点 DMA,由独立的拷贝引擎执行,与流式多处理器上的注意力计算并行,带宽通路互不挤占。于是三个环节得以同时进行:驻留策略为候选块打分,DMA 提前搬运,主内核只管消费已经就位的块。

驻留策略:决定谁留在显存里

换页方案的吞吐上限,取决于"所需的块是否恰好在显存里"。常见的三类策略:

  • LRU(最近最少使用):实现最简单,但隐含"越新越重要"的假设,对需要回看早期章节的长文档并不友好。
  • 草稿模型打分:用一个 0.6B 量级的小模型对候选块在线打分,预测主模型下一步会关注哪些块。小模型步进快,能赶在主内核之前完成打分,相当于用极低的代价做了一次注意力分布的预估。
  • 预计算相关度分数:FlashMemory(arXiv:2606.09079)在语料上离线预计算每个块的相关度分数,推理时按分数决定驻留。分数的生成方式与 DeepSeek 的多 token 预测(MTP)同源,权重系数取 α < 0.5。
策略 打分时机 额外开销 适用场景
LRU 无需打分 可忽略 顺序读取为主、几乎不回看
草稿模型 每步在线 一个 0.6B 模型的前向计算 通用,回看频繁的长文档
预计算分数 离线建库 语料预处理与少量显存 语料固定、可提前建库

实测:双 RTX 3090 跑满 256K

在参考实现上,双 RTX 3090(24 GB,带宽 931 GB/s)承载 Qwen3.6-27B 稠密模型,权重分片后每卡约 360 MiB,其余显存留给 KV 池与激活。实测结果:

指标 数值
预填充吞吐 1319 token/s
解码速度 36 token/s
支持上下文 256K token
显存中 KV 占用 72 MiB 固定池

解码每步只需读取 72 MiB 驻留数据,带宽不再是约束;换页由 DMA 在计算间隙完成,对关键路径几乎透明。解码速度从 13 token/s 提升到 36 token/s,同时输出与全量驻存逐位一致。

与几类常见做法的对比:

方案 KV 存放位置 256K 上下文下的表现
全量驻留显存 4.6 GiB 显存 难与 27B 权重共存于 24 GB 显卡;实测解码约 13 token/s
KV 量化 显存 精度有损,带宽压力仍在
前缀缓存 / 分层存储 热前缀驻留,其余进主机内存或 SSD 命中时收益大;单次长文档问答难以命中
KV 分页 固定小池 + 主机内存 与全量驻存逐位一致,解码 36 token/s

同类系统各有侧重:Mooncake 与 LMCache 面向前缀复用和读写分离,vLLM 的分页管理面向批处理调度,解决的是"多请求共享与调度"问题;KV 分页针对的是"单请求上下文放不下、跑不快",两者并不冲突,可以结合。

工程实践要点

  1. 局部性是前提。 注意力分布越集中,换页收益越大;如果任务本身要求每步全量扫读,任何驻留策略都会退化为换页抖动。
  2. 与其他优化正交。 权重量化、KV 量化、稀疏注意力都可以叠加在分页之上,精度取舍各自独立评估。
  3. 权重与上下文要一起省。 只解决 KV 而权重仍需多卡,部署门槛并没有实质下降。KV 分页与 MoE 卸载、CPU 混合推理等权重侧优化是互补关系。
  4. SWA 层需要特殊处理。 带滑动窗口注意力的模型,窗口内的块必须常驻,只有窗外块参与换页;槽位分配要把这两类区分开。

相关工作

  • FlashAttention-3:IO 感知的注意力内核,其变长序列支持是槽位掩码方案的基础。
  • vLLM / PagedAttention:以页为单位管理 KV,面向批处理的显存碎片与调度。
  • LMCache、Mooncake:跨层存储与前缀复用,面向多请求共享场景。
  • FlashMemory:预计算相关度分数指导 KV 驻留。
  • DeepSeek MTP 与 EAGLE-3:多 token 预测与草稿模型,为在线打分提供廉价信号。

结语

KV 缓存分页把"上下文越长、显存越贵"的线性关系切断了:显存占用由池的大小决定,上下文长度只影响主机内存的用量。配合逐位一致的内核语义,长上下文不再意味着在容量、带宽与精度之间做痛苦的取舍。对于合同审查、代码库分析、长文档问答这类以超长输入为常态的场景,这是一条比"更大的显存"与"更激进的近似"都更务实的路径。