Disponibilizar o Qwen3-Coder-30B-A3B no GKE com SGLang TorchTPU de vários hosts e desagregação de DP em aceleradores de TPU v6e

Neste tutorial, mostramos como implantar e disponibilizar o Qwen3-Coder-30B-A3B (Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8), um modelo de codificação de mix de especialistas, no Google Kubernetes Engine (GKE). A implantação usa aceleradores TPU v6e (Trillium) de vários hosts e o framework de veiculação SGLang com TorchTPU (torchtpu) e desagregação de pré-preenchimento e decodificação (DP).

Na configuração de implantação descrita neste tutorial, você provisiona dois pools de nós ct6e-standard-4t de vários hosts, cada um organizado em uma topologia 2x4 (oito chips de TPU v6e em dois nós por pool de nós, totalizando 16 chips de TPU v6e). Use a API LeaderWorkerSet do Kubernetes para fazer o seguinte:

  • Execute servidores de pré-preenchimento e decodificação desagregados de vários hosts em frações de TPU v6e separadas com o paralelismo de tensor e o paralelismo de especialistas ativados.
  • Encaminhe solicitações por um roteador de desagregação do SGLang.

Para executar um fluxo de trabalho de disponibilização de TorchTPU do SGLang de vários hosts semelhante em aceleradores TPU7x (Ironwood) com o modelo Qwen3-Coder-480B maior, consulte Disponibilizar o Qwen3-Coder-480B no GKE com TorchTPU do SGLang de vários hosts e desagregação DP em aceleradores TPU7x.

Objetivos

  • Configure o cluster do GKE e os pools de nós de TPU v6e de vários hosts.
  • Configurar credenciais e instalar o controlador LeaderWorkerSet.
  • Implante as cargas de trabalho de pré-preenchimento, decodificação e roteador do SGLang de vários hosts.
  • Envie uma solicitação de inferência ao serviço de roteador do SGLang.
  • Executar uma carga de trabalho de comparativo de mercado de exibição.
  • Fazer a limpeza.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços.

Ao concluir as tarefas descritas neste documento, é possível evitar o faturamento contínuo excluindo os recursos criados. Para mais informações, consulte Limpeza.

Antes de começar

Configure o ambiente para o tutorial:

  • Instale e inicialize a Google Cloud CLI e instale kubectl. Se você usa o Cloud Shell, as duas ferramentas já estão pré-instaladas.

  • Ative as API Kubernetes Engine, API Compute Engine, Cloud TPU e API Artifact Registry no projeto do Google Cloud :

    Ativar APIs

  • Verifique se o projeto tem cota de disco e de endereço IP externo suficientes na região em que a capacidade da TPU está localizada:

    1. No console do Google Cloud , acesse a página Cotas e limites do sistema.

      Acesse Cotas e limites do sistema

    2. Verifique se você tem pelo menos 400 GiB de cota disponível do Google Cloud Hyperdisk Balanced nos quatro volumes temporários de 100 GiB usados para armazenamento em cache de modelos pelos pods de preenchimento prévio e decodificação.

    3. Filtre por Endereços IPv4 externos regionais em uso e verifique se você tem cota de endereço IP suficiente na região de destino.

Receber um token de acesso do Hugging Face

Você precisa de um token de acesso do Hugging Face para usar este tutorial. Você pode se inscrever para uma conta sem custo financeiro no Hugging Face. Depois de criar uma conta, gere um token de acesso:

  1. Na página Welcome to Hugging Face, clique no avatar da sua conta e selecione Tokens de acesso.
  2. Na página Tokens de acesso, clique em Criar novo token.
  3. Selecione o tipo de token Read e insira um nome para ele.
  4. Seu token de acesso será exibido. Salve o token em um local seguro.

Funções exigidas

Para conseguir as permissões necessárias para concluir o tutorial, peça ao administrador para conceder a você os seguintes papéis do IAM no seu projeto:

Para mais informações sobre a concessão de papéis, consulte Gerenciar o acesso a projetos, pastas e organizações.

Também é possível conseguir as permissões necessárias usando papéis personalizados ou outros papéis predefinidos.

Verifique se a conta de serviço padrão do Compute Engine tem os seguintes papéis do Identity and Access Management (IAM) no projeto:

Configurar as variáveis de ambiente

Exporte as variáveis de ambiente que você vai usar neste tutorial:

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"

Substitua:

  • YOUR_CLUSTER_NAME: o nome do cluster do GKE.
  • YOUR_PROJECT_ID: ID do seu projeto do Google Cloud.
  • YOUR_REGION: a região em que você quer implantar o cluster, como us-central1.
  • YOUR_ZONE: a zona em que você quer implantar os pools de nós da TPU v6e, como us-central1-b.
  • YOUR_RESERVATION_NAME: se você estiver usando uma reserva, o nome da sua reserva de TPU v6e.
  • YOUR_HF_TOKEN: seu token de acesso do Hugging Face.

Se você estiver usando uma imagem de contêiner personalizada do SGLang TorchTPU, substitua lmsysorg/sglang:v0.1.0-tpu na variável SGLANG_IMAGE pelo URI da imagem.

Criar o cluster do GKE

Para criar um cluster do GKE no modo Standard e anexar dois pools de nós de TPU v6e de vários hosts para desagregação de pré-preenchimento e decodificação, siga estas etapas:

  1. Crie um cluster regional do GKE que use nós e2-standard-4 para o pool de nós padrão, que fornece recursos suficientes de CPU e memória para o controlador LeaderWorkerSet e o roteador de desagrupamento SGLang:

    gcloud container clusters create "${CLUSTER_NAME}" \
      --project="${PROJECT_ID}" \
      --location="${REGION}" \
      --machine-type=e2-standard-4
  2. Crie dois pools de nós de TPU v6e de vários hosts, cada um contendo dois nós ct6e-standard-4t (oito chips de TPU v6e em uma topologia 2x4 por pool de nós, totalizando 16 chips de 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}"

    Em vez de usar uma reserva específica, também é possível criar os pools de nós usando a capacidade do Spot ou as VMs flex-start:

    VMs spot

    Provisione os pools de nós usando a capacidade de spot substituindo as flags --reservation-affinity=specific e --reservation="${RESERVATION}" pela flag --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

    As instâncias que usam a capacidade spot podem levar algum tempo para serem criadas após a criação do pool de nós e podem ser interrompidas durante as execuções da carga de trabalho.

    VMs flex-start

    Provisione os pools de nós usando a opção de consumo Início flexível:

    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

    Para mais informações, consulte Executar uma carga de trabalho em lote pequena com TPUs e VMs de início flexível.

  3. Configure as credenciais de kubectl para se comunicar com o cluster:

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

Configurar credenciais e instalar o LeaderWorkerSet

Para preparar o cluster para a implantação do SGLang em vários hosts, siga estas etapas:

  1. Crie um secret do Kubernetes chamado hf-secret para armazenar o token de acesso do Hugging Face:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}"
  2. Instale o CustomResourceDefinition e o controlador do LeaderWorkerSet no cluster:

    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

Implantar o servidor SGLang de vários hosts

O manifesto da carga de trabalho (qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml) define os seguintes recursos para coordenar a disponibilização de pré-preenchimento e decodificação desagregados nos dois pools de nós da TPU v6e 2x4:

  • StorageClass (hyperdisk-balanced-tpu): provisiona volumes efêmeros de 100 GiB ReadWriteOnce com suporte do armazenamento do Google Cloud Hyperdisk Balanced. Cada um dos quatro pods de pré-preenchimento e decodificação monta o próprio volume em /data para armazenar localmente os checkpoints Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 baixados.

  • Pool de preenchimento (sglang-prefill-service e sglang-tpu-prefill): executa um LeaderWorkerSet, que contém um pod líder e um pod de worker, fixados exclusivamente em um pool de nós da TPU v6e 2x4. Esse pool de nós tem oito chips de TPU, que fornecem oito TensorCores. O LeaderWorkerSet usa a anotação leaderworkerset.sigs.k8s.io/exclusive-topology definida como "cloud.google.com/gke-nodepool". Os pods iniciam sglang.launch_server com as seguintes flags, expondo a porta 8000 e a porta de inicialização 8998:

    • --disaggregation-mode=prefill
    • --tensor-parallel-size=8
    • --ep-size=8
  • Pool de decodificação (sglang-decode-service e sglang-tpu-decode): executa um LeaderWorkerSet de size: 2 fixado ao segundo pool de nós de TPU v6e 2x4 (oito chips de TPU, que fornecem oito TensorCores). O LeaderWorkerSet inicia sglang.launch_server com as seguintes flags:

    • --disaggregation-mode=decode
    • --tensor-parallel-size=8
    • --ep-size=8
  • Roteador de desagregação (sglang-router e sglang-service): executa uma implantação de CPU que executa o módulo sglang_router.launch_router com a flag --pd-disaggregation e expõe um serviço ClusterIP na porta 8000. O roteador direciona os comandos e coordena a transferência de cache de chave-valor entre os serviços sglang-prefill-service e sglang-decode-service.

Para implantar e monitorar os servidores SGLang de vários hosts, siga estas etapas:

  1. Baixe o manifesto da carga de trabalho:

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

    Como alternativa, crie um arquivo chamado qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml com o seguinte conteúdo:

    # ==============================================================================
    # 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. Aplique o manifesto de carga de trabalho ao cluster:

    envsubst '$SGLANG_IMAGE' < qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml | kubectl apply -f -
  3. Verifique se os pods de roteador, preenchimento prévio e decodificação foram criados:

    kubectl get pods

    Quando todos os pods estiverem prontos, a saída será semelhante a esta:

    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. Monitore os registros do pod líder de preenchimento prévio e decodificação até que a inicialização seja concluída:

    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

    O download inicial do modelo, o carregamento de pesos e a compilação levam de sete a dez minutos na primeira execução.

    Se você estiver usando capacidade preemptiva, como capacidade spot ou VMs flex-start, pode levar mais tempo após a criação do pool de nós para que os nós de fração de TPU sejam criados, e eles podem ser interrompidos durante as execuções de carga de trabalho. Para mais informações, consulte Instâncias de VM preemptivas. Para verificar se os nós de fração de TPU foram criados, consulte Verificar o status e as condições do nó.

    Aguarde até que os registros do pod líder de preenchimento prévio e decodificação mostrem a seguinte mensagem e kubectl get pods mostre 1/1 na coluna READY para todos os pods:

    The server is fired up and ready to roll!
    
  5. Abra uma segunda sessão de terminal e encaminhe a porta local 8000 para sglang-service:

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

    Deixe o encaminhamento de portas em execução nessa sessão do terminal.

Enviar solicitações de inferência

Volte para a primeira sessão do terminal e envie uma solicitação de preenchimento de chat para o endpoint /v1/chat/completions exposto pelo roteador do SGLang:








  
  














  
  
  
  



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

Executar uma carga de trabalho de comparativo de mercado

Para comparar o modelo Qwen3-Coder-30B-A3B implantado usando uma carga de trabalho de entrada de 1.024 tokens e saída de 1.024 tokens (1k/1k) do pod sglang-router, execute o seguinte comando:

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

Depois que o comparativo de mercado for concluído, procure uma saída que contenha métricas como as seguintes:

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

Limpar

Para evitar cobranças contínuas adicionais na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua os seguintes recursos:

  1. Exclua as cargas de trabalho de preenchimento, decodificação e roteador do SGLang:

    kubectl delete -f qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml --ignore-not-found
  2. Exclua os pools de nós da TPU v6e e o cluster do 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

A seguir