你有没有想过,一个会写代码的AI在反复修改一份代码的时候,其实干的事情特别重复。
它读一遍文件,提一个方案,运行测试发现报错,再读一遍几乎一样的文件,再改一遍几乎一样的代码。这种重复性在人类工程师身上很常见,我们管它叫"调试"。但对于负责执行这个任务的AI系统来说,这种重复性其实是一个巨大的加速机会,前提是你得有办法抓住它。
这篇论文要讲的就是怎么抓住这个机会。
**先说清楚AI编程助手到底在干什么**
现在流行的编程智能体(coding agent,一种能自主完成软件工程任务的AI系统,不是简单问答,而是会反复调用模型、执行代码、看结果再决策的循环)早就不是那种"写个排序函数"的简单场景了。真实的任务是修复一个GitHub仓库里的bug,这意味着AI要先定位是哪个文件出了问题,再去改代码,再跑测试验证,如果测试不过还要继续改,可能要来回折腾五六轮。
这个过程里有专门分工的角色:有的负责在代码库里找到问题所在的文件(localizer),有的负责真正动手改代码(coder),有的负责跑测试看结果(executor),还有负责复审的(reviewer)。这几个角色可能是同一个大模型在不同阶段切换人设,也可能是真的部署了几个不同的模型分别负责。
这套流程跑起来有多慢,直接决定了用户要等多久才能拿到结果。而让它跑得快一点,业界早就有一套现成的办法,叫推测解码。
推测译码*:一种加速大模型生成文字的技术。基本思路是先用一个便宜的"草稿模型"快速猜出接下来可能出现的几个词,再让真正的大模型一次性验证这些猜测对不对,猜对的部分就直接采纳,省掉了大模型逐字生成的时间。
推测解码的经济账算得很简单:如果草稿猜中了,等于省了一步大模型的计算;如果猜错了,等于白跑了一趟验证,还浪费了算力。所以草稿猜得越准,这笔买卖越划算。关键是,大模型生成一个词本来就很贵,如果能让便宜的草稿机制去猜,猜中了净赚,猜错了也没亏太多,这就是为什么大家都在研究怎么让草稿猜得更准。
传统做法是训练一个小模型专门做猜测,或者给大模型加一个"草稿头"。但这篇论文关注的是另一条路,叫检索式推测解码。
检索式推测译码*:不训练任何新模型,草稿直接从已有文本里"抄"出来。比如当前的提示词、之前的回答、或者一个提前准备好的文本库,只要能找到一段和当前上下文相似的历史文本,就把它后面跟着的内容直接拿来当草稿。
这个思路对代码场景特别合适。代码里充满了重复的变量名、函数签名、项目约定,而编程智能体又天然地会反复生成相似的内容,改了又改,测了又测。听起来这应该是检索式推测解码的理想战场,对不对?
但研究者们发现,现成的检索式方法在编程智能体这个场景下,其实远远没有发挥出应有的威力。
**问题出在哪:能抄的东西,根本没抄到**
论文作者做了一个很直观的实验。他们测了几个模型(Devstral、Gemma3、Qwen3.6)在不同检索范围下的"接受长度",也就是草稿里有多少词真的被大模型采纳了。
结果是,只用当前这一次调用的提示词去检索,接受长度大概是2到3个词。但如果把检索范围扩大到整个会话历史,接受长度能冲到5个词以上,几乎翻了一倍。如果再把仓库里打开过的文件也纳入检索范围,还能再往上提一截。
这说明了什么?
**现有的检索方法根本没把该抄的东西收进自己的库里。**
想象你是一个总是忘记带笔记本的学生,每次考试都只能靠当场想。而你的同桌把这学期所有课堂笔记、作业本、甚至上一次模拟考的卷子都带在身边,遇到类似的题目直接翻出来抄。你俩的智力可能差不多,但考试速度和准确率完全不是一个量级。如果不把这些历史资料带全,你永远只能"临场发挥",永远抓不住重复出现的内容。
现有的检索式方法,大多只检索"当前这一次调用"看到的文本,或者一个提前打包好、和当前任务无关的通用语料库。它们没有覆盖两类东西:一是这次会话从头到尾积累下来的历史文本,二是这个代码仓库里实际存在的文件内容。
即便覆盖到了,还有第二个问题:抄的格式对不上。
论文里举了一个特别具体的例子。编程智能体修改代码的时候,常用一种叫"统一diff格式"的方式提交修改。
统一diff格式*:一种表示代码变更的标准格式,规定每一行代码前面要加一个符号,保留的行前面加空格,删除的行前面加减号,新增的行前面加加号。
问题是,原始代码文件里是没有这些空格、减号、加号前缀的。模型读到的文件是干净的代码,但它写出来的补丁是带了这些符号的"变形版"。如果检索库里存的是干净版的代码,而模型实际吐出来的是带符号的版本,两者在字面上根本对不上,检索系统压根搜不到这个本该匹配上的内容。
这就好比你把一份文件原件存进了档案柜,但同事每次用的时候都要先盖一个红色印章再引用。如果你去档案柜翻找原件和他手头盖了章的版本做比对,系统会告诉你"没有匹配",因为它俩字面上真的不一样,尽管内容其实是同一份东西。如果不解决这个格式错位问题,再怎么扩大检索库也没用,因为匹配规则本身就卡住了。
论文把这两个问题概括成两个条件:**能抄的内容必须先进入检索库(可用性),进了库之后还得长得和模型实际要吐出来的格式一样(可匹配性)。** 这两条缺一个,检索式推测解码的威力就打了折扣。
**第二个大坑:草稿长度一刀切,不同角色根本不是一回事**
即便找到了能抄的内容,还有一个问题没解决:到底该抄多长?
草稿抄得太短,省不了多少步骤;抄得太长,万一后面的内容猜错了,等于白白浪费了验证的算力,而且批量越大浪费得越狠,因为大模型一次要同时验证一整批请求,错的部分拖累的是所有人。
现有方法大多是"一刀切":定一个固定长度,比如每次最多抄8个词或32个词,或者让草稿长度跟着匹配到的那段文本本身的长度走。但论文里有个图特别打眼,它测了同一个模型(Gemma3)在扮演不同角色时的"可实现接受长度",也就是理论上最多能抄多长而不浪费。
结果是,扮演"定位员"角色时接受长度能冲到30甚至45,而扮演"复审员"时可能只有个位数,而且这个数字还会随着任务进行到第几轮而上下浮动,不是一条平的直线。
这说明每个角色、每个阶段的"可抄长度"都是不一样的,而且是动态变化的。用同一把尺子去量所有人,必然有的地方量少了、浪费了机会,有的地方量多了、白跑了验证。
这就像一家餐厅规定所有顾客的点菜数量上限都是固定的三道菜,不管你是一个人来吃便餐,还是一家六口来聚餐。结果就是独自吃饭的人点三道菜吃不完,浪费;一家人点三道菜根本不够吃,还要重新排队加单。合理的做法应该是看桌子大小、看人数灵活调整,而不是套用一个数字。如果不针对不同角色单独调整草稿长度,系统注定在一部分场景下吃亏,在另一部分场景下又吃得撑。
**论文给出的解法:AgSpec**
搞清楚了问题,论文提出的解决方案叫AgSpec,它不是重新发明一套检索引擎,而是给现有的检索引擎补上两套政策:一套管检索库该装什么,一套管草稿该抄多长。
它的设计思路可以拆成两块。
**第一块:按来源分层建库,并且按模型的"笔迹"存档**
AgSpec把检索的素材分成三个来源独立维护:
会话语料库,保存的是这次对话从头到尾积累的所有文本,包括提示词、工具输出、模型自己生成的内容,整个会话期间所有角色都能共用这个库,会话结束就清空。
工作区语料库,专门存这次任务打开过的仓库文件,会随着文件被打开逐渐变大,会话结束同样清空。
全局参考库,是提前准备好的、不针对具体任务的通用素材库,服务器启动时加载一次,长期固定不变。
这三个库区分对待的理由其实很直白:会话库和工作区库是"活的",跟着这次任务生生灭灭,内容和当前任务高度相关;全局库是"死的",固定不变,内容量大但和当前任务的关联性弱一些。检索的时候三个库都查一遍,但谁的匹配更长就优先信谁,而且全局库要比活库明显长出一截才会被采信,因为全局库内容多,碰巧撞上一段相似文本的概率也更高,不能轻易相信这种"巧合"。
更关键的设计是前面提到的格式错位问题怎么解决。AgSpec在第一次打开某个仓库文件的时候,会把这个文件存成好几个版本:原始版本,以及按diff格式提前加好空格前缀和减号前缀的版本。这样无论模型后面要保留这行代码还是要删除这行代码,它写出来的带符号版本都能在库里找到一个字面上完全匹配的拷贝。
这就好比提前把一份合同打印出三份,一份原件,一份盖了"已审核"章的,一份盖了"已拒绝"章的,不管最后用哪个版本,现场都不用临时手忙脚乱地去盖章再核对,直接拿现成的用。如果不提前准备好这些变体,系统每次都要在原始文件和模型实际输出之间做一次"翻译",而这种翻译在检索匹配的逐字比对机制下是做不到的,等于白白损失了一大块本可以抄到的内容。
实验数据印证了这套设计的价值。把检索库从只有全局库,逐步加上工作区库、会话库,接受长度能提升到2.9到4.8倍。其中加上会话历史这一步贡献最大,把接受长度直接拉到1.6到2.1倍。而针对diff格式单独存档这个动作,在任务刚开始的第一轮能把"改代码"这个角色的接受长度拉高13%到39%,不过从第二轮开始,模型自己写的补丁已经进入了会话库,这个专门适配格式的技巧带来的增量就慢慢降到了3%以内。这也说明一个有意思的现象:**专门的格式适配主要解决的是"冷启动"问题,一旦会话积累起足够的历史,很多格式差异会被会话库自然覆盖掉。**
**第二块:草稿长度跟着角色和实时反馈走**
解决了检索范围的问题,AgSpec接着处理草稿长度的问题,用的是两层机制。
第一层是离线画出每个角色的"能力画像"。具体做法是把之前收集到的历史任务记录重新跑一遍,记录每一步检索命中后,实际能对上多长的续写内容,由此算出"草稿的第k个词被接受的概率"这样一条曲线。概率会随着k增大而下降,这不难理解,猜得越远越不靠谱。只要这个概率还高于某个盈亏平衡点,这一步草稿就还值得发,一旦跌破这个点,就不该再往后猜了。这就给每个角色、每个模型定出了一个专属的草稿长度上限。
第二层是在线调整的缩放系数,每个角色一开始都从1.0起步,意思是草稿长度刚好等于检索匹配到的那段长度。随着验证结果不断反馈回来,系统会动态调整这个系数:如果草稿经常被完全接受,说明还可以抄得更长,系数就往上调;如果经常被部分拒绝,系数就往下压。这个调整过程背后其实是在估计一个统计量,叫分位数回归,用来追踪"这个角色当前这个阶段大概能被接受多长"这样一个会随时间漂移的目标。
把离线画像和在线调整结合起来,相当于给每个角色配了一把"伸缩尺",离线画像给出这把尺子的大致刻度范围,在线反馈再根据当下情况实时拉长或缩短。这就像给不同的运动员定制训练计划,教练手里有这名运动员过去几年比赛的数据,大致知道他适合的训练强度区间,但每一次实际训练时还要看今天的心率、疲劳程度做微调,不会死守一个固定的数字不放。如果没有这套动态调整,草稿长度就只能按最保守或最激进的固定值去赌,赌对了赚一点,赌错了在批量请求的场景下代价会被放大很多倍。
论文里有个案例特别说明问题。固定草稿长度设成32的时候,在批次大小为16的情况下,有一次"改代码"的调用在前160步里几乎没抄对过东西,平均每步浪费掉26个词的验证算力,只换来不到2个被接受的词。加上在线调整机制之后,系统很快把草稿长度收缩到4个词左右,等到模型真的开始大段复制可复用的文本时,又把长度放开到接近上限。整体效果是拒绝的草稿词数减少了81.1%,而总步数只多了11步。
**结果:到底快了多少**
论文在两个仓库级别的编程基准(SWE-bench Verified和TeamBench)上,拿AgSpec去对比五种现有的检索式方法以及一种基于训练的方法EAGLE-3。
结论是,AgSpec在绝大多数测试场景下拿到了最高或第二高的生成吞吐量。单个请求处理时(批次大小为1)能达到普通逐词生成速度的2.27到4.37倍,批量处理16个请求时能到1.08到4.76倍。平均下来,它比之前最快的方法还要再快18%。
而且这个框架不挑引擎。研究者把AgSpec分别套在两种不同的底层检索引擎上做测试,一种叫SAM-Decoding(基于后缀自动机),一种叫SuffixDecoding(基于后缀树)。两种引擎套上AgSpec之后都有提升,SuffixDecoding吞吐量平均涨了16.5%,SAM-Decoding涨得更猛,平均64.6%,几乎翻倍。这个差异也挺有意思,SuffixDecoding原本自己就带了一些跨会话记忆和匹配长度缩放的机制,AgSpec补的是增量;而SAM-Decoding原本什么都没有,AgSpec补的就是从零到一。
值得一提的是,这套框架在没有代码仓库、或者只有单一角色的场景下依然管用。在不依赖仓库上下文的LiveCodeBench上,吞吐量冲到了AR解码的4.87到7.94倍;在只用一个智能体处理所有角色的Terminal-Bench上,吞吐量也比此前最快的方法再提升了9.3%到27.2%。这说明这套检索范围和草稿长度的设计思路,并不是只适配特定流水线结构,而是适配了"编程智能体会反复抄自己历史"这个更根本的行为规律。
Q&A
Q1:AgSpec是什么?
A:AgSpec是一个针对编程智能体场景设计的检索式推测解码框架,它不替换现有检索引擎,而是补上两套关键策略:按会话、工作区、全局三类来源建立检索库并按模型实际输出格式存档,以及根据每个角色离线画像加在线反馈动态调整草稿长度,从而大幅提升代码生成速度。
Q2:AgSpec能带来多大的速度提升?
A:在SWE-bench Verified和TeamBench两个仓库级编程基准上,AgSpec相比普通逐词生成在单请求场景下能快2.27到4.37倍,批量处理16个请求时能快到4.76倍,平均比此前最快的检索式方法还要再快18%左右。
Q3:AgSpec为什么要给代码文件存多个格式版本?
A:因为模型读取文件和实际写出修改补丁时用的格式不一样,比如统一diff格式会给每行代码加空格或减号前缀,如果检索库里只存原始文件,系统就匹配不到模型实际吐出来的带符号文本,所以AgSpec会提前把文件按模型输出格式预先存好几个版本,确保能对上。







