Superviser un LLM dès le premier jour : latence, dérive, qualité, coût

Quatre signaux à suivre sur un service LLM dès sa mise en ligne, pourquoi le monitoring applicatif classique passe à côté, et l'ordre dans lequel je les instrumente.

Le monitoring applicatif classique répond à deux questions : est-ce que ça tourne, et est-ce que c'est rapide. Sur un service LLM, les deux peuvent être au vert alors que le service ne sert plus à rien : l'endpoint renvoie des 200 en 400 ms, avec des réponses fausses depuis trois jours.

Quatre signaux comblent ce trou. Aucun n'est exotique. On les oublie parce que seul le premier ressemble à du monitoring.

Quatre signaux d'observabilité pour un service LLM — latence, dérive, qualité sémantique, coût — et ce que chacun détecte1 · latenceTTFT, ITL, e2efile d'attentedétecte :la capacité2 · dériveembeddings d'entréelongueur, refusdétecte :le monde qui change3 · sémantiqueschéma validecitations résoluesdétecte :les régressions4 · coûttokens par requêtecoût par résultatdétecte :le budget qui glissepeu coûteux à instrumentersouvent instrumenté en dernierun 200 OK ne dit rien d'aucun des quatre
Figure 1. Seul le premier signal ressemble au monitoring habituel. C'est pour ça que les trois autres arrivent en général après le premier incident.

1. La latence, phase par phase

Le temps de réponse global cache les deux choses que l'utilisateur ressent. Je les enregistre séparément :

  • TTFT (temps jusqu'au premier token). Il dépend surtout du traitement du prompt : il varie avec la longueur du prompt et l'état du cache, et presque pas avec la longueur de la réponse.
  • ITL (latence entre deux tokens). Elle dépend surtout de la bande passante mémoire : elle varie avec la charge et la longueur du contexte.
  • Le temps de bout en bout, avec l'attente en file comme série à part.

C'est l'attente en file qu'il faut isoler. Quand un service sature, le temps de bout en bout monte alors que TTFT et ITL restent plats : le modèle est toujours aussi rapide, les requêtes attendent juste leur tour. Sans ce découpage, on croit à un problème de modèle et on cherche au mauvais endroit.

Sur mon infra, vLLM expose déjà ces mesures au format Prometheus, et je les lis dans Grafana. Il reste à les afficher en percentiles : une moyenne sur une distribution à longue traîne décrit très mal l'expérience réelle.

L'intuition mathématique : moyenne contre percentiles

La moyenne additionne toutes les durées et divise par leur nombre. Chaque requête pèse donc en proportion de sa durée : une requête de 30 secondes pèse autant que 75 requêtes de 400 ms. Le percentile fonctionne autrement : on range les durées de la plus courte à la plus longue et on lit une position. Sur 100 requêtes, le p50 (la médiane) est la 50e, le p99 la 99e. Une valeur extrême n'occupe qu'une seule place dans ce classement, quelle que soit sa durée.

Animation. 100 requêtes rangées par durée. p50, p95 et p99 sont des positions dans ce classement, et la moyenne (pointillés) est une somme divisée. Une requête de plus, à 30 s, déplace la moyenne et laisse la médiane en place.

Un exemple : 100 requêtes, une médiane à 0,41 s et une moyenne à 0,44 s. Les deux chiffres se ressemblent. Une seule requête de plus à 30 s fait passer la moyenne à 0,73 s, soit 67 % de plus, et la médiane reste à 0,41 s. Ce nouveau chiffre ne décrit presque personne : deux requêtes sur trois ont pris moins de 0,5 s, aucune autre n'a dépassé 1 s, une seule a pris 30 s.

D'où ma règle : p50 pour le cas typique, p95 et p99 pour la traîne, et la moyenne seulement pour ce qui s'additionne vraiment, comme le coût total ou le temps GPU consommé.

2. La dérive, en entrée et en sortie

Les entrées changent sans prévenir : un nouveau type d'utilisateur, une évolution de l'interface qui modifie les formulations, la panne d'un concurrent qui vous envoie un trafic inhabituel.

En entrée. Je calcule les embeddings d'un échantillon de requêtes et je suis la distance entre la fenêtre courante et une fenêtre de référence fixe. La valeur absolue dit peu de chose, c'est le mouvement qui compte. J'alerte sur un changement qui dure : alerter sur un pic, c'est se faire réveiller chaque lundi par la saisonnalité de la semaine.

En sortie, la partie la moins chère. Je suis la longueur des réponses, le taux de refus, le taux de réponses vides et le taux d'échec de validation du format. Pas besoin d'embeddings, de jeu de référence ni de modèle supplémentaire : ce sont des compteurs, et ils bougent avant les signaux plus fins.

Un saut du taux de refus est une des alertes les plus parlantes d'un service LLM. En général, il veut dire qu'un prompt a changé, qu'une version de modèle a changé, ou que les entrées se sont déplacées vers une zone qui déclenche l'entraînement de sécurité. Trois choses qu'on veut savoir dans l'heure.

3. La qualité des réponses

C'est le signal le plus difficile, et celui où on perd le plus de temps en sophistication avant d'avoir posé les bases.

Commencer par le déterministe. Avant de juger la qualité, je vérifie la structure : JSON valide, champs obligatoires présents, aucun bout de prompt qui fuit, citations qui renvoient à des documents existants, longueur dans les bornes. Ce sont des assertions ordinaires, gratuites. Dans un RAG, une citation cassée est à la fois facile à détecter et grave.

Ensuite, échantillonner et noter. Un petit échantillon continu, noté avec une grille, par un modèle, par un humain, ou les deux. La régularité compte plus que le volume : 50 éléments par jour dessinent une tendance, 2 000 éléments une seule fois n'en dessinent aucune.

Connaître les biais du juge. Un modèle-juge a des défauts documentés : biais de position quand il compare deux réponses, préférence pour les réponses longues, préférence pour sa propre famille de modèles. Je mélange l'ordre, je fais les deux sens, et je prends un juge d'une autre famille que le système évalué.

Recalibrer le juge contre des humains. Si les notes automatiques dérivent pendant que l'accord avec les humains baisse, c'est le juge qui a bougé. Sans ce contrôle, on finit par appeler « régression du modèle » ce qui est en fait un changement de comportement du juge.

Journaliser les signaux côté utilisateur. Régénérations, modifications, copies, abandons, pouces. Bruyants un par un, très clairs en agrégat, et ils mesurent la seule chose qui compte au final.

4. Le coût, rapporté au résultat

Les nombres de tokens sont disponibles sans effort, et on les suit presque toujours trop tard.

Je suis les tokens d'entrée et de sortie par requête, le coût par requête, et surtout le coût par résultat obtenu : par ticket résolu, par suggestion acceptée, par extraction réussie. Le coût par requête pousse vers des réponses bon marché. Le coût par résultat pousse vers des réponses qui servent.

Ensuite, deux dérives à surveiller.

Le contexte qui enfle. Un gabarit de prompt prend quelques centaines de tokens à chaque itération. Chaque ajout se défend, et au total la facture double. Je vérifie le nombre de tokens du prompt en CI, avec un plafond strict que je relève en connaissance de cause.

Les relances en cascade. Un échec de validation déclenche une relance, qui échoue, qui relance. La latence paraît correcte parce que le chemin qui réussit est rapide, et le coût triple. Je suis le nombre de relances par requête comme une série à part, et j'alerte sur le ratio.

L'ordre dans lequel je les mets en place

Du moins sophistiqué au plus sophistiqué :

  1. Tokens et coût par requête : un après-midi, utile tout de suite.
  2. Validité structurelle des sorties : des compteurs, sans modèle, très parlants.
  3. Latence découpée par phase : il faut que le serveur l'expose (vLLM le fait).
  4. Distributions des refus et des longueurs : gratuites une fois les sorties journalisées.
  5. Dérive des embeddings d'entrée : il faut une fenêtre de référence et une tâche planifiée.
  6. Notation de la qualité sur échantillon : il faut une grille, un juge et une calibration.

Beaucoup d'équipes commencent par le 6, parce que c'est le plus intéressant, et arrivent au 1 après la première facture surprise.

Le prérequis

Tout repose sur une habitude : journaliser la requête et la réponse complètes, avec un identifiant de requête, la version du gabarit de prompt et la version du modèle. Par échantillonnage si le volume l'impose, mais en entier sur les requêtes retenues : avec des journaux tronqués, l'analyse après incident s'enlise.

Sans ce journal, on constate que la qualité a baissé. Avec, on retrouve quelle version de prompt, quelle version de modèle et quelles entrées sont en cause, et on passe du constat à la correction.