Prefix Sliding 完全拆解:前缀 + 滑窗,恒定 KV 内存下的超长推理测试时扩展

8966 字
45 分钟
Prefix Sliding 完全拆解:前缀 + 滑窗,恒定 KV 内存下的超长推理测试时扩展

引子:当「想得越久越强」撞上内存墙#

2024 年 OpenAI o1 发布之后,推理模型(Reasoning Model)确立了一个新范式:测试时扩展(Test-Time Scaling)——不给模型更多参数,而是在推理阶段给它更多计算量,让它「想得更久」,从而解出更难的题。o1 内部的思路随后被一系列开源工作复现和推进:DeepSeek-R1 用强化学习(Reinforcement Learning,RL)训练出「想得越久越对」的行为;斯坦福 Muennighoff 等人的 s1 项目则展示了更朴素的路径——不需要 RL,用「预算强制(Budget Forcing)」控制思考长度,1 千美元级训练成本就能在 AIME 数学竞赛上追平 o1-preview。

这条路线有一个被反复强调的前提:想得越久,需要记住的东西越多。绝大多数推理模型仍然使用标准的全注意力(Full Attention),模型把整条推理轨迹(Reasoning Trace)完整保存在内存里——也就是 KV Cache。每生成一个新 token,注意力都要扫过历史上所有 token 的 K、V。于是:

  • 内存随序列长度线性增长。以 Qwen3-1.7B(28 层、GQA 2 个 KV 头、头维度 128、FP16)为例,每个 token 的 KV 占用约:
MKV=2×L×hkv×dhead×b=2×28×2×128×2 B28 KB/tokenM_{KV} = 2 \times L \times h_{kv} \times d_{head} \times b = 2 \times 28 \times 2 \times 128 \times 2 \text{ B} \approx 28 \text{ KB/token}

其中 LL 是层数,hkvh_{kv} 是 KV 头数,dheadd_{head} 是头维度,bb 是每个元素占用的字节数。10 万 token 的推理轨迹就要约 2.8 GB 显存,100 万 token 约 28 GB;换成 7B 级模型(4 个 KV 头)则约 57 KB/token,100 万 token 约 57 GB——单张 80 GB H100 在装下权重后已经放不下。

  • 计算也随序列长度线性增长。生成第 nn 个 token 时,注意力要对 nn 个历史 token 逐一算相似度并读取它们的 KV。序列越长,每个新 token 越贵,生成速度持续下降。vLLM + FlashAttention 在单张 H100 上的实测中,全注意力在序列长到 128K token 时吞吐只剩约 448 token/s(见后文 Table 1),而短序列时轻松上万。

这就是长程推理的「内存墙」:不是模型想不到 10 万步,而是基础设施不允许它想那么久。面对这道墙,社区已经有一批「上下文压缩」式方案:定期把旧内容总结成摘要(Compaction/Summarization,Claude Opus 4.6、GPT 5.4、Cursor Composer 等都在用)、只保留最后 k 个 token(Last-k,也被称为 Markovian Thinking)、纯滑动窗口注意力(Sliding Window Attention,Longformer/Big Bird 的遗产)、以及各种 KV Cache 淘汰策略(H2O、QEvict 等)。它们各有各的问题,后文会逐一对比。

2026 年 8 月 26 日,由 Niklas Muennighoff(s1 的作者)牵头、斯坦福大学、华盛顿大学、加州大学圣芭芭拉分校与 Prime Intellect 合作的团队在 arXiv 上发布了论文 《Prefix Sliding for efficient test-time scaling》(28 页正文 9 页、22 张图、3 张表),给出一个极简的答案:推理时只保留「前缀 + 最近几千年 token 的滑窗」,中间的推理过程全部丢弃。做法简单到可以写进几行注意力掩码,但效果是:不加任何训练就能让现有模型在同等思考时间内快 3 倍且不掉点;配合 RL 训练,模型可以稳定推理超过 10 万 token,成本恒定。

这篇文章把这套方法彻底拆开:先讲支撑它的注意力观察,再讲掩码机制、位置编码处理、FlashAttention kernel 实现、RL 训练细节,最后用论文的实验数据对比三条替代路线并讨论局限。

观察一:中间推理 token 会「过期」#

Prefix Sliding 的整个立论建立在两个注意力观察上。第一个观察来自论文的 Figure 2:对 Qwen3-1.7B 在 AIME25(美国数学邀请赛 2025 年题目)上的一条推理轨迹,统计所有层、所有注意力头平均后的 post-softmax 注意力概率(做了高斯平滑),画出「哪个位置的 token 拿到了多少注意力」:

注意力概率热力图:注意力集中在前缀与最近 token 上,中间推理 token 几乎拿不到注意力
注意力概率热力图:注意力集中在前缀与最近 token 上,中间推理 token 几乎拿不到注意力

图中有几个明显的高亮区域:

  • 最开头的几个 token 拿到大量注意力。这首先是「注意力汇聚点(Attention Sink)」现象——StreamingLLM 发现,无论上下文多长,前几个 token(尤其是第 1-4 个)总会吸收一大块概率质量,因为 softmax 需要把概率归一化,而早期 token 扮演了「溢出阀」的角色。此外,系统提示、任务描述、工具定义就在这一段,是模型必须随时能查的「任务手册」。
  • <think> 分隔符位置也显著高亮。DeepSeek-R1 系列用这个特殊 token 标记推理开始,模型在整条轨迹中持续注意它,因为它承担着「我正处于思考模式」的持续信号作用。
  • 末尾一小段注意力陡增,尤其是紧挨着正在生成 token 的前一个 token——模型正「看着」当前推理写到哪了。
  • 中间大段推理 token 的注意力概率整体压得很低,而且随轨迹推进还在继续走低。

第二个观察是:中间 token 的信息价值会快速衰减。推理的本质是逐步改写状态:算完一步,这步的推导过程就不再需要,只有结果进入后续步骤。论文里给了一个直观例子:解「((42 + 84) × 4) - 5」,一旦 42 + 84 算完,这一步的推理过程就失去了价值,下一步只需要结果 126;类似地,解一道几何题时,「构造辅助线」的思考过程在作图完成后就没用了。

两个观察合起来指向一个反直觉的结论:为了「万一有用」而把整条推理轨迹留在内存里,是巨大的浪费。中间 token 既不被模型注意,也大概率不会被未来用到——那么把它们丢掉,是不是可以?

这个「丢弃」与纯滑窗的差别在于:纯滑窗把前缀也一起丢掉了。而前缀恰恰是模型最容易失去的信息——任务是什么、能用什么工具、约束条件是什么。这就是 Prefix Sliding 名字的由来:前缀(Prefix)不许动,滑窗(Sliding window)负责跟随当前推理

核心机制:注意力掩码上的两个区间#

Prefix Sliding 的机制一句话可以说完:推理过程中,每个新 token 只能注意到「前缀」和「最近 W 个 token 的滑窗」两个区间,中间的 token 对注意力完全不可见。下图是论文 Figure 3 的机制示意:

Prefix Sliding 机制图:上方全注意力保留全部 token,下方只保留前缀与滑窗,中间 token 被丢弃
Prefix Sliding 机制图:上方全注意力保留全部 token,下方只保留前缀与滑窗,中间 token 被丢弃

具体来说,设前缀长度为 LprefixL_{prefix}(系统提示 + 用户任务 + 工具定义),滑窗大小为 WW(论文实验中取 512 到 16384 不等),那么任意时刻模型能看到的 token 总数被硬性封顶为:

Ltotal=Lprefix+WL_{total} = L_{prefix} + W

这个值与已经生成了多少 token 完全无关。生成第 100 个 token 和生成第 100 万个 token,注意力开销、KV 内存开销完全一样——每生成一个新 token 的成本是 O(1)O(1)(相对序列长度),而不是全注意力的 O(n)O(n)。论文把这称为「有界(Bounded)」成本:只要序列长度超过 LtotalL_{total},每个新 token 的成本就进入恒定区间,这是「无限测试时扩展」的必要条件——想让模型想几周,每个 token 的成本必须是常数。

实现上不需要改模型结构,只需要改注意力掩码。设第 ii 个生成的 token 为查询,合法注意力位置集合为:

A(i)={jjLprefix 或 iW<ji}A(i) = \{j \mid j \leq L_{prefix} \text{ 或 } i - W < j \leq i\}

即前缀区间 [1,Lprefix][1, L_{prefix}] 加上滑窗区间 (iW,i](i - W, i],其余位置的注意力分数在 softmax 前被掩码成 -\infty。注意两个细节:

  • 前缀和滑窗之间的 token 并不是被「悄悄忽略」,而是被显式地隐藏。这与「降低注意力权重」的软性方法不同——权重可以通过重归一化再分配,但 KV 内存是实打实的,隐藏意味着这些 token 的 KV 直接不被存储(或者存储后不再被读取)。
  • 滑窗的起点是前缀之后。窗口跟随生成位置 ii 前进,最老的内容被移出窗口。前缀永远固定,所以模型永远记得任务是什么。

还有一个「预热(Warm-up)」阶段:当序列总长还没超过 LtotalL_{total} 时,掩码退化为全注意力——反正所有 token 都在前缀和窗口范围内,此时与普通模型行为完全一致。这个阶段对短任务很重要,后文会看到它决定了 Prefix Sliding 的收益边界。

从系统角度看,Prefix Sliding 相当于给 KV Cache 画了一条显存上限:无论推理轨迹多长,KV 占用不超过 Lprefix+WL_{prefix} + W 个 token 的 KV。配合 vLLM 的分页式块管理(PagedAttention 的思路,本博客已有拆解),每个序列都能以固定上限预分配内存,调度和批处理都变得更可预测。

与已有稀疏注意力方案的关系#

这个机制在文献里并不突兀,它是三条老思路的「并集」:

  • Longformer / Big Bird:最早提出「全局 token + 滑窗」的混合注意力模式。它们的全局 token 通常是少量分散的(Big Bird 随机挑一些、Longformer 用分隔符),而 Prefix Sliding 的全局 token 是一整段连续的前缀——因为它服务的场景(推理轨迹)里,关键信息恰好集中在开头这一段。
  • StreamingLLM:只保留开头固定几个 token(比如 4 个)作为注意力汇聚点,其余全部用滑窗。Prefix Sliding 可以看作把「几个 token」扩展为「整段任务前缀」的 StreamingLLM——前缀不仅是 sink,还承载真实的任务信息。
  • 纯滑窗注意力(gpt-neo、gpt-oss 等):只有窗口、没有前缀。正因为模型会忘记任务,gpt-oss 用「全注意力层 + 滑窗层交错」来补救——Prefix Sliding 则用一个掩码同时拿到两者的优点。

关键区别在于:上述方案大多是训练期就定死的结构(模型从头就以这种注意力训练),而 Prefix Sliding 是直接套在现成模型上的推理期掩码,一行配置就能启用,不需要任何训练;训练则作为可选的加强项。这一点与之前拆解过的 NSA(原生稀疏注意力)形成鲜明对照:NSA 主张「稀疏模式必须在预训练时原生内置」,而 Prefix Sliding 证明了推理期硬掩码对「推理轨迹」这类特殊序列足够有效,因为推理轨迹的注意力结构比普通文本更极端、更可预测。

位置编码:Reset PE 还是 Continue PE?#

推理模型几乎都用旋转位置编码(RoPE,旋转位置编码,Rotary Position Embedding)。RoPE 把位置信息注入每个 token 的表示,而 Prefix Sliding 会改变 token 之间的相对位置关系——中间一段被丢弃后,「前缀最后一个 token」和「窗口第一个 token」在位置上变成了邻居,这带来了位置编码的两种处理方案:

  • 重置位置(Reset PE):把窗口内的 token 重新编号,让它们看起来紧跟前缀(前缀结尾是位置 LprefixL_{prefix},窗口开头是 Lprefix+1L_{prefix}+1,依次类推)。好处是位置号连续,模型看到的位置分布和训练时一致;坏处是每个窗口内 token 的位置都变了,已缓存的 KV 全部失效,必须重算
  • 延续位置(Continue PE):每个 token 保持它原始的位置号。窗口 token 的位置号巨大且与相邻 token 不连续(中间隔着几万号的缺口),但KV 缓存可以直接复用,不需要任何重算。

直觉上 Reset PE 更「干净」,但论文在附录 D 的消融里发现两者在 AIME25 上的性能差异统计上不显著(Figure 14,滑窗 2048,误差带重叠),因此选了 Continue PE——理由非常工程化:KV 复用省掉的算力是实打实的,而且训练时 Reset PE 几乎不可行(见训练一节)。

有意思的是,位置号的缺口并不会让模型崩溃:注意力本身对绝对位置不敏感(RoPE 依赖的是相对位置差),而模型在预训练中见过各种上下文截断,对「位置号跳跃」相当鲁棒。论文还指出一个更激进的未来简化方向:结合 DroPE 直接去掉位置编码(论文引用 Gelberg et al. 2025 的工作表明预训练模型可以丢弃位置编码而几乎不掉点),或者从头训练无位置编码的模型——那样连 Continue/Reset 的选择都消失了。

Kernel 实现:两层级过滤#

Prefix Sliding 不能靠「改一下注意力公式」就拿到全部收益,还得有一个快的 kernel。论文在 Hopper 架构上基于 FlashAttention 写了一个定制注意力 kernel(代码在开源仓库中,FlashAttention 的分块思想本博客已有完整拆解,这里直接沿用「HBM 块 / 片上 SRAM 块」的术语)。核心是两层级过滤

  1. 块内掩码(Intra-tile masking):FlashAttention 把序列切成块(tile)逐块处理。对那些部分重叠合法注意力区(前缀 ∪ 滑窗)的边界块,逐元素施加掩码,保证只有合法的 (q,k)(q, k) 对参与 softmax 与输出。这一层不改变 FlashAttention 的分块策略,只加一个逐元素 mask,保证数学正确性。
  2. 块间跳过(Inter-tile skipping):对完全落在丢弃区的块,直接跳过——不加载、不算。具体做法是把「生产者-消费者」流水线重构为遍历两个不相交的块区间:前缀块区间和窗口块区间。序列越长,被跳过的块占比越大,节省越可观。

两层合起来的效果是:Prefix Sliding kernel 的速度与标准滑窗注意力 kernel 相当(论文 Figure 6,单张 80GB H100、vLLM 自动批大小、窗口 4096):两者的吞吐都在序列超过窗口后稳定在约 5000 token/s 附近,而全注意力在 32K token 时已掉到约 1477 token/s、128K 时只剩约 448 token/s。Prefix Sliding 略慢于纯滑窗的差距来自前缀部分的额外读取——前缀必须每个 token 都参与注意力计算。

有意思的是吞吐曲线都有一个「先降后平」的形状:生成的 token 数还没超过窗口大小时,处于预热阶段,每个新 token 仍然要扫过全部历史(这阶段 PS 等价于全注意力),所以速度一路下滑;一旦超过窗口大小,成本进入恒定区间,曲线拉平。而全注意力的曲线永远在降——这就是「有界 vs 无界」在实测中的直接体现。

代码层面的启用方式也非常轻量。官方仓库给出的是基于定制分支的 vLLM + FlashAttention 构建(Muennighoff/vllmst29 分支、Muennighoff/flash-attentionst29 分支),推理代码本身只是改模型配置:

model = "Qwen/Qwen3-1.7B"
length = 32_000
w = 4096
hf_overrides = {
"use_sliding_window": True,
"sliding_window": w,
"max_position_embeddings": length * 2,
}
os.environ.update({"SWF": str(w)})
from vllm import LLM, SamplingParams
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model)
prompt = "Prime factorize 806912."
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True,
)
text += "<think>\n"
model = LLM(model, hf_overrides=hf_overrides)
s = SamplingParams(temperature=1, top_p=0.95, max_tokens=length)
output = model.generate([text] * 1, sampling_params=s)

仓库说明里特别强调:Prefix Sliding 在 vLLM / flash-attn / RL 侧都是「简单修改」,容易移植到更新版本——方法本身的复杂度不在 kernel,而在掩码语义。

无训练推理:3 倍加速到底从哪来#

论文最「诱人」的结果是:什么都不训练,把掩码换上,现有模型就能在同样思考时间内显著提分。注意这里的措辞——Figure 1(下图)展示的是「准确率 - 平均思考时间」曲线:

Prefix Sliding 与全注意力的效率对比:同样的思考时间内,Prefix Sliding 达到更高准确率
Prefix Sliding 与全注意力的效率对比:同样的思考时间内,Prefix Sliding 达到更高准确率

两条曲线用的是同一个 Qwen3-1.7B 模型,唯一区别是注意力掩码。Prefix Sliding 曲线整体在左上方。论文在 Figure 1 的图注里把原因说得很直白:Prefix Sliding 表现更好,不是因为它生成的每个 token 质量更高,而是因为在同样的思考时间内它能生成更多 token——预算强制(Budget Forcing,即按时间/预算截断思考的推理控制手段)按「思考时间」而非「token 数」预算计算,于是每个 token 更便宜的一方,在同一预算内思考得更深、更远。

更准确地说,3 倍加速来自三个叠加因素:

  1. 每 token 计算量恒定。全注意力下第 nn 个 token 要处理 nn 个历史 KV,Prefix Sliding 永远只处理 Lprefix+WL_{prefix} + W 个。序列越长,差距越大:Table 1(论文主结果表)显示在 32K 序列时窗口 4096 的 Prefix Sliding 吞吐 5479 token/s,是全注意力 1477 token/s 的 3.7 倍;到 128K 时是 5224 对 448,11.7 倍
  2. 显存释放 → 批大小更大。单条序列的 KV 上限固定后,同一张卡能容纳的并发序列数大大增加,批处理(连续批处理,Continuous Batching,本博客已有拆解)可以维持更大的批次,进一步摊薄固定开销、提高吞吐。
  3. 波动小、可规划。全注意力下 KV 需求随序列增长而增长,调度器需要不断为长序列扩容;Prefix Sliding 的每序列内存上限固定,显存分配可预测,碎片和 OOM 风险大幅降低。

下面把窗口尺寸扫描表(论文 Table 1 的核心数据)完整展开,Qwen3-1.7B、budget forcing、avg@64(每个样本采样 64 次取平均正确率):

滑窗大小AIME25 avg@64AIME25 平均长度GPQA avg@64GPQA 平均长度MATH500 avg@64MATH500 平均长度吞吐 @32K (tok/s)吞吐 @128K (tok/s)
204827.74764335.93010789.8931089738737
409633.92994337.01670791.5706954795224
819235.81937338.01360591.4622932912788
1638435.31987238.21437891.5616024411420
全注意力34.21915837.61140391.760561477448

这张表值得逐行读:

  • 窗口 8192 是甜蜜点:AIME25 达到 35.8,比全注意力的 34.2 还高;MATH500、GPQA 也基本打平(91.4 vs 91.7、38.0 vs 37.6)。此时平均生成长度 19373 与全注意力 19158 几乎相同,说明模型没有为了凑 token 而重复劳动,纯粹的掩码更换没有损失推理质量。
  • 窗口 2048 太小:AIME25 掉到 27.7,而平均长度暴涨到 47643——模型在反复「重新推导」。原因很直观:滑窗太小,一步推理刚算完就滑出窗口,后续步骤找不到中间结果,只能重算一遍。token 便宜了(吞吐最高,8973 tok/s),但质量崩了,总账算不过来。
  • 窗口 16384 不涨反降:AIME25 回落到 35.3。窗口大意味着每 token 成本高(吞吐 2441 vs 3291),而收益(更完整的中间信息)已经饱和——8192 窗口已经能覆盖「模型真正需要的最近上下文」,再多只是花钱买用不上的内存。
  • 速度对比的结论:在保持性能的前提下,8192 窗口在 128K 序列时比全注意力快 6.2 倍(2788 vs 448)。而论文摘要里说的「3 倍」是综合多个任务、常规序列长度的保守数字。

论文在评估方法上的一个细节值得注意:效率指标用「平均思考时间(秒)」而不是 FLOPs 或总 token 数。因为 FLOPs 和 token 数衡量的是「做了多少计算」,无法反映内存差异导致的批大小和延迟差异;而用户真正感知到的是「等了多久」。这是推理系统评估里一个重要的口径选择。

RL 训练:把「丢中间」训练进模型#

无训练版本已经很好用,但它有一个上限:模型在预训练/对齐阶段从没见过「中间 token 会消失」这件事,它会在窗口内依赖一些很快会被丢弃的信息,或者反复重述早前结论来对抗遗忘(窗口 2048 时 47643 的平均长度就是这种行为的证据)。如果让模型在知道中间 token 会被丢掉的约束下强化学习(RL),它会学会主动把关键信息组织进滑窗内(比如不写超长代码注释、把中间结论写进窗口内保持「可见」),从而在更小窗口下维持更长的有效推理。

论文的实验(Figure 8)用 GRPO(Group Relative Policy Optimization,DeepSeek 提出的组相对策略优化)训练 Qwen3-1.7B,同步版本用 Hugging Face 的 trl 实现,异步版本用 Prime Intellect 的 prime-rl 实现。训练数据是自建数学题集,用三个准则过滤:可猜性(Guessability)——小模型不思考 8 次内就能答对的题删掉;可验证性(Verifiability)——含「How」「Explain」这类无法客观判分的题删掉;难度(Difficulty)——弱模型永远答对和强模型永远答不对的题都删掉(前者太简单、后者可能无解)。

超长 rollouts 的反向传播:截断回传#

RL 训练长推理轨迹的真正难点在反向传播:一条 10 万 token 的 rollout 完整回传梯度,激活值会把 trainer 的显存撑爆——这正是业界普遍「把超长生成截断丢弃」的原因(DAPO 等系统都讨论过这个取舍)。Prefix Sliding 用截断反向传播(Truncated Backpropagation)解决:只对轨迹最后一段回传梯度。

为什么只回传最后一段就够了?这依赖滑窗的「感受野」性质:跨 L 层的滑窗注意力,理论上感受野是 W×LW \times L(每层向前传播 WW 个位置的信息,L 层串联),但由于信息瓶颈,实际感受野只有约 1.5×W1.5 \times W(这个结论来自滑窗注意力专门研究者的博客分析,论文引用了 Guangxuan Xiao 的分析)。也就是说,滑窗内一个 token 的梯度,最多只依赖它前面约 1.5×W1.5 \times W 个 token 的内容——更早的 token 对它没有任何因果影响,回传了也得不到非零梯度。

因此,要算准最后 WW 个 token 的梯度,只需把「最后 4×W4 \times W 个 token」传给 trainer。论文给的例子:一条 10 万 token 的轨迹、窗口 2048,采样器只把最后 8192 个 token 发给 trainer;trainer 用前 6144 个 token 作为上下文(计算它们的前向),只在最后 2048 个 token 上计算 token 级 RL loss——实现上就是一个 loss mask:

loss_mask = torch.zeros_like(logprobs) # 前 6144 个 token 的 loss 置零
loss_mask[-2048:] = 1.0
loss = -(logprobs * loss_mask * advantages).mean()

autograd 从被掩码的 loss 正常回传,梯度只更新最后 2048 个 token 对应的路径。这些梯度是在「比滑窗大 4 倍的上下文」上算出来的,与在全量 10 万 token 上回传的梯度非常接近(附录 E 用 DeepSeek-R1-Distill-Qwen-7B 验证:32768 个 token 传入 trainer,Prefix Sliding 只回传最后 8192 个,性能与全量回传相当)。

「传多少 token 给 trainer」本身是个超参数,论文做了专门的 KL 消融(KL 衡量 generator 里模型分布与 trainer 里模型分布的失配程度):

  • 只传最后 2K(正好一个窗口):KL 超过 0.1,明显失配——窗口内的 token 依赖窗口外的历史,梯度是错的;
  • 传 4K(2 倍窗口):KL 显著下降;
  • 传 8K(4 倍窗口):论文选定值,与传 16K(8 倍)几乎持平,且略优。

残留的 KL 部分来自数值差异:generator 用的是定制的 FlashAttention kernel,trainer 用的是 PyTorch 的 FlexAttention 实现,两种 kernel 的浮点运算顺序不同,logits 有微小出入。这个 4 倍经验法则在大窗口上也成立(附录 E 的 7B 实验:窗口 8192、乘子 4)。

论文还提到了另一条更昂贵的替代路径:分块回传(Chunked Backpropagation)——把轨迹切成若干块逐块回传并累积梯度,理论上与完整回传近似等价,但实现复杂得多。论文选择截断回传,并提示更大规模、更长的链可能需要分块回传。

为什么训练中必须用 Continue PE#

无训练推理时选 Continue PE 的理由是效率;训练时它近乎是唯一选择。原因在教师强制(Teacher Forcing,训练时用真实 token 而非模型自生成的 token 作为下一步输入)上:训练时每个位置都要同时计算「以任意前缀为条件的概率」,如果重置位置编码,那么同一个 token 在不同样本里、不同上下文长度下,位置号会随它前面被重置的 token 数而变——每个 token 看到的「位置历史组合」都不同,批内计算无法对齐,位置编码的 KV 也无法跨步骤复用。Continue PE 下每个 token 的位置号固定,模型自然学会了「位置号有缺口」的分布。这也是论文坚持「训练与推理都用 Continue PE」的工程理由。

训练收益:同等内存下更长的推理#

Figure 7 展示了训练的直接收益:把全注意力模型的内存预算限制为最多 8192 token、Prefix Sliding 模型用 8192 的滑窗,两者「同内存」竞技——此时 Prefix Sliding 可以继续推理远超 8192 token(因为中间 token 不占内存),而全注意力模型在 8192 token 后必须停止。结果 Prefix Sliding 训练模型在 AIME25 上持续领先,因为它把同样的显存预算花在了更长的推理轨迹上。这正是「测试时扩展」在工程上的意义:算力(时间)可以无限给,显存不能无限给,而 Prefix Sliding 把瓶颈从显存搬到了算力/延迟上。

与三条替代路线的对比:为什么它们都差一口气#

能支撑「无限测试时扩展」的既有方案不止 Prefix Sliding 一个,论文选了三个最能打的代表做对比(全部在 AIME25 上、最大生成长度 262144、窗口/阈值 4096、avg@64,见 Figure 9):

Prefix Sliding 与 last k、摘要、纯滑窗三条路线的对比:PS 始终在最优的「性能-效率」曲线上
Prefix Sliding 与 last k、摘要、纯滑窗三条路线的对比:PS 始终在最优的「性能-效率」曲线上

Last-k:重复处理是原罪#

做法:生成到阈值 nn 后,把除了最后 kk 个 token 以外的内容全部删除,用这 kk 个 token 接着生成,如此反复。这本质上是 agent 系统里常见的「删旧轮次」操作,也被独立提出为「Markovian Thinking(Delethink)」。

问题

  • 重复处理:最后 kk 个 token 会被处理两次——第一次是生成它们的时候,第二次是上下文变化(前面内容被删)之后它们作为新上下文的开头被重新处理。kk 越大,重复处理的量越大。
  • 有用信息可能被删kk 太小时,模型正在用的中间结果可能已经滑出保留区,于是重新推导一遍(论文 Figure 21 展示了 AIME 样本上模型「从头再来」的例子)。
  • 内存锯齿:每删一次上下文,显存用量骤降——内存曲线是锯齿状的(Figure 10),调度器难以让资源利用率保持平滑,GPU 利用率规划困难。

论文扫了 kk(64 到 1024,Table 2),选 k=256k=256:MATH500 60.8、AIME25 4.2,是「保留内容小 + 性能尚可」的折中——但这个最优值依赖上下文窗口和允许的轮次,实际部署中无法预先知道轮次,只能固定一个 kk 打天下。

Summary(摘要/压缩):模型记不住自己的摘要#

做法:生成到阈值 nn 后,让模型自己(或外部模型)把当前上下文总结成摘要,连同提示词一起作为新上下文继续。Opus 4.6、GPT 5.4、Cursor Composer 的「上下文压缩(Compaction)」走的都是这条路。

问题

  • 长链摘要的信息丢失:理论上模型可以从当前上下文中提取重要信息带到下一轮,但实证表明模型在多轮压缩中保留重要信息的能力很差——最近的系统研究(Lost in Compaction)证实了这一点。论文 Figure 20 展示了一个典型失败:模型看似生成了摘要,但下一轮它完全无视了自己的摘要,从头开始解题。
  • 超参数爆炸:阈值 nn、摘要长度、摘要提示词、摘要模型、摘要放在新上下文的哪个位置……每个都是要调的旋钮。
  • 额外开销:摘要本身要生成 token,摘要模型越大越贵;与 last-k 一样有重复处理问题(摘要和上下文都处理两次),内存同样锯齿状。
  • 论文的提示词消融(Table 3):把摘要当工具调用、并给模型一个「如何使用工具和上下文」的例子(prompt 2)效果最好,AIME25 准确率 26.4、覆盖率 53.3;只给工具定义(prompt 1)只有 23.2/33.3;再额外解释上下文(prompt 3)反而略降。也就是说摘要路线连「怎么写提示词」都要仔细调。

纯滑窗:模型忘了自己在干嘛#

做法:就是 Prefix Sliding 去掉前缀的版本。

问题:滑窗滑过头,任务描述、工具定义、约束条件全部滑出窗口——模型开始「迷失」,在长推理任务上性能随思考时间先涨后平(Figure 9 左下)。论文的原话是:模型忘了自己在解哪道题、能用哪些工具。这正是 gpt-neo/gpt-oss 这类纯滑窗模型需要「全注意力层交错」来补偿的原因——它们把滑窗当作训练期的稀疏化结构,而不是推理期的遗忘管理器。

对比结果#

Figure 9 的四个面板里,Prefix Sliding 的「准确率-思考时间」曲线在四个对比中全部位于左上方:同样的思考时间,Prefix Sliding 准确率最高;同样的准确率,它花的时间最少。纯滑窗最快拉平,last-k 和摘要能爬但爬得慢且成本结构差(锯齿)。而且 Prefix Sliding 只引入一个超参数(窗口大小 WW),相比之下 last-k 有 nnkk 两个,摘要有一串。论文还对比了 H2O(保留高频注意力 token 的 KV 淘汰策略),指出它「向后看」(依据历史注意力保留 token)而 Prefix Sliding「向前看」(前缀被无条件保留,即使它长时间没被注意——工具定义可能几千年 token 都没被用过,但一旦要用就必须在),两者思路可以结合:保留前缀 + 高频中间 token + 滑窗,可能更强,但 RL 训练下实现困难。

局限与未解决的问题#

论文诚实地列出了四条边界,都值得展开:

1. 中间 token 偶尔真的重要:LiveCodeBench 案例。 代码生成任务(LiveCodeBench)需要窗口至少 16384 才能追平全注意力。案例分析(Figure 22)显示原因很具体:模型在推理阶段先把函数实现写了一大段,然后用注释的形式思考了上千个 token——等它回来继续写代码时,函数开头已经滑出窗口。这类「长距离引用」正是滑窗的盲区。论文给出的出路:RL 训练让模型学会别在注释里思考(把结论写进代码本身);或者提供一个机制让模型把重要 token 主动追加到前缀

2. 短任务几乎没有收益。 如果任务平均只需要 2086 个 token(HealthBench 实测),而窗口是 2048,那么绝大多数样本还没进入「滑动」阶段就结束了——全程等价于全注意力,只是多了个掩码。论文 Figure 12(下图)验证了这一点:两条曲线几乎重合。Prefix Sliding 的收益与生成长度强相关,短任务上它不是免费的,只是没有损失。

HealthBench 短任务实测:平均生成长度 2086 小于滑窗 2048,Prefix Sliding 与全注意力几乎无差别
HealthBench 短任务实测:平均生成长度 2086 小于滑窗 2048,Prefix Sliding 与全注意力几乎无差别

3. 系统输出与多轮对话。 agent 场景里模型可能读文件、读网页,一次性灌入几万 token 的「外部内容」会淹没整个滑窗——如果内容比窗口还大,模型严格意义上读不完;更糟的是窗口被外部内容占满,正在进行的推理反而被挤出去。多轮场景里,用户后续的指令应该追加到前缀还是任它滑走?论文承认这些问题在摘要、last-k 方案下同样存在,可能的解法包括:RL 让模型学会分步读取(用 head 而不是 cat)、在系统侧对超长输出做护栏。

4. Prefill 成本不受益 + 规模验证有限。 Prefix Sliding 只作用于生成阶段的 KV,长提示词的 Prefill 成本一点没省(附录 A 明确指出,并建议长文档别塞进提示词,给文件路径让模型按需读取)。另外,论文的规模验证止步于 7B 模型、数十万 token 的轨迹,更大的模型和更长的链还需要进一步实验。

小结#

Prefix Sliding 的全部内容可以压缩成一句话:推理轨迹中,前缀和最近几千年 token 几乎承载了全部注意力与信息价值,中间的推理过程可以安全丢弃,从而把每个新 token 的内存与计算成本从 O(n)O(n) 变成 O(1)O(1)

它的位置在推理优化谱系里很特殊:不碰模型权重(无训练版)、不碰量化、不碰并行策略,只改注意力掩码 + 一个 kernel,就能拿到 3 倍加速和「推理长度不再受显存限制」的性质;而训练版(RL + 截断回传)又把「模型适应丢弃」这件事做进了行为里,让 10 万 token 以上的推理轨迹成为日常可能。把它和本博客讲过的几个概念放一起看,脉络很清楚:

  • 它和 FlashAttention 系列是同一枚硬币的两面:FlashAttention 解决「如何高效算注意力」,Prefix Sliding 解决「哪些注意力根本不用算」——其 kernel 就是 FlashAttention 分块体系加了两层过滤;
  • 它和 PagedAttention 互补:一个在掩码层封顶每序列 KV,一个在内存管理层消除碎片;
  • 它和 NSA 形成对照:NSA 说稀疏注意力要原生训练,Prefix Sliding 证明推理轨迹这种「注意力高度结构化」的序列,硬掩码在现成模型上就能用;
  • 它和 GQA 一样属于「用更少的内存换来接近全注意力的质量」,只是维度不同:GQA 压的是每个 token 的 KV 宽度,Prefix Sliding 压的是 KV 的序列长度。

对推理服务系统而言,它的启示可能比方法本身更广:测试时扩展的终极形态要求「每 token 成本有界」——无论是滑窗、摘要、RNN 还是状态空间模型,做不到这一点的架构在「想几周」的尺度上都会出局。Prefix Sliding 的价值在于用最少的工程改动(一个掩码)把一个已有的全注意力模型拖进了「有界成本」俱乐部。

参考资料#

  1. Prefix Sliding for efficient test-time scaling(论文原文,arXiv:2608.26070)
  2. Muennighoff/prefix-sliding(官方代码仓库:推理、RL、数据与评估脚本)
  3. s1: Simple test-time scaling(Muennighoff et al., 2025,arXiv:2501.19393)
  4. DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning(arXiv:2501.12948)
  5. Efficient Streaming Language Models with Attention Sinks(StreamingLLM,arXiv:2309.17453)
  6. Longformer: The Long-Document Transformer(arXiv:2004.05150)
  7. Big Bird: Transformers for Longer Sequences(arXiv:2007.14062)
  8. H2O: Heavy-Hitter Oracle for Efficient Generative Inference of Large Language Models(arXiv:2306.14048)
  9. DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models(GRPO 原始论文,arXiv:2402.03300)
  10. DAPO: An Open-Source LLM Reinforcement Learning System at Scale(arXiv:2503.14476)
  11. Qwen3 Technical Report(arXiv:2505.09388)
  12. FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning(arXiv:2307.08691)
  13. FlexAttention: The Flexibility of PyTorch with the Performance of FlashAttention(arXiv:2412.05496)
  14. Why stacking sliding windows can’t see very far(Guangxuan Xiao 博客,滑窗感受野分析)
  15. The Markovian Thinker(Delethink,arXiv:2510.06557)
  16. Extending the Context of Pretrained LLMs by Dropping their Positional Embeddings(DroPE,arXiv:2512.12167)
  17. prefixsliding/evals(论文评估结果数据集)
  18. prefixsliding/train_v6_filtered(RL 训练数据集)

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Prefix Sliding 完全拆解:前缀 + 滑窗,恒定 KV 内存下的超长推理测试时扩展
https://pinghaoyang.com.cn/aigc/posts/prefix-sliding/
作者
平昊阳
发布于
2026-08-28
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
80
分类
18
标签
105
总字数
654,628
运行时长
0
最后活动
0 天前

文章目录