OpenClaw-RL 的 Teacher log-probs 链路太长?让 Codex 走 TaoToken 读 openclaw_opd_api_server.py 读 OpenClaw-RL 的源码笔记写到 Teacher 这一段很多人会先卡在openclaw_opd_api_server.py上一条 teacher 前向要穿过 hint 注入、max_new_tokens0、logprob_start_len、BOS 偏移修正、pad/trim 和 top-K 返回好几层每层返回的张量形状还都不一样。自己拿编辑器上下跳很容易翻到一半就忘了当前这个 log-probs 到底是给哪个字段用的。让 Codex 陪着读会省劲不少先打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册并创建一把 API Key再把 Codex 的 Base URL 指向 https://taotoken.net/api 之后就能一边翻文件一边让它对照解释_compute_teacher_log_probs和_compute_teacher_topk_logprobs。这里 TaoToken 只负责统一接入和出 KeyTeacher 前向、log-probs 计算和蒸馏损失依旧全部在 OpenClaw-RL 自己的进程里完成不参与任何一步计算。1. 读 Teacher 链路前先把 Codex 接上 TaoToken1.1 这条链路到底长在哪openclaw_opd_api_server.py是 OPD 流程对外暴露 API 的那一层Teacher 相关的逻辑被拆成两个入口函数一个负责整段序列的候选 log-probs一个负责每步 top-K 的 log-probs。单独看每个函数都还好麻烦的是它们之间的输入输出不一致hint 注入会把原始 prompt 改掉max_new_tokens0让解码器只做一次前向不吐新 tokenlogprob_start_len又问你要从第几个位置开始截取最后 BOS 偏移和 pad/trim 再把索引整体挪一遍。读源码笔记时最容易犯的错就是拿一个函数里的索引去对另一个函数的输出。比如logprob_start_len是按 tokenized 之后的序列算的而 hint 注入发生在 tokenize 之前两个阶段的长度天然对不上再叠加 BOS 偏移修正返回值里第 i 个位置的 log-prob 在输入里对应的其实是第 i-1 或 i1 个 token。光靠肉眼对越读越乱。1.2 把 Codex 的出口指向 TaoToken 兼容通道Codex 读取~/.codex/config.toml作为主配置把 provider 和 base_url 分开声明就能走兼容通道。写入下面这段YOUR_MODEL_ID换成模型广场里当时列出的 ID不要凭记忆写model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY注意base_url末尾不要加/v1也不要把官网的 UTM 参数带进来接口地址就是干净的https://taotoken.net/api。Key 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的 TaoToken 控制台 创建然后写进环境变量export TAOTOKEN_API_KEYYOUR_API_KEY如果你的 Codex 版本要求显式声明 wire_api 之类的字段以你本地装的版本为准不要照搬网上别人的config.toml。Key 是占位符永远别把它硬编码进仓库再提交。2._compute_teacher_log_probs里的 hint 注入与max_new_tokens02.1 hint 注入改了哪一段输入Teacher 之所以要 hint是因为 OPD 想让 teacher 在一个已经知道方向的条件下给出分布而不是让它盲猜。_compute_teacher_log_probs在拿到原始样本之后会先把 hint 拼进 prompt 模板再送去 tokenize所以 teacher 看到的是「hint 原题」这一串而不是原题本身。这一点直接决定后面几件事第一logprob_start_len不能拿学生样本的 prompt 长度来算因为 teacher 的 prompt 被加长过第二如果你要拿这份 log-probs 去和学生的分布做对齐得先确认双方 token 序列是否落在同一个坐标系里否则 teacher 那边多出来的 hint 段会整体错位第三max_new_tokens0意味着不进入 decode 循环hint 只作为前向上下文不会变成被生成的 token。让 Codex 帮你读这一段的最实用问法是让它把「原始样本 → hint 拼接 → tokenize → 送入模型」这条链上每次变化的长度列出来而不是让它泛泛解释函数功能。你可以直接把文件路径和函数名丢给它要求按行号回答案这样方便你回到编辑器里核对。2.2max_new_tokens0是只取前向的开关max_new_tokens0是个很容易被忽略但很关键的参数。它告诉推理封装只跑 prefill别生成任何新 token直接拿最后一次前向的 logits。teacher log-probs 本来就是给定前面的 token预测下一个 token 的概率不需要真的生成所以这个开关是省算力的必要设置。理解这一点之后_compute_teacher_log_probs的输出形状就顺了它返回的是一串每个位置对目标 token 的 log 概率长度与有效 token 数挂钩而不是和生成长度挂钩。如果你在调试时发现返回值长度对不上输入序列先检查是不是这里被误设成了非 0或者是不是 pad/trim 阶段把某一段吃掉了。让 Codex 帮你把这两个阶段的长度变化对照着列出来通常一两轮就能定位。3._compute_teacher_topk_logprobs的logprob_start_len与 BOS 偏移修正3.1logprob_start_len为什么不是从 0 开始_compute_teacher_topk_logprobs和上一个函数最大的区别是它不只返回目标 token 那一个 log-prob而是每个位置返回 top-K 个候选 token 及其 log-prob。既然要算 top-K就涉及从哪个位置开始算。logprob_start_len就是那个起点。它不从 0 开始的常见原因有两个一个是 teacher 的 prompt 里含有 hint前一段本来就不需要参与对齐另一个是序列开头可能有特殊 token从第一个有效 token 之后开始取更干净。具体取哪一段要看openclaw_opd_api_server.py里传参的调用点不能想当然。读源码笔记时最好让 Codex 把「logprob_start_len从哪来 → 用它切哪一段 → 返回数组对应输入的第几位」这三步连起来说一遍比单看函数定义清楚得多。3.2 BOS 偏移修正把谁挪了一位BOS 偏移修正是这一节最容易读晕的地方。很多 tokenizer 在 encode 时要么自动补 BOS要么在拼接序列时把 BOS 放在不同位置结果就是 logits 的位置和 token id 的位置差了一位。写代码的人得在取 log-prob 时把索引整体挪一格才能让第 i 个 log-prob准确对应第 i 个 token。要验证这个修正是否做对了一个实用的办法是拿一小段纯文本进 teacher 分支打印 token id、logits 索引、最终 log-prob 序列三者看它们是否一一对齐。让 Codex 帮你读这段时可以直接要求它指出哪些行做了1、-1或切片偏移而不是让它笼统说修正了偏移。这类修正一旦漏掉下游蒸馏损失会整体错位一格但训练曲线短期内未必看得出来排查会非常痛苦。4. pad/trim 与 top-K 返回teacher logprobs 如何落进 Sample4.1 pad 和 trim 对齐不同长度Teacher 拿到的样本长度各不相同批处理时必须要 pad但 pad 出来的位置没有真实含义算完 log-probs 之后要 trim 掉。pad 和 trim 如果不配对就会出现两种情况要么 log-probs 里混进了一段 padding 位置的值要么有效位置被截掉一截。两者都会让 teacher 侧和 student 侧的序列长度不一致做 KL 或蒸馏损失时直接报形状错误。读源码时可以重点看两个地方一是 pad 用的 token id 是不是真正的 pad id二是 trim 是按 attention mask 还是按记录的有效长度切的。前者影响数值正确性后者影响索引对齐。让 Codex 把这两个变量在文件里的流转路径列出来经常能发现某一处以为自己在按 mask 切其实传的是固定长度这类小 bug。4.2teacher_log_probs与teacher_topk_log_probs往哪写这两个字段最终会落到 Sample 对象上teacher_log_probs通常保存每个有效位置对目标 token 的 log-probteacher_topk_log_probs保存每个位置 top-K 个候选的 id 和对应 log-prob。它们各自的形状和 dtype 都不是随手定的下游蒸馏损失直接靠它们算加权或截断字段名和形状错了损失要么算不出要么算出来是垃圾。用 Codex 读这一段的正确姿势是让它先列出这两个字段在哪个函数里被写入、写入时用的 key 名是什么、形状是什么再让它把对应位置的取值逻辑复述一遍。多轮追问之后你手里就有了一份能直接对照的字段表比你反复在文件里搜索变量名省时间。需要强调的是teacher log-probs 的计算本身依然在 OpenClaw-RL 的进程里跑Codex 只是帮你读代码和解释字段不会替你执行任何前向。5. Codex 多轮读源码时的验证与排障5.1 先用一条小请求验证通道配置改完先别急着把整个 repo 丢给 Codex。用一条最小请求确认通道通不通在 Codex 里让它只回答一个简单问题看是否能正常返回。如果返回正常说明base_url https://taotoken.net/api、Key、模型 ID 三者对得上可以开始读源码了。模型 ID 一定以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场当时列表为准不要凭印象写。切换模型时只改config.toml里model那一行即可model_provider和base_url不用动。这样做的另一个好处是出问题时你能分清是模型 ID 写错还是通道配置错。5.2 读源码时常见的几类报错第一类是认证相关Key 没导出、导出到了错的 shell、或者env_key写的变量名和实际 export 的不一致。排查方式是打印环境变量名确认拼写而不是反复换 Key。第二类是路径相关base_url被顺手加了/v1或者被误加了官网那串 UTM 参数。接口地址就该是https://taotoken.net/api末尾不加/v1也不挂任何查询串。第三类是上下文超限openclaw_opd_api_server.py加上周边文件一次读太多超出模型上下文。解决办法是让 Codex 先只看单个函数读完再追加下一段而不是一次塞整个目录。读 Teacher 链路时更是如此_compute_teacher_log_probs和_compute_teacher_topk_logprobs分开问答案会清楚很多。第四类是模型自己在猜Codex 有时会补全源码里没有的行。读到关键索引逻辑时要求它给出原文行号对照不上就重新追问。这是读第三方源码时最值得养成的习惯。6. 收尾把这次的 Key 和调用对上账Teacher 这条链路理清之后建议顺手去 TaoToken 模型对话 用同一把 Key 发一条消息确认模型 ID 和 Base URL 没被后来的编辑改错。长期在 Codex 里读源码、写脚本的话可以打开 Coding Plan 看套餐是否够用新的 Key 在 控制台 API Keys 里创建Codex 侧环境变量的对照写法见 Claude Code 接入文档 两边思路是一样的只是变量名不同。回到 OpenClaw-RLteacher_log_probs和teacher_topk_log_probs落进 Sample 之后整条蒸馏损失才真正连起来。下一步可以顺着 teacher 字段往下读损失那一层看它是怎么把 teacher 分布和学生分布对齐的读的时候仍然把字段名、形状、索引偏移三样东西一起记比单独记函数名有用得多。Codex 能帮你把这本账对齐但它不会替你计算任何一步 teacher 前向这一点在调试时心里要有数。