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 服務有地區限制,請以服務方條款為準,並遵守當地法規。