Aidez à améliorer cette page
Pour contribuer à ce guide de l’utilisateur, cliquez sur le lien Modifier cette page sur GitHub qui se trouve dans le volet droit de chaque page.
Guide de configuration de clusters basé sur les meilleures pratiques pour l’inférence en temps réel sur Amazon EKS
Introduction
Ce guide propose une procédure pratique pour configurer un cluster Amazon Elastic Kubernetes Service (EKS) optimisé pour les charges de travail d’inférence en ligne en temps réel, en intégrant les meilleures pratiques sélectionnées par des experts AWS. Il utilise une architecture EKS Quickstart bien pensée, un ensemble sélectionné de pilotes, de types d’instances et de configurations conformes aux meilleures pratiques AWS en matière de modèles, d’accélérateurs et de mise à l’échelle. Cette approche vous évite d’avoir à sélectionner les paramètres du cluster, ce qui vous permet de disposer rapidement d’un cluster fonctionnel et préconfiguré. Au cours de cette présentation, nous déploierons des exemples de charges de travail pour valider votre configuration, expliquerons les concepts architecturaux clés (tels que le découplage des tâches liées au CPU des calculs intensifs sur GPU), répondrons aux questions courantes (par exemple, pourquoi choisir Bottlerocket AMI plutôt que AL2023 ?) et présenterons les prochaines étapes pour étendre les capacités de votre cluster.
Conçu spécialement pour les ingénieurs en machine learning (ML) et en intelligence artificielle (IA), les administrateurs de plateformes, les opérateurs et les spécialistes des données/IA qui découvrent l’écosystème AWS et EKS, ce guide suppose une connaissance de Kubernetes, mais aucune expérience préalable d’EKS. Il est conçu pour vous aider à comprendre les étapes et les processus nécessaires à la mise en place et au fonctionnement des charges de travail d’inférence en ligne en temps réel. Ce guide vous présente les éléments essentiels à la création d’un cluster d’inférence à nœud unique, notamment l’approvisionnement en ressources GPU, l’intégration du stockage pour les artefacts de modèle, l’activation d’un accès sécurisé aux services AWS et l’exposition des points de terminaison d’inférence. Tout au long du document, l’accent est mis sur une conception résiliente et à faible latence pour les applications destinées aux utilisateurs, telles que la détection des fraudes, les chatbots en temps réel et l’analyse des sentiments dans les systèmes de commentaires des clients.
Dans ce guide, nous nous concentrons exclusivement sur la mise en place d’un point de départ fondamental et normatif à l’aide d’instances G5 EC2. Si vous recherchez des configurations de clusters spécifiques à AWS Inferentia ou des workflows de bout en bout, consultez Utilisez les instances Inferentia AWS avec Amazon EKS pour le machine learning ou nos ateliers dans Ressources pour démarrer avec l’IA/ML sur Amazon EKS.
Avant de commencer
Avant de commencer, assurez-vous d’avoir effectué les tâches suivantes :
Architecture
L’inférence en ligne en temps réel désigne le processus consistant à utiliser un modèle de machine learning entraîné pour générer des prédictions ou des résultats sur des flux de données entrants avec une latence minimale. Par exemple, il permet la détection de fraudes en temps réel, la classification d’images ou la génération de graphes de connaissances en réponse aux entrées des utilisateurs. L’architecture d’un système d’inférence en ligne en temps réel fournit des prédictions de machine learning à faible latence dans les applications destinées aux utilisateurs en dissociant le traitement du trafic Web lié au processeur des calculs d’IA gourmands en ressources GPU. Ce processus s’inscrit généralement dans un écosystème applicatif plus large et comprend souvent des services dorsaux, frontaux, vectoriels et de modélisation, en mettant l’accent sur des composants spécialisés afin de permettre une évolutivité indépendante, un développement parallèle et une résilience face aux défaillances. L’isolation des tâches d’inférence sur du matériel GPU dédié et l’utilisation d’interfaces telles que les API et les WebSockets garantissent une forte concurrence, un traitement rapide des modèles tels que les transformateurs et les interactions utilisateur via le frontend. Notez que, bien que les bases de données vectorielles et les pipelines de génération à enrichissement contextuel (RAG) jouent souvent un rôle important dans les systèmes d’inférence en temps réel, ces composants ne sont pas abordés dans ce guide. Au minimum, une architecture typique comprend souvent :
-
Service frontal : sert d’interface utilisateur, gère la logique côté client, affiche le contenu dynamique et facilite les interactions en temps réel. Il communique avec le service dorsal pour lancer des requêtes d’inférence et afficher les résultats, lançant souvent des requêtes vers le service dorsal qui utilise des WebSockets pour diffuser les mises à jour ou des API pour échanger des données structurées. Ce service ne nécessite généralement pas d’équilibreur de charge dédié, car il peut être hébergé sur des réseaux de diffusion de contenu (CDN) tels que AWS CloudFront pour les ressources statiques, ou directement depuis des serveurs Web, avec une mise à l’échelle gérée par des groupes Auto Scaling si nécessaire pour le contenu dynamique.
-
Service dorsal : agit comme orchestrateur de l’application, gérant la logique métier telle que l’authentification des utilisateurs, la validation des données et la coordination des services (par exemple, via des API pour les points de terminaison RESTful ou des WebSockets pour les connexions persistantes). Il communique avec le service d’inférence, s’adapte de manière indépendante aux processeurs multicœurs et à la mémoire vive pour gérer un trafic web élevé sans dépendre des processeurs graphiques, et nécessite souvent un équilibreur de charge (tel que AWS Application Load Balancer ou Network Load Balancer) pour répartir les requêtes entrantes entre plusieurs instances, en particulier dans les scénarios à forte concurrence. Un contrôleur d’accès peut également gérer les règles d’accès externe et de routage afin d’améliorer la sécurité et la gestion du trafic.
-
Service d’inférence : sert de noyau pour les calculs d’IA, fonctionnant sur des GPU dotés d’une mémoire VRAM suffisante (par exemple, 8 à 12 Go pour des modèles tels que DistilBERT) afin d’effectuer des intégrations vectorielles, l’extraction de connaissances et l’inférence de modèles (par exemple, exposée via des API pour les requêtes par lots ou des WebSockets pour le streaming en temps réel) à l’aide de modèles personnalisés ou open source. Cet isolement empêche les conflits de dépendance, permet la mise à jour des modèles sans durée d’indisponibilité du service et facilite la mise à l’échelle horizontale avec équilibrage de charge pour plusieurs requêtes simultanées. Pour exposer efficacement le service modèle, celui-ci est généralement placé derrière un équilibreur de charge afin de répartir les charges de travail liées au GPU entre les instances répliquées, tandis qu’une ressource ou un contrôleur d’entrée (tel que ALB Ingress Controller dans AWS) gère le routage externe, la terminaison SSL et le transfert basé sur le chemin d’accès afin de garantir un accès sécurisé et efficace sans surcharger les GPU individuels.
Présentation de la solution
Les systèmes d’inférence en ligne en temps réel nécessitent une architecture résiliente et hautement performante, capable d’offrir une latence ultra-faible tout en gérant des pics de trafic imprévisibles et volumineux. Cette présentation de la solution explique comment les composants AWS suivants fonctionnent ensemble dans le cluster Amazon EKS que nous allons créer afin de garantir que notre cluster soit capable d’héberger et de gérer des modèles de machine learning qui fournissent des prédictions immédiates sur des données en temps réel avec un délai minimal pour les utilisateurs finaux.
-
Instances Amazon G5 EC2
— Pour les tâches d’inférence gourmandes en ressources GPU, nous utilisons les types d’instances g5.xlarge et g5.2xlarge G5 EC2, qui disposent d’un (1) GPU NVIDIA A10G avec 24 Go de mémoire (par exemple, 8 milliards de paramètres en FP16). Basés sur l’architecture NVIDIA Ampere, ces GPU sont équipés de GPU NVIDIA A10G Tensor Core et de processeurs AMD EPYC de 2e génération. Ils prennent en charge 4 à 8 vCPU, une bande passante réseau pouvant atteindre 10 Gbit/s et 250 à 450 Go de stockage SSD NVMe local, garantissant ainsi un transfert rapide des données et une puissance de calcul suffisante pour les modèles complexes. Ils sont donc parfaits pour les tâches d’inférence à faible latence et haut débit. Le choix d’un type d’instance EC2 dépend de l’application, de votre modèle (par exemple, modèle d’image, de vidéo ou de texte) et de vos exigences en matière de latence et de débit. Par exemple, si vous utilisez un modèle d’image et/ou de vidéo, vous pouvez utiliser des instances P5 EC2 pour obtenir une latence optimale en temps réel. Nous vous recommandons de commencer avec des instances G5 EC2 , car elles constituent un bon point de départ pour être rapidement opérationnel, puis d’évaluer si elles sont adaptées à vos charges de travail à l’aide de tests de performance. Pour les cas plus avancés, pensez aux instances G6 EC2. -
Instances Amazon EC2 M7g
— Pour les tâches gourmandes en ressources CPU telles que le prétraitement des données, le traitement des requêtes API, l’hébergement du contrôleur Karpenter, les modules complémentaires et d’autres composants système, nous utilisons le type d’instance EC2 m5.xlarge M7g. Les instances M7g sont des instances basées sur ARM qui disposent de 4 vCPU, 16 Go de mémoire, une bande passante réseau pouvant atteindre 12,5 Gbit/s et sont équipées de processeurs AWS Graviton3. Le choix d’un type d’instance EC2 dépend de l’application et des exigences de votre charge de travail en matière de calcul, de mémoire et de capacité de mise à l’échelle. Pour les charges de travail optimisées pour le calcul, vous pouvez envisager les instances EC2 C7g , qui utilisent également des processeurs Graviton3, mais qui sont optimisées pour offrir des performances de calcul supérieures à celles des instances M7g dans certains cas d’utilisation. Par ailleurs, les nouvelles instances EC2 C8g (lorsqu’elles sont disponibles) offrent des performances de calcul jusqu’à 30 % supérieures à celles des instances C7g. Nous vous recommandons de commencer par les instances M7g EC2 pour leur rentabilité et leur compatibilité avec un large éventail de charges de travail (par exemple, serveurs d’applications, microservices, serveurs de jeux, magasins de données de taille moyenne), puis d’évaluer si elles conviennent à vos charges de travail à l’aide de tests de performance. -
Pilote CSI Amazon S3 Mountpoint : Pour les charges de travail sur des instances à GPU unique où plusieurs pods partagent un GPU (par exemple, plusieurs pods planifiés sur le même nœud pour utiliser ses ressources GPU), nous utilisons le pilote CSI Mountpoint S3 afin d’optimiser l’utilisation de la mémoire, ce qui est essentiel pour des tâches telles que l’inférence de modèles volumineux dans des configurations peu complexes et sensibles aux coûts. Il expose les compartiments Amazon S3 comme un système de fichiers de type POSIX accessible au cluster Kubernetes, ce qui permet aux pods d’inférence de lire les artefacts du modèle (par exemple, les poids du modèle) directement en mémoire sans avoir à les télécharger au préalable, et d’entrer des jeux de données à l’aide d’opérations de fichiers standard. De plus, S3 dispose d’une capacité de stockage pratiquement illimitée et accélère les charges de travail d’inférence gourmandes en données. Le choix d’un pilote CSI de stockage dépend de l’application et des exigences de votre charge de travail en matière de débit et de latence. Bien que le pilote FSx pour OpenZFS CSI offre une latence inférieure à la milliseconde pour les E/S aléatoires ou des volumes persistants partagés entièrement compatibles POSIX entre les nœuds, nous vous recommandons de commencer par le pilote Mountpoint S3 CSI en raison de sa capacité de mise à l’échelle, de ses coûts réduits pour les grands jeux de données et de son intégration native avec le stockage d’objets géré par S3 pour les modèles d’inférence à forte intensité de lecture (par exemple, les entrées de modèles en streaming), puis d’évaluer s’il convient à vos charges de travail à l’aide de tests de performance.
-
Agent d’identité EKS Pod : Pour permettre l’accès aux services AWS, nous utilisons l’agent d’identité EKS Pod, qui utilise un seul principal de service et facilite les associations de rôles IAM au niveau des pods au sein du cluster Amazon EKS. EKS Pod Identity offre une alternative simplifiée à l’approche traditionnelle IAM Roles for Service Accounts (IRSA) en utilisant un seul principal de service (pods.eks.amazonaws.com) au lieu de s’appuyer sur des fournisseurs OIDC individuels pour chaque cluster, ce qui facilite l’attribution des autorisations. De plus, il permet de réutiliser les rôles dans plusieurs clusters et prend en charge des fonctionnalités avancées telles que les balises de session de rôle IAM et les rôles IAM cibles.
-
Agent de surveillance des nœuds EKS : afin de garantir la disponibilité et la fiabilité continues des services d’inférence, nous utilisons l’agent de surveillance des nœuds EKS avec réparation automatique, qui détecte et remplace automatiquement les nœuds défectueux, minimisant ainsi la durée d’indisponibilité. Il surveille en permanence les nœuds à la recherche de problèmes matériels, liés au noyau, au réseau et au stockage à l’aide de contrôles de santé améliorés (par exemple, KernelReady, NetworkingReady). Pour les nœuds GPU, il détecte les défaillances spécifiques à l’accélérateur, lance une correction en douceur en isolant les nœuds défaillants, attend 10 minutes que les problèmes GPU transitoires se résolvent et remplace les nœuds après 30 minutes en cas de défaillances persistantes.
-
AMI Bottlerocket : Afin de fournir une base sécurisée à notre cluster EKS, nous utilisons l’AMI Bottlerocket, qui ne comprend que les composants essentiels nécessaires au fonctionnement des conteneurs et offre des temps de démarrage minimaux pour une mise à l’échelle rapide. Le choix d’une AMI de nœud dépend de l’application et des exigences de personnalisation, de sécurité et de capacité de mise à l’échelle de votre charge de travail. Bien que l’AMI AL2023 offre une plus grande flexibilité pour les installations et les personnalisations au niveau de l’hôte (par exemple, en spécifiant un répertoire de cache dédié dans un PV/PVC sans aucune configuration supplémentaire des nœuds), nous vous recommandons de commencer par l’AMI Bottlerocket en raison de son encombrement réduit et de son optimisation intégrée pour les charges de travail conteneurisées (par exemple, microservices, serveurs d’inférence, API évolutives), puis d’évaluer si elle convient à vos charges de travail à l’aide de tests de performance.
-
AWS Load Balancer Controller (LBC) : pour exposer les points de terminaison d’inférence en temps réel, nous utilisons l’AWS Load Balancer Controller, qui provisionne et gère automatiquement les Application Load Balancers (ALB) pour le trafic HTTP/HTTPS et les Network Load Balancers (NLB) pour le trafic TCP/UDP en fonction des ressources Kubernetes Ingress et Service, permettant ainsi l’intégration de modèles d’inférence avec des clients externes. De plus, il prend en charge des fonctionnalités telles que le routage basé sur le chemin pour répartir les demandes d’inférence entre plusieurs pods ou nœuds, garantissant ainsi la capacité de mise à l’échelle pendant les pics de trafic et minimisant la latence grâce à des optimisations natives AWS telles que le multiplexage des connexions et les surveillances de l’état.
1. Créer votre cluster EKS
Au cours de cette étape, nous créons un cluster avec des nœuds CPU et un groupe de nœuds gérés à l’aide d’un modèle eksctl ClusterConfig
Par défaut, eksctl créera un VPC dédié pour le cluster avec un bloc CIDR de 192.168.0.0/16. Le VPC comprend trois sous-réseaux publics et trois sous-réseaux privés, chacun réparti sur trois zones de disponibilité différentes (ou deux zones de disponibilité dans la région us-east-1), ce qui constitue la méthode idéale pour déployer des charges de travail Kubernetes. Le modèle déploie également une passerelle Internet, fournissant un accès Internet aux sous-réseaux publics via des routes par défaut dans leurs tables de routage et une passerelle NAT unique dans l’un des sous-réseaux publics, avec des routes par défaut dans les tables de routage des sous-réseaux privés dirigeant le trafic sortant via la passerelle NAT pour l’accès à Internet. Pour en savoir plus sur cette configuration, consultez Déployer des nœuds dans des sous-réseaux privés.
Vérifiez vos informations d’identification
Vérifiez si vos identifiants AWS CLI sont valides et peuvent s’authentifier auprès des services AWS :
aws sts get-caller-identity
En cas de succès, la CLI renverra des informations sur votre identité AWS (UserID, Account et Arn).
Vérifier la disponibilité des instances
Les types d’instances G5 ne sont pas disponibles dans toutes les régions. Vérifiez votre région la plus proche. Par exemple :
aws ec2 describe-instance-types --instance-types g5.xlarge g5.2xlarge --region us-east-1
Si l’opération réussit, le type d’instance G5 est disponible dans la région que vous avez spécifiée.
L’AMI Bottlerocket n’est pas disponible dans toutes les régions. Vérifiez en récupérant un identifiant AMI Bottlerocket pour votre région la plus proche. Par exemple :
aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-1.33/arm64/latest/image_id \ --region us-east-1 --query "Parameter.Value" --output text
Si l’opération réussit, l’AMI Bottlerocket est disponible dans la région que vous avez spécifiée.
Préparez votre environnement
Commencez par définir les variables d’environnement suivantes dans une nouvelle fenêtre de terminal. Remarque : Veillez à remplacer les espaces réservés par vos propres valeurs, notamment le nom du cluster, la région souhaitée, la version de Karpenter
Astuce
Certaines variables (telles que ${AWS_REGION} et ${K8S_VERSION}) sont définies au début du bloc, puis référencées dans les commandes suivantes pour des raisons de cohérence et afin d’éviter les répétitions. Veillez à exécuter les commandes dans l’ordre afin que ces valeurs soient correctement exportées et disponibles pour être utilisées dans les définitions suivantes.
export TEMPOUT="$(mktemp)" export K8S_VERSION=1.33 export KARPENTER_VERSION="1.5.0" export AWS_REGION="us-east-1" export EKS_CLUSTER_NAME="eks-rt-inference-${AWS_REGION}" export S3_BUCKET_NAME="eks-rt-inference-models-${AWS_REGION}-$(date +%s)" export NVIDIA_BOTTLEROCKET_AMI="$(aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-${K8S_VERSION}-nvidia/x86_64/latest/image_id --query Parameter.Value --output text)" export STANDARD_BOTTLEROCKET_AMI="$(aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-${K8S_VERSION}/arm64/latest/image_id --query Parameter.Value --output text)" export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)" export ALIAS_VERSION="$(aws ssm get-parameter --name "/aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2023/x86_64/standard/recommended/image_id" --query Parameter.Value | xargs aws ec2 describe-images --query 'Images[0].Name' --image-ids | sed -r 's/^.*(v[[:digit:]]+).*$/\1/')"
Créer les rôles et les politiques requis
Karpenter a besoin de rôles et de politiques IAM spécifiques (par exemple, rôle IAM du contrôleur Karpenter, profil d’instance et politiques) pour gérer les instances EC2 en tant que composants master Kubernetes. Il utilise ces rôles pour effectuer des actions telles que le lancement et l’arrêt d’instances EC2, le marquage de ressources et l’interaction avec d’autres services AWS. Créez les rôles et politiques Karpenter à l’aide du fichier cloudformation.yaml
curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v${KARPENTER_VERSION}/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml > "${TEMPOUT}" \ && aws cloudformation deploy \ --stack-name "Karpenter-${EKS_CLUSTER_NAME}" \ --template-file "${TEMPOUT}" \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides "ClusterName=${EKS_CLUSTER_NAME}"
Le AWS LBC a besoin d’une autorisation pour provisionner et gérer les équilibreurs de charge AWS, par exemple pour créer des ALB pour les ressources Ingress ou des NLB pour les services de type LoadBalancer. Nous définirons cette politique d’autorisation lors de la création du cluster. Lors de la création du cluster, nous créerons le compte de service avec eksctl dans ClusterConfig. Créer la politique IAM LBC :
aws iam create-policy \ --policy-name AWSLoadBalancerControllerIAMPolicy \ --policy-document "$(curl -fsSL https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.0/docs/install/iam_policy.json)"
Lorsque le pilote CSI Mountpoint S3 est installé, ses pods DaemonSet sont configurés pour utiliser un compte de service pour l’exécution. Le pilote Mountpoint pour Mountpoint S3 CSI nécessite une autorisation pour interagir avec le compartiment Amazon S3 que vous créerez plus loin dans ce guide. Nous définirons cette politique d’autorisation lors de la création du cluster. Lors de la création du cluster, nous créerons le compte de service avec eksctl dans ClusterConfig. Créez la politique IAM S3 :
aws iam create-policy \ --policy-name S3CSIDriverPolicy \ --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:ListBucket\"], \"Resource\": [\"arn:aws:s3:::${S3_BUCKET_NAME}\", \"arn:aws:s3:::${S3_BUCKET_NAME}/*\"]}]}"
Remarque : Si un rôle portant ce nom existe déjà, donnez-lui un autre nom. Le rôle que nous créons à cette étape est spécifique à votre cluster et à votre compartiment S3.
Créer le cluster
Dans ce modèle, eksctl crée automatiquement un compte de service Kubernetes pour EKS Pod Identity, Node Monitoring Agent, CoreDNS, Kubeproxy et le plug-in VPC CNI. À ce jour, le pilote Mountpoint S3 CSI n’est pas disponible pour EKS Pod Identity. Nous créons donc un rôle IAM pour compte de service (IRSA) et un point de terminaison OIDC. En outre, nous créons un compte de service pour AWS Load Balancer Controller (LBC). Pour accéder aux nœuds Bottlerocket, eksctl attache automatiquement AmazonSSMManagedInstanceCore pour Bottlerocket afin de permettre des sessions Secure Shell via SSM.
Dans le même terminal où vous définissez vos variables d’environnement, exécutez le bloc de commande suivant pour créer le cluster :
eksctl create cluster -f - <<EOF --- apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: ${EKS_CLUSTER_NAME} region: ${AWS_REGION} version: "${K8S_VERSION}" tags: karpenter.sh/discovery: ${EKS_CLUSTER_NAME} # Add more tags if needed for billing iam: # Creates an OIDC endpoint and IRSA service account for the Mountpoint S3 CSI Driver # Uses the S3 CSI Driver policy for permissions withOIDC: true podIdentityAssociations: # Creates the pod identity association and service account # Uses the Karpenter controller IAM policy for permissions - namespace: "kube-system" serviceAccountName: karpenter roleName: ${EKS_CLUSTER_NAME}-karpenter permissionPolicyARNs: - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${EKS_CLUSTER_NAME} # Creates the pod identity association and service account # Uses the AWS LBC policy for permissions - namespace: kube-system serviceAccountName: aws-load-balancer-controller createServiceAccount: true roleName: AmazonEKSLoadBalancerControllerRole permissionPolicyARNs: - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy iamIdentityMappings: - arn: "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${EKS_CLUSTER_NAME}" username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes managedNodeGroups: # Creates 2 CPU nodes for lightweight system tasks - name: ${EKS_CLUSTER_NAME}-m7-cpu instanceType: m7g.xlarge amiFamily: Bottlerocket desiredCapacity: 2 minSize: 1 maxSize: 10 labels: role: cpu-worker # Enable automatic Pod Identity associations for VPC CNI Driver, coreDNS, kube-proxy addonsConfig: autoApplyPodIdentityAssociations: true addons: # Installs the S3 CSI Driver addon and creates IAM role # Uses the S3 CSI Driver policy for IRSA permissions - name: aws-mountpoint-s3-csi-driver attachPolicyARNs: - "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/S3CSIDriverPolicy" - name: eks-pod-identity-agent - name: eks-node-monitoring-agent - name: coredns - name: kube-proxy - name: vpc-cni EOF
Ce processus prend plusieurs minutes. Si vous souhaitez surveiller le statut, consultez la console AWS CloudFormation
2. Vérifiez l’état du nœud et du pod du cluster
Effectuons quelques vérifications de santé pour nous assurer que le cluster est prêt. Une fois la commande précédente terminée, affichez les types d’instance et vérifiez que vos nœuds système CPU ont atteint l’état Ready souhaité à l’aide de la commande suivante :
kubectl get nodes -L node.kubernetes.io/instance-type
Le résultat attendu devrait ressembler à ceci :
NAME STATUS ROLES AGE VERSION INSTANCE-TYPE ip-192-168-35-103.ec2.internal Ready <none> 12m v1.33.0-eks-802817d m7g.xlarge ip-192-168-7-15.ec2.internal Ready <none> 12m v1.33.0-eks-802817d m7g.xlarge
Vérifiez toutes les associations d’identité de pod et la manière dont elles mappent un rôle à un compte de service dans un espace de noms du cluster à l’aide de la commande suivante :
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
La sortie doit indiquer les rôles IAM de Karpenter (« karpenter ») et du AWS LBC (« aws-load-balancer-controller »).
Vérifiez que les DAEMONSETS sont disponibles :
kubectl get daemonsets -n kube-system
Le résultat attendu devrait ressembler à ceci :
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE aws-node 3 3 3 3 3 <none> 12m dcgm-server 0 0 0 0 0 kubernetes.io/os=linux 12m eks-node-monitoring-agent 3 3 3 3 3 kubernetes.io/os=linux 12m eks-pod-identity-agent 3 3 3 3 3 <none> 12m kube-proxy 3 3 3 3 3 <none> 12m s3-csi-node 2 2 2 2 2 kubernetes.io/os=linux 12m
Vérifiez que tous les addons sont installés sur le cluster :
eksctl get addons --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
Le résultat attendu devrait ressembler à ceci :
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES POD IDENTITY ASSOCIATION ROLES aws-mountpoint-s3-csi-driver v1.15.0-eksbuild.1 ACTIVE 0 arn:aws:iam::143095308808:role/eksctl-eks-rt-inference-us-east-1-addon-aws-m-Role1-RAUjk4sJnc0L coredns v1.12.1-eksbuild.2 ACTIVE 0 eks-node-monitoring-agent v1.3.0-eksbuild.2 ACTIVE 0 eks-pod-identity-agent v1.3.7-eksbuild.2 ACTIVE 0 kube-proxy v1.33.0-eksbuild.2 ACTIVE 0 metrics-server v0.7.2-eksbuild.3 ACTIVE 0 vpc-cni v1.19.5-eksbuild.1 ACTIVE 0
3. Installez Karpenter
Installez le contrôleur Karpenter sur les composants master de votre processeur (cpu-worker) afin d’optimiser les coûts et de préserver les ressources du GPU. Nous allons l’installer dans l’espace de noms « kube-system » et spécifier le compte de service « karpenter » que nous avons défini lors de la création du cluster. En outre, cette commande configure le nom du cluster et une file d’attente d’interruption d’instance Spot pour les nœuds du processeur. Karpenter utilisera l’IRSA pour assumer ce rôle IAM.
# Logout of helm registry before pulling from public ECR helm registry logout public.ecr.aws # Install Karpenter helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "kube-system" --create-namespace \ --set "settings.clusterName=${EKS_CLUSTER_NAME}" \ --set "settings.interruptionQueue=${EKS_CLUSTER_NAME}" \ --set controller.resources.requests.cpu=1 \ --set controller.resources.requests.memory=1Gi \ --set controller.resources.limits.cpu=1 \ --set controller.resources.limits.memory=1Gi \ --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${EKS_CLUSTER_NAME}-karpenter" \ --wait
Le résultat attendu devrait ressembler à ceci :
Release "karpenter" does not exist. Installing it now. Pulled: public.ecr.aws/karpenter/karpenter:1.5.0 Digest: sha256:9a155c7831fbff070669e58500f68d7ccdcf3f7c808dcb4c21d3885aa20c0a1c NAME: karpenter LAST DEPLOYED: Thu Jun 19 09:57:06 2025 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None
Vérifiez que Karpenter est en cours d’exécution :
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
Le résultat attendu devrait ressembler à ceci :
NAME READY STATUS RESTARTS AGE karpenter-555895dc-865bc 1/1 Running 0 5m58s karpenter-555895dc-j7tk9 1/1 Running 0 5m58s
4. Configurez Karpenter NodePools
Dans cette étape, nous configurons des pools de nœuds Karpenterlimits dans la spécification NodePool limite le total maximal des ressources (par exemple, CPU, mémoire, GPU) que chaque NodePool peut consommer sur tous les nœuds provisionnés, empêchant ainsi le provisionnement de nœuds supplémentaires si ces limites sont dépassées. Bien que les NodePools prennent en charge de larges catégories d’instances (par exemple, c, g), la spécification de types d’instances
Configuration du pool de nœuds du GPU
Dans ce NodePool, nous définissons des limites de ressources afin de gérer l’approvisionnement des nœuds dotés de capacités GPU. Ces limites sont conçues pour plafonner les ressources totales sur tous les nœuds du pool, permettant jusqu’à 10 instances au total. Chaque instance peut être soit g5.xlarge (4 vCPU, 16 Gio de mémoire, 1 GPU), soit g5.2xlarge (8 vCPU, 32 Gio de mémoire, 1 GPU), à condition que le nombre total de vCPU ne dépasse pas 80, que la mémoire totale ne dépasse pas 320 Gio et que le nombre total de GPU ne dépasse pas 10. Par exemple, le pool peut provisionner 10 instances g5.2xlarge (80 vCPU, 320 Gio, 10 GPU) ou 10 instances g5.xlarge (40 vCPU, 160 Gio, 10 GPU), ou une combinaison telle que 5 instances g5.xlarge et 5 instances g5.2xlarge (60 vCPU, 240 Gio, 10 GPU), garantissant ainsi une flexibilité basée sur les exigences de la charge de travail tout en respectant les contraintes en matière de ressources.
De plus, nous indiquons l’ID de la variante Nvidia de l’AMI Bottlerocket. Enfin, nous avons défini une politique de perturbationconsolidateAfter: 30m) et fixé une durée de vie maximale de 30 jours (expireAfter: 720h) pour les nœuds afin d’optimiser les coûts et de maintenir la santé des nœuds pour les tâches gourmandes en ressources GPU. Pour en savoir plus, consultez les articles Désactiver la consolidation Karpenter pour les charges de travail sensibles aux interruptions et Utiliser ttlSecondsAfterFinished pour nettoyer automatiquement les tâches Kubernetes.
cat <<EOF | envsubst | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-a10g-inference-g5 spec: template: metadata: labels: role: gpu-worker gpu-type: nvidia-a10g spec: requirements: - key: node.kubernetes.io/instance-type operator: In values: ["g5.xlarge", "g5.2xlarge"] - key: "karpenter.sh/capacity-type" operator: In values: ["on-demand"] taints: - key: nvidia.com/gpu value: "true" effect: NoSchedule nodeClassRef: name: gpu-a10g-inference-ec2 group: karpenter.k8s.aws kind: EC2NodeClass expireAfter: 720h limits: cpu: "80" memory: "320Gi" nvidia.com/gpu: "10" disruption: consolidationPolicy: WhenEmpty consolidateAfter: 30m --- apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: gpu-a10g-inference-ec2 spec: amiFamily: Bottlerocket amiSelectorTerms: - id: ${NVIDIA_BOTTLEROCKET_AMI} role: "KarpenterNodeRole-${EKS_CLUSTER_NAME}" subnetSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" securityGroupSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" tags: nvidia.com/gpu: "true" EOF
Le résultat attendu devrait ressembler à ceci :
nodepool.karpenter.sh/gpu-a10g-inference-g5 created ec2nodeclass.karpenter.k8s.aws/gpu-a10g-inference-ec2 created
Vérifiez que le NodePool est créé et qu’il est sain :
kubectl get nodepool gpu-a10g-inference-g5 -o yaml
Recherchez status.conditions comme ValidationSucceeded: True, NodeClassReady: True, et Ready: True pour confirmer que le NodePool est en bon état.
Configuration du pool de nœuds du processeur
Dans ce NodePool, nous avons défini des limites pour prendre en charge environ 50 instances, en fonction d’une charge de travail CPU modérée (par exemple, 100 à 200 pods) et de quotas vCPU AWS classiques (par exemple, 128 à 1 152). Les limites sont calculées en supposant que le NodePool doit pouvoir augmenter verticalement jusqu’à 50 instances m7.xlarge : CPU (4 vCPU par instance × 50 instances = 200 vCPU) et mémoire (16 Gio par instance × 50 instances = 800 Gio). Ces limites sont conçues pour plafonner les ressources totales sur tous les nœuds du pool, permettant jusqu’à 50 instances m7g.xlarge (chacune avec 4 vCPU et 16 Gio de mémoire), tant que le total des vCPU ne dépasse pas 200 et que la mémoire totale ne dépasse pas 800 Gio.
De plus, nous indiquons l’ID de la variante standard de l’AMI Bottlerocket. Enfin, nous avons défini une politique de perturbationconsolidateAfter: 60m) et fixé une durée de vie maximale de 30 jours (expireAfter: 720h) pour les nœuds afin d’optimiser les coûts et de maintenir la santé des nœuds pour les tâches gourmandes en ressources GPU. Pour en savoir plus, consultez les articles Désactiver la consolidation Karpenter pour les charges de travail sensibles aux interruptions et Utiliser ttlSecondsAfterFinished pour nettoyer automatiquement les tâches Kubernetes.
cat <<EOF | envsubst | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: cpu-inference-m7gxlarge spec: template: metadata: labels: role: cpu-worker spec: requirements: - key: node.kubernetes.io/instance-type operator: In values: ["m7g.xlarge"] - key: karpenter.sh/capacity-type operator: In values: ["on-demand"] taints: - key: role value: cpu-intensive effect: NoSchedule nodeClassRef: name: cpu-inference-m7gxlarge-ec2 group: karpenter.k8s.aws kind: EC2NodeClass expireAfter: 720h limits: cpu: "200" memory: "800Gi" disruption: consolidationPolicy: WhenEmpty consolidateAfter: 60m --- apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: cpu-inference-m7gxlarge-ec2 spec: amiFamily: Bottlerocket amiSelectorTerms: - id: ${STANDARD_BOTTLEROCKET_AMI} role: "KarpenterNodeRole-${EKS_CLUSTER_NAME}" subnetSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" securityGroupSelectorTerms: - tags: karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" EOF
Le résultat attendu devrait ressembler à ceci :
nodepool.karpenter.sh/cpu-inference-m7gxlarge created ec2nodeclass.karpenter.k8s.aws/cpu-inference-m7gxlarge-ec2 created
Vérifiez que le NodePool est créé et qu’il est sain :
kubectl get nodepool cpu-inference-m7gxlarge -o yaml
Recherchez status.conditions comme ValidationSucceeded: True, NodeClassReady: True, et Ready: True pour confirmer que le NodePool est en bon état.
5. Déployer un module GPU pour exposer un GPU
Vous avez besoin du plug-in Nvidia Device pour permettre à Kubernetes d’exposer les périphériques GPU au cluster Kubernetes. En règle générale, vous devez déployer le plug-in en tant que DaemonSet ; cependant, l’AMI Bottlerocket préinstalle le plug-in dans le cadre de l’AMI. Cela signifie que lorsque vous utilisez les AMI Bottlerocket, il n’est pas nécessaire de déployer le plug-in DaemonSet du périphérique Nvidia. Pour en savoir plus, consultez Kubernetes Device Plugin pour exposer les GPU.
Déploiement d’un exemple de pod
Karpenter agit de manière dynamique : il provisionne des nœuds GPU lorsqu’une charge de travail (pod) demande des ressources GPU. Pour vérifier que les pods sont en mesure de demander et d’utiliser des GPU, déployez un pod qui demande la ressource nvidia.com/gpu dans ses limites (par exemple, nvidia.com/gpu: 1). Pour en savoir plus sur ces étiquettes, consultez Planifier des charges de travail avec des exigences GPU à l’aide d’étiquettes bien connues.
cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: gpu-nvidia-smi spec: restartPolicy: OnFailure tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: role: gpu-worker # Matches GPU NodePool's label containers: - name: cuda-container image: nvidia/cuda:12.9.1-base-ubuntu20.04 command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 EOF
Le résultat attendu devrait ressembler à ceci :
pod/gpu-ndivia-smi created
Attendez une minute, puis vérifiez si le Pod a le statut « En attente », « Création du conteneur », « En cours d’exécution », puis « Terminé » :
kubectl get pod gpu-nvidia-smi -w
Vérifiez que le nœud du pod appartient au pool de nœuds du GPU :
kubectl get node $(kubectl get pod gpu-nvidia-smi -o jsonpath='{.spec.nodeName}') -o custom-columns="Name:.metadata.name,Nodepool:.metadata.labels.karpenter\.sh/nodepool"
Le résultat attendu devrait ressembler à ceci :
Name Nodepool ip-192-168-83-245.ec2.internal gpu-a10g-inference-g5
Consultez les journaux du pod :
kubectl logs gpu-nvidia-smi
Le résultat attendu devrait ressembler à ceci :
Thu Jul 17 04:31:33 2025 +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 570.148.08 Driver Version: 570.148.08 CUDA Version: 12.9 | |-----------------------------------------+----------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+======================+======================| | 0 NVIDIA A10G On | 00000000:00:1E.0 Off | 0 | | 0% 30C P8 9W / 300W | 0MiB / 23028MiB | 0% Default | | | | N/A | +---------------------------------------------------------------------------------------+ +---------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=======================================================================================| | No running processes found | +---------------------------------------------------------------------------------------+
6. (Facultatif) Préparer et télécharger les artefacts du modèle pour le déploiement
Au cours de cette étape, vous allez déployer un service de modèle pour la classification d’images en temps réel, en commençant par télécharger les poids du modèle vers un compartiment Amazon S3. À titre de démonstration, nous utilisons le modèle de vision open source GPUNet-0
Configuration de votre environnement
Pour télécharger les poids du modèle GPUNet-0 Dans cette étape, vous devez avoir accès au catalogue NGC de NVIDIA et disposer de Docker
-
Inscrivez-vous pour obtenir un compte NGC gratuit
et générez une clé API à partir du tableau de bord NGC (icône utilisateur > Configuration > Générer une clé API > Générer une clé personnelle > Catalogue NGC). -
Téléchargez et installez la CLI NGC
(Linux/macOS/Windows) et configurez la CLI en utilisant : ngc config set. Saisissez votre clé API lorsque vous y êtes invité ; définissez org surnvidiaet appuyez sur Entrée pour accepter les valeurs par défaut pour les autres. Si l’opération aboutit, vous devriez voir quelque chose ressemblant à :Successfully saved NGC configuration to /Users/your-username/.ngc/config.
Vérifier les autorisations du compte de service
Avant de commencer, vérifiez les autorisations du compte de service Kubernetes :
kubectl get serviceaccount s3-csi-driver-sa -n kube-system -o yaml
Lors de la création du cluster, nous avons associé la politique S3CSIDriverPolicy à un rôle IAM et annoté le compte de service (« s3-csi-driver-sa »). Les pods du pilote CSI Mountpoint S3 héritent des autorisations du rôle IAM lorsqu’ils interagissent avec S3. Le résultat attendu devrait ressembler à ceci :
apiVersion: v1 kind: ServiceAccount metadata: annotations: eks.amazonaws.com/role-arn: arn:aws:iam::143095308808:role/eksctl-eks-rt-inference-us-east-1-addon-aws-m-Role1-fpXXjRYdKN8r creationTimestamp: "2025-07-17T03:55:29Z" labels: app.kubernetes.io/component: csi-driver app.kubernetes.io/instance: aws-mountpoint-s3-csi-driver app.kubernetes.io/managed-by: EKS app.kubernetes.io/name: aws-mountpoint-s3-csi-driver name: s3-csi-driver-sa namespace: kube-system resourceVersion: "2278" uid: 50b36272-6716-4c68-bdc3-c4054df1177c
Ajouter une tolérance
Le pilote S3 CSI s’exécute en tant que DaemonSet sur tous les nœuds. Les pods utilisent le pilote CSI sur ces nœuds pour monter les volumes S3. Pour lui permettre de se programmer sur nos nœuds GPU présentant des rejets, ajoutez une tolérance au DaemonSet :
kubectl patch daemonset s3-csi-node -n kube-system --type='json' -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]'
Le résultat attendu devrait ressembler à ceci :
daemonset.apps/s3-csi-node patched
Charger les poids du modèle dans S3
Au cours de cette étape, vous allez créer un compartiment Amazon S3, télécharger les poids du modèle GPUNet-0 depuis NVIDIA GPU Cloud (NGC) et les télécharger vers le compartiment. Ces poids seront accessibles par notre application au moment de l’exécution à des fins d’inférence.
Créez votre compartiment Amazon S3 :
aws s3 mb s3://${S3_BUCKET_NAME} --region ${AWS_REGION}
Activez la gestion des versions S3 pour le compartiment afin d’éviter que des suppressions et des écrasements accidentels n’entraînent une perte immédiate et permanente des données :
aws s3api put-bucket-versioning --bucket ${S3_BUCKET_NAME} --versioning-configuration Status=Enabled
Appliquez une règle de cycle de vie au compartiment pour supprimer les versions d’objets écrasées ou supprimées 14 jours après leur expiration, supprimer les marqueurs de suppression expirés et supprimer les téléchargements en plusieurs parties incomplets après 7 jours. Pour en savoir plus, consultez les exemples de configurations du cycle de vie S3.
aws s3api put-bucket-lifecycle-configuration --bucket $S3_BUCKET_NAME --lifecycle-configuration '{"Rules":[{"ID":"LifecycleRule","Status":"Enabled","Filter":{},"Expiration":{"ExpiredObjectDeleteMarker":true},"NoncurrentVersionExpiration":{"NoncurrentDays":14},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}'
Téléchargez les poids du modèle GPUnet-0 auprès de NGC. Par exemple, sur macOS :
ngc registry model download-version nvidia/dle/gpunet_0_pyt_ckpt:21.12.0_amp --dest ~/downloads
Note
Vous devrez peut-être adapter cette commande de téléchargement à votre système d’exploitation. Pour que cette commande fonctionne sur un système Linux, vous devrez probablement créer le répertoire dans le cadre de la commande (par exemple, mkdir ~/downloads).
Le résultat attendu devrait ressembler à ceci :
{ "download_end": "2025-07-18 08:22:39", "download_start": "2025-07-18 08:22:33", "download_time": "6s", "files_downloaded": 1, "local_path": "/Users/your-username/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp", "size_downloaded": "181.85 MB", "status": "Completed", "transfer_id": "gpunet_0_pyt_ckpt[version=21.12.0_amp]" }
Renommez le fichier de point de contrôle afin qu’il corresponde au nom attendu dans le code de notre application lors des étapes suivantes (aucune extraction n’est nécessaire, car il s’agit d’un point de contrôle PyTorch *.pth.tar standard contenant le dictionnaire d’état du modèle) :
mv ~/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp/0.65ms.pth.tar gpunet-0.pth
Activez l’exécution commune AWS
aws configure set s3.preferred_transfer_client crt
Téléchargez les poids du modèle dans votre compartiment S3 :
aws s3 cp gpunet-0.pth s3://${S3_BUCKET_NAME}/gpunet-0.pth
Le résultat attendu devrait ressembler à ceci :
upload: ./gpunet-0.pth to s3://eks-rt-inference-models-us-east-1-1752722786/gpunet-0.pth
Créer le service de modèle
Dans cette étape, vous allez configurer une application web FastAPI pour la classification d’images accélérée par GPU à l’aide du modèle de vision GPUNet-0. L’application télécharge les poids du modèle depuis Amazon S3 au moment de l’exécution, récupère l’architecture du modèle depuis le référentiel NVIDIA pour la mise en cache et télécharge les étiquettes de classe ImageNet via HTTP. L’application comprend des transformations de prétraitement d’images et expose deux points de terminaison : un GET racine pour la vérification de l’état et un point de terminaison /predict POST qui accepte une URL d’image.
Nous utilisons FastAPI avec PyTorch pour servir le modèle, en chargeant les poids depuis Amazon S3 lors de l’exécution dans une configuration conteneurisée pour un prototypage rapide et un déploiement Kubernetes. Pour d’autres méthodes telles que le traitement par lots optimisé ou les moteurs à haut débit, consultez la section Utilisation des modèles ML.
Pour créer l’application
Créez un répertoire pour vos fichiers d’application, tel que model-testing, puis accédez à ce répertoire et ajoutez le code suivant à un nouveau fichier nommé app.py :
import os import torch import json import requests from fastapi import FastAPI, HTTPException from PIL import Image from io import BytesIO, StringIO import torchvision.transforms as transforms from torch.nn.functional import softmax import warnings from contextlib import redirect_stdout, redirect_stderr import argparse import boto3 app = FastAPI() # Suppress specific warnings from the model code (quantization is optional and unused here) warnings.simplefilter("ignore", UserWarning) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # Load model code from cache (if present) # Use backed cache directory torch.hub.set_dir('/cache/torch/hub') # Allowlist for secure deserialization (handles potential issues in older checkpoints) torch.serialization.add_safe_globals([argparse.Namespace]) # Load the model architecture only on container startup (changed to pretrained=False) # Precision (FP32 for full accuracy, could be 'fp16' for speed on Ampere+ GPUs) with redirect_stdout(StringIO()), redirect_stderr(StringIO()): gpunet = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_gpunet', pretrained=False, model_type='GPUNet-0', model_math='fp32') # Download weights from S3 if not present, then load them model_path = os.getenv('MODEL_PATH', '/cache/torch/hub/checkpoints/gpunet-0.pth') os.makedirs(os.path.dirname(model_path), exist_ok=True) # Ensure checkpoints dir exists if not os.path.exists(model_path): s3 = boto3.client('s3') s3.download_file(os.getenv('S3_BUCKET_NAME'), 'gpunet-0.pth', model_path) checkpoint = torch.load(model_path, map_location=device, weights_only=True) gpunet.load_state_dict(checkpoint['state_dict']) # Move to GPU/CPU gpunet.to(device) gpunet.eval() # Preprocessing preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # Load ImageNet labels labels_url = "https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json" response = requests.get(labels_url) json_data = json.loads(response.text) labels = [json_data[str(i)][1].replace('_', ' ') for i in range(1000)] # Required, FastAPI root @app.get("/") async def hello(): return {"status": "hello"} # Serve model requests @app.post("/predict") async def predict(image_url: str): try: response = requests.get(image_url) response.raise_for_status() img = Image.open(BytesIO(response.content)).convert("RGB") input_tensor = preprocess(img).unsqueeze(0).to(device) with torch.no_grad(): output = gpunet(input_tensor) probs = softmax(output, dim=1)[0] top5_idx = probs.topk(5).indices.cpu().numpy() top5_probs = probs.topk(5).values.cpu().numpy() results = [{ "label": labels[idx], "probability": float(prob) } for idx, prob in zip(top5_idx, top5_probs)] return {"predictions": results} except Exception as e: raise HTTPException(status_code=400, detail=str(e))
Créer le fichier Dockerfile
Le fichier Dockerfile suivant crée une image de conteneur pour notre application à l’aide du modèle GPUNet issu du référentiel GitHub NVIDIA Deep Learning Examples for Tensor Cores
Nous réduisons la taille des images de conteneur en utilisant une base PyTorch uniquement en phase d’exécution, en installant uniquement les paquets essentiels avec nettoyage du cache, en pré-mettant en cache le code du modèle et en évitant de « cuire » les poids dans l’image du conteneur afin de permettre des extractions et des mises à jour plus rapides. Pour en savoir plus, consultez la section Réduire la taille des images de conteneur.
Dans le même répertoire que app.py, créez le Dockerfile :
FROM pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime # Install required system packages required for git cloning RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* # Install application dependencies RUN pip install --no-cache-dir fastapi uvicorn requests pillow boto3 timm==0.5.4 # Pre-cache the GPUNet code from Torch Hub (without weights) # Clone the repository containing the GPUNet code RUN mkdir -p /cache/torch/hub && \ cd /cache/torch/hub && \ git clone --branch torchhub --depth 1 https://github.com/NVIDIA/DeepLearningExamples NVIDIA_DeepLearningExamples_torchhub COPY app.py /app/app.py WORKDIR /app CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]
Tester l'application
À partir du même répertoire que vos app.py et Dockerfile, créez l’image conteneur pour l’application d’inférence, en ciblant l’architecture AMD64 :
docker build --platform linux/amd64 -t gpunet-inference-app .
Définissez les variables d’environnement pour vos informations d’identification AWS et, éventuellement, un jeton de session AWS. Par exemple :
export AWS_REGION="us-east-1" export AWS_ACCESS_KEY_ID=ABCEXAMPLESCUJFEIELSMUHHAZ export AWS_SECRET_ACCESS_KEY=123EXAMPLEMZREoQXr8XkiicsOgWDQ5TpUsq0/Z
Exécutez le conteneur localement, en injectant les informations d’identification AWS sous forme de variables d’environnement pour l’accès à S3. Par exemple :
docker run --platform linux/amd64 -p 8080:80 \ -e S3_BUCKET_NAME=${S3_BUCKET_NAME} \ -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ -e AWS_DEFAULT_REGION=${AWS_REGION} \ gpunet-inference-app
Le résultat attendu devrait ressembler à ceci :
INFO: Started server process [1] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
Dans une nouvelle fenêtre de terminal, testez le point de terminaison d’inférence en envoyant une requête POST d’exemple avec une URL d’image publique comme paramètre de requête :
curl -X POST "http://localhost:8080/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
Le résultat attendu devrait être une réponse JSON avec les 5 meilleures prédictions, similaire à ceci (les étiquettes et probabilités réelles peuvent varier légèrement en fonction de l’image et de la précision du modèle) :
{"predictions":[{"label":"desk","probability":0.28885871171951294},{"label":"laptop","probability":0.24679335951805115},{"label":"notebook","probability":0.08539070934057236},{"label":"library","probability":0.030645888298749924},{"label":"monitor","probability":0.02989606373012066}]}
Quittez l’application en appuyant sur « Ctrl + C ».
Poussez le conteneur vers Amazon ECR
Au cours de cette étape, nous téléchargeons l’image du conteneur pour le service de modèle GPUNet-0 vers Amazon Elastic Container Registry (ECR), afin qu’elle soit disponible pour un déploiement sur Amazon EKS. Ce processus consiste à créer un nouveau référentiel ECR pour stocker l’image, à s’authentifier auprès d’ECR, puis à baliser et à pousser l’image du conteneur vers notre registre.
Tout d’abord, retournez dans le répertoire où vous avez défini vos variables d’environnement au début de ce guide. Par exemple :
cd ..
Créer un référentiel dans Amazon ECR :
aws ecr create-repository --repository-name gpunet-inference-app --region ${AWS_REGION}
Connectez-vous à Amazon ECR :
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
Le résultat attendu devrait ressembler à ceci :
Login Succeeded
Balisez l’image :
docker tag gpunet-inference-app:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
Poussez l’image vers votre référentiel Amazon ECR :
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
Cette dernière étape prend plusieurs minutes.
7. (Facultatif) Exposez le modèle de service
Au cours de cette étape, vous allez exposer votre service de modèle d’inférence en temps réel en externe sur Amazon EKS à l’aide d’AWS Load Balancer Controller (LBC). Cela implique la configuration du LBC, le montage des poids de modèle depuis Amazon S3 en tant que volume persistant à l’aide du pilote Mountpoint S3 CSI, le déploiement d’un pod d’application accéléré par GPU, la création d’un service et d’une entrée pour provisionner un Application Load Balancer (ALB) et le test du point de terminaison.
Tout d’abord, vérifiez l’association d’identité Pod pour le AWS LBC, en confirmant que le compte de service est correctement lié au rôle IAM requis :
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --namespace kube-system --service-account-name aws-load-balancer-controller
Le résultat attendu devrait ressembler à ceci :
ASSOCIATION ARN NAMESPACE SERVICE ACCOUNT NAME IAM ROLE ARN OWNER ARN arn:aws:eks:us-east-1:143095308808:podidentityassociation/eks-rt-inference-us-east-1/a-buavluu2wp1jropya kube-system aws-load-balancer-controller arn:aws:iam::143095308808:role/AmazonEKSLoadBalancerControllerRole
Baliser votre groupe de sécurité de cluster
AWS Load Balancer Controller ne prend en charge qu’un seul groupe de sécurité avec la clé de balise karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" pour la sélection du groupe de sécurité Karpenter. Lors de la création d’un cluster avec eksctl, le groupe de sécurité par défaut du cluster (qui possède la balise "kubernetes.io/cluster/<cluster-name>: owned") n’est pas automatiquement balisé avec des balises karpenter.sh/discovery. Cette balise est essentielle pour que Karpenter puisse détecter et associer ce groupe de sécurité aux nœuds qu’il provisionne. L’ajout de ce groupe de sécurité garantit la compatibilité avec l’AWS Load Balancer Controller (LBC), lui permettant de gérer automatiquement les règles de trafic entrant pour les services exposés via Ingress, tels que le service de modèle dans ces étapes.
Exportez l’ID VPC de votre cluster :
CLUSTER_VPC_ID="$(aws eks describe-cluster --name ${EKS_CLUSTER_NAME} --query cluster.resourcesVpcConfig.vpcId --output text)"
Exportez le groupe de sécurité par défaut de votre cluster :
CLUSTER_SG_ID="$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$CLUSTER_VPC_ID Name=tag-key,Values=kubernetes.io/cluster/${EKS_CLUSTER_NAME} --query 'SecurityGroups[].[GroupId]' --output text)"
Ajoutez la balise karpenter.sh/discovery au groupe de sécurité du cluster par défaut. Cela permettra à nos sélecteurs EC2NodeClass du processeur et du GPU de l’utiliser :
aws ec2 create-tags --resources ${CLUSTER_SG_ID} --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}
Vérifiez que le tag a été ajouté :
aws ec2 describe-security-groups --group-ids ${CLUSTER_SG_ID} --query "SecurityGroups[].Tags"
Parmi les résultats, vous devriez voir ce qui suit avec le tag et le nom de votre cluster. Par exemple :
{ "Key": "karpenter.sh/discovery", "Value": "eks-rt-inference-us-east-1" }
Configuration d’AWS Load Balancer Controller (LBC)
Le AWS LBC est essentiel pour gérer le trafic entrant vers les charges de travail IA/ML sur Amazon EKS, garantissant ainsi l’accès aux points de terminaison d’inférence ou aux pipelines de traitement des données. En s’intégrant aux AWS Application Load Balancers (ALB) et aux Network Load Balancers (NLB), le LBC achemine dynamiquement le trafic vers des applications conteneurisées, telles que celles qui exécutent des modèles linguistiques volumineux, des modèles de vision par ordinateur ou des services d’inférence en temps réel. Comme nous avons déjà créé le compte de service et l’association d’identité de pod lors de la création du cluster, nous définissons le paramètre serviceAccount.name pour qu’il corresponde à ce qui est défini dans la configuration de notre cluster (aws-load-balancer-controller).
Ajoutez le référentiel des Charts de Helm possédé par AWS eks-charts :
helm repo add eks https://aws.github.io/eks-charts
Actualisez vos référentiels Helm locaux avec les graphiques les plus récents :
helm repo update eks
Déployez le AWS LBC à l’aide de Helm, en spécifiant le nom du cluster EKS et en référençant le compte de service pré-créé :
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=${EKS_CLUSTER_NAME} \ --set serviceAccount.create=false \ --set serviceAccount.name=aws-load-balancer-controller
Le résultat attendu devrait ressembler à ceci :
NAME: aws-load-balancer-controller LAST DEPLOYED: Wed Jul 9 15:03:31 2025 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: AWS Load Balancer controller installed!
Monter le modèle dans un volume persistant
Au cours de cette étape, vous allez monter des poids de modèle à partir de votre compartiment Amazon S3 à l’aide d’un PersistentVolume (PV) pris en charge par le pilote Mountpoint for Amazon S3 CSI. Cela permet aux pods Kubernetes d’accéder aux objets S3 comme s’il s’agissait de fichiers locaux, éliminant ainsi les téléchargements gourmands en ressources vers le stockage éphémère des pods ou les conteneurs d’initialisation, ce qui est idéal pour les modèles volumineux de plusieurs gigaoctets.
Le PV monte l’ensemble de la racine du bucket (aucun chemin spécifié dans volumeAttributes), prend en charge l’accès en lecture seule simultané par plusieurs pods et expose les fichiers tels que les poids du modèle (/models/gpunet-0.pth) à l’intérieur du conteneur pour l’inférence. Cela garantit que la fonction de secours « téléchargement » de notre application (app.py) ne se déclenche pas, car le fichier existe via le montage. En dissociant le modèle de l’image du conteneur, cela permet un accès partagé et des mises à jour indépendantes des versions du modèle sans avoir à reconstruire l’image.
Créez le PersistentVolume (PV)
Créez une ressource PersistentVolume (PV) pour monter le compartiment S3 contenant les poids de votre modèle, ce qui permet un accès en lecture seule pour plusieurs pods sans télécharger de fichiers lors de l’exécution :
cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: PersistentVolume metadata: name: s3-model-pv spec: capacity: storage: 5Gi # Ignored by the driver; can be any value accessModes: - ReadOnlyMany # Read only persistentVolumeReclaimPolicy: Retain storageClassName: "" # Required for static provisioning claimRef: namespace: default # Adjust if you prefer a different namespace name: s3-model-pvc mountOptions: - allow-other # Enables multi-user access (useful for non-root pods) - region ${AWS_REGION} # Optional, include if your bucket is in a different region than the cluster csi: driver: s3.csi.aws.com volumeHandle: gpunet-model-volume # Must be unique across all PVs volumeAttributes: bucketName: ${S3_BUCKET_NAME} EOF
Créez le PersistentVolumeClaim (PVC)
Créez un PersistentVolumeClaim (PVC) à lier au PV, en demandant un accès en lecture seule aux données du modèle S3 monté :
cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: s3-model-pvc spec: accessModes: - ReadOnlyMany storageClassName: "" # Required for static provisioning resources: requests: storage: 5Gi # Ignored, match PV capacity volumeName: s3-model-pv # Bind to the PV created above EOF
Déployer l'application
Déployez l’application d’inférence en tant que déploiement Kubernetes, en montant le volume persistant pris en charge par S3 pour l’accès au modèle, en appliquant les sélecteurs et les tolérances de nœuds GPU, et en définissant les variables d’environnement pour le chemin d’accès au modèle. Ce déploiement définit le chemin d’accès au modèle (variable d’environnement "/models/gpunet-0.pth"), de sorte que notre application (dans app.py) utilisera ce chemin par défaut. Avec le volume de déploiement monté à /models (en lecture seule), le téléchargement du modèle ne se déclenchera pas si le fichier est déjà présent via le PVC.
cat <<EOF | envsubst | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: gpunet-inference-app spec: replicas: 1 selector: matchLabels: app: gpunet-inference-app template: metadata: labels: app: gpunet-inference-app spec: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: role: gpu-worker containers: - name: inference image: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest ports: - containerPort: 80 env: - name: MODEL_PATH value: "/models/gpunet-0.pth" resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 volumeMounts: - name: model-volume mountPath: /models readOnly: true volumes: - name: model-volume persistentVolumeClaim: claimName: s3-model-pvc EOF
Il faudra quelques minutes à Karpenter pour provisionner un nœud GPU si aucun n’est déjà disponible. Vérifiez que le pod d’inférence est dans l’état « En cours d’exécution » :
kubectl get pods -l app=gpunet-inference-app
Le résultat attendu devrait ressembler à ceci :
NAME READY STATUS RESTARTS AGE gpunet-inference-app-5d4b6c7f8-abcde 1/1 Running 0 2m
Exposez le service avec Ingress et Load Balancer
Créez un service ClusterIP pour exposer le déploiement d’inférence en interne au sein du cluster EKS, en ciblant le port de l’application :
cat <<EOF | envsubst | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: gpunet-model-service spec: type: ClusterIP ports: - port: 80 targetPort: 80 selector: app: gpunet-inference-app EOF
Créez une ressource Ingress pour provisionner un Application Load Balancer (ALB) connecté à Internet via le AWS LBC, en acheminant le trafic externe vers le service d’inférence :
cat <<EOF | envsubst | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gpunet-model-ingress annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip spec: ingressClassName: alb rules: - http: paths: - path: / pathType: Prefix backend: service: name: gpunet-model-service port: number: 80 EOF
Donnez à Application Load Balancer (ALB) la fin du provisionnement en l’espace de quelques minutes. Surveillez l’état des ressources d’entrée pour confirmer que l’ALB a été provisionné :
kubectl get ingress gpunet-model-ingress
La sortie attendue devrait ressembler à ceci (avec le champ ADDRESS rempli) :
NAME CLASS HOSTS ADDRESS PORTS AGE gpunet-model-ingress alb * k8s-default-gpunetmo-183de3f819-516310036.us-east-1.elb.amazonaws.com 80 6m58s
Extrayez et exportez le nom d’hôte ALB à partir de l’état d’entrée pour l’utiliser lors de tests ultérieurs :
export ALB_HOSTNAME=$(kubectl get ingress gpunet-model-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
Testez le Model Service
Validez le point de terminaison d’inférence exposé en envoyant une demande POST avec une URL d’image échantillon (par exemple, provenant de du jeu de données COCO), simulant une prédiction en temps réel :
curl -X POST "http://${ALB_HOSTNAME}/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
Le résultat attendu devrait être une réponse JSON avec les 5 meilleures prédictions, similaire à ceci (les étiquettes et probabilités réelles peuvent varier légèrement en fonction de l’image et de la précision du modèle) :
{"predictions":[{"label":"desk","probability":0.2888975441455841},{"label":"laptop","probability":0.2464350312948227},{"label":"notebook","probability":0.08554483205080032},{"label":"library","probability":0.030612602829933167},{"label":"monitor","probability":0.029896672815084457}]}
Vous pouvez éventuellement continuer à tester d’autres images dans une nouvelle requête POST. Par exemple :
http://images.cocodataset.org/test-stuff2017/000000024309.jpg http://images.cocodataset.org/test-stuff2017/000000028117.jpg http://images.cocodataset.org/test-stuff2017/000000006149.jpg http://images.cocodataset.org/test-stuff2017/000000004954.jpg
Conclusion
Dans ce guide, vous allez configurer un cluster Amazon EKS optimisé pour les charges de travail d’inférence en temps réel accélérées par GPU. Vous avez provisionné un cluster avec des instances EC2 G5
Nettoyage
Pour éviter des frais futurs, vous devez supprimer manuellement la pile CloudFormation associée afin de supprimer toutes les ressources créées au cours de ce guide, y compris le réseau VPC.
Supprimez la pile CloudFormation à l’aide du drapeau --wait avec eksctl :
eksctl delete cluster --region ${AWS_REGION} --name ${EKS_CLUSTER_NAME} --wait
Une fois l’opération terminée, vous devriez voir s’afficher la réponse suivante :
2025-07-29 13:03:55 [✔] all cluster resources were deleted
Supprimez le compartiment Amazon S3 créé au cours de ce guide à l’aide de la console Amazon S3