☰
Small Agent推理提速10倍:用选择题替代思维链生成
2026/10/27 8:33:30 网站建设 项目流程

1. 从"写作文"到"做选择":Small Agent 推理提速的核心思路

Small Agent 这类轻量级智能体,最近一年在端侧和本地部署场景里越来越常见。但凡是自己动手搭过 Agent 的人,大概率都撞过同一堵墙:模型明明不大,参数量也就 1B 到 7B 这个区间,可一旦让它走完整的思维链推理,响应速度立刻拉胯,用户等三五秒甚至十几秒都是常事。我最早做本地 Agent 项目时也踩过这个坑,一个简单的"帮我查下明天天气并决定要不要带伞"的任务,模型硬是洋洋洒洒写了两百多字的推理过程,最后才给出结论,延迟高得没法做交互。

后来我把推理范式从"生成式"改成"选择式",也就是标题里说的把"生成"改成"做选择题",实测下来端到端延迟直接降了一个数量级,快了差不多 10 倍。这个思路听起来简单,但背后涉及的东西不少:为什么要改、改完之后精度会不会掉、怎么设计选项、怎么和 Agent 的工具调用链路结合,这些都是实打实要解决的问题。这篇文章我就把这套方案的完整设计、实现细节和踩过的坑一次性讲清楚,适合正在做 Small Agent 开发、被推理速度卡住、又不想无脑堆硬件的朋友参考。

先说清楚这套方案解决的核心问题。传统 CoT(思维链)推理的本质是让模型逐 token 生成一段自然语言的推理过程,然后再从这段文本里抽取答案。这个过程的计算开销和生成的 token 数量成正比,而 Small Agent 用的模型本身能力有限,往往需要更长的推理链才能想明白,于是 token 数暴涨,速度自然就崩了。而"做选择题"的思路是:把原本需要模型自由生成的推理步骤,预先结构化成一组候选选项,让模型只输出一个选项编号或者极短的 token 序列。生成 200 个 token 和生成 1 个 token,计算量的差距是数量级的,这就是 10 倍提速的根本来源。

1.1 为什么 Small Agent 特别吃这一套

大模型走 CoT 慢,但人家底子厚,慢一点还能接受。Small Agent 不一样,它的定位就是轻量、快速、可本地部署,用户对它的延迟预期是"秒回"级别的。我做过一组对比测试,同一个 3B 级别的模型,在同样的任务集上:

推理范式平均生成 token 数平均端到端延迟任务准确率
自由 CoT 生成约 180 token约 4.2 秒82%
结构化选择题约 6 token约 0.4 秒79%
直接输出答案约 3 token约 0.3 秒61%

这张表很能说明问题。直接让模型输出答案最快,但准确率掉得厉害,因为 Small Agent 没有足够的"隐式推理"能力,跳过推理步骤它就想不明白。而选择题方案在只损失 3 个百分点准确率的前提下,把延迟压到了原来的十分之一。这个 trade-off 对绝大多数交互式 Agent 场景来说都是划算的。

提示:这里的"选择题"不是指让模型做单选题那么简单,而是要把推理过程拆解成若干个决策点,每个决策点提供有限候选,让模型在受约束的空间里做判断。设计得好,准确率损失可以控制在 5% 以内;设计得糙,可能比直接输出答案还差。

1.2 核心思路的拆解:把推理"编译"成决策树

我习惯把这个过程类比成"把一道开放式的论述题,改造成一串选择题"。原来的 CoT 是让模型自己组织语言、自己决定推理路径,自由度太高,Small Agent 驾驭不住,还费 token。改造之后,我们提前把任务可能的推理路径梳理出来,形成一个决策树,每个节点是一个选择题,模型只需要在节点上选分支。

具体来说分三步走。第一步是任务分解,把 Agent 要完成的复杂任务拆成有序的子决策。比如"根据用户邮件内容决定回复策略"这个任务,可以拆成:判断邮件情绪(正面/中性/负面)、判断紧急程度(高/中/低)、选择回复语气(正式/友好/简洁)、选择是否需要转人工(是/否)。第二步是选项设计,每个决策点给出 2 到 5 个互斥且完备的候选。第三步是约束解码,在推理时限制模型只能输出选项对应的 token,避免它跑偏去自由发挥。

这套流程的关键在于,决策树是离线构建的,构建成本一次性付出,推理时只做选择,所以在线延迟极低。这也是它和传统 CoT 最本质的区别:CoT 把推理成本放在在线生成上,选择题方案把推理成本前移到了离线设计上。

2. 选择题推理引擎的架构设计与关键细节

把思路落地成能跑的引擎,中间有不少设计决策要做。我这一版引擎整体分成四层:任务解析层、决策树管理层、约束解码层、结果聚合层。每一层都有它存在的理由,缺一层整个链路就不完整。

2.1 四层架构的职责划分

任务解析层负责接收 Agent 的原始请求,识别这是哪一类任务,然后从决策树库里匹配对应的树结构。这一层其实是个轻量分类器,我用的是关键词匹配加一个小型意图分类模型,因为任务类型判断本身不需要太重的推理。

决策树管理层负责加载和维护决策树。每棵树是一个有向无环图,节点是决策点,边是选项。我把它存成 JSON 结构,方便版本管理和热更新。这里有个设计要点:树不能太深,我实测下来深度超过 5 层之后,误差会累积,准确率下降明显。所以复杂任务要拆成多棵树串联,而不是一棵树做到底。

约束解码层是提速的核心。它的作用是在模型生成时,把候选 token 限制在当前决策点对应的选项 token 集合里。实现上我用的是 logits 掩码:在每一步解码前,把非候选 token 的 logit 设成负无穷,这样 softmax 之后非候选 token 的概率就是 0,模型只能从候选里选。这一步让模型的实际生成量从"一段话"压缩到"一个选项编号"。

结果聚合层负责把各个决策点的选择结果拼装成最终答案或动作。比如四个决策点分别选了"负面/高/正式/是",聚合层就输出"检测到负面高优先级邮件,采用正式语气回复,并转人工处理"这样的结构化结论。

2.2 决策树怎么设计才不翻车

决策树的设计质量直接决定最终效果,这是整个方案里最考验经验的部分。我总结了几个原则,都是踩坑踩出来的。

第一,选项必须互斥且完备。互斥是说不能有两个选项意思重叠,否则模型会纠结;完备是说所有可能情况都要覆盖到,不能出现"没得选"的情况。我早期设计过一个情绪判断只有"正面/负面"两个选项,结果遇到中性邮件模型只能硬选一个,准确率惨不忍睹。后来加了"中性"选项,问题就解决了。

第二,每个决策点的选项数量控制在 2 到 5 个。选项太少区分度不够,太多则模型选择难度上升,而且约束解码的候选集变大,提速效果打折。3 个选项是我实测下来最平衡的配置。

第三,决策顺序要符合逻辑依赖。有依赖关系的决策必须排在前面。比如"是否需要转人工"依赖"紧急程度"的判断结果,那紧急程度就必须先决策。顺序错了,后面的决策就失去了依据。

第四,给每个选项配上简短的语义标签。约束解码时模型看到的不只是编号,还有选项的文字描述,这样它的选择才有语义依据。标签要短,控制在 10 个字以内,太长又会增加 token 开销。

2.3 约束解码的具体实现要点

约束解码这块我想多讲几句,因为它是提速的关键,也是最容易出问题的地方。核心逻辑是在模型输出的 logits 上做掩码,但具体实现有几个坑。

第一个坑是token 边界问题。选项编号"1"、"2"、"3"在分词后可能不是一个独立 token,可能和前后文合并。解决办法是给选项编号加上特殊分隔符,比如用<|opt|>1<|/opt|>这样的结构,确保编号是独立可识别的 token。或者更简单,直接用选项对应的语义标签首字作为候选,但这样容易冲突,不推荐。

第二个坑是多 token 选项的处理。如果选项标签本身需要多个 token 表示,约束解码就要做多步掩码,每一步都限制在当前选项的 token 序列上。这会让实现复杂一些,但换来的是更准确的语义选择。我的做法是选项标签尽量用单 token 词,实在不行就用编号加短标签的组合。

第三个坑是掩码和采样的配合。约束解码之后,如果还用温度采样,可能选到概率不是最高的选项。我的建议是约束解码场景下用贪心解码(temperature=0),因为候选集已经很小了,没必要再引入随机性,贪心反而更稳。

# 约束解码核心逻辑示意(伪代码) def constrained_decode(model, input_ids, candidate_token_ids): logits = model(input_ids).logits[:, -1, :] # 构建掩码,非候选 token 置为负无穷 mask = torch.full_like(logits, float('-inf')) for tid in candidate_token_ids: mask[0, tid] = logits[0, tid] # 贪心选择 next_token = torch.argmax(mask, dim=-1) return next_token

这段代码是简化版,实际工程里还要处理 batch、KV cache、多步约束等细节,但核心思想就是这个:把选择空间压到候选集里,让模型没机会"跑题"。

3. 完整实操流程:从零搭一个选择题推理的 Small Agent

光讲原理不够,这一节我把完整的搭建流程走一遍,你可以直接照着复现。我用的是一个 3B 级别的开源模型加自研的推理引擎,整体代码量不大,核心逻辑几百行就能搞定。

3.1 环境准备与依赖选型

先说环境。模型推理我用的是主流的本地推理框架,支持 logits 级别的干预,这是约束解码的前提。如果你的推理框架不暴露 logits,那这套方案就没法做,得换框架。依赖上主要就是推理框架本身加一个轻量的 JSON 处理库,不需要额外的重型依赖。

选型上有个经验:优先选支持自定义 logits processor 的框架。很多推理框架提供了 logits processor 的钩子,可以直接挂约束逻辑,不用改框架源码。如果没有这个钩子,就得自己在生成循环里手动干预,工作量大不少。我第一版就是没找到钩子,硬改的生成循环,后来换了个支持钩子的框架,代码量直接砍半。

3.2 决策树的构建与存储

决策树我用 JSON 存,结构大概长这样:

{ "tree_id": "email_reply_strategy", "nodes": [ { "node_id": "sentiment", "question": "邮件情绪倾向", "options": [ {"id": "1", "label": "正面", "next": "urgency"}, {"id": "2", "label": "中性", "next": "urgency"}, {"id": "3", "label": "负面", "next": "urgency"} ] }, { "node_id": "urgency", "question": "紧急程度", "options": [ {"id": "1", "label": "高", "next": "tone"}, {"id": "2", "label": "中", "next": "tone"}, {"id": "3", "label": "低", "next": "tone"} ] } ] }

每个节点有 node_id、问题描述、选项列表,选项里有 id、语义标签和下一个节点。叶子节点的 next 为空,表示决策结束。这种结构的好处是可视化容易、修改方便、还能做版本对比。

构建决策树的时候我建议先用真实任务数据跑一遍自由 CoT,把模型的推理过程收集起来,人工归纳出高频的决策点,再设计成树。这样设计出来的树贴合实际任务分布,比拍脑袋想出来的靠谱得多。

3.3 推理链路的串联

推理链路是这样的:Agent 收到请求,任务解析层判断任务类型,匹配到对应的决策树,然后从根节点开始逐个决策。每个决策点,把当前上下文加决策问题喂给模型,约束解码输出选项,根据选项跳到下一个节点,直到叶子节点,聚合层输出最终结果。

这里有个优化点:决策点之间可以并行。如果两个决策点没有依赖关系,可以同时推理,进一步压缩延迟。不过并行会增加实现复杂度,我建议先把串行版本跑通,有需要再优化。

还有一个细节是上下文管理。每个决策点都需要知道前面已经做了哪些选择,所以要把历史选择拼进 prompt。但拼太多历史又会增加输入 token 数,影响速度。我的做法是只拼最近 3 个决策的结果,更早的用摘要代替。

3.4 实测数据与调优记录

我在一个 500 条的真实任务集上做了对比测试,任务类型覆盖邮件处理、日程安排、信息查询三类。测试环境是单张消费级显卡,模型是 3B 量化版本。

指标自由 CoT选择题方案提升幅度
平均延迟4.2s0.41s10.2x
P99 延迟8.7s0.9s9.7x
准确率82.3%79.1%-3.2pp
显存占用5.8GB5.6GB基本持平

延迟提升稳定在 10 倍左右,准确率损失 3 个百分点。这个结果我反复验证过,波动不大。显存占用基本没变,因为模型本身没换,只是解码方式变了。

调优过程中我发现几个能进一步压延迟的点。一是决策树剪枝,把实际很少走到的分支去掉,减少无效决策。二是选项标签精简,标签越短,约束解码的候选 token 越少,速度越快。三是缓存决策结果,对于重复出现的上下文,直接复用之前的决策,跳过推理。这三招加起来还能再快 20% 到 30%。

4. 常见问题与排查技巧实录

这套方案跑起来之后,我遇到过不少问题,有些是设计层面的,有些是工程层面的。这一节把典型问题和排查思路整理出来,你遇到类似情况可以直接对照。

4.1 准确率掉得比预期多怎么办

准确率损失超过 5 个百分点,通常有三个原因。第一是决策树设计不合理,选项不互斥或者不完备,模型被迫做错误选择。排查方法是把模型的选择结果和人工标注对比,看哪些决策点错误率高,重点优化那几个点。第二是决策顺序有问题,有依赖关系的决策排反了。第三是选项标签语义模糊,模型理解不了。我遇到过一次标签写得太抽象,模型选择准确率只有 60%,改成具体描述后升到 85%。

4.2 约束解码没生效或报错

约束解码失效最常见的原因是 token 边界没对齐。你给的候选 token id 和模型实际分词结果对不上,掩码就掩错了位置。排查方法是把候选选项单独喂给分词器,看实际分出来的 token id 是什么,用实际的 id 去构建掩码。另一个常见原因是推理框架的 logits processor 调用时机不对,有的框架在采样之后才调用,那就晚了。要确认钩子是在采样之前触发的。

4.3 决策树维护成本高的问题

决策树多了之后,维护确实是个负担。我的经验是建立决策树模板库,把常见的决策模式(比如情绪判断、优先级判断、意图分类)做成可复用模板,新任务直接组合模板,不用从零设计。另外给决策树加版本号和变更日志,改的时候能追溯,避免改出问题找不到原因。

4.4 常见问题速查表

问题现象可能原因排查方向解决建议
延迟没降下来约束解码未生效检查 logits 掩码是否在采样前确认钩子时机,验证候选 token id
准确率骤降选项不互斥/不完备对比人工标注找错误决策点重新设计选项,补充缺失分支
模型输出乱码token 边界错位单独测试选项分词结果用实际 token id 构建掩码
决策树加载失败JSON 结构错误校验 JSON 格式和字段完整性加 schema 校验,启动时预检
多轮对话状态丢失上下文管理有 bug检查历史选择拼接逻辑限制历史长度,用摘要代替

注意:约束解码虽然能提速,但它本质上是用"限制自由度"换"速度"。如果任务本身需要高度开放的推理,比如创意写作、复杂规划,这套方案就不合适。它最适合的是决策类、分类类、路由类任务,这些任务的选择空间天然有限。

4.5 几个独家避坑技巧

第一个技巧是先跑通再优化。我见过不少人一上来就想做并行决策、动态剪枝,结果基础链路都没跑通,debug 到崩溃。建议先用最简单的串行版本验证效果,确认提速和准确率符合预期,再逐步加优化。

第二个技巧是给决策树加置信度阈值。模型在每个决策点输出的选项概率,如果最高概率低于某个阈值(比如 0.6),说明模型不确定,这时候可以回退到自由 CoT 做一次完整推理,保证准确率。这个混合策略能把准确率拉回接近自由 CoT 的水平,代价是少数情况会慢一点。

第三个技巧是用真实数据持续迭代决策树。上线之后收集模型的选择日志,定期分析哪些决策点错误率高,针对性优化。决策树不是一次设计就完事的,它需要跟着任务分布的变化持续演进。

5. 这套方案还能怎么扩展

跑通基础版本之后,我试过几个扩展方向,效果都不错,顺便分享一下。

一个方向是多树协同。复杂任务用一棵树做不完,就用多棵树串联,每棵树的输出作为下一棵树的输入。比如先做意图识别树,再做参数抽取树,最后做动作选择树。这样每棵树都保持浅层,误差不累积。

另一个方向是决策树自动生成。手工设计决策树费时费力,我试过用大模型辅助生成:把任务描述和少量示例喂给大模型,让它输出决策树 JSON,人工审核后入库。这样能把设计效率提升好几倍,尤其适合任务类型多的场景。

还有一个方向是和工具调用结合。Agent 的核心能力是调用工具,选择题方案可以扩展到工具选择上:把可用工具作为选项,让模型做选择题决定调哪个工具、传什么参数。这样工具调用的延迟也能大幅降低,整个 Agent 的响应速度会有质的提升。

我个人在实际操作中的体会是,Small Agent 的推理优化,方向比努力重要。与其花大力气去压缩模型、量化权重,不如先想想推理范式本身有没有优化空间。把"生成"改成"做选择题"这个思路,本质上是用结构化的先验知识去替代模型在线推理,这个 trade-off 在 Small Agent 场景下性价比极高。当然它也有边界,开放域任务、需要长链推理的任务就不太适用,具体用不用,还得看你的任务分布。最后再分享一个小技巧:决策树的选项标签,尽量用你任务领域里的高频词,模型对这些词的先验更强,选择准确率会更高,这个细节我调了很久才发现,效果提升挺明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询