🎁 新規 無料登録、10回の呼び出しを進呈。最大 $1、カード不要。
プロバイダードリフト:デフォルトルーティングが LLM のコストを押し上げる仕組み

プロバイダードリフト:デフォルトルーティングが LLM のコストを押し上げる仕組み

目次
  1. 発生条件は 2 つ
  2. 同一内容のリクエストを 20 回送った結果
  3. 結論 A:想定していたコストと実際に支払ったコスト
  4. 結論 B:cache が使えなければ latency も改善しない
  5. 5 分で自分の環境を監査する
  6. 確認すべきポイント
  7. まとめ
  8. よくある質問

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 つ

自分で誤った設定を選んだ結果ではない。初期設定のままで発生する。

  1. デフォルトの auto routing。 upstream を固定せずにモデルへリクエストを送るため、ゲートウェイが call ごとに 1 つを選ぶ。
  2. デフォルトの 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***aS***wM***hD***aA***LP***lS***eV***eA***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 に到達可能):

callTTFT
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 コストは上昇している。

← ブログに戻る