プロバイダードリフト:デフォルトルーティングが LLM のコストを押し上げる仕組み
目次
prompt caching を有効にすると、ヒットカウンターはときどき増えるようになった。それでも請求額はほとんど減っていない。prompt の構成を疑う前に、ダッシュボードに表示されない情報を確認しよう。各リクエストを実際に処理した upstream がどこだったかだ。
マルチプロバイダーゲートウェイでは、単一のモデルが複数の upstream provider にまたがって提供され、リクエストごとにそのうち 1 つが選ばれる。prompt cache は provider ごとに分かれており、provider 内でも node 単位になっていることが多い。そのため、同一内容の 2 回目のリクエストが初回とは別の upstream に送られると、prompt が 1 byte も変わっていなくても cache miss になる。これが プロバイダードリフト だ。token 単位で課金されるモデルでは、気づかないうちにコストが何倍にも膨らむ。
TL;DR
- あるマルチプロバイダーゲートウェイを初期設定のまま使うと、同一内容の 20 call が 9 つの upstream に分散し、prompt cache にヒットしたのは 20 回中 4 回だった。
- 同一の workload を単一 backend のゲートウェイで実行すると、20 回中 19 回ヒットした。ドリフトが発生した実行のコストは約 3.9 倍だった($0.0102 に対して $0.0026)。
- 計測対象の DeepSeek 系モデルでは、cache miss 1 回のコストは hit の約 4 倍だった(1 call あたりの中央値は $0.00062 に対して $0.00015)。
- ドリフトの有無はモデルごとに異なる。同じゲートウェイでも、GPT 系モデルは 20 call すべてが 1 つの upstream に送られ、20 回中 19 回ヒットした。
発生条件は 2 つ
自分で誤った設定を選んだ結果ではない。初期設定のままで発生する。
- デフォルトの auto routing。 upstream を固定せずにモデルへリクエストを送るため、ゲートウェイが call ごとに 1 つを選ぶ。
- デフォルトの provider sort が「default (balanced)」。 1 つの upstream を継続して使わず、利用可能な upstream 間でゲートウェイが load balancing する。
どちらも初期設定だ。何も変更しなければドリフトが発生する。回避するには設定を変更する必要がある。
同一内容のリクエストを 20 回送った結果
一般的なマルチプロバイダーゲートウェイを上記の初期設定のまま使用し、同一の 約 8K token の prefix を 20 回連続で送信した。毎回、upstream 自身が報告する provider と cache の field も取得した。disk cache を使う DeepSeek 系モデルでは、結果は次のとおりだった。
- 20 call を処理したのは 9 つの異なる upstream だった。
N***a、S***w、M***h、D***a、A***L、P***l、S***e、V***e、A***d。 - cache hit 率は 4/20(20%)。 すでに prefix を cache 済みの upstream に偶然送られた call だけがヒットした。
同じ 20 call を 単一 backend のゲートウェイで実行すると、同一 workload での hit 率は 19/20(95%) だった。モデルも prompt も call 数も同じだ。違うのは、routing がドリフトするかどうかだけである。
一方、まったく同じマルチプロバイダーゲートウェイでも、GPT 系モデルは 20 call すべてが 1 つ の upstream(A***e)に送られ、19/20 がヒットした。ドリフトは一様に発生するわけではない。ゲートウェイが複数の upstream に分散するモデルだけが影響を受ける。今回の実行では、それが DeepSeek 系モデルだった。
結論 A:想定していたコストと実際に支払ったコスト
ドリフトが発生したモデルの 1 call あたりのコストは、cache の結果によって明確に分かれた。
| call の種類 | 1 call あたりのコスト中央値 |
|---|---|
| cache hit | ~$0.00015 |
| cache miss | ~$0.00062 |
このモデルでは、miss のコストは hit の約 4 倍 になる。raw input token の公開価格で比較すると差はさらに広く、約 50 倍だ。20 call の合計は次のとおりだった。
| シナリオ | hit 率 | 同一内容の 20 call にかかったコスト |
|---|---|---|
| 想定(cache に到達可能) | 95% | $0.0026 |
| 実測(デフォルト設定でドリフト) | 20% | $0.0102 |
同じモデル、同じ prompt、同じ 20 request でも、プロバイダードリフトによって実行コストは 約 3.9 倍 になった。caching 自体は常に有効だったが、routing layer のせいで大半の token が miss 時の料金で課金された。大きく安定した prefix を一日中繰り返し送る本番 endpoint までスケールすると、この差額が input コストの大部分を占める。
結論 B:cache が使えなければ latency も改善しない
caching はコスト削減だけの仕組みではない。prefill が warm なら、最初の token が早く返る。ドリフトによって cache に到達できなければ、この高速化も失われる。同一内容の call を繰り返し、time-to-first-token(TTFT)を計測した。
GPT 系モデル(同じ upstream に継続して routing され、cache に到達可能):
| call | TTFT |
|---|---|
| 1 回目(cold、miss) | ~1760 ms |
| 2 回目以降(warm、hit) | ~1130 ms |
caching により、最初の token が返るまでの時間は約 36% 短縮 された。結果も安定しており、warm な call はすべて狭い範囲に収まった。
DeepSeek 系モデル(デフォルト設定でドリフトし、cache にはほとんど到達不能):
- 10 call の繰り返しで cache にヒットした回数は 0。
- TTFT は call ごとに 約 1000 ms から約 4500 ms まで変動し、空の response が返ることもあった。
ほぼすべての request が未使用の upstream に送られるため、latency は cold prefill のままだ。さらに、応答した provider 固有のばらつきも受ける。GPT モデルでは、到達可能な cache によって TTFT が 36% 改善した。ドリフトしたモデルでは改善がなく、最速と最遅の call には 4.5 倍の差が生じた。
5 分で自分の環境を監査する
ここに示した数値も、他人の数値も、そのまま信用してはいけない。同じ長い prefix を数回送り、2 つの field を確認しよう。domain は hardcode していない。env var で自分のゲートウェイを指定できる。
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)}")
同じモデルで複数の upstream が確認されたら、ドリフトが発生している。prompt の安定性から期待される水準を hit 率が大きく下回るなら、その差が追加コストになっている。詳しい手順は LLM ゲートウェイの cache 情報は正しいか? で説明している。
確認すべきポイント
ドリフトを解消するには、routing の構造を変える必要がある。call ごとに、prefix を見たことのない新しい upstream へ load balancing するのではなく、特定のモデルを一貫して同じ backend に送る。そうすれば、次の request でも warm cache に到達できる。ゲートウェイを評価するときは、同じ prefix を 20 回送り、使用された upstream の数を数える。望ましいのは 1 つだ。9 つなら、その分だけ余計なコストを支払っている。
ただし、prompt caching はどの環境でも best-effort である。disk cache を使うモデルでは、単一 backend であっても idle 状態が長く続くと hit 率が下がる。ドリフトを解消しても、無期限に保持される cache が得られるわけではない。解消できるのは、最大かつ最も無駄な miss の原因だ。利用者が選んだわけでもなく、画面からは見えない miss を減らせる。
まとめ
「prompt caching をサポートしている」と「自分の cache に到達できる」は別の話だ。1 つのモデルを入れ替わり続ける複数の upstream に分散するゲートウェイは、cache 対応を正しくうたいながら、実際には hit 率 20%、請求額は約 4 倍、最初の token が返るまでの latency は 4.5 倍の幅で変動することがある。確認すべきなのは、caching 対応の表示ではない。実測した hit 率と、同一内容の request がいくつの upstream に送られたかだ。probe を実行し、データで判断しよう。
監査手順の全体像は LLM ゲートウェイの cache 情報は正しいか? を参照してほしい。cache が必要な理由は KV Cache と TTL の仕組み で説明している。
よくある質問
こちら側の設定ミスですか? 違う。auto routing を使い、provider sort を「default (balanced)」のままにした初期設定で発生する。ドリフトを避けるには、明示的に upstream を固定する必要がある。固定したから問題が起きるわけではない。
1 つの upstream に固定すれば解決しますか? provider をまたぐドリフトは解消する。ただし、単一の upstream でも prefix affinity のない複数の replica を運用していることが多く、hit と miss が交互に発生する可能性は残る。固定後も、解決したと決めつけずに計測する必要がある。
GPT 系モデルでドリフトしなかったのはなぜですか? 今回の実行では、ゲートウェイがたまたま 1 つの upstream に routing したためだ。ドリフトはモデルごとに異なり、ゲートウェイが load balancing の対象とする upstream の数に左右される。一様には発生しない。
コスト差は本当に約 4 倍ですか? 計測した 1 call あたりの合計では、miss は hit の約 4 倍だった。このモデル系統の raw input token の公開価格では、hit と miss の差は 50 倍に近い。どちらの比較でも、本来 hit するはずの request が miss になることが高コストの原因だ。
監視すべき指標を 1 つ挙げるなら何ですか? モデルごとの cache hit 率を時系列で監視し、モデルごとに使用された異なる upstream の数も併せて確認する。hit 率が下がるか upstream 数が増えた場合、実質的な token コストは上昇している。