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


Ce tutoriel explique comment partager efficacement les ressources d'accélérateur entre les charges de travail de formation et d'inférence dans un seul 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 du cluster, réduisez les problèmes liés aux limites de quantité d'accélérateurs et améliorez l'efficacité globale.

Dans ce tutoriel, vous allez créer un déploiement de diffusion de haute priorité à l'aide du grand modèle de langage (LLM) Gemma 2 pour l'inférence et du framework de diffusion de l'interface de génération de texte (TGI) de Hugging Face, ainsi qu'une tâche de réglage fin de LLM de faible 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 tâches Open Source natif Kubernetes, pour gérer et planifier vos charges de travail. Kueue vous permet de hiérarchiser les tâches de diffusion et de préempter les jobs de formation de faible priorité pour optimiser l'utilisation des ressources. À mesure que les demandes de diffusion diminuent, vous réallouez 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 s'adresse 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 les frais de gestion, en particulier lorsqu'ils font face à 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 la section Rôles utilisateur et tâches courantes de l'utilisateur dans GKE Enterprise.

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 prioritaire.
  • Configurez des jobs d'entraînement à faible priorité.
  • 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

  • Make sure 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

  • Make sure 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 chaque rôle supplémentaire.
    7. Cliquez sur Enregistrer.

Préparer l'environnement

Dans cette section, vous allez provisionner 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. Signer l'accord de consentement à 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. Cloud Shell est préinstallé avec les logiciels dont vous avez besoin pour ce tutoriel, 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 ID de projet Google Cloud.

  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 poids de votre modèle entraîné.

  2. Créez un cluster Autopilot :

    gcloud container clusters create-auto ${CLUSTER_NAME} \
        --project=${PROJECT_ID} \
        --region=${REGION} \
        --release-channel=rapid
    
  3. Créez le bucket Cloud Storage pour la tâche d'ajustement:

    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 \
        --region=$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 la section Limites d'utilisation des GPU sur GKE. Par exemple, cet tutoriel utilise g2-standard-24 avec deux GPU associés par nœud. Pour obtenir la liste la plus à jour des GPU disponibles, consultez la section 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 dernière liste des GPU disponibles, consultez la section GPU pour les charges de travail de calcul.
    • MODEL_BUCKET: bucket Cloud Storage dans lequel vous stockez les poids de votre modèle entraîné.
  2. Créez un cluster standard :

    gcloud container clusters create ${CLUSTER_NAME} \
        --project=${PROJECT_ID} \
        --region=${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 d'ajustement fin:

    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 la tâche d'ajustement:

    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 \
        --region=$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. Kueue y parvient en définissant les exigences en termes de ressources ("saveurs"), en hiérarchisant les charges de travail via des files d'attente (les tâches de service étant prioritaires sur l'entraînement) et en allouant dynamiquement des ressources en fonction de la demande et de la priorité. Ce tutoriel utilise le type de ressource Charge de travail pour regrouper les charges de travail d'inférence et d'ajustement fin, respectivement.

La fonctionnalité de préemption de Kueue garantit que les charges de travail de diffusion de priorité élevée disposent toujours des ressources nécessaires en suspendant ou en supprimant les tâches d'entraînement de priorité inférieure lorsque les ressources sont rares.

Pour contrôler le déploiement du serveur d'inférence avec Kueue, vous activez l'intégration de v1/pod en appliquant une configuration personnalisée à l'aide de Kustomize, afin de vous assurer que les pods du serveur sont libellés avec "kueue-job: true".

  1. Dans le répertoire /kueue, affichez 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.10.0/manifests.yaml
    patches:
    - path: patch.yaml
      target:
        version: v1
        kind: ConfigMap
        name: kueue-manager-config
    
  2. Dans le répertoire /kueue, affichez le code dans patch.yaml. Ce ConfigMap personnalise Kueue pour gérer les pods avec le libellé "kueue-job: true".

    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
        #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/mxjob"
          - "kubeflow.org/paddlejob"
          - "kubeflow.org/pytorchjob"
          - "kubeflow.org/tfjob"
          - "kubeflow.org/xgboostjob"
          - "pod"
        #  externalFrameworks:
        #  - "Foo.v1.example.com"
          podOptions:
            # You can change namespaceSelector to define in which 
            # namespaces kueue will manage the pods.
            namespaceSelector:
              matchExpressions:
              - key: kubernetes.io/metadata.name
                operator: NotIn
                values: [ kube-system, kueue-system ]
            # Kueue uses podSelector to manage pods with particular 
            # labels. The default podSelector will match all the pods. 
            podSelector:
              matchExpressions:
              - key: kueue-job
                operator: In
                values: [ "true", "True", "yes" ]
    
  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    2/2     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 un 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 un ClusterQueue Kueue avec des limites de ressources pour le processeur, la mémoire et le GPU.

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

    Le champ preemption de la configuration ClusterQueue fait référence aux PriorityClasses pour déterminer les pods pouvant ê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 file d'attente Kueue LocalQueue 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 l'environnement d'exécution de diffusion TGI et le modèle gemma-2-9B-it.

    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
            kueue-job: "true"
        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 prend 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 est au courant de votre déploiement, exécutez cette commande pour récupérer l'état des objets de charge de travail:

    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 forçage des limites de quota, redimensionnez le déploiement à quatre instances dupliquées:

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

    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. Ramenez maintenant le déploiement à une instance. Cette étape est obligatoire avant de déployer la tâche de paramétrage fin, sinon elle ne sera pas acceptée, car la tâche d'inférence aura la priorité.

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

Explication du comportement

L'exemple de scaling ne génère que trois réplicas (bien que la mise à l'échelle soit définie sur quatre) en raison de la limite de quota de GPU que vous avez définie dans la configuration de 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 accueillir qu'un maximum de trois réplicas du déploiement à la fois (puisque 3 réplicas * 2 GPU par réplica = 6 GPU, qui correspond au quota total).

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

Déployer la tâche d'entraînement

Dans cette section, vous allez déployer un job de réglage fin de priorité inférieure pour un modèle Gemma 2 qui nécessite quatre GPU sur deux pods. Ce travail utilisera le quota de GPU restant dans la file d'attente ClusterQueue. La tâche utilise une image prédéfinie et enregistre des points de contrôle pour permettre de redémarrer à partir de résultats intermédiaires.

La tâche d'ajustement utilise l'ensemble de données b-mc2/sql-create-context. La source de la tâche de réglage fin se trouve dans le dépôt.

  1. Affichez le fichier fine-tune-l4.yaml. Ce fichier manifeste définit la tâche 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 la tâche de réglage fin:

    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 de charge de travail, 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
    

    Affichez ensuite les pods de 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
    

    La sortie indique que Kueue autorise l'exécution de vos pods de serveur d'inférence et de tâche d'ajustement, en réservant les ressources appropriées en fonction des limites de quota spécifiées.

  4. Affichez les journaux de sortie pour vérifier que votre tâche de réglage fin enregistre des points de contrôle dans le bucket Cloud Storage. La tâche de réglage fin 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 à ceci:

    {"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 de l'étendre. Ce scénario montre comment Kueue donne la priorité au serveur d'inférence de haute priorité en suspendant et en préemptant la tâche de réglage fin de priorité inférieure lorsque les ressources sont limitées.

  1. Exécutez la commande suivante pour faire passer le nombre de réplicas du serveur d'inférence à deux:

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

    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 indique que l'opération de réglage fin n'est plus acceptée, car les réplicas de serveur d'inférence supplémentaires utilisent le quota de GPU disponible.

  3. Vérifiez l'état de la tâche d'ajustement:

    kubectl get job --namespace=llm
    

    La sortie ressemble à ceci, ce qui indique que l'état de la tâche d'ajustement est maintenant 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
    

    La sortie ressemble à ceci, ce qui indique que Kueue a arrêté les pods de tâches d'ajustement pour libérer des ressources pour le déploiement du serveur d'inférence de priorité plus élevée.

    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 où la charge du serveur d'inférence diminue et que ses pods sont réduits. Exécutez la commande suivante :

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

    Exécutez la commande suivante pour afficher les objets de charge de travail:

    kubectl get workloads --namespace=llm
    

    La sortie ressemble à ceci, ce qui indique qu'un des déploiements du serveur d'inférence est arrêté et que la tâche d'ajustement est réadmise.

    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 cette commande pour afficher les tâches:

    kubectl get job --namespace=llm
    

    La sortie ressemble à ce qui suit, ce qui indique que la tâche d'ajustement s'exécute à nouveau, 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 ne 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}

Étape suivante