DeepSeek-R1 671B in GKE mit vLLM mit mehreren Hosts auf A4-VMs bereitstellen

In dieser Anleitung erfahren Sie, wie Sie DeepSeek-R1 (deepseek-ai/DeepSeek-R1), ein Mixture-of-Experts-Modell mit 671 Milliarden Parametern und 37 Milliarden aktiven Parametern, in der Google Kubernetes Engine (GKE) aufGoogle Cloudbereitstellen und bereitstellen. Für die Bereitstellung werden das Cluster Toolkit, zwei Multi-Host-A4-VM-Instanzen (a4-highgpu-8g) mit insgesamt 16 NVIDIA B200-GPUs und das vLLM-Serving-Framework verwendet.

In der in dieser Anleitung beschriebenen Bereitstellungskonfiguration verwenden Sie das Cluster Toolkit, um einen GKE-Cluster im Standardmodus bereitzustellen. Der Cluster hat zwei a4-highgpu-8g-Knoten, die über ein GPUDirect-RDMA-Netzwerk (Remote Direct Memory Access) verbunden sind. Anschließend stellen Sie die Modellgewichtungen in Cloud Storage bereit und verwenden die Kubernetes JobSet API und Ray, um das Modell auf allen 16 NVIDIA B200-GPUs mit Tensorparallelität, Expertenparallelität und einem FP8-KV-Cache bereitzustellen.

Ziele

  • Cluster Toolkit installieren und einen GKE-Cluster mit mehreren Hosts im Standardmodus bereitstellen.
  • Stagen Sie die DeepSeek-R1-Modellgewichtungen in einem Cloud Storage-Bucket.
  • Stellen Sie einen vLLM-Inferenzserver mit mehreren Hosts mit JobSet und Ray auf zwei A4-Knoten mit insgesamt 16 NVIDIA B200-GPUs bereit.
  • Senden Sie eine Chat-Vervollständigungsanfrage an den vLLM-Server.
  • Führen Sie eine Benchmark-Arbeitslast für die Bereitstellung aus.
  • Bereinigen Sie die bereitgestellten Ressourcen.

Kosten

In diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten vonGoogle Cloud:

Mit dem Preisrechner können Sie eine Kostenschätzung Ihrer voraussichtlichen Nutzung vornehmen.

Hinweis

Richten Sie die Umgebung für die Anleitung ein:

  1. Installieren und initialisieren Sie die Google Cloud CLI und installieren Sie kubectl. Wenn Sie Cloud Shell verwenden, sind beide Tools vorinstalliert.
  2. Wenn Sie diese Anleitung auf einer Linux- oder macOS-Workstation statt in Cloud Shell ausführen, installieren Sie die erforderlichen Cluster Toolkit-Abhängigkeiten (z. B. Terraform und Git).
  3. Aktivieren Sie die Kubernetes Engine API, die Compute Engine API, die Cloud Storage API, die Artifact Registry API und die Service Usage API in Ihrem Google Cloud Projekt:

    APIs aktivieren

Hugging Face-Zugriffstoken abrufen

Für diese Anleitung benötigen Sie ein Hugging Face-Zugriffstoken. Sie können sich bei Hugging Face für ein kostenloses Konto registrieren. Nachdem Sie ein Konto erstellt haben, generieren Sie ein Zugriffstoken:

  1. Klicken Sie auf der Seite Willkommen bei Hugging Face auf Ihren Konto-Avatar und wählen Sie Zugriffstokens aus.
  2. Klicken Sie auf der Seite Zugriffstokens auf Neues Token erstellen.
  3. Wählen Sie den Tokentyp Lesen aus und geben Sie einen Namen für das Token ein.
  4. Ihr Zugriffstoken wird angezeigt. Speichern Sie das Token an einem sicheren Ort.

Erforderliche Rollen

Bitten Sie Ihren Administrator, Ihnen die folgenden IAM-Rollen für Ihr Projekt zuzuweisen, um die Berechtigungen zu erhalten, die Sie zum Ausführen dieser Anleitung benötigen:

Weitere Informationen zum Zuweisen von Rollen finden Sie unter Zugriff auf Projekte, Ordner und Organisationen verwalten.

Sie können die erforderlichen Berechtigungen auch über benutzerdefinierte Rollen oder andere vordefinierte Rollen erhalten.

Umgebungsvariablen einrichten

Richten Sie die Authentifizierung und die Umgebungsvariablen für die Anleitung ein:

  1. Richten Sie die Authentifizierung ein:

    gcloud auth login
    gcloud auth application-default login
  2. Exportieren Sie die Umgebungsvariablen, die Sie in dieser Anleitung verwenden:

    export PROJECT_ID="YOUR_PROJECT_ID"
    export REGION="YOUR_REGION"
    export ZONE="YOUR_ZONE"
    export CLUSTER_NAME="YOUR_CLUSTER_NAME"
    export RESERVATION_NAME="YOUR_RESERVATION_NAME"
    export HF_TOKEN="YOUR_HF_TOKEN"
    export MODEL_BUCKET="YOUR_GCS_BUCKET"
    export TERRAFORM_BUCKET="${MODEL_BUCKET}-tf-state"
    export CLUSTER_TOOLKIT_VERSION="v1.104.0"
    
    gcloud config set project "${PROJECT_ID}"
    gcloud config set billing/quota_project "${PROJECT_ID}"

    Ersetzen Sie Folgendes:

    • YOUR_PROJECT_ID: die ID IhresGoogle Cloud -Projekts.
    • YOUR_REGION: die Compute-Region, in der sich Ihre A4-Kapazität befindet und in der Sie Ihren Cluster bereitstellen möchten, z. B. us-central1.
    • YOUR_ZONE: die Compute-Zone, in der sich Ihre A4-Kapazität befindet, z. B. us-central1-b.
    • YOUR_CLUSTER_NAME: der Name für Ihren GKE-Cluster und die Cluster Toolkit-Bereitstellung. Verwenden Sie einen Namen mit maximal 20 Zeichen, damit die generierte Dienstkonto-ID des Knotenpools (YOUR_CLUSTER_NAME-gke-np-sa) innerhalb des IAM-Limits (Identity and Access Management) von 30 Zeichen bleibt.
    • YOUR_RESERVATION_NAME: der Name Ihrer A4-Reservierung oder der Pfad des erweiterten Reservierungsblocks im Format RESERVATION_NAME/reservationBlocks/RESERVATION_BLOCK_NAME.
    • YOUR_HF_TOKEN: Ihr Hugging Face-Zugriffstoken.
    • YOUR_GCS_BUCKET: ein global eindeutiger Name für den Cloud Storage-Bucket, in dem Sie die Modellgewichtungen bereitstellen. Das Script legt außerdem TERRAFORM_BUCKET auf YOUR_GCS_BUCKET-tf-state fest, um den Terraform-Status für das Cluster Toolkit zu speichern.

    Das Script setzt außerdem CLUSTER_TOOLKIT_VERSION auf v1.104.0, wodurch die zu installierende Cluster Toolkit-Version angegeben wird. Cluster Toolkit-Version v1.104.0 und höher enthalten integrierte Optionen für den Kapazitätsverbrauch für examples/gke-a4/gke-a4-deployment.yaml. In Versionen vor v1.104.0 müssen Sie diese Kapazitätsoptionen manuell hinzufügen.

GKE-Cluster erstellen

Laden Sie das Cluster Toolkit herunter und stellen Sie einen GKE-Cluster im Standardmodus mit zwei a4-highgpu-8g-Knoten, GPUDirect RDMA-Netzwerk und dem JobSet-Controller bereit:

  1. Laden Sie das vordefinierte Cluster Toolkit-Bundle für die in CLUSTER_TOOLKIT_VERSION (v1.104.0) angegebene Version herunter und extrahieren Sie es. Wechseln Sie dann in das Verzeichnis cluster-toolkit und prüfen Sie die Binärversion:

    case "$(uname -s)-$(uname -m)" in
      Linux-x86_64)              BUNDLE="gcluster_bundle_linux_amd64.tgz" ;;
      Linux-aarch64|Linux-arm64) BUNDLE="gcluster_bundle_linux_arm64.tgz" ;;
      Darwin-x86_64)             BUNDLE="gcluster_bundle_mac_amd64.tgz" ;;
      Darwin-arm64)              BUNDLE="gcluster_bundle_mac_arm64.tgz" ;;
      *) BUNDLE=""; echo "Unsupported platform: $(uname -s)-$(uname -m)" >&2 ;;
    esac
    mkdir -p cluster-toolkit
    curl -fsSL "https://github.com/GoogleCloudPlatform/cluster-toolkit/releases/download/${CLUSTER_TOOLKIT_VERSION}/${BUNDLE:?}" \
      | tar -xz -C cluster-toolkit
    cd cluster-toolkit
    ./gcluster --version
  2. Optional können Sie sich die verfügbaren Befehle und Flags im Cluster Toolkit ansehen:

    ./gcluster --help
  3. Erstellen Sie den Cloud Storage-Bucket, in dem der Terraform-Status für Ihre Cluster Toolkit-Bereitstellung gespeichert wird:

    gcloud storage buckets create "gs://${TERRAFORM_BUCKET}" \
        --default-storage-class=STANDARD \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
  4. Erstellen Sie die examples/gke-a4/gke-a4-deployment.yaml-Bereitstellungskonfigurationsdatei für zwei a4-highgpu-8g-Knoten:

    cat <<EOF > examples/gke-a4/gke-a4-deployment.yaml
    terraform_backend_defaults:
      type: gcs
      configuration:
        bucket: ${TERRAFORM_BUCKET}
    
    vars:
      project_id: ${PROJECT_ID}
      deployment_name: ${CLUSTER_NAME}
      region: ${REGION}
      zone: ${ZONE}
      static_node_count: 2
      authorized_cidr: 0.0.0.0/0
      reservation_affinity:
        consume_reservation_type: SPECIFIC_RESERVATION
        specific_reservations:
        - name: ${RESERVATION_NAME}
    EOF

    Standardmäßig ist der Blueprint examples/gke-a4/gke-a4.yaml so konfiguriert, dass eine bestimmte Reservierung verwendet wird. Wenn Sie keine bestimmte Reservierung haben, können Sie examples/gke-a4/gke-a4.yaml vor der Bereitstellung des Clusters bearbeiten, um eine der folgenden Verbrauchsoptionen zu verwenden:

    • Spot-Kapazität: Prüfen Sie, ob Ihr Projekt ein ausreichendes Kontingent für vorzeitige Beendigungen für Ihre GPUs, Laufwerke und CPUs hat. Kommentieren Sie am Ende von examples/gke-a4/gke-a4.yaml den Abschnitt --- Option 1: Specific Reservation --- aus und entfernen Sie das Kommentarzeichen für den Abschnitt --- Option 4: Spot ---.
    • Flex-Start-VMs: Prüfen Sie, ob Ihr Projekt ein ausreichendes Kontingent für vorzeitige Beendigungen für Ihre GPUs, Laufwerke und CPUs hat. Kommentieren Sie unten in examples/gke-a4/gke-a4.yaml den Abschnitt --- Option 1: Specific Reservation --- aus und entfernen Sie das Kommentarzeichen im Abschnitt --- Option 2: DWS Flex Start ---.
  5. Stellen Sie den GKE-Cluster mit dem Befehl gcluster deploy bereit:

    ./gcluster deploy -d examples/gke-a4/gke-a4-deployment.yaml examples/gke-a4/gke-a4.yaml \
        --download-dependencies \
        --auto-approve -w

    Die Erstellung eines Clusters dauert etwa 30 bis 45 Minuten. Wenn Sie ./gcluster deploy ohne das Flag --auto-approve ausführen, geben Sie a ein, wenn Sie dazu aufgefordert werden, um die vorgeschlagenen Änderungen anzuwenden. Wenn die Bereitstellung abgeschlossen ist, endet die Ausgabe mit einer Meldung wie dieser:

    Apply complete! Resources: 80 added, 0 changed, 0 destroyed.
    
  6. Konfigurieren Sie kubectl-Anmeldedaten für die Kommunikation mit Ihrem Cluster:

    gcloud container clusters get-credentials "${CLUSTER_NAME}" \
        --project="${PROJECT_ID}" \
        --location="${REGION}"

Modellgewichtungen in Cloud Storage bereitstellen

Laden Sie die DeepSeek-R1-Gewichtungen auf die lokale SSD eines Ihrer A4-Knoten herunter und übertragen Sie sie in einen Cloud Storage-Bucket, um sie schnell über Knoten hinweg zu streamen:

  1. Laden Sie die Kubernetes-Manifestdateien für den Modell-Download-Job, den vLLM-Inferenzserver und die Benchmark-Pods herunter:

    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/gpu/inference/deepseek-r1-gcluster/download_model.yaml
    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/gpu/inference/deepseek-r1-gcluster/deepseek_r1_server.yaml
    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/gpu/inference/deepseek-r1-gcluster/deepseek_r1_8k_1k.yaml
  2. Erstellen Sie den Cloud Storage-Bucket mit einheitlichem Zugriff auf Bucket-Ebene, um die Modellgewichtungen zu speichern:

    gcloud storage buckets create "gs://${MODEL_BUCKET}" \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
  3. Gewähren Sie dem Kubernetes-Standarddienstkonto, dem Compute Engine-Standarddienstkonto und dem Dienstkonto des Clusterknotenpools (${CLUSTER_NAME}-gke-np-sa) IAM-Berechtigungen für den Bucket:

    PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")
    
    gcloud storage buckets add-iam-policy-binding "gs://${MODEL_BUCKET}" \
        --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/default/sa/default" \
        --role="roles/storage.objectAdmin"
    
    gcloud storage buckets add-iam-policy-binding "gs://${MODEL_BUCKET}" \
        --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
        --role="roles/storage.objectAdmin"
    
    gcloud storage buckets add-iam-policy-binding "gs://${MODEL_BUCKET}" \
        --member="serviceAccount:${CLUSTER_NAME}-gke-np-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
        --role="roles/storage.objectAdmin"
    
    # legacyBucketReader is required to load the model with --load-format runai_streamer
    gcloud storage buckets add-iam-policy-binding "gs://${MODEL_BUCKET}" \
        --member="serviceAccount:${CLUSTER_NAME}-gke-np-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
        --role="roles/storage.legacyBucketReader"

    Die durch das vorherige Skript gewährten IAM-Berechtigungen umfassen die Rolle „Storage Legacy Bucket Reader“ (roles/storage.legacyBucketReader), die für --load-format runai_streamer erforderlich ist.

  4. Erstellen Sie ein Kubernetes-Secret mit dem Namen hf-secret, um Ihr Hugging Face-Zugriffstoken zu speichern:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}" \
        --dry-run=client -o yaml | kubectl apply -f -
  5. Prüfen Sie das Manifest download_model.yaml oder erstellen Sie die Datei lokal, wenn Sie sie nicht zuvor heruntergeladen haben:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: download-deepseek-r1
      namespace: default
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: OnFailure
          # Schedule onto an A4 node with Local SSD
          nodeSelector:
            cloud.google.com/gke-accelerator: nvidia-b200
          tolerations:
          - operator: Exists
          containers:
          - name: model-downloader
            image: gcr.io/google.com/cloudsdktool/google-cloud-cli:slim
            env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_api_token
            # Enable high-performance concurrent chunked downloads in huggingface_hub
            - name: HF_XET_HIGH_PERFORMANCE
              value: "1"
            # Place Hugging Face cache on Local SSD so the boot disk never fills up
            - name: HF_HOME
              value: "/local-ssd/hf-cache"
            command: ["/bin/bash", "-c"]
            args:
            - |
              set -euo pipefail
    
              echo "=== 1. Installing huggingface_hub ==="
              pip3 install --break-system-packages -q -U "huggingface_hub"
    
              echo "=== 2. Downloading deepseek-ai/DeepSeek-R1 to Local NVMe SSD ==="
              hf download deepseek-ai/DeepSeek-R1 --local-dir /local-ssd/DeepSeek-R1
              rm -rf /local-ssd/DeepSeek-R1/.cache
    
              echo "=== 3. Uploading from Local SSD to GCS ==="
              gcloud config set storage/parallel_composite_upload_compatibility_check False
              gcloud storage cp -r /local-ssd/DeepSeek-R1 gs://$MODEL_BUCKET/
    
              echo "=== 4. Cleaning up Local SSD ==="
              rm -rf /local-ssd/DeepSeek-R1 /local-ssd/hf-cache
            resources:
              requests:
                cpu: "64"
                memory: "128Gi"
            volumeMounts:
            - name: local-ssd
              mountPath: /local-ssd
          volumes:
          - name: local-ssd
            emptyDir: {}
  6. Ersetzen Sie den Namen Ihres Cloud Storage-Bucket im Manifest download_model.yaml und starten Sie den Job zum Herunterladen des Modells:

    envsubst '$MODEL_BUCKET' < download_model.yaml | kubectl apply -f -
  7. Warten Sie, bis der Download-Pod gestartet wurde, und streamen Sie dann die Joblogs:

    kubectl wait --for=condition=Ready pod -l job-name=download-deepseek-r1 --timeout=120s
    kubectl logs -f job/download-deepseek-r1

    Es dauert etwa 30 bis 40 Minuten, das Modell auf die lokale NVMe-SSD des Knotens herunterzuladen und die Gewichtungen in Ihren Cloud Storage-Bucket hochzuladen.

  8. Prüfen Sie nach Abschluss des Jobs, ob alle 163 .safetensors-Shards in Ihrem Cloud Storage-Bucket vorhanden sind:

    gcloud storage ls "gs://${MODEL_BUCKET}/DeepSeek-R1/*.safetensors" | wc -l
  9. Löschen Sie den abgeschlossenen Staging-Job:

    kubectl delete job download-deepseek-r1

vLLM-Server mit mehreren Hosts bereitstellen

Das Arbeitslastmanifest (deepseek_r1_server.yaml) definiert einen ClusterIP-Dienst (vllm-r1-leader) und ein Kubernetes-JobSet (vllm-r1), die die Bereitstellung auf mehreren Hosts über die beiden a4-highgpu-8g-Knoten koordinieren:

  • Leader (leader): Führt ein Replikat auf einem A4-Knoten mit acht NVIDIA B200-GPUs aus, bezieht die NCCL/gIB-Umgebung (/usr/local/gib/scripts/set_nccl_env.sh), startet den Ray-Head-Knoten auf Port 6379 für einen Cluster mit zwei Knoten und startet vllm serve auf HTTP-Port 8080. Der Server streamt Gewichtungen direkt aus gs://$MODEL_BUCKET/DeepSeek-R1 mithilfe von --load-format runai_streamer und lädt den Tokenizer aus dem bereitgestellten Cloud Storage FUSE-Volume unter /models/DeepSeek-R1. Es wird mit --tensor-parallel-size 16, --pipeline-parallel-size 1, --distributed-executor-backend ray, --enable-expert-parallel, --kv-cache-dtype fp8_e4m3, --async-scheduling und CUDA-Grafiken (FULL_AND_PIECEWISE) ausgeführt.
  • Worker (worker): Führt ein headless Replikat auf dem zweiten A4-Knoten mit acht NVIDIA B200-GPUs aus, konfiguriert NCCL/gIB und tritt dem Ray-Cluster des Leaders unter vllm-r1-leader:6379 bei.

Stellen Sie den vLLM-Server mit mehreren Hosts bereit und überwachen Sie ihn:

  1. Prüfen Sie das Manifest deepseek_r1_server.yaml oder erstellen Sie die Datei lokal, wenn Sie sie nicht zuvor heruntergeladen haben:

    apiVersion: v1
    kind: Service
    metadata:
      name: vllm-r1-leader
      namespace: default
      labels:
        app: vllm-r1
    spec:
      ports:
      - name: http
        port: 8080
        targetPort: 8080
      - name: ray-head
        port: 6379
        targetPort: 6379
      selector:
        app: vllm-r1
        role: leader
      type: ClusterIP
    ---
    apiVersion: jobset.x-k8s.io/v1alpha2
    kind: JobSet
    metadata:
      name: vllm-r1
      namespace: default
      labels:
        app: vllm-r1
    spec:
      failurePolicy:
        maxRestarts: 3
        restartStrategy: Recreate
      network:
        enableDNSHostnames: true
        publishNotReadyAddresses: true
      replicatedJobs:
      # -------------------- LEADER --------------------
      - name: leader
        replicas: 1
        template:
          spec:
            parallelism: 1
            completions: 1
            backoffLimit: 1
            template:
              metadata:
                labels:
                  app: vllm-r1
                  role: leader
                annotations:
                  gke-gcsfuse/volumes: "true"
              spec:
                hostNetwork: true
                dnsPolicy: ClusterFirstWithHostNet
                hostIPC: true
                nodeSelector:
                  cloud.google.com/gke-accelerator: nvidia-b200
                tolerations:
                - key: nvidia.com/gpu
                  operator: Exists
                - key: cloud.google.com/impending-node-termination
                  operator: Exists
                - effect: NoSchedule
                  key: cloud.google.com/gke-queued
                  value: "true"
                containers:
                - name: vllm-leader
                  image: docker.io/vllm/vllm-openai@sha256:61fc8a896b0a4fbbbdc063bc4b0dbc25ce98e02b5050c24aeb7830ac02039b14
                  imagePullPolicy: IfNotPresent
                  securityContext:
                    privileged: true
                  env:
                  - name: PYTHONUNBUFFERED
                    value: "1"
                  - name: NCCL_DEBUG
                    value: "INFO"
                  - name: VLLM_ENGINE_READY_TIMEOUT_S
                    value: "1800"
                  - name: VLLM_RPC_TIMEOUT
                    value: "1800000"
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                  command: ["/bin/bash", "-c"]
                  args:
                  - |
                    set -e
                    echo "precedence ::ffff:0:0/96  100" >> /etc/gai.conf 2>/dev/null || true
                    ulimit -l unlimited || true
                    export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH:-}
                    source /usr/local/gib/scripts/set_nccl_env.sh
                    export GLOO_SOCKET_IFNAME=eth0
                    export NCCL_SOCKET_IFNAME=eth0
                    export VLLM_HOST_IP=$(hostname -I | awk '{print $1}')
                    echo "=== Starting Optimized vLLM Server with Ray (Leader, rank 0) ==="
                    echo "Host IP: ${VLLM_HOST_IP}"
                    # 0. Install Ray with Compiled Graph support (not pre-installed in vLLM 0.28.0 image)
                    uv pip install --system "ray[cgraph]"
                    # 1. Start Ray head on port 6379 and wait until all 2 nodes (16 GPUs) join
                    bash /vllm-workspace/examples/ray_serving/multi-node-serving.sh leader \
                      --ray_cluster_size=2 \
                      --ray_port=6379 \
                      --node-ip-address="${VLLM_HOST_IP}"
                    # 2. Launch vLLM on the Leader using the Ray cluster
                    exec vllm serve \
                      --host 0.0.0.0 \
                      --port 8080 \
                      --model gs://$MODEL_BUCKET/DeepSeek-R1 \
                      --tokenizer /models/DeepSeek-R1 \
                      --load-format runai_streamer \
                      --model-loader-extra-config '{"distributed": true, "concurrency": 16, "memory_limit": 34359738368}' \
                      --served-model-name deepseek-ai/DeepSeek-R1 \
                      --tensor-parallel-size 16 \
                      --pipeline-parallel-size 1 \
                      --distributed-executor-backend ray \
                      --async-scheduling \
                      --disable-custom-all-reduce \
                      --kv-cache-dtype fp8_e4m3 \
                      --block-size 128 \
                      --enable-expert-parallel \
                      --max-num-batched-tokens 8192 \
                      --max-num-seqs 160 \
                      --compilation-config '{"mode": 3, "cudagraph_mode":"FULL_AND_PIECEWISE", "custom_ops":["none"], "pass_config":{"fuse_allreduce_rms":false}}' \
                      --trust-remote-code \
                      --max-model-len 16384 \
                      --gpu-memory-utilization 0.90
                  resources:
                    requests:
                      nvidia.com/gpu: "8"
                    limits:
                      nvidia.com/gpu: "8"
                  ports:
                  - containerPort: 8080
                    name: http
                  - containerPort: 6379
                    name: ray-head
                  volumeMounts:
                  - mountPath: /dev/shm
                    name: dshm
                  - mountPath: /models
                    name: model-volume
                    readOnly: true
                  - mountPath: /usr/local/gib
                    name: gib
                  - mountPath: /usr/local/nvidia/lib64
                    name: nvidia-lib64
                volumes:
                - name: dshm
                  emptyDir:
                    medium: Memory
                    sizeLimit: 64Gi
                - name: model-volume
                  csi:
                    driver: gcsfuse.csi.storage.gke.io
                    volumeAttributes:
                      bucketName: $MODEL_BUCKET
                      mountOptions: implicit-dirs
                - name: gib
                  hostPath:
                    path: /home/kubernetes/bin/gib
                - name: nvidia-lib64
                  hostPath:
                    path: /home/kubernetes/bin/nvidia/lib64
      # -------------------- WORKER --------------------
      - name: worker
        replicas: 1
        template:
          spec:
            parallelism: 1
            completions: 1
            backoffLimit: 1
            template:
              metadata:
                labels:
                  app: vllm-r1
                  role: worker
                annotations:
                  gke-gcsfuse/volumes: "true"
              spec:
                hostNetwork: true
                dnsPolicy: ClusterFirstWithHostNet
                hostIPC: true
                nodeSelector:
                  cloud.google.com/gke-accelerator: nvidia-b200
                tolerations:
                - key: nvidia.com/gpu
                  operator: Exists
                - key: cloud.google.com/impending-node-termination
                  operator: Exists
                - effect: NoSchedule
                  key: cloud.google.com/gke-queued
                  value: "true"
                containers:
                - name: vllm-worker
                  image: docker.io/vllm/vllm-openai@sha256:61fc8a896b0a4fbbbdc063bc4b0dbc25ce98e02b5050c24aeb7830ac02039b14
                  imagePullPolicy: IfNotPresent
                  securityContext:
                    privileged: true
                  env:
                  - name: PYTHONUNBUFFERED
                    value: "1"
                  - name: NCCL_DEBUG
                    value: "INFO"
                  - name: VLLM_ENGINE_READY_TIMEOUT_S
                    value: "1800"
                  - name: VLLM_RPC_TIMEOUT
                    value: "1800000"
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                  command: ["/bin/bash", "-c"]
                  args:
                  - |
                    set -e
                    echo "precedence ::ffff:0:0/96  100" >> /etc/gai.conf 2>/dev/null || true
                    ulimit -l unlimited || true
                    export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH:-}
                    source /usr/local/gib/scripts/set_nccl_env.sh
                    export GLOO_SOCKET_IFNAME=eth0
                    export NCCL_SOCKET_IFNAME=eth0
                    export VLLM_HOST_IP=$(hostname -I | awk '{print $1}')
                    echo "=== Starting Optimized vLLM Ray Worker (rank 1) ==="
                    echo "Host IP: ${VLLM_HOST_IP}"
                    # 0. Install Ray with Compiled Graph support (not pre-installed in vLLM 0.28.0 image)
                    uv pip install --system "ray[cgraph]"
                    # Join the Leader's Ray cluster and block so the container stays alive
                    exec bash /vllm-workspace/examples/ray_serving/multi-node-serving.sh worker \
                      --ray_address=vllm-r1-leader \
                      --ray_port=6379 \
                      --node-ip-address="${VLLM_HOST_IP}"
                  resources:
                    requests:
                      nvidia.com/gpu: "8"
                    limits:
                      nvidia.com/gpu: "8"
                  ports:
                  - containerPort: 8080
                    name: http
                  volumeMounts:
                  - mountPath: /dev/shm
                    name: dshm
                  - mountPath: /models
                    name: model-volume
                    readOnly: true
                  - mountPath: /usr/local/gib
                    name: gib
                  - mountPath: /usr/local/nvidia/lib64
                    name: nvidia-lib64
                volumes:
                - name: dshm
                  emptyDir:
                    medium: Memory
                    sizeLimit: 64Gi
                - name: model-volume
                  csi:
                    driver: gcsfuse.csi.storage.gke.io
                    volumeAttributes:
                      bucketName: $MODEL_BUCKET
                      mountOptions: implicit-dirs
                - name: gib
                  hostPath:
                    path: /home/kubernetes/bin/gib
                - name: nvidia-lib64
                  hostPath:
                    path: /home/kubernetes/bin/nvidia/lib64

    Wenn Sie dieses Deployment auf mehr Hosts skalieren möchten, aktualisieren Sie die Parameter in deepseek_r1_server.yaml. Wenn Sie beispielsweise auf vier A4-Knoten mit insgesamt 32 NVIDIA B200-GPUs skalieren möchten, legen Sie replicas: 3 unter dem replizierten Job worker fest und aktualisieren Sie die Leader-Argumente so:

    • --ray_cluster_size=4
    • --tensor-parallel-size=16
    • --pipeline-parallel-size=2
  2. Ersetzen Sie deepseek_r1_server.yaml durch den Namen Ihres Cloud Storage-Bucket und wenden Sie das Manifest auf Ihren Cluster an:

    envsubst '$MODEL_BUCKET' < deepseek_r1_server.yaml | kubectl apply -f -
  3. Überwachen Sie die Logs des Leader- und Worker-Pods, bis der Serverstart abgeschlossen ist:

    # monitor leader
    kubectl logs -l jobset.sigs.k8s.io/replicatedjob-name=leader -c vllm-leader -f --tail=-1
    # monitor worker
    kubectl logs -l jobset.sigs.k8s.io/replicatedjob-name=worker -c vllm-worker -f --tail=-1

    Da kubectl logs -f Logs im Vordergrund streamt, führen Sie den Befehl für die Worker-Logs in einer separaten Terminalsitzung aus, wenn Sie beide Pods gleichzeitig überwachen möchten. Wenn Sie das Streamen von Logs beenden möchten, drücken Sie Strg + C.

    Das anfängliche Gewichtungsstreaming, das Laden des Modells und die CUDA-Grafikerfassung dauern etwa 15 bis 20 Minuten. Warten Sie, bis die Logs des Leader-Pods die folgenden Meldungen ausgeben, bevor Sie Anfragen senden:

    (APIServer pid=1) INFO:     Started server process [1]
    (APIServer pid=1) INFO:     Waiting for application startup.
    (APIServer pid=1) INFO:     Application startup complete.
    

Inferenzanfragen senden

Senden Sie eine Chat-Vervollständigungsanfrage an den bereitgestellten vLLM-Server:

  1. Leiten Sie den lokalen Port 8080 im Hintergrund an den Dienst vllm-r1-leader weiter:

    kubectl port-forward service/vllm-r1-leader 8080:8080 &
  2. Senden Sie eine Anfrage zur Chat-Vervollständigung an den Endpunkt /v1/chat/completions:

    curl http://localhost:8080/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{
            "model": "deepseek-ai/DeepSeek-R1",
            "messages": [
                {"role": "user", "content": "Explain tensor parallelism in one sentence."}
            ],
            "max_tokens": 100,
            "temperature": 0.6
        }'

Benchmark-Arbeitslasten ausführen

Führen Sie einen Benchmark für den bereitgestellten DeepSeek-R1-Server mit vllm bench serve und einer Konfiguration von 8.192 Eingabetokens und 1.024 Ausgabetokens (8k/1k) durch:

  1. Prüfen Sie das Manifest deepseek_r1_8k_1k.yaml oder erstellen Sie die Datei lokal, wenn Sie sie nicht zuvor heruntergeladen haben:

    apiVersion: v1
    kind: Pod
    metadata:
      name: vllm-bench-8k-1k
      namespace: default
      labels:
        app: vllm-bench
      annotations:
        gke-gcsfuse/volumes: "true"
    spec:
      restartPolicy: Never
      tolerations:
      - operator: Exists
      containers:
      - name: benchmark
        image: docker.io/vllm/vllm-openai@sha256:61fc8a896b0a4fbbbdc063bc4b0dbc25ce98e02b5050c24aeb7830ac02039b14
        imagePullPolicy: IfNotPresent
        env:
        - name: PYTHONUNBUFFERED
          value: "1"
        - name: HF_TOKEN
          valueFrom:
            secretKeyRef:
              name: hf-secret
              key: hf_api_token
        - name: HUGGING_FACE_HUB_TOKEN
          valueFrom:
            secretKeyRef:
              name: hf-secret
              key: hf_api_token
        command: ["/bin/bash", "-c"]
        args:
        - |
          set -e
          echo "=== Benchmark: Input 8k (8192) / Output 1k (1024) [1280 Prompts, Concurrency 112] ==="
          vllm bench serve \
            --backend openai \
            --base-url http://vllm-r1-leader:8080 \
            --model deepseek-ai/DeepSeek-R1 \
            --tokenizer /models/DeepSeek-R1 \
            --dataset-name random \
            --random-input-len 8192 \
            --random-output-len 1024 \
            --random-range-ratio 0.0 \
            --num-prompts 1280 \
            --max-concurrency 112 \
            --request-rate inf \
            --ready-check-timeout-sec 0 \
            --trust-remote-code
        resources:
          requests:
            cpu: "8"
            memory: 16Gi
        volumeMounts:
        - mountPath: /models
          name: model-volume
          readOnly: true
      volumes:
      - name: model-volume
        csi:
          driver: gcsfuse.csi.storage.gke.io
          volumeAttributes:
            bucketName: $MODEL_BUCKET
            mountOptions: implicit-dirs

    Das Manifest gibt 8.192 Eingabetokens, 1.024 Ausgabetokens, 1.280 Prompts und eine maximale Nebenläufigkeit von 112 an.

  2. Wenden Sie das deepseek_r1_8k_1k.yaml-Manifest an:

    envsubst '$MODEL_BUCKET' < deepseek_r1_8k_1k.yaml | kubectl apply -f -
  3. Warten Sie, bis der Pod vllm-bench-8k-1k gestartet wurde, und streamen Sie dann die Benchmark-Logs:

    kubectl wait --for=condition=Ready pod/vllm-bench-8k-1k --timeout=120s
    kubectl logs -f vllm-bench-8k-1k -c benchmark

    Der Benchmark dauert etwa 15 Minuten. Suchen Sie nach Abschluss des Benchmarks nach einer Ausgabe, die Messwerte wie die folgenden enthält:

    • Output token throughput
    • Total token throughput
    • TTFT
    • TPOT

Bereinigen

So vermeiden Sie, dass Ihrem Google Cloud -Konto die in dieser Anleitung verwendeten Ressourcen laufend in Rechnung gestellt werden: Löschen Sie die Kubernetes-Arbeitslasten, den GKE-Cluster und Ihre Cloud Storage-Buckets:

  1. Löschen Sie die Benchmark-Pods, das JobSet und den Dienst des vLLM-Servers, den Job zum Herunterladen des Modells und das Hugging Face-Secret:

    envsubst '$MODEL_BUCKET' < deepseek_r1_8k_1k.yaml | kubectl delete -f - --ignore-not-found || true
    envsubst '$MODEL_BUCKET' < deepseek_r1_server.yaml | kubectl delete -f - --ignore-not-found || true
    kubectl delete job download-deepseek-r1 --ignore-not-found || true
    kubectl wait --for=delete pod -l app=vllm-r1 --timeout=120s || true
    kubectl delete secret hf-secret --ignore-not-found || true
  2. Löschen Sie den Cloud Storage-Bucket, in dem die bereitgestellten Modellgewichtungen gespeichert sind:

    gcloud storage rm --recursive "gs://${MODEL_BUCKET}"
  3. Löschen Sie im Verzeichnis cluster-toolkit den GKE-Cluster und die zugehörige Infrastruktur mit dem Befehl gcluster destroy:

    ./gcluster destroy "${CLUSTER_NAME}" --robust --auto-approve
  4. Löschen Sie den Cloud Storage-Bucket, in dem der Terraform-Status gespeichert ist:

    gcloud storage rm --recursive "gs://${TERRAFORM_BUCKET}"

Nächste Schritte