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