Menyajikan DeepSeek-R1 671B di GKE dengan vLLM multi-host di VM A4

Tutorial ini menunjukkan cara men-deploy dan menyajikan DeepSeek-R1 (deepseek-ai/DeepSeek-R1), model Mixture-of-Experts dengan 671 miliar parameter dan 37 miliar parameter aktif, di Google Kubernetes Engine (GKE) di Google Cloud. Deployment ini menggunakan Cluster Toolkit, dua instance virtual machine (VM) multi-host A4 (a4-highgpu-8g) dengan total 16 GPU NVIDIA B200, dan framework inferensi vLLM.

Dalam konfigurasi deployment yang dijelaskan dalam tutorial ini, Anda akan menggunakan Cluster Toolkit untuk menyediakan cluster GKE dalam mode Standard. Cluster ini memiliki dua node a4-highgpu-8g yang terhubung melalui jaringan GPUDirect Remote Direct Memory Access (RDMA). Kemudian, Anda akan melakukan staging bobot model di Cloud Storage dan menggunakan Kubernetes JobSet API dan Ray untuk menyajikan model di 16 GPU NVIDIA B200 dengan paralelisme tensor, paralelisme pakar, dan cache KV FP8.

Tujuan

  • Instal Cluster Toolkit dan sediakan cluster GKE A4 multi-host dalam mode Standard.
  • Tempatkan bobot model DeepSeek-R1 di bucket Cloud Storage.
  • Men-deploy server inferensi vLLM multi-host dengan JobSet dan Ray di dua node A4, dengan total 16 GPU NVIDIA B200.
  • Kirim permintaan penyelesaian chat ke server vLLM.
  • Menjalankan workload benchmark inferensi.
  • Bersihkan resource yang di-deploy.

Biaya

Dalam dokumen ini, Anda akan menggunakan komponenGoogle Cloudyang dapat ditagih berikut:

Untuk membuat perkiraan biaya berdasarkan proyeksi penggunaan Anda, gunakan kalkulator harga.

Sebelum memulai

Siapkan lingkungan Anda untuk tutorial:

  1. Instal dan lakukan inisialisasi Google Cloud CLI, dan instal kubectl. Jika Anda menggunakan Cloud Shell, kedua alat tersebut sudah diinstal sebelumnya.
  2. Jika Anda menjalankan tutorial ini dari workstation Linux atau macOS, bukan Cloud Shell, instal dependensi Cluster Toolkit yang diperlukan (seperti Terraform dan Git).
  3. Aktifkan Kubernetes Engine API, Compute Engine API, Cloud Storage API, Artifact Registry API, dan Service Usage API di project Google Cloud Anda:

    Aktifkan API

Mendapatkan token akses Hugging Face

Anda memerlukan token akses Hugging Face untuk menggunakan tutorial ini. Anda dapat mendaftar untuk mendapatkan akun gratis di Hugging Face. Setelah Anda memiliki akun, buat token akses:

  1. Di halaman Welcome to Hugging Face, klik avatar akun Anda, lalu pilih Token akses.
  2. Di halaman Token akses, klik Create new token.
  3. Pilih jenis token Read dan masukkan nama untuk token Anda.
  4. Token akses Anda akan ditampilkan. Simpan token di tempat yang aman.

Peran yang diperlukan

Untuk mendapatkan izin yang Anda perlukan untuk menyelesaikan tutorial ini, minta administrator untuk memberi Anda peran IAM berikut di project Anda:

Untuk mengetahui informasi selengkapnya tentang pemberian peran, lihat Mengelola akses ke project, folder, dan organisasi.

Anda mungkin juga bisa mendapatkan izin yang diperlukan melalui peran khusus atau peran bawaan lainnya.

Menyiapkan variabel lingkungan Anda

Siapkan autentikasi dan variabel lingkungan Anda untuk tutorial ini:

  1. Siapkan autentikasi Anda:

    gcloud auth login
    gcloud auth application-default login
  2. Ekspor variabel lingkungan yang Anda gunakan di sepanjang tutorial ini:

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

    Ganti kode berikut:

    • YOUR_PROJECT_ID: ID project Google Cloud Anda.
    • YOUR_REGION: region komputasi tempat kapasitas A4 Anda berada dan tempat Anda ingin men-deploy cluster, seperti us-central1.
    • YOUR_ZONE: zona komputasi tempat kapasitas A4 Anda berada, seperti us-central1-b.
    • YOUR_CLUSTER_NAME: nama untuk cluster GKE dan deployment Cluster Toolkit Anda. Gunakan nama yang terdiri dari 20 karakter atau kurang agar ID akun layanan node pool yang dihasilkan (YOUR_CLUSTER_NAME-gke-np-sa) tetap berada dalam batas 30 karakter Identity and Access Management (IAM).
    • YOUR_RESERVATION_NAME: nama pemesanan A4 Anda, atau jalur blok pemesanan yang diperluas dalam format RESERVATION_NAME/reservationBlocks/RESERVATION_BLOCK_NAME.
    • YOUR_HF_TOKEN: token akses Hugging Face Anda.
    • YOUR_GCS_BUCKET: nama unik global untuk bucket Cloud Storage tempat Anda melakukan staging bobot model. Skrip ini juga menetapkan TERRAFORM_BUCKET ke YOUR_GCS_BUCKET-tf-state guna menyimpan status Terraform untuk Cluster Toolkit.

    Skrip ini juga menetapkan CLUSTER_TOOLKIT_VERSION ke v1.104.0, yang menentukan versi Cluster Toolkit yang akan diinstal. Cluster Toolkit versi v1.104.0 dan yang lebih baru menyertakan opsi konsumsi kapasitas bawaan untuk examples/gke-a4/gke-a4-deployment.yaml. Pada versi sebelum v1.104.0, Anda harus menambahkan opsi kapasitas ini secara manual.

Membuat cluster GKE

Download Cluster Toolkit dan sediakan cluster GKE dalam mode Standard dengan dua node a4-highgpu-8g, jaringan GPUDirect RDMA, dan pengontrol JobSet:

  1. Download dan ekstrak paket Cluster Toolkit bawaan untuk versi yang ditentukan di CLUSTER_TOOLKIT_VERSION (v1.104.0), ubah direktori kerja Anda ke direktori cluster-toolkit, dan verifikasi versi biner:

    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. Atau, lihat perintah dan flag yang tersedia di Cluster Toolkit:

    ./gcluster --help
  3. Buat bucket Cloud Storage yang menyimpan status Terraform untuk deployment Cluster Toolkit Anda:

    gcloud storage buckets create "gs://${TERRAFORM_BUCKET}" \
        --default-storage-class=STANDARD \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
  4. Buat file konfigurasi deployment examples/gke-a4/gke-a4-deployment.yaml untuk dua node a4-highgpu-8g:

    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

    Secara default, blueprint examples/gke-a4/gke-a4.yaml dikonfigurasi untuk menggunakan reservasi tertentu. Jika tidak memiliki pemesanan tertentu, Anda dapat mengedit examples/gke-a4/gke-a4.yaml sebelum men-deploy cluster untuk menggunakan salah satu opsi penggunaan berikut:

    • Kapasitas spot: pastikan project Anda memiliki kuota preemptible yang cukup untuk GPU, disk, dan CPU Anda. Di bagian bawah examples/gke-a4/gke-a4.yaml, jadikan bagian --- Option 1: Specific Reservation --- sebagai komentar dan hapus tanda komentar di bagian --- Option 4: Spot ---.
    • Flex-start VM: pastikan project Anda memiliki kuota preemptible yang memadai untuk GPU, disk, dan CPU Anda. Di bagian bawah examples/gke-a4/gke-a4.yaml, jadikan bagian --- Option 1: Specific Reservation --- sebagai komentar dan hapus tanda komentar pada bagian --- Option 2: DWS Flex Start ---.
  5. Deploy cluster GKE menggunakan perintah gcluster deploy:

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

    Pembuatan cluster memerlukan waktu sekitar 30 hingga 45 menit. Jika Anda menjalankan ./gcluster deploy tanpa flag --auto-approve, untuk menerapkan perubahan yang diusulkan, masukkan a saat diminta. Saat deployment selesai, output akan diakhiri dengan pesan yang mirip dengan berikut ini:

    Apply complete! Resources: 80 added, 0 changed, 0 destroyed.
    
  6. Konfigurasi kredensial kubectl untuk berkomunikasi dengan cluster Anda:

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

Menyiapkan bobot model di Cloud Storage

Download bobot DeepSeek-R1 ke SSD Lokal salah satu node A4 Anda dan transfer ke bucket Cloud Storage untuk streaming cepat di seluruh node:

  1. Download file manifes Kubernetes untuk Tugas download model, server inferensi vLLM, dan Pod benchmark:

    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. Buat bucket Cloud Storage dengan akses level bucket yang seragam untuk menyimpan bobot model:

    gcloud storage buckets create "gs://${MODEL_BUCKET}" \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
  3. Berikan izin IAM pada bucket ke akun layanan Kubernetes default, akun layanan Compute Engine default, dan akun layanan node pool cluster (${CLUSTER_NAME}-gke-np-sa):

    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"

    Izin IAM yang diberikan oleh skrip sebelumnya mencakup peran Storage Legacy Bucket Reader (roles/storage.legacyBucketReader), yang diperlukan untuk --load-format runai_streamer.

  4. Buat Secret Kubernetes bernama hf-secret untuk menyimpan token akses Hugging Face Anda:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}" \
        --dry-run=client -o yaml | kubectl apply -f -
  5. Tinjau manifes download_model.yaml, atau buat file secara lokal jika Anda belum mendownloadnya sebelumnya:

    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. Ganti nama bucket Cloud Storage Anda ke dalam manifes download_model.yaml dan mulai Tugas download model:

    envsubst '$MODEL_BUCKET' < download_model.yaml | kubectl apply -f -
  7. Tunggu hingga Pod download dimulai, lalu streaming log Tugas:

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

    Perlu waktu sekitar 30 hingga 40 menit untuk mendownload model ke SSD NVMe Lokal node dan mengupload bobot ke bucket Cloud Storage Anda.

  8. Setelah Tugas selesai, pastikan semua 163 shard .safetensors ada di bucket Cloud Storage Anda:

    gcloud storage ls "gs://${MODEL_BUCKET}/DeepSeek-R1/*.safetensors" | wc -l
  9. Hapus Tugas staging yang telah selesai:

    kubectl delete job download-deepseek-r1

Men-deploy server vLLM multi-host

Manifes workload (deepseek_r1_server.yaml) menentukan Service ClusterIP (vllm-r1-leader) dan JobSet Kubernetes (vllm-r1) yang mengoordinasikan penyajian multi-host di dua node a4-highgpu-8g:

  • Leader (leader): menjalankan satu replika pada node A4 dengan delapan GPU NVIDIA B200, mengambil lingkungan NCCL/gIB (/usr/local/gib/scripts/set_nccl_env.sh), memulai node head Ray pada port 6379 untuk cluster dua node, dan meluncurkan vllm serve pada port HTTP 8080. Server menstreaming bobot langsung dari gs://$MODEL_BUCKET/DeepSeek-R1 menggunakan --load-format runai_streamer dan memuat tokenizer dari volume Cloud Storage FUSE yang terpasang di /models/DeepSeek-R1. berjalan dengan --tensor-parallel-size 16, --pipeline-parallel-size 1, --distributed-executor-backend ray, --enable-expert-parallel, --kv-cache-dtype fp8_e4m3, --async-scheduling, dan grafik CUDA (FULL_AND_PIECEWISE).
  • Worker (worker): menjalankan satu replika headless di node A4 kedua dengan delapan GPU NVIDIA B200, mengonfigurasi NCCL/gIB, dan bergabung dengan cluster Ray leader di vllm-r1-leader:6379.

Deploy dan pantau server vLLM multi-host:

  1. Tinjau manifes deepseek_r1_server.yaml, atau buat file secara lokal jika Anda belum mendownloadnya sebelumnya:

    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

    Untuk menskalakan deployment ini ke lebih banyak host, perbarui parameter di deepseek_r1_server.yaml. Misalnya, untuk menskalakan ke empat node A4, dengan total 32 GPU NVIDIA B200, tetapkan replicas: 3 di bagian Tugas yang direplikasi worker, dan perbarui argumen leader sebagai berikut:

    • --ray_cluster_size=4
    • --tensor-parallel-size=16
    • --pipeline-parallel-size=2
  2. Ganti nama bucket Cloud Storage Anda menjadi deepseek_r1_server.yaml dan terapkan manifes ke cluster Anda:

    envsubst '$MODEL_BUCKET' < deepseek_r1_server.yaml | kubectl apply -f -
  3. Pantau log Pod leader dan worker hingga startup server selesai:

    # 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

    Karena kubectl logs -f melakukan streaming log di latar depan, jalankan perintah log worker di sesi terminal terpisah jika Anda ingin memantau kedua Pod secara bersamaan. Untuk berhenti melakukan streaming log kapan saja, tekan Control+C.

    Streaming bobot awal, pemuatan model, dan pengambilan grafik CUDA memerlukan waktu sekitar 15 hingga 20 menit. Sebelum Anda mengirim permintaan, tunggu hingga log Pod leader menghasilkan pesan berikut:

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

Mengirim permintaan inferensi

Kirim permintaan penyelesaian chat ke server vLLM yang di-deploy:

  1. Teruskan port lokal 8080 ke Layanan vllm-r1-leader di latar belakang:

    kubectl port-forward service/vllm-r1-leader 8080:8080 &
  2. Kirim permintaan penyelesaian chat ke endpoint /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
        }'

Menjalankan workload tolok ukur

Lakukan tolok ukur server DeepSeek-R1 yang di-deploy menggunakan vllm bench serve dengan konfigurasi 8.192 token input dan 1.024 token output (8k/1k):

  1. Tinjau manifes deepseek_r1_8k_1k.yaml, atau buat file secara lokal jika Anda belum mendownloadnya sebelumnya:

    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

    Manifes menentukan 8.192 token input, 1.024 token output, 1.280 perintah, dan konkurensi maksimum 112.

  2. Terapkan manifes deepseek_r1_8k_1k.yaml:

    envsubst '$MODEL_BUCKET' < deepseek_r1_8k_1k.yaml | kubectl apply -f -
  3. Tunggu hingga Pod vllm-bench-8k-1k dimulai, lalu lakukan streaming log benchmark:

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

    Benchmark memerlukan waktu sekitar 15 menit untuk diselesaikan. Setelah benchmark selesai, cari output yang berisi metrik seperti berikut:

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

Pembersihan

Agar tidak menimbulkan biaya berkelanjutan tambahan pada akun Google Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus workload Kubernetes, cluster GKE, dan bucket Cloud Storage Anda:

  1. Hapus Pod benchmark, JobSet dan Service server vLLM, Tugas download model, dan Secret Hugging Face:

    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. Hapus bucket Cloud Storage yang menyimpan bobot model yang di-stage:

    gcloud storage rm --recursive "gs://${MODEL_BUCKET}"
  3. Dari direktori cluster-toolkit, hancurkan cluster GKE dan infrastruktur terkaitnya menggunakan perintah gcluster destroy:

    ./gcluster destroy "${CLUSTER_NAME}" --robust --auto-approve
  4. Hapus bucket Cloud Storage yang menyimpan status Terraform:

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

Langkah berikutnya