在 GKE 上使用 A4 VM 的多主機 vLLM 提供 DeepSeek-R1 671B

本教學課程說明如何在Google Cloud的 Google Kubernetes Engine (GKE) 上部署及提供 DeepSeek-R1 (deepseek-ai/DeepSeek-R1),這是一種混合專家模型,具有 6,710 億個參數,其中 370 億個為有效參數。部署作業會使用 Cluster Toolkit、兩個多主機 A4 (a4-highgpu-8g) 虛擬機器 (VM) 執行個體 (共 16 個 NVIDIA B200 GPU),以及 vLLM 服務架構。

在本教學課程所述的部署設定中,您會使用 Cluster Toolkit,以標準模式佈建 GKE 叢集。叢集有兩個 a4-highgpu-8g 節點,透過 GPUDirect 遠端直接記憶體存取 (RDMA) 網路連線。接著,您會將模型權重暫存至 Cloud Storage,並使用 Kubernetes JobSet API 和 Ray,透過張量平行處理、專家平行處理和 FP8 KV 快取,在所有 16 個 NVIDIA B200 GPU 上提供模型。

目標

  • 安裝 Cluster Toolkit,並以標準模式佈建多主機 A4 GKE 叢集。
  • 將 DeepSeek-R1 模型權重暫存至 Cloud Storage bucket。
  • 使用 JobSet 和 Ray,在兩個 A4 節點部署多主機 vLLM 推論伺服器,總共使用 16 個 NVIDIA B200 GPU。
  • 向 vLLM 伺服器傳送對話完成要求。
  • 執行基準測試工作負載。
  • 清除已部署的資源。

費用

在本文件中,您會使用下列Google Cloud計費元件:

您可以使用 Pricing Calculator 根據預測用量估算費用。

事前準備

為教學課程設定環境:

  1. 安裝並初始化 Google Cloud CLI,然後安裝 kubectl。如果使用 Cloud Shell,這兩項工具都已預先安裝。
  2. 如果您是透過 Linux 或 macOS 工作站 (而非 Cloud Shell) 執行本教學課程,請安裝必要的 Cluster Toolkit 依附元件 (例如 Terraform 和 Git)。
  3. 在 Google Cloud 專案中啟用 Kubernetes Engine API、Compute Engine API、Cloud Storage API、Artifact Registry API 和 Service Usage API:

    啟用 API

取得 Hugging Face 存取權杖

您需要 Hugging Face 存取權杖,才能使用本教學課程。您可以在 Hugging Face 註冊免費帳戶。擁有帳戶後,請產生存取權杖:

  1. 在「Welcome to Hugging Face」頁面,點選帳戶顯示圖片,然後選取「存取權杖」。
  2. 在「存取權杖」頁面,按一下「建立新權杖」。
  3. 選取「Read」權杖類型,並輸入權杖名稱。
  4. 系統會顯示存取權杖。將權杖儲存在安全的地方。

必要的角色

如要取得完成本教學課程所需的權限,請要求管理員在專案中授予您下列 IAM 角色:

如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

設定環境變數

設定教學課程的驗證和環境變數:

  1. 設定驗證機制:

    gcloud auth login
    gcloud auth application-default login
  2. 匯出本教學課程中使用的環境變數:

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

    更改下列內容:

    • YOUR_PROJECT_ID:Google Cloud 專案的 ID。
    • YOUR_REGION:A4 容量所在的運算區域,以及要部署叢集的區域,例如 us-central1。
    • YOUR_ZONE:A4 容量所在的運算可用區,例如 us-central1-b。
    • YOUR_CLUSTER_NAME:GKE 叢集和 Cluster Toolkit 部署作業的名稱。請使用 20 個字元以內的名稱,確保產生的節點集區服務帳戶 ID (YOUR_CLUSTER_NAME-gke-np-sa) 不會超過 Identity and Access Management (IAM) 的 30 個字元限制。
    • YOUR_RESERVATION_NAME:A4 預留項目的名稱,或格式為 RESERVATION_NAME/reservationBlocks/RESERVATION_BLOCK_NAME 的擴充預留區塊路徑。
    • YOUR_HF_TOKEN:您的 Hugging Face 存取權杖。
    • YOUR_GCS_BUCKET:Cloud Storage bucket 的全域不重複名稱,用於暫存模型權重。指令碼也會將 TERRAFORM_BUCKET 設為 YOUR_GCS_BUCKET-tf-state,以儲存 Cluster Toolkit 的 Terraform 狀態。

    指令碼也會將 CLUSTER_TOOLKIT_VERSION 設為 v1.104.0,指定要安裝的 Cluster Toolkit 版本。Cluster Toolkit v1.104.0 以上版本內建 examples/gke-a4/gke-a4-deployment.yaml 的容量消耗選項。在 v1.104.0 之前的版本中,您必須手動新增這些容量選項。

建立 GKE 叢集

下載 Cluster Toolkit,並以 Standard 模式佈建 GKE 叢集,其中包含兩個 a4-highgpu-8g 節點、GPUDirect RDMA 網路和 JobSet 控制器:

  1. 下載並解壓縮 CLUSTER_TOOLKIT_VERSION (v1.104.0) 中指定版本的預先建構 Cluster Toolkit 套件,將目前使用的目錄變更為 cluster-toolkit 目錄,然後驗證二進位檔版本:

    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. (選用) 查看 Cluster Toolkit 中的可用指令和旗標:

    ./gcluster --help
  3. 建立 Cloud Storage bucket,用於儲存 Cluster Toolkit 部署作業的 Terraform 狀態:

    gcloud storage buckets create "gs://${TERRAFORM_BUCKET}" \
        --default-storage-class=STANDARD \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
  4. 為兩個 a4-highgpu-8g 節點建立 examples/gke-a4/gke-a4-deployment.yaml 部署設定檔:

    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

    根據預設,examples/gke-a4/gke-a4.yaml 藍圖會設為使用特定預留項目。如果沒有特定預留項目,可以在部署叢集前編輯 examples/gke-a4/gke-a4.yaml,使用下列其中一種消耗選項:

    • Spot 運算資源:確認專案有足夠的 GPU、磁碟和 CPU 先占配額。在 examples/gke-a4/gke-a4.yaml 底部,將 --- Option 1: Specific Reservation --- 部分註解排除,並取消註解 --- Option 4: Spot --- 部分。
    • 彈性啟動型 VM:確認專案有足夠的 GPU、磁碟和 CPU 先占配額。在 examples/gke-a4/gke-a4.yaml 底部,將 --- Option 1: Specific Reservation --- 部分註解排除,並將 --- Option 2: DWS Flex Start --- 部分取消註解。
  5. 使用 gcluster deploy 指令部署 GKE 叢集:

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

    叢集建立作業約需 30 到 45 分鐘。如果您執行 ./gcluster deploy 時未使用 --auto-approve 旗標,請在系統提示時輸入 a,套用建議的變更。部署作業完成後,輸出內容結尾會顯示類似以下的訊息:

    Apply complete! Resources: 80 added, 0 changed, 0 destroyed.
    
  6. 設定 kubectl 憑證,以便與叢集通訊:

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

將模型權重暫存至 Cloud Storage

將 DeepSeek-R1 權重下載至其中一個 A4 節點的本機 SSD,並轉移至 Cloud Storage bucket,以便在節點間快速串流:

  1. 下載 Kubernetes 資訊清單檔案,用於模型下載工作、vLLM 推論伺服器和基準 Pod:

    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. 建立 Cloud Storage bucket,並啟用統一 bucket 層級存取權,用於儲存模型權重:

    gcloud storage buckets create "gs://${MODEL_BUCKET}" \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
  3. 將 bucket 的 IAM 權限授予預設 Kubernetes 服務帳戶、預設 Compute Engine 服務帳戶,以及叢集節點集區服務帳戶 (${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"

    上述指令碼授予的 IAM 權限包括 Storage 舊版 bucket 讀取者角色 (roles/storage.legacyBucketReader),這是 --load-format runai_streamer 所需的角色。

  4. 建立名為 hf-secret 的 Kubernetes Secret,用於儲存 Hugging Face 存取權杖:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}" \
        --dry-run=client -o yaml | kubectl apply -f -
  5. 查看 download_model.yaml 資訊清單,如果先前未下載,請在本機建立檔案:

    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. 將 Cloud Storage bucket 名稱代入 download_model.yaml 資訊清單,然後啟動模型下載工作:

    envsubst '$MODEL_BUCKET' < download_model.yaml | kubectl apply -f -
  7. 等待下載 Pod 啟動,然後串流工作記錄:

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

    將模型下載至節點的本機 NVMe SSD,並將權重上傳至 Cloud Storage bucket,大約需要 30 到 40 分鐘。

  8. 工作完成後,請確認 Cloud Storage bucket 中有全部 163 個 .safetensors shard:

    gcloud storage ls "gs://${MODEL_BUCKET}/DeepSeek-R1/*.safetensors" | wc -l
  9. 刪除已完成的暫存工作:

    kubectl delete job download-deepseek-r1

部署多主機 vLLM 伺服器

工作負載資訊清單 (deepseek_r1_server.yaml) 定義了 ClusterIP Service (vllm-r1-leader) 和 Kubernetes JobSet (vllm-r1),用於協調兩個 a4-highgpu-8g 節點的多主機服務:

  • Leader (leader):在 A4 節點上執行一個副本,該節點配備八個 NVIDIA B200 GPU、NCCL/gIB 環境 (/usr/local/gib/scripts/set_nccl_env.sh) 來源、在通訊埠 6379 上啟動 Ray head 節點,用於雙節點叢集,並在 HTTP 通訊埠 8080 上啟動 vllm serve。伺服器會使用 --load-format runai_streamer 直接從 gs://$MODEL_BUCKET/DeepSeek-R1 串流權重,並從掛接的 Cloud Storage FUSE 磁碟區 /models/DeepSeek-R1 載入 tokenizer。支援 --tensor-parallel-size 16、--pipeline-parallel-size 1、--distributed-executor-backend ray、--enable-expert-parallel、--kv-cache-dtype fp8_e4m3、--async-scheduling 和 CUDA 圖表 (FULL_AND_PIECEWISE)。
  • Worker (worker):在第二個 A4 節點上執行一個無頭副本,該節點配備八個 NVIDIA B200 GPU、設定 NCCL/gIB,並在 vllm-r1-leader:6379 加入 Leader 的 Ray 叢集。

部署及監控多主機 vLLM 伺服器:

  1. 查看 deepseek_r1_server.yaml 資訊清單,如果先前未下載,請在本機建立檔案:

    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

    如要將這項部署作業擴充至更多主機,請更新 deepseek_r1_server.yaml 中的參數。舉例來說,如要擴充至四個 A4 節點,總共 32 個 NVIDIA B200 GPU,請在複製的工作下方設定 replicas: 3,並按照下列方式更新 leader 引數:worker

    • --ray_cluster_size=4
    • --tensor-parallel-size=16
    • --pipeline-parallel-size=2
  2. 將 Cloud Storage bucket 名稱替換為 deepseek_r1_server.yaml,並將資訊清單套用至叢集:

    envsubst '$MODEL_BUCKET' < deepseek_r1_server.yaml | kubectl apply -f -
  3. 監控 leader 和 worker Pod 記錄,直到伺服器啟動完成:

    # 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

    由於 kubectl logs -f 會在前台串流記錄,如要同時監控兩個 Pod,請在獨立的終端機工作階段中執行 worker 記錄指令。如要隨時停止串流記錄,請按下 Control+C。

    初始權重串流、模型載入和 CUDA 圖表擷取作業大約需要 15 到 20 分鐘。請等到 Leader Pod 記錄輸出下列訊息,再傳送要求:

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

傳送推論要求

傳送對話完成要求至已部署的 vLLM 伺服器:

  1. 在背景將本機通訊埠 8080 轉送至 vllm-r1-leader 服務:

    kubectl port-forward service/vllm-r1-leader 8080:8080 &
  2. 傳送對話完成要求至 /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
        }'

執行基準工作負載

使用 vllm bench serve 基準測試已部署的 DeepSeek-R1 伺服器,設定為 8,192 個輸入詞元和 1,024 個輸出詞元 (8k/1k):

  1. 查看 deepseek_r1_8k_1k.yaml 資訊清單,如果先前未下載,請在本機建立檔案:

    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

    資訊清單指定 8,192 個輸入詞元、1,024 個輸出詞元、1,280 個提示,並行數量上限為 112。

  2. 套用 deepseek_r1_8k_1k.yaml 資訊清單:

    envsubst '$MODEL_BUCKET' < deepseek_r1_8k_1k.yaml | kubectl apply -f -
  3. 等待 vllm-bench-8k-1k Pod 啟動,然後串流基準測試記錄:

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

    基準測試約需 15 分鐘才能完成。基準測試完成後,請尋找包含下列指標的輸出內容:

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

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收取本教學課程所用資源的額外費用,請刪除 Kubernetes 工作負載、GKE 叢集和 Cloud Storage bucket:

  1. 刪除基準 Pod、vLLM 伺服器 JobSet 和 Service、模型下載 Job,以及 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. 刪除儲存暫存模型權重的 Cloud Storage bucket:

    gcloud storage rm --recursive "gs://${MODEL_BUCKET}"
  3. 使用 gcluster destroy 指令,從 cluster-toolkit 目錄刪除 GKE 叢集及其相關聯的基礎架構:

    ./gcluster destroy "${CLUSTER_NAME}" --robust --auto-approve
  4. 刪除儲存 Terraform 狀態的 Cloud Storage bucket:

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

後續步驟