Optimiser l'utilisation des ressources GKE pour les charges de travail d'entraînement et d'inférence mixtes d'IA/ML


Ce tutoriel explique comment partager efficacement les ressources d'accélérateur entre les charges de travail d'entraînement et de diffusion d'inférences au sein d'un même cluster Google Kubernetes Engine (GKE). En distribuant vos charges de travail mixtes sur un seul cluster, vous améliorez l'utilisation des ressources, simplifiez la gestion des clusters, réduisez les problèmes liés aux limites de quantité d'accélérateurs et améliorez la rentabilité globale.

Dans ce tutoriel, vous allez créer un déploiement de diffusion à haute priorité à l'aide du grand modèle de langage (LLM) Gemma 2 pour l'inférence et du framework de diffusion Hugging Face TGI (Text Generation Interface), ainsi qu'un job de réglage fin de LLM à basse priorité. Les deux charges de travail s'exécutent sur un seul cluster qui utilise des GPU NVIDIA L4. Vous utilisez Kueue, un système de mise en file d'attente de jobs Kubernetes natif et Open Source, pour gérer et planifier vos charges de travail. Kueue vous permet de définir la priorité des tâches de diffusion et de préempter les jobs d'entraînement de priorité inférieure pour optimiser l'utilisation des ressources. Lorsque les demandes de diffusion diminuent, vous réattribuez les accélérateurs libérés pour reprendre les jobs d'entraînement. Vous utilisez Kueue et les classes de priorité pour gérer les quotas de ressources tout au long du processus.

Ce tutoriel est destiné aux ingénieurs en machine learning (ML), aux administrateurs et opérateurs de plate-forme, ainsi qu'aux spécialistes des données et de l'IA qui souhaitent entraîner et héberger un modèle de machine learning (ML) sur un cluster GKE, et qui souhaitent également réduire les coûts et la charge de gestion, en particulier lorsqu'ils traitent un nombre limité d'accélérateurs. Pour en savoir plus sur les rôles courants et les exemples de tâches que nous citons dans le contenu Google Cloud , consultez Rôles utilisateur et tâches courantes de GKE.

Avant de lire cette page, assurez-vous de connaître les éléments suivants :

Objectifs

À la fin de ce guide, vous devriez être capable d'effectuer les étapes suivantes:

  • Configurez un déploiement de diffusion à haute priorité.
  • Configurez des jobs d'entraînement de priorité inférieure.
  • Implémentez des stratégies de préemption pour répondre à la demande variable.
  • Gérez l'allocation des ressources entre les tâches d'entraînement et de diffusion à l'aide de Kueue.

Avant de commencer

  • Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  • In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  • Verify that billing is enabled for your Google Cloud project.

  • Enable the required APIs.

    Enable the APIs

  • In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  • Verify that billing is enabled for your Google Cloud project.

  • Enable the required APIs.

    Enable the APIs

  • Make sure that you have the following role or roles on the project: roles/container.admin, roles/iam.serviceAccountAdmin

    Check for the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. In the Principal column, find all rows that identify you or a group that you're included in. To learn which groups you're included in, contact your administrator.

    4. For all rows that specify or include you, check the Role column to see whether the list of roles includes the required roles.

    Grant the roles

    1. In the Google Cloud console, go to the IAM page.

      Accéder à IAM
    2. Sélectionnez le projet.
    3. Cliquez sur  Accorder l'accès.
    4. Dans le champ Nouveaux comptes principaux, saisissez votre identifiant utilisateur. Il s'agit généralement de l'adresse e-mail d'un compte Google.

    5. Dans la liste Sélectionner un rôle, sélectionnez un rôle.
    6. Pour attribuer des rôles supplémentaires, cliquez sur  Ajouter un autre rôle et ajoutez tous les rôles supplémentaires.
    7. Cliquez sur Enregistrer.
    8. Préparer l'environnement

      Dans cette section, vous provisionnez les ressources dont vous avez besoin pour déployer TGI et le modèle pour vos charges de travail d'inférence et d'entraînement.

      Accéder au modèle

      Pour accéder aux modèles Gemma en vue du déploiement sur GKE, vous devez d'abord signer le contrat d'autorisation de licence, puis générer un jeton d'accès Hugging Face.

      1. Signez le contrat de consentement de la licence. Accédez à la page d'autorisation du modèle, vérifiez l'autorisation à l'aide de votre compte Hugging Face et acceptez les conditions d'utilisation du modèle.
      2. Générez un jeton d'accès. Pour accéder au modèle via Hugging Face, vous avez besoin d'un jeton Hugging Face. Pour générer un nouveau jeton si vous n'en possédez pas, procédez comme suit:

        1. Cliquez sur Your Profile > Settings > Access Tokens (Votre profil > Paramètres > Jetons d'accès).
        2. Sélectionnez New Token (Nouveau jeton).
        3. Spécifiez le nom de votre choix et un rôle d'au moins Read.
        4. Sélectionnez Générer un jeton.
        5. Copiez le jeton dans votre presse-papiers.

      Lancer Cloud Shell

      Dans ce tutoriel, vous utilisez Cloud Shell pour gérer les ressources hébergées surGoogle Cloud. Les logiciels dont vous avez besoin pour ce tutoriel sont préinstallés sur Cloud Shell, y compris kubectl, gcloud CLI et Terraform.

      Pour configurer votre environnement avec Cloud Shell, procédez comme suit :

      1. Dans la console Google Cloud , lancez une session Cloud Shell en cliquant sur Icône d'activation Cloud Shell Activer Cloud Shell dans la consoleGoogle Cloud . Une session s'ouvre dans le volet inférieur de la console Google Cloud .

      2. Définissez les variables d'environnement par défaut :

        gcloud config set project PROJECT_ID
        export PROJECT_ID=$(gcloud config get project)
        

        Remplacez PROJECT_ID par votre Google Cloud ID de projet.

      3. Clonez l'exemple de code depuis GitHub. Dans Cloud Shell, exécutez les commandes suivantes :

        git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/
        cd kubernetes-engine-samples/ai-ml/mix-train-and-inference
        export EXAMPLE_HOME=$(pwd)
        

      Créer un cluster GKE

      Vous pouvez utiliser un cluster Autopilot ou Standard pour vos charges de travail mixtes. Nous vous recommandons d'utiliser un cluster Autopilot pour une expérience Kubernetes entièrement gérée. Pour choisir le mode de fonctionnement GKE le mieux adapté à vos charges de travail, consultez la section Choisir un mode de fonctionnement GKE.

      Autopilot

      1. Définissez les variables d'environnement par défaut dans Cloud Shell :

        export HF_TOKEN=HF_TOKEN
        export REGION=REGION
        export CLUSTER_NAME="llm-cluster"
        export PROJECT_NUMBER=$(gcloud projects list \
            --filter="$(gcloud config get-value project)" \
            --format="value(PROJECT_NUMBER)")
        export MODEL_BUCKET="model-bucket-$PROJECT_ID"
        

        Remplacez les valeurs suivantes :

        • HF_TOKEN : jeton Hugging Face que vous avez généré précédemment.
        • REGION : région compatible avec le type d'accélérateur que vous souhaitez utiliser, par exemple us-central1 pour le GPU L4.

        Vous pouvez ajuster la variable MODEL_BUCKET, qui représente le bucket Cloud Storage dans lequel vous stockez les pondérations de votre modèle entraîné.

      2. Créez un cluster Autopilot :

        gcloud container clusters create-auto ${CLUSTER_NAME} \
            --project=${PROJECT_ID} \
            --location=${REGION} \
            --release-channel=rapid
        
      3. Créez le bucket Cloud Storage pour le job d'affinage :

        gcloud storage buckets create gs://${MODEL_BUCKET} \
            --location ${REGION} \
            --uniform-bucket-level-access
        
      4. Pour accorder l'accès au bucket Cloud Storage, exécutez la commande suivante :

        gcloud storage buckets add-iam-policy-binding "gs://$MODEL_BUCKET" \
            --role=roles/storage.objectAdmin \
            --member=principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$PROJECT_ID.svc.id.goog/subject/ns/llm/sa/default \
            --condition=None
        
      5. Pour obtenir les identifiants d'authentification du cluster, exécutez la commande suivante :

        gcloud container clusters get-credentials llm-cluster \
            --location=$REGION \
            --project=$PROJECT_ID
        
      6. Créez un espace de noms pour vos déploiements. Dans Cloud Shell, exécutez la commande suivante :

        kubectl create ns llm
        

      Standard

      1. Définissez les variables d'environnement par défaut dans Cloud Shell :

        export HF_TOKEN=HF_TOKEN
        export REGION=REGION
        export CLUSTER_NAME="llm-cluster"
        export GPU_POOL_MACHINE_TYPE="g2-standard-24"
        export GPU_POOL_ACCELERATOR_TYPE="nvidia-l4"
        export PROJECT_NUMBER=$(gcloud projects list \
            --filter="$(gcloud config get-value project)" \
            --format="value(PROJECT_NUMBER)")
        export MODEL_BUCKET="model-bucket-$PROJECT_ID"
        

        Remplacez les valeurs suivantes :

        • HF_TOKEN : jeton Hugging Face que vous avez généré précédemment.
        • REGION : région compatible avec le type d'accélérateur que vous souhaitez utiliser, par exemple us-central1 pour le GPU L4.

        Vous pouvez ajuster les variables suivantes :

        • GPU_POOL_MACHINE_TYPE : série de machines du pool de nœuds que vous souhaitez utiliser dans la région sélectionnée. Cette valeur dépend du type d'accélérateur que vous avez sélectionné. Pour en savoir plus, consultez Limites d'utilisation des GPU sur GKE. Par exemple, ce tutoriel utilise g2-standard-24 avec deux GPU associés par nœud. Pour obtenir la liste la plus récente des GPU disponibles, consultez GPU pour les charges de travail de calcul.
        • GPU_POOL_ACCELERATOR_TYPE : type d'accélérateur compatible dans la région sélectionnée. Par exemple, ce tutoriel utilise nvidia-l4. Pour obtenir la liste la plus récente des GPU disponibles, consultez GPU pour les charges de travail de calcul.
        • MODEL_BUCKET : bucket Cloud Storage dans lequel vous stockez les pondérations de votre modèle entraîné.
      2. Créez un cluster standard :

        gcloud container clusters create ${CLUSTER_NAME} \
            --project=${PROJECT_ID} \
            --location=${REGION} \
            --workload-pool=${PROJECT_ID}.svc.id.goog \
            --release-channel=rapid \
            --machine-type=e2-standard-4 \
            --addons GcsFuseCsiDriver \
            --num-nodes=1
        
      3. Créez le pool de nœuds GPU pour les charges de travail d'inférence et de réglage précis :

        gcloud container node-pools create gpupool \
            --accelerator type=${GPU_POOL_ACCELERATOR_TYPE},count=2,gpu-driver-version=latest \
            --project=${PROJECT_ID} \
            --location=${REGION} \
            --node-locations=${REGION}-a \
            --cluster=${CLUSTER_NAME} \
            --machine-type=${GPU_POOL_MACHINE_TYPE} \
            --num-nodes=3
        
      4. Créez le bucket Cloud Storage pour le job d'affinage :

        gcloud storage buckets create gs://${MODEL_BUCKET} \
            --location ${REGION} \
            --uniform-bucket-level-access
        
      5. Pour accorder l'accès au bucket Cloud Storage, exécutez la commande suivante :

        gcloud storage buckets add-iam-policy-binding "gs://$MODEL_BUCKET" \
            --role=roles/storage.objectAdmin \
            --member=principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$PROJECT_ID.svc.id.goog/subject/ns/llm/sa/default \
            --condition=None
        
      6. Pour obtenir les identifiants d'authentification du cluster, exécutez la commande suivante :

        gcloud container clusters get-credentials llm-cluster \
            --location=$REGION \
            --project=$PROJECT_ID
        
      7. Créez un espace de noms pour vos déploiements. Dans Cloud Shell, exécutez la commande suivante :

        kubectl create ns llm
        

      Créer un secret Kubernetes pour les identifiants Hugging Face

      Pour créer un secret Kubernetes contenant le jeton Hugging Face, exécutez la commande suivante :

      kubectl create secret generic hf-secret \
          --from-literal=hf_api_token=$HF_TOKEN \
          --dry-run=client -o yaml | kubectl apply --namespace=llm --filename=-
      

      Configurer Kueue

      Dans ce tutoriel, Kueue est le gestionnaire de ressources central, qui permet de partager efficacement les GPU entre vos charges de travail d'entraînement et de diffusion. Pour ce faire, Kueue définit les besoins en ressources ("saveurs"), hiérarchise les charges de travail à l'aide de files d'attente (en privilégiant les tâches de diffusion par rapport à l'entraînement) et alloue les ressources de manière dynamique en fonction de la demande et de la priorité. Ce tutoriel utilise le type de ressource Workload pour regrouper respectivement les charges de travail d'inférence et de réglage fin.

      La fonctionnalité de préemption de Kueue garantit que les charges de travail de diffusion à haute priorité disposent toujours des ressources nécessaires en mettant en pause ou en expulsant les jobs d'entraînement à priorité inférieure lorsque les ressources sont rares.

      Pour contrôler le déploiement du serveur d'inférence avec Kueue, activez l'intégration pod et configurez managedJobsNamespaceSelector pour exclure les espaces de noms kube-system et kueue-system.

      1. Dans le répertoire /kueue, consultez le code dans kustomization.yaml. Ce fichier manifeste installe le gestionnaire de ressources Kueue avec des configurations personnalisées.

        apiVersion: kustomize.config.k8s.io/v1beta1
        kind: Kustomization
        resources:
        - https://github.com/kubernetes-sigs/kueue/releases/download/v0.12.3/manifests.yaml
        patches:
        - path: patch.yaml
          target:
            version: v1
            kind: ConfigMap
            name: kueue-manager-config
        
      2. Dans le répertoire /kueue, consultez le code dans patch.yaml. Cette ConfigMap personnalise Kueue pour exclure la gestion des pods dans les espaces de noms kube-system et kueue-system.

        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: kueue-manager-config
        data:
          controller_manager_config.yaml: |
            apiVersion: config.kueue.x-k8s.io/v1beta1
            kind: Configuration
            health:
              healthProbeBindAddress: :8081
            metrics:
              bindAddress: :8080
            # enableClusterQueueResources: true
            webhook:
              port: 9443
            leaderElection:
              leaderElect: true
              resourceName: c1f6bfd2.kueue.x-k8s.io
            controller:
              groupKindConcurrency:
                Job.batch: 5
                Pod: 5
                Workload.kueue.x-k8s.io: 5
                LocalQueue.kueue.x-k8s.io: 1
                ClusterQueue.kueue.x-k8s.io: 1
                ResourceFlavor.kueue.x-k8s.io: 1
            clientConnection:
              qps: 50
              burst: 100
            #pprofBindAddress: :8083
            #waitForPodsReady:
            #  enable: false
            #  timeout: 5m
            #  blockAdmission: false
            #  requeuingStrategy:
            #    timestamp: Eviction
            #    backoffLimitCount: null # null indicates infinite requeuing
            #    backoffBaseSeconds: 60
            #    backoffMaxSeconds: 3600
            #manageJobsWithoutQueueName: true
            managedJobsNamespaceSelector:
              matchExpressions:
                - key: kubernetes.io/metadata.name
                  operator: NotIn
                  values: [ kube-system, kueue-system ]
            #internalCertManagement:
            #  enable: false
            #  webhookServiceName: ""
            #  webhookSecretName: ""
            integrations:
              frameworks:
              - "batch/job"
              - "kubeflow.org/mpijob"
              - "ray.io/rayjob"
              - "ray.io/raycluster"
              - "jobset.x-k8s.io/jobset"
              - "kubeflow.org/paddlejob"
              - "kubeflow.org/pytorchjob"
              - "kubeflow.org/tfjob"
              - "kubeflow.org/xgboostjob"
              - "kubeflow.org/jaxjob"
              - "workload.codeflare.dev/appwrapper"
              - "pod"
            #  - "deployment" # requires enabling pod integration
            #  - "statefulset" # requires enabling pod integration
            #  - "leaderworkerset.x-k8s.io/leaderworkerset" # requires enabling pod integration
            #  externalFrameworks:
            #  - "Foo.v1.example.com"
            #fairSharing:
            #  enable: true
            #  preemptionStrategies: [LessThanOrEqualToFinalShare, LessThanInitialShare]
            #admissionFairSharing:
            #  usageHalfLifeTime: "168h" # 7 days
            #  usageSamplingInterval: "5m"
            #  resourceWeights: # optional, defaults to 1 for all resources if not specified
            #    cpu: 0    # if you want to completely ignore cpu usage
            #    memory: 0 # ignore completely memory usage
            #    example.com/gpu: 100 # and you care only about GPUs usage
            #resources:
            #  excludeResourcePrefixes: []
            #  transformations:
            #  - input: nvidia.com/mig-4g.5gb
            #    strategy: Replace | Retain
            #    outputs:
            #      example.com/accelerator-memory: 5Gi
            #      example.com/accelerator-gpc: 4
            #objectRetentionPolicies:
            #  workloads:
            #    afterFinished: null # null indicates infinite retention, 0s means no retention at all
            #    afterDeactivatedByKueue: null # null indicates infinite retention, 0s means no retention at all
        
      3. Dans Cloud Shell, exécutez la commande suivante pour installer Kueue :

        cd ${EXAMPLE_HOME}
        kubectl kustomize kueue |kubectl apply --server-side --filename=-
        

        Attendez que les pods Kueue soient prêts :

        watch kubectl --namespace=kueue-system get pods
        

        Le résultat doit ressembler à ce qui suit :

        NAME                                        READY   STATUS    RESTARTS   AGE
        kueue-controller-manager-bdc956fc4-vhcmx    1/1     Running   0          3m15s
        
      4. Dans le répertoire /workloads, affichez les fichiers flavors.yaml, cluster-queue.yaml et local-queue.yaml. Ces fichiers manifestes spécifient comment Kueue gère les quotas de ressources :

        ResourceFlavor

        Ce fichier manifeste définit une ResourceFlavor par défaut dans Kueue pour la gestion des ressources.

        apiVersion: kueue.x-k8s.io/v1beta1
        kind: ResourceFlavor
        metadata:
          name: default-flavor
        

        ClusterQueue

        Ce fichier manifeste configure une ClusterQueue Kueue avec des limites de ressources pour le processeur, la mémoire et le GPU.

        Ce tutoriel utilise des nœuds auxquels sont associés deux GPU Nvidia L4, avec le type de nœud correspondant g2-standard-24, qui offre 24 vCPU et 96 Go de RAM. L'exemple de code montre comment limiter l'utilisation des ressources de votre charge de travail à un maximum de six GPU.

        Le champ preemption de la configuration ClusterQueue fait référence aux PriorityClasses pour déterminer quels pods peuvent être préemptés lorsque les ressources sont rares.

        apiVersion: kueue.x-k8s.io/v1beta1
        kind: ClusterQueue
        metadata:
          name: "cluster-queue"
        spec:
          namespaceSelector: {} # match all.
          preemption:
            reclaimWithinCohort: LowerPriority
            withinClusterQueue: LowerPriority
          resourceGroups:
          - coveredResources: [ "cpu", "memory", "nvidia.com/gpu", "ephemeral-storage" ]
            flavors:
            - name: default-flavor
              resources:
              - name: "cpu"
                nominalQuota: 72
              - name: "memory"
                nominalQuota: 288Gi
              - name: "nvidia.com/gpu"
                nominalQuota: 6
              - name: "ephemeral-storage"
                nominalQuota: 200Gi
        

        LocalQueue

        Ce fichier manifeste crée une LocalQueue Kueue nommée lq dans l'espace de noms llm.

        apiVersion: kueue.x-k8s.io/v1beta1
        kind: LocalQueue
        metadata:
          namespace: llm # LocalQueue under llm namespace 
          name: lq
        spec:
          clusterQueue: cluster-queue # Point to the ClusterQueue
        
      5. Affichez les fichiers default-priorityclass.yaml, low-priorityclass.yaml et high-priorityclass.yaml. Ces fichiers manifestes définissent les objets PriorityClass pour la planification Kubernetes.

        Priorité par défaut

        apiVersion: scheduling.k8s.io/v1
        kind: PriorityClass
        metadata:
          name: default-priority-nonpreempting
        value: 10
        preemptionPolicy: Never
        globalDefault: true
        description: "This priority class will not cause other pods to be preempted."
        

        Priorité faible

        apiVersion: scheduling.k8s.io/v1
        kind: PriorityClass
        metadata:
          name: low-priority-preempting
        value: 20
        preemptionPolicy: PreemptLowerPriority
        globalDefault: false
        description: "This priority class will cause pods with lower priority to be preempted."
        

        Priorité élevée

        apiVersion: scheduling.k8s.io/v1
        kind: PriorityClass
        metadata:
          name: high-priority-preempting
        value: 30
        preemptionPolicy: PreemptLowerPriority
        globalDefault: false
        description: "This high priority class will cause other pods to be preempted."
        
      6. Créez les objets Kueue et Kubernetes en exécutant ces commandes pour appliquer les fichiers manifestes correspondants.

        cd ${EXAMPLE_HOME}/workloads
        kubectl apply --filename=flavors.yaml
        kubectl apply --filename=default-priorityclass.yaml
        kubectl apply --filename=high-priorityclass.yaml
        kubectl apply --filename=low-priorityclass.yaml
        kubectl apply --filename=cluster-queue.yaml
        kubectl apply --filename=local-queue.yaml --namespace=llm
        

      Déployer le serveur d'inférence TGI

      Dans cette section, vous allez déployer le conteneur TGI pour diffuser le modèle Gemma 2.

      1. Dans le répertoire /workloads, affichez le fichier tgi-gemma-2-9b-it-hp.yaml. Ce fichier manifeste définit un déploiement Kubernetes pour déployer le runtime de diffusion TGI et le modèle gemma-2-9B-it. Un déploiement est un objet de l'API Kubernetes qui vous permet d'exécuter plusieurs instances dupliquées de pods répartis entre les nœuds d'un cluster.

        Le déploiement donne la priorité aux tâches d'inférence et utilise deux GPU pour le modèle. Il utilise le parallélisme de tenseur en définissant la variable d'environnement NUM_SHARD pour adapter le modèle à la mémoire du GPU.

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: tgi-gemma-deployment
          labels:
            app: gemma-server
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: gemma-server
          template:
            metadata:
              labels:
                app: gemma-server
                ai.gke.io/model: gemma-2-9b-it
                ai.gke.io/inference-server: text-generation-inference
                examples.ai.gke.io/source: user-guide
                kueue.x-k8s.io/queue-name: lq
            spec:
              priorityClassName: high-priority-preempting
              containers:
              - name: inference-server
                image: us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu121.2-1.ubuntu2204.py310
                resources:
                  requests:
                    cpu: "4"
                    memory: "30Gi"
                    ephemeral-storage: "30Gi"
                    nvidia.com/gpu: "2"
                  limits:
                    cpu: "4"
                    memory: "30Gi"
                    ephemeral-storage: "30Gi"
                    nvidia.com/gpu: "2"
                env:
                - name: AIP_HTTP_PORT
                  value: '8000'
                - name: NUM_SHARD
                  value: '2'
                - name: MODEL_ID
                  value: google/gemma-2-9b-it
                - name: HUGGING_FACE_HUB_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                volumeMounts:
                - mountPath: /dev/shm
                  name: dshm
              volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
              nodeSelector:
                cloud.google.com/gke-accelerator: "nvidia-l4"
        ---
        apiVersion: v1
        kind: Service
        metadata:
          name: llm-service
        spec:
          selector:
            app: gemma-server
          type: ClusterIP
          ports:
          - protocol: TCP
            port: 8000
            targetPort: 8000
        
      2. Appliquez le fichier manifeste en exécutant la commande suivante :

        kubectl apply --filename=tgi-gemma-2-9b-it-hp.yaml --namespace=llm
        

        L'opération de déploiement prendra quelques minutes.

      3. Pour vérifier si GKE a bien créé le déploiement, exécutez la commande suivante :

        kubectl --namespace=llm get deployment
        

        Le résultat doit ressembler à ce qui suit :

        NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
        tgi-gemma-deployment   1/1     1            1           5m13s
        

      Vérifier la gestion des quotas Kueue

      Dans cette section, vous vérifiez que Kueue applique correctement le quota de GPU pour votre déploiement.

      1. Pour vérifier si Kueue connaît votre déploiement, exécutez cette commande pour récupérer l'état des objets Workload :

        kubectl --namespace=llm get workloads
        

        Le résultat doit ressembler à ce qui suit :

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        pod-tgi-gemma-deployment-6bf9ffdc9b-zcfrh-84f19   lq      cluster-queue   True                  8m23s
        
      2. Pour tester le remplacement des limites de quota, mettez à l'échelle le déploiement à quatre répliques :

        kubectl scale --replicas=4 deployment/tgi-gemma-deployment --namespace=llm
        
      3. Exécutez la commande suivante pour afficher le nombre de répliques déployées par GKE :

        kubectl get workloads --namespace=llm
        

        Le résultat doit ressembler à ce qui suit :

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        pod-tgi-gemma-deployment-6cb95cc7f5-5thgr-3f7d4   lq      cluster-queue   True                  14s
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  5m41s
        pod-tgi-gemma-deployment-6cb95cc7f5-tznkl-80f6b   lq                                            13s
        pod-tgi-gemma-deployment-6cb95cc7f5-wd4q9-e4302   lq      cluster-queue   True                  13s
        

        Le résultat indique que seuls trois pods sont admis en raison du quota de ressources appliqué par Kueue.

      4. Exécutez la commande suivante pour afficher les pods dans l'espace de noms llm :

        kubectl get pod --namespace=llm
        

        Le résultat doit ressembler à ce qui suit :

        NAME                                    READY   STATUS            RESTARTS   AGE
        tgi-gemma-deployment-7649884d64-6j256   1/1     Running           0          4m45s
        tgi-gemma-deployment-7649884d64-drpvc   0/1     SchedulingGated   0          7s
        tgi-gemma-deployment-7649884d64-thdkq   0/1     Pending           0          7s
        tgi-gemma-deployment-7649884d64-znvpb   0/1     Pending           0          7s
        
      5. Maintenant, réduisez le déploiement à 1. Cette étape est obligatoire avant de déployer le job de réglage précis, sinon il ne sera pas accepté, car le job d'inférence est prioritaire.

        kubectl scale --replicas=1 deployment/tgi-gemma-deployment --namespace=llm
        

      Explication du comportement

      L'exemple de scaling ne génère que trois répliques (malgré le scaling à quatre) en raison de la limite de quota de GPU que vous avez définie dans la configuration ClusterQueue. La section spec.resourceGroups de ClusterQueue définit un nominalQuota de "6" pour nvidia.com/gpu. Le déploiement spécifie que chaque pod nécessite deux GPU. Par conséquent, ClusterQueue ne peut accepter qu'un maximum de trois répliques du déploiement à la fois (car 3 répliques * 2 GPU par réplique = 6 GPU, ce qui correspond au quota total).

      Lorsque vous essayez de passer à quatre répliques, Kueue reconnaît que cette action dépasserait le quota de GPU et empêche la quatrième réplique d'être planifiée. Cela est indiqué par l'état SchedulingGated du quatrième pod. Ce comportement illustre l'application des quotas de ressources de Kueue.

      Déployer le Job d'entraînement

      Dans cette section, vous allez déployer un job d'affinage de priorité inférieure pour un modèle Gemma 2 qui nécessite quatre GPU répartis sur deux pods. Dans Kubernetes, un contrôleur Job crée un ou plusieurs pods et s'assure qu'ils exécutent correctement une tâche spécifique.

      Ce job utilisera le quota de GPU restant dans la ClusterQueue. Le job utilise une image prédéfinie et enregistre des points de contrôle pour permettre le redémarrage à partir de résultats intermédiaires.

      Le job d'optimisation utilise l'ensemble de données b-mc2/sql-create-context. La source du job d'affinage se trouve dans le dépôt.

      1. Affichez le fichier fine-tune-l4.yaml. Ce fichier manifeste définit le job d'affinage.

        apiVersion: v1
        kind: Service
        metadata:
          name: headless-svc-l4
        spec:
          clusterIP: None # clusterIP must be None to create a headless service
          selector:
            job-name: finetune-gemma-l4 # must match Job name
        ---
        apiVersion: batch/v1
        kind: Job
        metadata:
          name: finetune-gemma-l4
          labels:
            kueue.x-k8s.io/queue-name: lq
        spec:
          backoffLimit: 4
          completions: 2
          parallelism: 2
          completionMode: Indexed
          suspend: true # Set to true to allow Kueue to control the Job when it starts
          template:
            metadata:
              labels:
                app: finetune-job
              annotations:
                gke-gcsfuse/volumes: "true"
                gke-gcsfuse/memory-limit: "35Gi"
            spec:
              priorityClassName: low-priority-preempting
              containers:
              - name: gpu-job
                imagePullPolicy: Always
                image: us-docker.pkg.dev/google-samples/containers/gke/gemma-fine-tuning:v1.0.0
                ports:
                - containerPort: 29500
                resources:
                  requests:
                    nvidia.com/gpu: "2"
                  limits:
                    nvidia.com/gpu: "2"
                command:
                - bash
                - -c
                - |
                  accelerate launch \
                  --config_file fsdp_config.yaml \
                  --debug \
                  --main_process_ip finetune-gemma-l4-0.headless-svc-l4 \
                  --main_process_port 29500 \
                  --machine_rank ${JOB_COMPLETION_INDEX} \
                  --num_processes 4 \
                  --num_machines 2 \
                  fine_tune.py
                env:
                - name: "EXPERIMENT"
                  value: "finetune-experiment"
                - name: MODEL_NAME
                  value: "google/gemma-2-2b"
                - name: NEW_MODEL
                  value: "gemma-ft"
                - name: MODEL_PATH
                  value: "/model-data/model-gemma2/experiment"
                - name: DATASET_NAME
                  value: "b-mc2/sql-create-context"
                - name: DATASET_LIMIT
                  value: "5000"
                - name: EPOCHS
                  value: "1"
                - name: GRADIENT_ACCUMULATION_STEPS
                  value: "2"
                - name: CHECKPOINT_SAVE_STEPS
                  value: "10"
                - name: HF_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                volumeMounts:
                - mountPath: /dev/shm
                  name: dshm
                - name: gcs-fuse-csi-ephemeral
                  mountPath: /model-data
                  readOnly: false
              nodeSelector:
                cloud.google.com/gke-accelerator: nvidia-l4
              restartPolicy: OnFailure
              serviceAccountName: default
              subdomain: headless-svc-l4
              terminationGracePeriodSeconds: 60
              volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
              - name: gcs-fuse-csi-ephemeral
                csi:
                  driver: gcsfuse.csi.storage.gke.io
                  volumeAttributes:
                    bucketName: <MODEL_BUCKET>
                    mountOptions: "implicit-dirs"
                    gcsfuseLoggingSeverity: warning
        
      2. Appliquez le fichier manifeste pour créer le job d'affinage :

        cd ${EXAMPLE_HOME}/workloads
        
        sed -e "s/<MODEL_BUCKET>/$MODEL_BUCKET/g" \
            -e "s/<PROJECT_ID>/$PROJECT_ID/g" \
            -e "s/<REGION>/$REGION/g" \
            fine-tune-l4.yaml |kubectl apply --filename=- --namespace=llm
        
      3. Vérifiez que vos déploiements sont en cours d'exécution. Pour vérifier l'état des objets Workload, exécutez la commande suivante :

        kubectl get workloads --namespace=llm
        

        Le résultat doit ressembler à ce qui suit :

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        job-finetune-gemma-l4-3316f                       lq      cluster-queue   True                  29m
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  68m
        

        Ensuite, affichez les pods dans l'espace de noms llm en exécutant la commande suivante :

        kubectl get pod --namespace=llm
        

        Le résultat doit ressembler à ce qui suit :

        NAME                                    READY   STATUS    RESTARTS   AGE
        finetune-gemma-l4-0-vcxpz               2/2     Running   0          31m
        finetune-gemma-l4-1-9ppt9               2/2     Running   0          31m
        tgi-gemma-deployment-6cb95cc7f5-cbxg2   1/1     Running   0          70m
        

        Le résultat indique que Kueue autorise l'exécution de votre job de réglage fin et de vos pods de serveur d'inférence, en réservant les ressources appropriées en fonction des limites de quota que vous avez spécifiées.

      4. Affichez les journaux de sortie pour vérifier que votre tâche d'affinage enregistre les points de contrôle dans le bucket Cloud Storage. Le job d'affinage prend environ 10 minutes avant de commencer à enregistrer le premier point de contrôle.

        kubectl logs --namespace=llm --follow --selector=app=finetune-job
        

        La sortie du premier point de contrôle enregistré ressemble à ce qui suit :

        {"name": "finetune", "thread": 133763559483200, "threadName": "MainThread", "processName": "MainProcess", "process": 33, "message": "Fine tuning started", "timestamp": 1731002351.0016131, "level": "INFO", "runtime": 451579.89835739136}
        …
        {"name": "accelerate.utils.fsdp_utils", "thread": 136658669348672, "threadName": "MainThread", "processName": "MainProcess", "process": 32, "message": "Saving model to /model-data/model-gemma2/experiment/checkpoint-10/pytorch_model_fsdp_0", "timestamp": 1731002386.1763802, "level": "INFO", "runtime": 486753.8924217224}
        

      Tester la préemption et l'allocation dynamique de Kueue sur votre charge de travail mixte

      Dans cette section, vous allez simuler un scénario dans lequel la charge du serveur d'inférence augmente, ce qui nécessite une mise à l'échelle. Ce scénario montre comment Kueue donne la priorité au serveur d'inférence à priorité élevée en suspendant et en préemptant le job de réglage précis à priorité inférieure lorsque les ressources sont limitées.

      1. Exécutez la commande suivante pour faire passer le nombre d'instances dupliquées du serveur d'inférence à deux :

        kubectl scale --replicas=2 deployment/tgi-gemma-deployment --namespace=llm
        
      2. Vérifiez l'état des objets Workload :

        kubectl get workloads --namespace=llm
        

        La sortie ressemble à ceci :

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        job-finetune-gemma-l4-3316f                       lq                      False                 32m
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  70m
        pod-tgi-gemma-deployment-6cb95cc7f5-p49sh-167de   lq      cluster-queue   True                  14s
        

        La sortie montre que le job de réglage fin n'est plus accepté, car les répliques de serveur d'inférence supplémentaires utilisent le quota de GPU disponible.

      3. Vérifiez l'état du job de réglage fin :

        kubectl get job --namespace=llm
        

        Le résultat ressemble à ce qui suit, indiquant que l'état du job d'affinage est désormais "Suspendu" :

        NAME                STATUS      COMPLETIONS   DURATION   AGE
        finetune-gemma-l4   Suspended   0/2                      33m
        
      4. Exécutez la commande suivante pour inspecter vos pods :

        kubectl get pod --namespace=llm
        

        Le résultat ressemble à ce qui suit, indiquant que Kueue a mis fin aux pods de job de réglage précis pour libérer des ressources pour le déploiement du serveur d'inférence de priorité supérieure.

        NAME                                    READY   STATUS              RESTARTS   AGE
        tgi-gemma-deployment-6cb95cc7f5-cbxg2   1/1     Running             0          72m
        tgi-gemma-deployment-6cb95cc7f5-p49sh   0/1     ContainerCreating   0          91s
        
      5. Ensuite, testez le scénario dans lequel la charge du serveur d'inférence diminue et ses pods sont mis à l'échelle à la baisse. Exécutez la commande suivante :

        kubectl scale --replicas=1 deployment/tgi-gemma-deployment --namespace=llm
        

        Exécutez la commande suivante pour afficher les objets Workload :

        kubectl get workloads --namespace=llm
        

        Le résultat ressemble à ce qui suit, indiquant qu'un des déploiements du serveur d'inférence est terminé et que le job de réglage fin est réadmis.

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        job-finetune-gemma-l4-3316f                       lq      cluster-queue   True                  37m
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  75m
        
      6. Exécutez la commande suivante pour afficher les jobs :

        kubectl get job --namespace=llm
        

        Le résultat ressemble à ce qui suit, indiquant que le job d'affinage est à nouveau en cours d'exécution, en reprenant à partir du dernier point de contrôle disponible.

        NAME                STATUS    COMPLETIONS   DURATION   AGE
        finetune-gemma-l4   Running   0/2           2m11s      38m
        

      Effectuer un nettoyage

      Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez les ressources individuelles.

      Supprimer les ressources déployées

      Pour éviter que les ressources que vous avez créées dans ce guide soient facturées sur votre compte Google Cloud , exécutez les commandes suivantes :

      gcloud storage rm --recursive gs://${MODEL_BUCKET}
      gcloud container clusters delete ${CLUSTER_NAME} --location ${REGION}
      

      Étapes suivantes