استقرار استنتاج چندمیزبانی TPU vLLM با Ray روی GKE

۱. مقدمه

در این آزمایشگاه کد، شما یاد خواهید گرفت که چگونه سرویس‌های استنتاج vLLM (مدل زبان بزرگ مجازی) چند میزبانه با کارایی بالا را روی موتور گوگل کوبرنتیز (GKE) با استفاده از TPUهای گوگل کلود پیاده‌سازی کنید. شما استنتاج توزیع‌شده را با استفاده از Ray پیکربندی خواهید کرد و حجم کار را به صورت بومی روی GKE با استفاده از LeaderWorkerSets مدیریت خواهید کرد.

این راهنما، یک محیط تولید برای ارائه مدل‌های بزرگ مانند Qwen 30B را شبیه‌سازی می‌کند.

کاری که انجام خواهید داد

  • یک شبکه VPC سفارشی برای ترافیک شتاب‌دهنده ایجاد کنید.
  • یک کلاستر GKE به همراه Ray Operator و درایور GCS Fuse CSI تهیه کنید.
  • برای بارگذاری سریع‌تر مدل، یک GCS Rapid Cache راه‌اندازی کنید.
  • یک مخزن گره TPU v6e چند میزبانه با ظرفیت رزرو شده فراهم کنید.
  • پیکربندی هویت بار کاری برای دسترسی ایمن به وزن‌های مدل.
  • موتور vLLM را که یک مدل پارامتر 30B را ارائه می‌دهد، مستقر و آزمایش کنید.

آنچه نیاز دارید

  • یک پروژه گوگل کلود با قابلیت پرداخت.
  • رزرو فضای ابری گوگل برای منابع TPU v6e (32 تراشه، ct6e-standard-4t ).
  • دسترسی به کپی کردن وزن‌های مدل از یک سطل منبع.
  • Cloud Shell یا یک ترمینال محلی با gcloud ، kubectl و helm نصب شده.
  • مدت زمان تخمینی: ۶۰ دقیقه
  • هزینه تخمینی: کمتر از ۶۰ دلار (با فرض اینکه کالبدشکافی به سرعت انجام شود).

۲. قبل از شروع

یک پروژه Google Cloud ایجاد یا انتخاب کنید

  1. در کنسول گوگل کلود ، یک پروژه گوگل کلود انتخاب یا ایجاد کنید.
  2. مطمئن شوید که پرداخت برای پروژه ابری شما فعال است.

شروع پوسته ابری

  1. روی فعال کردن Cloud Shell در بالای کنسول Google Cloud کلیک کنید.
  2. تأیید اعتبار:
gcloud auth list
  1. پروژه خود را تایید کنید:
gcloud config get project
  1. در صورت نیاز آن را تنظیم کنید:
export PROJECT_ID=<YOUR_PROJECT_ID>
gcloud config set project $PROJECT_ID

تنظیم متغیرهای محیطی

برای آسان‌تر کردن اجرای دستورات، متغیرهای زیر را در پوسته خود تعریف کنید. <YOUR_ZONE> را با منطقه TPU اختصاص داده شده خود و <YOUR_RESERVATION_NAME> را با شناسه رزرو خود جایگزین کنید. برای دانلود وزن‌های مدل دروازه‌دار، باید یک توکن دسترسی کاربر Hugging Face ایجاد کنید. پس از ایجاد آن، <YOUR_HUGGING_FACE_TOKEN> را با توکن تازه ایجاد شده خود جایگزین کنید.

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export ZONE="<YOUR_ZONE>" # e.g., us-east5-a
export REGION=${ZONE%-*}
export CLUSTER_NAME="qwen-serving-cluster"
export GVNIC_NETWORK_PREFIX="qwen-serving"
export BUCKET_NAME="inf-demo-model-storage-${PROJECT_NUMBER}"
export RESERVATION_NAME="<YOUR_RESERVATION_NAME>"
export NODE_POOL_NAME="tpu-v6e-32-resvd-pool"
export MULTIHOST_COLLECTION_NAME="tpu-6-collection"
export HF_TOKEN="<YOUR_HUGGING_FACE_TOKEN>" # Token with access to Qwen model if restricted

فعال کردن APIها

سرویس‌های مورد نیاز Google Cloud را فعال کنید:

gcloud services enable \
    container.googleapis.com \
    compute.googleapis.com \
    iam.googleapis.com \
    cloudresourcemanager.googleapis.com

۳. ایجاد شبکه‌سازی سفارشی

بارهای کاری TPU چند میزبانه به پیکربندی‌های شبکه خاصی نیاز دارند، از جمله اندازه‌های MTU بالاتر برای ارتباط شتاب‌دهنده کارآمد. یک شبکه VPC سفارشی برای کلاستر خود ایجاد کنید.

  1. شبکه VPC را با MTU بزرگ (8896) ایجاد کنید :
    gcloud compute --project=${PROJECT_ID} \
        networks create ${GVNIC_NETWORK_PREFIX}-main \
        --subnet-mode=custom \
        --mtu=8896
    
  2. زیرشبکه را برای کلاستر ایجاد کنید :
    gcloud compute --project=${PROJECT_ID} \
        networks subnets create ${GVNIC_NETWORK_PREFIX}-tpu \
        --network=${GVNIC_NETWORK_PREFIX}-main \
        --region=${REGION} \
        --range=192.168.100.0/24
    
  3. ایجاد قوانین فایروال که به ترافیک داخلی اجازه می‌دهد تا کارگران بتوانند ارتباط برقرار کنند:
    gcloud compute --project=${PROJECT_ID} firewall-rules create ${GVNIC_NETWORK_PREFIX}-allow-internal \
        --network=${GVNIC_NETWORK_PREFIX}-main \
        --allow=all \
        --source-ranges=172.16.0.0/12,192.168.0.0/16,10.0.0.0/8 \
        --description="Allow all internal traffic within the network."
    

۴. خوشه GKE تأمین

یک تنظیمات کلاستر استاندارد GKE ایجاد کنید که برای پشتیبانی از GCS Fuse mounts و بارهای کاری Ray Operator پیکربندی شده باشد.

  1. خوشه را ایجاد کنید :
    gcloud container clusters create ${CLUSTER_NAME} \
        --project=${PROJECT_ID} \
        --location=${REGION} \
        --release-channel=rapid \
        --machine-type=e2-standard-4 \
        --network=${GVNIC_NETWORK_PREFIX}-main \
        --subnetwork=${GVNIC_NETWORK_PREFIX}-tpu \
        --num-nodes=1 \
        --gateway-api=standard \
        --enable-managed-prometheus \
        --enable-dataplane-v2 \
        --enable-dataplane-v2-metrics \
        --workload-pool=${PROJECT_ID}.svc.id.goog \
        --addons=GcsFuseCsiDriver,RayOperator \
        --enable-ip-alias
    
  2. بازیابی اعتبارنامه‌های کلاستر :
    gcloud container clusters get-credentials ${CLUSTER_NAME} --region=${REGION}
    
  3. ایجاد راز چهره در آغوش گرفته : توکن خود را به طور ایمن برای دانلودهای دسترسی به کانتینر ذخیره کنید:
    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token=${HF_TOKEN} \
        --dry-run=client -o yaml | kubectl apply -f -
    
  4. LeaderWorkerSet (LWS) را از طریق Helm نصب کنید . LWS گروه‌هایی از podها را که باید با هم زمان‌بندی شوند، مدیریت می‌کند:
    helm install lws oci://registry.k8s.io/lws/charts/lws \
        --version=0.7.0 \
        --namespace lws-system \
        --create-namespace \
        --wait
    

۵. فعال کردن حافظه پنهان سریع GCS

برای سرعت بخشیدن به خواندن ده‌ها گیگابایت وزن از فضای ذخیره‌سازی ابری در حین سرویس‌دهی، یک سطل GCS ایجاد کنید و GCS Rapid Cache را در منطقه خود فعال کنید.

  1. سطل را ایجاد کنید :
    gcloud storage buckets create gs://$BUCKET_NAME \
        --location=$REGION \
        --uniform-bucket-level-access
    
  2. مقداردهی اولیه Rapid Cache در ناحیه TPU:
    gcloud storage buckets anywhere-caches create gs://$BUCKET_NAME $ZONE \
        --ttl=1d \
        --admission-policy=ADMIT_ON_FIRST_MISS
    

۶. تنظیم هویت بار کاری و مجوزهای ذخیره‌سازی

پیوندهای هویت را پیکربندی کنید تا سطل وزنه را به طور ایمن در غلاف‌های GKE خود نصب کنید بدون اینکه کلیدهای بادوام تعبیه کنید.

  1. یک حساب کاربری اختصاصی برای سرویس IAM ایجاد کنید :
    gcloud iam service-accounts create tpu-reader-sa
    
  2. مجوزهای خواندن باکت را اعطا کنید :
    gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
        --member="serviceAccount:tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
        --role="roles/storage.objectAdmin"
    
  3. ایجاد اتصال هویت بار کاری برای فضای نام default حساب سرویس Kubernetes:
    gcloud iam service-accounts add-iam-policy-binding tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com \
        --role="roles/iam.workloadIdentityUser" \
        --member="serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]"
    
  4. Kubernetes SA را حاشیه‌نویسی کنید :
    kubectl annotate serviceaccount default iam.gke.io/gcp-service-account=tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com
    

۷. تنظیمات وزن‌های مدل

برای ارائه یک مدل پارامتر 30B، باید وزن‌ها را از Hugging Face در سطل GCS خود دانلود کنید. برای دور زدن محدودیت سهمیه دیسک Cloud Shell (5 گیگابایت)، از یک کار استاندارد Kubernetes برای دانلود مستقیم درون خوشه و نوشتن ایمن در حجم GCS Fuse نصب شده استفاده کنید.

  1. استقرار کار دانلود مدل : برای شروع دانلود، فایل مانیفست زیر را ایجاد و اعمال کنید:
    cat <<EOF | kubectl apply -f -
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: model-downloader
    spec:
      ttlSecondsAfterFinished: 60
      template:
        metadata:
          annotations:
            gke-gcsfuse/volumes: "true"
            gke-gcsfuse/memory-limit: "0"
        spec:
          serviceAccountName: default
          restartPolicy: OnFailure
          containers:
          - name: downloader
            image: python:3.10-slim
            command: ["/bin/sh", "-c"]
            args:
            - |
              pip install -U "huggingface_hub[hf_transfer]" filelock
              export HF_HUB_ENABLE_HF_TRANSFER=1
    
              python -c '
              import filelock
    
              class DummyLock:
                  def __init__(self, *args, **kwargs): pass
                  def __enter__(self): return self
                  def __exit__(self, *args): pass
                  def acquire(self, *args, **kwargs): pass
                  def release(self, *args, **kwargs): pass
    
              filelock.FileLock = DummyLock
    
              from huggingface_hub import snapshot_download
              snapshot_download(
                  repo_id="Qwen/Qwen3-30B-A3B", 
                  local_dir="/models/qwen3-30b-weights",
                  local_dir_use_symlinks=False
              )
              '
            env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_api_token
            volumeMounts:
            - name: model-weights
              mountPath: /models
          volumes:
          - name: model-weights
            csi:
              driver: gcsfuse.csi.storage.gke.io
              volumeAttributes:
                bucketName: ${BUCKET_NAME}
                mountOptions: "implicit-dirs"
    EOF
    
  2. نظارت بر دانلود : برای پیگیری پیشرفت، لاگ‌های مربوط به دانلودر پاد را بررسی کنید:
    kubectl logs -f job/model-downloader
    
    صبر کنید تا کار با وضعیت موفقیت‌آمیز به پایان برسد.

۸. ایجاد یک TPU Node Pool رزرو شده

با استفاده از رزرو ظرفیت موجود، قطعه TPU چند میزبانه واقعی را فراهم کنید.

  1. دستور ایجاد را اجرا کنید :
    gcloud beta container node-pools create ${NODE_POOL_NAME} \
        --project=${PROJECT_ID} \
        --cluster=${CLUSTER_NAME} \
        --region=${REGION} \
        --node-locations=${ZONE} \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=4x8 \
        --num-nodes=8 \
        --scopes=https://www.googleapis.com/auth/cloud-platform \
        --reservation-affinity=specific \
        --reservation=${RESERVATION_NAME} \
        --accelerator-network-profile=auto \
        --node-labels=cloud.google.com/gke-nodepool-group-name=${MULTIHOST_COLLECTION_NAME} \
        --node-labels=cloud.google.com/gke-workload-type=HIGH_AVAILABILITY \
        --node-labels=cloud.google.com/gke-networking-dra-driver=true
    
  2. منتظر بمانید تا گره‌ها به هم بپیوندند : می‌توانید مقیاس‌بندی تجمیع گره‌ها را مستقیماً مشاهده کنید. منتظر بمانید تا ۸ گره حاوی ct6e به kubectl get nodes بپیوندند.

۹. استقرار سرویس vLLM

  1. ایجاد درخواست‌های شبکه : شما باید محیط شبکه را درخواست کنید:
    cat <<EOF | kubectl apply -f -
    apiVersion: resource.k8s.io/v1
    kind: ResourceClaimTemplate
    metadata:
      name: all-netdev
    spec:
      spec:
        devices:
          requests:
          - name: req-netdev
            exactly:
              deviceClassName: netdev.google.com
              allocationMode: All
    EOF
    
  2. نقطه پایانی API متعادل کننده بار را مستقر کنید :
    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: vllm-tpu-service
    spec:
      type: LoadBalancer
      selector:
        leaderworkerset.sigs.k8s.io/name: vllm-tpu-qwen
        leaderworkerset.sigs.k8s.io/worker-index: "0"
      ports:
      - protocol: TCP
        port: 8000
        targetPort: 8000
    EOF
    
  3. استقرار بار کاری LeaderWorkerSet : این مانیفست، تجمیع سر/کارگر Ray را به صورت پویا در 8 میزبان برش آغاز می‌کند.
    cat <<EOF | kubectl apply -f -
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    metadata:
      name: vllm-tpu-qwen
    spec:
      replicas: 1
      leaderWorkerTemplate:
        size: 8
        restartPolicy: RecreateGroupOnPodRestart
        workerTemplate:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
              gke-gcsfuse/memory-limit: "0"
            labels:
              leaderworkerset.sigs.k8s.io/name: vllm-tpu-qwen
              gke-gcsfuse/volumes: "true"
          spec:
            hostname: vllm-tpu-qwen
            serviceAccountName: default
            containers:
            - name: vllm-tpu
              image: vllm/vllm-tpu:nightly
              command: ["sh", "-c"]
              args:
              - |
                MY_TPU_IP=\$(hostname -I | awk '{print \$1}')
                echo "My TPU Network IP is: \$MY_TPU_IP"
    
                LEADER_DNS="vllm-tpu-qwen-0.vllm-tpu-qwen"
                until getent hosts \$LEADER_DNS; do
                  echo "DNS not ready. Sleeping 5s..."
                  sleep 5
                  done
                LEADER_IP=\$(getent hosts \$LEADER_DNS | awk '{print \$1}')
    
                export JAX_PLATFORMS=''
                export SCAN_TPU_CHIPS=True
                export TPU_MULTIHOST_BACKEND=ray
                export JAX_DISTRIBUTED_INITIALIZATION_TIMEOUT=300
                export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:/usr/local/lib
                export VLLM_HOST_IP=\$MY_TPU_IP
    
                if [ "\$LWS_WORKER_INDEX" = "0" ]; then
                  echo "Starting Ray Head..."
                  ray start --head --port=6379 --node-ip-address=\$MY_TPU_IP --resources='{"TPU": 4}' --block &
                  sleep 20
                  until ray status; do sleep 5; done
    
                  echo "Starting vLLM API Server..."
                  python3 -m vllm.entrypoints.openai.api_server \
                    --model=/models/qwen3-30b-weights \
                    --tensor-parallel-size=32 \
                    --pipeline-parallel-size=1 \
                    --distributed-executor-backend=ray \
                    --host=0.0.0.0 --port=8000 \
                    --enforce-eager \
                    --gpu-memory-utilization=0.90
                else
                  ray start --address=\${LEADER_IP}:6379 --node-ip-address=\$MY_TPU_IP --resources='{"TPU": 4}' --block
                fi
              ports:
              - containerPort: 8000
              - containerPort: 6379
              volumeMounts:
              - name: model-weights
                mountPath: /models
                readOnly: true
              - name: dshm
                mountPath: /dev/shm
              resources:
                claims:
                - name: net-resources
                limits:
                  google.com/tpu: 4
                  memory: "100Gi"
                requests:
                  google.com/tpu: 4
                  memory: "100Gi"
            nodeSelector:
              cloud.google.com/gke-tpu-accelerator: tpu-v6e-slice
              cloud.google.com/gke-tpu-topology: 4x8
              gke.networks.io/accelerator-network-profile: auto
            resourceClaims:
            - name: net-resources
              resourceClaimTemplateName: all-netdev
            volumes:
            - name: model-weights
              csi:
                driver: gcsfuse.csi.storage.gke.io
                volumeAttributes:
                  bucketName: ${BUCKET_NAME}
                  mountOptions: "implicit-dirs"
            - name: dshm
              emptyDir:
                medium: Memory
    EOF
    

۱۰. تست پاسخ استقرار

ممکن است ۵ تا ۱۰ دقیقه طول بکشد تا تمام پادهای موجود در LeaderWorkerSet تصاویر کانتینر را دریافت کنند، Ray را مقداردهی اولیه کنند و کاملاً Ready شوند. می‌توانید وضعیت را با مشاهده مقداردهی اولیه پاد پیگیری کنید:

kubectl get pods -l leaderworkerset.sigs.k8s.io/name=vllm-tpu-qwen -w

صبر کنید تا هر ۸ vllm-tpu-qwen- pods STATUS به صورت Running و READY به صورت 2/2 نشان دهند، و قبل از ادامه، مطمئن شوید که متعادل‌کننده بار، یک IP خارجی دریافت کرده است. این کار می‌تواند ۷ تا ۱۰ دقیقه طول بکشد.

  1. بازیابی آی‌پی خارجی :
    export EXTERNAL_IP=$(kubectl get svc vllm-tpu-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    echo $EXTERNAL_IP
    

احتیاط: در یک سرویس در حال تولید، این نقطه پایانی باید با چیزی مانند پروکسی آگاه از هویت (IAP) ایمن شود.

  1. ارسال درخواست استنتاج با استفاده از curl :
        curl -N -s http://$EXTERNAL_IP:8000/v1/chat/completions \
            -H "Content-Type: application/json" \
            -d '{
                "model": "/models/qwen3-30b-weights",
                "messages": [{"role": "user", "content": "Write a haiku about high-performance computing on TPUs."}],
                "temperature": 0.7,
                "max_tokens": 100,
                "stream": true
            }' | sed 's/^data: //' | grep -v '\[DONE\]' | grep -v '^$' | jq -rj '.choices[0].delta.content // empty' ; echo ""
    
    شما باید خروجی شبیه به یک پاسخ JSON حاوی متنی که نشان‌دهنده‌ی استنتاج تولید شده‌ی شماست را ببینید!

۱۱. تمیز کردن

برای جلوگیری از هزینه‌های مداوم برای حساب Google Cloud خود، منابع ایجاد شده در طول این codelab را حذف کنید.

  1. حذف استخر گره :
    gcloud container node-pools delete "${NODE_POOL_NAME}" \
        --cluster="${CLUSTER_NAME}" \
        --region="${REGION}" \
        --project="${PROJECT_ID}" --quiet
    
  2. حذف خوشه :
    gcloud container clusters delete "${CLUSTER_NAME}" \
        --region="${REGION}" \
        --project="${PROJECT_ID}" --quiet
    
  3. تنظیمات شبکه و فایروال را حذف کنید :
    gcloud compute firewall-rules delete \
        "${GVNIC_NETWORK_PREFIX}-allow-internal" \
        --project="${PROJECT_ID}" --quiet
    
    gcloud compute networks subnets delete "${GVNIC_NETWORK_PREFIX}-tpu" \
        --region="${REGION}" --quiet
    
    gcloud compute networks delete "${GVNIC_NETWORK_PREFIX}-main" --quiet
    
  4. لغو اتصال و حذف حساب سرویس :
        # 1. Create the cleanup script
        cat << 'EOF' > clean_up_sa.sh
        #!/bin/bash
    
        # Validate that PROJECT_ID is available
        if [ -z "$PROJECT_ID" ]; then
          echo "Error: PROJECT_ID environment variable is not set."
          exit 1
        fi
    
        SA_EMAIL="tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com"
        SA_MEMBER="serviceAccount:${SA_EMAIL}"
    
        echo "Gathering IAM policy for ${SA_EMAIL}..."
    
        # Fetch roles assigned to this specific SA
        ROLES=$(gcloud projects get-iam-policy ${PROJECT_ID} \
            --flatten="bindings[].members" \
            --filter="bindings.members:${SA_MEMBER}" \
            --format="value(bindings.role)")
    
        if [ -z "$ROLES" ]; then
            echo "No IAM bindings found for this service account."
        else
            for ROLE in $ROLES; do
                echo "Removing binding for: ${ROLE}..."
                gcloud projects remove-iam-policy-binding ${PROJECT_ID} \
                    --member="${SA_MEMBER}" \
                    --role="${ROLE}" --quiet > /dev/null
            done
            echo "Successfully unbound all roles."
        fi
    
        # 2. Delete the service account itself
        echo "Deleting service account..."
        gcloud iam service-accounts delete ${SA_EMAIL} --project=${PROJECT_ID} --quiet
    
        echo "Cleanup complete."
        EOF
    
        # 2. Make the script executable and run it
        chmod +x clean_up_sa.sh
        ./clean_up_sa.sh
    
  5. حذف سطل GCS به کنسول ابری خود بروید، Cloud Storage -> Buckets را انتخاب کنید، inf-demo-model-storage را انتخاب کنید و سپس «حذف» را انتخاب کنید.

۱۲. تبریک

تبریک! شما با موفقیت یک پشته vLLM با نرخ استنتاج بالا و چند میزبانه TPU را با استفاده از Ray به صورت بومی بر روی موتور Google Kubernetes مستقر کردید.

آنچه آموخته‌اید

  • تأمین مسیرهای سفارشی متناسب با ترافیک پرسرعت TPU.
  • نصب وزنه‌ها با استفاده از فیوز GCS و حافظه‌های ذخیره‌سازی سریع منطقه‌ای.
  • هماهنگ‌سازی برش‌های بار کاری چند میزبانه که به صورت بومی از طریق LeaderWorkerSets همگام‌سازی شده‌اند.
  • برای کسب اطلاعات بیشتر به راهنمای کاربر vLLM و راهنماهای استقرار llm-d مراجعه کنید.