上下文窗口里的干预权重:为什么模型更听开头、最近和当前任务

基于长上下文与多轮对话论文,拆解上下文里哪些信息最能干预模型输出、哪些信息会被有效淘汰,以及如何安排系统提示、历史、摘要和当前任务。

我之前很容易把这个问题说成“近因效应”:长上下文里,模型更听最近的内容。

这个说法太粗了。真正值得研究的问题是:

在一段很长的上下文和多轮对话里,哪一块内容最能干预模型下一次输出?哪些内容虽然还在窗口里,却已经被有效淘汰?背后的机制是什么?

先给结论:最强干预区通常不是单一位置,而是“当前任务控制面”。 它经常位于上下文末尾,因为最后的用户请求、最近工具结果、最近纠错和当前输出格式直接定义下一步生成。但开头也有强锚定作用,中间则最容易被注意力偏置、检索竞争、摘要损失和旧信息干扰稀释。

所以这不是一个简单的“近因 vs 远因”问题,而是位置、任务绑定、指令层级、检索难度、相似信息干扰和应用层记忆管理共同作用的结果。

先把证据钉住

这篇不是凭感觉讲 prompt 技巧。相关现象已经被多条研究线反复观察到。

Lost in the Middle 是最经典的一条证据。论文把相关信息放在长上下文的不同位置,发现模型在开头和结尾利用得更好,放在中间时性能明显下降。这个结论很重要:模型“拥有长窗口”不等于它能稳定使用窗口里的所有信息。

Found in the Middle 更进一步,把 lost-in-the-middle 和模型内在的 U-shaped attention bias 联系起来。它观察到开头和结尾位置更容易获得注意,即使那些位置并不一定更相关。论文还通过 attention calibration 改善中部信息利用,这说明问题不只是“文本太长读不过来”,而是 attention 本身带有位置偏置。

StreamingLLM 解释了开头为什么特殊。论文发现初始 token 会成为 attention sink:即使语义上不重要,也能吸走稳定注意。保留初始 token 的 KV 能让流式长文本生成更稳定。这给我们一个提醒:开头的强影响力里,有一部分是结构性锚点,不完全等同于“模型认为开头语义最重要”。

Attention Sorting Combats Recency Bias 从 RAG 场景观察到,较早位置的相关信息平均被关注得更少。作者利用一次解码时的 attention 对文档排序,再把高 attention 文档放到更靠后的位置,能改善长上下文生成。这支持一个工程判断:当多个证据块竞争时,位置会影响相关证据能不能真正进入答案。

多轮对话里还有另一类证据。LLMs Get Lost In Multi-Turn Conversation 发现,多轮设置相比单轮完整指令有明显性能下降,论文把退化拆成能力小幅下降和不可靠性显著上升,并指出模型常常过度依赖早期错误假设,走错后不易恢复。

最贴近“淘汰和干扰机制”的,是 Unable to Forget。这篇用 proactive interference 范式测试模型:连续给一组相似 key-value 更新,最后只问最终值。结果是,干扰越多,准确率会持续下降;错误常来自早先已被覆盖的旧值。更关键的是,正确值明明靠近查询,模型仍会被旧值污染。也就是说,近并不一定赢,旧信息如果相似、重复、没有被明确废弃,也会形成强干扰。

Positional Biases Shift as Inputs Approach Context Window Limits 还补了一层:位置偏置会随输入长度占模型窗口比例变化。论文指出,当输入占窗口比例较小时,lost-in-the-middle 更明显;当输入接近窗口上限时,primacy 减弱,recency 和距离末尾更近的优势更突出。它还强调,推理任务里的位置偏差很大程度继承自检索偏差:如果模型先没把证据捞出来,后面的 reasoning 再强也没用。

这些论文合在一起,给出的不是一句“模型有近因效应”,而是一张更复杂的图:模型会受开头锚点、结尾任务、注意力偏置、证据竞争和旧信息干扰共同影响。

什么叫“有效淘汰”

上下文里的淘汰有两种。

第一种是物理淘汰。超过 context window,或者应用层做了截断、滑动窗口、KV cache 裁剪、消息裁剪,这些 token 就真的不在模型输入里了。很多聊天系统默认保留最近 N 条消息,早期内容会直接消失。StreamingLLM 这类工作讨论的就是在有限 KV 预算下,哪些 token 应该被保留。

第二种更隐蔽,是有效淘汰。token 还在上下文里,但它对下一次生成几乎没有稳定干预力。它可能在长文中部,可能被更近的任务指令覆盖,可能和其他相似片段冲突,可能被摘要压缩掉关键条件,也可能被模型的检索偏置漏掉。

这就是长上下文最容易误导人的地方:你以为“我已经把材料塞进去了”,但模型实际用到的,可能只是开头、结尾和少数被当前查询强绑定的片段。中间那一大段更像档案,不像控制面。

哪一块干预最强

如果只问“下一次输出会被什么最强改变”,我会按这个顺序理解。

第一,最后的当前任务块。 包括最后一条用户请求、刚返回的工具结果、最近的格式要求、最近的纠错、当前步骤目标。它最强,不只是因为近,而是因为它定义了下一 token 生成的条件:现在要回答什么、用什么材料、按什么格式、不能做什么。

第二,开头的稳定规则和身份锚点。 系统提示、角色、长期约束、工具使用边界通常放在最前面。开头有 primacy 和 attention sink 的结构优势,但它最好承载短而稳定的规则,而不是塞满长篇背景。因为开头被关注,不代表长篇开头都能被正确执行。

第三,被当前查询显式召回的证据块。 一段材料如果和当前问题词面、实体、格式、任务目标强相关,就算不在末尾,也可能被捞出来。但如果它在中间、周围噪声多、相似干扰多,利用率就会下降。

第四,普通历史对话。 历史本身不是控制面。多轮对话越长,越容易混入过期决策、早期误解、被覆盖的约束和无关寒暄。如果不做状态整理,历史会从“记忆”变成“干扰源”。

这也是为什么多轮 agent 不能只把完整聊天记录一直塞回模型。长历史最需要被管理,而不是被崇拜。

背后机制可以拆成三层

第一层是结构层。Decoder-only Transformer 使用因果注意力,生成当前位置只能看过去 token。不同位置到最终输出的路径、残差连接、位置编码、attention softmax 归一化都会造成不均匀影响。attention sink 说明开头 token 可能因为结构和训练动态获得稳定注意;lost-in-the-middle 说明中间位置常处在弱势区。具体机制在研究中仍有争论,但“位置影响可用性”已经不是玄学。

第二层是检索层。模型先要从上下文里找到相关信息,然后才谈得上推理。长上下文里,相关片段和无关片段竞争注意力;相似片段还会互相污染。Unable to Forget 的结果尤其关键:旧值即使已经被新值覆盖,仍可能在检索时冒出来。很多“模型不听话”其实不是它不会推理,而是它检索到了错误版本的状态。

第三层是控制层。模型不是数据库,它是在续写当前对话。最后的用户请求、最近的开发者约束、工具返回结果和输出格式,会强烈规定“接下来该写什么”。这就是为什么同一条事实放在历史深处,和放在当前任务前一段,干预力完全不同。上下文里的信息不只是信息,还是行动指令、证据、状态或噪声。不同类型的文本有不同权重。

上下文安排策略

我现在会把长上下文分成四个区域,而不是简单按时间顺序堆。

开头放宪法,不放档案。 系统级规则、不可变目标、权限边界、输出原则放开头,而且要短。开头是锚点,不适合承载大量细节。越长越容易把真正重要的规则埋掉。

中间放档案,但不要让档案承担控制职责。 长文档、历史记录、参考材料可以放中间,但要承认它们是低干预区。关键事实不能只在这里出现一次。需要被执行的约束,要进入状态摘要或当前任务块。

结尾放当前状态和当前命令。 在最后请求前,放一个短的 working state:当前目标、已确认约束、最近决策、废弃信息、必须使用的证据、输出格式。这个块应该比原始历史更可信。

被覆盖的信息要显式作废。 不要只写“新的值是 B”。更好的是写“旧值 A 已废弃,当前唯一有效值是 B”。这不是啰嗦,而是在对抗 proactive interference。旧信息如果不被标废弃,会继续参与竞争。

关键约束要双写。 稳定原则在开头写一次,当前任务前再写成操作化约束。比如开头写“不要泄露内部路径”,结尾当前任务块写“发布稿不得包含本机绝对路径”。前者提供长期边界,后者提供当下执行力。

摘要不是压缩历史,而是重建状态。 很多摘要失败,是因为它只把聊天缩短,没有区分“仍有效”“已作废”“待确认”“背景参考”。好的摘要应该像状态机,不像会议纪要。

RAG 不要盲目塞满窗口。 少量高质量证据通常比大量噪声更稳定。特别关键的证据可以放在靠近问题的位置,或者在末尾形成 answer-critical facts。Found in the Middle 和 Attention Sorting 都支持一个方向:要么校准位置偏置,要么承认偏置并做重排。

让模型先检索再推理。 对长上下文任务,可以要求模型先列出使用了哪些证据块、哪些约束仍有效,再给最终答案。这样不是为了形式,而是强迫它把“取到正确上下文”这一步显性化。

一个实用模板

我会把复杂多轮任务的上下文组织成这样:

[开头:稳定规则]
- 身份和权限边界
- 永久约束
- 高风险禁止项

[中间:参考档案]
- 原始资料
- 历史对话
- 长文档
- 工具日志

[靠后:工作状态]
- 当前目标
- 当前唯一有效决策
- 已废弃信息
- 必须使用的证据
- 待解决问题

[最后:当前请求]
- 这一步要做什么
- 输出格式
- 验证标准

这个模板背后的原则是:开头负责锚定,结尾负责执行,中间负责存档,状态摘要负责把历史转成当前可用信息。

怎么验证自己的上下文是否安排对了

最简单的办法是做位置消融。

拿同一条关键约束,分别放在开头、中间、结尾、状态摘要里。然后让模型完成同一个任务,统计它是否遵守约束。再加入相似旧约束,看它是否会被干扰。最后把原始历史替换成结构化状态摘要,看输出是否更稳定。

如果一个约束只在结尾有效,说明它是执行性约束,应该进 working state。如果一个事实放中间经常丢,说明它不能只当普通档案,要被召回到当前证据块。如果模型经常引用旧值,说明上下文缺少废弃标记。

这个实验比凭感觉调 prompt 更可靠。

Takeaway

长上下文不是线性记忆。它更像一个带偏置的工作台:开头有锚,结尾有命令,中间有档案,旧信息会干扰,新信息也不一定自动胜出。

所以,上下文工程的重点不是“尽量塞进去”,而是把不同文本放到正确角色里:

规则要短,放开头并在当前任务前重申。
历史要整理,变成状态而不是原始堆叠。
证据要靠近问题,避免在中间孤立出现。
旧信息要标废弃,否则它会继续污染答案。
当前请求要清楚,因为它通常拥有最强执行干预力。

近因效应只是表层。真正的机制是:模型在有限注意力、位置偏置、检索竞争和当前生成目标之间做选择。我们安排上下文,就是在决定哪些信息能从“窗口里的文本”变成“下一步输出的约束”。