Aller au contenu
English

Inférence en ligne

  • Leçon 5
  • 35 min
  • Module 2 : Choisir un mode de service

Ce que vous saurez faire

Donnez à l’agent le score d’escalade dès l’ouverture du billet de soutien, et non le lendemain matin. Convenez du temps d’attente permis au centre de soutien. Décidez aussi quoi faire si le score n’arrive pas à temps.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 35 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

Cette leçon ne modifie aucun fichier du projet. Vous mesurez le code de calcul des scores dans une session Python.

Testé avec Python 3.14.6 et Docker Desktop (Docker Engine 29.6.1) sur macOS, avec une puce Apple. Les étapes pour Windows et Linux n’ont pas été testées.

Vous payez par carte dans un magasin. Le terminal envoie le paiement à la banque. Vous attendez à la caisse jusqu’à ce qu’il affiche « approved » (approuvé). Quelques secondes, c’est normal. Après une demi-minute, les gens derrière vous s’impatientent. Si la banque ne répond pas du tout, le magasin a un plan : demander une autre carte ou un paiement comptant.

C’est l’autre façon d’utiliser un modèle. Une personne attend pendant qu’elle pose sa question. La réponse doit arriver dans ce même échange. Il faut aussi un plan en cas d’« absence de réponse ».

Le problème : un signalement avec 23 heures de retard

Section intitulée « Le problème : un signalement avec 23 heures de retard »

Le traitement par lots de la dernière leçon signale un billet le lendemain matin de son arrivée. En juillet, les billets arrivaient à toute heure, surtout de 7 h à 22 h. Un billet reçu à 9 h 05 attend jusqu’à 8 h le lendemain : environ 23 heures. Le client peut demander une escalade dans les 72 heures. Le tiers de ce temps s’écoule donc avant que quelqu’un examine le billet.

Grace demande autre chose : « Montrez le signalement quand l’agent ouvre le billet. Un agent principal pourra alors le prendre avant que le client écrive de nouveau. » Le score doit maintenant être prêt pendant qu’une personne attend. Chaque milliseconde du calcul s’ajoute à l’attente de l’agent.

Le service en ligne signifie que le modèle répond à une requête à la fois, dès son arrivée, pendant que l’appelant attend. L’appel est synchrone : le centre de soutien envoie le billet, attend et continue seulement quand la réponse arrive. Le temps entre la requête et la réponse est la latence.

Un diagramme de séquence avec les trois mêmes acteurs que le diagramme par lots : le système du centre de soutien, escalation-service en ligne et l'équipe principale. À 9 h 05, un agent ouvre le nouveau billet T-120001. Le système du centre de soutien envoie POST /v1/score avec un billet. Le service vérifie le contrat, puis exécute frame, prepare et model. Il répond 200 avec un score de 0,35 et un signalement true, en environ 3 ms. À 9 h 05, le billet rejoint la file d'examen de l'équipe principale. Une note : sans réponse en 50 ms, la page s'ouvre sans score et le traitement par lots nocturne calcule le score du billet.
En ligne : un billet, une requête, une réponse, pendant que l'agent attend. Le signalement arrive à l'équipe principale à 9 h 05, pas le lendemain matin.
Description textuelle du diagramme

Les trois mêmes acteurs que dans le diagramme par lots : le système du centre de soutien, escalation-service (en ligne) et l’équipe principale. À 9 h 05, un agent ouvre le nouveau billet T-120001. Le système du centre de soutien envoie POST /v1/score avec ce seul billet. Le service vérifie le contrat, puis exécute ses trois étapes : frame, prepare et model. Il répond 200 avec le score de 0,35 et le signalement true (vrai), en environ 3 ms. À 9 h 05, le billet rejoint la file d’examen de l’équipe principale. Une note au bas : sans réponse en 50 ms, la page s’ouvre sans score. Le traitement par lots nocturne calcule alors le score du billet.

Comparez ce diagramme à celui du traitement par lots. Les acteurs, le modèle et le contrat sont les mêmes. Seul le moment change : le score arrive en millisecondes. Le signalement parvient donc à l’équipe principale dans la même minute. La route HTTP POST /v1/score arrive au module 3. Dans cette leçon, vous mesurez le travail de calcul des scores derrière cette route.

Omar pose la question qui détermine la conception : « Combien de temps le centre de soutien attend-il? »

Grace répond pour ses agents : « La page du billet doit s’ouvrir en 300 ms. Au-delà, les agents remarquent le délai. » Mais la page fait plus que demander un score. Elle charge le billet et l’historique du client, puis demande la catégorie à ticket-api. L’équipe du centre de soutien a besoin de la majeure partie des 300 ms pour ce travail.

Ils conviennent donc d’un budget de latence : la part du temps total qu’une étape peut utiliser. escalation-service obtient 50 ms sur les 300 ms de la page. Le centre de soutien attend au plus 50 ms pour le score. Sans réponse, la page s’ouvre sans score. Le traitement par lots nocturne calcule le score du billet le lendemain matin. Le modèle ne ralentit jamais la page.

Ce budget est une promesse sur les requêtes lentes, pas sur la requête habituelle. On l’exprime donc en p95 : 95 scores sur 100 doivent arriver en 50 ms. C’est le temps observé par le centre de soutien, réseau compris. (Mesurer la performance du service explique les centiles en détail.) Ce cours conserve ce budget : p95 inférieur à 50 ms. Le module 4 mesure si le service le respecte.

Quelle part du budget le modèle utilise-t-il? Faites d’abord une prédiction, puis essayez l’explorateur. La première valeur est mesurée; les autres sont simulées, comme si le modèle était plus lent, par exemple un grand modèle de texte.

Explorer

Un billet face aux 50 ms du centre de soutien

Durée par billet
1,76 ms (mesuré)
Billets dans la requête
1 billet
Une requête prend
1,76 ms
Résultat
Le service en ligne convient

1 billet × 1,76 ms par billet (mesuré) = 1,76 ms. Un agent ouvre un billet, et le score arrive bien avant les 50 ms du centre d’assistance. Par HTTP, la requête entière a pris environ 3 à 4 ms (le module 4 la mesure). Le service en ligne convient.

  • Tient dans les 50 ms du centre d’assistance
  • Plus de 50 ms, moins de 30 s
  • Plus de 30 s : utilisez une tâche
  • Pâle, pointillé : durée simulée
Une requête : 1,76 ms1,76 msle centre d’assistance attend 50 msla passerelle arrête à 30 s1 ms10 ms100 ms1 s10 s1 min10 min1 hDurée d’une requête (chaque pas : ×10)
Une requête : 1,76 ms1,76 msassistance : 50 mspasserelle : 30 s1 ms100 ms10 s1 hDurée d’une requête (chaque pas : ×10)
Une requête avec un billet. Faites passer le temps par billet de la mesure de 1,76 ms à des modèles simulés plus lents. Comparez la requête au budget de 50 ms.
Description textuelle de l’explorateur

L’explorateur montre une requête avec un billet sur un axe de temps allant de 1 ms à 1 heure. Chaque graduation vaut 10 fois la précédente. Deux lignes pointillées marquent les limites : le centre de soutien attend 50 ms et la passerelle arrête toute requête après 30 secondes. Une commande règle le temps par billet : 1,76 ms (mesuré), puis 5, 10, 20, 25, 40, 100 et 400 ms (simulés). Les temps simulés ont un remplissage pâle et un contour pointillé.

Avec la mesure de 1,76 ms, la requête prend 1,76 ms, bien en dessous de 50 ms. Sur HTTP, la requête complète a pris environ 3 à 4 ms. Jusqu’à 25 ms par billet, la requête respecte facilement le budget. À 40 ms, elle le respecte encore, mais en utilise la majeure partie. À 100 ms et 400 ms, un billet prend plus de 50 ms. La page s’ouvre donc sans score chaque fois. La réponse à la question : environ 28 fois plus lent (50 ÷ 1,76), entre les étapes de 40 ms et 100 ms.

Le modèle a une marge : environ 28 fois son temps actuel. Mais il ne peut pas utiliser toute cette marge. Les 50 ms doivent aussi couvrir le réseau et les moments lents où plusieurs agents ouvrent des billets en même temps. Le cas d’échec à la fin de cette leçon montre un tel moment.

Tomás a une idée : « Si chaque requête prend du temps, envoyez tous les billets du jour dans une seule requête. » Avant de lui répondre, mesurez le temps que prend un appel à score_tickets. Lancez une session Python dans le dossier du projet (python), puis entrez :

Python session, in ~/projects/escalation-service
import time
import pandas as pd
from escalation.settings import load_settings
from escalation.bundle import load_bundle
from escalation.batch import COLUMNS, check_rows
from escalation.scoring import score_tickets
settings = load_settings()
bundle = load_bundle(settings.model_bundle, settings.model_sha256)
rows = pd.read_csv("data/july_tickets.csv", usecols=COLUMNS, dtype=str, keep_default_na=False, na_values=[""])
tickets, bad = check_rows(rows, first_row=2)
len(tickets), len(bad)

La session charge l’ensemble une seule fois, avec toutes les vérifications. Elle réutilise check_rows du traitement par lots pour transformer les 1 400 lignes de juillet en billets vérifiés. La sortie :

(1400, 0)

Calculez maintenant les scores de deux façons. D’abord, un appel par billet, comme dans un service en ligne. Puis un appel pour tous les billets, comme dans le traitement par lots. La première ligne est un appel de préparation. Ainsi, le premier appel mesuré n’est pas plus lent que les autres :

Python session (continued)
results, timings = score_tickets(bundle, tickets[:1])
start = time.perf_counter()
for ticket in tickets:
results, timings = score_tickets(bundle, [ticket])
one_by_one = time.perf_counter() - start
timings.header()
start = time.perf_counter()
results, timings = score_tickets(bundle, tickets)
all_at_once = time.perf_counter() - start
timings.header()
print(f"one by one: {one_by_one:.2f} s, all at once: {all_at_once * 1000:.1f} ms, {one_by_one / all_at_once:.0f} times faster")

Appuyez sur Entrée sur la ligne vide après la boucle for pour l’exécuter. Les sorties, dans l’ordre (sur l’ordinateur de test, sans conteneur) :

'frame;dur=0.46, prepare;dur=1.09, model;dur=0.04'
'frame;dur=2.98, prepare;dur=1.77, model;dur=0.07'
one by one: 2.44 s, all at once: 5.7 ms, 429 times faster

La première ligne correspond au dernier billet individuel : 1,59 ms de travail, dont seulement 0,04 ms pour le modèle. La construction du tableau (frame) et le prétraitement (prepare) prennent 39 fois plus de temps que le modèle. La deuxième ligne correspond aux 1 400 billets en un appel : environ trois fois le travail pour un seul billet. La majeure partie du coût d’un appel est un coût fixe qui n’augmente pas avec le nombre de billets. timings.header() est l’en-tête Server-Timing que le service envoie au module 3.

Amira a fait la même mesure dans un conteneur avec 1 CPU, la taille utilisée au module 4 :

Les mêmes 1 400 billets, le même modèle. Un appel par billet a pris 2,46 secondes (1,76 ms par billet). Un appel pour tous a pris 7 ms : environ 350 fois moins. La deuxième barre est trop courte pour être visible.
Description textuelle du graphique

Un graphique à colonnes montre deux mesures dans un conteneur avec 1 CPU, pour les 1 400 billets de juillet avec score_tickets. Un appel par billet : 2 460 ms, soit 1,76 ms par billet. Un appel pour tous les billets : 7 ms, soit 0,005 ms par billet. La deuxième colonne est si courte que seule son étiquette, 7 ms, apparaît au-dessus de l’axe. Le rapport est d’environ 350. Les étapes d’un appel pour un billet étaient frame à 0,64 ms, prepare à 1,23 ms et model à 0,05 ms. Pour les 1 400 billets, elles étaient frame à 2,80 ms, prepare à 1,70 ms et model à 0,07 ms.

Sur votre ordinateur, les valeurs sont différentes et changent d’une exécution à l’autre. La tendance ne change pas : un appel par billet répète le coût fixe 1 400 fois.

Tomás a donc en partie raison. Un appel pour plusieurs billets a un débit beaucoup plus élevé. C’est exactement pourquoi le traitement par lots calcule les scores de 500 billets par appel. Mais le service en ligne reçoit un billet par requête, parce que l’agent ouvre un seul billet. Il paie le coût fixe à chaque requête pour offrir une courte attente. À 1,76 ms, ce coût respecte facilement les 50 ms.

Un traitement par lots doit fonctionner une fois par nuit. Un service en ligne doit répondre chaque fois qu’un billet arrive. En juillet, les billets arrivaient à toute heure. La proportion du temps où un service répond correctement est sa disponibilité. Par exemple, une disponibilité de 99,9 % permet environ 43 minutes sans réponse dans un mois de 30 jours.

Deux choses rendent cela abordable chez Larkfield. D’abord, le centre de soutien a un plan en cas d’« absence de réponse ». La page s’ouvre sans score et le traitement par lots nocturne rattrape le travail. Une heure sans service retarde donc certains signalements, mais ne bloque jamais un agent. Ensuite, le service doit indiquer clairement quand il n’est pas prêt, pour que la plateforme ne lui envoie aucune requête. C’est le rôle du point de terminaison /ready du module 3.

Le service en ligne a aussi besoin de limites des requêtes : des règles sur ce qu’une requête peut demander. Elles protègent la latence pour tous les autres. Si un appelant envoie 50 000 billets dans une requête, tous les agents derrière cette requête attendent.

  • Un billet par requête pour POST /v1/score. Le corps contient un seul TicketIn, donc le travail d’une requête reste toujours petit.
  • Une limite de taille du corps. Le service refuse un corps de plus de 2 000 000 octets avant de le lire. L’API de prix des maisons du cours sur les API utilisait la même idée (Partie 5).
  • Des délais d’expiration des deux côtés. Le centre de soutien attend au plus 50 ms. La passerelle de Larkfield, le serveur mandataire devant chaque service, arrête toute requête après 30 secondes.

La limite de 30 secondes compte pour les gros travaux, comme un mois complet de billets. La prochaine leçon commence là.

Le service en ligne convient quand une personne ou un programme attend une réponse immédiate. Exemples : un signalement à l’ouverture d’un billet, une vérification de fraude pendant un paiement, un prix sur une page.

Il ne convient pas quand personne n’attend ou quand le travail d’une requête dépasse le temps d’attente de l’appelant. Utilisez alors un traitement par lots ou une tâche en file d’attente.

Il coûte plus cher que le traitement par lots. Un service fonctionne toute la journée et quelqu’un le surveille. Il faut un budget de latence, des limites, une vérification de santé et un plan en cas d’« absence de réponse ». Il paie aussi le coût fixe d’un appel pour chaque billet.

  1. Ouvrez un terminal dans ~/projects/escalation-service et activez .venv.

  2. Définissez MODEL_SHA256 avec l’empreinte de votre ensemble, comme à l’étape 3 d’Inférence par lots : exercice guidé.

  3. Lancez Python avec python. L’invite devient >>>.

  4. Entrez le premier bloc d’Où passe le temps d’une requête. La dernière ligne affiche (1400, 0).

  5. Entrez le deuxième bloc. Appuyez sur Entrée sur la ligne vide après la boucle for.

  6. Comparez vos deux lignes header(). Vérifiez que model est la plus courte des trois étapes dans les deux cas.

  7. Lisez la dernière ligne. Un appel par billet est quelques centaines de fois plus lent qu’un appel pour tous.

  8. Quittez Python avec exit().

Afficher un exemple de sortie

Sur l’ordinateur de test, trois exécutions de la même session ont pris 2,43, 2,84 et 2,54 secondes, billet par billet. Tous ensemble, les billets ont pris de 5,7 à 5,8 ms : 417, 499 et 435 fois plus vite. D’autres programmes fonctionnaient sur l’ordinateur, donc les temps variaient. Vos valeurs peuvent être plus grandes ou plus petites. Si le calcul billet par billet est moins de 100 fois plus lent, vérifiez la boucle. Elle doit appeler score_tickets avec [ticket], une liste d’un seul élément.

Avant de mesurer, prédisez le temps nécessaire pour les 100 premiers billets de juillet, évalués individuellement et en un seul appel. Utilisez les chiffres de cette leçon : 1,76 ms par billet individuellement, et environ 7 ms pour 1 400 billets en un appel. Remplacez ensuite les deux tickets du deuxième bloc par tickets[:100], puis exécutez-le.

Afficher la réponse

La prédiction : individuellement, 100 × 1,76 ms ≈ 180 ms. En un appel, moins que les 7 ms pour 1 400 billets. Le coût fixe représente la majeure partie du temps d’un appel : environ 2 ms.

Sur l’ordinateur de test, quatre exécutions ont donné 199, 321, 239 et 286 ms pour les évaluations individuelles. Un seul appel a pris de 2,2 à 5,6 ms. Le temps des évaluations individuelles dépasse la prédiction parce que l’ordinateur exécutait d’autres tâches. Un appel pour 100 billets coûte à peu près autant qu’un appel pour un billet : encore le coût fixe.

Erreur courante

Rapide seul, trop lent ensemble

Symptôme : Tomás envoie une requête à la fois. Chaque réponse prend environ 4 ms, bien en dessous de 50 ms. « Ça respecte toujours la limite », dit-il. Puis le banc d’essai du module 4 envoie 8 requêtes simultanées à 1 processus sur 1 CPU. Chaque réponse est 200, et le journal ne montre aucune erreur :

1000 requests, concurrency 8: p50 31.6 ms, p95 72.5 ms, p99 105.7 ms, max 136.1 ms, 216.5 requests/s, 0 errors.

Le p95 est de 72,5 ms, au-dessus du budget de 50 ms. Si 8 agents ouvraient des billets au même moment, plus d’un billet sur 20 s’ouvrirait sans score.

Cause : un processus évalue une requête à la fois. Quand 8 requêtes arrivent ensemble, chacune attend celles qui la précèdent. La requête habituelle (p50) a pris 31,6 ms au lieu de 4 ms, et les plus lentes ont pris davantage.

Solution : comparez le p95 au budget avec la charge réelle du service. Larkfield reçoit environ 45 billets par jour, donc 8 à la fois n’arrive presque jamais. Avec une requête à la fois, le p95 était de 6,1 ms. Si la charge augmente, donnez plus de CPU au service. Avec 8 requêtes simultanées, 2 processus sur 2 CPU avaient un p95 de 37,3 ms. Mesurer la performance du service montre comment le mesurer.

Pour un vrai service d’IA : une requête rapide ne prouve pas grand-chose. Convenez d’un budget exprimé en percentile, et mesurez-le avec la charge réelle et pendant une période occupée. Gardez le plan prévu en cas d’« absence de réponse », parce que cela arrivera un jour.

Vérifiez votre compréhension

La page de Grace dispose de 300 ms au total. Pourquoi escalation-service n'en reçoit-il que 50 ms? Que se passe-t-il si le score d'un billet n'arrive pas en 50 ms?

Afficher la réponse

La page charge aussi le billet, l’historique du client et la catégorie depuis ticket-api. L’équipe de soutien a besoin de la majeure partie des 300 ms pour cela. Le score reçoit donc un budget de 50 ms. Le service de soutien cesse d’attendre après 50 ms et ouvre la page sans score. Le traitement par lots nocturne évalue ensuite ce billet le lendemain matin. Le signalement arrive donc en retard, mais l’agent n’est jamais bloqué.

Vérifiez votre compréhension

Un appel de score_tickets pour un billet a pris environ 1,8 ms. Un appel pour 1 400 billets a pris environ 7 ms. Expliquez pourquoi et ce que cela signifie pour l'évaluation en ligne et par lots.

Afficher la réponse

La majeure partie du coût d’un appel est fixe. Construire la table et exécuter le prétraitement prennent à peu près le même temps pour un billet ou plusieurs. Le modèle lui-même ne coûte presque rien (0,05 ms). Un appel par billet paie ce coût fixe 1 400 fois. L’évaluation par lots, avec plusieurs billets par appel, offre donc un débit beaucoup plus élevé. Le service en ligne paie le coût fixe à chaque requête pour donner une réponse rapide. Cela convient tant qu’il respecte le budget de latence.

Vérifiez votre compréhension

Tomás veut que POST /v1/score accepte jusqu'à 50 000 billets dans une requête. Donnez deux raisons de ne pas le faire.

Afficher la réponse

Premièrement, une requête contenant beaucoup de billets prend beaucoup plus de temps. Les requêtes des agents attendent derrière elle, donc leurs scores dépassent le budget de 50 ms. Deuxièmement, une grosse requête peut dépasser la limite de 30 s de la passerelle et échouer complètement. Et un corps de 50 000 billets dépasse largement la limite de 2 000 000 octets. Un billet par requête permet de garder chaque requête petite. Les gros traitements vont dans le traitement par lots ou dans une tâche mise en file d’attente (prochaine leçon).

Récapitulatif

  • Le service en ligne répond à une requête à la fois pendant que l’appelant attend : c’est un appel synchrone. Son chiffre clé est la latence, le temps entre la requête et la réponse.
  • Le budget de latence de Larkfield : escalation-service reçoit 50 ms des 300 ms de la page du service de soutien. Sans réponse en 50 ms, la page s’ouvre sans score, et le traitement par lots nocturne rattrape le retard.
  • Un appel coûte environ 1,76 ms par billet, dont seulement 0,05 ms pour le modèle. Évaluer 1 400 billets en un appel a pris 7 ms : le coût fixe d’un appel domine.
  • Un service en ligne doit être disponible chaque fois que des billets arrivent. Des limites gardent chaque requête petite : un billet, une taille maximale du corps et des délais d’expiration.
  • Un budget est une promesse concernant les requêtes lentes (p95 sous 50 ms). Avec 8 requêtes simultanées sur 1 CPU, le p95 était de 72,5 ms. La charge réelle de Larkfield est d’environ 45 billets par jour.

Ensuite, vous gérez un travail trop volumineux pour une seule requête : un mois entier de billets ou un modèle beaucoup plus lent.

Leçon suivante : Inférence asynchrone