ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LLM输入输出压缩实测:输入压缩超60%,模型开始一本正经地胡说八道

LLM输入输出压缩实测:输入压缩超60%,模型开始一本正经地胡说八道

我最近被一个实际问题逼到了墙角:上下文窗口标称越来越大,可一旦把真实业务数据塞进去,还是会撞上长度上限;就算塞得下,调用成本和延时也扛不住。于是我开始尝试在两头做文章——输入侧砍掉prompt里的冗余描述,输出侧限制生成长度。折腾几轮后我发现一个特别诡异的现象:有些任务压缩完依然稳,有些任务一压缩模型就开始满嘴跑火车,给出的答案像模像样但经不起细看。后来我专门搭了一套测试框架,代号CAVEWOMAN,用来系统观察LLM在语言输入和输出压缩下的行为变化。这篇文章就是把这段实验记录整理出来,包括设计思路、关键数据、翻车现场,以及可以直接拿去用的一套自测方法。适合正在做LLM应用落地的工程师、研究模型压缩和提示词稳定性的算法同学,也适合那些被“文本超过长度限制”反复折腾的产品开发者。

1. 为什么非要研究输入输出压缩:窗口、成本与失控感

1.1 上下文窗口再大,也填不满真实业务的贪心

我见过不少团队,一看模型宣传里写着几百K上下文窗口,就觉得“长文本焦虑解除了”。真到接项目的时候才发现,事情完全不是那么回事。RAG模式下,检索回来的片段随便一拼就是两三万token;多轮对话里,历史消息按轮次累加,聊到二十轮以后,光历史就能占到窗口的一半。你以为窗口够用,其实留给思考的位置越来越窄,模型开始把早先的信息“选择性遗忘”,注意力的均值被拉平,越靠中间的内容越容易被忽略。

所以一个绕不开的工程命题出现了:我们真正需要的不是“更大的窗口”,而是“更小的输入”。输入压缩在应用里已经无处不在——删掉提示词里的客套话、把长文档浓缩成摘要、把历史对话截断成关键事件、用关键词列表代替整段文本。这些都是压缩。问题是每个团队都在凭感觉压缩,删多删少全看心情,删完之后模型是否还能保持原有表现,缺乏一个可度量的标准。

输出压缩也是同样的逻辑。为了控制响应时长和token成本,给生成接口设一个max_tokens上限是常规操作;或者要求模型“只给结论”“用三句话回答”“不要解释”。这些约束本质上是把输出空间压缩到更小的区域。结果就是模型在信息表达上做减法,有时减掉的是冗余,有时减掉的却是必要条件。它自己不知道哪些信息该丢,于是走一步看一步,经常在“字少了”和“话说错了”之间反复横跳。

1.2 输入压缩和输出压缩在真实场景里的典型形态

做测试之前,我把常见的压缩动作分成了三类。第一类是删除压缩,按词频、停用词表或者简单的关键信息重要度把词句直接删掉。这种方式最省资源,但语义损失随压缩率指数增长。比如把“小明因为下雨所以没去打球”压成“小明雨打球”,人类还能半猜半推,模型就很容易理解成“小明喜欢在雨天打球”。

第二类是语义压缩,先用一个小模型把原文生成一段摘要,再用摘要去喂主模型。这种方式的压缩质量上限高,但引入了级联误差。摘要模型漏掉的细节,主模型无论如何也补不回来。而且摘要模型本身也是LLM,它也会有自己的偏见和幻觉,等于在正式回答之前先埋了一颗雷。

第三类是混合压缩,先抽关键句,再把抽出来的句子做指代消解和逻辑词保留,尽可能维持实体关系和因果链。这种方式在工程上最实用,但操作难度也最高,需要维护一套改写规则。许多团队做到一半就放弃了,因为压缩逻辑越复杂,给线上排查带来的麻烦也越多。

输出压缩的形态相对简单:一种是硬性长度限制,比如“最多生成150个token”,模型在预算内硬挤答案,很容易出现结论残缺、重复啰嗦;另一种是模板约束,比如“必须按JSON格式回答”“只输出数字”,这本质上是一种结构化压缩,模型必须把宽泛的自然语言表达压进一个窄模板里。第二种比第一种稳定,但会牺牲表达能力,遇到需要补充说明的情况就变得非常僵硬。

2. CAVEWOMAN项目:我到底想测出什么

2.1 项目代号的含义:一个关于“洞穴视角”的假设

先解释一下这个奇怪代号。CAVEWOMAN直译过来是“洞穴女人”,听起来像考古项目,其实我想表达的是:当LLM拿到一段被高度压缩的输入,它面对的世界就像穴居人只能看到墙上的影子,所有判断都建立在残缺的符号上。这个代号提醒我,做压缩测试时不能把模型当作一个“能自动脑补缺失信息的完美读者”,而是要承认它在残缺环境下会发展出一套诡异的生存策略——有时猜得很准,有时会编出完全不存在的东西。

项目全称我起得比较随意,就叫Compression Analysis and Verbal Evaluation of Word-level Omission with Model Attention Navigation,核心就是把“词层面的信息缺失”和“输出层面的行为漂移”绑定在一起观察。实验设计围绕一个核心假设:LLM对压缩的耐受不是平滑下降的,而是存在一个断裂阈值。在阈值之前,模型还能靠语境和概率惯性维持表面正确;越过了阈值,各种异常会集中爆发。另一个假设是:不同的任务类型、不同的输出约束,会让模型走上完全不同的“失常方向”,有的趋向幻觉,有的趋向漏信息,有的趋向复读机。

2.2 我用的数据集与三类压缩策略

为了不对真实业务数据负责,我构造了一套模拟项目X的测试集,一共1000条样本,覆盖五类常见LLM任务:事实问答、摘要生成、信息抽取、逻辑判断和指令执行。每条样本包含原始输入、标准答案以及一个可量化的压缩目标。我跑测试用的模型是两个代称模型:模型A是开源社区里常见的中等规模对话模型,模型B是某个商用接口背后的大模型。这样做的目的是观察压缩行为是否在不同能力模型之间出现共性。

三类压缩策略,我用表格整理如下。

策略操作方式保留的信息主要风险
删除压缩按频次和重要度删除低价值词句,控制保留比例关键词、命名实体逻辑关系断裂、代词指代不清
语义压缩用小模型生成原文摘要,设置不同摘要长度整体语义主线细节丢失、摘要模型自身幻觉
混合压缩抽取关键句并做指代还原、连接词补全实体关系、因果链条规则复杂度高,难以覆盖所有句式

实验时我把每个任务的输入按比例压缩到原始长度的90%、70%、50%、30%和10%,也就是压缩率从10%到90%递增。输出侧则分别采用硬性长度限制和结构化摘要指令。每组实验跑五遍取多数结果,用来消除采样随机性。

2.3 评价指标:只看准确率会严重误判

只用一个正确率衡量压缩影响,会被表面数字骗过去。举一个我真实遇到的例子:一个事实问答任务在输入压缩50%后正确率依然有78%,看起来不错。但细看错误案例发现,模型给出的错误答案里,有将近一半是“看起来像答案但实际上偷换了主语”的表述。准确率数字没有区分“这个答案是完整正确的”和“这个答案里恰好包含了正确答案片段”。

所以我在CAVEWOMAN里跑了四个维度。第一是任务完成率,也就是标准答案匹配情况。第二是信息保留度,用一个未压缩的强模型当裁判,比对压缩输入下的输出和原始输入下的输出是否包含等价的关键信息。第三是语言质量,包括输出是否通顺、有没有重复片段、有没有不完整句。第四是行为稳定性,统计幻觉率、拒绝率和答非所问的比例。这四个维度合在一起,才能回答“压缩后模型到底是在努力保持正确,还是在硬着头皮编一个高概率答案”。

3. 测试结果:压缩率超过某个阈值后,画风突变

3.1 输入压缩梯度测试:60%是一条危险水位线

我把输入压缩测试跑完以后,拿到了一个非常清晰的趋势。模型A在未经压缩的原始输入上,五类任务的平均完成率是82%。删除压缩到30%的时候,完成率还维持在78%附近,损失很小。压缩到50%,完成率降到70%,开始出现零星的逻辑断裂。继续压到70%,完成率直接掉到41%,很多答案开始只抓住高频词做猜测,甚至出现自相矛盾的表述。最后压到90%,完成率只有13%,接近随机乱猜。

模型B的表现整体高一些,但断裂曲线的形状几乎一样。它在压缩到70%时仍有58%的完成率,可到了90%同样崩到19%左右。这说明“存在阈值”不是某个模型的缺陷,而是LLM在信息压缩条件下的普遍行为:一定范围内能靠上下文惯性弥补缺失,超过范围后,模型会从推理模式切换成“概率猜词模式”。

为什么会出现这条断崖?我的推测是,压缩不仅减少了文字数量,更破坏了句法枝干。词与词之间、实体与实体之间的共现频率降低,模型能够用来锁定语义空间的上下文信号不足。于是它转而依赖训练数据里的高频关联,而不是当前语境里的真实关系。这就是为什么压缩率越高,模型越容易给出“常见但错误”的答案——它不是在回答你的问题,它是在填一个最流行的空。

3.2 输出压缩实验:硬截断比语义压缩更容易翻车

输出压缩我做了两轮实验。第一轮直接限制生成token数量,把目标答案长度分别压到原始回复的50%和25%。结果在摘要任务上,限制到50%时,模型经常写到一半就截断,三条重点只写两条,而且缺失的重点恰好是最依赖上下文细节的那条。限制到25%时,模型开始出现复读现象,把第一句重复一遍充字数,或者直接复制输入中的片段当作输出。

第二轮改成语义压缩,告诉模型“用十个词以内回答”“只保留最关键的数字和结论”。这种约束下,模型的输出完整性明显变好,因为它是带着“目标”在组织语言,而不是在一个硬边界里勉强收尾。但它又暴露了另一类问题:太擅长省略限定条件。我有一条逻辑判断题,原答案应当是“如果不下雨,计划就执行”,模型压缩后写成了“计划执行”,把关键条件丢了。这说明输出压缩本质上是逼模型做“信息取舍”,而模型做取舍时默认丢弃那些“低频但关键”的限定词。

两类方式的关键信息命中率对比如下。

输出约束方式50%压缩时关键信息命中率25%压缩时关键信息命中率幻觉率
硬性长度限制72%41%18%
语义压缩指令81%63%12%

硬截断节省了token,但损失的是句间连贯性和结论完整性。语义压缩稍好,却又会把“模型认为不重要”的信息偷偷丢掉。两份结果放在一起,结论非常直白:如果你必须压缩输出,优先用指令约束,而不是直接用长度参数硬切。

3.3 分层追踪:模型内部对压缩最敏感的环节在哪

除了看输入输出结果,我还试着做了浅浅的模型内部观测。方法很简单:把完整输入下模型中间层的向量表示取出来,再拿同一句经过压缩后的向量表示,对比每一层的偏移量。偏移越大,说明那一层受到压缩的扰动越严重。

结果让我有点意外。浅层词嵌入层的偏移很大,这很好理解,因为压缩后词形和词序都变了;深层的任务决策层偏移也很大,说明最终语义判断受到了影响;反而是中间层相对稳定,似乎还在努力维持一个“主题表征”。换句话说,压缩造成的语义损失,在模型内部像一个前后都被挤压、中间勉强撑住的气球。

这个观测带给我一个工程启发:如果希望缓解压缩带来的性能衰退,单纯优化prompt可能不够,更好的方向是在决策层附近给模型补偿关键信息,或者让中间层的主题表征更直接地参与到最终生成。当然这部分涉及更底层的干预,不是只调接口就能实现的,但至少指明了一条路:压缩补偿不应该只停留在文本层面。

4. 实操落地:如何用CAVEWOMAN方法给应用做压缩体检

4.1 5分钟搭一个粗粒度的压缩压力测试

不指望每个人都复刻全套实验,但这套思路完全可以下沉成一个小脚本,用来给业务跑一轮“压缩体检”。我自己的做法是准备50到100条任务样本,每一条都有输入、预期输出和可判断对错的规则,然后写三个函数:压缩输入、调模型、评估输出。

下面是一个浓缩版伪代码,我刻意隐去了具体的模型调用方式,你可以直接用自己熟悉的库替换。

def compress_text(text, ratio): # 这里做的是“按长度截断+关键词保留”的简化压缩 # ratio: 0.3表示压缩掉30%,保留70%的文本 words = tokenize(text) k = max(1, int(len(words) * (1 - ratio))) # 真实场景中应根据词重要度排序,保留前k个词 important_words = rank_by_importance(words)[:k] # 为了保持可读性,按原句顺序重新排列 return restore_order(important_words) def evaluate_compression(model, tasks, ratios): report = {} for ratio in ratios: acc, weird = 0, 0 for task in tasks: compressed_input = compress_text(task["input"], ratio) output = model.generate( compressed_input, # 输出侧可以采用固定长度或指令模板 instruction="briefly answer without extra explanation", ) acc += judge_match(output, task["expected"]) weird += judge_weird(output) # 检测幻觉、重复、残缺句 report[ratio] = { "acc": acc / len(tasks), "weird_rate": weird / len(tasks), } return report

这套脚本的逻辑核心是量化。不管你的压缩是关键词删除、摘要生成还是规则改写,只要能把“压缩到什么程度”变成一个比例,就能放到同一个坐标系里对比。跑完一轮,你至少会得到一张类似“压缩率与正确率”的曲线,以及一组异常样本。

4.2 看懂报告,给不同模块定一条安全压缩率

拿到曲线后,第一步不是急着调优,而是找断裂点。假设某个模块在压缩率40%时正确率还高,压缩率50%时突然掉下去,那这条业务的安全阈值就应该定在30%左右,留出10个百分点余量。很多团队喜欢顶格压,认为“模型没崩就是没问题”,但线上输入是动态变化的,今天这句话能被猜出来,明天换一种说法就未必,所以必须留足裕量。

第二步是按模块区别对待。我的经验是,RAG检索上下文可以用删除压缩加上关键词标记,把最关键的实体和数字放在输入头部;多轮对话历史更适合语义压缩,因为用户聊天的重复信息太多,但核心意图和约束条件不能丢;而工具调用、功能指令这类输入,尽量完全不做压缩。原因很简单:指令是模型的“约束层”,一旦被压掉“不要”“只”“必须”之类的高位词,模型行为就会失序。

第三步是输出侧的选择。能用结构化输出,就优先用结构化输出。比如让模型先给出一个只有关键字段的JSON,然后再把JSON渲染成给用户看的自然语言。这等于把“输出压缩”从一个危险的截断操作,变成了一个可控的模板填充操作。相比之下,直接限制max_tokens是最不推荐的做法,它会破坏模型的收尾能力。

4.3 给输入压缩留一条“逃生通道”

实验中我发现一个很有意思的现象:当压缩输入不足以支撑答案时,模型不会主动承认自己信息不足,反而会生成一个“看起来合理”的回答。这是因为训练目标鼓励它输出完整答案,承认未知往往被视为一种失败。为了对抗这种倾向,我后来在每条压缩输入后面都额外加了一句提示:“如果上述信息不足以回答,请明确说不确定。”

别小看这一句,它直接把幻觉率降了一个档位。原理其实不复杂:给模型一个合法的“不做答”出口,它就不用硬着头皮编造答案了。这个方法成本最低,但很多团队会忽略,因为他们默认模型知道什么情况下该拒绝。压缩场景恰恰是模型最容易“过度自信”的场景,因为它收到的信号太稀疏,反而更容易生成光滑连贯的错误。

5. 典型问题与排查技巧实录

5.1 压缩后模型开始一本正经地胡说八道

一个很常见的现场是:输入压缩到50%以后,模型在一个摘要任务里凭空补出了原文里根本没有的结论。排查这种问题,我习惯按三个步骤走。第一步,把压缩后的输入单独打印出来,人工读一遍,看是否存在语义歧义或实体被删的情况。第二步,换一个未压缩的强模型当裁判,让它比对压缩输出和原始输入之间是否包含等价信息。第三步,检查是不是压缩组件自己产出了幻觉词汇——如果第二步发现问题,但压缩后的文本在人类看来还算通顺,那复现模型自己的补全能力就太强了。

根治这个问题,我最有效的办法是在输出指令里加一个“引用约束”:让模型在给出结论时,必须从压缩输入里复制至少一个关键短语作为依据。有了这个约束,模型就不容易完全脱离输入自由发挥,即使它想编,也得先找一个可复制的锚点,这就变相限制了幻觉的空间。

5.2 压缩率一高,指令遵循却像喝了假酒

明明让模型“只输出结论”,压缩后它反而开始解释、道歉、反问,这是我在指令执行任务里经常遇到的。原因通常不是模型变笨了,而是压缩把指令中的约束词删掉了。比如“严格按两步回答”被压成“两步回答”,“不要解释”里的“不要”直接被删除。一旦约束词消失,指令的效力就大打折扣,模型回到默认的“礼貌且多话”模式。

所以我在自己的项目里定了一条死规矩:指令区永远不参与压缩。把prompt拆分成指令区和数据区,指令区原样保留,数据区再做删除或摘要。如果总体积还必须压,那就优先压缩数据区,牺牲一些上下文细节,也不要动约束条件。这一点在混合压缩策略里尤其重要,因为改写规则本身就可能稀释指令强度。

5.3 不同模型对压缩的耐受度差异非常大

跑完模型A和模型B的横向对比,我发现了一个很实用的经验:经过大量指令微调、行为约束更明确的模型,对输入压缩的耐受度明显更高;而那些偏自由对话、热爱发散表达的模型,一压缩就开始跑偏。同样的压缩率70%的输入,模型A完成任务只剩不到一半,模型B却能凭更强的语义重建能力稳住大半。

这个差异提醒我,选型的时候不仅要看模型的原始能力,还要看它的“压缩鲁棒性”。如果你的业务注定要在低token预算下运行,那么在模型评估阶段就应该把“压缩容忍度”列为一个关键指标。具体做法很简单:把50条核心业务样本压到70%再跑一遍,哪个模型的完成率衰减最小,就优先选哪个。这个测试花不了多少时间,却能帮你避开上线后才发现“模型越压越傻”的大坑。

5.4 输出压缩引发的截断与重复

用硬性长度限制时,我见过最多的问题是模型输出第一句重复好几遍,以及话说一半被截断。第二个问题很好理解,预算不够;第一个问题则是模型在预判到“马上要收尾”之后,不断重复当前句以保证生成概率持续稳定。它就像一个人被要求在三十秒内讲完复杂事件,结果因为时间紧张,嘴里反复念叨同一个词,而不是把信息高效压缩。

我试过最管用的缓解方式是“内容规划代替长度限制”。让模型在输出前先自己列一个最小要点清单,再按要点逐条铺开。这样即使每条要点被压得很短,至少结构是完整的,不会出现漏掉一个分支的情况。另一个技巧是要求“输出必须以句号结尾”,这个看似无关的约束能让模型在生成过程中主动收束句法,减少半截句。实测下来,这两个小改动比单纯调大max_tokens效果好得多。

5.5 快速排查速查表

把上面这些整理成一张速查表,线上出了压缩相关的问题可以直接对号入座。

现象可能原因快速解法
答案通顺但事实错误输入关键实体被删、输出引导不足加“引用原文”约束,保留实体名词
指令执行变形指令词被压缩掉指令区独立于数据区,不做压缩
模型拒绝回答压缩后信息过少,模型过度防御增加“基于已有信息推测”的提示
输出截断max_tokens与实际长度不匹配用要点式输出替代硬截断
输出重复模型进入收尾态后概率漂移强制完整句结构+内容规划
压缩后准确率骤降超过该模型的压缩断裂阈值降低压缩率或换压缩鲁棒性更强的模型

写在最后的一点体会

踩过几次坑之后,我现在做LLM应用,凡是涉及动态长度控制的,第一件事不是调prompt,而是先跑一轮CAVEWOMAN式的压缩测试。你会发现很多看似玄学的问题,本质就是压缩与模型能力边界不匹配。另一个让我印象深刻的细节是:把每次测试的压缩文本和输出都存下来,回看时经常能发现模型在某类词被删后会出现高度一致的错误模式。这个规律比任何量化指标都更能指导后续优化方向。如果你也遇到“模型一压缩就变蠢”的问题,不妨用这套方法自己跑一遍,大概率能在报表里挖出你从未注意到的行为线索。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进