
大模型现在最大的问题之一不是不会推理而是明知道推不出结果还要硬推到底。你让它解一道无解的数学题它会一本正经地写满几百行推理过程最后给出一个错误答案你让它回答一个前提有误的问题它会在错误前提上继续扩展浪费大量 token。这篇题目为Knowing When to Quit: Diagnosing and Training LLMs to Abort Futile Reasoning的工作做的正是这件事让 LLM 学会“判断推理是否已经无意义”并在合适的时候主动终止而不是继续输出无效内容。这听起来像是给模型加一个“止损开关”。实际上它涉及两个层面的问题一是如何诊断推理正在走向徒劳二是如何通过训练让模型具备这种终止能力。这篇文章不讨论某个具体一键包怎么装而是把这个研究方向拆开讲清楚问题、诊断方法、训练方案、评估指标以及在推理服务里落地时的工程思路。如果你在做 LLM 推理优化、RAG 问答、数学推理评测或者被“模型死磕无解问题然后胡说八道”这件事困扰过这篇文章可以直接往下看。1. 核心能力速览能力项说明研究对象LLM 的推理过程重点是无解问题、歧义问题、错误前提问题核心目标让模型识别“推理已无意义”并主动终止减少无效 token 和错误输出诊断手段推理长度、重复度、置信度变化、多次采样一致性等信号训练手段数据构建 监督微调 / 强化学习给模型引入“放弃”或“中止”行为推理阶段干预结合 max_tokens、早停策略、预算控制等方式做工程层止损硬件要求取决于基础模型规模属于模型训练与推理优化类任务需按实际环境测试评估指标解题正确率、中止准确率、平均推理长度、token 节省量适合场景数学推理、RAG 问答、Agent 任务、API 成本优化、推理服务稳定性治理说明这篇文章讨论的是方法与思路不绑定某个具体开源仓库。文中的代码都属于通用实现模板实际接入时需按你的模型接口和数据结构调整。2. 问题背景为什么 LLM 会死磕无解问题先看一个常见场景。一个 7B 数学模型输入“请证明 13”。模型会真的开始列等式、做变形、写推导最后给出一个看似完整但明显错误的结论。这个过程在评测集里会被判定为“回答错误”但它消耗的推理 token 可能比一道正常题目还多。再比如 RAG 场景知识库里根本没有“某某产品在 2025 年的新功能”模型却能从检索片段里找到几个相似词然后顺着编一段。用户等了几十秒拿到一段不可信内容。这类问题的共性是什么模型缺少“推理是否还有意义”的自我判断。它可以评估每一步推导是否合理但很难判断“整体上这个方向已经死了”。原因有很多训练数据里基本都是“有解题目 完整解答”很少出现“无解题目 正确放弃”的样本RLHF 或推理强化学习阶段奖励函数通常只看最终答案对不对不看中间过程是否冗余或无效长 CoT 风格的推理模型进一步放大了这种情况——模型被鼓励“多想几步”结果就更不容易停。也就是说这不仅是推理能力问题也是训练目标问题。模型没有足够的“理由”去放弃因为放弃在大多数训练数据里并没有被定义成一种合理行为。3. 诊断方法怎么判断推理已经“没救”了要让模型学会中止第一步是定义“推理陷入徒劳”的可观测信号。诊断信号可以从推理过程本身提取不需要依赖外部知识。3.1 推理长度异常膨胀正常推理会有“前提 → 推导 → 中间结论 → 最终答案”的节奏。当推理 token 数远超同类题目均值且仍没有出现收敛性表达如“综上”“因此答案为”时大概率已经进入无效循环。实现上可以先对一批问题做基线统计得到每个难度级别的平均推理长度。之后设定阈值比如超过均值 2.5 倍就触发可疑标记。avg_len compute_avg_reasoning_len(similar_problems) if reasoning_len avg_len * 2.5: mark_futile(length_exceeded)3.2 局部重复与语义停滞模型死磕时最常见的表现是重复表达。它会把同样一个中间结论换着说法写三遍甚至连续出现相同或高度相似的句子。可以通过 n-gram 重复率、嵌入相似度、或相邻句子的余弦相似度来检测。from sentence_transformers import SentenceTransformer model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) sim cosine_similarity(model.encode(sentences[i]), model.encode(sentences[i1])) if sim 0.92 and repeated_span_detected: mark_futile(semantic_stagnation)3.3 置信度与熵的变化趋势推理初期模型对方向的置信度会逐步提升。但进入无效推理后如果输出分布的熵持续不下降甚至上升说明模型自己也在“赌”。对开源模型可以逐 token 记录 logprob观察尾部 token 的平均负对数似然是否异常升高。3.4 多次采样一致性同一个问题采样 3 到 5 次如果模型给出多个互不相容的答案且中间过程中都存在大段“似乎要成功但最后失败”的段落那么这道题基本可以判定为当前模型能力边界之外的问题。此时中止比继续硬推更合理。3.5 自评信号部分模型支持让模型自己输出“是否能确定答案”的置信度。可以在推理段最后插入一个自评提示例如请评估当前推理状态 A. 已可以得出确定答案 B. 还缺信息但方向可行 C. 方向不可行继续推理无意义如果模型输出 C则立即停止。这个方式最接近原论文“训练模型主动放弃”的思路也是从诊断走向训练的最自然过渡。4. 训练方案让模型学会“主动放弃”单靠推理时的硬判断不够更好的方式是让模型把“中止”作为推理策略的一部分。这对应论文里的“Training”部分下面拆成三条路线。4.1 构造带放弃标签的训练数据核心思路在数据里明确标记“应该中止”的位置。对有解题目保留完整推理标签为“继续”对无解题目、错误前提题目、信息不足题目构造推理过程并在某个位置标记“到此为止不再继续”。这样模型会学习到一个条件当推理进入某种状态时下一层行为不是继续推导而是输出一个终止标记。终止标记可以是特殊 token也可以是一句固定表达例如“我判断该题无解停止推理”。{ question: 请证明 13, reasoning: [ {step: 假设 13 成立则两边同时减 2得到 -11, label: continue}, {step: 对 -11 两边取平方得到 11未出现矛盾, label: continue}, {step: 该方向无法导出矛盾且原命题与已知算术规则冲突, label: abort} ], final: 我判断该题无解停止推理。 }4.2 用强化学习奖励“及时止损”在 RL 阶段奖励函数需要重新设计。传统做法只看最终答案导致模型走完整个推理过程才被惩罚。更好的做法是把推理长度和最终状态一起纳入奖励无解题目若模型正确识别并中止给正奖励无解题目若模型继续推理并给出错误答案给负奖励且推理越长惩罚越大有解题目若模型过早放弃给负奖励有解题目正常完成推理给正奖励。这本质上是让模型权衡“继续推理的期望收益”和“当前状态的实际价值”。训练后的模型会学会一个决策当置信度低到一定程度时中止是收益最高的动作。def reward(question, reasoning, answer, is_solvable): if not is_solvable and terminated_by_model: return 1.0 - 0.001 * len(reasoning) if not is_solvable and not terminated_by_model: return -1.0 - 0.001 * len(reasoning) if is_solvable and terminated_by_model: return -0.5 if is_solvable and answer_correct(reasoning): return 1.0 return -0.54.3 在推理链上引入显式的“决策点”第三种做法是在模型的推理过程中周期性插入决策点。模型每写若干步就需要输出一个决策标记如continue或abort。训练时对决策标记单独计算损失推理时拿到abort就立即终止。这种方案的好处是可控性好工程接入简单不需要额外解析自由文本。缺点是决策点的间隔需要调参间隔太小会打断推理流畅度间隔太大则浪费时间。5. 评估指标与效果验证训练完成后不能只看整体准确率。要重点评估“该停的时候有没有停、不该停的时候有没有乱停”。指标定义作用解题正确率有解题目中最终答案正确的比例确认训练没有牺牲正常推理能力中止精确率模型主动中止的题目中真正无解/无法完成的比例防止模型用“放弃”逃避难题中止召回率真正的无解题目中模型正确中止的比例衡量模型对无解问题的敏感度平均推理长度所有题目平均使用的 token 数衡量训练后是否真的减少了无效推理token 节省率相对基线模型的推理 token 减少百分比直接对应成本和延迟优化效果过早放弃率有解题目中被错误中止的比例训练失效时最先暴露的问题验证流程可以这样设计构造一个包含有解、无解、错误前提、信息不足四类题目的评测集各类数量平衡先跑基线模型记录四类题目的准确率和平均推理长度再跑训练后的模型记录同一组指标对比中止精确率和过早放弃率确认模型的“放弃行为”是准确判断而不是偷懒统计 token 节省率评估实际成本和延迟收益。6. 推理服务的工程落地给 LLM 推理加一个“止损器”即使不动训练推理服务层也可以先做一个止损器。这对于接入 OpenAI 风格接口、本地 vLLM 服务、或者国内各家大模型 API 的工程团队都适用。6.1 请求级预算控制调用推理接口时不要只依赖模型自己停下来。给每个请求设置 max_tokens 上限并分阶段监控。对于需要多轮 Agent 调用的场景还要设置整条链路的预算而不是单次请求预算。import time MAX_REASONING_TOKENS 1200 MAX_TOTAL_SECONDS 30 def run_with_budget(prompt, generate_fn): start time.time() segments [] total_tokens 0 while time.time() - start MAX_TOTAL_SECONDS: resp generate_fn(prompt, max_tokensMAX_REASONING_TOKENS) segments.append(resp) reasoning resp.get(reasoning_content, ) total_tokens resp.get(usage, {}).get(total_tokens, len(reasoning)) if is_converged(reasoning): return {ok: True, answer: resp.get(content), segments: segments} if is_futile_signal(reasoning): return {ok: False, reason: futile_reasoning_stopped, segments: segments} if total_tokens 4000: return {ok: False, reason: budget_exceeded, segments: segments} prompt resp.get(content, ) return {ok: False, reason: timeout, segments: segments}6.2 诊断信号的实时监控把前面提到的诊断规则做成一个中间件既能用于统计也能用于干预。def is_futile_signal(reasoning_text): signals [] if reasoning_token_count(reasoning_text) 1500: signals.append(length) if ngram_repeat_rate(reasoning_text, n4) 0.35: signals.append(repetition) if semantic_stagnation(reasoning_text): signals.append(stagnation) return len(signals) 26.3 接入 OpenAI 兼容接口对于 OpenAI 兼容接口可以用 stream 模式逐段接收推理内容在本地判断是否触发止损条件。触发后直接断开不回传最终结果或者回传一个固定说明。from openai import OpenAI client OpenAI(base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY) response client.chat.completions.create( modelyour-model, messages[{role: user, content: 请证明 13}], max_tokens2048, streamTrue ) buffer for chunk in response: delta chunk.choices[0].delta.reasoning_content or buffer delta if len(buffer) 1500 or is_futile_signal(buffer): print(触发止损终止本次推理) break这个方案不需要改模型适合先验证“中止策略”对服务成本和响应延迟的实际收益。7. 资源占用与性能观察从资源角度看“死磕问题”的代价直接体现在三个方面token 消耗模型推理得越长单次请求的 token 费用越高显存占用长序列推理时 KV Cache 持续增长显存占用随推理长度上升服务延迟单请求推理时间变长会挤压并发槽位影响整体吞吐。观察方法比较简单。本地部署时用nvidia-smi看推理过程中显存的增长曲线用服务端日志记录每个请求的total_tokens和耗时。批量评测时按题目类型分组统计平均推理 token 数能直观看到模型在无解问题上浪费了多少资源。更稳妥的判断是即使不上任何训练手段仅靠推理层的预算控制就能减少显存峰值和响应时间。要实时监控的话可以把每次请求的reasoning_tokens、finish_reason、response_time三条日志信息汇总到 Prometheus 或简单的 CSV 文件按周对比。# 观察推理过程中的显存占用变化 watch -n 1 nvidia-smi如果在批量任务里发现某一类问题总是触发max_tokens截断说明这类问题很可能落入了“无解死磕区”应当单独做一轮诊断样本分析而不是简单调大上限。8. 常见问题与排查方法问题现象可能原因排查方式解决方案模型开始频繁放弃有解题目训练数据中“中止”样本占比过高统计“中止”样本与正常推理样本比例平衡数据或调低过早放弃惩罚该停的场景仍然不停模型没有充分学到终止信号检查推理片段末尾是否出现abort相关 token增加带终止标记的样本或在推理层附加诊断规则兜底推理层止损器误伤正常回答n-gram 重复率阈值设置过低抽样看被误判的样本日志调高阈值或改为多个信号加权判断触发止损后结果不可用止损策略太激进长推理题目被直接切断分难度设置不同预算按题目难度和来源分别设置预算强化学习训练不稳定奖励函数中长度惩罚过重单独验证 reward 曲线适当降低长度惩罚系数分阶段引入API 调用超时请求 max_tokens 设置过大服务端排队检查服务端负载和并发数降低 max_tokens增加请求级超时批量任务大量触发预算上限任务集中包含无解或低质量检索内容统计批次内触发原因对输入做前置筛选或在 batch 日志中标记“non_answerable”字段长上下文显存溢出推理长度增长导致 KV Cache 增加观察显存随推理 token 的增长曲线使用分块推理、降低并发、或限制推理 token 上限9. 最佳实践与使用建议先诊断后训练。不要直接动手构造训练集。先把你的任务数据按有解、无解、信息不足、错误前提分好类统计模型在每类上的推理长度和行为模式再决定要采用哪种训练策略。推理层止损器和训练方案可以并行。推理层止损器是一道保险训练方案是治本。两者不冲突但要以推理层止损为基础避免训练过程中的坏样例继续烧钱。数据构建要注意标签一致性。构造“应中止”样本时必须由人或者强模型审核避免把“模型能力不够导致推不出来”的题目标记成“客观无解”。这两者的训练目标是不同的。批量任务要加失败原因字段。在批量日志里单独记录abort_reason比如length、repetition、self_consistency_low、model_declared后续分析时才能定位是哪一类信号在起作用。强化学习阶段的奖励设置要有实时监控。重点关注“过早放弃率”和“中止准确率”两个指标。过早放弃率升高比准确率下降更危险因为它意味着模型学会了逃避。涉及真实用户问题、个人数据、版权材料时不能直接拿来做训练数据。要确认授权范围并做脱敏处理。推理层的止损策略也应避免在用户面前暴露“我放弃了”这类生硬表达。商用或发布前务必用一份独立评测集复核。训练集和评测集要分离否则无法判断模型是真的学会了“判断无用推理”还是只是背下了训练样本。10. 总结与下一步这个研究方向最值得关注的点是它把“推理能力”和“推理资源”放进了同一个优化目标里。过去我们默认模型应该把问题推完这篇文章的视角是一段注定失败的推理推到一半就停比推完更有价值。它改变的不只是生成逻辑还有我们对“正确答案”的定义——在无解问题上正确行为就是不再继续。最先值得验证的功能是在你的业务数据上复现“有解/无解”分类然后跑一次推理层止损。这一步不需要训练模型只需要在接口层加几行判断代码就能看到 token 消耗和响应延迟的变化。最容易踩的坑有两个一是把“无法解答”和“模型不会”混为一谈导致训练数据标签造错二是止损阈值调得太激进让模型在有解问题上频繁放弃最后整体准确率反而下降。后续可以继续扩展的方向包括把中止信号接入 Agent 规划器让 Agent 在任务不可完成时提前返回在 RAG 检索质量下降时触发重新检索以及把“推理是否该停止”做成一个独立的轻量判别模型配合大模型做两层判断。这些都还需要根据实际模型和环境逐步验证。建议先收藏这篇文章等你的推理日志里出现明显的 token 浪费时再按这里的思路做一轮排查和改造。