昨天那篇 自回归不是物理定律 算的是一笔理论账:扩散语言模型省下的解码步数,是从哪里借的。
今天我把账拿到真实工作负载上验。做法是把 Claude Code 的模型后端整个换掉——它还是那个 harness,Bash/Read/Edit 工具一个没变,但每一次 LLM 推理都打到 Inception 的 Mercury 2(一个在售的扩散语言模型)上。然后跑 9 个带确定性校验的编码任务,重复到 28 次运行。
28 次全过。而这不是重点。 重点是账单:最后一轮完整跑下来,输出 token 只占总成本的 4.2%。扩散模型花大力气优化的那一侧,在 agent 场景里根本不是成本和延迟的主项——它的核心卖点,在这里没有花出去的地方。
名词速查
| 术语 | 一句话解释 |
|---|---|
| dLLM(扩散语言模型) | 不按从左到右逐个吐字,而是并行地把一整段文字从”模糊”改到”清晰”的语言模型,详见 扩散语言模型那篇 |
| harness | 包在模型外面那层代码:给它工具、喂它上下文、接住它的输出。同一个模型换个 harness 性能能差几倍,见 什么才是好的 Harness |
| prefill / decode | 推理的两个阶段:一次性读完整段输入 vs 逐段生成输出。扩散优化的是后者 |
| Messages API | Anthropic 的对话接口格式,和 OpenAI 的 chat completions 格式不兼容 |
| SSE | Server-Sent Events,服务器通过一条长连接持续推送片段的协议,流式输出用它 |
| tool_use / tool_calls | 模型”我要调这个工具”的结构化输出。Anthropic 叫 tool_use 块,OpenAI 叫 tool_calls 数组 |
| reasoning token | 模型内部推演消耗、但不出现在最终答案里的 token。要付钱,且要占 max_tokens 额度 |
为什么值得这么折腾
想知道一个模型”能不能干活”,最有信息量的测法不是问它题目,是把它塞进一个已经调好的生产级 harness 里,看它能不能在真工具、真文件系统、真多轮循环里把活干完。
Claude Code 恰好是个很硬的测试台:它的系统提示词长、工具多(这次实测每轮带 24 个工具定义)、循环深。一个模型在这里能跑完,说明的东西比刷榜多。
而它支持 ANTHROPIC_BASE_URL 指向自定义端点——理论上可以换后端。理论上。
第一道坎:协议不兼容
Claude Code 说 Anthropic Messages API,Inception 说 OpenAI chat completions。本机没装任何现成的翻译层(litellm、claude-code-router 都没有),所以我写了一个约 500 行的 Python 标准库代理。
翻译的主体部分是机械的,值得写下来的是三个把我卡住的坑——它们都不在文档里。
坑 1:SSE 流没有分帧,客户端干等 600 秒
第一次跑最小任务,verify 显示文件已正确创建,但 claude 进程又挂了整整 600 秒,最后报 API Error: The operation timed out。
根因是 HTTP 层:我用 HTTP/1.1 返回 SSE,但既没有 Content-Length,也没做 chunked 分帧。
# 伪代码,对应代理里的流式响应分支
# 错的:客户端读完最后一个 SSE 事件后,不知道响应结束了,继续等
send_header("Connection", "keep-alive")
# 对的:SSE 无法预知长度、又不分帧时,必须用"关闭连接"来界定响应边界
self.close_connection = True
send_header("Connection", "close")
# ... 推完所有事件后主动 flush 并让连接关闭
可迁移的教训:HTTP/1.1 下响应边界只有三种界定方式——Content-Length、chunked 分帧、关闭连接。流式接口用不了第一种,那就必须明确选后两种之一,不能什么都不选。症状会伪装成”上游模型很慢”,而实际是你自己的传输层没收尾。
坑 2:max_tokens 被内部推理吃光,返回空内容
最早的连通性测试我给了 max_tokens: 20,结果拿回:
{"choices":[{"message":{"content":null,"tool_calls":[]},"finish_reason":"length"}],
"usage":{"completion_tokens":0,"reasoning_tokens":19}}
19 个 token 全花在内部推理上,一个字都没轮到输出。reasoning token 和输出 token 抢的是同一个 max_tokens 额度。 我在代理里加了 4096 的下限。
这个坑对任何”带内部推演的模型 + 老的 max_tokens 直觉”都成立:一个在非推理模型上够用的额度,换到推理模型上会变成静默的空响应——finish_reason 是 length 而不是报错,很容易被当成模型抽风。
坑 3:流式响应默认不回 usage,成本失明
第一轮 226 次调用跑完,我去统计成本,发现 token 数全是 0——Inception 的流式响应里根本没有 usage 块。非流式有,流式没有。
解法是 OpenAI 的标准可选参数,加上就有了:
{"stream": true, "stream_options": {"include_usage": true}}
顺带还多了个 server_timing.server_latency_ms,让我能把服务端延迟和网络往返分开看。
教训:自建 LLM 代理时,成本可观测性不是自动来的。流式路径要单独确认 usage 有没有被上报,否则你会在完全不知道花了多少钱的情况下跑完整轮评测——我就是。
第二道坎:怎么设计一个不骗自己的评测
模型自己写实现、又自己跑测试判断”我做完了”,这就是 独立验证真空 那篇讲的问题:写与测同源时,测试从独立证据退化成自我确认。
所以每个任务我都加了 harness 之外的、模型看不到的校验:
- 确定性
verify.sh:跑在 agent 结束之后,不由 agent 触发。 - 防篡改检查:
grep确认它没改测试文件。 - 通用性检查:用测试里没出现过的输入独立验证,防止它硬编码测试值。
- 隐藏测试套件(L8 专用):可见的只有 3 个 smoke test,判分用的 37 个用例放在工作区之外,agent 全程看不到。而且我先用一份参考实现跑过隐藏套件确认全绿——评分标准本身也要被验证,否则你测的是自己写错的题。
九个任务按难度递增:
| 编号 | 任务 | 考什么 |
|---|---|---|
| L0 | 写一个内容精确的文件 | 链路连通性 |
| L1 | 按 7 个测试实现 3 个函数 | 单文件编码 |
| L2 | 跨文件找出折扣分档的边界 bug | 定位非本地的根因 |
| L3 | 加 --json 参数,且不许改受保护文件、要更新 changelog | 多约束指令遵循 |
| L4 | 两个互相耦合的 bug(可变默认参数 + 缓存未失效) | 会不会修一个就交卷 |
| L5 | 跟随现有代码库约定新增一个配置项(9 个测试) | 多文件导航 + 模仿既有模式 |
| L6 | 重构去重,但禁止使用正则 | 反直觉约束下的重构 |
| L7 | 12 个文件里长程改名,且必须放过一个同名的线协议字符串常量 | 长程一致性 + 陷阱识别 |
| L8 | 按规格实现解析函数,用隐藏测试判分 | 抗”拟合可见测试” |
L4–L8 各重复跑 3 次,L0–L3 各 1 次,加上最后一轮 9 个任务的完整重跑,共 28 次计入统计的运行(协议代理修好之前的调试运行不计入)。
结果:28/28
全部通过。这是我亲手跑出的数字,校验脚本和运行记录都在本地。
| 任务 | 通过 | 轮次范围 | 耗时范围 |
|---|---|---|---|
| L0 | 2/2 | 2 | 3.0–5.2s |
| L1 | 2/2 | 8–9 | 14.4–14.8s |
| L2 | 2/2 | 8–10 | 11.6–24.9s |
| L3 | 2/2 | 7–11 | 13.8–27.6s |
| L4 | 4/4 | 6–8 | 10.0–20.6s |
| L5 | 4/4 | 14–15 | 25.1–33.9s |
| L6 | 4/4 | 7–13 | 14.2–27.9s |
| L7 | 4/4 | 23–40 | 52.5–93.5s |
| L8 | 4/4 | 4–10 | 10.9–28.8s |
代码质量不是勉强及格。L8 那份实现连”数字和单位之间不许有空格”这条藏在规格里的隐含规则都处理对了——它是靠”解析数字时遇到空格就停下,此时下一个字符必须是单位”这个结构自然满足的,不是特判出来的。L6 在禁用正则的前提下老实写了字符级循环,并把公共逻辑抽成了带 fallback 参数的私有函数。
先接住一个合理的怀疑:28/28 说明我的题目没有触到它的上限,不说明它没有上限。这套评测测出的是”能不能在生产级 harness 里稳定干完中等难度的活”,答案是能;它测不出”和前沿模型比差多少”。想测后者需要难得多的题目,那是另一篇的工作。
真正的发现:这笔账里,输出侧只占 4.2%
拿到 usage 之后,最后一轮 9 个任务的完整开销是这样(一手实测,官方定价来自 API 的 /v1/models 响应:输入 0.75/M、缓存读 $0.025/M):
| 项 | 数值 |
|---|---|
| LLM 调用次数 | 107 |
| 累计输入 token | 1,593,388(其中命中缓存 302,391,19%) |
| 累计输出 token | 19,329(其中 reasoning 11,903,占 61.6%) |
| 单次调用平均 | 输入 14,891 tok,输出 181 tok |
| 输入 : 输出 | 82.4 : 1 |
| 总成本 | 0.0383) |
| 输出侧占总成本 | 4.2% |
| 服务端延迟 | p50 801ms / p90 2821ms / max 7038ms |
这个 82:1 是这篇文章的核心。它是结构性的,不是我这套任务的偶然:agent 每轮都要重发整段对话历史、24 个工具定义、长系统提示词,而模型一轮通常只回一个工具调用——一百多个 token。轮次越多,这个比例越极端。
于是扩散模型的价值主张在这里遇到了尴尬:
- 成本上:它优化的输出侧只占 4.2%。就算解码免费,账单也只降 4.2%。
- 延迟上:181 个输出 token,即使解码时间归零,能砍掉的也只是延迟里对应这 181 个 token 的那一小段。p50 的 801ms 主要花在读那 14,891 个输入 token(prefill)和网络往返上。
- 而 prefill 本来就是并行的——自回归模型读输入时也是一次算完整段,扩散在这一侧没有结构性优势。
昨天那篇算出扩散省下的步数是”从质量预算里借的”。今天这笔账补上另一半:在 agent 负载下,它省下的那个东西本身就不值钱。 输出快 5–10 倍(这是 Inception 在自家模型描述里的说法,我没有独立复现速度对比)作用于一个只占 4.2% 成本的项。
这里必须说清一个我的口子:我的代理把 Claude Code 发来的 prompt cache 断点全丢掉了,只吃到了 Inception 自己的自动缓存,命中率 19%。如果把缓存做好,输入侧成本会明显下降,4.2% 这个比例会相应上升。所以 4.2% 应当读作”输出侧占比”的下限估计。但要注意:token 数量比 82:1 不受缓存影响——缓存改变的是价格,不是你得往模型嘴里塞多少东西。延迟和轮次的结论也一样成立。
失败模式:两个值得记下来的
一、工具名幻觉,4/98 ≈ 4.1%,且全部集中在最长的任务里。
L7(12 文件长程改名)中,它连续做了三次越界调用:
Search({"query": "fetch_user", "path": "", "max_results": 20})
WebSearch({"query": "fetch_user python function"})
web_search({"query": "fetch_user python function"})
Search 和 web_search 不是 Claude Code 的工具名(对应的真名是 Grep 和 WebSearch)——它编了工具。更有意思的是中间那次:它想上网搜一个只存在于本地代码库里的函数名。这不是格式错误,是把”我需要找东西”错误地映射成了”我需要上网”。
这三次都发生在同一个任务、上下文最长的时候。我的判断(观点,非实测因果):这是长上下文下工具选择退化的表现,而不是随机抽风——因为在短任务里 0 次发生。要确认这一点需要更多样本。
harness 在这里救了它:不存在的工具名被拒绝,它换回 Grep 继续干活,最终照样通过。这正好是 判断归模型、物理归代码 那篇的意思——harness 里的约束是物理法则,模型犯错也撞不穿。
二、长程任务的轮次方差极大。
L7 四次运行的轮次是 23 / 28 / 30 / 40。最后那次吃满了我设的 40 轮上限,耗时 93.5 秒(是最快那次的 1.8 倍),才刚好在上限前完成。
这意味着:给这类模型做长程任务时,轮次上限是个危险参数。 短任务 2–10 轮很稳,长任务的尾部会甩得很远。如果我把上限设成 30,那次运行就会被截断而失败——同一个模型、同一道题,成功率取决于我拍的那个数。
另外,第一轮 226 次调用里有 3 次网络层瞬时失败(1.3%),被 Claude Code 自己的重试吸收了,没有影响任何任务结果。
那什么时候该用扩散模型
不是”不该用”,是用在输出侧真的占大头的地方:
- 值得:长文生成、批量翻译或改写、代码补全(输入短、输出长、延迟敏感),以及任何输入输出比接近 1:1 或反过来的负载。
- 不值得(按这次实测):多轮 agent 循环。输入主导,扩散优化的那一侧占比太小。
- 需要另测才知道:输入很长但输出也长的场景,比如”读一大段代码然后重写整个文件”。我这套任务里模型多数走的是
Edit(小改),没怎么走整文件重写。
一个更一般的提炼(这是我的观察,不是定律):评估一个模型优化方向对你有多大价值,先量一下你自己负载的输入输出比。 这个比值决定了厂商宣传的那个倍数会被乘上多小的一个系数。我在动手之前完全没想到它是 82:1。
诚实的提醒
- 亲手测出的:28/28 通过率、各任务轮次与耗时、107 次调用的 token 与成本、p50/p90/max 服务端延迟、4.1% 越界工具调用、1.3% 网络失败率。都来自本地运行记录。
- 来自厂商自述、我没有独立复现的:“比 Claude 3.5 Haiku、GPT-4o Mini 快 5–10 倍""Copilot Arena 速度第一、质量并列第二”——这两句来自 Inception 在
/v1/models响应里给 Mercury 2 写的模型描述,我没做速度横评。 - 我的观点,非实测因果:工具名幻觉集中在长上下文是”长程退化”而非随机。
- 明确的局限:只测了一个模型(
mercury-coder系列在这个账号上已不可用);任务是我自己出的,没有触到能力上限;没有和自回归模型做同 harness 对照——那才是最该补的实验。
成本最低的亲手验证实验(约半小时):不用写代理。直接对你平时用的 agent 工具抓一次真实会话,把每次请求的 prompt_tokens 和 completion_tokens 加起来,算出你自己的输入输出比。然后拿这个比值去重新读任何一份”输出速度提升 N 倍”的宣传——你会得到一个和宣传数字很不一样的、属于你自己负载的预期收益。
参考来源
工程实践
- Inception Labs API(
/v1/models与/v1/chat/completions,含 Mercury 2 的定价、上下文长度与厂商自述性能描述) - Anthropic Messages API 与 OpenAI chat completions 的流式事件格式差异(本次翻译代理的实现依据)
- Claude Code 的
ANTHROPIC_BASE_URL/CLAUDE_CONFIG_DIR自定义端点与配置隔离机制
站内相关
- 自回归不是物理定律:扩散语言模型买到了什么,亏在哪里 — 本篇的理论前作
- 扩散模型说人话 — 不熟悉扩散机制先读这篇
- 什么才是好的 Harness:判断归模型,物理归代码 — 为什么 harness 能兜住模型的错
- Agent 写码、Agent 测码:独立验证真空 — 本次评测设计隐藏测试的理由