Ottimizza l'utilizzo delle risorse GKE per i carichi di lavoro di addestramento e inferenza misti di AI/ML


Questo tutorial mostra come condividere in modo efficiente le risorse dell'acceleratore tra i carichi di lavoro di addestramento e di inferenza all'interno di un singolo cluster Google Kubernetes Engine (GKE). Distribuendo i carichi di lavoro misti su un singolo cluster, migliori l'utilizzo delle risorse, semplifichi la gestione del cluster, riduci i problemi dovuti alle limitazioni di quantità degli acceleratori e aumenti l'economicità complessiva.

In questo tutorial, crei un deployment di pubblicazione con priorità elevata utilizzando il modello linguistico di grandi dimensioni (LLM) Gemma 2 per l'inferenza e il framework di pubblicazione Hugging Face TGI (Text Generation Interface), insieme a un job di ottimizzazione fine degli LLM con priorità bassa. Entrambi i carichi di lavoro vengono eseguiti su un singolo cluster che utilizza GPU NVIDIA L4. Utilizzi Kueue, un sistema di coda di job open source nativo di Kubernetes, per gestire e pianificare i carichi di lavoro. Kueue ti consente di dare la priorità alle attività di pubblicazione e di anticipare i job di addestramento di priorità inferiore per ottimizzare l'utilizzo delle risorse. Man mano che le richieste di pubblicazione diminuiscono, riallochi gli acceleratori liberati per riprendere i job di addestramento. Utilizzi Kueue e le classi di priorità per gestire le quote di risorse durante il processo.

Questo tutorial è rivolto a data engineer, gestori e operatori della piattaforma e a specialisti di dati e AI che vogliono addestrare e ospitare un modello di machine learning (ML) su un cluster GKE e che vogliono anche ridurre i costi e il sovraccarico di gestione, soprattutto quando si ha a che fare con un numero limitato di acceleratori. Per scoprire di più sui ruoli comuni e su esempi di attività a cui facciamo riferimento nei Google Cloud contenuti, consulta Ruoli e attività comuni degli utenti di GKE Enterprise.

Prima di leggere questa pagina, assicurati di conoscere quanto segue:

Obiettivi

Al termine di questa guida, dovresti essere in grado di eseguire i seguenti passaggi:

  • Configura un deployment di pubblicazione con priorità elevata.
  • Configura job di addestramento con priorità inferiore.
  • Implementa strategie di prelazione per soddisfare la domanda variabile.
  • Gestisci l'allocazione delle risorse tra le attività di addestramento e di pubblicazione utilizzando Kueue.

Prima di iniziare

  • 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.

      Vai a IAM
    2. Seleziona il progetto.
    3. Fai clic su Concedi accesso.
    4. Nel campo Nuovi principali, inserisci il tuo identificatore utente. In genere si tratta dell'indirizzo email di un Account Google.

    5. Nell'elenco Seleziona un ruolo, seleziona un ruolo.
    6. Per concedere altri ruoli, fai clic su Aggiungi un altro ruolo e aggiungi ogni ruolo aggiuntivo.
    7. Fai clic su Salva.

Prepara l'ambiente

In questa sezione esegui il provisioning delle risorse necessarie per eseguire il deployment di TGI e del modello per i carichi di lavoro di inferenza e addestramento.

Ottieni l'accesso al modello

Per accedere ai modelli Gemma per il deployment in GKE, devi prima firmare il contratto di consenso per la licenza, quindi generare un token di accesso a Hugging Face.

  1. Firma il contratto di consenso alla licenza. Accedi alla pagina del consenso per i modelli, verifica il consenso utilizzando il tuo account Hugging Face e accetta i termini del modello.
  2. Genera un token di accesso. Per accedere al modello tramite Hugging Face, hai bisogno di un token Hugging Face. Se non ne hai già uno, segui questi passaggi per generare un nuovo token:

    1. Fai clic su Il tuo profilo > Impostazioni > Token di accesso.
    2. Seleziona Nuovo token.
    3. Specifica un nome a tua scelta e un ruolo di almeno Read.
    4. Seleziona Genera un token.
    5. Copia il token generato negli appunti.

Avvia Cloud Shell

In questo tutorial utilizzerai Cloud Shell per gestire le risorse ospitate su Google Cloud. Cloud Shell include il software necessario per questo tutorial, tra cui kubectl, gcloud CLI e Terraform.

Per configurare l'ambiente con Cloud Shell:

  1. Nella console Google Cloud, avvia una sessione Cloud Shell facendo clic su Icona di attivazione di Cloud Shell Attiva Cloud Shell nella console Google Cloud. Viene avviata una sessione nel riquadro inferiore della console Google Cloud.

  2. Imposta le variabili di ambiente predefinite:

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

    Sostituisci PROJECT_ID con il tuo Google Cloud ID progetto.

  3. Clona il codice campione da GitHub. In Cloud Shell, esegui i seguenti comandi:

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

Crea un cluster GKE

Puoi utilizzare un cluster Autopilot o Standard per i carichi di lavoro misti. Ti consigliamo di utilizzare un cluster Autopilot per un'esperienza Kubernetes completamente gestita. Per scegliere la modalità di funzionamento GKE più adatta ai tuoi carichi di lavoro, consulta Scegliere una modalità di funzionamento GKE.

Autopilot

  1. Imposta le variabili di ambiente predefinite in 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"
    

    Sostituisci i seguenti valori:

    • HF_TOKEN: il token Hugging Face che hai generato in precedenza.
    • REGION: una regione che supporta il tipo di acceleratore che vuoi utilizzare, ad esempio us-central1 per la GPU L4.

    Puoi modificare la variabile MODEL_BUCKET, che rappresenta il bucket Cloud Storage in cui memorizzi i pesi del modello addestrato.

  2. Crea un cluster Autopilot:

    gcloud container clusters create-auto ${CLUSTER_NAME} \
        --project=${PROJECT_ID} \
        --region=${REGION} \
        --release-channel=rapid
    
  3. Crea il bucket Cloud Storage per il job di ottimizzazione fine:

    gcloud storage buckets create gs://${MODEL_BUCKET} \
        --location ${REGION} \
        --uniform-bucket-level-access
    
  4. Per concedere l'accesso al bucket Cloud Storage, esegui questo comando:

    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. Per recuperare le credenziali di autenticazione per il cluster, esegui questo comando:

    gcloud container clusters get-credentials llm-cluster \
        --region=$REGION \
        --project=$PROJECT_ID
    
  6. Crea uno spazio dei nomi per i deployment. In Cloud Shell, esegui il seguente comando:

    kubectl create ns llm
    

Standard

  1. Imposta le variabili di ambiente predefinite in 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"
    

    Sostituisci i seguenti valori:

    • HF_TOKEN: il token Hugging Face che hai generato in precedenza.
    • REGION: la regione che supporta il tipo di acceleratore che vuoi utilizzare, ad esempio us-central1 per la GPU L4.

    Puoi modificare queste variabili:

    • GPU_POOL_MACHINE_TYPE: la serie di macchine del pool di nodi da utilizzare nella regione selezionata. Questo valore dipende dal tipo di acceleratore selezionato. Per scoprire di più, consulta la sezione Limitazioni dell'utilizzo delle GPU su GKE. Ad esempio, questo tutorial utilizza g2-standard-24 con due GPU collegate per nodo. Per l'elenco più aggiornato delle GPU disponibili, consulta GPU per i carichi di lavoro di calcolo.
    • GPU_POOL_ACCELERATOR_TYPE: il tipo di acceleratore supportato nella regione selezionata. Ad esempio, questo tutorial utilizza nvidia-l4. Per l'elenco più recente delle GPU disponibili, consulta GPU per i carichi di lavoro di calcolo.
    • MODEL_BUCKET: il bucket Cloud Storage in cui memorizzi i pesi del modello addestrato.
  2. Crea 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. Crea il pool di nodi GPU per i carichi di lavoro di inferenza e ottimizzazione fine:

    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. Crea il bucket Cloud Storage per il job di ottimizzazione fine:

    gcloud storage buckets create gs://${MODEL_BUCKET} \
        --location ${REGION} \
        --uniform-bucket-level-access
    
  5. Per concedere l'accesso al bucket Cloud Storage, esegui questo comando:

    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. Per recuperare le credenziali di autenticazione per il cluster, esegui questo comando:

    gcloud container clusters get-credentials llm-cluster \
        --region=$REGION \
        --project=$PROJECT_ID
    
  7. Crea uno spazio dei nomi per i deployment. In Cloud Shell, esegui il seguente comando:

    kubectl create ns llm
    

Crea un secret di Kubernetes per le credenziali di Hugging Face

Per creare un secret di Kubernetes contenente il token di Hugging Face, esegui il seguente comando:

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

Configura Kueue

In questo tutorial, Kueue è il gestore delle risorse centrali, che consente la condivisione efficiente delle GPU tra i carichi di lavoro di addestramento e di pubblicazione. Kueue ottiene questo risultato definendo i requisiti delle risorse ("flavor"), dando la priorità ai carichi di lavoro tramite le code (con le attività di pubblicazione prioritarie rispetto alla formazione) e allocando dinamicamente le risorse in base alla domanda e alla priorità. Questo tutorial utilizza il tipo di risorsa Workload per raggruppare rispettivamente i carichi di lavoro di inferenza e di ottimizzazione fine.

La funzionalità di preemption di Kueue garantisce che i carichi di lavoro di pubblicazione con priorità elevata dispongano sempre delle risorse necessarie mettendo in pausa o espellendo i job di addestramento con priorità inferiore quando le risorse sono scarse.

Per controllare il deployment del server di inferenza con Kueue, attiva l'integrazione v1/pod mediante l'applicazione di una configurazione personalizzata utilizzando Kustomize, per assicurarti che i pod del server siano etichettati con "kueue-job: true".

  1. Nella directory /kueue, visualizza il codice in kustomization.yaml. Questo manifest installa il gestore delle risorse Kueue con configurazioni personalizzate.

    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. Nella directory /kueue, visualizza il codice in patch.yaml. Questo ConfigMap personalizzato consente a Kueue di gestire i pod con l'etichetta "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. In Cloud Shell, esegui questo comando per installare Kueue:

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

    Attendi che i pod Kueue siano pronti:

    watch kubectl --namespace=kueue-system get pods
    

    L'output dovrebbe essere simile al seguente:

    NAME                                        READY   STATUS    RESTARTS   AGE
    kueue-controller-manager-bdc956fc4-vhcmx    2/2     Running   0          3m15s
    
  4. Nella directory /workloads, visualizza i file flavors.yaml, cluster-queue.yaml e local-queue.yaml. Questi manifest specificano in che modo Kueue gestisce le quote di risorse:

    ResourceFlavor

    Questo manifest definisce un valore predefinito per ResourceFlavor in Kueue per la gestione delle risorse.

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

    ClusterQueue

    Questo manifest configura una coda ClusterQueue di Kueue con limiti di risorse per CPU, memoria e GPU.

    Questo tutorial utilizza nodi con due GPU Nvidia L4 collegate, con il tipo di nodo corrispondente g2-standard-24, che offre 24 vCPU e 96 GB di RAM. Il codice di esempio mostra come limitare l'utilizzo delle risorse del carico di lavoro a un massimo di sei GPU.

    Il campo preemption nella configurazione di ClusterQueue fa riferimento a PriorityClasses per determinare quali pod possono essere prelevati quando le risorse sono scarse.

    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

    Questo manifest crea una LocalQueue Kueue denominata lq nello spazio dei nomi 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. Visualizza i file default-priorityclass.yaml, low-priorityclass.yaml e high-priorityclass.yaml. Questi manifest definiscono gli oggetti PriorityClass per la pianificazione di Kubernetes.

    Priorità predefinita

    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."
    

    Bassa priorità

    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à elevata

    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. Crea gli oggetti Kueue e Kubernetes eseguendo questi comandi per applicare i manifest corrispondenti.

    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
    

Esegui il deployment del server di inferenza TGI

In questa sezione esegui il deployment del contenitore TGI per pubblicare il modello Gemma 2.

  1. Nella directory /workloads, visualizza il file tgi-gemma-2-9b-it-hp.yaml. Questo manifest definisce un deployment Kubernetes per eseguire il deployment del runtime di pubblicazione TGI e del modello gemma-2-9B-it. Un deployment è un oggetto dell'API Kubernetes che ti consente di eseguire più repliche di pod distribuite tra i nodi di un cluster.

    Il deployment dà la priorità alle attività di inferenza e utilizza due GPU per il modello. Utilizza il parallelismo tensoriale, impostando la variabile di ambiente NUM_SHARD, per adattare il modello alla memoria 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. Applica il manifest eseguendo il seguente comando:

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

    Il completamento dell'operazione di deployment richiederà alcuni minuti.

  3. Per verificare se GKE ha creato correttamente il deployment, esegui questo comando:

    kubectl --namespace=llm get deployment
    

    L'output dovrebbe essere simile al seguente:

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

Verifica la gestione della quota di Kueue

In questa sezione, confermi che Kueue applica correttamente la quota GPU per il tuo deployment.

  1. Per verificare se Kueue è a conoscenza del tuo deployment, esegui questo comando per recuperare lo stato degli oggetti Workload:

    kubectl --namespace=llm get workloads
    

    L'output dovrebbe essere simile al seguente:

    NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
    pod-tgi-gemma-deployment-6bf9ffdc9b-zcfrh-84f19   lq      cluster-queue   True                  8m23s
    
  2. Per testare l'override dei limiti di quota, esegui lo scale del deployment a quattro repliche:

    kubectl scale --replicas=4 deployment/tgi-gemma-deployment --namespace=llm
    
  3. Esegui il seguente comando per visualizzare il numero di repliche di GKE di cui viene eseguito il deployment:

    kubectl get workloads --namespace=llm
    

    L'output dovrebbe essere simile al seguente:

    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
    

    L'output mostra che vengono ammessi solo tre pod a causa della quota di risorse applicata da Kueue.

  4. Esegui il seguente comando per visualizzare i pod nello spazio dei nomi llm:

    kubectl get pod --namespace=llm
    

    L'output dovrebbe essere simile al seguente:

    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. Ora, fare lo scale down nuovamente il deployment a 1. Questo passaggio è obbligatorio prima di eseguire il deployment del job di ottimizzazione fine, altrimenti non verrà ammesso perché il job di inferenza ha la priorità.

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

Spiegazione del comportamento

L'esempio di scalabilità genera solo tre repliche (nonostante la scalabilità a quattro) a causa del limite di quota GPU impostato nella configurazione di ClusterQueue. La sezione spec.resourceGroups di ClusterQueue definisce una nominalQuota pari a "6" per nvidia.com/gpu. Il deployment specifica che ogni pod richiede "2" GPU. Pertanto, ClusterQueue può gestire al massimo tre repliche del deployment alla volta (poiché 3 repliche * 2 GPU per replica = 6 GPU, che è la quota totale).

Quando provi a eseguire il ridimensionamento a quattro repliche, Kueue riconosce che questa azione supererebbe la quota GPU e impedisce la pianificazione della quarta replica. Questo è indicato dallo stato SchedulingGated del quarto pod. Questo comportamento dimostra l'applicazione della quota delle risorse di Kueue.

Esegui il deployment del job di addestramento

In questa sezione esegui il deployment di un job di ottimizzazione fine di priorità inferiore per un modello Gemma 2 che richiede quattro GPU su due pod. Questo job utilizzerà la quota GPU rimanente in ClusterQueue. Il job utilizza un'immagine predefinita e salva i checkpoint per consentire il riavvio dai risultati intermedi.

Il job di ottimizzazione utilizza il set di dati b-mc2/sql-create-context. Il codice sorgente del job di ottimizzazione è disponibile nel repository.

  1. Visualizza il file fine-tune-l4.yaml. Questo manifest definisce il job di ottimizzazione fine.

    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. Applica il manifest per creare il job di ottimizzazione:

    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. Verifica che i deployment siano in esecuzione. Per controllare lo stato degli oggetti Workload, esegui il seguente comando:

    kubectl get workloads --namespace=llm
    

    L'output dovrebbe essere simile al seguente:

    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
    

    Quindi, visualizza i pod nello spazio dei nomi llm eseguendo questo comando:

    kubectl get pod --namespace=llm
    

    L'output dovrebbe essere simile al seguente:

    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
    

    L'output mostra che Kueue ammette l'esecuzione sia del job di ottimizzazione fine sia dei pod del server di inferenza, riservando le risorse corrette in base ai limiti di quota specificati.

  4. Visualizza i log di output per verificare che il job di ottimizzazione fine salvi i checkpoint nel bucket Cloud Storage. Il job di ottimizzazione richiede circa 10 minuti prima di iniziare a salvare il primo checkpoint.

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

    L'output del primo checkpoint salvato è simile al seguente:

    {"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}
    

Testa la preemption e l'allocazione dinamica di Kueue sul tuo carico di lavoro misto

In questa sezione simuli uno scenario in cui il carico del server di inferenza aumenta, richiedendo un aumento di scala. Questo scenario mostra come Kueue dà la priorità al server di inferenza ad alta priorità sospendendo e anticipando il job di ottimizzazione fine ad alta priorità quando le risorse sono limitate.

  1. Esegui il seguente comando per scalare le repliche del server di inferenza a due:

    kubectl scale --replicas=2 deployment/tgi-gemma-deployment --namespace=llm
    
  2. Controlla lo stato degli oggetti Workload:

    kubectl get workloads --namespace=llm
    

    L'output è simile al seguente:

    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
    

    L'output mostra che il job di ottimizzazione fine non è più accettato perché le repliche del server di inferenza aumentate stanno utilizzando la quota GPU disponibile.

  3. Controlla lo stato del job di ottimizzazione:

    kubectl get job --namespace=llm
    

    L'output è simile al seguente, a indicare che lo stato del job di ottimizzazione fine è ora sospeso:

    NAME                STATUS      COMPLETIONS   DURATION   AGE
    finetune-gemma-l4   Suspended   0/2                      33m
    
  4. Esegui il comando seguente per ispezionare i pod:

    kubectl get pod --namespace=llm
    

    L'output è simile al seguente, a indicare che Kueue ha terminato i pod dei job di ottimizzazione fine per liberare risorse per il deployment del server di inferenza con priorità più elevata.

    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. Successivamente, testa lo scenario in cui il carico del server di inferenza diminuisce e i suoi pod fare lo scale down. Esegui questo comando:

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

    Esegui il seguente comando per visualizzare gli oggetti Workload:

    kubectl get workloads --namespace=llm
    

    L'output è simile al seguente, a indicare che uno dei deployment del server di inferenza è stato terminato e il job di ottimizzazione fine è stato riammesso.

    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. Esegui questo comando per visualizzare i job:

    kubectl get job --namespace=llm
    

    L'output è simile al seguente, a indicare che il job di ottimizzazione fine è di nuovo in esecuzione e riprende dall'ultimo checkpoint disponibile.

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

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

Elimina le risorse di cui è stato eseguito il deployment

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse che hai creato in questa guida, esegui i seguenti comandi:

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

Passaggi successivi