长上下文的真实成本
设想一个典型场景:用户把一份六万词的合同(约八万个 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 缓存不再随上下文增长
换一个角度看待缓存,可以得到两个关键观察:
- 单步解码具有局部性。 生成下一个 token 时,注意力权重集中在少数关键块上,久远的历史多数时候只是"在场备查"。既然如此,KV 完全可以像操作系统的内存页一样按块换入换出,不必常驻显存。
- 换页可以做到无损。 数据移动本身不改变数值。只要按块的原始字节搬运,注意力内核的计算结果就与全量驻存完全一致。这与量化、稀疏化等有损手段是本质不同的正交维度,可以任意叠加。
据此可以构造一个固定容量的 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 分页针对的是"单请求上下文放不下、跑不快",两者并不冲突,可以结合。
工程实践要点
- 局部性是前提。 注意力分布越集中,换页收益越大;如果任务本身要求每步全量扫读,任何驻留策略都会退化为换页抖动。
- 与其他优化正交。 权重量化、KV 量化、稀疏注意力都可以叠加在分页之上,精度取舍各自独立评估。
- 权重与上下文要一起省。 只解决 KV 而权重仍需多卡,部署门槛并没有实质下降。KV 分页与 MoE 卸载、CPU 混合推理等权重侧优化是互补关系。
- SWA 层需要特殊处理。 带滑动窗口注意力的模型,窗口内的块必须常驻,只有窗外块参与换页;槽位分配要把这两类区分开。
相关工作
- FlashAttention-3:IO 感知的注意力内核,其变长序列支持是槽位掩码方案的基础。
- vLLM / PagedAttention:以页为单位管理 KV,面向批处理的显存碎片与调度。
- LMCache、Mooncake:跨层存储与前缀复用,面向多请求共享场景。
- FlashMemory:预计算相关度分数指导 KV 驻留。
- DeepSeek MTP 与 EAGLE-3:多 token 预测与草稿模型,为在线打分提供廉价信号。
结语
KV 缓存分页把"上下文越长、显存越贵"的线性关系切断了:显存占用由池的大小决定,上下文长度只影响主机内存的用量。配合逐位一致的内核语义,长上下文不再意味着在容量、带宽与精度之间做痛苦的取舍。对于合同审查、代码库分析、长文档问答这类以超长输入为常态的场景,这是一条比"更大的显存"与"更激进的近似"都更务实的路径。

