供應商漂移:預設路由如何推高 LLM 成本

目錄
  1. 觸發它的兩個條件
  2. 20 筆相同請求長什麼樣
  3. 結論 A:你預期的成本 vs 你實際付的成本
  4. 結論 B:沒有快取也意味著沒有延遲優勢
  5. 五分鐘稽核你自己的設定
  6. 該注意什麼
  7. 結語
  8. FAQ

你開啟了 prompt caching,命中計數器偶爾跳動一下,但帳單幾乎沒變。在怪罪你的提示結構之前,先看看儀表板隱藏起來的東西:實際上是哪個上游服務了每一筆請求。

多供應商閘道會把單一模型分散到數個上游供應商,並為每筆請求挑選其中一個。Prompt 快取是各供應商獨立的(在供應商內部通常還是各節點獨立)。因此當你第二筆相同的請求落到與第一筆不同的上游時,這就是一次快取未命中,即便你的提示一個位元組都沒改。這就是供應商漂移,而在按權杖計費的模式下,它會悄悄把你的成本翻好幾倍。

觸發它的兩個條件

這不是你主動選擇的設定錯誤,而是開箱即得的結果:

  1. 預設自動路由。 請求被送往模型時並未釘選某個上游,所以閘道會為每次呼叫挑選一個。
  2. 預設供應商排序 = 「default(balanced)」。 閘道會在符合條件的上游之間做負載平衡,而非固定使用其中一個。

兩者都是出廠預設值。你不必動任何設定就會發生漂移;反而是要動設定才能避免它。

20 筆相同請求長什麼樣

我們在上述預設值下,對一個熱門的多供應商閘道連續送了 20 次相同的約 8K 權杖前綴,每次都向上游索取它自己回報的供應商與快取欄位。對於 DeepSeek 家族的某個磁碟快取模型:

  • 9 個不同的上游服務了這 20 次呼叫:N***aS***wM***hD***aA***LP***lS***eV***eA***d
  • 快取命中率:4/20(20%)。 你只在剛好落到已經快取了你前綴的上游那幾次呼叫上命中。

把同樣的 20 次呼叫送到單一後端閘道(一個模型、一個上游、不做平衡),在相同的工作負載下命中率是 19/20(95%)。相同模型、相同提示、相同呼叫次數。唯一的變數就是路由會不會漂移。

作為對比,在同一個多供應商閘道上,某個 GPT 等級的模型在這 20 次呼叫中全部被路由到同一個上游(A***e),命中 19/20。漂移並不一致;它會咬住閘道剛好拿去分散的那個模型,而在這次執行中那是 DeepSeek 家族的模型。

結論 A:你預期的成本 vs 你實際付的成本

漂移模型上的每次呼叫成本,依快取結果清楚地分成兩類:

呼叫類型中位成本 / 次呼叫
快取命中~$0.00015
快取未命中~$0.00062

在這個模型上,一次未命中的成本約為一次命中的 4 倍(就原始輸入權杖而言,公布的差距更大,約 50 倍)。現在把這 20 次呼叫加總起來:

情境命中率20 次相同呼叫的成本
預期(快取可觸及)95%$0.0026
實際(預設漂移)20%$0.0102

相同模型、相同提示、相同的 20 筆請求。供應商漂移讓這次執行的成本變成約 3.9 倍。快取自始至終都「開著」;只是路由層把你大部分的權杖以未命中費率計費了。把這個情況放大到一個整天重播大型穩定前綴的生產端點,這個差距就是你輸入花費的大宗。

結論 B:沒有快取也意味著沒有延遲優勢

快取不只是成本槓桿。一個預熱過的 prefill 會更快回傳第一個權杖。當漂移讓你拿不到快取時,你也一併放棄了這個提速。我們在重複的相同呼叫上量測了首個權杖時間(TTFT):

GPT 等級模型(路由到單一一致的上游,快取可觸及):

呼叫TTFT
第 1 次(冷、未命中)~1760 ms
後續(熱、命中)~1130 ms

快取大約買到快 36% 的首個權杖,而且很穩定:每次熱呼叫都落在一個緊密的區間內。

DeepSeek 家族模型(預設漂移,快取很少可觸及):

  • 在 10 次重複呼叫中的快取命中數:0
  • TTFT 在各呼叫之間從 ~1000 ms 擺盪到 ~4500 ms,偶爾還出現空回應。

因為幾乎每筆請求都打到一個全新的上游,你會一直停留在冷 prefill 的延遲,並承受是哪個供應商回應就帶來的變異。GPT 模型從可觸及的快取獲得 36% 的 TTFT 改善;漂移的模型一點都沒拿到,最快與最慢呼叫之間還相差 4.5 倍。

五分鐘稽核你自己的設定

別相信這些數字,也別相信任何人的。把相同的長前綴送好幾次,然後盯住兩個欄位。不寫死任何網域;用環境變數把它指向你自己的閘道。

import os, uuid
from openai import OpenAI

client = OpenAI(api_key=os.environ["GW_KEY"], base_url=os.environ["GW_BASE"])
SYS = f"[probe {uuid.uuid4().hex}]\n\n" + ("You are a support assistant. " * 300)

seen, hits = {}, 0
for i in range(20):
    r = client.chat.completions.create(
        model=os.environ["GW_MODEL"], max_tokens=16,
        messages=[{"role": "system", "content": SYS},
                  {"role": "user", "content": f"q{i}"}],
        extra_body={"usage": {"include": True}})
    d = r.model_dump()
    det = r.usage.prompt_tokens_details
    cached = (getattr(det, "cached_tokens", 0) or 0) if det else 0
    seen[d.get("provider")] = seen.get(d.get("provider"), 0) + 1   # populated when exposed
    hits += 1 if cached else 0

print(f"hit rate {hits}/20; upstreams seen: {len(seen)}")

同一個模型出現超過一個上游就代表漂移。命中率遠低於你的提示穩定度,就代表它正在向你課稅。更完整的方法在 Does Your LLM Gateway Lie About Cache?

該注意什麼

對付漂移的解法是結構性的:把某個模型路由到一致的後端,讓預熱過的快取在下一筆請求時真的可觸及,而不是把每次呼叫負載平衡到一個從未見過你前綴的全新上游。當你評估閘道時,把相同的前綴送 20 次並計算上游數量。一個才是你要的。九個就是課稅。

公允地補充一點:prompt caching 在任何地方都是盡力而為,而在磁碟快取的模型上,即便是單一後端,命中率在長時間閒置後仍會逐漸軟化。消除漂移不會給你一個無限的快取。它只是移除了最大、最浪費的未命中來源——那個你從未同意、也看不見的來源。

結語

「支援 prompt caching」與「你的快取可觸及」是兩個不同的主張。一個把單一模型分散到輪番上陣的上游陣容的閘道,可以如實回報快取支援,卻交付 20% 的命中率、約 4 倍的帳單,以及擺盪達 4.5 倍的首個權杖延遲。該盯的數字不是有沒有宣稱支援快取,而是你實測的命中率,以及你的相同請求觸及了多少個上游。跑一下探測程式,讓資料來定奪。

更廣泛的稽核方法見 Does Your LLM Gateway Lie About Cache?;要了解快取存在的根本原因,見 How KV Cache & TTL Work

FAQ

這是我這邊的設定錯誤嗎? 不是。它在出廠預設值下就會發生:自動路由加上供應商排序維持在「default(balanced)」。要避免漂移得主動釘選一個上游,而非反過來。

釘選一個上游就能修好嗎? 它能移除跨供應商的漂移,但單一上游往往會跑多個沒有前綴親和性的副本,所以命中仍可能反覆不定。釘選之後要量測,別只是假設。

為什麼那個 GPT 等級的模型沒有漂移? 在這次執行中,閘道剛好把它路由到單一上游。漂移是各模型獨立的,取決於閘道在多少符合條件的上游之間做平衡;它並不一致。

成本差距真的是約 4 倍嗎? 就我們量測的每次呼叫總額而言,一次未命中約為一次命中的 4 倍;就這類模型公布的原始輸入權杖定價而言,命中與未命中的差距更接近 50 倍。無論如何,把預期的命中變成未命中才是昂貴的部分。

我該監控哪個單一指標? 隨時間追蹤每個模型的快取命中率,並搭配每個模型的不同上游數量。如果命中率下降或上游數量上升,你的實質權杖成本就剛剛上去了。

← 返回部落格