LLM Engineer's Handbook
Chapitre 10 / 11 · 16 min de lecture

Le déploiement du pipeline d'inférence

Mettre le modèle entre les mains des utilisateurs : déploiement online/temps réel, asynchrone et batch, API REST, conteneurs et autoscaling.

Déployer le pipeline d'inférence est l'étape où votre travail rencontre enfin ses utilisateurs. Tant que ce maillon n'est pas en place, le meilleur modèle du monde n'a, du point de vue métier, quasiment aucune valeur : si la latence est trop élevée ou si l'API tombe en panne, l'utilisateur ira voir ailleurs, vers un produit moins performant mais fiable. Iusztin et Labonne rappellent l'étude de Google de 2016 — 53 % des visites mobiles sont abandonnées si une page met plus de trois secondes à charger. Le déploiement n'est donc pas une formalité, c'est un problème d'ingénierie à part entière.

Ce chapitre suit une progression simple. On part des quatre critères qui gouvernent toute décision de déploiement (débit, latence, données, infrastructure), puis on examine les trois grandes architectures de service (temps réel, asynchrone, batch). On tranche ensuite entre architecture monolithique et microservices, avant de descendre dans le concret avec le projet fil rouge, le « LLM Twin » : un microservice LLM déployé sur AWS SageMaker et un microservice métier en FastAPI qui orchestre le RAG. On termine par l'autoscaling, le levier qui maîtrise le coût dominant de tout déploiement de LLM — le GPU.

Les quatre critères qui dictent le déploiement

Avant de choisir une architecture, posez-vous des questions sur quatre dimensions présentes dans toute application de ML. Elles sont en tension permanente : optimiser l'une dégrade souvent une autre.

  • Le débit (throughput) est le nombre de requêtes traitées par unité de temps, typiquement en requêtes par seconde (RPS). Il devient critique quand on doit absorber un grand volume sans goulot d'étranglement, ce qui réclame une infrastructure scalable, souvent à base de GPU haut de gamme.
  • La latence (latency) est le temps de traitement d'une seule requête, de sa réception au renvoi du résultat. Elle est la somme du réseau (I/O), de la sérialisation/désérialisation et du temps d'inférence du LLM. Elle est vitale pour les interactions en direct comme un chatbot.
  • Les données (data) : format, volume et complexité des entrées et sorties. Un modèle qui prend des données tabulaires et renvoie une probabilité n'a rien à voir avec un LLM qui ingère du texte et produit une longue séquence de tokens.
  • L'infrastructure (infrastructure) : matériel, réseau et pile logicielle qui supportent le service.

Le rapport entre latence et débit n'est pas linéaire. Une latence plus basse augmente le débit quand on traite en parallèle : 100 ms par requête donnent 10 RPS, 10 ms en donnent 100. Mais dès qu'on adopte une stratégie de regroupement (batching) au moment du service, la relation s'inverse : traiter 20 requêtes regroupées en 100 ms donne une latence de 100 ms mais un débit de 200 RPS ; 60 requêtes en 200 ms montent à 300 RPS. Le batching échange de la latence contre du débit — il faut donc fixer la latence maximale acceptable pour une bonne expérience avant de pousser le débit.

Astuce

Optimiser pour la basse latence en regroupant les requêtes laisse souvent le matériel sous-utilisé : moins de requêtes par seconde signifie du calcul inactif, donc un coût par requête plus élevé. Choisir la bonne machine pour le bon objectif est l'un des principaux leviers de coût. En entraînement, on optimise le débit ; en inférence online, on optimise la latence.

Les trois architectures d'inférence

Une fois les critères posés, trois architectures fondamentales s'offrent à vous. Le choix dépend du compromis latence/débit/coût, mais aussi de la fraîcheur des prédictions exigée et de la façon dont l'utilisateur interagit avec le modèle : directement (un chatbot) ou de façon cachée dans le système (un classifieur qui vérifie qu'une entrée est sûre).

Temps réel online

En inférence temps réel online (online real-time inference), le client envoie une requête HTTP à un serveur qui la traite immédiatement et renvoie le résultat dans la même réponse. L'interaction est synchrone : le client attend avant de poursuivre. Deux protocoles dominent.

L'API REST est accessible et universelle, mais plus lente : elle échange du JSON entre client et serveur. C'est le choix pour servir un modèle au grand public, hors de votre réseau interne — l'API d'OpenAI est une API REST. Le gRPC est plus rapide mais moins flexible : il faut implémenter des schémas protobuf côté client, plus fastidieux que du JSON, en échange d'objets compilés en octets qui transitent bien plus vite sur le réseau. On le réserve donc aux services internes d'un même système.

Cette architecture exige une infrastructure à basse latence, un répartiteur de charge (load balancer) pour distribuer le trafic et de l'autoscaling pour absorber les variations. C'est l'architecture des interactions LLM : ChatGPT ou Claude utilisent souvent des WebSockets pour diffuser (stream) chaque token individuellement, rendant l'échange plus réactif. Sa simplicité est séduisante, mais elle se met mal à l'échelle et gaspille des ressources pendant les creux de trafic.

Asynchrone

En inférence asynchrone (asynchronous inference), le client envoie sa requête, le service en accuse réception et la place dans une file de messages (message queue) ; le client n'attend pas. Quand le résultat est prêt, on le dépose dans une autre file ou dans un stockage objet, et le client le récupère par interrogation périodique (polling) ou via une notification (push).

Le grand avantage est l'efficacité d'utilisation des ressources et la capacité à absorber les pics sans timeout. Reprenons l'exemple du livre : une boutique en ligne traite d'ordinaire 10 RPS avec deux machines. Une promotion fait grimper le trafic à 100 RPS. Plutôt que de multiplier les machines par dix — un coût drastique — les requêtes s'empilent dans la file et les deux mêmes machines les traitent à leur rythme, sans échec. L'asynchrone brille aussi pour les tâches longues : si un job prend plus de cinq minutes, on ne veut pas bloquer le client.

La contrepartie est une latence plus élevée — inadaptée au temps réel — et une complexité accrue. Cette architecture se situe entre l'online et l'offline. On la choisit quand la latence importe peu mais que le coût compte beaucoup : extraction de mots-clés, résumé de documents par un LLM, application de modèles de deepfake sur des vidéos.

Batch hors-ligne

Le transfert batch hors-ligne (offline batch transform) traite de gros volumes en une seule opération, sur planification ou déclenchement manuel. Le service tire les données d'un stockage (un stockage objet comme AWS S3, ou un entrepôt comme BigQuery), les traite en masse, et y réécrit les résultats. Le client lit directement ce stockage, qu'on peut voir comme un grand cache de prédictions : il n'attend jamais le service, mais ne peut pas non plus demander un résultat frais à tout moment.

C'est l'approche optimisée pour le haut débit avec latence permissive, la plus économique et la plus rapide à développer. Sa limite est structurelle : elle introduit toujours un délai entre le calcul et la consommation. Un recommandeur de films peut tolérer un jour de retard ; un fil de réseau social où l'on veut du contenu frais en permanence, non. C'est pourquoi on la dit « offline ».

CritèreOnline temps réelAsynchroneBatch hors-ligne
InteractionSynchrone (le client attend)Découplée par une fileDécouplée par un stockage
LatenceFaible (priorité)Moyenne à élevéeÉlevée (non critique)
DébitLimité par le temps réelÉlevé, lissé dans le tempsTrès élevé
FraîcheurImmédiateDifférée (file)Différée (planifiée)
CoûtÉlevé (ressources prêtes)Optimisé (pas de surdimensionnement)Le plus bas
Pics de traficExige load balancing + autoscalingAbsorbés par la file, sans timeoutSans objet
Cas d'usageChatbot, RAG, reco en directRésumé, extraction, deepfakeAnalytique, reporting périodique

Monolithe ou microservices ?

Au-delà du protocole client-serveur, il faut décider de l'architecture interne du service. Deux options, qui influencent fortement la scalabilité et la maintenabilité.

Dans une architecture monolithique (monolithic architecture), le LLM et la logique métier (pré- et post-traitement) sont réunis dans un seul service. Simple à démarrer et à maintenir sur un petit projet, elle souffre d'un défaut majeur : on ne peut pas mettre les composants à l'échelle indépendamment. Or le LLM réclame du GPU tandis que la logique métier est bornée par le CPU et les I/O. Résultat : le GPU reste inactif pendant l'exécution de la logique métier, et inversement — du gaspillage coûteux. Le monolithe impose aussi une pile technique unique : difficile d'exécuter le LLM en Rust, C++ ou compilé avec ONNX/TensorRT tout en gardant le métier en Python.

L'architecture microservices (microservices architecture) découpe le pipeline en services indépendants — typiquement un service LLM et un service métier — qui communiquent par REST ou gRPC. L'avantage décisif : mettre chaque composant à l'échelle séparément. Le service LLM, gourmand en GPU, peut être répliqué horizontalement sans toucher au reste ; on n'occupe la coûteuse machine GPU qu'avec ce qui en a réellement besoin, le calcul léger restant sur une machine bien moins chère. Chaque service peut aussi adopter sa propre pile. La contrepartie : déploiement, monitoring et maintenance se multiplient, et la communication réseau ajoute de la latence et des points de défaillance.

À retenir

Une stratégie éprouvée consiste à démarrer monolithique puis à découpler en grandissant. Mais cela ne fonctionne que si le monolithe est conçu modulaire dès le départ : même sur une seule machine, séparez la logique ML et la logique métier en modules Python distincts qui ne s'appellent pas directement, recollés à un niveau supérieur (une classe de service, ou directement FastAPI). Sans cette discipline, le passage aux microservices imposera de tout réécrire.

Le choix dépend donc du contexte : monolithe pour les petites équipes, les applications simples, les modèles légers sans GPU ; microservices pour les systèmes complexes où les composants ont des besoins de mise à l'échelle ou des piles différentes — en particulier pour isoler un service LLM gourmand en GPU coûteux (Nvidia A100, V100, A10G).

La stratégie du LLM Twin

L'objectif du LLM Twin est un chatbot d'aide à la création de contenu, qui traite les requêtes séquentiellement avec une forte exigence de basse latence. Cela impose l'inférence temps réel online. Côté architecture interne, le choix se porte sur les microservices : un serveur REST API porte la logique métier, un microservice LLM optimisé porte le modèle. La justification est financière et pratique — on peut ajuster l'infrastructure selon la taille du modèle. Un modèle de 8 milliards de paramètres tient sur une seule machine à GPU A10G après quantification ; un modèle de 30 milliards demande un A100. On met alors à niveau le seul microservice LLM, sans toucher au REST API.

Utilisateur
    | (1) requete HTTP
    v
+--------------------------------------------------+
|  Microservice metier (FastAPI, CPU/IO)           |
|  (2) retrieval RAG avance  -> Qdrant (vector DB)  |
|  (3) augmentation du prompt (template dedie)      |
+--------------------------------------------------+
    | (4) requete HTTP (prompt)        ^ (5) attend la reponse
    v                                  |
+--------------------------------------------------+
|  Microservice LLM (SageMaker + TGI, GPU)         |
|  generation (composant RAG)                       |
+--------------------------------------------------+
    | (6) trace (query, prompt, reponse) -> monitoring
    v
(7) reponse renvoyee a l'utilisateur

Le découpage suit l'architecture feature/training/inference (FTI). Le pipeline d'inférence a besoin de deux choses : les features temps réel pour le RAG, interrogées depuis le magasin de features online (concrètement la base de données vectorielle Qdrant), et un LLM affiné tiré du registre de modèles (model registry). La pile retenue : Qdrant pour la base vectorielle, Hugging Face comme registre de modèles (pour partager publiquement le modèle), FastAPI pour le microservice métier et AWS SageMaker pour le microservice LLM.

Note

Le registre de modèles n'est pas qu'un détail d'ingénierie : il rend le modèle partageable et accessible. Dans le livre, le modèle affiné est publié sur Hugging Face afin que les lecteurs n'aient pas à relancer l'entraînement, qui peut coûter jusqu'à 100 dollars. Le pipeline d'entraînement produit des poids stockés dans le registre ; le pipeline d'inférence produit des prédictions servies à l'utilisateur. Veillez à appliquer exactement le même pré- et post-traitement des deux côtés, sous peine d'un décalage entraînement-service (training-serving skew) qui dégrade silencieusement les performances.

Le microservice LLM sur SageMaker

Le modèle, stocké sur Hugging Face, est déployé comme endpoint d'inférence temps réel sur SageMaker via un conteneur spécialisé, le Hugging Face LLM DLC (Deep Learning Container). Un DLC est une image Docker préchargée avec les frameworks et bibliothèques utiles (transformers, datasets, tokenizers), ce qui supprime la configuration d'environnement complexe. Le DLC d'inférence est propulsé par le moteur TGI (Text Generation Inference) de Hugging Face, qui apporte parallélisme de tenseurs, batching dynamique continu des requêtes entrantes, attention flash, quantification bitsandbytes, chargement accéléré des poids avec safetensors et streaming de tokens via Server-Sent Events (SSE).

Le déploiement est entièrement automatisé en Python et orchestré par un patron stratégie. Le point d'entrée récupère l'URI de l'image DLC, puis délègue à une stratégie de déploiement.

def create_endpoint(
    endpoint_type=EndpointType.INFERENCE_COMPONENT_BASED,
):
    llm_image = get_huggingface_llm_image_uri(
        "huggingface", version=None
    )
    resource_manager = ResourceManager()
    deployment_service = DeploymentService(
        resource_manager=resource_manager
    )

    SagemakerHuggingfaceStrategy(deployment_service).deploy(
        role_arn=settings.ARN_ROLE,
        llm_image=llm_image,
        config=hugging_face_deploy_config,
        endpoint_name=settings.SAGEMAKER_ENDPOINT_INFERENCE,
        endpoint_config_name=settings.SAGEMAKER_ENDPOINT_CONFIG_INFERENCE,
        gpu_instance_type=settings.GPU_INSTANCE_TYPE,
        resources=model_resource_config,
        endpoint_type=endpoint_type,
    )

Quatre concepts SageMaker s'articulent ici : le endpoint (l'API scalable et sécurisée exposée pour les prédictions temps réel), le model (l'artefact issu de l'entraînement, poids et logique de calcul), la configuration (le matériel et le logiciel hôtes, type et nombre d'instances) et l'inference component (le lien entre modèle, configuration et endpoint, qui permet de déployer plusieurs modèles avec chacun ses ressources).

La configuration des ressources et celle du moteur LLM contrôlent à elles deux toute l'infrastructure et le comportement du modèle. Elles sont pilotables depuis un fichier .env, sans toucher au code.

from sagemaker.compute_resource_requirements.resource_requirements 
    import ResourceRequirements

# Ressources materielles par endpoint multi-replicas.
model_resource_config = ResourceRequirements(
    requests={
        "copies": settings.COPIES,          # nb de repliques
        "num_accelerators": settings.GPUS,  # nb de GPU
        "num_cpus": settings.CPUS,          # nb de coeurs CPU
        "memory": 5 * 1024,                 # RAM minimale (Mo)
    },
)

# Comportement du moteur LLM (TGI) cote serveur.
hugging_face_deploy_config = {
    "HF_MODEL_ID": settings.HF_MODEL_ID,    # modele a charger
    "SM_NUM_GPUS": json.dumps(settings.SM_NUM_GPUS),
    "MAX_INPUT_LENGTH": json.dumps(settings.MAX_INPUT_LENGTH),
    "MAX_TOTAL_TOKENS": json.dumps(settings.MAX_TOTAL_TOKENS),
    "MAX_BATCH_TOTAL_TOKENS": json.dumps(
        settings.MAX_BATCH_TOTAL_TOKENS
    ),
    "HUGGING_FACE_HUB_TOKEN": settings.HUGGINGFACE_ACCESS_TOKEN,
    "MAX_BATCH_PREFILL_TOKENS": "10000",
    "HF_MODEL_QUANTIZE": "bitsandbytes",     # quantification
}

Le déploiement complet tient en une commande (poe deploy-inference-endpoint) et prend 15 à 30 minutes. Le plus dur n'est pas le code, mais le réglage : trouver la configuration qui satisfait vos besoins tout en réduisant les coûts. Les valeurs par défaut utilisent une seule instance GPU ml.g5.xlarge ; on change le nombre de répliques, de GPU ou le type d'instance depuis le .env.

Piège courant

Presque toutes les ressources AWS sont facturées à l'usage. Quelques heures de SageMaker ne ruinent personne, mais un endpoint GPU oublié quelques jours fait croître les coûts de façon exponentielle. Règle d'or : supprimez systématiquement l'infrastructure après vos tests (poe delete-inference-endpoint), puis vérifiez dans le tableau de bord SageMaker.

Appeler le LLM et écrire le microservice métier

Côté client, deux classes découplent l'appel : LLMInferenceSagemakerEndpoint interagit directement avec SageMaker via boto3 (le SDK AWS pour Python) et construit le payload, tandis qu'InferenceExecutor formate le prompt avec la requête et le contexte. Comme l'exécuteur accepte n'importe quelle implémentation de l'interface Inference, on peut injecter une autre stratégie d'inférence sans rien changer ailleurs.

def inference(self) -> Dict[str, Any]:
    try:
        invoke_args = {
            "EndpointName": self.endpoint_name,
            "ContentType": "application/json",
            "Body": json.dumps(self.payload),
        }
        if self.inference_component_name not in ["None", None]:
            invoke_args["InferenceComponentName"] = (
                self.inference_component_name
            )
        response = self.client.invoke_endpoint(**invoke_args)
        body = response["Body"].read().decode("utf8")
        return json.loads(body)
    except Exception:
        logger.exception("SageMaker inference failed.")
        raise

Le microservice métier en FastAPI est l'entrée unique des utilisateurs. Il définit ses contrats d'entrée/sortie avec Pydantic, exécute la logique RAG du chapitre 9 (récupération du contexte dans Qdrant, augmentation du prompt), puis délègue la génération au microservice LLM. Point essentiel : tout le calcul lourd vit ailleurs. La fonction rag() est bornée par le CPU et le réseau (appels à OpenAI, Qdrant, modèle d'embedding sur CPU), et l'appel au LLM est purement réseau. Le serveur FastAPI tourne donc sur une machine légère et bon marché, sans GPU, à faible latence.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()


class QueryRequest(BaseModel):
    query: str


class QueryResponse(BaseModel):
    answer: str


def call_llm_service(query: str, context: str | None) -> str:
    # Appel reseau pur vers le microservice LLM (SageMaker).
    llm = LLMInferenceSagemakerEndpoint(
        endpoint_name=settings.SAGEMAKER_ENDPOINT_INFERENCE,
        inference_component_name=None,
    )
    return InferenceExecutor(llm, query, context).execute()


def rag(query: str) -> str:
    # Logique metier : CPU + I/O uniquement, pas de GPU.
    retriever = ContextRetriever(mock=False)
    documents = retriever.search(query, k=3 * 3)
    context = EmbeddedChunk.to_context(documents)
    return call_llm_service(query, context)


@app.post("/rag", response_model=QueryResponse)
async def rag_endpoint(request: QueryRequest):
    try:
        answer = rag(query=request.query)
        return {"answer": answer}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e)) from e

On lance ce serveur avec uvicorn, le serveur web de référence pour FastAPI :

uvicorn tools.ml_service:app --host 0.0.0.0 --port 8000 --reload

# Tester l'endpoint /rag avec une requete POST :
curl -X POST 'http://127.0.0.1:8000/rag' 
  -H 'Content-Type: application/json' 
  -d '{"query": "your_query"}'

Ce serveur ne tourne pour l'instant qu'en local. Le mettre en production demanderait de conteneuriser le code FastAPI, de pousser l'image vers AWS ECR (Elastic Container Registry), puis de la déployer sur un cluster AWS EKS (Kubernetes managé) ou ECS, idéalement via un outil d'infrastructure-as-code comme Terraform. Cette partie n'étant pas spécifique aux LLM, le livre la traite ailleurs.

L'autoscaling : maîtriser le coût du GPU

Jusqu'ici, le microservice LLM tournait avec un nombre fixe de répliques, quelle que soit la charge. C'est doublement mauvais : pendant les creux, des GPU coûteux restent inactifs ; pendant les pics, le serveur sature et l'expérience se dégrade — or les pics amènent souvent les nouveaux utilisateurs, ceux qu'il ne faut surtout pas décevoir. La réponse est l'autoscaling (autoscaling) : ajuster dynamiquement le nombre de répliques selon des métriques comme le nombre de requêtes.

Trafic         Repliques en ligne
  0 RPS   ->   1 (ou 0 si latence non critique)
 10 RPS   ->   2
100 RPS   ->   20

(chiffres fictifs, a calibrer pour votre cas d'usage)

Entre le client et les répliques s'intercale un répartiteur de charge (Application Load Balancer, ALB). Toutes les requêtes passent par lui ; il les route, par exemple en tourniquet (round robin), vers les répliques. Le client appelle toujours la même adresse — celle du load balancer — si bien qu'ajouter ou retirer des répliques n'affecte jamais le protocole client-serveur.

SageMaker fournit Application Auto Scaling en deux étapes.

  1. Enregistrer une cible scalable (scalable target) : on déclare à AWS la ressource à mettre à l'échelle (Resource ID, namespace du service, dimension scalable comme le nombre de copies) et ses bornes (MinCapacity, MaxCapacity). Le minimum doit valoir au moins 1 pour garantir une capacité permanente ; le maximum n'a pas de plafond imposé. Cette étape ne dit pas comment ni quand scaler.
  2. Créer une politique de scaling (scaling policy) : on définit les règles qui déclenchent les événements. Le type courant est TargetTrackingScaling, qui ajuste la capacité pour maintenir une valeur cible sur une métrique choisie — par exemple SageMakerInferenceComponentInvocationsPerCopy, ou une utilisation GPU autour de 70 %.

Le fonctionnement de TargetTrackingScaling ressemble à un thermostat. On fixe la valeur idéale d'une métrique ; Application Auto Scaling crée et gère les alarmes CloudWatch nécessaires. Quand l'utilisation GPU dépasse la cible, le système scale out (ajoute des répliques) ; quand elle passe sous la cible, il scale in (en retire pour économiser). Viser 70 % laisse une marge pour absorber les pics tout en évitant les ressources inactives, sans avoir à configurer manuellement les alarmes.

Un dernier paramètre clé est la période de refroidissement (cooldown period). Elle introduit une pause calculée entre deux actions de scaling, pour que le système ne suroscille pas : elle retarde la suppression de répliques au scale-in et limite la création de nouvelles au scale-out, garantissant un environnement stable.

Attention

L'autoscaling a deux écueils symétriques. Le sur-scaling (over-scaling), le plus dangereux pour la facture : une politique ou un cooldown trop sensibles font surgir des machines qui restent inactives. À l'inverse, un système qui ne scale pas assez dégrade l'expérience. La parade : comprendre vos besoins (par exemple 100 utilisateurs/minute en moyenne, 10 000 lors d'un pic exceptionnel), puis stress-tester et ajuster les paramètres dans un environnement de test jusqu'au point d'équilibre — exactement comme on règle des hyperparamètres. Ces principes sont transposables à Kubernetes ou ECS.

À retenir

  • Toute décision de déploiement arbitre entre quatre critères en tension — débit, latence, données, infrastructure — et le batching échange explicitement de la latence contre du débit.
  • Trois architectures : online temps réel (synchrone, REST/gRPC, basse latence, chatbot), asynchrone (file de messages, découplage, pics absorbés sans timeout, tâches longues), batch hors-ligne (gros volumes, coût minimal, prédictions différées).
  • L'architecture microservices sépare le service LLM (GPU) de la logique métier (CPU/IO), permettant de mettre chaque composant à l'échelle indépendamment ; commencez monolithique mais conçu modulaire pour pouvoir découpler plus tard.
  • Le LLM Twin déploie son microservice LLM sur SageMaker (DLC Hugging Face propulsé par TGI) et son microservice métier en FastAPI, qui orchestre le RAG sur une machine légère sans GPU et appelle le LLM par le réseau.
  • Le GPU est le coût dominant : l'autoscaling (cible scalable + politique TargetTrackingScaling, à la manière d'un thermostat) ajuste les répliques selon la charge, avec une période de refroidissement pour éviter la suroscillation.
  • Réglez l'autoscaling par stress-test entre deux écueils — le sur-scaling qui gonfle la facture et le sous-scaling qui ruine l'expérience — et supprimez toujours l'infrastructure GPU après vos tests.