AI API 延迟拆解:连接建立、网关往返与首 token 时间(TTFT)
一次 AI 调用的 API 延迟,通常是三段时间叠加:建立连接(DNS、TCP、TLS),请求到网关走一个来回,以及模型吐出第一个 token 之前的排队和预填充。前两段主要由网络决定,第三段主要由服务端和请求本身决定。用 curl -w 加一个流式请求,就能把三段分别量出来,再决定该改网络、改代码还是改请求。
三段延迟分别是什么
第一段:连接建立
客户端第一次访问某个 API 域名时,要先做三件事:
- DNS 解析:把
api.example.com解析成 IP。命中本地缓存时接近 0,没有缓存时取决于解析器的距离和递归链路。 - TCP 握手:1 个往返(RTT)。
- TLS 握手:TLS 1.3 需要 1 个 RTT,TLS 1.2 需要 2 个 RTT。
所以一条全新连接在传任何业务数据之前,至少要花 2 到 3 个 RTT,再加上 DNS 时间。假设客户端到 API 入口的 RTT 是 200 ms、使用 TLS 1.3,光建连就要大约 400 ms 加 DNS。RTT 越大,这一段放大得越明显。
第二段:网关往返
连接建好后,发出请求、收到响应头,这个来回包含网络上的一个 RTT,加上服务端入口(负载均衡、鉴权、限流、路由)的处理时间。这一段不涉及模型推理,可以用一个不需要模型参与的请求单独测,例如不带密钥访问 /v1/models,网关通常会直接返回 401。这样测到的就是“网络 + 入口处理”的开销。
如果中间还有代理、中转或聚合网关,每多一跳,这一段就多一个往返和一次处理。
第三段:首 token 时间(TTFT)
TTFT(Time To First Token)是从请求发出到收到第一个生成内容的时间。连接是新建的话,它包含前两段,再加上服务端的:
- 排队等待推理资源;
- 预填充(prefill),即模型处理整段输入提示词,输入越长越久;
- 部分接口开启推理或思考模式时,会先生成不可见或单独返回的思考内容,第一个正文 token 会更晚出现。
TTFT 之后还有生成速度(每秒输出多少 token),它决定整段回答要多久,不在本文讨论的三段之内。
三段对照表
| 段 | 主要成因 | 受什么影响 | 能做的调整 |
|---|---|---|---|
| 连接建立 | DNS、TCP、TLS 握手 | 客户端到入口的 RTT、DNS 解析器、TLS 版本 | 连接复用、预热连接、使用就近的解析器 |
| 网关往返 | 1 个 RTT + 入口处理 | 网络路径、中转跳数、服务端入口负载 | 减少中转、选择离客户端近的入口 |
| TTFT(超出前两段的部分) | 排队、prefill、思考 | 输入长度、模型大小、服务端负载 | 缩短提示词、换更轻的模型 |
怎么用 curl 分别测出三段延迟
以下命令在 macOS 和 Linux 上都能运行,Windows 可以在 WSL 或 Git Bash 里执行。把 API_HOST 换成你要测的入口。
步骤 1:测连接建立的每个阶段
API_HOST=api.openai.com
curl -s -o /dev/null https://$API_HOST/v1/models -w '
DNS 解析 : %{time_namelookup}s
TCP 建连完成 : %{time_connect}s
TLS 握手完成 : %{time_appconnect}s
发出请求前 : %{time_pretransfer}s
收到首字节 : %{time_starttransfer}s
总耗时 : %{time_total}s
HTTP 状态码 : %{http_code}
'
curl -w 输出的都是从开始计时的累计值,每段耗时要相减:
- DNS =
time_namelookup - TCP 握手 =
time_connect − time_namelookup,约等于 1 个 RTT - TLS 握手 =
time_appconnect − time_connect - 网关往返 =
time_starttransfer − time_pretransfer
这个请求没带密钥,正常会返回 401。401 由网关给出,不经过模型,所以 time_starttransfer − time_pretransfer 对应的就是第二段。
步骤 2:看连接复用能省掉多少
在同一条 curl 命令里写同一个 URL 多次,curl 会复用第一条连接:
curl -s -o /dev/null -o /dev/null -o /dev/null \
https://$API_HOST/v1/models \
https://$API_HOST/v1/models \
https://$API_HOST/v1/models \
-w 'connect=%{time_connect}s tls=%{time_appconnect}s total=%{time_total}s new_conns=%{num_connects}\n'
第一行的 connect 和 tls 有数值,后两行接近 0,new_conns 为 0 表示这次没有新建连接。第一行和后两行 total 的差值,大致就是连接复用每次能省下的时间。cdnping 首页的“往返”指标也是这个思路:连接复用后请求 3 次取中位数。
步骤 3:测真实的 TTFT
time_starttransfer 不能直接当 TTFT。流式接口往往先返回响应头,再逐步推送内容,首字节可能早于第一个 token。下面的脚本以第一行带内容的 data: 为准:
API_HOST=api.openai.com
API_KEY=你的密钥
MODEL=你的模型名
start=$(date +%s%N)
curl -sN https://$API_HOST/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"model\":\"$MODEL\",\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":\"说一个字\"}]}" \
| while IFS= read -r line; do
case "$line" in
*'"content":"'[!\"]*)
now=$(date +%s%N)
echo "TTFT: $(( (now - start) / 1000000 )) ms"
break ;;
esac
done
macOS 自带的 date 不支持 %N,可以先 brew install coreutils,再把 date 换成 gdate。Anthropic 格式接口的路径、请求头和事件结构都不同,判断条件要按官方文档调整。
步骤 4:把三段放在一起看
在同一时间、同一网络下各跑 3 到 5 次,取中位数,填进下表:
| 测量项 | 计算方式 | 你的结果 |
|---|---|---|
| DNS | time_namelookup | |
| TCP + TLS | time_appconnect − time_namelookup | |
| 网关往返 | time_starttransfer − time_pretransfer(401 请求) | |
| TTFT | 步骤 3 的脚本 | |
| 模型侧耗时(估算) | TTFT − DNS − TCP/TLS − 网关往返 |
判断时看哪一项占大头:TCP 握手本身就有几百毫秒,先查网络路径;建连和往返都不高但 TTFT 很长,问题在服务端或请求本身,先缩短提示词、换一个更轻的模型再测。
连接复用和流式输出分别能缩短哪一段
连接复用:省掉第一段
代码里最常见的问题是每次请求都新建 HTTP 客户端。官方 SDK 会在客户端实例内部维护连接池,前提是你复用同一个实例:
# 不推荐:每次调用都重新建连
def ask(q):
client = OpenAI()
return client.chat.completions.create(...)
# 推荐:进程内复用同一个客户端
client = OpenAI()
def ask(q):
return client.chat.completions.create(...)
另外几点:
- 服务端会关闭空闲一段时间的连接,低频调用的服务可以在业务请求前先发一个轻量请求预热;
- HTTP/2 允许在一条连接上并发多个请求,并发量大时比开很多条 HTTP/1.1 连接省握手;
- 无服务器函数冷启动时连接池是空的,第一次调用一定要付建连成本。
流式输出:TTFT 基本不变,用户更早看到内容
开启 stream: true 不会让模型更快生成第一个 token。它改变的是用户什么时候看到内容:非流式要等整段生成完才返回,流式在第一个 token 生成后就开始显示。回答越长,两者的体感差别越大。
流式还有一个用处:首个内容事件到达的时间就是你能拿到的最准的 TTFT,方便做监控。如果链路里有反向代理,要确认它没有缓冲 SSE 响应,否则流式会退化成一次性返回。
cdnping 边缘探测数据怎么读
cdnping 的 API 状态页 从 Cloudflare 边缘每 5 分钟探测一次各官方 API 入口。下表取自 cdnping 在 2026 年 10 月 17 日的一份快照:执行探测的边缘节点是 DEN(美国丹佛),窗口为北京时间 16:40 至 18:05,约 85 分钟,所有入口合计 242 次探测。cdnping 从 2026-10-17 才开始采集,这份数据只覆盖这一小段时间,不是长期统计。数据以 CC BY 4.0 发布,测量方法见 数据方法。
| 入口 | p50 | p95 |
|---|---|---|
| api.x.ai | 42 ms | 55 ms |
| openrouter.ai | 54 ms | 83 ms |
| api.groq.com | 61 ms | 131 ms |
| generativelanguage.googleapis.com | 69 ms | 89 ms |
| api.anthropic.com | 73 ms | 84 ms |
| api.openai.com | 96 ms | 194 ms |
| api.mistral.ai | 153 ms | 536 ms |
| api.deepseek.com | 353 ms | 414 ms |
| api.moonshot.cn | 375 ms | 1125 ms |
| open.bigmodel.cn | 395 ms | 674 ms |
| ark.cn-beijing.volces.com | 485 ms | 1149 ms |
| dashscope.aliyuncs.com | 615 ms | 1152 ms |
| api.siliconflow.cn | 631 ms | 1030 ms |
读这张表时注意三点:
- p50 是中位数,一半探测比它快;p95 表示 95% 的探测不超过这个值,能反映抖动。例如 api.openai.com 的 p50 是 96 ms,p95 是 194 ms,多数时候稳定,偶尔会翻倍。
- 这是从美国丹佛测的。海外入口在几十到一百多毫秒,国内入口在 350 到 630 ms,反映的主要是丹佛到各入口的网络距离,不代表模型快慢。在中国大陆测,排序很可能完全不同,具体数字要在自己的网络里量。
- 探测不触发模型推理,对应的是本文的第一段和第二段,不包含 TTFT 里模型侧的部分。
聚合网关会多出一跳。以 AGI Plan 为例(AGI Plan 是 cdnping 的商业合作方),同一快照里 agiplan.dev 的 p50 和 p95 都是 883 ms。两个值相同,说明这个窗口里还看不出抖动情况,不宜下结论。经过网关的请求要多走“客户端到网关、网关到上游”两段网络,评估这类服务时,建议用上面的步骤 1 和步骤 3 在自己的网络里各测一遍,分别比较建连、往返和 TTFT。
测完之后可以接着查什么
- 打开 cdnping 在线测速,在浏览器里看 15 个 AI API 入口从你当前网络测得的“首次连接”和“往返”,和 curl 的结果对照。
- 如果往返明显高于预期,用 IP 与环境检测 看出口 IP 的定位和延迟特征。它会对比浏览器往返和 Cloudflare 测得的出口 TCP 往返,用来判断请求是否经过了中转。检测结果可以生成带水印的结果图,分享前可以选择隐藏 IP 地址。
- 针对单个服务,看 OpenAI API 状态 这类分服务页面,区分是自己这边慢还是入口本身在抖。
- 想了解国内访问海外 AI API 的整体网络情况,可以读 从中国访问 AI API 的延迟。
部分海外 AI 服务有地区限制,请以服务方条款为准,并遵守当地法规。