1. 先搞清楚:AI 回答慢,到底慢在哪一段
你问 AI 一个问题,它半天不吭声,和它开口之后一个字一个字往外蹦,这两种体验都叫“慢”,但背后的原因完全不是一回事。前者是模型还在读你的输入,后者是模型在逐 token 生成回答。在推理服务里,这两个阶段分别叫 Prefill 和 Decode。
Prefill 阶段,模型要把你发过去的全部内容读完——系统提示词、历史对话、检索到的文档、工具返回结果、整段代码或日志,全都算输入。它得先把这些内容处理完,生成 KV Cache,才能准备开口。这个阶段影响的是你多久能看到第一个字,对应的指标是 TTFT(Time To First Token,首字延迟)。
Decode 阶段,模型开始一个 token 一个 token 地生成回答。第 10 个 token 依赖前 9 个,第 100 个依赖前 99 个,没法跳步也没法并行。这个阶段影响的是输出速度,对应的指标是 TPS(Tokens Per Second,每秒生成 token 数)。
所以判断慢点其实就一句话:迟迟不开口,问题在 Prefill;开口后写得慢,问题在 Decode。但光知道概念不够,你得能实际跑出这两个指标来验证。这篇就带你用 Codex 接上 TaoToken,分别跑“长输入短输出”和“短输入长输出”两类请求,从响应日志里直接读出 TTFT 和 TPS,让数据告诉你慢在哪。
适合谁看:正在调 AI 应用延迟的开发者、用 Agent 跑长上下文任务觉得慢的人、想搞清楚 TTFT 和 TPS 到底怎么测的人。你不需要提前懂推理引擎,跟着配一遍就能看到指标。
2. 前置准备:TaoToken 通道与 Codex 环境
TaoToken 在这里的角色是模型通道——它把请求转发到模型,返回标准的响应数据,包括 token 用量和耗时信息。你不需要自己搭推理服务,也不用管底层 GPU 调度,拿到 Key 填进 Codex 就能跑。
先打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建一个账号,然后在控制台里生成一把 API Key。这把 Key 后面会同时用于两类请求,不用建两把。
创建 Key 的入口在控制台的 API Keys 页面,直接访问 https://taotoken.net/console/api-keys 就能到。生成后先复制保存,页面刷新后就不再完整显示了。
Codex 这边,你需要确认版本支持自定义 Base URL。目前 Codex CLI 和 Codex 的模型配置都允许覆盖 API 端点。把 Base URL 填成 https://taotoken.net/api,API Key 填你刚生成的那把。
如果你用的是 Codex CLI,配置文件通常在~/.codex/config.json或项目根目录的.codex/config.json。下面是一个最小配置示例:
{ "model": "gpt-4o", "provider": { "baseURL": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥" } }如果你更习惯用环境变量,也可以这样:
export OPENAI_BASE_URL="https://taotoken.net/api" export OPENAI_API_KEY="sk-你的TaoToken密钥"配好之后先别急着跑长请求,用一个最简单的对话验证通道是否通。在 Codex 里发一句“你好”,看能不能正常返回。能返回就说明 Base URL 和 Key 都对了。
注意:Base URL 结尾不要多加
/v1或斜杠,直接写https://taotoken.net/api即可。多写路径会导致 404。
3. 可复制配置:两类请求的构造方式
要区分 Prefill 和 Decode,核心思路是控制输入长度和输出长度这两个变量。我们构造两组请求:
第一组:长输入 + 短输出。输入塞一大段文本,但要求模型只回答一个很短的结果。这组请求的 TTFT 会明显偏高,因为 Prefill 要处理大量输入;但 TPS 看起来正常,因为输出很短,Decode 阶段很快就结束了。
第二组:短输入 + 长输出。输入就一句话,但要求模型生成一大段内容。这组请求的 TTFT 很低,几乎立刻开始输出;但总耗时很长,因为 Decode 要一个 token 一个 token 生成很久,TPS 直接反映生成速度。
先准备一段长输入文本。你可以用任意长文档,这里用一段重复填充的文本模拟长上下文:
long_input = "请阅读以下内容并回答最后的问题。\n" + ("这是一段用于测试 Prefill 阶段的长文本内容。" * 500) + "\n问题:这段文本里有没有出现'测试'这个词?只回答有或没有。"这段输入大概几千 token,足够把 Prefill 阶段拉长。输出要求只有一个词,Decode 几乎不花时间。
第二组请求这样构造:
short_input = "请写一篇关于分布式系统一致性的详细说明,要求至少 2000 字,分五个部分展开。"输入极短,但输出要求很长,Decode 阶段会被充分拉长。
在 Codex 里跑这两组请求时,打开响应日志。Codex 的日志里会记录每次请求的耗时分解,包括首 token 时间和总生成 token 数。如果你用的是 CLI,加--verbose或查看~/.codex/logs/下的日志文件。
关键字段对照:
| 字段 | 含义 | 对应阶段 |
|---|---|---|
| first_token_ms | 首字延迟,毫秒 | Prefill(TTFT) |
| total_tokens | 本次生成的总 token 数 | Decode 输出量 |
| total_ms | 请求总耗时 | Prefill + Decode |
| tokens_per_sec | 每秒生成 token 数 | Decode(TPS) |
拿到这些字段后,TTFT 就是 first_token_ms,TPS 就是 tokens_per_sec。如果日志里没有直接给 tokens_per_sec,可以用total_tokens / ((total_ms - first_token_ms) / 1000)自己算。
4. 验证请求:跑出 TTFT 和 TPS 看结果
先跑第一组,长输入短输出。在 Codex 里执行:
codex run --prompt "$(cat long_input.txt)" --verbose或者直接在交互模式里粘贴长文本加问题。跑完后看日志,你会看到类似这样的输出:
[request] first_token_ms=2840 total_tokens=3 total_ms=3120这里 first_token_ms 是 2840ms,说明模型花了将近 3 秒才开口——Prefill 阶段在处理那几千 token 的输入。total_tokens 只有 3,输出极短,Decode 几乎不占时间。TPS 算下来是3 / ((3120-2840)/1000) ≈ 10.7,但这个数字参考意义不大,因为输出太短,统计噪声大。
再跑第二组,短输入长输出:
codex run --prompt "请写一篇关于分布式系统一致性的详细说明,要求至少 2000 字,分五个部分展开。" --verbose日志会变成这样:
[request] first_token_ms=320 total_tokens=2180 total_ms=48200first_token_ms 只有 320ms,模型几乎立刻开始输出——Prefill 阶段输入很短,处理很快。但 total_ms 达到 48200ms,接近 48 秒,因为要生成 2180 个 token。TPS 算下来是2180 / ((48200-320)/1000) ≈ 45.5,这个数字就很有参考价值了,它直接反映 Decode 阶段的生成速度。
把两组结果放一起对比:
| 请求类型 | TTFT (first_token_ms) | 总 token | 总耗时 | TPS |
|---|---|---|---|---|
| 长输入短输出 | 2840ms | 3 | 3120ms | ~10.7(噪声大) |
| 短输入长输出 | 320ms | 2180 | 48200ms | ~45.5 |
结论很直接:第一组慢在 Prefill,TTFT 高;第二组慢在 Decode,TPS 决定了总耗时。如果你遇到一个请求 TTFT 和 TPS 都不理想,那就是两边都慢——常见于 Agent 场景,既塞了大量工具返回结果,又要求生成完整报告。
提示:跑第二组时如果 TPS 明显低于预期,可以检查是不是并发请求太多导致排队。TaoToken 返回的响应里会带用量信息,你可以对照确认 token 计数是否一致。
5. 本篇常见错排查
报错一:401 Unauthorized。最常见的原因是 Key 没填对或者 Base URL 写错了。检查OPENAI_API_KEY是不是完整的sk-开头字符串,Base URL 是不是https://taotoken.net/api。如果 Key 是在控制台生成的,确认没有多余空格。
报错二:404 Not Found。多半是 Base URL 多写了路径。有人习惯写https://taotoken.net/api/v1,但 Codex 会自动拼接/chat/completions,多一层/v1就找不到路由了。改成https://taotoken.net/api即可。
报错三:日志里没有 first_token_ms 字段。说明 Codex 版本较旧,或者 verbose 模式没开。升级 Codex 到最新版,运行时加--verbose。如果还是没有,可以看原始响应里的usage字段,用总耗时减去生成耗时反推 TTFT。
现象四:TTFT 正常但 TPS 很低。先确认输出 token 数是不是真的很多。如果输出只有几十个 token,TPS 波动会很大,不具参考性。建议输出至少 500 token 以上再看 TPS。另外检查是不是同时跑了多个请求,并发会分摊带宽和算力。
现象五:两组请求的 TTFT 差不多。说明长输入那组的输入可能不够长。Prefill 耗时和输入 token 数正相关,如果输入只有几百 token,TTFT 不会明显拉高。把长输入加到几千 token 以上再试。
现象六:请求超时。长输出请求可能触发 Codex 默认超时。在配置里把 timeout 调大,比如设成 120000ms。TaoToken 通道本身不会主动断长请求,但客户端超时设置会。
6. 拿到指标之后怎么用
跑通这两组请求之后,你手里就有了一套可复用的延迟定位方法。下次遇到 AI 回答慢,不用猜,直接看 TTFT 和 TPS:TTFT 高就去查输入是不是太长、有没有重复前缀可以缓存;TPS 低就去查输出是不是太长、有没有并发争抢。
如果你要长期跑编码类任务或 Agent 工作流,建议把这类指标监控起来。TaoToken 的 Coding Plan 适合需要持续调用模型通道的场景,配合 Codex 的日志可以持续观察 TTFT 和 TPS 的变化趋势。具体可以看 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
想快速验证不同模型的 TTFT 和 TPS 差异,可以直接在模型对话页面切换模型跑同样的两组请求,对比日志里的 first_token_ms 和 tokens_per_sec。入口在 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。
接入文档里有完整的响应字段说明和错误码对照,配置过程中遇到不确定的字段可以先查文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。API Key 管理在 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,需要轮换或新建 Key 时从这里进。
实测下来,把 TTFT 和 TPS 分开看之后,调优方向会清晰很多。以前只觉得“慢”,现在能明确告诉自己是 Prefill 还是 Decode 的问题,改起来有的放矢。