La gestion du cycle de vie des modèles de vision par ordinateur à l’échelle d’une flotte est un problème qui se pose dès qu’une preuve de concept d’inspection qualité aboutit. Une caméra et un modèle fonctionnent bien. Ensuite, l’usine demande le même résultat sur 40 lignes, chacune avec plusieurs caméras, et chaque caméra voit le monde différemment. Chacune a un angle différent, un reflet différent sur la même surface en acier inoxydable et une lumière ambiante différente selon la porte du hangar ouverte. Dans les déploiements de production sur lesquels nous avons travaillé, un modèle unique entraîné sur des images regroupées est suffisamment moins performant que les caméras prises individuellement pour que chaque caméra finisse par avoir son propre modèle affiné. Le vrai problème n’est alors plus d’entraîner un seul modèle correctement. C’est d’entraîner, d’évaluer, de versionner et de livrer des milliers de modèles sans intervention humaine pour chacun d’eux.
Cet article met à jour le schéma introduit dans la série 2023 Construisez un pipeline MLOps de bout en bout pour l'inspection visuelle de la qualité à la périphérie pour le Amazon SageMaker Python SDK v3 actuel. Il remplace Amazon SageMaker Edge Manager, obsolète depuis, par Amazon SageMaker Neo pour l’optimisation en périphérie. Je présente un pipeline Amazon SageMaker à deux branches qui entraîne un détecteur d’anomalies et un classifieur de défauts par caméra, avec des garde-fous qualité qui bloquent les modèles insuffisamment performants. Une étape de compilation SageMaker Neo dans le pipeline garantit que chaque modèle enregistré est livré avec un artefact optimisé pour l’appareil, et une couche d’orchestration AWS Step Functions rythme l’entraînement de la flotte. Vous déployez le tout avec l’AWS Cloud Development Kit (AWS CDK), et une exécution complète du pipeline sur des données synthétiques coûte environ 0,07 $ en calcul.
L’exemple complet est disponible dans le dépôt GitHub aws-samples.
Présentation de la solution
Le diagramme suivant montre le cycle de vie des modèles à l’échelle de la flotte de l’exemple. Une couche d’orchestration met en file d’attente les demandes de réentraînement et limite le travail simultané. Un pipeline d’entraînement par caméra prépare les données, entraîne et évalue un détecteur d’anomalies et un classifieur, et produit des artefacts de modèle versionnés pour les appareils en périphérie.
Figure 1 : Architecture de la solution
La partie entraînement est un pipeline SageMaker unique, paramétré par caméra, qui exécute 13 étapes par exécution. Trois étapes d’entrée s’exécutent en parallèle : Preprocess, Fetch Latest Anomaly et Fetch Latest Classifier. Le pipeline prétraite les images de cette caméra et récupère le modèle approuvé le plus récent de la caméra pour chaque branche, puis entraîne deux modèles. Le premier est un petit autoencodeur convolutionnel qui apprend à quoi ressemble un produit normal et signale les anomalies par erreur de reconstruction. Le second est un classifieur ResNet18 affiné pour confirmer le défaut. Chaque branche exporte son modèle en ONNX, évalue l’artefact exporté sur des données de test non vues et passe par un contrôle qualité. Une fois ce contrôle franchi, le modèle est compilé avec SageMaker Neo pour la cible en périphérie configurée, et seulement ensuite enregistré dans le SageMaker Model Registry. Ainsi, chaque package approuvé dispose d’un artefact compilé prêt pour l’appareil. Les packages portent l’identifiant de la caméra en métadonnées, et la récupération de warm-start de chaque branche filtre sur cet identifiant, de sorte que le réentraînement repart toujours de l’historique propre à cette caméra.
La partie orchestration rythme la flotte. Les demandes de réentraînement arrivent sur une file Amazon Simple Queue Service (Amazon SQS) FIFO, et une fonction AWS Lambda démarre une exécution Step Functions par demande. Chaque exécution acquiert un créneau via un sémaphore DynamoDB avant de démarrer son pipeline, de sorte qu’une salve de demandes se draine à un rythme régulier au lieu de toutes se lancer d’un coup. Les achèvements sont publiés sur Amazon Simple Notification Service (Amazon SNS), et les échecs opérationnels parviennent au même sujet via une règle Amazon EventBridge avec l’erreur d’origine conservée.
La sortie du système est constituée d’artefacts de modèle versionnés, contrôlés en qualité et propres à chaque caméra. Le Model Registry conserve le modèle ONNX portable, et S3 conserve la compilation Neo de ce modèle, qui est une bibliothèque partagée optimisée pour l’appareil, chargée par le runtime DLR plutôt qu’en ONNX. Les deux sont prêts pour le mécanisme de livraison qu’utilise votre usine.
Prérequis
Pour déployer l’exemple, vous avez besoin des éléments suivants :
- Un compte AWS avec des autorisations pour créer des rôles AWS Identity and Access Management (IAM).
- Un compartiment Amazon Simple Storage Service (Amazon S3).
- Un dépôt Amazon Elastic Container Registry (Amazon ECR).
- Les ressources SageMaker AI, AWS Step Functions, AWS Lambda, Amazon Simple Queue Service (Amazon SQS), Amazon Simple Notification Service (Amazon SNS), Amazon EventBridge et Amazon DynamoDB dans les stacks.
- Python 3.10, 3.11 ou 3.12, Docker.
- La version 2.1131.0 ou ultérieure de l’interface de ligne de commande AWS CDK.
Les versions précédentes de l’interface de ligne de commande échouent avec une incompatibilité de schéma d’assembly cloud face aux versions récentes de aws-cdk-lib. Aucune augmentation de quota de service n’est nécessaire. La couche d’orchestration admet au maximum cinq exécutions de pipeline à la fois par défaut, ce qui maintient les tâches d’entraînement SageMaker simultanées dans le quota standard du compte.
Si vous déployez vers une Région AWS autre que celle par défaut de votre CLI, définissez les trois variables AWS_REGION, AWS_DEFAULT_REGION, et CDK_DEFAULT_REGION sur la commande de déploiement elle-même plutôt que dans votre profil shell, par exemple, AWS_REGION=us-east-2 AWS_DEFAULT_REGION=us-east-2 CDK_DEFAULT_REGION=us-east-2 cdk deploy --all. La CLI CDK, Boto3 et l'AWS Command Line Interface (AWS CLI) résolvent la Région à partir d'ensembles de variables qui se chevauchent sans être identiques, et une incohérence produit des déploiements partiels déroutants. Préfixer la commande définit les trois pour cette invocation uniquement, de sorte que votre Région par défaut personnelle reste inchangée.
Définition du pipeline d'entraînement avec le SDK v3
Le code de cette section est extrait de l'application CDK du dépôt plutôt que d'un script autonome, et il s'exécute pendant cdk deploy, au moment où la stack synthétise la définition du pipeline. La configuration de l'environnement et les commandes de déploiement sont présentées plus loin, sous la section Deploying and running the sample. Le SDK v3 a réorganisé le SageMaker Python SDK en paquets modulaires, et les imports montrent la nouvelle forme de la bibliothèque :
from sagemaker.train import ModelTrainer
from sagemaker.train.configs import InputData, Compute, OutputDataConfig
from sagemaker.core.training.configs import MetricDefinition
from sagemaker.serve.model_builder import ModelBuilder
from sagemaker.mlops.workflow.pipeline import Pipeline
from sagemaker.mlops.workflow.steps import ProcessingStep, TrainingStep
from sagemaker.mlops.workflow.model_step import ModelStep
from sagemaker.mlops.workflow.condition_step import ConditionStep
from sagemaker.core.workflow.conditions import ConditionGreaterThanOrEqualTo
from sagemaker.core.workflow.functions import JsonGet
L'installation de sagemaker>=3.0.0,<4 entraîne celle de sagemaker-core, sagemaker-train, sagemaker-serve, et sagemaker-mlops en tant que paquets distincts. La configuration d'entraînement que v2 répartissait entre les arguments du constructeur Estimator se trouve désormais dans des objets de configuration typés passés à ModelTrainer. Voici l'entraîneur de la branche d'anomalie, repris verbatim de l'exemple :
anomalib_trainer = ModelTrainer(
training_image=custom_training_image_uri,
compute=Compute(
instance_type=training_instance_type.to_string(),
instance_count=1,
),
base_job_name="cv-pipeline-anomalib-train",
sagemaker_session=pipeline_session,
role=role_arn,
hyperparameters={
"program": "train_anomalib.py",
"model_name": "autoencoder",
"image_size": "256",
"num_epochs": "3",
"batch_size": "16",
"learning_rate": "0.001",
},
input_data_config=anomalib_input_data,
output_data_config=OutputDataConfig(
s3_output_path=run_prefix("anomalib-training")
),
).with_metric_definitions(
[
MetricDefinition(
name="image_auroc",
regex=r"validation:image_auroc=([0-9]*\.?[0-9]+)",
),
]
)
anomalib_input_data connecte quatre canaux (train, validation, test, pretrained_model) à partir des propriétés des étapes en amont, et run_prefix limite chaque chemin S3 à l'ID d'exécution du pipeline afin que des exécutions concurrentes de flotte n'entrent jamais en collision. Les deux éléments se trouvent dans le dépôt.
Deux comportements de v3 comptent lorsque le pipeline réside dans une application CDK, et aucun des deux n'apparaît dans le changelog comme changement disruptif. Premièrement, le SDK v3 valide le rôle d'exécution du pipeline auprès de l'IAM en direct lors de la construction de la définition du pipeline. Cette validation a lieu au moment de la synthèse plutôt qu'au moment de l'exécution. Une application CDK ne peut pas créer le rôle et le pipeline en un seul déploiement, car le rôle n'existe pas encore lorsque le SDK le valide. Ceci est distinct de cdk bootstrap, qui approvisionne les ressources du kit d'outils CDK une fois par compte et par Région et qui a peut-être déjà été effectué dans votre compte. Le déploiement en deux phases de l'exemple est piloté par un indicateur de contexte qui porte justement ce même nom. Une première passe avec -c bootstrap=true crée le rôle, le compartiment et le dépôt ECR, et la deuxième passe synthétise le pipeline en s'appuyant sur le rôle désormais existant. L'Amazon Resource Name (ARN) du rôle intégré dans la définition du pipeline doit également être une chaîne concrète, et non un jeton CDK non résolu.
Deuxièmement, le validateur de rôle de v3 simule les autorisations de pull ECR sans ARN de ressource, de sorte qu'un rôle d'exécution limité à un seul dépôt ECR échoue à la validation même s'il fonctionnerait à l'exécution. Le rôle de l'exemple porte une autorisation ECR plus large pour cette raison.
Enfin, une habitude de v3 que l'exemple utilise : construire le pipeline avec PipelineSession(default_bucket=your_bucket) route les téléversements gérés par le SDK, tels que le code des étapes, vers le propre compartiment du pipeline, de sorte que chaque artefact réside au même endroit et que les autorisations du rôle d'exécution couvrent un seul compartiment. (Accorder au rôle l'accès au compartiment par défaut du SDK fonctionne aussi. Cette approche est plus propre.)
Des barrières de qualité adaptées au comportement des données de défauts
Chaque modèle entraîné est évalué avant l'enregistrement, et l'évaluation porte sur l'artefact ONNX exporté plutôt que sur le modèle PyTorch en mémoire. Cette distinction compte : l'artefact que vous validez est celui que vous livrez, de sorte qu'un problème d'exportation ne peut jamais faire passer un modèle défectueux malgré un entraînement d'apparence saine.
La branche d'anomalie exige un AUROC d'au moins 0.8. La branche de classification exige deux métriques à la fois, et le choix des métriques est façonné par la propriété déterminante des données de défauts : un fort déséquilibre des classes. Dans cet exemple, environ 90 pour cent des images sont bonnes, ce qui est réaliste pour une ligne de production. Face à cette distribution, la précision simple n'a aucun sens, car un modèle qui prédit « bon » pour chaque image obtient un score d'environ 0.9 tout en ne détectant aucun défaut. La précision équilibrée, c'est-à-dire la moyenne des rappels par classe, corrige cela, mais elle a son propre angle mort. Un modèle avec un rappel parfait sur les pièces bonnes et seulement 20 pour cent de rappel sur les défauts obtient quand même (1.0 + 0.2) / 2 = 0.6. La barrière exige donc une précision équilibrée d'au moins 0.6 et un rappel des défauts d'au moins 0.5, conjointement :
step_check_classifier = ConditionStep(
name="CheckClassifierQuality",
conditions=[
ConditionGreaterThanOrEqualTo(
left=JsonGet(
step_name=step_eval_classifier.name,
property_file="evaluation",
json_path="balanced_accuracy",
),
right=min_accuracy,
),
ConditionGreaterThanOrEqualTo(
left=JsonGet(
step_name=step_eval_classifier.name,
property_file="evaluation",
json_path="defect_recall",
),
right=min_defect_recall,
),
],
if_steps=[step_compile_classifier, step_register_classifier],
else_steps=[step_fail_classifier],
depends_on=[step_eval_classifier],
)
Un ConditionStep combine ses conditions avec un ET logique, donc les deux doivent être satisfaites. Les seuils sont des paramètres du pipeline, ajustables à chaque exécution sans redéploiement. Si une barrière échoue, un FailStep marque l'exécution comme échouée et le modèle de cette branche n'atteint jamais le registre (les deux branches valident indépendamment, de sorte que le modèle de l'autre branche peut quand même s'enregistrer). La flotte continue de servir le modèle approuvé précédent pour cette caméra.
Le code d'entraînement traite le même déséquilibre par l'autre bout. Le classifieur est un resnet18 affiné avec des poids ImageNet et une tête à deux classes. Aux tailles de jeux de données par caméra, c'est l'affinement des poids pré-entraînés qui empêche le modèle de s'effondrer sur la classe majoritaire. Le code d'entraînement équilibre les lots d'entraînement avec un WeightedRandomSampler utilisant les fréquences de classes inverses, tandis que la perte reste non pondérée. Combiner une perte pondérée avec un échantillonnage pondéré sur-corrige vers la classe minoritaire.
Compilation pour l'edge avec SageMaker Neo, à l'intérieur du pipeline
Notez la porte de qualité (gate) dans if_steps le code précédent : après la porte, la compilation s'exécute d'abord et l'enregistrement en dépend. Les deux scripts d'entraînement exportent en ONNX avec des axes de lot dynamiques. Les portes de qualité évaluent ces artefacts exportés sous ONNX Runtime, puis une étape CompileAnomalibNeo ou CompileClassifierNeo remet l'artefact validé à SageMaker Neo. L'invariant est : porte, puis compilation, puis enregistrement. Un modèle ne devient un package Approved que s'il a également été compilé pour la cible edge de la flotte, de sorte que le registre n'annonce jamais un modèle que les dispositifs ne peuvent pas exécuter.
L'étape de compilation encapsule trois détails sources d'erreurs. Neo exige que l'archive tar d'entrée contienne uniquement le fichier du modèle, mais les tâches d'entraînement émettent model.tar.gz avec le checkpoint et les métriques à côté du modèle ONNX. L'étape reconditionne d'abord une archive tar contenant uniquement le modèle ONNX. Neo exige également des formes d'entrée concrètes alors que les exports déclarent un axe de lot dynamique, donc la taille de lot 1 est figée dans la requête de compilation. Et comme les étapes de pipeline peuvent être réessayées, le nom de la tâche de compilation est dérivé de façon déterministe de l'ID d'exécution, de sorte qu'une nouvelle tentative reprend l'interrogation de la tâche déjà démarrée au lieu de l'abandonner.
La cible est un paramètre du pipeline : NeoTargetDevice sélectionne un dispositif nommé tel que jetson_xavier ou rasp4b, et sa valeur par défaut compile pour la NeoTargetOs/NeoTargetArch plateforme, LINUX/X86_64 par défaut. Le package compilé contient le modèle optimisé compiled.so ainsi que le runtime DLR pour le charger. La sortie par défaut s'exécute sur n'importe quel hôte Linux x86_64, donc vous pouvez la valider avant de vous engager sur des cibles spécifiques à un dispositif. La compilation elle-même n'entraîne aucun frais. Vous payez l'instance de traitement qui la pilote et le stockage S3 des sorties. Pour compiler ultérieurement un modèle déjà enregistré pour des types de dispositifs supplémentaires, sans ré-entraînement, le dépôt inclut un script ad hoc qui exécute le même flux sur le Model Registry.
Modèles par caméra dans le Model Registry
Chaque branche s'enregistre dans son propre groupe de packages de modèles, et chaque package porte l'identifiant de la caméra et ses métriques d'évaluation. D'après l'exemple, avec le câblage des métriques omis :
classifier_model_builder = ModelBuilder(
s3_model_data_url=classifier_model_data,
image_uri=custom_training_image_uri,
sagemaker_session=pipeline_session,
role_arn=role_arn,
)
step_register_classifier = ModelStep(
name="RegisterClassifierModel",
depends_on=[step_compile_classifier],
step_args=classifier_model_builder.register(
model_package_group_name="sm1k-classifier-model-group",
approval_status="Approved",
model_metrics=classifier_metrics,
customer_metadata_properties={"CameraId": camera_id},
),
)
L'identité de la caméra dans customer_metadata_properties est ce qui transforme deux groupes de packages partagés en un catalogue de flotte. Les packages de toutes les caméras vivent dans les mêmes groupes, et l'historique par caméra est retrouvé en filtrant sur les métadonnées. Chaque branche a une étape de récupération qui parcourt les packages approuvés du plus récent au plus ancien jusqu'à trouver le dernier modèle de la caméra demanderesse, car ListModelPackages ne peut pas filtrer côté serveur sur les métadonnées client. Lors de la première exécution d'une caméra, la récupération ne trouve rien et l'entraînement retombe sur son initialisation à froid (poids ImageNet pour le classifieur, initialisation aléatoire pour l'auto-encodeur). Chaque exécution suivante repart du modèle précédent propre à cette caméra, ce qui rend viable une ré-entraînement fréquent, peu coûteux et incrémental sur une flotte.
Notez ce que ces entrées de registre sont et ne sont pas : elles cataloguent des artefacts versionnés et validés par qualité, mais leur référence de conteneur est l'image d'entraînement, qui ne sert aucun endpoint d'inférence. Elles existent pour un flux de travail de flotte où un système en aval récupère les artefacts et les expédie aux dispositifs, et non pour CreateEndpoint.
Orchestrer les demandes d'entraînement de la flotte
Chaque compte AWS a des quotas de service sur les tâches d'entraînement SageMaker simultanées, et chaque exécution de pipeline ici lance plusieurs tâches, donc la ré-entraînement de la flotte doit être rythmé. Quelque chose doit empêcher une rafale de 500 demandes de devenir 500 lancements de pipeline simultanés. L'exemple utilise un sémaphore, un compteur d'emplacements disponibles qui fonctionne comme un parking de cinq places. Avant que la machine à états Step Functions ne démarre un pipeline, elle prend un emplacement en incrémentant atomiquement un compteur dans DynamoDB, avec une condition qui refuse l'incrément une fois la limite atteinte :
acquire_semaphore = tasks.DynamoUpdateItem(
self, "AcquireSemaphore",
table=semaphore_table,
key={"LockName": tasks.DynamoAttributeValue.from_string("pipeline-concurrency")},
update_expression="SET ActiveCount = if_not_exists(ActiveCount, :zero) + :one",
condition_expression="attribute_not_exists(ActiveCount) OR ActiveCount < :limit",
)
Si tous les emplacements sont pris, l'exécution attend et réessaie. Lorsque son pipeline se termine, elle décrémente le compteur pour libérer l'emplacement. Un détail mérite d'être repris dans vos propres conceptions. L'expression de condition DynamoDB impose la limite en une seule opération atomique, plutôt que la machine à états lisant le compteur puis décidant, ce qui laisserait une fenêtre de course entre la lecture et l'écriture. Le dépôt implémente le reste du durcissement opérationnel, y compris la libération des emplacements sur tous les chemins d'échec et le transfert des exécutions échouées vers Amazon SNS via Amazon EventBridge. Son README couvre le dimensionnement des bornes d'attente.
La limite de cinq est un paramètre de la pile, réglé bas pour que l'exemple se déploie et s'exécute dans n'importe quel compte standard sans demander d'augmentation de quota. Ajustez-la selon ce que les quotas de tâches d'entraînement de votre compte prennent en charge. Au-delà des quotas d'un seul compte, le passage à l'échelle de l'entraînement de flotte est un sujet à part entière. Les leviers standards sont l'augmentation des quotas de service, la répartition de l'entraînement sur plusieurs types d'instances ou Régions, et la distribution de la flotte sur plusieurs comptes. Vous pouvez aussi maintenir le backlog dans SQS et n'admettre du travail qu'à mesure que de la capacité se libère. Ces choix dépendent de la taille et de la forme de votre flotte. L'exemple vous fournit le squelette fonctionnel à étendre.
Déployer et exécuter l'exemple
La procédure pas à pas du README du dépôt fait foi. La forme abrégée suivante part d'un checkout propre et configure l'environnement avant le premier déploiement :
git clone https://github.com/aws-samples/sample-sagemaker-fleet-cv-pipeline
cd sample-sagemaker-fleet-cv-pipeline && python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cdk bootstrap # once per account/region
cdk deploy SageMakerCVPipelineStack -c bootstrap=true # roles, bucket, ECR
cdk deploy --all # pipeline + orchestration
./scripts/push_ecr_image.sh # build and push training image
Par défaut, le pipeline génère des images synthétiques de chaîne de production, ce qui vous permet de valider l'ensemble du système de bout en bout sans jeu de données ni approbation de données. Une exécution complète exécute les 13 étapes, y compris les deux compilations Neo, en environ 15 minutes et coûte environ $0.07 en temps de calcul facturé ml.m5.xlarge (Région US East (Ohio) (us-east-2), juillet 2026, à la demande). Les ressources d'orchestration coûtent au total bien moins de $0.10 par mois à l'échelle de l'échantillon.
Pour exercer le pipeline sur de vraies images, le dépôt inclut un outil de téléchargement pour le jeu de données BCCD. Ce jeu de données est une collection de 364 images microscopiques annotées de frottis sanguins, initialement publiées en open source sur GitHub et réorganisées au format Pascal VOC sous une licence MIT. Le même jeu de données est également distribué par Roboflow en tant que domaine public. Le script associe la présence de plaquettes à la classe de défaut, divise éventuellement les images par caméra pour simuler une flotte, et téléverse les archives d'entraînement dans le bucket du pipeline. Le notebook accompagnant l'article exécute le pipeline complet sur les données réelles et effectue une inférence ONNX côte à côte sur un échantillon normal et un échantillon anormal. Traiter les plaquettes comme des défauts est un substitut illustratif d'une distribution de défauts de fabrication, et non une affirmation clinique.
Nettoyage
Les packages de modèle sont créés au moment de l'exécution du pipeline, de sorte qu'AWS CloudFormation ne peut pas les supprimer. Supprimez-les d'abord avec l'AWS CLI, puis détruisez les piles avec cdk destroy --all -c bootstrap=true --force. Le README liste les commandes exactes.
Conclusion
Dans cet article, j'ai présenté une architecture de référence fonctionnelle avec le SDK SageMaker v3 pour le cycle de vie de la vision par ordinateur à l'échelle d'une flotte. L'architecture comprend un pipeline d'entraînement à double branche dans lequel chaque modèle est soumis à un contrôle qualité sur son artefact ONNX exporté, compilé avec SageMaker Neo, et seulement ensuite enregistré. Une organisation du Model Registry utilise les métadonnées de la caméra pour transformer des groupes de packages partagés en historique par caméra permettant un démarrage à chaud, et une couche d'orchestration Step Functions rythme l'entraînement de la flotte dans les limites des quotas du compte. Pour les équipes dont les besoins sont couverts par des options gérées telles qu'Amazon Lookout for Vision, c'est le chemin le plus simple. Ce modèle cible la contrainte par caméra, à l'échelle de la flotte, que les options gérées ne couvrent pas. Les règles de conception se transposent au-delà de la vision par ordinateur : effectuer le contrôle qualité sur la précision équilibrée plus le rappel de la classe minoritaire plutôt que sur la seule précision, évaluer et compiler l'artefact que vous livrez, et faire en sorte que les échecs opérationnels notifient plutôt que de se terminer en silence.
L'exemple se déploie en quelques minutes et une exécution de validation complète coûte environ sept cents de calcul, une prochaine étape à faible coût consiste donc à cloner le dépôt et à l'exécuter. À partir de là, pointez l'outil de téléchargement BCCD vers un vrai bucket, définissez NeoTargetDevice pour votre matériel, ou dimensionnez les bornes d'orchestration selon votre flotte. Pour la migration vers le SDK v3 elle-même, la documentation du SageMaker Python SDK et la série en deux parties annonçant le nouveau SDK sont les références que j'ai gardées ouvertes tout au long du processus.
Si vous souhaitez de l'aide pour adapter ce modèle à votre environnement de production, contactez votre équipe de compte AWS.