This tutorial shows you how to deploy and serve
Qwen3-30B-A3B (Qwen/Qwen3-30B-A3B-FP8),
a mixture of experts model, on Google Kubernetes Engine (GKE). The deployment uses
multi-host TPU v6e (Trillium) accelerators and the
vLLM TPU serving framework
with TorchTPU (torchtpu) and prefill-decode (PD) disaggregation.
In the deployment configuration that's described in this tutorial, you provision
two multi-host ct6e-standard-4t node pools, each arranged in a 2x4
topology (eight TPU v6e chips across two nodes per node pool, for a total of 16
TPU v6e chips). You use the Kubernetes JobSet API and Ray to do the following:
- Run disaggregated multi-host prefill and decode servers on separate TPU v6e slices with data parallelism and expert parallelism enabled.
- Route requests through a disaggregation proxy.
To run a similar multi-host vLLM TorchTPU serving workflow on TPU7x (Ironwood) accelerators with the larger Qwen3.5-397B model, see Serve Qwen3.5-397B on GKE with multi-host vLLM TorchTPU on TPU7x accelerators.
Objectives
- Set up your GKE cluster and multi-host TPU v6e node pools.
- Stage the model weights in a Cloud Storage bucket.
- Deploy the multi-host vLLM TorchTPU prefill, decode, and proxy servers with JobSet.
- Run a serving benchmark workload and analyze the results.
- Clean up.
Costs
In this document, you use the following billable components of Google Cloud:
To generate a cost estimate based on your projected usage, use the pricing calculator.
When you finish the tasks that are described in this document, you can avoid continued billing by deleting the resources that you created. For more information, see Clean up.
Before you begin
Set up your environment for the tutorial:
Enable the Kubernetes Engine API in your Google Cloud project:
Install and initialize the Google Cloud CLI, and install
kubectl. If you use Cloud Shell, both tools are preinstalled.Ensure that your project has sufficient SSD quota and external IP address quota in the region where your TPU capacity is located:
In the Google Cloud console, go to the Quotas & System Limits page.
Filter by Persistent Disk SSD (GB) and verify that you have sufficient available SSD quota in your target region.
Filter by In-use regional external IPv4 addresses and verify that you have sufficient IP address quota in your target region.
Get a Hugging Face access token
You need a Hugging Face access token to use this tutorial. You can sign up for a free account at Hugging Face. After you have an account, generate an access token:
- On the Welcome to Hugging Face page, click your account avatar and select Access tokens.
- On the Access tokens page, click Create new token.
- Select the Read token type and enter a name for your token.
- Your access token is displayed. Save the token in a safe place.
Required roles
To get the permissions that you need to complete this tutorial, ask your administrator to grant you the following IAM roles on your project:
- Kubernetes Engine Admin (
roles/container.admin) - Compute Admin (
roles/compute.admin) - Storage Admin (
roles/storage.admin) - Service Account User (
roles/iam.serviceAccountUser)
For more information about granting roles, see Manage access to projects, folders, and organizations.
You might also be able to get the required permissions through custom roles or other predefined roles.
Set up your environment variables
To set up your environment variables, complete the following steps:
Set up your authentication:
Export the environment variables that you use throughout this tutorial:
Replace the following:
YOUR_PROJECT_ID: the ID of your Google Cloud project.YOUR_REGION: the region where you want to deploy your cluster, such asus-central1.YOUR_ZONE: the zone where you want to deploy your TPU v6e node pools, such asus-central1-c.YOUR_CLUSTER_NAME: the name of your GKE cluster.YOUR_RESERVATION_NAME: if you're using a reservation, the name of your TPU v6e reservation.YOUR_GCS_BUCKET: the name of the Cloud Storage bucket that you use to stage the model weights.YOUR_HF_TOKEN: your Hugging Face access token.
If you're using a custom vLLM TorchTPU container image, replace the default
vllm/vllm-torchtpuimage URI in theVLLM_IMAGEvariable with the URI of your image. For public images, see the vLLM TorchTPU repository.
Create the GKE cluster
To create a GKE cluster in Standard mode and attach two multi-host TPU v6e node pools for prefill and decode disaggregation, complete the following steps:
Create a regional GKE cluster with Workload Identity Federation for GKE enabled:
The output is similar to the following:
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS STACK_TYPE asq-v6e us-central1 1.35.7-gke.1027000 35.226.172.84 e2-standard-4 1.35.7-gke.1027000 3 RUNNING IPV4Create two multi-host TPU v6e node pools, each containing two
ct6e-standard-4tnodes (eight TPU v6e chips in a2x4topology per node pool, for a total of 16 TPU v6e chips):This tutorial uses Spot capacity (
--spot). Instances using Spot capacity might take time to be created after the node pool is created, and they might be preempted during workload runs. You can also create the node pools using a reservation or Flex-start VMs by passing the following flags:Specific reservation: create the node pool using a specific reservation:
To learn more about TPU reservations, see About Cloud TPU reservations.
Flex-start VMs: provision the node pool using the Flex-start consumption option:
For more information, see Run a small batch workload with TPUs and Flex-start VMs.
The output for each node pool is similar to the following:
NAME MACHINE_TYPE DISK_SIZE_GB NODE_VERSION np-0 ct6e-standard-4t 100 1.35.7-gke.1027000Configure
kubectlcredentials to communicate with your cluster:
Stage the model weights in Cloud Storage
Stage the model weights in a Cloud Storage bucket for your workload runs:
Create a Cloud Storage bucket with uniform bucket-level access:
Grant Identity and Access Management (IAM) permissions on the bucket to your default Compute Engine service account and the default Kubernetes service account:
Create a Kubernetes Secret named
hf-secretto store your Hugging Face access token:Fetch the
download_qwen3_30b.yamlmanifest:Alternatively, create a file named
download_qwen3_30b.yamlwith the following content:Download the
Qwen/Qwen3-30B-A3B-FP8model weights to the staging volume and transfer them to your Cloud Storage bucket:This step takes approximately 5 to 10 minutes to complete.
Wait until the Pod starts and monitor the download Job logs:
After the Job finishes, verify that all 16
.safetensorsshards are in your Cloud Storage bucket:Delete the completed staging Job:
Deploy the multi-host vLLM TorchTPU server
The workload manifest (v6e_qwen3_server_multihost_slice.yaml) defines a
Kubernetes JobSet resource (vllm-qwen3-30b-pd-disagg-v6e) with three
replicated jobs that coordinate disaggregated prefill and decode serving across
the two 2x4 TPU v6e node pools:
Prefill server (
prefill): runs two replicas across one2x4TPU v6e node pool (twoct6e-standard-4tnodes with four chips per host, for eight TPU cores total). The Pods form a two-node Ray cluster and launch vLLM TorchTPU on port8000forQwen/Qwen3-30B-A3B-FP8. The server usesTPURaidenConnectorconfigured askv_producerwith the following flags:--data-parallel-size=8--tensor-parallel-size=1--enable-expert-parallel--load-format=runai_streamer
Decode server (
decode): runs two replicas across the second2x4TPU v6e node pool (eight TPU cores total). The JobSet uses Pod affinity and anti-affinity rules on thecloud.google.com/gke-nodepoollabel to isolate prefill and decode onto separate node pools. The Pods form a two-node Ray cluster and launch vLLM TorchTPU on port7200withTPURaidenConnectorconfigured askv_consumer.Disaggregation proxy (
proxy): runs thetoy_proxy_server.pyscript on port10000to coordinate request routing and KV cache transfer between the prefill server (port8000) and the decode server (port7200).
To deploy and monitor the disaggregated vLLM servers, complete the following steps:
Install the Kubernetes JobSet CustomResourceDefinition and controller on your cluster:
Fetch the server manifest:
Alternatively, create a file named
v6e_qwen3_server_multihost_slice.yamlwith the following content:Substitute your Cloud Storage bucket name and container image into the
v6e_qwen3_server_multihost_slice.yamlfile and apply the manifest:This configuration uses one
2x4TPU v6e slice (two nodes) for prefill and one2x4TPU v6e slice (two nodes) for decode. To scale the number of nodes in either pool, update thereplicasfield (replicas: NUM_NODES). ReplaceNUM_NODESwith the number of nodes that you want to use.Monitor the prefill and decode Pod logs until application startup has completed:
Initial model loading and compilation take approximately 15 to 25 minutes on the first run.
If you're using preemptible capacity such as Spot capacity or Flex-start VMs, it might take longer after node pool creation for your TPU slice nodes to be created, and they might be preempted during workload runs. For more details, see Preemptible VM instances. To check whether your TPU slice nodes have been created, see Check the node's status and conditions.
Wait until both the prefill and decode server logs report
Application startup completebefore you run the benchmark workload:(APIServer pid=1) INFO: Started server process [1] (APIServer pid=1) INFO: Waiting for application startup. (APIServer pid=1) INFO: Application startup complete.
Run a benchmark workload
To benchmark the deployed Qwen3-30B-A3B model using an 8,192-token input and
1,024-token output (8k/1k) workload against the disaggregation proxy, complete
the following steps:
Fetch the benchmark manifest:
Alternatively, create a file named
v6e_qwen3_benchmark_multihost_slice.yamlwith the following content:Apply the benchmark Job manifest:
Wait for the benchmark Pod to be ready, and monitor the benchmark Job logs:
After the benchmark has completed, look for output that contains metrics such as the following:
Output token throughputTotal token throughputTTFTTPOT
Clean up
To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, delete the Kubernetes workloads, the TPU v6e node pools, the GKE cluster, and your Cloud Storage bucket:
Delete the Kubernetes workloads:
Delete the TPU v6e node pools and the GKE cluster:
Delete the Cloud Storage bucket that stores the staged model weights. If you want to keep the model weights, skip this step or copy them to your preferred location:
What's next
- Learn more about vLLM on Cloud TPU.
- Learn more about TPU v6e (Trillium).
- Learn more about Cloud TPU.