我前两天调一个检索增强问答的场景上下文里塞了接近八千 token 的文档片段结果模型给出的答案连基本信息都对不上。一开始我还以为是模型太弱后来把上下文砍到两千 token同一个模型同一套提示词模板准确率直接涨回去了。这个现象其实这两年已经不算冷门——上下文对模型的影响在提示词工程之后成了新的深水区。很多人习惯性认为给模型的信息越多越聪明实际操作下来完全不是这么回事。这篇文章不是我搬运哪篇论文是我自己在本地模型上跑的一组相关性裁剪实测。我会把触发这个实验的翻车现场、实验怎么设计、数字怎么变化、裁剪的几种方案怎么做以及哪些场景下裁剪救不回来全部摊开来说。适合正在做 RAG、对话式 Agent、或者长文档问答的读者参考尤其是那些总觉得上下文不够用的朋友。1. 起因一次塞满上下文的真实翻车现场1.1 一个典型的答非所问先说项目背景。我在做一个面向售后场景的问答机器人输入是用户提问模型需要结合产品手册、历史工单和客服对话记录来回答。最初版本非常简单——把可能相关的产品文档片段、几条相似历史工单、加上最近几轮对话内容全部拼进上下文丢给模型让它自己找答案。那次测试的问题是我的设备开机之后屏幕一直黑但电源灯亮的是什么原因我在上下文里放了两份文档片段。一份讲电源板指示灯的含义另一份讲屏幕背光模块的检修流程。还附带了两条历史工单一条是设备无法开机另一条是屏幕闪烁。总 token 数大概七千多。模型给出的回答是建议检查电源板可能电源供电异常请更换电源模块。这个回答放在设备无法开机的问题上是合理的但用户问的明明是电源灯亮、屏幕黑——典型背光或屏线问题。模型没抓住关键差异反而被另一份文档的强信号带跑了。更麻烦的是当我把溢出对话拿掉、只保留背光相关的那份手册片段重测模型第一时间就给出了正确的排查方向先查屏线再查背光驱动板。同一时刻我开始意识到问题不在模型能力而在于上下文本身。信息多了模型不是吸收了而是被干扰了。1.2 为什么我要专门做一次裁剪实测遇到这个翻车后我去翻了半天资料看到不少关于上下文长度的讨论但真正写到如何裁剪相关性、裁剪前后性能变化多少的实测内容很少。大多数人还在追求把上下文塞满 32K、128K、甚至 1M。我的看法是先不谈窗口长度够不够先得弄清楚一个问题塞进来的信息是不是每一条都有必要另一个原因很直接。我手头这个 32B 开源模型跑一次推理要好几秒上下文越长单次响应耗时还线性增加。如果裁剪能换来更高的准确率和更低的延迟那这套算法就不只是锦上添花而是实打实的性能优化。所以我决定做一组有对照的实验同一批问题分别在极简上下文全量塞满上下文经过相关性裁剪的上下文三种条件下跑量化到底退化多少、恢复多少。2. 实验设计把上下文变笨量化成数字2.1 测试集与任务类型我先造了一个测试集。数据来自真实售后问答场景脱敏后的模拟数据一共 1000 道问题分三类事实抽取类400 题从产品手册片段中定位具体参数例如最大噪音是多少分贝额定功率多少瓦。故障诊断类400 题给定现象要求从手册、历史工单中判断可能原因例如开头提到的黑屏问题。摘要总结类200 题基于多份文档片段生成不超过 80 字的处理建议。每道题配了相关信息池——注意是信息池而不是正确答案片段。池里包含 8 到 15 份文档片段或历史工单其中只有 2 到 4 份是真正相关的其余都是同主题但不同型号、不同故障现象的内容。这个设计模拟的是真实场景中最常见的情况你能抓到的相关信息永远混着大量看起来沾边、其实不相关的噪音。2.2 三个对照组对照组 A极简上下文。只放系统提示词 问题本身不附加任何外部资料。对照组 B全量上下文。把信息池里的所有片段全部拼进上下文再加最近 5 轮历史对话模拟能塞多少塞多少的朴素做法。这部分平均 token 量约 1.1 万到 1.4 万。对照组 C裁剪后上下文。用三套不同的裁剪方案把上下文压缩到 2000 到 3000 token 左右只保留与当前问题最相关的片段。为了控制变量模型固定用同一个开源 32B 权重文件量化分数、解码参数全部一致温度为 0。每道题只跑一次答案规则化后和标准答案比对。准确率的判定采用人工抽样复核加规则打分结合的方式避免模型自己给自己打分造成的水分。2.3 评估指标我不只盯着准确率还记录了四个指标答案准确率核心信息是否与标准答案一致。事实性错误率答案中出现标准答案没有的实体信息比如错误型号、错误参数。平均响应延迟从发送请求到完整返回的时间。平均回答长度控制组的答案长度差异也能反映问题——上下文太杂时模型容易废话多。2.4 一次不得不做的预实验理论上应该直接跑三组对比但我在预实验时发现一个坑模型对上下文中的最近内容明显更敏感。同一份正确信息放在开头还是末尾结果完全不同。所以后面所有对照实验都把正确片段的位置统一固定在一个区间避免位置变量混进来。这个发现本身就是后面要讲的位置偏差的一部分。3. 实测结果全量上下文平均掉了多少分3.1 数字出来的那一刻我有点懵先看最粗颗粒度的一组数据对照组平均准确率事实性错误率平均回答长度平均响应延迟A极简上下文84.3%7.1%86 字3.8 秒B全量上下文61.5%19.4%152 字6.9 秒C裁剪后上下文83.6%8.3%91 字4.4 秒全量上下文比极简上下文掉了接近 23 个百分点的准确率事实性错误率几乎翻了三倍回答长度膨胀了差不多一倍延迟也翻了将近一倍。这还是比较保守的数据——我在另外一组更长的测试里全量上文的准确率一度跌破 50%。裁剪后上下文基本把准确率拉回到了极简水平事实性错误率也压了下来。这说明一个关键结论上下文里真正起作用的是那几份相关片段其余部分不仅没帮忙还在不断拉后腿。3.2 分任务类型看退化幅度三类任务受影响程度完全不同。事实抽取类问题相对抗干扰全量上下文下准确率从 91.2% 掉到 78.5%。这类问题有一个明确的目标实体模型哪怕被干扰也更倾向于从中找实体词回答。故障诊断类问题最惨从 80.6% 掉到 51.2%。这类问题需要现象 - 原因的推理链一旦被不相关的工单里类似的故障描述干扰模型会很容易把两个不同故障混成一件事。我抽查了几条错误样本发现高比例的情况是模型把屏幕故障答案写成电池故障然后一本正经地给出检测步骤完全没意识到现象对不上。摘要总结类介于两者之间从 80.2% 掉到 64.8%。模型在信息嘈杂时倾向于平均引用多个来源导致摘要变得泛泛而谈不敢给出具体判断。3.3 位置偏差中间信息真的会被忽略我把相关片段放在三种位置测试上下文前部、中部、尾部。结果和图里大量关于 lost in the middle 的讨论一致中部信息被利用率最低准确率只有 52.3%前部和尾部都在 70% 以上。这个现象在长上下文场景格外明显可能和模型自注意力机制对各位置信息的加权方式有关中间位置的信息容易被两侧内容掩盖。这也是我在预实验里踩坑的原因。一开始我图省事把所有文档按固定顺序拼接相关片段有时候在中间有时候在末尾结果就是同一套数据跑出完全不同的成绩。所以在设计任何上下文方案时位置分配和相关性筛选同等重要。3.4 降级背后的两个机制全量上下文为什么会造成这样的退化我自己的理解是两个机制叠加。第一是注意力稀释。模型的自注意力机制在每一层都在不同 token 之间分配注意力权重当上下文里出现大量与问题无关的内容时无关 token 会占用一部分注意力预算。这就像一群人同时说话你没法只挑选其中真正有用的那句音量大的废话会盖过关键信息。第二是信息冲突诱导。外部文档里经常带有隐含的指令性内容例如如果出现 X 情况请先更换 Y。当多个不相关但表面上相近的片段同时在场时模型容易把某个片段中的指令迁移到当前问题上形成错误推理。这就是为什么事实性错误率会三倍上涨——不是模型变笨了而是它从比噪音里学到了错误规则。4. 相关性裁剪三套方案从词频到 embedding4.1 方案一关键词匹配裁剪先说最朴素的一版。把用户问题和每个文档片段做词级别重叠统计按重叠度从高到低排序只保留前 N 个片段。做法很简单分词后移除停用词。对每个片段统计与用户问题的共享词汇数。按数量排序截取前 4 到 6 个片段。好处是快几乎零成本本地跑一千条数据也就几秒钟。坏处也很明显词汇重叠度高不代表语义相关。用户问屏幕黑屏一个讲屏幕亮度调节的片段和一个讲不开机黑屏的片段可能有相同的关键词但前者对诊断毫无帮助。这套方案实测准确率只有 75.2%比全量好一些但距离极简基线还有明显差距。4.2 方案二embedding 相似度裁剪第二版我把文本向量化后用余弦相似度做排序。当时我用了一个开源的 embedding 模型把用户问题和每个片段各转成 768 维向量算相似度后保留 top-K。效果比词频法明显提升准确率到了 81.9%。这套方案的问题是阈值不好拍。top-K 里 K 设多大设太大还是会带进低相关片段设太小可能漏掉必要信息。我试过 K3、5、8 三档。K8 时准确率不升反降说明多出来的低相关片段又开始制造噪音。最后我改成动态阈值——按相似度分布的自然断点决定保留数量而不是写死 K。这里有个小提醒embedding 模型选择会影响结果。我对比过两个不同的开源 embedding 模型在同一批数据上准确率能差 3 到 4 个点。不用盲目追榜单高分先在自己数据上跑一遍看稳定性。4.3 方案三滑动窗口分块 块级排名第三版是效果最好的方案也是我现在常用的方案。思路是把长文档切成长度相近的片段再配合窗口滑动做块级筛选。具体操作路径先把原始文档按 200 字左右切块相邻块之间保留 30 字左右的重叠。重叠是为了避免信息断在边缘。对每个块和用户问题计算 embedding 相似度。额外引入一个滑动窗口打分机制相似度最高的块及其前后相邻块会被同时保留。因为嵌在上下文中的关键信息往往前后都有铺垫单纯保留一个块可能丢失上下文。最后对保留下来的块按原始文档顺序重新排列而不是按相似度排序。这套方法实测准确率到了 83.6%接近极简基线。前后相邻块这一手很关键它能缓解信息断点问题很多真正有用的细节其实藏在高相似块附近。我用一个只保留前 2000~3000 token 的后续测试里发现滑动窗口方案比单纯 top-K 多保留了约 12% 的修正性信息这些信息负责纠正模型对前面高相似块的过度依赖。4.4 三套方案横评裁剪方案平均准确率事实性错误率平均上下文长度处理耗时无裁剪全量61.5%19.4%~12000 token无额外耗时关键词匹配75.2%12.6%~2800 token0.3 秒/条embedding 相似度81.9%9.1%~2500 token0.8 秒/条滑动窗口分块83.6%8.3%~2300 token1.1 秒/条滑动窗口方案额外耗时相对可控带来的准确率提升也最值。如果追求极低延迟的场景可以用 embedding 相似度效果也够用。4.5 关于示例选择的延伸上下文选择还涉及到另一个热门话题——示例选择策略。在做少样本任务时我们应该选与当前输入最相似的示例。而实测表明示例数不是越多越好给 4 条高度相似的示例往往比给 12 条相关性参差不齐的示例效果更好。相关性裁剪在选示例场景同样适用。5. 裁剪不是万能药边界情况与失败案例5.1 相关性和必要性不是一回事裁剪方案有一个天然盲区它只保留与问题相关的片段但有时候模型要正确回答问题反而需要一段看似不太相关、却提供了关键前提的信息。我遇到过一个典型案例。某道题问设备支持的输入电压范围是多少标准答案在片段 A 里而片段 B 说的是该型号只支持直流供电看起来和问题无关。但如果不告诉模型只支持直流供电模型可能从同类产品的通用参数里猜出交流电压范围导致错误。按 embedding 相似度排序片段 B 被裁掉了模型输出的是通用参数。这类问题单靠相似度最高的规则解决不了。我的补救措施是在裁剪后额外保留一个安全片段区——不参与相似度排序专门放那些低频但可能影响全局判断的内容比如适用范围、注意事项、限制条件。5.2 跨块信息拼接的丢失滑动窗口方案已经考虑了相邻块但遇到知识散布在多个不相邻块里时还是会出问题。比如现象描述在文档前部排查步骤在文档中部注意事项在文档末尾。即使我把三处都保留模型也可能因为顺序被打断而无法建立完整链路。针对这个问题的实验里我试过把保留片段按原文顺序拼接而不是按相似度排序。效果有改善但仍然比完整文档全量放入差。这类场景的本质是需要的是跨文档推理而不是单片段检索。此时我会考虑把裁剪阈值调高保留更多上下文甚至直接切换成先检索后融合的多跳方案——也就是用第一轮答案生成检索词再带着新信息重新询问一次模型。5.3 多轮对话中的时间戳陷阱当上下文包含多轮历史对话时相关性本身是动态的。用户第一轮问的是 A 问题第五轮转到了 B 问题如果只按当前问题相关性裁剪会把第一轮的背景信息全部裁掉。但用户可能会基于第一轮信息在第七轮回马枪。我在一版实验里犯了这个问题当时的裁剪模型只看了当前轮次的问题完全没有保留历史关键信息。结果面对我之前说的那个设备现在又出现新问题了这类表述时模型完全失忆。解决方案是把多轮对话拆成三部分分别处理历史问题摘要、历史关键答案、当前轮上下文。对历史问题摘要常驻保留对历史关键答案做时间衰减加权——越近的越重要但早期的核心实体信息单独持久化。这套做法让我在后续一个客服 Agent 的项目里多轮任务完成率提升了接近 20%。5.4 阈值过严导致的召回不足还有一个需要警惕的方向裁剪的目标是去掉不相关的但相关的判定永远存在误差。如果阈值设得太严比如只保留 top-2遇到相关片段分布比较散的问题就会漏掉关键信息。我在另一组对比里试过 top-2、top-5、top-8结论很有意思阈值过严造成的准确率下降有时候比阈值过松还要严重。因为漏掉信息会让模型进入自由发挥模式幻觉率飙升。所以一个稳妥的做法是与其追求完美的筛选不如留出冗余。在资源允许的前提下多保留 20% 的候选片段通常不是坏事。6. 最后最后掏几句实操心得实验做到这里我对上下文塞太多模型反而变笨这件事已经有了比较完整的体验。最后说几点实际操作中沉淀下来的经验。先记住一条铁律上下文优化追求的不是信息多而是信息干净。你给模型输入的每一条无关信息都在为它犯错误创造空间。系统的提示词、外部文档、历史对话、示例内容四类信息各有各的作用不要图方便全部拼在一起。再补充一个容易被忽视的细节上下文内信息的顺序会显著影响模型对内容的利用程度。重要的内容放在开头和结尾最容易被模型把握放中间容易被淹没。裁剪完之后优先把系统指令放在最前面紧跟高相关文档片段最后再放历史对话这比随手拼装的顺序能多出几个百分点的准确率。关于成本收益我个人的选择倾向是小规模、低延迟的内联应用先用关键词方案起步因为零依赖、可解释性强有向量索引基础时直接上 embedding 相似度兼顾效果和速度处理长文档、跨段推理多的场景滑动窗口分块加块级排序是默认方案贴近真实信息分布能省掉很多信息断点问题。裁剪方案上线前一定要配一套评估集持续跑。我发现很多问题是在数据分布变化后才暴露出来的比如用户开始用更多口语化表达、新兴词汇增多后旧的相似度阈值就不再可靠。定期用新数据回归一次比任何调参都重要。如果你准备把这套思路用到自己的项目里我建议从对照组 A 和对照组 B 跑起。先量化塞满上下文到底损失了多少再决定要不要上裁剪。不同模型对上下文噪音的耐受度差异很大有的模型塞到一定程度才开始崩有的从一开始就崩。数据不会骗人跑完你就知道自己的模型处在哪一段了。