
1. best-of-8 跑完token 涨了 6.9 倍Elo-per-token 在量化什么在 TaoToken官网入口带 UTMhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_intro 拿到 Key、把客户端base_url指向https://taotoken.net/api之后我做的第一件事不是调模型参数而是把手里那批 Agent 评测任务的采样数从 best-of-1 一路调到 best-of-8。结果很典型同一批任务通过率从 41% 爬到 55%但runs/*.jsonl里的usage.total_tokens涨了将近 6.9 倍。收益是真的边际收益掉得也是真的——这正是 Elo-per-token 这类分析想量化的问题。我最近在重读一篇关于 LLM Agent 测试时算力策略的工作它的核心主张并不复杂如果你只报告「采样 n 次后准确率是多少」你没法判断这个提升是来自策略变强还是单纯来自你多烧了 token。于是它引入 Bradley-Terry 模型把任务内的两两排序谁比谁好聚合成一个跨任务的 Elo 评分再把 Elo 除以消耗的 token得到一个带成本的强度指标。曲线一画出来收益递减就藏不住了Elo 随算力增长的斜率在某个 n 之后迅速压平而 token 消耗仍然近似线性。对于做 Agent 评测和推理成本工程的我们这篇论文的价值不在结论本身而在它给了一套可复现的记账方式。它把「把预算花在哪一档采样策略上」变成了一个可测量的工程决策而不是拍脑袋。本文不复述论文而是把它翻译成一条能在本地跑通的链路用 TaoToken 统一模型出口 → 采集每次采样的 usage → 用 Bradley-Terry 拟合 Elo → 用 token 归一化 → 画收益递减对照表。全程只需要一个 Key 和几段可复制的配置。2. Bradley-Terry 到跨任务 Elo聚合链路里最容易踩的三个坑Bradley-Terry 的基本假设很朴素每个「选手」有一个正强度参数 γᵢi 战胜 j 的概率是 γᵢ / (γᵢ γⱼ)。把 γ 取对数再线性缩放到 400 的刻度上就得到了我们熟悉的 Elo 形式。这里的「选手」不是模型而是同一任务的同一条轨迹trajectory。链路大概是这样四步任务内排序对同一个任务让评判器规则校验器、测试用例、或另一个模型对你采样的 n 条轨迹排序展开成两两比较对。拟合强度对每个任务单独或全局拟合 BT 的 γ 参数。聚合跨任务所有任务的两两比较对汇入同一个 BT 拟合得到一套全局强度。工程上常给任务加一个偏移项避免某个任务因为题面简单而把所有轨迹都推到高分。除以 token把 Elo 增量除以该策略的 token 消耗得到 Elo-per-token。三个坑必须提前说清楚。坑一比较图不连通。如果你只在 best-of-1 和 best-of-8 之间做比较而这两档之间没有共享轨迹BT 的参数会漂移Elo 只具有相对意义。稳妥做法是让不同 arm 共用一部分任务与评判器保证比较图连成一个整体。坑二token 尺度不一致。有的任务一次采样只花 800 token有的花 4 万。直接把总 token 当分母会让长任务主导整条曲线。常见修正是在任务维度先做归一化per-task token 均值再跨任务求平均或者使用「加权 token」而不是原始总量。坑三胜率饱和。当某个 arm 在弱任务上全胜、在强任务上全败时BT 的似然会在参数发散方向拉扯。工程上给每个选手的胜场加 0.5 的平滑项相当于一个弱先验迭代就稳定了。下面的拟合脚本就是这么处理的。3. 接入准备在 TaoToken 拿 Key把 base_url 指对不管你是用 OpenAI SDK、Claude Code 还是 Codex评测链路的第一步都是同一个动作把模型出口统一到一个可记账的地址。到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_key 注册后在控制台创建一个 API Key然后写入本地环境变量。# ~/.zshrc 或 ~/.bashrc export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASEhttps://taotoken.net/api这里的TAOTOKEN_BASE就是所有客户端要填的 base URLhttps://taotoken.net/api。注意它不带/v1后缀OpenAI SDK 会自动拼接请求路径。所有示例统一用YOUR_API_KEY占位不要把真实 Key 提交到仓库。Python 侧的自检只需要三行# smoke_test.py import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ.get(TAOTOKEN_BASE, https://taotoken.net/api), ) resp client.chat.completions.create( modelgpt-4o-mini, # 换成你账号里可用的模型 ID messages[{role: user, content: reply with the single word: pong}], max_tokens8, ) print(resp.choices[0].message.content, resp.usage)关键在最后那个resp.usage。评测记账的全部原材料都在这里prompt_tokens、completion_tokens、total_tokens。后续 Elo-per-token 的分母就来自它所以从第一行代码起就要把它落盘而不是只打印通过率。4. Claude Codesettings.json 与 ANTHROPIC_* 的正确写法Claude Code 读取ANTHROPIC_*系列环境变量来定位供应商。把这三项落到配置里就能让它走 TaoToken 出口。用户级配置放在~/.claude/settings.json项目级可以放在仓库的.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: claude-sonnet-4-5, ANTHROPIC_SMALL_FAST_MODEL: claude-haiku-4-5 }, permissions: { allow: [] } }几个容易出错的点ANTHROPIC_BASE_URL只填到https://taotoken.net/api多写一层路径会导致 404。认证用的是ANTHROPIC_AUTH_TOKEN而不是ANTHROPIC_API_KEY两者在某些版本上行为不同如果你发现请求头没带上优先检查这一项。想临时切换模型做 Elo 对照实验直接在启动时覆盖变量即可ANTHROPIC_MODEL... claude。跑完一轮评测后会话内用内置的成本统计看一眼本次消耗再和runs/*.jsonl里累计的数字交叉核对能快速发现有没有请求走了别的出口、账没记全。5. Codexconfig.toml 里定义 provider别套 ANTHROPIC_*Codex 的配置体系是 TOML不是ANTHROPIC_*环境变量。把供应商写进~/.codex/config.tomlmodel gpt-5-codex model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat对应的环境变量只有一个export TAOTOKEN_API_KEYYOUR_API_KEYenv_key指向的是环境变量名本身而不是 Key 值这是最常见的配置错误。字段名在不同版本间略有差异如果启动时报 provider 解析失败先用本地codex --help确认当前版本的 provider 字段约定再回来对照修改。把wire_api与你的模型能力对齐否则可能出现在评测里返回空响应、usage 记成 0 的情况——那种数据一旦混进 Elo 拟合会直接污染分母。6. CC Switch 三件套Claude Code / Codex / Gemini CLI 各留一份 profile如果你同时在多个客户端之间切换手动改配置文件很容易把 Key 和 base URL 改串。用 CC Switch 这类多供应商切换工具把三套客户端的供应商信息各自维护成一份 profile切换时只改一处。客户端配置载体关键字段指向Claude Code~/.claude/settings.jsonANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKENhttps://taotoken.net/apiCodex~/.codex/config.toml[model_providers.*]下的base_url/env_keyhttps://taotoken.net/apiGemini 系 CLI各自的 env 或配置文件对应的 base URL / API Key 字段https://taotoken.net/api三件套的统一原则只有一条所有客户端的出口主机名必须完全一致且都在 usage 采集覆盖范围内。切换 profile 后跑一次 smoke test确认返回体里带 usage再开始正式评测。任何一次「漏记 token」的调用都会让 Elo-per-token 的分母偏小、曲线虚高。7. 把 Agent 采样接进来一次调用同时产出 Elo 原料和 token 账下面这段脚本是整条链路的核心。它对每个任务采样 n 条轨迹把文本和 usage 一起写入 JSONL。日后再做评判和 BT 拟合时不需要重跑模型。# agent_eval.py import json import os import time from pathlib import Path from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ.get(TAOTOKEN_BASE, https://taotoken.net/api), ) MODEL gpt-4o-mini ARMS [1, 4, 8] # 三档采样预算 OUT Path(runs/eval.jsonl) OUT.parent.mkdir(parentsTrue, exist_okTrue) def run_task(task_id: str, prompt: str, n: int, fh) - None: for k in range(n): t0 time.time() resp client.chat.completions.create( modelMODEL, messages[{role: user, content: prompt}], temperature0.7 if n 1 else 0.0, ) u resp.usage record { task_id: task_id, arm: fbest_of_{n}, sample: k, model: MODEL, latency_ms: int((time.time() - t0) * 1000), prompt_tokens: u.prompt_tokens, completion_tokens: u.completion_tokens, total_tokens: u.total_tokens, text: resp.choices[0].message.content, } fh.write(json.dumps(record, ensure_asciiFalse) \n) fh.flush() if __name__ __main__: tasks [(t-001, 修复这个函数的边界条件……), (t-002, 解释这段报错……)] with OUT.open(a, encodingutf-8) as fh: for tid, p in tasks: for n in ARMS: run_task(tid, p, n, fh)三个工程细节值得强调temperature在 n1 时置 0保证单次基线可复现fh.flush()保证任务中断时账不丢arm字段以采样档位命名后面聚合时不需要再做映射。8. token 统计命令从 JSONL 到每档采样的平均成本有了落盘日志统计就是一管道的事。按 arm 汇总总 token 和平均 tokenjq -r select(.total_tokens ! null) | [.arm, .total_tokens] | tsv runs/*.jsonl \ | awk {sum[$1]$2; cnt[$1]} END {for (a in sum) printf %-12s calls%-6d tokens%-10d avg%.0f\n, a, cnt[a], sum[a], sum[a]/cnt[a]} \ | sort如果要按任务维度看长尾先聚合任务再求均值避免少数超长任务主导分母jq -r [.task_id, .arm, .total_tokens] | tsv runs/*.jsonl \ | awk {k$1|$2; s[k]$3} END {for (k in s) {split(k,p,|); print p[2]\ts[k]}} \ | awk {sum[$1]$2; cnt[$1]} END {for (a in sum) printf %-12s task_avg_tokens%.0f\n, a, sum[a]/cnt[a]} \ | sort这两条命令的输出直接就是 Elo-per-token 对照表的成本列。建议每次评测前先把它们跑一遍做基线评测后再跑一遍差值就是这次实验的真实预算。9. Elo 评分与收益递减对照表怎么算、怎么读Elite 拟合用 MM 迭代实现几十行就够。核心更新式是 Rᵢ ← Wᵢ / Σⱼ nᵢⱼ /(Rᵢ Rⱼ)迭代后做几何均值归一化防止参数整体漂移。# bt_elo.py import json import math from collections import Counter, defaultdict def load_pairs(path): by_task defaultdict(list) with open(path, encodingutf-8) as f: for line in f: r json.loads(line) by_task[r[task_id]].append((r[winner], r[loser])) return by_task def fit_bt(pairs, iters500, eps1e-9): players sorted({p for w, l in pairs for p in (w, l)}) R {p: 1.0 for p in players} W, N Counter(), Counter() for w, l in pairs: W[w] 1 N[(w, l)] 1 W Counter({p: max(W[p], 0.5) for p in players}) # 平滑避免零胜场发散 for _ in range(iters): new {} for i in players: denom 0.0 for j in players: if i j: continue n_ij N[(i, j)] N[(j, i)] if n_ij: denom n_ij / (R[i] R[j]) new[i] W[i] / denom if denom eps else R[i] gm math.exp(sum(math.log(v) for v in new.values()) / len(new)) R {k: v / gm for k, v in new.items()} return R def to_elo(R, base1000.0): return {k: 400.0 * math.log10(v) base for k, v in R.items()}把拟合结果和 token 统计拼起来就得到了下面这张对照表的格式。表中数字是示例量级你需要用自己日志跑出来的真实值替换采样策略平均 token/任务任务内胜率聚合 EloElo / 1k token相对上一档的边际增益best_of_13,2000.4110020.313—best_of_412,6000.5010410.08339 Elo / 9.4k tokenbest_of_825,1000.5510530.04212 Elo / 12.5k token表格读法比数字本身重要第一看 Elo-per-token 的衰减速度而不是单看 Elo。从 1 档到 4 档Elo 涨了 39单位成本效率掉了将近四倍从 4 档到 8 档Elo 只涨 12token 却多花了 12.5k。这就是收益递减的量化形态——不是「变差了」而是「每单位算力买到的东西变少了」。第二看边际增益列的比值。当「每千 token 换来的 Elo」跌破你的验收阈值时那一档就不该进入默认配置只保留给少数关键任务。第三注意跨任务的一致性。如果某个 arm 的整体 Elo 涨了但在你的核心任务子集上没涨说明聚合被长尾任务拉高了。这时应该按任务族分组重跑 BT而不是直接采信全局数字。第四把 latency 一起看。采样数翻倍通常也意味着墙钟时间翻倍如果评测本身有交付窗口成本维度要加上时间而不是只有 token。10. 落到工程上一份可复用的评测预算清单把上面的链路收拢成一份可执行清单下次开新评测时按顺序过一遍在 TaoToken 创建 Key把TAOTOKEN_BASE固定为https://taotoken.net/api所有客户端逐一对齐。跑 smoke test确认返回体里带 usage 字段再开始正式采样。用agent_eval.py的形态落盘 JSONL任务 ID、arm、usage、latency 一个都不省。跑 token 统计两条命令拿到每 arm 的总量与按任务的均值。组织评判把排序展开成 winner/loser 对注意保持比较图连通。用 BT 拟合得到跨任务 Elo再除以 token 得到 Elo-per-token。对照表出来之后先做一个决定哪一档进入默认配置哪一档只在关键任务上开。把这次实验的 token 总量和 Elo 曲线归档下次评测前先看历史曲线避免重复买同一段已经压平的收益。如果你还没开始跑第一轮最省事的路径是这样先去 https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_chat 用对话页做一次最小验证确认 base URL 和 Key 都对然后看 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_coding_plan 选择合适的方案承载批量评测接着在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_api_keys 创建长期使用的 Key最后照着 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_claudecode_doc 把 Claude Code 的ANTHROPIC_*配置改完。更完整的入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentelo_per_token_footer 把 base URL 统一成https://taotoken.net/api之后Elo-per-token 这条曲线你就会越跑越熟——因为你花的每一分算力账上都有记录。