Dérive de fournisseur : comment le routage par défaut gonfle le coût des LLM
Sommaire
Vous avez activé la mise en cache des prompts, le compteur de succès s’incrémente de temps en temps, mais votre facture n’a presque pas bougé. Avant d’accuser la structure de votre prompt, regardez quelque chose que le tableau de bord vous cache : quel upstream a réellement traité chaque requête.
Les passerelles multi-fournisseurs répartissent un même modèle sur plusieurs fournisseurs upstream et en choisissent un par requête. Les caches de prompts sont par fournisseur (souvent par nœud au sein d’un fournisseur). Donc quand votre deuxième requête identique atterrit sur un upstream différent de la première, c’est un échec de cache, même si votre prompt n’a pas changé d’un seul octet. C’est la dérive de fournisseur, et sur un modèle de tarification au token elle multiplie discrètement votre coût.
Les deux conditions qui la déclenchent
Ce n’est pas une mauvaise configuration que vous avez choisie. C’est ce que vous obtenez d’office :
- Routage automatique par défaut. La requête est envoyée au modèle sans fixer d’upstream, donc la passerelle en choisit un à chaque appel.
- Tri des fournisseurs par défaut = « default (balanced) ». La passerelle répartit la charge sur les upstreams éligibles au lieu de s’en tenir à un seul.
Les deux sont les réglages d’usine. Vous n’avez rien à toucher pour avoir la dérive ; vous devez toucher aux réglages pour l’éviter.
À quoi ressemblent 20 requêtes identiques
Nous avons envoyé le même préfixe d’environ 8K tokens 20 fois de suite à une passerelle multi-fournisseurs populaire, avec les réglages par défaut ci-dessus, en demandant à chaque fois les champs de fournisseur et de cache rapportés par l’upstream lui-même. Pour un modèle à cache disque de la famille DeepSeek :
- 9 upstreams distincts ont traité les 20 appels :
N***a,S***w,M***h,D***a,A***L,P***l,S***e,V***e,A***d. - Taux de succès du cache : 4/20 (20 %). Vous ne touchez le cache que sur les appels qui ont par hasard atterri sur un upstream ayant déjà mis votre préfixe en cache.
Lancez les mêmes 20 appels contre une passerelle à backend unique (un modèle, un upstream, pas de répartition de charge) et le taux de succès est de 19/20 (95 %) sur la charge de travail identique. Même modèle, même prompt, même nombre d’appels. La seule variable est de savoir si le routage dérive.
Par contraste, sur cette même passerelle multi-fournisseurs, un modèle de classe GPT a été routé vers un seul upstream (A***e) pour les 20 appels et a touché le cache 19/20. La dérive n’est pas uniforme ; elle frappe le modèle que la passerelle se trouve répartir, et sur cette exécution c’était le modèle de la famille DeepSeek.
Conclusion A : le coût attendu vs le coût payé
Le coût par appel sur le modèle qui dérive se sépare nettement selon le résultat du cache :
| type d’appel | coût médian / appel |
|---|---|
| succès de cache | ~$0.00015 |
| échec de cache | ~$0.00062 |
Un échec coûte environ 4x un succès sur ce modèle (sur les tokens d’entrée bruts, l’écart publié est encore plus large, environ 50x). Maintenant, faisons le total sur les 20 appels :
| scénario | taux de succès | coût pour 20 appels identiques |
|---|---|---|
| attendu (cache atteignable) | 95 % | $0.0026 |
| réel (dérive par défaut) | 20 % | $0.0102 |
Même modèle, même prompt, mêmes 20 requêtes. La dérive de fournisseur a fait coûter l’exécution ~3,9x plus cher. La mise en cache était « activée » tout le temps ; la couche de routage a simplement facturé la plupart de vos tokens au tarif d’échec. Mettez cela à l’échelle d’un endpoint de production qui rejoue un grand préfixe stable toute la journée et l’écart représente l’essentiel de votre dépense en entrée.
Conclusion B : pas de cache signifie aussi pas de gain de latence
La mise en cache n’est pas qu’un levier de coût. Un prefill chaud renvoie le premier token plus tôt. Quand la dérive vous prive du cache, vous renoncez aussi à cette accélération. Nous avons mesuré le temps jusqu’au premier token (TTFT) sur des appels identiques répétés :
Modèle de classe GPT (routé vers un upstream cohérent, cache atteignable) :
| appel | TTFT |
|---|---|
| 1er (froid, échec) | ~1760 ms |
| suivants (chaud, succès) | ~1130 ms |
La mise en cache fait gagner environ 36 % de rapidité sur le premier token, et c’est stable : chaque appel chaud atterrit dans une fourchette serrée.
Modèle de la famille DeepSeek (dérive par défaut, cache rarement atteignable) :
- Succès de cache sur une répétition de 10 appels : 0.
- Le TTFT oscillait de ~1000 ms à ~4500 ms d’un appel à l’autre, avec parfois des réponses vides.
Parce que presque chaque requête tombe sur un nouvel upstream, vous restez à la latence de prefill froid et héritez de la variance du fournisseur qui a répondu. Le modèle GPT a obtenu une amélioration de 36 % du TTFT grâce à un cache atteignable ; le modèle qui dérive n’a rien obtenu, plus un écart de 4,5x entre son appel le plus rapide et le plus lent.
Auditez votre propre configuration en cinq minutes
Ne faites pas confiance à ces chiffres, ni à ceux de qui que ce soit. Envoyez le même long préfixe plusieurs fois et surveillez deux champs. Aucun domaine codé en dur ; pointez-le vers votre propre passerelle avec des variables d’environnement.
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)}")
Plus d’un upstream pour le même modèle signifie une dérive. Un taux de succès bien en dessous de la stabilité de votre prompt signifie qu’elle vous taxe. La méthode plus complète est dans Votre passerelle LLM ment-elle sur le cache ?.
Ce qu’il faut rechercher
Le remède à la dérive est structurel : routez un modèle donné vers un backend cohérent pour qu’un cache chaud soit réellement atteignable à la prochaine requête, au lieu de répartir chaque appel sur un nouvel upstream qui n’a jamais vu votre préfixe. Quand vous évaluez une passerelle, envoyez le même préfixe 20 fois et comptez les upstreams. Un seul, c’est ce que vous voulez. Neuf, c’est une taxe.
Une mise en garde honnête : la mise en cache des prompts est au mieux-effort partout, et sur les modèles à cache disque le taux de succès s’amollit quand même sur de longs intervalles d’inactivité, même avec un backend unique. Éliminer la dérive ne vous donne pas un cache infini. Cela supprime la source de manqués la plus importante et la plus gaspilleuse, celle que vous n’avez jamais acceptée et que vous ne pouvez pas voir.
Conclusion
« Prend en charge la mise en cache des prompts » et « votre cache est atteignable » sont deux affirmations différentes. Une passerelle qui éparpille un modèle sur un casting tournant d’upstreams peut rapporter une prise en charge du cache en toute vérité tout en livrant un taux de succès de 20 %, une facture ~4x, et une latence du premier token qui varie de 4,5x. Le chiffre à surveiller n’est pas de savoir si la mise en cache est annoncée. C’est votre taux de succès mesuré et combien d’upstreams vos requêtes identiques touchent. Lancez la sonde et laissez les données trancher.
Pour la méthode d’audit plus large, voyez Votre passerelle LLM ment-elle sur le cache ? ; pour comprendre pourquoi les caches existent, voyez Comment fonctionnent le KV Cache et le TTL.
FAQ
Est-ce une mauvaise configuration de mon côté ? Non. Cela arrive avec les réglages d’usine : routage automatique avec le tri des fournisseurs laissé sur « default (balanced) ». Éviter la dérive nécessite de fixer activement un upstream, et non l’inverse.
Fixer un seul upstream règle-t-il le problème ? Cela supprime la dérive inter-fournisseurs, mais un upstream unique exécute souvent plusieurs réplicas sans affinité de préfixe, donc les succès peuvent quand même fluctuer. Mesurez après avoir fixé plutôt que de présumer.
Pourquoi le modèle de classe GPT n’a-t-il pas dérivé ? Sur cette exécution, la passerelle l’a par hasard routé vers un seul upstream. La dérive est par modèle et dépend du nombre d’upstreams éligibles sur lesquels la passerelle répartit ; elle n’est pas uniforme.
L’écart de coût est-il vraiment de ~4x ? Sur les totaux par appel que nous avons mesurés, un échec valait ~4x un succès ; sur la tarification brute des tokens d’entrée pour cette classe de modèle, l’écart publié succès-vs-échec est plus proche de 50x. D