多轮 RAG 何时停止检索?基于结构化判定的检索削减方案
- 关联论文:2608.13237
- 作者:flyP
- 更新:2026-08-15
一句话结论
把"何时停止检索"重写为序列选择问题,在冻结的 Search-R1 流水线上外挂一个 Qwen3.5-2B 结构化判定器,在 900 题互不相交的 HotpotQA 上训练并在确认集上评估:检索调用相比 Native Search-R1 减少 3.70%(77 次),Exact Match 仅下降 0.625 个百分点——一个"少搜一点、几乎不掉点"的诚实增量。
解决什么真问题
多轮 RAG(典型代表 Search-R1、IRCoT、ReAct 等 Agent 式 RAG)需要在每一轮决定"继续搜"还是"停"。现有做法有两条路线:
- 二分类器路线:训练一个"当前状态是否充分"的分类器。但论文明确指出这是独立的状态分类(i.i.d. state classification)——忽略了"第一个 STOP 决定整条轨迹部署策略"这一序列结构,会让训练目标与部署目标错位。
- 规则路线:token 预算 / 固定轮数 / 答案重复等启发式。粗糙且与证据充分度脱钩,常出现"已经够了还在搜"或"还没够就停了"。
本文的关键观察是:部署策略由轨迹上的第一个 STOP 决定,所以这是 sequential selection(类似最优停止 / 阈值策略),不是 i.i.d. 分类。作者因此把 S2G-RAG 的"结构化 sufficiency-and-gap 判定"(结构化判定:同时评估"信息够不够"与"缺口在哪里"两件事)搬过来,作为 Search-R1 的早停门。
为什么这件事真重要:多轮 RAG 的延迟与成本随检索轮数线性甚至超线性增长(每多一轮就多一次 retriever forward + 一次 LLM 重读),而很多查询其实在第 1-2 轮就已经证据充分——一个 5% 的削减就能给生产环境省下可观的钱。但如果停错了,准确率立刻掉,所以"何时停"是个高风险决策点。
核心方法
整体架构
冻结的 Search-R1 推理与检索流水线 + 外挂的 Qwen3.5-2B 判定器 + 一个标量阈值 τ。
For trajectory t = 1, 2, ...:
candidate_answer = Search-R1.reasoner.step(state_t)
retrieval_docs = Search-R1.retriever.query(state_t)
state_t' = append(state_t, candidate_answer, retrieval_docs)
judge_output = judge(state_t') # 结构化 sufficiency-and-gap
if judge_output.predicts_STOP and
judge_output.confidence > τ:
break # 第一处 STOP 决定部署策略
deploy(state_t' as final state)
关键设计点: - 冻结基线:Search-R1 的 reasoner / retriever / corpus / prompt / 检索预算全部不动;只换判定器和 τ。这是干净的"插件式"评估,避免与基线相互干扰,也保护了原作者的 RL 训练成果不被破坏。 - 判定器只读:判定器从 state 读信息,从不修改 state;它只是"看一眼,吐个判定"。 - 阈值在分组验证上冻结:这是 sequential selection 的标准做法——不允许在测试集上调 τ,否则评估失效。
判定器的输入/输出(结构化判定,非单一标签)
输入:当前 state(reasoner 中间步骤 + 已检索文档摘要 + 候选答案)。
输出:自然语言结构化判定,含三件事: 1. 信息是否充分(sufficiency):是 / 否 / 边缘。 2. 缺口在哪(gap):实体 / 时间 / 关系 / 数值中哪一类缺。这是论文借自 S2G-RAG 的关键差异化——不只是"够了 / 不够",而是"如果不够,缺什么类型"。 3. 置信度:与 τ 比较的标量。
这正是 S2G-RAG 思路:让判定带"gap 定位"的可解释信号,而非单一 logit。可解释性带来两个红利:① 失败归因(log gap 类型反哺检索索引);② 调试(人工能直接读懂判定器为何停 / 不停)。
训练数据
从 900 条互不相交的 HotpotQA 题上采出 3,009 个状态。互不相交 = 训练题不与验证 / 测试重叠,避免 leakage。每个状态由 Search-R1 真实轨迹上截取,并由一个 oracle(基于 HotpotQA gold supporting facts 的脚本)打 sufficiency / gap 标签。
⚠️ 标注口径未在摘要披露:oracle 是怎么从 gold supporting facts 推出"信息充分"判定的?比如一个 2-hop 问题需要 2 个 bridge entity 都被检索到才算充分?还是只需"答案命中 gold"就够?这关系到 3,009 状态标签的噪声水平,原文未明确。
部署时的 τ 选择
作者明确说"在分组验证上选阈值,再冻结用于确认集评估"。分组验证(grouped validation)通常意味着同一题的多状态不跨组——这对 sequential selection 的统计有效性很关键。τ 锁定后,整个确认集评估走一次,不再回头调参。
关键实验与数据
- 检索削减:相比 Native Search-R1,检索调用减 77 次(3.70%)。
- 答案准确率:Official Exact Match 由基线下降到 −0.625 个百分点。
- 作者自己的诚实标注(原文摘要末句):结果不意味着: 1. 准确率持平或上升; 2. 安全停止(safe stopping); 3. 总推理成本下降。
这一点非常关键——它直接划清了本文的边界:只是"少搜一点,准确率几乎不掉",而不是"更准 / 更便宜 / 更安全"。
亮点与局限
亮点
- 问题重述做对了:把"何时停"明确为 sequential selection 而非 i.i.d. 分类,是本文最大的方法学贡献。判定器和阈值都因此有了正确语义。
- 冻结基线:Search-R1 的 reasoner / retriever / corpus / prompt / 检索预算全部不动,只换判定器和 τ——这是干净的"插件式"评估,避免与基线相互干扰。
- 结构化输出:判定器吐 sufficiency + gap 类型 + 置信度,远比单 logit 信息密度高,可用于早停也能用于失败归因。
- 诚实标注:摘要末尾主动声明三项"结果不蕴含"的事,远比常见的"显著提升 SOTA"话术更可信。
局限 / 风险
- 3.70% 的削减幅度有限:减 77 次检索对单条轨迹意义不大,对大批量推理是否折算成真实成本(wall-clock、token)原文未明确,需进一步算。
- 只在 HotpotQA 评测:900 题 / 3,009 状态是单数据集规模。泛化到 2WikiMultihopQA / Bamboogle / Musique / 开放域 QA 未知。
- 2B 判定器本身有推理开销:每个 state 多一次 forward,部署时是否真的"总成本下降"未给出;摘要已显式说 no。
- τ 在分组验证上冻结:意味着调参空间被锁死,性能对 τ 不敏感才能成立——原文未给敏感性曲线,⚠️ 标注存疑。
- sequential selection 的统计效率:单条轨迹一个 STOP,3,009 状态样本量对阈值估计够不够稳健,未做 bootstrap / 置信区间。
- oracle 标签的可靠性:摘要未披露 oracle 怎么从 gold supporting facts 推出"信息充分"——这是训练标签的真实噪声源。
⚠️ 原文未明确:wall-clock 节省数字、跨数据集泛化、判定器相对 Search-R1 的延迟开销、τ 敏感性曲线、是否对 Search-R1 之外的 RL-trained retriever 同样有效、oracle 标签的具体规则。
对工程落地的启发
- 最小改动接入:如果你的栈已经是 Search-R1 / IRCoT / ReAct 类多轮 RAG,加一个 2B 判定器就能拿到 3.70% 的检索削减——改动面 = 加模型 + 调 τ,不需要重训 reasoner。
- 判定器要带结构化输出:单纯 logit 二分类丢失"缺什么"的可解释信号;改成"sufficiency + gap 类型 + 置信度"三项,既能用于早停也能用于失败归因(log 缺口类型反哺检索索引)。
- 阈值必须在验证上冻结:部署后只改判定器权重,不改 τ;否则会破坏 sequential selection 的统计假设。
- 不要把"检索减"等价于"省成本":判定器 forward 也要算钱;要真正省钱,得把判定器和 KV cache / 投机解码做联合优化。
- 失败归因的副产物:即便不上线早停,单跑判定器也能给出"系统最常缺的是什么"——这是宝贵的检索诊断信号,可用来迭代 corpus 与 query 改写策略。
- 可与 Self-RAG / Auto-RAG 共存:本判定器只管"是否停",不管"下一轮查什么"——可以放在 Self-RAG 的 reflection token 之前作为前置过滤。
与同方向工作的关系
- S2G-RAG(前置工作):本文直接借用了其"结构化 sufficiency-and-gap 判定"思路,但把应用场景从单轮 RAG 扩展到多轮 Search-R1 流水线。可以理解为 S2G-RAG 的"序列选择再发现"。
- Search-R1(基线):被冻结为推理与检索后端;本文判定器是其外挂早停门。
- FLARE / Self-RAG / Auto-RAG:同属"动态决定检索时机"家族。本文的差异点是:① 把问题显式建模成 sequential selection;② 强调"第一个 STOP 决定部署"这一序列性质。
- Token-level 早停(Early-Exit Transformer):思路相近但作用于模型层(中间层退出),作用于"轮次层"是本文定位。
- Optimal Stopping Theory:理论背景;本文是其在 RAG 工业场景的具体落地。
适合谁读
- 做多轮 RAG / Agentic RAG 推理栈的工程师:可直接借鉴接入方式(外挂判定器 + 冻结 τ)。
- 研究 sequential decision making / optimal stopping 的人:这是一个干净的工业应用案例。
- 做 RAG 评估与可解释性的人:结构化 gap 输出可作为新的可解释信号与失败归因工具。
- 不适合:想要"显著 SOTA 提升"故事的读者——本文是诚实的"小幅削减 + 小幅 EM 下降",不卖 SOTA。
一个补完视角:为什么 3.70% 也值得看
乍看 3.70% 削减是个小数字,但有几个上下文让它在工程上不"小":
- 多轮 RAG 的边际成本不是线性的:每多一轮检索都要重新跑 retriever + 把新文档塞回 LLM context。在 8K+ context 的现代栈里,第 N 轮的成本可能远高于第 1 轮(前缀 cache 失效、attention 增长)。所以"少 3.70% 的轮次"折算成 wall-clock 可能比 3.70% 大。
- 延迟尾部削减:很多生产事故不在平均延迟而在 P99 延迟——一个会反复搜的"难 query"被早停后,P99 立刻下降。摘要虽未量化 P99,但工程上这是 3.70% 之外的隐藏红利。
- 可叠加性:RMM / 投机解码 / KV cache 优化都在做"少算一点",本文做"少搜一点"——彼此正交。一个生产栈可以同时上,收益叠加。
复现清单(如果想自己跑)
⚠️ 以下均基于摘要 + 论文评论里的代码链接;具体超参需读正文:
- 克隆作者仓库:
github.com/luobostorm/search-r1-s2g-stopping(原文评论字段)。 - 准备 Search-R1 基线:reasoner / retriever / HotpotQA corpus 按 Search-R1 原始配置;不要替换。
- 训练判定器:从 900 题采 3,009 状态,oracle 打 sufficiency + gap 标签,监督微调 Qwen3.5-2B。
- 选 τ:在分组验证集上扫阈值,固定一个值。
- 评估:在确认集(与训练题互不相交)上算检索调用次数与 EM。
- 自检:Native Search-R1 vs 加判定器,确认检索 −77 次、EM −0.625 pp 的量级在你的设置上能复现。
一句反方
如果只看"−77 次检索 + −0.625 pp EM",本文几乎就是"花 3 页写了一个 −0.6 pp 的负向结果"。但 sequential selection 的问题重述 + 结构化 gap 输出 + 冻结基线评估 + 诚实标注四件事合起来,使它成为多轮 RAG 工程优化方向上一个值得复现的基线,而不是一个被高引的 SOTA。
工程落地与核查(Jay)
事实核查注记
- 3.70% = 77 次检索:⚠️ 摘要只说"77 次"削减,未说分子分母各自绝对值;77 / 总调用数 = 3.70% 反推总调用数约 2,081 次(77 ÷ 0.037 ≈ 2,081),但原文未显式给出总调用数,读者无法独立验证百分比计算是否自洽。建议补:引用时注明「原文仅披露削减 77 次,总调用数未公布」。
- τ 值未在摘要给出:⚠️ 判定器置信度阈值 τ 的具体数值未披露;无法判断"τ = 某个固定值"在不同生产数据集上的泛化性。
- oracle 标注规则缺失:⚠️ 3,009 状态训练标签的 oracle 规则未披露;gap 类型判断(实体 / 时间 / 关系 / 数值)是否与你的实际 corpus 一致,无法预判。
- 代码链接来自评论区:
github.com/luobostorm/search-r1-s2g-stopping是评论区的非官方引用;正文是否包含正式 GitHub 链接待核实。
实际系统怎么用
- 插件式接入,不动基线:在现有 Search-R1(或同类多轮 RAG)流水线外串一个 Qwen3.5-2B forward;推理链路无需改动。但要注意:判定器是额外的一次 LLM forward,在计算总成本时必须把它算进去——3.70% 检索削减可能不足以覆盖额外的 LLM 调用成本。
- gap 类型用在自己的检索诊断:即便不直接上线早停,也可以单独跑判定器,把 gap 类型 log 收集起来做检索诊断——「系统最常缺的是哪类信息」是迭代 corpus 与 query rewrite 的珍贵信号。
- τ 必须冻结:选好 τ 后部署到生产就不要再调;τ 变了 sequential selection 的统计假设就失效,导致评估结果不可信。建议:τ 上线前在离线验证集上做敏感性分析,确认性能对 τ 在合理区间内不剧烈抖动。
坑在哪
- 3.70% 削减可能不够覆盖判定器成本:如果你的 Search-R1 用的是 GPT-4o / Claude 等重型 reasoner,一次额外 2B forward 确实便宜;但如果你的 reasoner 本身就是小模型(如 Qwen2.5-7B),额外 forward 的相对成本会更高。需要自己在生产数据上跑一遍 cost accounting,不能假设"一定省钱"。
- HotpotQA 的 2-hop 特性可能不泛化:HotpotQA 是「找 bridge entity」的 2-hop 问题;真实生产环境的问题类型更广(事实类、比较类、定义类、计算类),gap 类型分布可能完全不同,判定器在你的数据上需要重训或至少微调。
- sequential selection 的统计假设对冷启动不成立:本文在 900 题 × 3,009 状态的规模上做 sequential selection 估计;在真实生产中,很多 query 类型只有几十条轨迹,阈值估计极不稳健,冷启动阶段不要迷信论文数字。
- 分组验证 vs 随机验证的陷阱:作者的 grouped validation 保证同一题的多状态不跨组,防止 leakage;但你的内部验证集如果没有做 grouped split,τ 可能会过拟合到随机 split 上,导致确认集性能虚高。
- 判定器无法防止"停错了之后又回头":一旦判定器输出 STOP 且置信度 > τ,就 break;但如果底层 reasoner 实际只走了半步(state 不充分但刚好 confidence 虚高),系统会在证据不足时停掉。这个 failure mode 没有 recovery 机制,需要在上游 retriever 里加兜底逻辑。