AI API 延迟拆解:连接建立、网关往返与首 token 时间(TTFT)

博客 / 性能

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 次,取中位数,填进下表:

测量项计算方式你的结果
DNStime_namelookup
TCP + TLStime_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 发布,测量方法见 数据方法。

入口p50p95
api.x.ai42 ms55 ms
openrouter.ai54 ms83 ms
api.groq.com61 ms131 ms
generativelanguage.googleapis.com69 ms89 ms
api.anthropic.com73 ms84 ms
api.openai.com96 ms194 ms
api.mistral.ai153 ms536 ms
api.deepseek.com353 ms414 ms
api.moonshot.cn375 ms1125 ms
open.bigmodel.cn395 ms674 ms
ark.cn-beijing.volces.com485 ms1149 ms
dashscope.aliyuncs.com615 ms1152 ms
api.siliconflow.cn631 ms1030 ms

读这张表时注意三点:

  1. p50 是中位数,一半探测比它快;p95 表示 95% 的探测不超过这个值,能反映抖动。例如 api.openai.com 的 p50 是 96 ms,p95 是 194 ms,多数时候稳定,偶尔会翻倍。
  2. 这是从美国丹佛测的。海外入口在几十到一百多毫秒,国内入口在 350 到 630 ms,反映的主要是丹佛到各入口的网络距离,不代表模型快慢。在中国大陆测,排序很可能完全不同,具体数字要在自己的网络里量。
  3. 探测不触发模型推理,对应的是本文的第一段和第二段,不包含 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 服务有地区限制,请以服务方条款为准,并遵守当地法规。