
多轮工具调用训练跑到第三、四轮就吐乱码、重要性采样比值暴涨、梯度范数瞬间拉满这类雪崩十有八九是 void turn 没被过滤掉。用 TaoToken 拿模型通道注册与创建 Key 在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_content 完成然后让 Codex 帮你逐段核对那份「无效轮」检测代码漏在哪一行。这篇不聊排行榜只做一件事把 SimpleTIR 的启发式规则摊开对着你自己的 rollout 源码、检测函数和训练日志一个接入点一个接入点地查。先讲清分工。SimpleTIR 的判定逻辑必须留在你自己的训练代码里TaoToken 只负责提供模型通道的 Key 和 Base URLCodex 通过这条通道读你贴过去的代码片段和日志做静态对照与逐行解释。它不会连你的训练集群也不会替你跑 rollout。回归测试、梯度检查、重跑训练全都在本地执行再把输出贴回对话——这条桥不能反过来搭。1. 第三、四轮开始吐乱码void turn 判定条件的现场还原1.1 崩的链条从工具返回那一刻就埋好了多轮工具调用里模型的输出不是终点而是一段会被拼回上下文的中间产物。Python 解释器的 stdout、搜索接口返回的 JSON、报错堆栈这些内容的分布和模型预训练语料差得很远属于典型的分布外 token。它们作为下一轮输入被喂回去之后模型在采样时就开始偏离原本的语言分布。偏一轮不致命问题在于它会自我强化。第二轮偏离一点点第三轮接着在偏离的上下文上再采样到第三、四轮就会出现重复符号、半截标签、中英夹杂的乱码甚至直接停止生成。与此同时那些低概率 token 在新旧策略下的概率比会被放大成分母极小的分数重要性采样比值从 1 附近窜到两位数梯度范数跟着立起来一次更新就能把前面几十步的收敛成果冲掉。梯度裁剪和 KL 正则在这里只是止血。裁剪把超标的梯度按比例缩回去等于把「这一轮学错了」这件事压住但没删掉KL 惩罚则是在整体上拉住策略管不了单条轨迹里的局部畸变。真正脏的数据还留在 batch 里下一个 iteration 继续贡献噪声。1.2 SimpleTIR 那 10 行到底判了什么SimpleTIR 换了个思路不去修正这条轨迹直接判定它该不该进入策略更新。规则只有一句话——模型在某一回合既没有生成可执行代码块也没有给出最终答案这一轮就是 void turn整条轨迹直接丢弃不参与更新。这个判定很粗暴但正好切断了前面那条链。第一无效轮几乎总是伴随极低概率 token把它连同整条轨迹一起扔掉等于从源头掐掉了高幅值梯度的来源。第二无效轮之后的那次失败不应该拿来惩罚前面几轮本来正确的推理步骤整条丢弃就避免了对正确步骤的信用分配错位——你不需要去算「这两步该给多少分」这种没有标准答案的账。要注意「整条」这两个字。不是把无效轮对应的 token 的 loss mask 置零而是把这条轨迹从这一批样本里删掉。置零只是让这一步不产生梯度但优势函数、比值计算仍然把这条轨迹算进分母噪声还在。这一点是后面排障时最容易写错的地方。2. 备齐三份材料再去拿 Key 配 Codex2.1 递给 Codex 的三份材料在打开任何配置之前先把材料准备齐否则对话里只能靠描述Codex 也只能给你泛泛的结论。三份材料分别是rollout 生成函数从第一轮拼 prompt、调工具、把返回值拼回上下文到循环结束的整段代码。重点是工具返回结果的拼接位置。void turn 检测函数或片段就是那段判「既无代码块又无最终答案」的逻辑含它调用的代码块提取函数。一段真实训练日志包含每步的有效轨迹条数、importance sampling ratio 的 min/max、grad norm。有 ratio 曲线更好。把这三份东西按顺序贴进对话比丢一句「我的训练炸了」有用得多。日志不用全贴把 ratio 出现尖刺的前后 20 步截出来就够定位。2.2 创建 Key 与模型 ID 的取法材料备好之后打开 TaoToken 注册账号进控制台创建一把 API Key记作YOUR_API_KEY。模型 ID 不要凭记忆写以站内模型广场当时的列表为准把列表里的 ID 原样复制过去不要自己加日期后缀。一个常见的小坑Key 只显示一次创建完先存到密码管理器里后面 Codex 的env_key要用到它。2.3 在 config.toml 里把 Codex 指到统一通道Codex 走的是~/.codex/config.toml不是 Claude Code 那套ANTHROPIC_*环境变量两者别混。按下面这份改注意base_url是填进工具的接口地址末尾不带/v1也不要挂任何查询参数model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chatKey 通过环境变量传进去别硬编码进文件export TAOTOKEN_API_KEYYOUR_API_KEY codex如果你的通道按responses协议对接把wire_api改成对应值即可具体以接入文档说明为准。改完启动 Codex随便问一句「读一下这段文件里的函数」能正常返回就说明通道通了。3. 逐段核对 10 行无误轮检测三个最容易漏的接入点3.1 判定端代码块与最终答案必须是「双空」把检测逻辑单独抽成一个纯函数先保证它本身是对的再去谈接入位置。下面这段只做一件事判断这一轮是不是既没有代码块、也没有最终答案。CODE_FENCE re.compile(r(?:python|py|bash|sh)\s*\n(.*?), re.S) def has_executable_code(text: str) - bool: return CODE_FENCE.search(text or ) is not None def is_void_turn(turn_text: str, final_answer: str | None) - bool: no_code not has_executable_code(turn_text) no_answer not (final_answer or ).strip() return no_code and no_answer让 Codex 对照你原来的实现检查三处代码块的正则是不是只认了 Python 围栏导致其他语言的可执行代码被漏判成空工具返回的代码块是不是也被同一段正则扫进去了那属于误判最终答案的判定是不是用了过于宽松的endswith匹配把「我再确认一下」这种过渡句也算成了答案。3.2 丢弃端整条轨迹要在策略更新之前消失判定函数只是零件真正决定成败的是它被调用的位置。理想顺序是rollout 生成完毕 → 逐条扫描是否存在 void turn → 剔除整条轨迹 → 计算优势与比值 → 策略更新。判定写在策略更新之后等于这一批脏数据已经污染过参数了。for traj in rollouts: traj[valid] not any( is_void_turn(t[text], t.get(final_answer)) for t in traj[turns] ) kept [t for t in rollouts if t[valid]] # 从这里开始才算 advantage、ratio、KL把这段代码贴给 Codex让它指出你的训练脚本里 advantage 的计算位置在剔除之前还是之后。这一步是纯静态对照不涉及任何执行。3.3 日志端从 ratio 尖刺倒推是哪一步漏滤如果源码层面看不出问题就回到日志。把尖刺前后的步数截出来让 Codex 一起看三组数字有效轨迹条数、单条轨迹的最大 token 长度、ratio 的极值。典型的漏滤特征是——ratio 极值和最大序列长度同时出现在同一步而且那一步的有效轨迹条数没有下降。反过来如果有效轨迹条数确实在下降但 ratio 依然会尖刺那就要怀疑淘汰发生得太晚脏轨迹已经参与了一次更新。这个判断不需要跑实验把日志和源码并排贴出来讨论就够了。4. 第三、四轮乱码与梯度炸裂的排障对照现象更可能的原因先查哪里ratio 尖刺且伴随超长乱码轮该轮未被判为 void仍进了 lossis_void_turn与剔除位置有效轨迹数长期不变检测函数恒返回 False代码块正则、最终答案取值来源某步有效样本数为 0一批轨迹被整组丢弃是否需要重采样或放宽批次策略报 401环境变量名与env_key不一致config.toml与 shell 导出请求路径重复base_url末尾多写了/v1改回https://taotoken.net/api4.1 误判成「有效轮」的几种写法最隐蔽的一类是把「我这就要调用工具了」这种过渡性发言当成有效轮。它既没有代码块也没有最终答案按规则就该判 void但如果你的检测只看tool_calls是否非空就会被放过去。工具调用本身是否算「可执行动作」要按你自己框架对动作的定义对齐别用一套标准写检测、用另一套标准算奖励。第二类是把工具返回的内容当成模型的输出。解析上下文时如果没有把 role 区分开正则就会扫到工具侧贴进来的代码块于是明明空洞的一轮被判成了有代码。第三类是最终答案字段的取值时机有些框架在生成结束前就把final_answer预填了一个空字符串以外的占位内容检测自然失效。4.2 配置侧的两个报错401基本都是 Key 没被读到env_key写的是TAOTOKEN_API_KEY但 shell 里导出成了别的名字或者新开一个终端窗口忘了再导出一次。验证时不要只看 Key 字符串有没有贴对直接看请求返回的错误体更准确。另一个高频问题是地址写错。填进 Codex 的base_url是https://taotoken.net/api末尾的/v1不要加也不要在这一项上挂任何查询参数。模型 ID 报错则先回模型广场核对以当时列表里的写法为准不要保留旧的记忆版本。想确认通道本身是否正常可以先去 模型对话 用同一把 Key 发一条消息绕过 Codex 先验证 Key 和模型 ID 这一层。4.3 训练侧整组被丢空的时候整组轨迹都被判成 void是这套启发式规则在大 batch 下的正常代价。处理方式不是放宽判定而是把重采样当成流程的一部分一批样本全丢就重跑一次 rollout或者把 batch 内有效轨迹的最小条数作为监控指标打出来。还有一个容易被忽略的副作用如果检测把「带正确最终答案的短轨迹」也判成 void有效样本会莫名变少训练看起来更稳定但学得更慢。查法很简单——统计一下被判 void 的轨迹里有多少条的最后一轮其实给出了正确答案。这个比例长期高于预期就该回头修判定条件了。5. 小批量回归验证再回控制台对账5.1 用一条已知含 void turn 的轨迹做回归准备一条你确定第三轮是乱码、且第三轮既无代码块也无最终答案的样本单独跑检测函数确认它返回 True再打印剔除前后的轨迹条数确认这条样本在 advantage 计算之前就消失了。这一步只跑数据不跑训练几秒钟就能出结果但它能挡住后面几天的无效调参。之后把剔除逻辑改动的 diff、加上这段验证输出一起贴回 Codex 对话让它按前面三个接入点再对照一遍判定条件有没有漏、丢弃位置够不够早、策略更新前有没有残留的引用。确认无误再放开批量训练并盯住前 50 步的 ratio 极值曲线。5.2 回到控制台看这次调用有没有记上账通道跑通、检测逻辑也核完之后值得顺手确认一下账目。打开 控制台 API Keys 看看刚才那把 Key 的调用记录有没有正常入账如果打算长期用它做代码对照Coding Plan 里能看清套餐额度够不够支撑你的日常读码量。Key 用完了记得回 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_content 重建一把别把旧 Key 硬编码进训练脚本里。排障这件事最贵的成本从来不是找到答案而是把问题描述清楚。把 void turn 判定摊成三行代码、把接入点标成两个位置、把日志截成 20 步这三个动作做完往往不用等 Codex 给结论你自己就已经看见漏在哪一行了。