Inférence asynchrone
- Leçon 6
- 45 min
- Module 2 : Choisir un mode de service
Leçon 6 sur 15 · Déployer des modèles d'IA pour de vrais utilisateursInférence asynchrone
1. Définir le contrat d'inférence
2. Choisir un mode de service
3. Créer le service de prédiction
4. Choisir l'infrastructure et mesurer
5. Mettre en production, surveiller et rétablir
Ce que vous saurez faire
Calculez les scores d’un travail trop volumineux pour une seule requête, comme un mois de billets de soutien avec un modèle lent. L’appelant reçoit une réponse aussitôt et récupère les scores plus tard. Le service reste rapide pour les agents.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- La tâche par lots et ses sorties, présentées dans Inférence par lots.
- Le budget de latence (p95 sous 50 ms pour un score), la limite de 30 s de la passerelle et le coût fixe d’un appel, présentés dans Inférence en ligne.
Ce qu’il vous faut sur votre ordinateur
escalation/settings.py,scoring.pyetbatch.py, ainsi quedata/july_tickets.csv, de Inférence par lots : exercices guidés.- Un terminal dans
~/projects/escalation-serviceavec.venvactivé etMODEL_SHA256défini, comme à l’étape 3 de ces exercices. - Si votre projet manque ou ne fonctionne plus, copiez l’instantané
end-of-m01et faites les exercices guidés de Inférence par lots. Utiliser un instantané présente les étapes.
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.
Un reçu chez le nettoyeur
Section intitulée « Un reçu chez le nettoyeur »Vous apportez un manteau chez le nettoyeur. Personne ne le nettoie pendant que vous attendez au comptoir. Vous recevez un reçu papier avec un numéro et vous revenez demain. Vous montrez le numéro et vous récupérez votre manteau. Si le portant est plein, le commerce vous le dit aussitôt : « Pas aujourd’hui, revenez demain. » Vous n’attendez pas au comptoir, et le commerce nettoie les manteaux à son rythme.
Le problème : un travail plus long que l’attente permise
Section intitulée « Le problème : un travail plus long que l’attente permise »L’équipe qualité de Larkfield vérifie un mois de billets à la fois. Son outil envoie le mois à escalation-service et attend les scores en retour. Pour juillet, cela représente 1 400 billets.
Avec le modèle actuel, ce n’est pas un problème. Un appel pour 1 400 billets a pris 7 ms. Mais Priya teste un modèle de texte qui lit les mots de chaque billet, et ces modèles sont beaucoup plus lents. Supposons qu’il lui faut 40 ms par billet. Le mois prend alors 1 400 × 40 ms = 56 s. La passerelle de Larkfield arrête chaque requête après 30 s. La requête ne peut donc jamais se terminer. L’équipe qualité reçoit une erreur, et le service a travaillé 30 s pour rien.
Où se trouve la limite? Faites une prédiction, puis essayez. Le premier temps par billet est mesuré; les temps plus longs sont simulés.
Explorer
1 400 billets dans une requête face à la limite de 30 s de la passerelle
Prédisez d’abord : À partir de quel temps par billet une requête de 1 400 billets dépasse-t-elle les 30 s de la passerelle?
- Durée par billet
- 1,76 ms (mesuré)
- Billets dans la requête
- 1 400 billets
- Une requête prend
- 2,46 s
- Résultat
- Une requête, une longue attente
1 400 billets × 1,76 ms par billet (mesuré) = 2,46 s. Une requête finit encore avant la limite de 30 s de la passerelle, mais l’appelant attend avec la connexion ouverte. (Un seul appel pour tous est beaucoup plus rapide : 1 400 billets en un appel ont pris 7 ms.)
- 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
Description textuelle de l’explorateur
L’explorateur montre une requête de 1 400 billets sur un axe temporel allant de 1 ms à 1 heure. Chaque graduation de l’axe vaut 10 fois la précédente. Des lignes pointillées indiquent les 50 ms du centre de soutien et les 30 s de la passerelle. 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é.
L’explorateur suppose que chaque billet prend tout le temps indiqué, comme le ferait un modèle lent. À 1,76 ms, le temps mesuré, cela donne 2,46 s. C’est le temps mesuré pour 1 400 billets traités un à un; un appel pour les 1 400 a pris seulement 7 ms. À 5 ms, le travail prend 7 s, et à 10 ms, 14 s. À 20 ms, il prend 28 s : encore sous 30 s, mais près de la limite. À 25 ms, il prend 35 s; à 40 ms, 56 s; à 100 ms, 2,3 minutes; à 400 ms, 9,3 minutes. Dès 25 ms, la passerelle arrête la requête, et l’explorateur indique d’utiliser une tâche. La limite exacte est de 30 000 ms ÷ 1 400 ≈ 21,4 ms par billet.
La limite est d’environ 21 ms par billet : 30 000 ms ÷ 1 400. Et 20 ms est déjà risqué. Les 28 s ne laissent aucune marge pour une période chargée ou un mois légèrement plus volumineux.
L’explorateur multiplie le nombre de billets par le temps par billet. Pour le modèle actuel, c’est trop pessimiste. La majeure partie des 1,76 ms correspond au coût fixe d’un appel. Un appel pour les 1 400 billets a pris 7 ms. Un modèle de texte lent consacre son temps à chaque billet. La multiplication est donc juste pour les temps simulés.
Tomás demande : « Alors, faites attendre la passerelle 10 minutes. » Cela déplace le problème. L’outil qualité garde une connexion ouverte pendant 10 minutes. Une interruption réseau à la neuvième minute fait perdre tout le travail. Pendant ce temps, le service consacre son CPU à une énorme requête. Les agents attendent leurs scores prévus en 50 ms.
L’idée : l’inférence asynchrone
Section intitulée « L’idée : l’inférence asynchrone »L’inférence asynchrone sépare la requête du travail. L’appelant envoie le travail et reçoit aussitôt un reçu. Le service fait le travail plus tard, en arrière-plan. L’appelant revient chercher le résultat. Asynchrone signifie « pas en même temps » : la réponse n’arrive pas dans le même échange que la question.
Le travail que l’appelant transmet est une tâche. Les tâches qui attendent leur tour forment une file d’attente, où la première entrée est la première sortie. Trois éléments HTTP jouent le rôle du reçu du nettoyeur :
202 Accepted: « J’ai votre travail, mais il n’est pas terminé. » Le corps contient l’identifiant de tâche, le numéro sur le reçu papier.Location: /v1/jobs/<id>: où demander des nouvelles de cette tâche.Retry-After: 2: combien de secondes attendre avant de demander à nouveau. Demander à répétition jusqu’à la fin de la tâche s’appelle l’interrogation périodique.
Description textuelle du diagramme
Les trois mêmes acteurs : le système du centre de soutien, escalation-service (tâches) et l’équipe senior. Le système du centre de soutien envoie POST /v1/jobs avec 1 400 billets. Le service répond aussitôt : 202 Accepted (accepté), avec un identifiant de tâche, l’état queued (en attente), un en-tête Location (emplacement) et Retry-After: 2 (réessayez après 2 secondes). La tâche attend dans la file, puis le processus de traitement calcule ses scores. Dans une boucle, toutes les Retry-After secondes, le système du centre de soutien envoie GET /v1/jobs/{id}. Le service répond queued ou running (en cours). Puis un GET reçoit done (terminé), avec les 1 400 scores. Le système du centre de soutien transmet la liste à examiner à l’équipe senior. Une note au bas : si la file est pleine, le service répond 429; réessayez plus tard.
L’exécuteur de tâches : jobs.py
Section intitulée « L’exécuteur de tâches : jobs.py »Créez escalation/jobs.py. Il contient les tâches, la file d’attente et un fil d’exécution qui calcule les scores des tâches l’une après l’autre :
"""Asynchronous scoring: accept a large job now, score it later, keep the result.
POST /v1/jobs 202 Accepted, with the job's ID and status URL GET /v1/jobs/{job_id} queued → running → done (or failed), and the scores
One worker thread takes jobs from a queue, one at a time. The queue has alimit: when it is full, a new job gets 429, so a burst cannot use all memory.
The jobs live in this process's memory. That is enough to learn the pattern,but a restart loses every job, and a second worker process cannot see them.A real service keeps jobs in a queue service and results in storage (see the course *Scaling APIs and AI Workloads*)."""
import loggingimport queueimport threadingimport timeimport uuidfrom dataclasses import dataclass, fieldfrom datetime import datetime, timezone
from escalation.contract import TicketInfrom escalation.scoring import score_tickets
logger = logging.getLogger("escalation")
class QueueFull(Exception): pass
@dataclassclass Job: id: str tickets: list[TicketIn] status: str = "queued" created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat(timespec="seconds")) finished_at: str | None = None seconds: float | None = None model_version: str | None = None results: list[dict] | None = None error: str | None = None
class JobRunner: def __init__(self, get_bundle, max_queued: int, extra_ms_per_ticket: float = 0.0): self.get_bundle = get_bundle self.extra_ms = extra_ms_per_ticket self.queue: queue.Queue[Job] = queue.Queue(maxsize=max_queued) self.jobs: dict[str, Job] = {} self.thread = threading.Thread(target=self.work, daemon=True, name="job-worker") self.thread.start()
def submit(self, tickets: list[TicketIn]) -> Job: job = Job(id=uuid.uuid4().hex[:12], tickets=tickets) try: self.queue.put_nowait(job) except queue.Full: raise QueueFull from None self.jobs[job.id] = job return job
def work(self) -> None: while True: job = self.queue.get() job.status = "running" started = time.perf_counter() try: bundle = self.get_bundle() results, _ = score_tickets(bundle, job.tickets) if self.extra_ms: # a stand-in for a slow model, such as a large text model time.sleep(self.extra_ms * len(job.tickets) / 1000) job.results = [ {"ticket_id": t.ticket_id, "score": r.score, "flag": r.flag} for t, r in zip(job.tickets, results) ] job.model_version = bundle.version job.status = "done" except Exception as error: # a failed job must not stop the worker logger.exception("job %s failed", job.id) job.status, job.error = "failed", str(error) job.seconds = round(time.perf_counter() - started, 3) job.finished_at = datetime.now(timezone.utc).isoformat(timespec="seconds") job.tickets = [] # the input is not needed any more self.queue.task_done()Lisez-le en trois parties. submit donne un identifiant à la tâche et la place dans la file sans attendre. Si la file est pleine, put_nowait échoue aussitôt, et submit lève QueueFull. work s’exécute sans arrêt dans son propre fil d’exécution. Il prend la tâche suivante et calcule les scores de tous ses billets en un seul appel à score_tickets. Il stocke ensuite les résultats dans la tâche. Si quelque chose échoue, la tâche reçoit l’état failed (échec) avec l’erreur. Le fil d’exécution passe à la tâche suivante.
extra_ms_per_ticket sert seulement à l’apprentissage. Il ajoute une pause de ce nombre de millisecondes par billet, pour simuler un modèle lent. Son paramètre, EXTRA_MS_PER_TICKET, vaut 0 sauf si vous le modifiez.
Les routes HTTP POST /v1/jobs et GET /v1/jobs/{id} sont présentées dans Connecter l’API. Ici, vous essayez l’exécuteur directement, dans une session Python.
Exemple détaillé : deux tâches dans une session Python
Section intitulée « Exemple détaillé : deux tâches dans une session Python »Amira démarre Python dans le dossier du projet. Elle charge le paquet et les billets de juillet, comme dans la leçon précédente :
import timeimport pandas as pdfrom escalation.settings import load_settingsfrom escalation.bundle import load_bundlefrom escalation.batch import COLUMNS, check_rowsfrom escalation.jobs import JobRunner, QueueFullsettings = 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)Une petite tâche. Elle crée un exécuteur avec une file de 4 places, la valeur par défaut de MAX_QUEUED_JOBS, et soumet 3 billets :
runner = JobRunner(lambda: bundle, max_queued=4)job = runner.submit(tickets[:3])job.id, job.statustime.sleep(1)job.status, job.seconds, job.model_versionjob.results[0]Les sorties :
('68e0ec7a36af', 'running')('done', 0.004, '1.0.0'){'ticket_id': 'T-119233', 'score': 0.037624397312637836, 'flag': False}submit a répondu aussitôt avec un identifiant. Le fil d’exécution avait déjà pris la tâche; son état était donc running, et non queued. Une seconde plus tard, la tâche était done. Elle a pris 4 ms, et son premier résultat est le score de T-119233 obtenu par la tâche par lots. Le même modèle et le même contrat donnent le même score dans chaque mode.
Une tâche lente. Amira simule maintenant le modèle de texte lent de Priya avec 40 ms par billet (simulées) et soumet le mois entier. Puis, pendant que la grosse tâche s’exécute, elle soumet cinq petites tâches de 10 billets :
slow = JobRunner(lambda: bundle, max_queued=4, extra_ms_per_ticket=40)start = time.perf_counter()big = slow.submit(tickets)print(f"accepted in {(time.perf_counter() - start) * 1000:.1f} ms: job {big.id}, {big.status}")time.sleep(0.5)for n in range(1, 6): try: small = slow.submit(tickets[:10]) print(f"job {n} of 10 tickets: accepted") except QueueFull: print(f"job {n} of 10 tickets: refused, the queue is full")
while big.status in ("queued", "running"): print(f"after {time.perf_counter() - start:2.0f} s: {big.status}") time.sleep(10)
big.status, big.seconds, len(big.results)Les sorties :
accepted in 2.9 ms: job 19733f90f765, runningjob 1 of 10 tickets: acceptedjob 2 of 10 tickets: acceptedjob 3 of 10 tickets: acceptedjob 4 of 10 tickets: acceptedjob 5 of 10 tickets: refused, the queue is fullafter 1 s: runningafter 11 s: runningafter 21 s: runningafter 31 s: runningafter 41 s: runningafter 51 s: running('done', 56.014, 1400)Le mois a été accepté en 2,9 ms, même si le travail a pris 56 s. Dans une seule requête, il aurait échoué à 30 s. La pause d’une demi-seconde a laissé le temps au fil d’exécution de prendre la grosse tâche. La file était donc vide. Quatre petites tâches l’ont ensuite remplie, et la cinquième a été refusée aussitôt. La boucle fait de l’interrogation périodique : elle demande des nouvelles toutes les 10 secondes, comme un client avec Retry-After. Après 56 s, la tâche était done avec 1 400 résultats : 1 400 × 40 ms = 56 s, comme prévu.
Les quatre petites tâches ont attendu derrière la grosse. Cette attente est le prix à payer pour un seul fil d’exécution. Chaque tâche échappe à la limite de la passerelle, mais elle peut attendre plusieurs minutes dans une file chargée.
La vie d’une tâche
Section intitulée « La vie d’une tâche »Chaque tâche passe par quelques états :
Description textuelle du diagramme
Une tâche commence dans l’état queued, après que POST /v1/jobs a répondu 202. Si la file est pleine, la requête est refusée avec 429, et aucune tâche n’existe. Le processus de traitement prend une tâche en attente, et elle passe à running. Une tâche en cours se termine dans l’état done, avec ses scores conservés dans la tâche. Elle peut aussi se terminer dans l’état failed, avec son erreur conservée dans la tâche. Une note sur running : les tâches sont dans la mémoire de ce processus; un redémarrage les perd donc toutes.
Trois limites protègent la file. Elle contient au plus MAX_QUEUED_JOBS (4) tâches en attente. Un afflux de tâches ne peut donc pas utiliser toute la mémoire : la cinquième reçoit 429 too_many_jobs (trop de tâches). Une tâche contient au plus MAX_JOB_TICKETS (5 000) billets. Elle ne peut donc pas occuper le processus de traitement pendant des heures. Au-delà, la réponse est 413 job_too_large (tâche trop volumineuse). Enfin, les résultats restent dans la mémoire d’un processus jusqu’à son redémarrage. C’est le point faible, comme le montre le cas d’échec à la fin de cette leçon. Un vrai service conserve les résultats pendant une durée fixe, par exemple un jour, et l’indique dans sa documentation.
Choisir un mode de service
Section intitulée « Choisir un mode de service »Vous connaissez maintenant trois modes. Le même modèle et la même fonction score_tickets se trouvent derrière chacun. Ce qui change, c’est qui lance le travail, qui attend et comment les résultats arrivent. Trois questions permettent de choisir :
- Qui attend le résultat, et combien de temps peut-on attendre? Personne, jusqu’à une heure connue : par lots. Une personne, moins d’une seconde : en ligne. Un programme, plusieurs minutes : asynchrone.
- Quelle est la taille d’un travail? Un cas : en ligne. Plusieurs cas qui arrivent ensemble à une heure prévue : par lots. Plusieurs cas envoyés par quelqu’un à la demande : asynchrone.
- Que se passe-t-il en cas d’échec? La réponse donne le plan de reprise dont le mode a besoin.
Le tableau résume le module.
| Par lots | En ligne | Asynchrone | |
|---|---|---|---|
| Qui lance le travail | Un planificateur, à une heure fixe | L’appelant, une requête par cas | L’appelant, une tâche pour plusieurs cas |
| Combien de temps l’appelant attend | Personne n’attend; les résultats sont prêts à une heure connue | Toute la latence : budget de 50 ms chez Larkfield | Quelques millisecondes pour le reçu (202), puis des minutes pour le résultat |
| Comment les résultats arrivent | Fichiers : scores.csv, summary.json |
Dans la réponse : score, indicateur, version du modèle | Interrogation périodique avec GET /v1/jobs/{id} jusqu’à done |
| Nombre clé | Débit : 1 400 billets en 0,75 s | Latence : environ 3 à 4 ms par requête | Temps dans la file plus le travail |
| Limites | 5 % de lignes incorrectes; colonnes du contrat | Un billet, taille du corps, délais d’expiration | File de 4 places (429), 5 000 billets (413) |
| Reprise après un échec | Relancer dans le même dossier : mêmes entrées et même paquet, mêmes sorties | Plan de l’appelant sans réponse : ouvrir la page sans score; le traitement par lots complète plus tard | Soumettre à nouveau après 429, failed ou une tâche perdue; conserver les tâches dans un service de file d’attente et un stockage |
| Exemple chez Larkfield | Le fichier nocturne de Mei pour l’examen du matin | L’indicateur quand un agent ouvre un billet | Un mois de billets pour l’équipe qualité, avec un modèle lent |
Quand choisir l’inférence asynchrone
Section intitulée « Quand choisir l’inférence asynchrone »Utilisez l’inférence asynchrone quand un appelant demande un travail à la demande, trop long pour l’attente permise dans une requête. Les cas courants comprennent les gros fichiers, les modèles lents et les travaux dont vous ne pouvez pas prévoir la durée.
Ne l’utilisez pas quand le travail respecte le budget de latence ou se fait seulement à des heures prévues. Les modes en ligne et par lots sont plus simples.
Elle coûte plus cher que les deux autres. Le service doit stocker les tâches et les résultats, limiter la file et supprimer les anciens résultats. Chaque appelant a aussi besoin d’une boucle d’interrogation périodique. Un vrai système a également besoin d’un service de file d’attente et d’un stockage. Ce sont d’autres éléments à exploiter et à payer.
Exercices guidés : exécuter les tâches
Section intitulée « Exercices guidés : exécuter les tâches »-
Créez
escalation/jobs.py, comme dans L’exécuteur de tâches : jobs.py. -
Ouvrez un terminal dans
~/projects/escalation-service, activez.venvet définissezMODEL_SHA256. -
Démarrez Python avec
pythonet saisissez le premier bloc de Exemple détaillé : deux tâches dans une session Python. -
Saisissez le bloc de la petite tâche. Vérifiez que l’état devient
doneet quejob.results[0]contient le score0.037624397312637836pour T-119233. -
Saisissez le bloc de la tâche lente. Attendez-vous à ce que la boucle s’exécute pendant environ une minute.
-
Vérifiez la dernière ligne :
'done', environ 56 secondes et 1 400 résultats. Vérifiez qu’exactement une des cinq petites tâches a été refusée. -
Quittez Python avec
exit(). -
Exécutez
python -m pytest. Il indique encore20 passed(20 tests réussis) :jobs.pyreçoit ses tests avec les routes HTTP dans le module 3.
Afficher ce qui peut différer sur votre ordinateur
Vos identifiants de tâche sont différents : ils sont aléatoires. L’état juste après submit peut être queued ou running. Cela dépend du fait que le fil d’exécution a déjà pris la tâche ou non. Deux petites tâches peuvent être refusées au lieu d’une. Dans ce cas, le fil d’exécution n’avait pas pris la grosse tâche après une demi-seconde. La grosse tâche occupait donc aussi une place dans la file. Votre valeur seconds pour la tâche lente dépasse légèrement 56. La pause de 56 s est simulée, et le calcul réel des scores ajoute quelques millisecondes.
À vous de jouer : la plus grande requête qui respecte la limite
Section intitulée « À vous de jouer : la plus grande requête qui respecte la limite »Le modèle de texte de Priya prend 40 ms par billet de soutien (simulation). Combien de billets une requête peut-elle contenir au maximum sans dépasser la limite de 30 secondes de la passerelle? Notez votre réponse, puis vérifiez-la avec l’explorateur. Sa deuxième commande règle le nombre de billets.
Explorer
Temps par billet et billets par requête
- Durée par billet
- 40 ms (simulé)
- Billets dans la requête
- 1 400 billets
- Une requête prend
- 56 s
- Résultat
- Utilisez une tâche
1 400 billets × 40 ms par billet (simulé) = 56 s. La passerelle arrête toute requête après 30 s, donc cette requête ne peut jamais finir. Envoyez-la comme tâche : le service répond 202 tout de suite, et les scores sont prêts après environ 56 s.
- 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
Description textuelle de l’explorateur
L’explorateur commence à 40 ms par billet (simulation), avec 1 400 billets. Cela donne 56 secondes, au-delà de la limite de 30 secondes. Il indique donc d’utiliser une tâche. Une deuxième commande règle le nombre de billets dans une requête : 1, 20, 200, 1 400 ou 5 000.
À 40 ms, un billet respecte les 50 ms du service de soutien. Avec 20 billets, une requête prend 800 ms. Avec 200 billets, elle prend 8 secondes : une seule requête, mais une longue attente. Avec 1 400 billets, elle prend 56 secondes; avec 5 000 billets, 3,3 minutes. Les deux nécessitent une tâche. Au temps mesuré de 1,76 ms, même 5 000 billets traités un par un prennent seulement 8,8 secondes.
Afficher la réponse
30 000 ms ÷ 40 ms = 750 billets. Dans l’explorateur, 200 billets prennent 8 secondes et respectent la limite; 1 400 prennent 56 secondes et la dépassent. Mais 750 billets prendraient les 30 secondes au complet, sans marge pour une période chargée. Une requête sûre est beaucoup plus petite, par exemple 200 billets. Pour un mois de billets, utilisez une tâche.
Erreur courante
GET pour une tâche répond 404 après un redémarrage
Symptôme : l’équipe qualité soumet la tâche de juillet et reçoit 202 avec un identifiant de tâche. Quelques minutes plus tard, les vérifications périodiques reçoivent soudainement 404, même si l’identifiant est correct. Amira a fait l’essai avec le service terminé du module 3. Elle a soumis 3 billets et reçu l’identifiant de tâche e6cd444d099c. Elle a vu l’état done (terminé), redémarré le service, puis demandé à nouveau :
HTTP/1.1 404 Not Found...{"error":{"code":"job_not_found","message":"There is no job with this ID here.","request_id":"ec357afe6eb3","fields":[]}}Cause : les tâches résident dans la mémoire d’un seul processus. Un redémarrage, après une mise en production ou une panne, lance un nouveau processus avec un dictionnaire jobs vide. Toutes les tâches en attente, en cours et terminées disparaissent. Avec deux processus, chacun possède son propre jobs. Une requête d’état peut donc aussi atteindre le processus qui n’a jamais eu la tâche.
Solution : dans ce cours, un appelant qui reçoit 404 pour une tâche qu’il a soumise doit la soumettre à nouveau. Dans un système réel, conservez les tâches dans un service de file d’attente et les résultats dans un stockage externe au processus. Ainsi, n’importe quel processus peut répondre, et un redémarrage ne perd rien. Le cours Mise à l’échelle des API et des charges de travail d’IA traite des services de messagerie et de file d’attente.
Pour un vrai service d’IA : une tâche acceptée est une promesse. Avant d’accepter un travail qui prend des minutes, décidez où la tâche réside quand le processus s’arrête. Dites aux appelants combien de temps les résultats sont conservés.
Vérifiez votre compréhension
Pourquoi POST /v1/jobs répond-il 202 Accepted plutôt que 200 OK avec les scores?
Afficher la réponse
202 Accepted (accepté) signifie « le travail est accepté, mais il n’est pas encore terminé ». Les scores n’existent pas quand le service répond; la tâche est seulement dans la file d’attente. La réponse fournit l’identifiant de tâche et Location (emplacement) pour demander son état. Retry-After (réessayer après) indique quand demander à nouveau. L’appelant obtient les scores plus tard avec GET /v1/jobs/{id}, quand l’état est done.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
La limite de la file d'attente est de 4. Une grosse tâche s'exécute, et l'équipe qualité soumet 6 tâches à la fois. Prédisez ce qui arrive à chacune des 6 tâches. Expliquez pourquoi une limite vaut mieux qu'une file d'attente illimitée.
Afficher la réponse
La tâche en cours n’est plus dans la file d’attente. Les 4 premières nouvelles tâches sont acceptées et attendent dans la file (202). Les tâches 5 et 6 sont refusées immédiatement avec 429 too_many_jobs (trop de tâches). L’équipe doit les soumettre à nouveau plus tard. Sans limite, une arrivée massive de tâches pourrait remplir la mémoire du service. Toutes les tâches, y compris les requêtes des agents, en souffriraient. Une limite refuse tôt et clairement, tout en gardant le service en santé.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Exercice du module : choisir un mode pour trois charges de travail
Section intitulée « Exercice du module : choisir un mode pour trois charges de travail »D’abord, un exemple détaillé. Les réponses d’Amira aux trois besoins de Larkfield forment les colonnes du tableau dans Choisir un mode de service. Lisez chaque colonne à partir du haut : qui lance le travail, qui attend et combien de temps, comment arrivent les résultats, puis la reprise après échec.
Votre tâche. Choisissez un mode pour chacune des trois charges de travail fictives ci-dessous. Pour chacune, répondez aux trois questions et indiquez le mode, la réception des résultats et la reprise après échec nécessaire.
- La liste des clients à risque de départ. L’équipe marketing d’une petite boutique en ligne envoie un courriel de rabais chaque lundi matin. Elle cible les clients susceptibles de ne plus acheter. Un modèle de départ des clients attribue un score aux 80 000 clients. Les courriels partent à 9 h.
- Le paiement par carte. Une entreprise de paiement vérifie chaque paiement par carte pour détecter la fraude pendant que le client attend à la caisse. Le terminal de la boutique attend au maximum 2 secondes pour l’ensemble du paiement. Le modèle de fraude prend environ 15 ms par paiement.
- Le recalcul des scores des polices. Une compagnie d’assurance recalcule les scores de ses 2 millions de polices d’assurance habitation une fois par mois. Elle le fait quand elle met à jour son modèle de risque. Le modèle est lent : environ 200 ms par police. Les actuaires demandent un recalcul depuis leur propre outil quand le nouveau modèle est prêt. Ils ont besoin des résultats dans les deux jours.
Afficher une réponse modèle
1. La liste des clients à risque de départ : par lots. Personne n’attend un seul score; l’équipe marketing a besoin de la liste avant 9 h le lundi. Les 80 000 clients reçoivent leur score ensemble. Exécutez la tâche le dimanche soir, écrivez la liste et un résumé dans un dossier daté, puis envoyez les courriels. Utilisez la liste pour les destinataires. Les utilisateurs reçoivent les résultats dans un fichier. Reprise : mettez les lignes clients incorrectes de côté, avec une limite d’échecs, et écrivez le fichier de façon atomique. Si la tâche échoue, avertissez quelqu’un et relancez-la avant 9 h. Une liste manquante signifie aucun courriel cette semaine, pas des courriels envoyés aux mauvaises personnes.
2. Le paiement par carte : en ligne. Un client attend, et un paiement représente un cas. La vérification de fraude reçoit un budget de latence, une partie des 2 secondes, par exemple 100 ms. Les 15 ms respectent ce budget. Les utilisateurs reçoivent le résultat dans la réponse : approuver, refuser ou vérifier manuellement. Reprise : le terminal a besoin d’un plan si aucune réponse n’arrive dans le budget prévu. L’entreprise doit le décider d’avance, par exemple « approuver les petits paiements, vérifier les gros manuellement ». Le service a besoin d’une haute disponibilité, car les paiements arrivent toute la journée.
3. Le recalcul des scores des polices : asynchrone (ou par lots, s’il s’exécute à date fixe). 2 millions × 200 ms = 400 000 secondes, soit environ 4,6 jours avec un seul processus. Aucune requête ne peut donc contenir ce travail. L’outil des actuaires soumet une tâche et reçoit immédiatement un identifiant de tâche. Le service découpe le travail en plusieurs petites parties et les exécute sur plusieurs processus pour terminer dans les deux jours. Les utilisateurs vérifient périodiquement la tâche, puis téléchargent un fichier de résultats depuis le stockage. Reprise : une exécution de plusieurs jours connaîtra un redémarrage. Les tâches et leur progression résident donc dans un service de file d’attente et du stockage, pas dans la mémoire d’un processus. Une partie qui échoue est relancée, pas la tâche entière.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Nommez les trois modes de service de ce module. Pour chacun, dites comment les utilisateurs reçoivent les résultats.
Afficher la réponse
Par lots : les utilisateurs lisent les résultats dans des fichiers écrits par une exécution planifiée, comme scores.csv avec summary.json. En ligne : l’appelant reçoit le score dans la réponse à sa propre requête, pendant qu’il attend. Asynchrone : l’appelant reçoit immédiatement un identifiant de tâche (202). Il interroge ensuite périodiquement GET /v1/jobs/{id} jusqu’à l’état done, puis obtient les scores dans cette réponse.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Le modèle de Larkfield devient 30 fois plus lent par billet. Prédisez les effets sur chacun des trois besoins de Larkfield, avec les nombres de ce module.
Afficher la réponse
30 × 1,76 ms ≈ 53 ms par billet. En ligne : un billet dépasse le budget de 50 ms. La page s’ouvre donc sans score la plupart du temps; le mode en ligne ne convient plus. Par lots : environ 45 billets par nuit prennent environ 2,4 secondes, et même 1 400 prennent environ 74 secondes. Cela termine bien avant 8 h, donc le mode par lots fonctionne encore. Asynchrone : un mois de 1 400 billets prend environ 74 secondes, au-delà des 30 secondes de la passerelle. Il faut donc une tâche, et une tâche fonctionne encore. Le modèle plus lent compromet seulement le mode où une personne attend.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vérification de fin : pour les modes par lots, en ligne et asynchrone, expliquez la réception des résultats et la reprise après échec nécessaire.
Afficher la réponse
Par lots : les utilisateurs lisent un fichier, comme scores.csv, à une heure connue. Reprise : les lignes incorrectes vont dans rejected.csv. Trop de lignes incorrectes, ou une colonne manquante, font échouer l’exécution sans produire de sortie. La tâche écrit ses fichiers de façon atomique. summary.json consigne les empreintes de l’entrée et de l’ensemble empaqueté. La tâche peut ainsi être relancée avant que quelqu’un ait besoin des résultats.
En ligne : l’appelant reçoit le score dans la réponse, dans le budget de latence (50 ms chez Larkfield). La reprise relève de l’appelant : un délai maximal et un plan en cas d’absence de réponse. Chez Larkfield, la page s’ouvre sans score, et le traitement par lots de nuit rattrape le travail. Le service a besoin de disponibilité et d’un signal clair « pas prêt ».
Asynchrone : l’appelant reçoit un identifiant de tâche (202) et vérifie périodiquement jusqu’à l’état done, puis lit les scores. Reprise : 429 signifie réessayer plus tard. Une tâche failed (échouée) conserve son erreur et est soumise à nouveau. Une tâche perdue après un redémarrage est soumise à nouveau. En production, les tâches et les résultats résident dans un service de file d’attente et du stockage. Un redémarrage ne perd donc rien.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- L’inférence asynchrone accepte une tâche immédiatement (
202 Accepted, un identifiant de tâche,Location,Retry-After). Elle calcule les scores en arrière-plan, et l’appelant vérifie périodiquement le résultat. Une file d’attente contient les tâches qui attendent. - Une requête de 1 400 billets dépasse la limite de 30 secondes d’une passerelle à environ 21 ms par billet. À 40 ms par billet (simulation), le mois a pris 56 secondes comme tâche, acceptée en 2,9 ms.
- Les limites gardent le service en santé : 4 tâches en attente (
429), 5 000 billets par tâche (413). Les tâches en mémoire sont perdues au redémarrage. Les systèmes réels utilisent un service de file d’attente et du stockage (voir Mise à l’échelle des API et des charges de travail d’IA). - Module 2 : choisissez le mode selon qui attend, la taille d’une unité de travail et la reprise après échec. Par lots pour les listes planifiées; en ligne pour un cas pendant que quelqu’un attend; asynchrone pour les gros travaux sur demande. Le même
score_ticketssert aux trois.
Revenir à un état connu. Le module 2 n’a créé ni conteneur ni image. Si votre projet ne fonctionne plus, copiez l’instantané end-of-m02 dans ~/projects/escalation-service, comme dans Utiliser un instantané. Exécutez ensuite python -m pytest : 20 passed (20 tests réussis). Vous pouvez supprimer le dossier out/; il figure dans .gitignore.
Ensuite, vous placerez le calcul des scores derrière un service HTTP. Le service charge l’ensemble empaqueté une seule fois, au démarrage. Chaque requête ne fait donc que le travail rapide.
Leçon suivante : Charger et initialiser