推論超時不是玄學:給 LLM 呼叫設三個數
LLM 呼叫的超時糾結常常是這樣的:20 秒太短,長 prompt 偶發被掐斷;120 秒太長,上游掛了你的佇列先堵死。多數團隊最後是拍腦袋定一個值,然後每次故障復盤都要重新吵一遍。

推論超時不是玄學:給 LLM 呼叫設三個數
LLM 呼叫的超時糾結常常是這樣的:20 秒太短,長 prompt 偶發被掐斷;120 秒太長,上游掛了你的佇列先堵死。多數團隊最後是拍腦袋定一個值,然後每次故障復盤都要重新吵一遍。
其實只需要拍板三個數:預算(budget)、hard timeout、重試政策。定了,寫進設定,剩下就是執行。
失效模式先分清楚
超時的來源不是一種,混在一起設閾值只會互相打架:
- **模型排隊**:上游併發打滿,請求進佇列乾等。特徵:延遲整體抬高,成功變慢。
- **長 prompt / 長輸出**:prefill 和 decode 本身就慢。特徵:延遲和 token 數強相關。
- **上游卡死**:連線建了但永遠不回傳。特徵:個別請求頂格撞超時。
這三種對應的解法完全不同:第一種限流,第二種調預算或換模型,第三種才是超時 + 重試。超時只能救第三種,但對三種都有效——這是它能兜底的原因,也是單獨靠它不夠的原因。
預算:預算 = p95 × 1.5
別從「平均延遲 3 秒所以設 10 秒」推。看最近 7 天全部呼叫的 p95 延遲(按 prompt token 分桶更準),乘 1.5 作為正常呼叫的延遲預算。
例:某介面 p95 = 8.2s(medium prompt 桶),預算 ≈ 12s。偶發超過預算的請求不是「超時故障」,是長尾,它們應當在監控裡單獨計數,而不是混進錯誤率。預算這個數字以後每次調超時都有錨點,不用重新吵。
Hard timeout:預算 + 緩衝
hard timeout 設預算的 1.5–2 倍,但必須考慮 decode 特性:**輸出越長,越不能按固定值掐**。如果介面支援串流(SSE),更好的做法是只檢測空閒——首 token 前給一個 prefill timeout(比如 15s),之後每收到一個 chunk 就續命,兩個 chunk 間隔超過 30s 判死。這樣 2 萬 token 的合法長輸出不會被誤殺,而真卡死的請求照樣被切斷。
非串流的介面就老老實實一個 hard timeout。經驗值:hard timeout ≥ 該介面見過的最大合法輸出耗時,否則你會把「慢但成功」和「死了」判成一類。
重試:只重試一次,條件寫進程式碼
重試的坑不在次數在條件:
- **只對 HTTP 5xx 和連線斷開重試**,502/503/504 算,client 自己撞超時的算半個(有一次機會)。
- **429 不重試,記 fail 報限流**。對 429 重試只是把佇列壓力放大,限流不會因此消失。429 應該觸發「降速」而不是「再試」。
- **重試過一次仍超時 → 記 fail**,讓呼叫方決定降級。不疊指數退避:在線路徑上退避 30 秒比直接失敗更傷體驗,離線 batch 才值得退避。
另外,重試必須帶冪等判斷。同一個使用者請求如果兩次都到模型側,使用者可能得到兩個不同答案,或者帳單算兩次。能帶 idempotency key 的帶上;不能的,至少日誌裡用同一個 request_id 串起來,復現時看得清是「一次失敗」還是「兩次半成功」。
三個數落進設定
llm_call:
budget_ms: 12000 # p95 x 1.5
timeout:
prefill_ms: 15000 # 串流: 首 token 前
chunk_idle_ms: 30000 # 串流: chunk 間隔
hard_ms: 30000 # 非串流: 唯一
retry:
max: 1
on: [http_5xx, connection_error, hard_timeout]
not_on: [http_429, http_4xx]
告警也照這個分:超時率按「預算內長尾」「撞 hard timeout」「重試後仍失敗」三層分別計數,每層獨立報警。這樣復盤時看到的不是一個籠統的「超時 3%」,而是「長尾變多 → 上游該擴容」或「撞 hard 變多 → 有東西掛了」,指向完全不同的動作。
最後
超時的本質不是「等多久放棄」,是把「上游變慢」和「上游死了」這兩種信號分開。預算、hard timeout、重試條件,三個數定了,每一次超時故障都能落到上圖三類之一,修復動作也就唯一了。能這樣切分的超時設定才是完成了,剩下的只是定期用最新 p95 校準預算。
留言區
歡迎分享你的想法!
載入留言中…