הצגת Qwen3-Coder-480B ב-GKE עם SGLang TorchTPU בכמה מארחים במאיצי TPU7x

במדריך הזה נסביר איך לפרוס את Qwen3-Coder-480B (Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8), מודל תכנות מסוג שילוב מומחים (MoE), ולהגיש אותו ב-Google Kubernetes Engine ‏(GKE). הפריסה משתמשת במאיצי TPU7x (Ironwood) עם כמה מארחים ובמסגרת להצגת מודעות SGLang עם TorchTPU (torchtpu) ופירוק של prefill-decode (PD).

בתצורת הפריסה שמתוארת במדריך הזה, אתם מקצים שני מאגרי צמתים של tpu7x-standard-4t עם כמה מארחים, שכל אחד מהם מסודר בטופולוגיה של 2x2x2 (שמונה שבבי TPU7x בשני צמתים לכל מאגר צמתים, סה"כ 16 שבבי TPU7x). אתם יכולים להשתמש ב-Kubernetes LeaderWorkerSet API כדי:

  • הפעלת שרתי מילוי מראש ופענוח מרובי-מארחים מפורקים בפרוסות TPU7x נפרדות, עם הפעלה של מקביליות טנזור ומקביליות מומחים.
  • ניתוב בקשות שתואמות ל-OpenAI דרך נתב SGLang disaggregation.

מטרות

  • מגדירים את אשכול ה-GKE ואת מאגרי הצמתים של TPU7x עם כמה מארחים.
  • מגדירים את פרטי הכניסה ומתקינים את הבקר של LeaderWorkerSet.
  • פורסים את עומסי העבודה של SGLang למילוי מראש, לפענוח ולניתוב בכמה מארחים.
  • שולחים בקשת הסקה לשירות הנתב של SGLang.
  • מריצים עומס עבודה להערכת ביצועים של מילוי בקשות.
  • לפנות מקום.

עלויות

במסמך הזה משתמשים ברכיבים הבאים שלGoogle Cloud, והשימוש בהם כרוך בתשלום:

כדי ליצור הערכת עלויות בהתאם לשימוש החזוי, אתם יכולים להשתמש ב[מחשבון עלויות](https://cloud.google.com/products/calculator).

כשמסיימים את המשימות שמתוארות במסמך הזה אפשר למחוק את המשאבים שיצרתם כדי להימנע מחיובים נוספים. מידע נוסף זמין בקטע הסרת המשאבים.

לפני שמתחילים

  • כדי להשתמש במדריך הזה, אתם צריכים טוקן גישה ל-Hugging Face. אתם יכולים להירשם לחשבון בחינם ב-Hugging Face. אחרי שיוצרים חשבון, יוצרים אסימון גישה:

    1. בדף Welcome to Hugging Face, לוחצים על הדמות של החשבון ובוחרים באפשרות טוקן גישה.
    2. בדף טוקני גישה, לוחצים על יצירת טוקן חדש.
    3. בוחרים את סוג הטוקן Read ומזינים שם לטוקן.
    4. טוקן הגישה שלכם יוצג. שומרים את הטוקן במקום בטוח.

  • מפעילים את ממשקי ה-API הבאים בפרויקט ב- Google Cloud : ‏Kubernetes Engine API, ‏Compute Engine API, ‏Cloud TPU API ו-Artifact Registry API.

    הפעלת ממשקי API

  • מוודאים שבפרויקט יש מספיק מכסת דיסק ומכסת כתובות IP חיצוניות באזור שבו נמצאת קיבולת ה-TPU:

    1. במסוף Google Cloud , נכנסים לדף Quotas & System Limits.

      כניסה לדף Quotas & System Limits

    2. מוודאים שיש לכם מכסה פנויה של לפחות 2,800GiB ב-Google Cloud Hyperdisk Balanced, בארבעת הנפחים הזמניים של 700GiB שמשמשים לשמירת המודל במטמון על ידי ה-Pods למילוי מראש ולפענוח.

    3. מסננים לפי In-use regional external IPv4 addresses ומוודאים שיש לכם מספיק מכסות של כתובות IP באזור היעד.

כדי לקבל את ההרשאות שדרושות להשלמת המדריך, בקשו מהאדמין להקצות לכם את התפקידים הבאים ב-IAM בפרויקט:

להסבר על מתן תפקידים, ראו איך מנהלים את הגישה ברמת הפרויקט, התיקייה והארגון.

יכול להיות שאפשר לקבל את ההרשאות הנדרשות גם באמצעות תפקידים בהתאמה אישית או תפקידים מוגדרים מראש.

מוודאים שלחשבון השירות שמוגדר כברירת מחדל ב-Compute Engine יש את התפקידים הבאים של ניהול זהויות והרשאות גישה (IAM) בפרויקט:

הגדרת משתני הסביבה

מייצאים את משתני הסביבה שבהם משתמשים במדריך הזה:

export CLUSTER_NAME="YOUR_CLUSTER_NAME"
export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="YOUR_REGION"
export ZONE="YOUR_ZONE"
export RESERVATION="YOUR_RESERVATION_NAME"
export HF_TOKEN="YOUR_HF_TOKEN"
export SGLANG_IMAGE="lmsysorg/sglang:v0.1.0-tpu"

מחליפים את מה שכתוב בשדות הבאים:

  • ‫YOUR_CLUSTER_NAME: השם של אשכול GKE.
  • ‫YOUR_PROJECT_ID: מזהה הפרויקט ב- Google Cloud.
  • ‫YOUR_REGION: האזור שבו רוצים לפרוס את האשכול, למשל us-central1.
  • ‫YOUR_ZONE: האזור שבו רוצים לפרוס את מאגרי הצמתים של TPU7x, למשל us-central1-c.
  • ‫YOUR_RESERVATION_NAME: אם אתם משתמשים בהזמנה, השם של ההזמנה של TPU7x.
  • ‫YOUR_HF_TOKEN: טוקן הגישה ל-Hugging Face.
  • ‫YOUR_SGLANG_TPU_IMAGE: אם אתם משתמשים בקובץ אימג' של קונטיינר SGLang TorchTPU בהתאמה אישית, ה-URI של קובץ האימג'. אחרת, קובץ האימג' של SGLang TorchTPU שמוגדר כברירת מחדל.

יצירת אשכול GKE

כדי ליצור אשכול GKE במצב Standard ולצרף שני מאגרי צמתים של TPU7x עם כמה מארחים לצורך הפרדה של מילוי מראש ופענוח, מבצעים את השלבים הבאים:

  1. יוצרים אשכול GKE אזורי שמשתמש ב-e2-standard-4 צמתים למאגר הצמתים שמוגדר כברירת מחדל, שמספק מספיק משאבי מעבד (CPU) וזיכרון לבקר LeaderWorkerSet ולנתב הפירוק של SGLang:

    gcloud container clusters create $CLUSTER_NAME \
      --project=$PROJECT_ID \
      --location=$REGION \
      --machine-type=e2-standard-4
  2. יוצרים מדיניות מיקום של עומסי עבודה עם תפוקה גבוהה וטופולוגיית מאיץ 2x2x2, שנדרשת למאגרי צמתים של TPU7x עם כמה מארחים:

    gcloud compute resource-policies create workload-policy tpu7x-2x2x2-workload-policy \
        --type=HIGH_THROUGHPUT \
        --accelerator-topology=2x2x2 \
        --project=${PROJECT_ID} \
        --region=${REGION}
  3. יוצרים שני מאגרי צמתים של TPU7x עם כמה מארחים (tpu7x-pool-0 ו-tpu7x-pool-1), שכל אחד מהם מכיל שני צומתי tpu7x-standard-4t (שמונה שבבי TPU7x בטופולוגיית 2x2x2 לכל מאגר צמתים, סה"כ 16 שבבי TPU7x):

    gcloud container node-pools create tpu7x-pool-0 \
        --cluster=$CLUSTER_NAME \
        --location=$REGION \
        --node-locations=$ZONE \
        --machine-type=tpu7x-standard-4t \
        --num-nodes=2 \
        --placement-policy=tpu7x-2x2x2-workload-policy \
        --reservation-affinity=specific \
        --reservation=$RESERVATION \
        --project=$PROJECT_ID
    
    gcloud container node-pools create tpu7x-pool-1 \
        --cluster=$CLUSTER_NAME \
        --location=$REGION \
        --node-locations=$ZONE \
        --machine-type=tpu7x-standard-4t \
        --num-nodes=2 \
        --placement-policy=tpu7x-2x2x2-workload-policy \
        --reservation-affinity=specific \
        --reservation=$RESERVATION \
        --project=$PROJECT_ID

    במקום להשתמש במקום שמור ספציפי, אתם יכולים גם ליצור את מאגרי הצמתים באמצעות קיבולת Spot או Flex-start VMs, על ידי העברת הדגלים הבאים:

    • VM במודל Spot: מחליפים את --reservation-affinity=specific ואת --reservation=${RESERVATION} ב---spot כדי להשתמש בקיבולת של Spot. יכול להיות שייקח זמן ליצור מכונות שמשתמשות בקיבולת של Spot אחרי שמאגר הצמתים נוצר, ויכול להיות שהן יופקעו במהלך הרצת עומסי העבודה.
    • ‫Flex-start VMs: מחליפים את --num-nodes=2,‏ --reservation-affinity=specific ו---reservation=${RESERVATION} ב---flex-start,‏ --enable-autoscaling,‏ --num-nodes=0,‏ --min-nodes=0 ו---max-nodes=2 כדי להקצות את מאגר הצמתים באמצעות אפשרות הצריכה Flex-start. מידע נוסף זמין במאמר הרצת עומס עבודה קטן של אצווה באמצעות יחידות TPU ומכונות וירטואליות מסוג Flex-start.
  4. מגדירים את פרטי הכניסה של kubectl כדי לתקשר עם האשכול:

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

הגדרת פרטי כניסה והתקנת LeaderWorkerSet

כדי להכין את האשכול לפריסה של SGLang בכמה מארחים, מבצעים את הפעולות הבאות:

  1. יוצרים סוד של Kubernetes בשם hf-secret כדי לאחסן את טוקן הגישה של Hugging Face:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token=${HF_TOKEN}
  2. מתקינים את ה-CustomResourceDefinition והבקר של LeaderWorkerSet באשכול:

    kubectl apply --server-side -k github.com/kubernetes-sigs/lws/config/default?ref=main
    echo "Waiting for LWS controller to be ready..."
    kubectl wait --for=condition=Available --timeout=300s deployment/lws-controller-manager -n lws-system

פריסת שרת SGLang עם כמה מארחים

במניפסט של עומס העבודה (qwen3-coder-480b-multihost-disagg-v7x.yaml) מוגדרים המשאבים הבאים, כדי לתאם בין מילוי מראש מבוזר לבין פענוח של בקשות בשני מאגרי הצמתים של 2x2x2 TPU7x:

  • StorageClass (hyperdisk-balanced-tpu): מספק אמצעי אחסון זמניים של ReadWriteOnce בנפח 700 GiB המגובים על ידי אחסון Hyperdisk Balanced של Google Cloud. כל אחד מארבעת ה-Pods למילוי מראש ולפענוח טוען את אמצעי האחסון שלו ב-/data כדי לאחסן באופן מקומי את נקודת הבדיקה של Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 שהורדה.
  • ‫Prefill pool (sglang-prefill-service and sglang-tpu-prefill): מריץ LeaderWorkerSet של size: 2 (פוד Leader אחד ופוד Worker אחד) שמוצמד באופן בלעדי למאגר צמתים אחד של 2x2x2 TPU7x (שמונה שבבי TPU, שמספקים 16 TensorCores). ה-LeaderWorkerSet משתמש בהערה leaderworkerset.sigs.k8s.io/exclusive-topology שמוגדרת ל-"cloud.google.com/gke-nodepool". ה-Pods מופעלים ב-sglang.launch_server עם הדגלים --disaggregation-mode=prefill,‏ --tensor-parallel-size=16 ו---ep-size=16, וחושפים את יציאת ה-HTTP‏ 8000 ואת יציאת ה-bootstrap‏ 8998.
  • ‫Decode pool (sglang-decode-service and sglang-tpu-decode): מריץ LeaderWorkerSet של size: 2 שמוצמד למאגר הצמתים השני של 2x2x2 TPU7x (שמונה שבבי TPU שמספקים 16 TensorCores). ‫LeaderWorkerSet מפעיל את sglang.launch_server עם הדגלים --disaggregation-mode=decode,‏ --tensor-parallel-size=16 ו---ep-size=16.
  • נתב פירוק (sglang-router ו-sglang-service): מריץ פריסת מעבד (CPU) שמפעילה את המודול sglang_router.launch_router עם הדגל --pd-disaggregation וחושפת שירות ClusterIP ביציאה 8000. הנתב מפנה את ההנחיות ומתאם את העברת המטמון של ה-KV בין השירותים sglang-prefill-service ו-sglang-decode-service.

כדי לפרוס את שרתי SGLang עם כמה מארחים ולעקוב אחריהם:

  1. מורידים את המניפסט של עומס העבודה:

    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/tpu/inference/qwen3-coder/qwen3-coder-480b-multihost-disagg-v7x.yaml

    אפשר גם ליצור קובץ בשם qwen3-coder-480b-multihost-disagg-v7x.yaml עם התוכן הבא:

    # ==============================================================================
    # Serve Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 on GKE / Ironwood (TPU v7x)
    # using SGLang TorchTPU with Prefill/Decode Disaggregation, multi-host
    # ==============================================================================
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: hyperdisk-balanced-tpu
    provisioner: pd.csi.storage.gke.io
    volumeBindingMode: WaitForFirstConsumer
    allowVolumeExpansion: true
    parameters:
      type: hyperdisk-balanced
    ---
    # ==============================================================================
    # PREFILL POOL: Internal ClusterIP Service exposing SGLang Prefill Endpoint
    # ==============================================================================
    apiVersion: v1
    kind: Service
    metadata:
      name: sglang-prefill-service
      labels:
        app: sglang-prefill
    spec:
      type: ClusterIP
      selector:
        app: sglang-prefill
        role: leader
      ports:
        - name: http
          protocol: TCP
          port: 8000
          targetPort: 8000
        - name: bootstrap
          protocol: TCP
          port: 8998
          targetPort: 8998
    ---
    # ==============================================================================
    # PREFILL POOL: LeaderWorkerSet (TPU v7x, 1 slice = 2 nodes, 8 chips / 16 TensorCores)
    # ==============================================================================
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    metadata:
      name: sglang-tpu-prefill
      annotations:
        leaderworkerset.sigs.k8s.io/exclusive-topology: "cloud.google.com/gke-nodepool"
      labels:
        app: sglang-prefill
    spec:
      replicas: 1
      rolloutStrategy:
        type: RollingUpdate
      leaderWorkerTemplate:
        size: 2
        restartPolicy: RecreateGroupOnPodRestart
        leaderTemplate:
          metadata:
            labels:
              app: sglang-prefill
              role: leader
          spec: &prefill-pod-spec
            securityContext:
              fsGroup: 65534
            nodeSelector:
              cloud.google.com/gke-tpu-accelerator: tpu7x
              cloud.google.com/gke-tpu-topology: 2x2x2
            tolerations:
              - key: "google.com/tpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
              - name: sglang
                image: $SGLANG_IMAGE
                command:
                  - /bin/bash
                  - -c
                  - |
                    set -ex
                    ulimit -c 0
                    LEADER_POD="${LWS_LEADER_ADDRESS%%.*}"
                    DNS_SUFFIX="${LWS_LEADER_ADDRESS#*.}"
                    HOSTS="${LWS_LEADER_ADDRESS}"
                    for ((i = 1; i < LWS_GROUP_SIZE; i++)); do
                      HOSTS="${HOSTS},${LEADER_POD}-${i}.${DNS_SUFFIX}"
                    done
                    export TPU_WORKER_HOSTNAMES="${HOSTS}"
                    export TPU_WORKER_ID="${LWS_WORKER_INDEX}"
                    export TPU_TOPOLOGY="${TPU_TOPOLOGY:-2x2x2}"
                    echo "=== [prefill rank ${LWS_WORKER_INDEX}/${LWS_GROUP_SIZE}] hosts=${TPU_WORKER_HOSTNAMES} ==="
                    exec python3 -m sglang.launch_server \
                      --device=tpu \
                      --host=0.0.0.0 \
                      --port=8000 \
                      --model-path=Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 \
                      --tensor-parallel-size=16 \
                      --ep-size=16 \
                      --nnodes="${LWS_GROUP_SIZE}" \
                      --node-rank="${LWS_WORKER_INDEX}" \
                      --dist-init-addr="${LWS_LEADER_ADDRESS}:20000" \
                      --attention-backend=pallas_tpu_rpa_batched \
                      --mem-fraction-static=0.8 \
                      --max-running-requests=256 \
                      --chunked-prefill-size=8192 \
                      --max-total-tokens=786432 \
                      --context-length=9216 \
                      --enable-torch-compile \
                      --torch-compile-max-bs=256 \
                      --tpu-compile-tracer=direct \
                      --sampling-defaults=openai \
                      --enable-fused-qk-norm-rope \
                      --disaggregation-mode=prefill \
                      --disaggregation-transfer-backend=tpu \
                      --download-dir=/data \
                      --tpu-compile-tokens=8192 \
                      --disable-radix-cache
                env:
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                        optional: true
                resources:
                  requests:
                    cpu: "16"
                    memory: 128Gi
                    google.com/tpu: "4"
                  limits:
                    memory: 768Gi
                    google.com/tpu: "4"
                ports:
                  - containerPort: 8000
                    name: http
                  - containerPort: 8998
                    name: bootstrap
                startupProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  initialDelaySeconds: 60
                  periodSeconds: 15
                  timeoutSeconds: 10
                  failureThreshold: 236
                readinessProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  periodSeconds: 10
                  timeoutSeconds: 5
                  failureThreshold: 3
                volumeMounts:
                  - name: model-cache
                    mountPath: /data
                  - name: dshm
                    mountPath: /dev/shm
            volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
                  sizeLimit: 32Gi
              - name: model-cache
                ephemeral:
                  volumeClaimTemplate:
                    spec:
                      accessModes: ["ReadWriteOnce"]
                      storageClassName: hyperdisk-balanced-tpu
                      resources:
                        requests:
                          storage: 700Gi
        workerTemplate:
          metadata:
            labels:
              app: sglang-prefill
              role: worker
          spec: *prefill-pod-spec
    ---
    # ==============================================================================
    # DECODE POOL: Internal ClusterIP Service exposing SGLang Decode Endpoint
    # ==============================================================================
    apiVersion: v1
    kind: Service
    metadata:
      name: sglang-decode-service
      labels:
        app: sglang-decode
    spec:
      type: ClusterIP
      selector:
        app: sglang-decode
        role: leader
      ports:
        - name: http
          protocol: TCP
          port: 8000
          targetPort: 8000
        - name: bootstrap
          protocol: TCP
          port: 8998
          targetPort: 8998
    ---
    # ==============================================================================
    # DECODE POOL: LeaderWorkerSet (TPU v7x, 1 slice = 2 nodes, 8 chips / 16 TensorCores)
    # ==============================================================================
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    metadata:
      name: sglang-tpu-decode
      annotations:
        leaderworkerset.sigs.k8s.io/exclusive-topology: "cloud.google.com/gke-nodepool"
      labels:
        app: sglang-decode
    spec:
      replicas: 1
      rolloutStrategy:
        type: RollingUpdate
      leaderWorkerTemplate:
        size: 2
        restartPolicy: RecreateGroupOnPodRestart
        leaderTemplate:
          metadata:
            labels:
              app: sglang-decode
              role: leader
          spec: &decode-pod-spec
            securityContext:
              fsGroup: 65534
            nodeSelector:
              cloud.google.com/gke-tpu-accelerator: tpu7x
              cloud.google.com/gke-tpu-topology: 2x2x2
            tolerations:
              - key: "google.com/tpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
              - name: sglang
                image: $SGLANG_IMAGE
                command:
                  - /bin/bash
                  - -c
                  - |
                    set -ex
                    ulimit -c 0
                    LEADER_POD="${LWS_LEADER_ADDRESS%%.*}"
                    DNS_SUFFIX="${LWS_LEADER_ADDRESS#*.}"
                    HOSTS="${LWS_LEADER_ADDRESS}"
                    for ((i = 1; i < LWS_GROUP_SIZE; i++)); do
                      HOSTS="${HOSTS},${LEADER_POD}-${i}.${DNS_SUFFIX}"
                    done
                    export TPU_WORKER_HOSTNAMES="${HOSTS}"
                    export TPU_WORKER_ID="${LWS_WORKER_INDEX}"
                    export TPU_TOPOLOGY="${TPU_TOPOLOGY:-2x2x2}"
                    echo "=== [decode rank ${LWS_WORKER_INDEX}/${LWS_GROUP_SIZE}] hosts=${TPU_WORKER_HOSTNAMES} ==="
                    exec python3 -m sglang.launch_server \
                      --device=tpu \
                      --host=0.0.0.0 \
                      --port=8000 \
                      --model-path=Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 \
                      --tensor-parallel-size=16 \
                      --ep-size=16 \
                      --nnodes="${LWS_GROUP_SIZE}" \
                      --node-rank="${LWS_WORKER_INDEX}" \
                      --dist-init-addr="${LWS_LEADER_ADDRESS}:20000" \
                      --attention-backend=pallas_tpu_rpa_batched \
                      --mem-fraction-static=0.8 \
                      --max-running-requests=256 \
                      --chunked-prefill-size=8192 \
                      --max-total-tokens=786432 \
                      --context-length=9216 \
                      --enable-torch-compile \
                      --torch-compile-max-bs=256 \
                      --tpu-compile-tracer=direct \
                      --sampling-defaults=openai \
                      --enable-fused-qk-norm-rope \
                      --disaggregation-mode=decode \
                      --disaggregation-transfer-backend=tpu \
                      --download-dir=/data
                env:
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                        optional: true
                resources:
                  requests:
                    cpu: "16"
                    memory: 128Gi
                    google.com/tpu: "4"
                  limits:
                    memory: 768Gi
                    google.com/tpu: "4"
                ports:
                  - containerPort: 8000
                    name: http
                  - containerPort: 8998
                    name: bootstrap
                startupProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  initialDelaySeconds: 60
                  periodSeconds: 15
                  timeoutSeconds: 10
                  failureThreshold: 236
                readinessProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  periodSeconds: 10
                  timeoutSeconds: 5
                  failureThreshold: 3
                volumeMounts:
                  - name: model-cache
                    mountPath: /data
                  - name: dshm
                    mountPath: /dev/shm
            volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
                  sizeLimit: 32Gi
              - name: model-cache
                ephemeral:
                  volumeClaimTemplate:
                    spec:
                      accessModes: ["ReadWriteOnce"]
                      storageClassName: hyperdisk-balanced-tpu
                      resources:
                        requests:
                          storage: 700Gi
        workerTemplate:
          metadata:
            labels:
              app: sglang-decode
              role: worker
          spec: *decode-pod-spec
    ---
    # ==============================================================================
    # SGLANG DISAGGREGATION ROUTER / PROXY DEPLOYMENT
    # ==============================================================================
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sglang-router
      labels:
        app: sglang-router
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sglang-router
      template:
        metadata:
          labels:
            app: sglang-router
        spec:
          containers:
            - name: router
              image: $SGLANG_IMAGE
              command:
                - python3
                - -m
                - sglang_router.launch_router
              args:
                - --pd-disaggregation
                - --prefill
                - http://sglang-prefill-service:8000
                - "8998"
                - --decode
                - http://sglang-decode-service:8000
                - --host
                - 0.0.0.0
                - --port
                - "8000"
                - --model-path
                - Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8
                - --request-timeout-secs
                - "3600"
                - --worker-startup-timeout-secs
                - "5400"
              env:
                - name: HF_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                      optional: true
              ports:
                - containerPort: 8000
                  name: http
              readinessProbe:
                httpGet:
                  path: /health_generate
                  port: 8000
                initialDelaySeconds: 15
                periodSeconds: 30
                timeoutSeconds: 10
                failureThreshold: 3
              resources:
                requests:
                  cpu: "1"
                  memory: 4Gi
                limits:
                  cpu: "4"
                  memory: 12Gi
    ---
    # ==============================================================================
    # SGLANG EXTERNAL SERVICE: Public / Client Endpoint for OpenAI-compatible Serving
    # ==============================================================================
    apiVersion: v1
    kind: Service
    metadata:
      name: sglang-service
      labels:
        app: sglang-router
    spec:
      type: ClusterIP
      selector:
        app: sglang-router
      ports:
        - name: http
          protocol: TCP
          port: 8000
          targetPort: 8000
  2. מחילים את המניפסט של עומס העבודה על האשכול:

    envsubst '$SGLANG_IMAGE' < qwen3-coder-480b-multihost-disagg-v7x.yaml | kubectl apply -f -
  3. מוודאים שהפודים של הנתב, המילוי מראש והפענוח נוצרו:

    kubectl get pods

    כשהפודים מוכנים, הפלט אמור להיראות כך:

    NAME                             READY   STATUS    RESTARTS   AGE
    sglang-router-6d87cb8c7d-gv4wr   1/1     Running   0          39m
    sglang-tpu-decode-0              1/1     Running   0          40m
    sglang-tpu-decode-0-1            1/1     Running   0          40m
    sglang-tpu-prefill-0             1/1     Running   0          40m
    sglang-tpu-prefill-0-1           1/1     Running   0          40m
    
  4. עוקבים אחרי היומנים של Pod ה-Leader למילוי מראש ולפענוח עד לסיום ההפעלה:

    kubectl logs -f -l role=leader --prefix=true &
    LOG_PID=$!
    kubectl wait --for=condition=Ready --timeout=3600s pod -l role=leader
    kill "${LOG_PID}" 2>/dev/null || true

    ההורדה הראשונית של המודל, טעינת המשקלים וההידור נמשכים כ-15 עד 30 דקות בהפעלה הראשונה.

    אם אתם משתמשים בקיבולת שניתנת להפסקה לפני הזמן, כמו קיבולת Spot או מכונות וירטואליות מסוג Flex-start, יכול להיות שיעבור יותר זמן אחרי יצירת מאגר הצמתים עד ליצירת הצמתים של פרוסת ה-TPU, ויכול להיות שהם יופסקו לפני הזמן במהלך הרצת עומסי העבודה. מידע נוסף זמין במאמר מכונות VM זמניות. כדי לבדוק אם נוצרו צומתי פרוסות ה-TPU, עיינו בקטע בדיקת הסטטוס והתנאים של הצומת.

    מחכים עד שביומני ה-Pod הראשיים של המילוי מראש והפענוח תופיע ההודעה הבאה, ובעמודה READY של כל ה-Pods יופיע הערך 1/1 בשדה kubectl get pods:

    The server is fired up and ready to roll!
    
  5. פותחים סשן שני של טרמינל ומעבירים את היציאה המקומית 8000 אל sglang-service:

    kubectl port-forward service/sglang-service 8000:8000

    משאירים את ההעברה ליציאה אחרת פועלת בסשן הטרמינל הזה.

שליחת בקשות להסקת מסקנות

חוזרים לסשן הראשון בטרמינל ושולחים בקשה להשלמת צ'אט לנקודת הקצה (endpoint) של /v1/chat/completions שנחשפה על ידי הנתב של SGLang:

curl -s -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
    "messages": [
      {"role": "user", "content": "Explain quicksort in two sentences."}
    ],
    "max_tokens": 60,
    "temperature": 0.0
  }'

הרצת עומס עבודה להערכת ביצועים

כדי להשוות את המודל Qwen3-Coder שנפרס באמצעות עומס עבודה של קלט של 1,024 אסימונים ופלט של 1,024 אסימונים (1k/1k) מ-Pod של sglang-router, מריצים את הפקודה הבאה:

kubectl exec deployment/sglang-router -- python3 -m sglang.bench_serving \
  --backend sglang \
  --base-url http://localhost:8000 \
  --model Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 \
  --dataset-name random \
  --random-input-len 1024 \
  --random-output-len 1024 \
  --random-range-ratio 1.0 \
  --num-prompts 256 \
  --max-concurrency 64

הסרת המשאבים

כדי להימנע מחיובים נוספים בחשבון Google Cloud על המשאבים שבהם השתמשתם במדריך הזה, מחקו את המשאבים הבאים:

  1. מוחקים את עומסי העבודה של SGLang למילוי מראש, לפענוח ולניתוב:

    kubectl delete -f qwen3-coder-480b-multihost-disagg-v7x.yaml --ignore-not-found
  2. מוחקים את מאגרי הצמתים של TPU7x, את המדיניות של מיקום עומסי העבודה ואת אשכול GKE:

    gcloud container node-pools delete tpu7x-pool-0 \
        --cluster=${CLUSTER_NAME} \
        --location=${REGION} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud container node-pools delete tpu7x-pool-1 \
        --cluster=${CLUSTER_NAME} \
        --location=${REGION} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud container clusters delete ${CLUSTER_NAME} \
        --location=${REGION} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud compute resource-policies delete tpu7x-2x2x2-workload-policy \
        --region=${REGION} \
        --project=${PROJECT_ID} \
        --quiet

המאמרים הבאים