Module 7 — Points de terminaison en temps réel et sans serveur
Le meilleur estimateur du module 6 est un model.tar.gz sur S3. L'application mobile de l'opérateur télécom, elle, veut un POST /predict qui renvoie la probabilité de résiliation en moins de 100 ms. Ce module explique comment SageMaker construit ce point de terminaison à partir de l'artefact, quelles sont les deux variantes (temps réel et sans serveur), et laquelle choisir selon le trafic.
Trois objets, pas un
SageMaker sépare le modèle, la configuration et le point de terminaison. Cette séparation paraît lourde en carnet mais devient précieuse en production, parce qu'elle permet de changer le modèle sans recréer le point de terminaison, donc sans changer l'URL.
| Objet | Ce qu'il porte | Cycle de vie |
|---|---|---|
Model | Un artefact model.tar.gz, une image d'inférence, un rôle | Immuable une fois créé |
EndpointConfig | Une ou plusieurs ProductionVariant, chacune sur un type d'instance | Immuable |
Endpoint | URL HTTPS stable, pointant vers un EndpointConfig | Modifiable — c'est là qu'on change de version |
Le SDK Python cache cette structure derrière estimator.deploy(...) :
predicteur = meilleur_estimateur.deploy(
initial_instance_count=1,
instance_type="ml.m5.large", # 0,115 $/h dans us-east-1
endpoint_name="resiliation-prod",
)
Derrière la scène : CreateModel → CreateEndpointConfig → CreateEndpoint. Le point de terminaison est prêt en 6 à 9 minutes ; c'est le temps de tirer l'image d'inférence, de charger le modèle et de passer les vérifications de santé.
Servir depuis un modèle scikit-learn personnalisé
Pour le pipeline scikit-learn du module 5, la structure de model.tar.gz doit contenir modele.joblib et un script inference.py qui expose quatre fonctions attendues par le conteneur sklearn :
# code/inference.py
import os
import json
import joblib
import pandas as pd
def model_fn(model_dir):
return joblib.load(os.path.join(model_dir, "modele.joblib"))
def input_fn(request_body, content_type):
if content_type == "application/json":
return pd.DataFrame([json.loads(request_body)])
raise ValueError(f"Type non supporté : {content_type}")
def predict_fn(donnees, modele):
return modele.predict_proba(donnees)[:, 1]
def output_fn(prediction, accept):
return json.dumps({"proba_resiliation": float(prediction[0])}), accept
Ce contrat des quatre fonctions est standard dans les conteneurs d'inférence AWS. Le respecter permet de servir un modèle scikit-learn sans écrire une seule ligne de Flask, sans gérer gunicorn, sans surveiller les fuites mémoire — le conteneur AWS s'en charge.
Invoquer le point de terminaison
Côté application :
import boto3, json
client = boto3.client("sagemaker-runtime")
reponse = client.invoke_endpoint(
EndpointName="resiliation-prod",
ContentType="application/json",
Body=json.dumps({
"nb_appels_support_30j": 3,
"anciennete_mois": 14,
"forfait": "premium",
"retard_paiement": 0,
"region": "IDF",
}),
)
print(json.loads(reponse["Body"].read()))
# {"proba_resiliation": 0.184}
La latence type sur ml.m5.large pour ce modèle : 12 à 25 ms au 50ᵉ percentile, 60 à 90 ms au 99ᵉ. La marge par rapport à la contrainte de 100 ms est confortable, mais elle est mangée par le réseau si l'application appelle depuis une autre région — un point à toujours vérifier.
Mise à l'échelle automatique
À trafic constant, une instance suffit. À trafic variable (pics du lundi matin), on active la mise à l'échelle automatique sur une métrique d'invocations :
import boto3
auto = boto3.client("application-autoscaling")
auto.register_scalable_target(
ServiceNamespace="sagemaker",
ResourceId="endpoint/resiliation-prod/variant/AllTraffic",
ScalableDimension="sagemaker:variant:DesiredInstanceCount",
MinCapacity=1,
MaxCapacity=6,
)
auto.put_scaling_policy(
PolicyName="cible-invocations",
ServiceNamespace="sagemaker",
ResourceId="endpoint/resiliation-prod/variant/AllTraffic",
ScalableDimension="sagemaker:variant:DesiredInstanceCount",
PolicyType="TargetTrackingScaling",
TargetTrackingScalingPolicyConfiguration={
"TargetValue": 1000.0, # invocations par instance par minute
"PredefinedMetricSpecification": {
"PredefinedMetricType": "SageMakerVariantInvocationsPerInstance",
},
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60,
},
)
Trois cooldowns et une cible : SageMaker ajoute une instance quand invocationsParInstance dépasse 1 000/min, et en retire une quand la charge redescend et que 5 minutes se sont écoulées. Le ScaleInCooldown plus long (300 s) qu'ScaleOutCooldown (60 s) traduit une règle universelle : ajouter vite, retirer lentement pour ne pas osciller.
Variantes de production et A/B
Un EndpointConfig peut contenir plusieurs ProductionVariant, chacune servant une part du trafic :
config = {
"EndpointConfigName": "resiliation-config-2026-09-15",
"ProductionVariants": [
{"ModelName": "resiliation-v3", "VariantName": "V3", "InstanceType": "ml.m5.large",
"InitialInstanceCount": 1, "InitialVariantWeight": 90},
{"ModelName": "resiliation-v4", "VariantName": "V4", "InstanceType": "ml.m5.large",
"InitialInstanceCount": 1, "InitialVariantWeight": 10},
],
}
90 % du trafic vers V3, 10 % vers V4. SageMaker répartit par requête selon les poids, et journalise la variante dans CloudWatch. On peut ensuite comparer les distributions de probabilité, la latence et le taux d'erreur des deux variantes sur des trafics réels — c'est la base d'un déploiement canari maîtrisé (shadow, blue/green par bascule des poids).
Sans serveur : la variante à trafic irrégulier
Certains cas d'usage ont un trafic très irrégulier : quelques centaines de requêtes par jour, concentrées à des heures imprévisibles. Payer une instance 24h/24 pour cela est absurde. Serverless Inference provisionne à la demande :
from sagemaker.serverless import ServerlessInferenceConfig
predicteur = meilleur_estimateur.deploy(
endpoint_name="resiliation-sans-serveur",
serverless_inference_config=ServerlessInferenceConfig(
memory_size_in_mb=2048, # entre 1024 et 6144
max_concurrency=20,
),
)
Facturation à la milliseconde de calcul consommée, plus un coefficient sur la mémoire allouée. Aucune facture quand aucune requête n'arrive.
Le prix à payer est le démarrage à froid : la première requête après une période d'inactivité (typiquement 15 à 30 minutes) attend le provisionnement du conteneur, soit 2 à 6 secondes de latence supplémentaire. Les requêtes suivantes, tant que l'instance reste chaude, ont la latence normale.
| Critère | Temps réel | Sans serveur |
|---|---|---|
| Latence typique | 10-100 ms constants | 10-100 ms sauf démarrage à froid (2-6 s) |
| Facturation | À la seconde, 24h/24 | À la milliseconde utilisée |
| Coût mensuel à 100 000 requêtes/mois | ≈ 83 $ (ml.m5.large) | ≈ 8-20 $ |
| Coût mensuel à 10 M requêtes/mois | ≈ 83 $ (auto-scaling) | ≈ 500-800 $ |
| Cas idéal | Application temps réel, trafic prévisible | Outils internes, prédiction par lots légère, prototype |
Le point d'égalité économique se situe autour de 1 à 2 millions de requêtes par mois ; au-dessus, le temps réel avec auto-scaling gagne ; en-dessous, le sans serveur écrase.
Débogage : logs d'invocation et ping de santé
Chaque invocation apparaît dans CloudWatch Logs, groupe /aws/sagemaker/Endpoints/<nom>. Les métriques Invocations, Invocation4XXErrors, Invocation5XXErrors, ModelLatency et OverheadLatency sont exposées automatiquement dans CloudWatch Metrics sans configuration.
Pour vérifier qu'un point de terminaison répond, un ping HTTPS interne : SageMaker frappe GET /ping sur le conteneur toutes les secondes ; toute réponse hors 200 déclenche une réinitialisation. Un modèle qui plante silencieusement sur une donnée particulière fait échouer POST /invocations, pas GET /ping — c'est un piège classique à connaître pour ne pas croire à tort que « tout va bien ».
Un point de terminaison créé pour une démonstration puis jamais supprimé continue à facturer, même sans une seule requête. ml.m5.large à 0,115 $/h × 24 × 30 = 83 $/mois pour du néant. Sur 5 endpoints oubliés — cas fréquent dans les équipes qui expérimentent — cela dépasse 400 $ mensuels. aws sagemaker list-endpoints --status-equals InService en fin de semaine repère les orphelins ; c'est un des points explicitement thématisés dans la banque d'examen.
En résumé
Model,EndpointConfigetEndpointsont trois objets distincts : cette séparation permet de changer de version sans changer l'URL de production.- Un modèle scikit-learn se sert avec les fonctions
model_fn,input_fn,predict_fn,output_fn; aucun serveur web à écrire. - La mise à l'échelle automatique cible une charge par instance ; « ajouter vite, retirer lentement » via des cooldowns asymétriques évite l'oscillation.
- Sans serveur facture à la milliseconde et coûte moins cher jusqu'à 1-2 millions de requêtes par mois, au prix d'un démarrage à froid de 2-6 s après inactivité.
Module suivant : la transformation par lots, pour scorer 5 millions de clients une fois par mois sans jamais garder un point de terminaison actif.