3.2 上下文压缩与窗口管理


文档摘要

3.2 上下文压缩与窗口管理 一句话带走:上下文窗口不是「越长越好」,而是「越省越好」。用滑动窗口保近期、用摘要保长程、用 RAG 保外部知识,再给每轮对话设一个「上下文预算」,你就能在显存不爆、推理不慢的前提下,让多轮长对话依然答得准。 为什么必须管窗口 很多人以为「上下文窗口越大越省事」。但在百万级 QPS 下,这是个危险的错觉。原因有三: 成本随 token 线性增长:上下文越长,每次 prefill 和 KV 占用越大,算力账单线性上升。 长上下文会「稀释注意力」:模型在超长上下文里未必能稳定抓住关键指令,反而更容易答偏——这不是玄学,是长上下文场景反复被验证的现象。 显存是硬约束:KV Cache 随上下文长度增长,海量并发长对话会直接把显存打满,导致排队甚至 OOM。

3.2 上下文压缩与窗口管理

一句话带走:上下文窗口不是「越长越好」,而是「越省越好」。用滑动窗口保近期、用摘要保长程、用 RAG 保外部知识,再给每轮对话设一个「上下文预算」,你就能在显存不爆、推理不慢的前提下,让多轮长对话依然答得准。

为什么必须管窗口

很多人以为「上下文窗口越大越省事」。但在百万级 QPS 下,这是个危险的错觉。原因有三:

  1. 成本随 token 线性增长:上下文越长,每次 prefill 和 KV 占用越大,算力账单线性上升。
  2. 长上下文会「稀释注意力」:模型在超长上下文里未必能稳定抓住关键指令,反而更容易答偏——这不是玄学,是长上下文场景反复被验证的现象。
  3. 显存是硬约束:KV Cache 随上下文长度增长,海量并发长对话会直接把显存打满,导致排队甚至 OOM。

所以正确姿态是:用最少的 token,保住回答所需的全部信息。这就是上下文压缩的精髓。

上下文预算裁剪

手段一:滑动窗口(保近期)

最简单的压缩:只保留最近 N 轮对话,更早的丢弃。实现成本最低,适合「对话大多只关心最近几轮」的场景(闲聊、短期任务)。

坑点 · 长程依赖断裂:纯滑动窗口会丢掉「前文定下的偏好/事实」。比如用户第 1 轮说「我姓王,用尊称」,第 20 轮还在对话,滑动窗口早把这轮滑掉了,模型突然忘了叫你「王先生」——这就是经典的「记忆断片」。所以滑动窗口不能单独扛所有场景,必须配摘要。

滑动窗口

手段二:对话摘要(保长程)

解决滑动窗口「断片」的标准解法:把超出窗口的早期对话,周期性地提炼成一段摘要,再用「摘要 + 近期原文」拼成上下文。这样模型既记得「你说过的关键偏好」,又看得清「最近在聊什么」。

两种触发时机

  • 定时触发:每 5~10 轮让模型生成一次滚动摘要,老摘要再被新摘要吸收。
  • 容量触发:当拼好的上下文逼近预算上限时,先压缩旧的、保住新的。

摘要的质量决定天花板:摘要本质是「再生成一次」,可能丢细节。对强事实场景(如「订单号 12345」)要保留关键实体清单,不要只留泛泛的「用户问了订单」。

摘要压缩

手段三:RAG 按需检索(保外部知识)

当对话依赖「长文档 / 知识库」(合同、手册、代码库)时,把全文塞进窗口既不经济也不现实。正确做法是 RAG:先根据用户问题检索出最相关的几个片段,只把片段拼进窗口

和压缩的关系:RAG 把「全量上下文」换成「按需取用的精华上下文」,本身就是最强的上下文裁剪。它和滑动窗口/摘要并不冲突,反而互补——近期对话用窗口+摘要,外部知识用 RAG 片段。

要点:检索的相关性直接决定回答质量。检索太宽(塞一堆无关片段)会稀释注意力、浪费 token;太窄会漏关键信息。检索结果要做去重和重排,并控制拼入窗口的片段数量上限。

RAG上下文组装

手段四:上下文预算(把上面三者统起来)

我强烈建议给每轮对话设一个上下文预算(token budget):比如「单轮上下文不超过 8000 token」。然后按优先级分配这块预算:

  1. 系统提示词与工具定义:最高优先级,固定占用。
  2. 当前用户问题:必保。
  3. 近期对话窗口:按轮数保留。
  4. 摘要:压缩后的长程记忆。
  5. RAG 片段:按需取用,且受剩余预算约束。

当预算不够时,先砍 RAG 片段数量,再压缩摘要,最后才动近期窗口——优先级低的先退让。这一套分配逻辑,应当做成可观测、可配置的策略,而不是硬编码在业务代码里。

把压缩和缓存连起来看

本章两节其实是一套组合拳:3.1 的缓存让「算过的不重算」,3.2 的压缩让「要算的更少」。二者叠加效果最好——压缩后的上下文更短,缓存命中率反而更高(因为相似的短上下文更容易完全一致)。所以在工程落地时,我建议先做好上下文压缩(把噪声砍掉),再叠缓存(把干净结果留下来),顺序别反。

几个实战建议

  • 默认给所有对话开滑动窗口 + 摘要,RAG 仅在涉及外部知识时启用。
  • 监控两个指标:「平均上下文 token 数」和「长对话(>20 轮)的回答准确率」,后者掉下来说明压缩策略在丢关键信息。
  • 摘要触碰关键实体(订单号、人名、配置项)时要设「白名单保护」,摘要不得删改。
  • 上下文预算做成配置项,按模型窗口大小和成本实时调整,别写死。

把这节做透,你的引擎就能在不堆显存的前提下,稳稳撑住超长多轮对话——这是「算得起」和「答得准」之间真正的解法。

与缓存的协同:压缩顺序很关键

再强调一次工程落地的顺序:先压缩、再缓存。理由很硬——压缩把噪声和冗余砍掉后,每条上下文更「纯净且趋同」,相同意图的请求更容易落到字节级一致,结果缓存命中率随之升高;反之若先缓存原始长上下文,不仅每条占的空间大、淘汰快,而且相似请求因冗余差异难以精确命中。所以把「上下文预算裁剪」放在「写回缓存」之前,是事半功倍的顺序。

一个容易被忽视的成本:摘要与检索本身也花钱

上下文压缩不是免费午餐。摘要要调用模型再生成一次,RAG 检索要跑向量库查询,这些都有成本和延迟。所以压缩策略必须算「净收益」:

  • 若一段对话只有 3 轮、上下文本来就很短,做摘要反而亏(生成摘要的 token 比省下的还多)——小对话直接原样进窗即可,别为了「统一架构」而过度压缩。
  • 若对话超过 15~20 轮或引用长文档,压缩收益才明显。建议按上下文长度设阈值:短于此阈值走「原样 + 滑动窗口」,长于此才触发摘要与 RAG。

换句话说,压缩策略本身要「自适应」:上下文越膨胀,压缩越激进;上下文本就精简,就少动它。这比「无差别全量压缩」更省、更稳。

上下文质量的可观测性

压缩做得好不好,不能只看「省了多少 token」,更要看「答得准不准」。建议埋两组指标:

  • 显性指标:平均上下文 token 数、RAG 检索命中数、摘要触发频率、预算超限被裁剪的请求占比。
  • 隐性指标(更重要):长对话(大于 20 轮)的「用户退回率」或「重复提问率」。如果用户开始反复问同一件事、或明显在纠正模型「你忘了之前说的」,基本就是压缩把关键信息丢了——这是摘要质量或 RAG 召回在报警。

把隐性指标接进监控,压缩策略才有「刹车」,不会为了省 token 把回答质量拖垮。

常见反模式清单

我见过太多团队在这块踩坑,列几条最典型的:

  1. 无脑堆窗口:以为「长窗口 = 不用管压缩」,结果显存爆、账单炸、注意力还被稀释。窗口是预算,不是垃圾桶。
  2. 只滑动不摘要:短期对话没问题,一遇到「前文定下的偏好/事实」就断片,用户体验断崖式下跌。
  3. RAG 片段一股脑全塞:检索回来 20 个片段全拼进去,既浪费 token 又稀释关键信号。要重排、要去重、要限量。
  4. 摘要丢实体:把「订单号、人名、配置项」这类强事实也一并概括掉,导致后续回答张冠李戴。关键实体必须白名单保护。
  5. 压缩与缓存脱节:缓存原始长上下文,命中率低、占用大。记住先压后存。

落到配置的清单

为了可运维,建议把以下项做成配置,而非写死在业务代码:

  • 单轮上下文预算上限(token)。
  • 滑动窗口保留轮数。
  • 摘要触发轮数 / 触发预算阈值。
  • 是否启用 RAG、单次最多拼入片段数。
  • 关键实体白名单(摘要不得删除的字段)。
  • 压缩策略的优先级顺序(系统提示词 > 当前问题 > 近期窗口 > 摘要 > RAG 片段)。

这套配置接上灰度发布,你就能边放量边调参,而不是上线后被动救火。

把这节和上一节的缓存组合拳打顺,你会在「显存不爆、推理不慢、账单可控」的前提下,稳稳撑住超长多轮对话——这就是「算得起」和「答得准」之间真正的解法。

摘要的本质:一次「有损但可控」的再生成

要正确对待摘要,得先接受它是有损的。模型把十轮对话压成一段话,必然丢掉细节。工程上能做的,是把「损」控制在不影响后续回答的范围内:

  • 分层摘要:不要每次都从零摘要。维护一份「滚动摘要」,每轮只把「本轮新增 + 与旧摘要有冲突的更正」合并进去,避免反复从原始历史重摘要带来的信息漂移。
  • 结构化摘要优于自由文本:比起「用户问了订单相关的事」,更好的摘要是保留字段——「用户姓名:王;订单号:12345;诉求:退款」。把摘要做成半结构化,关键实体零丢失。
  • 冲突检测:用户中途改了偏好(「不,用顺丰」),摘要必须覆盖旧的而非叠加旧的。摘要逻辑要识别「修正」语义,否则会把矛盾信息一起喂给模型。

RAG 片段的拼装学问

RAG 不是「检索到就全塞进去」。拼装阶段有几个细节决定回答质量:

  • 重排先于拼装:检索返回的一批片段按相关性初排后,再用重排模型精排,只取 top-k 拼入窗口,避免无关片段稀释注意力。
  • 带出处而非裸拼:把片段连同其来源标识一起拼入,并在提示词里让模型「仅依据所给片段回答」。这既减少幻觉,也方便用户溯源。
  • 去重与合并:同一份知识可能被拆成多个片段重复召回,拼装前要去重,否则浪费 token。

上下文预算的优先级分配示例

假设单轮预算 8000 token,一个典型分配(可按业务调):

  • 系统提示词与工具定义:固定 1500 token(最高优先级,不可被裁)。
  • 当前用户问题:必保,约 200 token。
  • 近期对话窗口(最近 6 轮):约 2400 token。
  • 滚动摘要:约 1500 token。
  • RAG 片段(top-4):剩余约 2400 token 由它占用,若检索片段少则空出预算不硬塞。

当近期窗口变长、逼近预算时,先压缩摘要(保留实体,砍掉描述),再减少 RAG 片段数;只有极端情况才裁剪近期窗口。这个「优先级退让」逻辑写成配置,比硬编码灵活得多。

多模态与超长文档的延伸

如果对话涉及图片或超长文档(代码仓库、PDF),上下文管理还要升级:

  • 图片不要原图入窗,先转成描述或裁剪关键区域再入上下文。
  • 超长文档用「分块检索 + 按需取片段」,绝不全量入窗;必要时做多轮检索(先检索章节,再检索段落)。
  • 这类场景 RAG 的权重会显著上升,滑动窗口和摘要退居辅助。

延伸方向很多,但万变不离其宗:用最少 token 保住回答所需信息。这一条是上下文管理的第一性原理。

小结

本章两节合起来,给出了「让进来该算的请求算得更少」的完整打法:3.1 用三级缓存复用已算结果,3.2 用压缩让每次要算的上下文更短。二者叠加,是百万级对话引擎「算得起」的真正杠杆,远比换更快的框架来得实在。落地时记住三句话:缓存先精确后泛化、压缩先裁后存、配置要可灰度可观测。


发布者: 作者: 转发
评论区 (0)
U