跳转至

Codex Model Speed Benchmark

导言

Codex 的 Fast 模式究竟快多少?Sol 与 Luna 谁输出更快?我在同一台 Mac、同一账号和同一时间窗口内,串行运行了 36 次真实请求:24 次约 10K 用户输入差分,12 次至少 10K 文本 token 的流式输出。结果很明确:Fast 将持续输出吞吐提高到约 1.50–1.53 倍,Sol 的流式速度只比 Luna 高 3.4%–5.6%;但输入处理速度被连接重试与缓存噪声淹没,本轮无法识别。

先给结论

本文所有速度数字都来自 2026 年 8 月 12 日的一次本地实测,推理档位固定为 medium,每个 模型 × 服务层 组合重复 3 次。官方资料只用于确认模型定位和 streaming 支持,不提供或背书本文的速度数字。

模型 Standard 流式 P50 Fast 流式 P50 Fast/Standard 同 tier 模型差异
GPT-5.6 Sol 57.40 tok/s 88.02 tok/s 1.53× 比 Luna 快 3.4%–5.6%
GPT-5.6 Luna 55.52 tok/s 83.36 tok/s 1.50× 比 Sol 慢 3.3%–5.3%

服务层差异远大于模型差异。如果任务需要连续生成数千到数万 token,Fast 是本轮最稳定、最显著的速度变量;如果任务更看重能力或成本,单凭这张速度表仍不足以选模型。OpenAI 将 Sol 定位为复杂专业工作的旗舰模型,将 Luna 定位为成本敏感、高吞吐工作负载的模型,但本文没有评测答案质量,也没有把 API 标价换算成 Codex 订阅用量

不要给输入速度编一个数字

10K 输入差分的四组长减短响应时间都小于或等于零,无法形成有效的正 prefill 代理值。看见长输入反而更快,不应得出“输入越长越快”,而应先承认传输重试、排队与缓存差异已经压过输入长度效应。

测试口径

为什么拆开测

一次 Codex 请求的端到端时间可以写成:

T_e2e = T_client + T_queue + T_prefill + T_reasoning + T_stream + T_finish

这里的 T_e2e 是从发起 turn 到完成的总时间;T_queue 是排队或调度等待;T_prefill 是输入处理;T_reasoning 是首段可见文本之前的隐藏推理;T_stream 是可见文本持续到达的阶段。客户端只能直接观察其中若干边界,不能从总秒数反推出服务端每一项的真实耗时

长输出测试使用 Codex app-server 的 item/agentMessage/delta 事件。最终消息文本使用 o200k_base 独立计数,边界修正流式吞吐定义为:

R_stream = (T_text - T_first_delta) / (t_last_delta - t_first_delta)

T_text 是最终消息文本 token 数,T_first_delta 是第一个 delta 已经携带的 token 数。扣掉首 delta 是因为它在计时起点到达时已经生成。这个指标排除了首 token 前等待,但仍会受网络、服务端调度、批处理和流式节流影响,因而应称为客户端可观测流式文本吞吐,而不是服务端纯解码算力。

小黑把总耗时拆成首段等待和流式速度

自绘小黑示意图:同一请求至少要分开看首段等待与持续输出;一个总秒表无法解释慢在哪里。

图中应重点看两个测量边界:左侧红色秒表停在首个文本 delta 到来之前,右侧橙色标尺只观察文本连续输出。TTFT(Time To First Token,首 token 延迟)与流式 tok/s 可以朝相反方向变化,所以 Fast 的持续输出更快,不自动等于每个请求的总等待都缩短相同比例。

如何保证可比

测试固定了以下条件:

  • 同一客户端codex-cli 0.147.0-alpha.6.5
  • 同一模型档位:Sol 与 Luna 都使用 medium reasoning effort。
  • 同一服务层定义:Standard 不设置 service_tier;Fast 显式设置 service_tier="priority"
  • 同一输出对象:结构化 JSON 中固定 909 行,每行 10 个 speed,目标至少 10,000 个最终消息文本 token。
  • 同一执行方式:隔离临时工作目录、只读沙箱、禁止工具调用、串行运行,避免并发争抢速率额度。
  • 同一统计方式:每组 3 次,报告 P50 和 P90,不以单次最快样本排名;失败与异常样本不删除。

10K 输出的单次命令形态如下,四组只改变 modeltier

PYTHONPATH=/tmp/codex-speed-benchmark-deps python3 codex_stream_benchmark.py \
  --model gpt-5.6-sol \
  --effort medium \
  --tier fast \
  --min-text-tokens 10000 \
  --rows 909 \
  --words-per-row 10 \
  --confirm-usage \
  --output-dir /absolute/path/to/result

每次实际输出为 10,003 或 10,093 个文本 token;12/12 次达到目标,12/12 次最终消息与 delta 拼接完全一致,输出测试缓存比例为 0%。因此,流式吞吐结果可以在本轮范围内直接比较。

10K 输出结果

流式吞吐

Codex Sol 与 Luna 在 Standard 和 Fast 下的 10K 输出吞吐与端到端时间

实测数据图:左侧柱为边界修正流式吞吐 P50,空心点是三次原始样本;右侧点为端到端 P50,横线延伸至 P90,三角标记 TTFT P50。数据来自 12 次本地 Codex app-server 请求。
模型 模式 流式 tok/s P50 流式 tok/s P90 三次样本范围
Sol Standard 57.40 60.08 56.56–60.74
Sol Fast 88.02 88.45 86.12–88.56
Luna Standard 55.52 55.56 54.77–55.57
Luna Fast 83.36 83.36 83.26–83.36

Fast 的效果在三次长输出里相当稳定:Sol 的 P50 从 57.40 提升到 88.02 tok/s,Luna 从 55.52 提升到 83.36 tok/s。相反,模型间差异较小:Standard 下 Sol/Luna 为 1.034×,Fast 下为 1.056×。如果只讨论长文本持续输出,优先级应是先决定是否使用 Fast,再讨论 Sol 与 Luna 的几百分点差异。

P90 的方向

吞吐的 P90 是较高分位,不代表“慢尾部”;时间的 P90 才表示较长等待。本文同时给出原始范围,避免把同名分位数机械地解释成同一种风险。

端到端等待

端到端时间的结论没有流式吞吐那么整齐:

模型 模式 端到端 P50 端到端 P90 TTFT P50 Standard/Fast 端到端加速
Sol Standard 307.09 s 308.11 s 132.37 s
Sol Fast 193.96 s 231.76 s 80.42 s 1.58×
Luna Standard 254.68 s 294.99 s 74.30 s
Luna Fast 239.57 s 244.44 s 119.22 s 1.06×

Sol 的 Fast 同时改善 TTFT 与持续输出,所以端到端 P50 从 307.09 秒降到 193.96 秒。Luna Fast 虽然持续输出快了约 50%,TTFT P50 却从 74.30 秒升到 119.22 秒,最终总耗时只改善约 6%。这说明本轮 Luna 的首段等待主要受请求级条件影响,不能拿一次端到端总秒数代替模型解码速度

原始事件还记录了多轮 request timed out、stream reconnect,以及从 WebSocket 回退到 HTTPS。日志时间位置表明,这些传输层重试很可能解释了一部分 30–125 秒 TTFT 波动;但客户端看不到服务端内部状态,所以这只是有证据支持的推断,而不是已证明的唯一原因。

10K 输入为何失效

输入测试用短提示与长提示的响应时间差估计处理速度。短提示本地计数约 397 token,长提示约 10,000 token;服务端 usage 还包含固定系统上下文,所以模型实际 usage 输入更大,但每组长短差都保持 9,603 token。

理论代理公式是:

R_prefill_proxy = (input_long - input_short) / (response_long - response_short)

只有当长输入稳定地比短输入多花时间时,分母才有可解释意义。本轮结果如下:

模型 模式 短输入 usage P50 长输入 usage P50 输入差 短输入 P50 长输入 P50 结论
Sol Standard 21,954 31,557 9,603 118.63 s 29.18 s 不可识别
Sol Fast 21,954 31,557 9,603 119.07 s 27.33 s 不可识别
Luna Standard 17,993 27,596 9,603 28.50 s 27.28 s 不可识别
Luna Fast 17,993 27,596 9,603 118.65 s 118.39 s 不可识别

所有分母都非正数。造成失效的可观测因素至少有三类:

  1. 连接重试:24/24 个输入样本的客户端错误文本都记录了 WebSocket 超时并回退到 HTTPS,响应集中在约 25–31 秒或 117–119 秒两个台阶。
  2. 缓存不一致:输入测试总体缓存比例为 17.6%,不同样本的 cached input tokens 不同,无法把差值视为同一种输入工作量。
  3. 固定上下文较大:约 397 token 的用户短提示对应 Luna 17,993、Sol 21,954 个 usage 输入 token,用户输入差只占完整请求的一部分。

错误的公开方式

如果直接计算一个负的 tok/s,再把绝对值当作“输入速度”,数字看似完整,物理意义却已经消失。公开 benchmark 的价值不只是给出排名,也包括明确记录哪些问题这轮没有测出来

如何复测输入

下一轮应把输入 prefill 单独作为实验,而不是继续增加同样噪声下的重复次数:

  1. 先修传输路径:确保 WebSocket 或 HTTPS 只走一种稳定路径,消除固定超时回退台阶。
  2. 控制缓存状态:分别设计全未缓存与可控前缀缓存测试,不把两种样本混在同一组。
  3. 采用配对交错:每组按 short → long → long → short 的 ABBA 顺序执行,用相邻配对减弱时间漂移。
  4. 扩大输入梯度:至少使用 1K、10K、50K 三个输入点,检查延迟是否随 uncached input tokens 单调变化;只有拟合残差可接受时才报告斜率。
for each model × tier:
    verify_transport_is_stable()
    for block in randomized_ABBA_blocks:
        short_1 = run(prompt_1k, cache_policy="controlled")
        long_1  = run(prompt_10k, cache_policy="controlled")
        long_2  = run(prompt_10k, cache_policy="controlled")
        short_2 = run(prompt_1k, cache_policy="controlled")
        keep_raw_events(short_1, long_1, long_2, short_2)
    fit(response_time ~ uncached_input_tokens)
    reject_result_if_slope_is_nonpositive_or_residuals_are_large()

这段伪代码的重点是预先定义拒绝条件:如果传输仍重试、缓存不可控、斜率非正或残差过大,就继续报告“未识别”,不从异常值里挑一个有利结果。

选择建议

  • 长文本生成优先:在本轮条件下,Fast 的流式收益约 50%,明显大于 Sol/Luna 的 3%–6% 差异。
  • 能力优先:先按官方定位和真实任务质量选择 Sol/Luna,再把速度作为次级指标;本文没有质量 eval,不能用 tok/s 代替正确率。
  • 成本优先:Luna 是官方面向成本敏感、高吞吐场景的选择;但 Codex 产品用量与公开 API 美元价格不是同一个计费合同,本文不做虚假换算。
  • 交互延迟优先:重点复测 TTFT 和传输稳定性。Fast 提高持续输出速度,不保证连接建立、排队和首段等待同步改善。

最终可以把这次结果概括为一句话:Fast 稳定提高了 10K 持续输出吞吐,Sol 只比 Luna 略快;端到端体验仍被 TTFT 与连接重试主导,而 10K 输入速度需要在更稳定的传输与缓存控制下重测。

参考资料

评论