Valeurs par défaut de production SageMaker
La différence entre un endpoint de démo et un endpoint que vous pouvez laisser tournant est : il s'adapte au trafic, il vous indique quand il dysfonctionne, et vous pouvez le déboguer plus tard. Cette skill fait de ces trois éléments la valeur par défaut plutôt que des extras optionnels.
Quand cette skill s'exécute, le planner a choisi un endpoint temps réel, IAM dispose d'un rôle utilisable, et la sélection d'image a résolu un URI de conteneur + une version AMI. Cette skill les transforme en un véritable déploiement.
Ce qui est créé
Pour chaque endpoint, la skill crée ceux-ci en tant qu'unité :
- SageMaker Model — image + variables d'env + rôle d'exécution + artefacts S3
- Endpoint config — type d'instance, nombre initial, capture de données optionnelle
- Endpoint — l'endpoint temps réel servant l'inférence
- Cible + politique d'autoscaling — suivi cible sur invocations par instance
- Alarmes CloudWatch — latence, erreurs, surcharge plateforme
La capture de données (enregistrement des requêtes/réponses dans S3) est désactivée par défaut — utile pour le débogage mais génère des coûts S3 continus que l'utilisateur n'a pas nécessairement demandés. Activez avec --enable-data-capture.
Toutes les ressources reçoivent un ensemble de tags cohérent incluant CreatedBy=agentic-deploy-skills pour un nettoyage ultérieur.
Valeurs par défaut et justifications dans references/deployment-template.md.
Exécution du déploiement
Pour un LLM de génération de texte (vLLM) :
python scripts/deploy.py \
--model-name qwen3-medical \
--image-uri "$IMAGE_URI" \
--inference-ami-version "$AMI" \
--role-arn "$ROLE_ARN" \
--instance-type ml.g5.xlarge \
--region "$REGION" \
--env SM_VLLM_MODEL=Qwen/Qwen3-0.6B \
--env SM_VLLM_HOST=0.0.0.0 \
--env SM_VLLM_TRUST_REMOTE_CODE=true \
--env SM_VLLM_MAX_MODEL_LEN=4096
Pour un modèle d'embedding (TEI, souvent sur CPU) :
python scripts/deploy.py \
--model-name bge-large-embeddings \
--image-uri "$IMAGE_URI" \
--role-arn "$ROLE_ARN" \
--instance-type ml.c6i.2xlarge \
--region "$REGION" \
--env HF_MODEL_ID=BAAI/bge-large-en-v1.5
Note : les déploiements TEI n'ont pas besoin de --inference-ami-version. Ce flag est spécifique à vLLM. Les variables d'env TEI sont aussi plus simples (HF_MODEL_ID au lieu de SM_VLLM_*, pas d'hôte ou trust-remote-code à configurer).
D'où vient chaque valeur :
| Paramètre | Source |
|---|---|
--image-uri |
hf-cloud-serving-image-selection — l'agent lit depuis la page du catalogue AWS DLC |
--inference-ami-version |
hf-cloud-serving-image-selection — requis pour les tags vLLM contenant cu130+ |
--role-arn |
hf-cloud-sagemaker-iam-preflight (check_role.py) |
--region |
hf-cloud-aws-context-discovery |
--instance-type |
Entrée utilisateur ou recommandation du planner |
--env |
Spécifique au modèle ; voir hf-cloud-serving-image-selection pour les variables SM_VLLM_* requises |
--model-s3-uri |
Optionnel — chemin S3 vers les artefacts du modèle ; omettez si vous chargez depuis HF Hub |
Le script crée les ressources dans l'ordre avec gestion d'erreur, attend InService (jusqu'à 30 min), affiche les raisons d'échec, enregistre l'autoscaling et les alarmes, et imprime un résumé incluant la commande de teardown. Affiche un blob JSON sur stdout avec les noms d'endpoint/config/model pour les scripts en aval.
Les scripts sont livrés avec cette skill. Si la copie installée ne dispose pas du répertoire scripts/ (certains mécanismes ne copient que SKILL.md lors de l'installation), récupérez-les depuis le repo source plutôt que de les réimplémenter à partir de cette description.
Attente au démarrage à froid : quand le modèle se charge depuis HF Hub, le téléchargement se produit à l'intérieur du conteneur après le démarrage de l'endpoint — 5–15+ minutes jusqu'à InService est normal, pas une défaillance. deploy.py attend 30 minutes ; si vous écrivez du code d'attente personnalisé, ne dépassez pas 15 minutes. La mise en place préalable des poids dans S3 (--model-s3-uri) réduit cela et supprime la dépendance du Hub.
InService n'est pas succès — smoke-test avant de déclarer victoire
InService signifie seulement que le conteneur a répondu à /ping. Dans les conteneurs basés sur MMS (HuggingFace Inference Toolkit) le front-end Java répond aux pings même pendant que le worker Python crash-loop — un endpoint peut être InService et ne rien servir. Deux vérifications, toujours :
-
Une vraie invocation.
- Temps réel :
invoke_endpoint.py(ci-dessous) avec une charge minimale ; exigez un HTTP 200 avec un corps sensé. - Async : uploadez une entrée dans S3, appelez
invoke-endpoint-async, interrogez l'URI de sortie pendant quelques minutes (voir « Invocation d'endpoints async »). Un objet résultat = succès ; un objet à l'URI d'échec, ou rien n'apparaît = cassé.
- Temps réel :
-
Analysez les logs d'endpoint pour les marqueurs de crash worker — capture le cas crash-loop même quand la requête smoke expire simplement :
aws logs filter-log-events \ --log-group-name /aws/sagemaker/Endpoints/<endpoint-name> \ --filter-pattern '?"Worker died" ?"Load model failed" ?"ImportError"' \ --region <region> --max-items 5
Rapportez le déploiement complet seulement après que les deux réussissent. Si l'analyse des logs trouve quelque chose, affiche la véritable traceback depuis CloudWatch — pas le statut InService.
Test d'un endpoint temps réel
Une fois que l'endpoint est InService, testez-le avec l'assistant fourni. Il est multiplateforme et BOM-safe — utilisez-le au lieu d'écrire manuellement un fichier payload et d'appeler invoke-endpoint directement :
# macOS / Linux
python3 scripts/invoke_endpoint.py \
--endpoint-name <endpoint-name> \
--payload '{"inputs": "Hello"}' \
--region "$REGION"
# Windows (PowerShell)
python scripts\invoke_endpoint.py `
--endpoint-name <endpoint-name> `
--payload-file payload.json `
--region $REGION
Il accepte soit --payload '<json>' (inline) soit --payload-file <path>, valide le JSON, écrit le corps de la requête en UTF-8 pur, invoque l'endpoint, et imprime le corps de la réponse sur stdout.
L'arnaque du BOM UTF-8 (Windows)
Si vous écrivez vous-même la charge payload sur Windows, ne pas utiliser Set-Content -Encoding UTF8 — selon la version de PowerShell cela ajoute un mark d'ordre d'octets UTF-8 (BOM). Le parseur JSON de SageMaker rejette un BOM avec une erreur 400 ModelError :
Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)
Ce n'est pas un problème de modèle, de santé d'endpoint, ou d'image — seulement l'encodage du fichier du corps de la requête. invoke_endpoint.py l'évite entièrement (il supprime même un BOM d'un --payload-file qui en a déjà un). Si vous devez appeler le CLI directement, écrivez le corps en UTF-8 sans BOM :
# UTF-8 sans BOM — utilisez ceci
[System.IO.File]::WriteAllText((Resolve-Path "payload.json"), $json, [System.Text.UTF8Encoding]::new($false))
aws sagemaker-runtime invoke-endpoint `
--endpoint-name <endpoint-name> `
--content-type application/json `
--body fileb://payload.json `
--region $REGION `
response.json
Recours : si une invocation échoue avec Unexpected UTF-8 BOM, réécrivez la charge en UTF-8 sans BOM (ou relancez via invoke_endpoint.py) et réessayez une fois avant de traiter l'endpoint ou le modèle comme cassé.
Invocation d'un reranker génératif (vLLM)
Les rerankers génératifs (Qwen3-Reranker etc. — routés vers le DLC vLLM HuggingFace par hf-cloud-serving-image-selection) sont des LM causals notés par leur premier token généré, pas des modèles chat. Utilisez l'API completions avec un prompt brut, pas l'API messages/chat : la création du template chat n'honore pas fiablement chat_template_kwargs comme {"enable_thinking": false}, et un mauvais template retourne silencieusement des scores quasi-identiques pour chaque paire query–document au lieu d'errorer.
Forme de la charge (format attendu de Qwen3-Reranker — substituez {query} / {document}) :
{
"prompt": "<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\".<|im_end|>\n<|im_start|>user\n<Instruct>: Given a web search query, retrieve relevant passages that answer the query\n<Query>: {query}\n<Document>: {document}<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n",
"max_tokens": 1,
"temperature": 0,
"logprobs": 20
}
Le suffixe final <|im_start|>assistant\n<think>\n\n</think>\n\n est essentiel : il pré-remplit un bloc de réflexion vide de sorte que le premier token généré est le jugement yes/no. Notez à partir des logprobs retournés : P("yes") / (P("yes") + P("no")). Vérifiez l'endpoint avec une paire pertinente (attendez >0,9) et une paire non pertinente (attendez <0,05) — des scores quasi-identiques sur les paires signifient que le template de prompt est faux, pas que le modèle est cassé.
La même règle se généralise : pour tout modèle en mode réflexion où le prompt doit être byte-exact, préférez l'API completions brute au chat.
Choix de l'URI d'image
L'agent lit l'URI d'image du catalogue Deep Learning Containers d'AWS — choisissez la ligne correspondant à la famille de modèles (HuggingFace vLLM pour les LLM, TEI pour les embeddings, etc.), substituez <region> à la région de déploiement, et passez à deploy.py --image-uri.
Pour les images vLLM en particulier (à la fois huggingface-vllm et le recours AWS vllm), vérifiez aussi la version CUDA du tag :
# Exemple : HuggingFace vLLM 0.21.0 depuis le catalogue
IMAGE_URI="763104351884.dkr.ecr.eu-west-1.amazonaws.com/huggingface-vllm:0.21.0-transformers5.8.1-gpu-py312-cu130-ubuntu22.04"
# tag cu130 → doit passer --inference-ami-version
python deploy.py --image-uri "$IMAGE_URI" \
--inference-ami-version al2-ami-sagemaker-inference-gpu-3-1 \
...
Pour les tags avec cu129 ou inférieur, omettez --inference-ami-version. Voir hf-cloud-serving-image-selection pour la table de lookup AMI vLLM complète et les exigences en variables d'env pour chaque famille d'images.
Déploiements d'inférence async
Pour les inférences longues (>60s), les grandes charges, ou les workloads suffisamment bursty/sparse pour bénéficier d'une scale-to-zero, utilisez deploy_async.py au lieu de deploy.py. L'async supporte vraiment MinCapacity=0 — l'autoscaling temps réel ne peut pas.
python scripts/deploy_async.py \
--model-name flux-text-to-image \
--image-uri "$IMAGE_URI" \
--role-arn "$ROLE_ARN" \
--instance-type ml.g5.2xlarge \
--region "$REGION" \
--output-s3-uri s3://my-bucket/async-output/ \
--env HF_MODEL_ID=black-forest-labs/FLUX.1-dev
Extras requis au-delà de deploy.py :
--output-s3-uri— où les résultats async aboutissent (les résultats ne sont pas retournés de manière synchrone)
Flags optionnels spécifiques à l'async :
--failure-s3-uri— chemin séparé pour les invocations échouées--success-sns-topic,--error-sns-topic— recevez une notification quand les résultats async sont prêts ou échouent--min-capacity 0(la valeur par défaut) — scale à zéro entre les batch--backlog-per-instance-target N— profondeur de queue cible par instance (défaut 5)--max-concurrent-invocations-per-instance N— défaut 4
Comment fonctionne la scale-to-zero
Le script async enregistre deux politiques d'autoscaling sur la variant :
- Suivi cible sur
ApproximateBacklogSizePerInstance— gère l'autoscaling continu entre min et max - Suivi par palier déclenché par une alarme CloudWatch
HasBacklogWithoutCapacity— gère le réveil du zéro (0→1)
Les deux sont nécessaires. Le suivi cible seul ne peut pas faire la transition de zéro (il ne peut pas diviser par zéro instances), donc sans la politique par palier l'endpoint démarre, scale à zéro après le premier batch, et ne se réveille jamais. Le script configure cela automatiquement.
Alarmes async
Le script crée trois alarmes CloudWatch :
ApproximateBacklogSize > 50— la queue se construit plus vite que la capacité ne peut la drainerInvocationsFailed > 5— défaillances répétées de traitementHasBacklogWithoutCapacity— conduit la politique de réveil du zéro (pas une alarme de notification ; son action est la politique de suivi par palier, pas le topic SNS)
Si vous passez --sns-alarm-topic <arn>, les deux premières notifient sur ce topic. L'alarme de réveil pointe toujours sur la politique par palier.
Invocation d'endpoints async
Les endpoints async ne sont pas appelés de manière synchrone. Vous uploadez l'entrée dans S3, appelez invoke-endpoint-async avec la location S3 d'entrée, et SageMaker écrit le résultat dans votre --output-s3-uri quand c'est fait :
# Uploadez d'abord votre entrée
aws s3 cp input.json s3://my-input-bucket/job1/input.json
# Invoquez
aws sagemaker-runtime invoke-endpoint-async \
--endpoint-name <endpoint-name> \
--input-location s3://my-input-bucket/job1/input.json \
--content-type application/json \
--region <region>
# Interrogez le résultat à votre URI de sortie
aws s3 cp s3://my-bucket/async-output/<inference-id>.out result.json
La même mise en garde sur le BOM UTF-8 s'applique au input.json que vous uploadez (voir « L'arnaque du BOM UTF-8 » plus haut) — si vous le construisez sur Windows, écrivez-le en UTF-8 sans BOM ou le parseur JSON du conteneur le rejettera.
Le teardown fonctionne comme en temps réel : python3 scripts/teardown.py <endpoint-name> (le script teardown découvre les politiques et alarmes par préfixe de nom, il gère donc les deux modes de déploiement).
Valeurs par défaut en un coup d'œil
| Paramètre | Valeur par défaut | Override |
|---|---|---|
| Nombre initial d'instances | 1 | --initial-instance-count |
| Autoscaling min / max | 1 / 4 | --min-capacity, --max-capacity |
| Cible d'autoscaling | 20 invocations/min/instance | --target-invocations-per-instance |
| Capture de données | désactivée (opt-in) | --enable-data-capture |
| Alarmes CloudWatch | 3 alarmes | --no-alarms |
| Notification SNS | aucune (alarmes créées mais ne notifieront pas) | --sns-alarm-topic <arn> |
| Tag Environment | dev |
--environment |
| InferenceAmiVersion | aucun (valeur par défaut SageMaker) | --inference-ami-version (REQUIS pour vLLM CUDA 13+) |
Non définis (entrée spécifique à l'utilisateur nécessaire) : config VPC, clé KMS, multi-variant, inférence async.
Cible d'autoscaling — ajustez selon le type de modèle
La valeur par défaut --target-invocations-per-instance 20 est conservative et ajustée pour les workloads LLM où chaque requête prend 1–5 secondes. Pour les déploiements d'embedding (TEI), chaque requête est beaucoup plus rapide (typiquement <100ms sur CPU, <20ms sur GPU), donc une seule instance peut gérer beaucoup plus de débit. Pour les déploiements d'embedding, augmentez la cible à 100–500 selon la taille de l'instance et du modèle. La valeur par défaut de 20 déclenchera l'autoscaling bien trop agressivement pour les embeddings et gaspillera de l'argent.
Une règle empirique : valeur cible ≈ 60 / (latence de requête typique en secondes). LLM à 3s de latence → cible 20. Embedding à 100ms → cible 600. Les rerankers génératifs se situent entre les deux — ils génèrent un seul token par requête, donc ~40–100 est une cible raisonnable.
Capture de données + piège IAM
Si l'utilisateur active la capture de données, le rôle d'exécution a besoin d'accès en écriture S3 au préfixe de capture. L'URI par défaut (s3://sagemaker-<region>-<account>/<endpoint>/data-capture/) est typiquement un bucket différent du bucket d'artefacts du modèle. Si hf-cloud-sagemaker-iam-preflight a limité la politique inline étroitement à juste le bucket du modèle, les écritures de capture échouent silencieusement — l'endpoint continue de servir mais aucune donnée n'apparaît.
Si l'utilisateur rapporte « la capture de données n'apparaît pas », vérifiez l'accès S3 du rôle. Soit élargissez la politique inline soit passez --data-capture-s3-uri pointant vers un bucket auquel le rôle peut écrire.
Teardown
python3 scripts/teardown.py <endpoint-name> <region> # macOS / Linux
python scripts\teardown.py <endpoint-name> <region> # Windows
Supprime dans l'ordre sûr : alarmes → autoscaling → endpoint (arrête la facturation) → endpoint config → model. Idempotent.
Ne supprime pas : le rôle d'exécution IAM (peut être partagé), les objets S3 de capture de données (l'utilisateur peut vouloir les garder), le topic SNS, les artefacts du modèle original.
Dites toujours à l'utilisateur la commande teardown après le résumé de déploiement. Les utilisateurs oublient ; les endpoints accumulent les coûts.
Quand le déploiement échoue
CannotStartContainerError + aucun log CloudWatch jamais créé — le problème InferenceAmiVersion. Si le tag d'image contient cu130 ou plus récent et vous n'avez pas passé --inference-ami-version al2-ami-sagemaker-inference-gpu-3-1, c'est la cause. Voir hf-cloud-serving-image-selection. Ne chassez pas les images, rôles IAM, variables d'env, ou types d'instance — la signature de défaillance est identique pour beaucoup d'autres choses mais la cause ici est l'AMI.
« Failed to pass ping health check » — le conteneur a démarré et produit des logs, mais /ping ne répond pas. Vérifiez CloudWatch à /aws/sagemaker/Endpoints/<endpoint-name>. Usuellement : image mauvaise pour l'architecture du modèle, token HF manquant, ou OOM.
« Container failed to start » (avec logs présents) — l'entrypoint a tourné, puis s'est arrêté. Vérifiez CloudWatch. Courant : variables d'env requises manquantes (SM_VLLM_MODEL, SM_VLLM_HOST, SM_VLLM_TRUST_REMOTE_CODE), format ModelDataUrl faux, artefacts du modèle illisibles.
ResourceLimitExceeded — pas de quota pour le type d'instance dans cette région. Demandez une augmentation ou choisissez un type différent (le planner aurait dû vérifier les quotas à l'avance — voir hf-cloud-sagemaker-deployment-planner).
ImportError: libtorch_cuda.so: undefined symbol: ncclCommResume dans les logs CloudWatch — défaut d'empaquetage connu dans les images GPU huggingface-pytorch-inference (voir « Known-broken images » dans hf-cloud-serving-image-selection). À l'intérieur du conteneur, donc aucune variable d'env, AMI, type d'instance, ou tag frère ne le corrige. Basculez vers DJL Inference.
InService, mais les invocations expirent / les sorties async n'apparaissent jamais — worker Python mort derrière un front-end MMS vivant. Exécutez l'analyse des logs depuis « InService n'est pas succès » ci-dessus ; la traceback dans CloudWatch est la vraie erreur.
403 Forbidden téléchargement de poids depuis HF Hub au démarrage — le huggingface_hub fourni du conteneur prédante l'auth XET CDN de HF. Ajoutez --env HF_HUB_ENABLE_HF_TRANSFER=0, ou mettez les poids en place préalablement dans S3. Note : cela peut masquer une défaillance plus profonde (le worker peut toujours crash après que le téléchargement réussisse) — revérifiez les logs après l'avoir corrigé.
Règle de diagnostic : quand les défaillances ressemblent à identiques sur plusieurs configurations (différentes images, rôles, types d'instance) et aucun log n'est jamais produit, la cause est presque toujours sous le conteneur — AMI hôte, réseau, niveau-compte — pas la config de déploiement. Arrêtez d'itérer sur la config ; vérifiez la version AMI et l'état du compte.
Ne réessayez pas en aveugle. Le script imprime le FailureReason spécifique depuis describe-endpoint — corrigez la cause racine avant de réessayer.