在 GKE 上使用多主机 SGLang TorchTPU 和 TPU v6e 加速器上的 PD 解耦部署 Qwen3-Coder-30B-A3B

本教程介绍了如何在 Google Kubernetes Engine (GKE) 上部署和提供 Qwen3-Coder-30B-A3B (Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8)(一种混合专家编码模型)。该部署使用多主机 TPU v6e (Trillium) 加速器和 SGLang 应用框架,并采用 TorchTPU (torchtpu) 和预填充-解码 (PD) 解耦。

在本教程所述的部署配置中,您将预配两个多主机 ct6e-standard-4t 节点池,每个节点池都采用 2x4 拓扑(每个节点池包含两个节点,每个节点包含 8 个 TPU v6e 芯片,总共 16 个 TPU v6e 芯片)。您可以使用 Kubernetes LeaderWorkerSet API 执行以下操作:

  • 在启用了张量并行处理和专家并行处理的单独 TPU v6e 切片上运行分离式多主机预填充和解码服务器。
  • 通过 SGLang 解耦路由器路由请求。

如需在 TPU7x (Ironwood) 加速器上使用更大的 Qwen3-Coder-480B 模型运行类似的多主机 SGLang TorchTPU 部署工作流,请参阅在 GKE 上使用多主机 SGLang TorchTPU 和 TPU7x 加速器上的 PD 分离部署 Qwen3-Coder-480B。

目标

  • 设置 GKE 集群和多主机 TPU v6e 节点池。
  • 配置凭证并安装 LeaderWorkerSet 控制器。
  • 部署多主机 SGLang 预填充、解码和路由器工作负载。
  • 向 SGLang 路由器服务发送推理请求。
  • 运行服务基准工作负载。
  • 清理。

费用

在本文档中,您将使用Google Cloud的以下收费组件:

如需根据您的预计使用量来估算费用,请使用价格计算器。

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理。

准备工作

为本教程设置环境:

  • 安装并初始化 Google Cloud CLI,然后安装 kubectl。如果您使用 Cloud Shell,则这两个工具都已预安装。

  • 在 Google Cloud 项目中启用 Kubernetes Engine API、Compute Engine API、Cloud TPU API 和 Artifact Registry API:

    启用 API

  • 确保您的项目在 TPU 容量所在的区域中有足够的磁盘配额和外部 IP 地址配额:

    1. 在 Google Cloud 控制台中,前往配额和系统限制页面。

      进入“配额和系统限制”

    2. 验证您是否至少有 400 GiB 的可用 Google Cloud Hyperdisk Balanced 配额,这些配额分布在预填充和解码 Pod 用于模型缓存的四个 100 GiB 临时卷中。

    3. 按正在使用的区域级外部 IPv4 地址过滤,并验证目标区域中是否有足够的 IP 地址配额。

获取 Hugging Face 访问令牌

您需要拥有 Hugging Face 访问令牌才能使用本教程。您可以在 Hugging Face 注册免费账号。拥有账号后,生成访问令牌:

  1. 在欢迎使用 Hugging Face 页面上,点击您的账号头像,然后选择访问令牌。
  2. 在访问令牌页面上,点击创建新令牌。
  3. 选择读取令牌类型,然后输入令牌名称。
  4. 系统会显示您的访问令牌。将令牌保存在安全的位置。

所需的角色

如需获得完成本教程所需的权限,请让您的管理员为您授予项目的以下 IAM 角色:

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限。

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

确保您的 Compute Engine 默认服务账号在您的项目中具有以下 Identity and Access Management (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项目的 ID。
  • YOUR_REGION:您要在其中部署集群的区域,例如 us-central1。
  • YOUR_ZONE:您要部署 TPU v6e 节点池的可用区,例如 us-central1-b。
  • YOUR_RESERVATION_NAME:如果您使用的是预留,则为 TPU v6e 预留的名称。
  • YOUR_HF_TOKEN:您的 Hugging Face 访问令牌。

如果您使用的是自定义 SGLang TorchTPU 容器映像,请将 SGLANG_IMAGE 变量中的 lmsysorg/sglang:v0.1.0-tpu 替换为映像的 URI。

创建 GKE 集群

如需在 Standard 模式下创建 GKE 集群,并挂接两个多主机 TPU v6e 节点池以实现预填充和解码解耦,请完成以下步骤:

  1. 创建一个区域级 GKE 集群,该集群使用 e2-standard-4 节点作为默认节点池,为 LeaderWorkerSet 控制器和 SGLang 解耦路由器提供充足的 CPU 和内存资源:

    gcloud container clusters create "${CLUSTER_NAME}" \
      --project="${PROJECT_ID}" \
      --location="${REGION}" \
      --machine-type=e2-standard-4
  2. 创建两个多主机 TPU v6e 节点池,每个节点池包含两个 ct6e-standard-4t 节点(每个节点池在 2x4 拓扑中包含 8 个 TPU v6e 芯片,总共 16 个 TPU v6e 芯片):

    gcloud container node-pools create tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --reservation-affinity=specific \
        --reservation="${RESERVATION}" \
        --project="${PROJECT_ID}"
    
    gcloud container node-pools create tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --reservation-affinity=specific \
        --reservation="${RESERVATION}" \
        --project="${PROJECT_ID}"

    您也可以使用 Spot 容量或灵活启动型虚拟机创建节点池,而不是使用特定预留:

    Spot 虚拟机

    通过将 --reservation-affinity=specific 和 --reservation="${RESERVATION}" 标志替换为 --spot 标志,预配使用 Spot 容量的节点池:

    gcloud container node-pools create tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --project="${PROJECT_ID}" \
        --spot
    
    gcloud container node-pools create tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --project="${PROJECT_ID}" \
        --spot

    使用 Spot 容量的实例在节点池创建后可能需要一些时间才能创建,并且可能会在工作负载运行期间被抢占。

    灵活启动型虚拟机

    使用灵活启动使用选项预配节点池:

    gcloud container node-pools create tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --project="${PROJECT_ID}" \
        --reservation-affinity=none \
        --enable-autoscaling \
        --num-nodes=0 \
        --min-nodes=0 \
        --max-nodes=2 \
        --flex-start
    
    gcloud container node-pools create tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --project="${PROJECT_ID}" \
        --reservation-affinity=none \
        --enable-autoscaling \
        --num-nodes=0 \
        --min-nodes=0 \
        --max-nodes=2 \
        --flex-start

    如需了解详情,请参阅使用 TPU 和灵活启动虚拟机运行小型批量工作负载。

  3. 配置 kubectl 凭据以与您的集群通信:

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

配置凭证并安装 LeaderWorkerSet

如需为多主机 SGLang 部署准备集群,请完成以下步骤:

  1. 创建一个名为 hf-secret 的 Kubernetes Secret,用于存储您的 Hugging Face 访问令牌:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}"
  2. 在集群上安装 LeaderWorkerSet CustomResourceDefinition 和控制器:

    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-30b-multihost-disagg-v6e-2x4.yaml) 定义了以下资源,用于在两个 2x4 TPU v6e 节点池中协调解耦预填充和解码服务:

  • StorageClass (hyperdisk-balanced-tpu):预配由 Google Cloud Hyperdisk Balanced 存储支持的临时 100 GiB ReadWriteOnce 卷。四个预填充和解码 Pod 中的每一个都会在 /data 处装载自己的卷,以在本地存储下载的 Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 检查点。

  • 预填充池(sglang-prefill-service 和 sglang-tpu-prefill):运行一个 LeaderWorkerSet,其中包含一个 Leader Pod 和一个 Worker Pod,专门固定到一个 2x4 TPU v6e 节点池。此节点池有 8 个 TPU 芯片,可提供 8 个 TensorCore。LeaderWorkerSet 使用设置为 "cloud.google.com/gke-nodepool" 的 leaderworkerset.sigs.k8s.io/exclusive-topology 注解。Pod 使用以下标志启动 sglang.launch_server,从而公开端口 8000 和引导端口 8998:

    • --disaggregation-mode=prefill
    • --tensor-parallel-size=8
    • --ep-size=8
  • 解码池(sglang-decode-service 和 sglang-tpu-decode):运行固定到第二个 2x4 TPU v6e 节点池(八个 TPU 芯片,提供八个 TensorCore)的 size: 2 LeaderWorkerSet。LeaderWorkerSet 使用以下标志启动 sglang.launch_server:

    • --disaggregation-mode=decode
    • --tensor-parallel-size=8
    • --ep-size=8
  • 解聚路由器(sglang-router 和 sglang-service):运行一个 CPU 部署,该部署使用 --pd-disaggregation 标志执行 sglang_router.launch_router 模块,并在端口 8000 上公开 ClusterIP Service。路由器负责引导提示,并协调 sglang-prefill-service 和 sglang-decode-service 服务之间的 KV 缓存传输。

如需部署和监控多主机 SGLang 服务器,请完成以下步骤:

  1. 下载工作负载清单:

    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/tpu/inference/qwen3-coder/qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml

    或者,创建一个名为 qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml 的文件,其中包含以下内容:

    # ==============================================================================
    # Serve Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 on GKE / Trillium (TPU v6e)
    # using SGLang TorchTPU with Prefill/Decode Disaggregation, multi-host (2x4)
    # ==============================================================================
    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 v6e, 1 slice = 2 nodes, 8 chips / 8 TensorCores, 2x4)
    # ==============================================================================
    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: tpu-v6e-slice
              cloud.google.com/gke-tpu-topology: 2x4
            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 TORCH_TPU_DEVICES_PER_HOST=4
                    export TPU_TOPOLOGY="2x4x1"
                    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-30B-A3B-Instruct-FP8 \
                      --tensor-parallel-size=8 \
                      --ep-size=8 \
                      --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=524288 \
                      --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: 280Gi
                    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: 100Gi
        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 v6e, 1 slice = 2 nodes, 8 chips / 8 TensorCores, 2x4)
    # ==============================================================================
    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: tpu-v6e-slice
              cloud.google.com/gke-tpu-topology: 2x4
            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 TORCH_TPU_DEVICES_PER_HOST=4
                    export TPU_TOPOLOGY="2x4x1"
                    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-30B-A3B-Instruct-FP8 \
                      --tensor-parallel-size=8 \
                      --ep-size=8 \
                      --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=524288 \
                      --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: 280Gi
                    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: 100Gi
        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-30B-A3B-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-30b-multihost-disagg-v6e-2x4.yaml | kubectl apply -f -
  3. 验证是否已创建路由器、预填充和解码 Pod:

    kubectl get pods

    所有 Pod 都准备就绪后,输出类似于以下内容:

    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 日志,直到启动完成:

    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

    首次运行时,初始模型下载、权重加载和编译大约需要 7 到 10 分钟。

    如果您使用的是可抢占容量(例如 Spot 容量或灵活启动型虚拟机),则在创建节点池后,TPU 切片节点可能需要更长时间才能创建,并且可能会在工作负载运行期间被抢占。如需了解详情,请参阅抢占式虚拟机实例。如需检查 TPU 切片节点是否已创建,请参阅检查节点的状态和条件。

    等待预填充和解码主 Pod 日志输出以下消息,并且 kubectl get pods 在所有 Pod 的 READY 列中显示 1/1:

    The server is fired up and ready to roll!
    
  5. 打开第二个终端会话,并将本地端口 8000 转发到 sglang-service:

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

    让端口转发在此终端会话中保持运行。

发送推理请求

返回到第一个终端会话,然后向 SGLang 路由器公开的 /v1/chat/completions 端点发送聊天补全请求:








  
  














  
  
  
  



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

运行基准工作负载

如需使用 sglang-router Pod 中的 1,024 个词元的输入和 1,024 个词元的输出 (1k/1k) 工作负载对已部署的 Qwen3-Coder-30B-A3B 模型进行基准测试,请运行以下命令:

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

基准测试完成后,查找包含如下指标的输出:

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

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生额外的持续费用,请删除以下资源:

  1. 删除 SGLang 预填充、解码和路由器工作负载:

    kubectl delete -f qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml --ignore-not-found
  2. 删除 TPU v6e 节点池和 GKE 集群:

    gcloud container node-pools delete tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet
    
    gcloud container node-pools delete tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet
    
    gcloud container clusters delete "${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet

后续步骤