GKE-Ressourcennutzung für gemischte KI/ML-Trainings- und Inferenzarbeitslasten optimieren


In dieser Anleitung erfahren Sie, wie Sie Beschleunigerressourcen innerhalb eines einzelnen GKE-Clusters (Google Kubernetes Engine) effizient zwischen Arbeitslasten für die Modelltrainings und die Bereitstellung von Inferenzen teilen. Wenn Sie Ihre gemischten Arbeitslasten auf einen einzigen Cluster verteilen, verbessern Sie die Ressourcennutzung, vereinfachen die Clusterverwaltung, reduzieren Probleme aufgrund von Einschränkungen bei der Anzahl der Accelerator und steigern die Gesamtkosteneffizienz.

In dieser Anleitung erstellen Sie eine Bereitstellung mit hoher Priorität, die das Gemma 2 Large Language Model (LLM) für die Inferenz und das Serving-Framework Hugging Face TGI (Text Generation Interface) sowie einen Job zur LLM-Feinabstimmung mit niedriger Priorität verwendet. Beide Arbeitslasten werden auf einem einzigen Cluster ausgeführt, der NVIDIA L4-GPUs verwendet. Sie verwenden Kueue, ein Open-Source-Jobwarteschlangensystem für Kubernetes, um Ihre Arbeitslasten zu verwalten und zu planen. Mit Kueue können Sie Aufgaben für die Bereitstellung priorisieren und Trainingsjobs mit niedrigerer Priorität vorzeitig beenden, um die Ressourcennutzung zu optimieren. Wenn die Anforderungen an die Auslieferung sinken, können Sie die freigegebenen Beschleuniger neu zuweisen, um Trainingjobs fortzusetzen. Sie verwenden Warteschlangen und Prioritätsklassen, um Ressourcenkontingente während des gesamten Prozesses zu verwalten.

Diese Anleitung richtet sich an Machine-Learning-Entwickler, Plattformadministratoren und ‑bearbeiter sowie an Daten- und KI-Spezialisten, die ein ML-Modell in einem GKE-Cluster trainieren und hosten und gleichzeitig die Kosten und den Verwaltungsaufwand senken möchten, insbesondere bei einer begrenzten Anzahl von Beschleunigern. Weitere Informationen zu gängigen Rollen und Beispielaufgaben, auf die wir in Google Cloud -Inhalten verweisen, finden Sie unter Häufig verwendete GKE Enterprise-Nutzerrollen und -Aufgaben.

Machen Sie sich vor dem Lesen dieser Seite mit den folgenden Themen vertraut:

Lernziele

Sie sollten am Ende dieses Leitfadens in der Lage sein, die folgenden Schritte auszuführen:

  • Konfigurieren Sie eine Bereitstellung mit hoher Priorität.
  • Richten Sie Trainingsjobs mit niedrigerer Priorität ein.
  • Implementieren Sie Voraktivierungsstrategien, um der schwankenden Nachfrage gerecht zu werden.
  • Verwalten Sie die Ressourcenzuweisung zwischen Trainings- und Bereitstellungsaufgaben mit Kueue.

Hinweise

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

      IAM aufrufen
    2. Wählen Sie das Projekt aus.
    3. Klicken Sie auf Zugriff erlauben.
    4. Geben Sie im Feld Neue Hauptkonten Ihre Nutzer-ID ein. Dies ist in der Regel die E-Mail-Adresse eines Google-Kontos.

    5. Wählen Sie in der Liste Rolle auswählen eine Rolle aus.
    6. Wenn Sie weitere Rollen hinzufügen möchten, klicken Sie auf Weitere Rolle hinzufügen und fügen Sie weitere Rollen hinzu.
    7. Klicken Sie auf Speichern.
  • Erstellen Sie ein Hugging Face-Konto, falls Sie noch keines haben.
  • Prüfen Sie, ob Ihr Projekt ein ausreichendes Kontingent für L4-GPUs hat. Weitere Informationen finden Sie unter GPUs und Zuteilungskontingente.

Umgebung vorbereiten

In diesem Abschnitt stellen Sie die Ressourcen bereit, die Sie zum Bereitstellen von TGI und dem Modell für Ihre Inferenz- und Trainingsarbeitslasten benötigen.

Zugriff auf das Modell erhalten

Wenn Sie Zugriff auf die Gemma-Modelle für die Bereitstellung in GKE erhalten möchten, müssen Sie zuerst die Lizenzeinwilligungsvereinbarung unterzeichnen und dann ein Hugging-Face-Zugriffstoken generieren.

  1. Unterzeichnen Sie die Lizenz-Einwilligungsvereinbarung. Rufen Sie die Seite zur Modelleinwilligung auf, bestätigen Sie die Einwilligung mit Ihrem Hugging Face-Konto und akzeptieren Sie die Nutzungsbedingungen für das Modell.
  2. Erstelle ein Zugriffstoken. Für den Zugriff auf das Modell über Hugging Face benötigen Sie ein Hugging Face-Token. Führen Sie die folgenden Schritte aus, um ein neues Token zu generieren, falls Sie noch keines haben:

    1. Klicken Sie auf Profil > Einstellungen > Zugriffstokens.
    2. Wählen Sie Neues Token aus.
    3. Geben Sie einen Namen Ihrer Wahl und eine Rolle von mindestens Read an.
    4. Wählen Sie Token generieren aus.
    5. Kopieren Sie das Token in die Zwischenablage.

Cloud Shell starten

In dieser Anleitung verwenden Sie Cloud Shell zum Verwalten von Ressourcen, die inGoogle Cloudgehostet werden. Die Software, die Sie für diese Anleitung benötigen, ist in Cloud Shell vorinstalliert, einschließlich kubectl, gcloud CLI und Terraform.

So richten Sie Ihre Umgebung mit Cloud Shell ein:

  1. Starten Sie in der Google Cloud Console eine Cloud Shell-Sitzung. Klicken Sie dazu in der Google Cloud Console auf Symbol für die Cloud Shell-Aktivierung Cloud Shell aktivieren. Dadurch wird im unteren Bereich der Google Cloud Console eine Sitzung gestartet.

  2. Legen Sie die Standardumgebungsvariablen fest:

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

    Ersetzen Sie PROJECT_ID durch Ihre Google Cloud Projekt-ID.

  3. Klonen Sie den Beispielcode aus GitHub. Führen Sie in Cloud Shell die folgenden Befehle aus:

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

GKE-Cluster erstellen

Sie können einen Autopilot- oder Standardcluster für Ihre gemischten Arbeitslasten verwenden. Für eine vollständig verwaltete Kubernetes-Umgebung empfehlen wir die Verwendung eines Autopilot-Clusters. Informationen zum Auswählen des GKE-Betriebsmodus, der für Ihre Arbeitslasten am besten geeignet ist, finden Sie unter GKE-Betriebsmodus auswählen.

Autopilot

  1. Legen Sie die Standardumgebungsvariablen in Cloud Shell fest:

    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"
    

    Ersetzen Sie die folgenden Werte:

    • HF_TOKEN: das Hugging Face-Token, das Sie zuvor generiert haben.
    • REGION: eine Region, die den Beschleunigertyp unterstützt, den Sie verwenden möchten, z. B. us-central1 für die L4-GPU.

    Sie können die Variable MODEL_BUCKET anpassen. Diese steht für den Cloud Storage-Bucket, in dem Sie die Gewichte Ihres trainierten Modells speichern.

  2. Autopilot-Cluster erstellen:

    gcloud container clusters create-auto ${CLUSTER_NAME} \
        --project=${PROJECT_ID} \
        --region=${REGION} \
        --release-channel=rapid
    
  3. Erstellen Sie den Cloud Storage-Bucket für den Job zur Feinabstimmung:

    gcloud storage buckets create gs://${MODEL_BUCKET} \
        --location ${REGION} \
        --uniform-bucket-level-access
    
  4. Führen Sie den folgenden Befehl aus, um Zugriff auf den Cloud Storage-Bucket zu gewähren:

    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. Führen Sie den folgenden Befehl aus, um Authentifizierungsdaten für den Cluster abzurufen:

    gcloud container clusters get-credentials llm-cluster \
        --region=$REGION \
        --project=$PROJECT_ID
    
  6. Erstellen Sie einen Namespace für Ihre Bereitstellungen. Führen Sie in Cloud Shell den folgenden Befehl aus:

    kubectl create ns llm
    

Standard

  1. Legen Sie die Standardumgebungsvariablen in Cloud Shell fest:

    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"
    

    Ersetzen Sie die folgenden Werte:

    • HF_TOKEN: das Hugging Face-Token, das Sie zuvor generiert haben.
    • REGION: die Region, die den Beschleunigertyp unterstützt, den Sie verwenden möchten, z. B. us-central1 für die L4-GPU.

    Sie können die folgenden Variablen anpassen:

    • GPU_POOL_MACHINE_TYPE: Die Maschinenreihe des Knotenpools, die Sie in der ausgewählten Region verwenden möchten. Dieser Wert hängt vom ausgewählten Beschleunigertyp ab. Weitere Informationen finden Sie unter Einschränkungen bei der Verwendung von GPUs in GKE. In dieser Anleitung wird beispielsweise g2-standard-24 mit zwei GPUs pro Knoten verwendet. Die aktuellste Liste der verfügbaren GPUs finden Sie unter GPUs für Compute-Arbeitslasten.
    • GPU_POOL_ACCELERATOR_TYPE: Der Beschleunigertyp, der in der ausgewählten Region unterstützt wird. In dieser Anleitung wird beispielsweise nvidia-l4 verwendet. Die aktuelle Liste der verfügbaren GPUs finden Sie unter GPUs für Compute-Arbeitslasten.
    • MODEL_BUCKET: Der Cloud Storage-Bucket, in dem Sie die Gewichte des trainierten Modells speichern.
  2. Standardcluster erstellen:

    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. Erstellen Sie den GPU-Knotenpool für Inferenz- und Feinabstimmungsarbeitslasten:

    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. Erstellen Sie den Cloud Storage-Bucket für den Job zur Feinabstimmung:

    gcloud storage buckets create gs://${MODEL_BUCKET} \
        --location ${REGION} \
        --uniform-bucket-level-access
    
  5. Führen Sie den folgenden Befehl aus, um Zugriff auf den Cloud Storage-Bucket zu gewähren:

    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. Führen Sie den folgenden Befehl aus, um Authentifizierungsdaten für den Cluster abzurufen:

    gcloud container clusters get-credentials llm-cluster \
        --region=$REGION \
        --project=$PROJECT_ID
    
  7. Erstellen Sie einen Namespace für Ihre Bereitstellungen. Führen Sie in Cloud Shell den folgenden Befehl aus:

    kubectl create ns llm
    

Kubernetes-Secret für Hugging Face-Anmeldedaten erstellen

Führen Sie den folgenden Befehl aus, um ein Kubernetes-Secret zu erstellen, das das Hugging Face-Token enthält:

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

Kueue konfigurieren

In dieser Anleitung ist Kueue der zentrale Ressourcenmanager, der eine effiziente Freigabe von GPUs zwischen Ihren Trainings- und Bereitstellungslasten ermöglicht. Kueue erreicht dies, indem es Ressourcenanforderungen („Varianten“) definiert, Arbeitslasten über Warteschlangen priorisiert (wobei Bereitstellungsaufgaben vor Training priorisiert werden) und Ressourcen dynamisch basierend auf Nachfrage und Priorität zuweist. In dieser Anleitung wird der Ressourcentyp Arbeitslast verwendet, um die Arbeitslasten für die Inferenz und die Feinabstimmung zu gruppieren.

Die Voraktivierungsfunktion von Kueue sorgt dafür, dass Arbeitslasten mit hoher Priorität immer die erforderlichen Ressourcen haben. Dazu werden Trainingsjobs mit niedrigerer Priorität pausiert oder entfernt, wenn Ressourcen knapp sind.

Wenn Sie die Bereitstellung des Inferenzservers mit Kueue steuern möchten, aktivieren Sie die v1/pod-Integration, indem Sie mit Kustomize eine benutzerdefinierte Konfiguration anwenden, damit die Server-Pods mit "kueue-job: true" gekennzeichnet sind.

  1. Rufen Sie im Verzeichnis /kueue den Code in kustomization.yaml auf. Mit diesem Manifest wird der Kueue-Ressourcenmanager mit benutzerdefinierten Konfigurationen installiert.

    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. Rufen Sie im Verzeichnis /kueue den Code in patch.yaml auf. Mit dieser ConfigMap wird Kueue so angepasst, dass Pods mit dem Label "kueue-job: true" verwaltet werden.

    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. Führen Sie in Cloud Shell den folgenden Befehl aus, um Kueue zu installieren:

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

    Warten Sie, bis die Kueue-Pods bereit sind:

    watch kubectl --namespace=kueue-system get pods
    

    Die Ausgabe sollte in etwa so aussehen:

    NAME                                        READY   STATUS    RESTARTS   AGE
    kueue-controller-manager-bdc956fc4-vhcmx    2/2     Running   0          3m15s
    
  4. Rufen Sie im Verzeichnis /workloads die Dateien flavors.yaml, cluster-queue.yaml und local-queue.yaml auf. In diesen Manifesten wird angegeben, wie Kueue Ressourcenkontingente verwaltet:

    ResourceFlavor

    Dieses Manifest definiert in Kueue ein standardmäßiges ResourceFlavor für die Ressourcenverwaltung.

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

    ClusterQueue

    In diesem Manifest wird eine ClusterQueue mit Ressourcenlimits für CPU, Arbeitsspeicher und GPU eingerichtet.

    In dieser Anleitung werden Knoten mit zwei angeschlossenen Nvidia L4-GPUs verwendet. Der entsprechende Knotentyp ist g2-standard-24 mit 24 vCPUs und 96 GB RAM. Im Beispielcode wird gezeigt, wie Sie die Ressourcennutzung Ihrer Arbeitslast auf maximal sechs GPUs begrenzen.

    Das Feld preemption in der ClusterQueue-Konfiguration verweist auf die PriorityClasses, um zu bestimmen, welche Pods bei knappen Ressourcen vorzeitig beendet werden können.

    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

    Mit diesem Manifest wird eine LocalQueue mit dem Namen lq im Namespace llm erstellt.

    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. Rufen Sie die Dateien default-priorityclass.yaml, low-priorityclass.yaml und high-priorityclass.yaml auf. Diese Manifeste definieren die PriorityClass-Objekte für die Kubernetes-Planung.

    Standardpriorität

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

    Niedrige Priorität

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

    Hohe Priorität

    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. Erstellen Sie die Kueue- und Kubernetes-Objekte, indem Sie die folgenden Befehle ausführen, um die entsprechenden Manifeste anzuwenden.

    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
    

TGI-Inferenzserver bereitstellen

In diesem Abschnitt stellen Sie den TGI-Container für das Gemma 2-Modell bereit.

  1. Öffnen Sie die Datei tgi-gemma-2-9b-it-hp.yaml im Verzeichnis /workloads. In diesem Manifest wird ein Kubernetes-Deployment definiert, um die TGI-Ausführungslaufzeit und das gemma-2-9B-it-Modell bereitzustellen. Ein Deployment ist ein Kubernetes API-Objekt, mit dem Sie mehrere Replikate von Pods ausführen können, die auf die Knoten in einem Cluster verteilt sind.

    Bei der Bereitstellung werden Inferenzaufgaben priorisiert und zwei GPUs für das Modell verwendet. Es verwendet Tensorparallelität, indem die Umgebungsvariable NUM_SHARD festgelegt wird, um das Modell in den GPU-Speicher zu passen.

    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. Wenden Sie das Manifest mit dem folgenden Befehl an:

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

    Die Bereitstellung dauert einige Minuten.

  3. Führen Sie den folgenden Befehl aus, um zu prüfen, ob die Bereitstellung in GKE erfolgreich erstellt wurde:

    kubectl --namespace=llm get deployment
    

    Die Ausgabe sollte in etwa so aussehen:

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

Kueue-Kontingentverwaltung prüfen

In diesem Abschnitt prüfen Sie, ob das GPU-Kontingent für Ihre Bereitstellung von Kueue korrekt erzwungen wird.

  1. Führen Sie diesen Befehl aus, um den Status der Workload-Objekte abzurufen und zu prüfen, ob Kueue Ihr Deployment kennt:

    kubectl --namespace=llm get workloads
    

    Die Ausgabe sollte in etwa so aussehen:

    NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
    pod-tgi-gemma-deployment-6bf9ffdc9b-zcfrh-84f19   lq      cluster-queue   True                  8m23s
    
  2. Skalieren Sie das Deployment auf vier Replikate, um das Überschreiben der Kontingentlimits zu testen:

    kubectl scale --replicas=4 deployment/tgi-gemma-deployment --namespace=llm
    
  3. Führen Sie den folgenden Befehl aus, um die Anzahl der von GKE bereitgestellten Replikate zu sehen:

    kubectl get workloads --namespace=llm
    

    Die Ausgabe sollte in etwa so aussehen:

    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
    

    Die Ausgabe zeigt, dass aufgrund des von Kueue erzwungenen Ressourcenkontingents nur drei Pods zugelassen werden.

  4. Führen Sie Folgendes aus, um die Pods im Namespace llm anzuzeigen:

    kubectl get pod --namespace=llm
    

    Die Ausgabe sollte in etwa so aussehen:

    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. Skalieren Sie das Deployment jetzt wieder auf 1 herunter. Dieser Schritt ist vor dem Bereitstellen des Jobs zur Feinabstimmung erforderlich. Andernfalls wird er nicht zugelassen, da der Inferenzjob Vorrang hat.

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

Erläuterung des Verhaltens

Im Beispiel für die Skalierung werden aufgrund des GPU-Kontingentlimits, das Sie in der ClusterQueue-Konfiguration festgelegt haben, nur drei Replikate erstellt, obwohl die Skalierung auf vier festgelegt ist. Im Abschnitt spec.resourceGroups von ClusterQueue wird für nvidia.com/gpu eine Nominalkontingent von „6“ definiert. Im Deployment wird angegeben, dass für jeden Pod „2“ GPUs erforderlich sind. Daher kann die ClusterQueue maximal drei Replikate der Bereitstellung gleichzeitig aufnehmen, da 3 Replikate * 2 GPUs pro Replikate = 6 GPUs entspricht, was dem Gesamtkontingent entspricht.

Wenn Sie versuchen, auf vier Replikat zu skalieren, erkennt Kueue, dass diese Aktion das GPU-Kontingent überschreiten würde, und verhindert die Planung des vierten Replikats. Dies wird durch den SchedulingGated-Status des vierten Pods angezeigt. Dieses Verhalten veranschaulicht die Durchsetzung von Ressourcenkontingenten in Kueue.

Stellen Sie den Trainingsjob bereit.

In diesem Abschnitt stellen Sie einen Job mit niedrigerer Priorität für die Feinabstimmung eines Gemma-2-Modells bereit, für das vier GPUs in zwei Pods erforderlich sind. Ein Jobcontroller in Kubernetes erstellt einen oder mehrere Pods und sorgt dafür, dass sie eine bestimmte Aufgabe erfolgreich ausführen.

Dieser Job verwendet das verbleibende GPU-Kontingent in der ClusterQueue. Der Job verwendet ein vordefiniertes Image und speichert Checkpoints, um einen Neustart von Zwischenergebnissen auszuführen.

Für den Job zur Feinabstimmung wird das Dataset b-mc2/sql-create-context verwendet. Die Quelle für den Abstimmungsjob finden Sie im Repository.

  1. Sehen Sie sich die Datei fine-tune-l4.yaml an. Dieses Manifest definiert den Job für die Feinabstimmung.

    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. Wenden Sie das Manifest an, um den Job für die Feinabstimmung zu erstellen:

    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. Prüfen Sie, ob Ihre Deployments ausgeführt werden. Führen Sie den folgenden Befehl aus, um den Status der Arbeitslastobjekte zu prüfen:

    kubectl get workloads --namespace=llm
    

    Die Ausgabe sollte in etwa so aussehen:

    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
    

    Rufen Sie als Nächstes die Pods im Namespace llm mit dem folgenden Befehl auf:

    kubectl get pod --namespace=llm
    

    Die Ausgabe sollte in etwa so aussehen:

    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
    

    Die Ausgabe zeigt, dass Kueue sowohl die Ausführung Ihres Jobs zur Feinabstimmung als auch die Ausführung der Pods des Inferenzservers zulässt und die entsprechenden Ressourcen basierend auf den angegebenen Kontingentlimits reserviert.

  4. Sehen Sie sich die Ausgabeprotokolle an, um zu prüfen, ob der Job zur Feinabstimmung Checkpoints im Cloud Storage-Bucket speichert. Der Optimierungsjob dauert etwa 10 Minuten, bevor der erste Checkpoint gespeichert wird.

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

    Die Ausgabe für den ersten gespeicherten Checkpoint sieht in etwa so aus:

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

Kueue-Voraktivierung und dynamische Zuordnung für gemischte Arbeitslast testen

In diesem Abschnitt simulieren Sie ein Szenario, in dem die Last des Inferenzservers zunimmt und er skaliert werden muss. In diesem Szenario wird gezeigt, wie Kueue den Inferenzserver mit hoher Priorität priorisiert, indem der Job zur Feinabstimmung mit niedrigerer Priorität bei knappen Ressourcen angehalten und vorzeitig beendet wird.

  1. Führen Sie den folgenden Befehl aus, um die Replikate des Inferenzservers auf zwei zu skalieren:

    kubectl scale --replicas=2 deployment/tgi-gemma-deployment --namespace=llm
    
  2. Prüfen Sie den Status der Arbeitslastobjekte:

    kubectl get workloads --namespace=llm
    

    Die Ausgabe sieht dann ungefähr so aus:

    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
    

    Die Ausgabe zeigt, dass der Job für die Feinabstimmung nicht mehr zugelassen wird, da die zusätzlichen Replikatserver für die Inferenz das verfügbare GPU-Kontingent nutzen.

  3. Prüfen Sie den Status des Jobs zur Feinabstimmung:

    kubectl get job --namespace=llm
    

    Die Ausgabe sieht dann ungefähr so aus:

    NAME                STATUS      COMPLETIONS   DURATION   AGE
    finetune-gemma-l4   Suspended   0/2                      33m
    
  4. Führen Sie den folgenden Befehl aus, um Ihre Pods zu prüfen:

    kubectl get pod --namespace=llm
    

    Die Ausgabe sieht in etwa so aus:

    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. Testen Sie als Nächstes das Szenario, in dem die Last des Inferenzservers sinkt und seine Pods herunterskaliert werden. Führen Sie dazu diesen Befehl aus:

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

    Führen Sie den folgenden Befehl aus, um die Arbeitslastobjekte anzuzeigen:

    kubectl get workloads --namespace=llm
    

    Die Ausgabe sieht dann ungefähr so aus:

    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. Führen Sie diesen Befehl aus, um die Jobs anzuzeigen:

    kubectl get job --namespace=llm
    

    Die Ausgabe sieht in etwa so aus:

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

Bereinigen

Damit Ihrem Google Cloud-Konto die in dieser Anleitung verwendeten Ressourcen nicht in Rechnung gestellt werden, löschen Sie entweder das Projekt, das die Ressourcen enthält, oder Sie behalten das Projekt und löschen die einzelnen Ressourcen.

Bereitgestellte Ressourcen löschen

Führen Sie die folgenden Befehle aus, damit Ihrem Google Cloud -Konto die in diesem Leitfaden erstellten Ressourcen nicht in Rechnung gestellt werden:

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

Nächste Schritte