Overview
This document serves as the Solution Reference Implementation (SRI) guide, describing the concrete steps required to deploy, serve, and validate the Open Model Serving on Distributed Cloud software only solution.
This guide implements the deployment of the optimized vLLM serving engine
running Gemma 4 (google/gemma-4-31B-it) on a Distributed Cloud software only
Single-Node Cluster configuration targeted for edge deployment utilizing
physical NVIDIA GPU resources (e.g., RTX PRO 6000).
This walkthrough is designed to be executed from a CLI terminal client with
kubectl access to the Distributed Cloud software only cluster.
Objectives
By completing this Solution Reference Implementation, you will:
- Configure serving environment parameters via a local
.envfile. - Deploy a Persistent Volume Claim (PVC) using the
local-sharedstorage class to persist model weights. - Deploy the optimized vLLM serving engine utilizing
Vertex AI-qualified containers, mapping physical
nvidia.com/gpuresources. - Deploy the Gradio Web UI to provide an interactive chat interface.
- Establish local port-forwarding tunnels to validate serving responsiveness via APIs and the Web UI.
- Verify observability integration by confirming log and metric ingestion (including GPU telemetry) into Cloud Logging and Cloud Monitoring.
Before you begin
Prerequisites (cluster setup)
This guide assumes you have a running Distributed Cloud software only cluster with GPU support configured. For installing the cluster, refer to the official Distributed Cloud software only for bare metal documentation. The GPU configuration must comply with the official Google Cloud documentation for Set up and use NVIDIA GPUs.
Client workstation prerequisites
Ensure your local terminal environment has the following tools installed and configured:
- gcloud CLI: Required for project configuration and querying logs.
Must be authenticated (
gcloud auth login) and configured to the active Google Cloud project where the Distributed Cloud software only cluster is registered. - kubectl: Required for managing cluster resources. Must be configured with the appropriate context to access the target Distributed Cloud software only cluster (e.g., via connect gateway or direct local network access).
- curl: Required for sending test requests to the serving endpoint.
- jq: Required for parsing JSON output from the serving endpoint.
Hugging Face model access
To download and deploy the Gemma 4 model, you must have access to the Hugging Face repository:
- Hugging Face Account: Ensure you have a registered account on Hugging Face.
- Accept Model License: Navigate to the Gemma 4 31B IT model page and agree to the license terms to gain access to the gated model weights.
- Generate Access Token: Generate a User Access Token with Read
permissions from your Hugging Face account settings
(Settings -> Access Tokens). This token will be used as
HF_TOKENin your configuration.
Central reference configuration
All deployment parameters are managed via a central .env file located at
<local-config-dir>/.env. Ensure this file exists and contains your specific
parameters (Hugging Face token, namespace, model name, etc.).
Example .env structure:
# GDCso Cluster Namespace
NAMESPACE_NAME="your-custom-namespace"
# Hugging Face Token (Required to download gated Gemma models)
HF_TOKEN="your-hf-token-here"
# Model Sizing Parameters
MODEL_NAME="google/gemma-4-31B-it"
SAFE_MODEL_NAME="google-gemma-4-31B-it"
# Hyperparameters
MAX_MODEL_LEN=8192
GPU_MEMORY_UTILIZATION=0.95
MAX_NUM_SEQS=512
MAX_NUM_BATCHED_TOKENS=4096
DTYPE="bfloat16"
# Pod Sizing Requests
CPU_REQUEST="4"
MEMORY_REQUEST="80"
Set up environment and credentials
Create namespace and Hugging Face secret
Before deploying, you must create the namespace and the Hugging Face token secret:
# Navigate to your project root
cd <local-working-dir>
# Sourced from your local .env file
source <local-config-dir>/.env
# Create namespace
kubectl create namespace ${NAMESPACE_NAME} --dry-run=client -o yaml | kubectl apply -f -
# Create Hugging Face token secret
kubectl create secret generic hf-token-secret \
--from-literal=token="${HF_TOKEN}" \
-n "${NAMESPACE_NAME}" --dry-run=client -o yaml | kubectl apply -f -
vLLM deployment on Kubernetes
The deployment consists of three main manifests: PVC, Service, and Deployment. These templates use environment variables that are substituted at deployment time.
Persistent Volume Claim (PVC)
The PVC ensures model weights persist across pod restarts, using the Distributed Cloud software only
local-shared storage class.
Create a file named vllm-pvc.yaml with the following content:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-cache-pvc
namespace: ${NAMESPACE_NAME}
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-shared
resources:
requests:
storage: 100Gi
Service
Exposes the vLLM API server internally on port 8000.
Create a file named vllm-service.yaml with the following content:
apiVersion: v1
kind: Service
metadata:
name: vllm-service
namespace: ${NAMESPACE_NAME}
labels:
app: vllm
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "8000"
spec:
ports:
- name: http
port: 8000
targetPort: 8000
selector:
app: vllm
type: ClusterIP
Deployment
Runs the vLLM container, mounts the model cache volume, and requests physical
GPU resources. While 96 GB of VRAM fits unquantized BF16 weights (~62 GB), this
reference implementation enables Blackwell FP8 quantization
(--quantization=fp8, ~31 GB weights) to expand the KV cache to 56.53 GiB
(61,728 tokens) for higher concurrent throughput.
Create a file named vllm-deployment.yaml with the following content:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-deployment
namespace: ${NAMESPACE_NAME}
labels:
app: vllm
ai.gke.io/inference-server: vllm
ai.gke.io/model: ${SAFE_MODEL_NAME}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
examples.ai.gke.io/source: user-guide
ai.gke.io/inference-server: vllm
ai.gke.io/model: ${SAFE_MODEL_NAME}
spec:
runtimeClassName: nvidia
containers:
- name: vllm-container
image: us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-vllm-serve:gemma4
imagePullPolicy: IfNotPresent
command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
args:
- --model=$(MODEL_ID)
- --host=0.0.0.0
- --port=8000
- --tensor-parallel-size=1
- --enable-log-requests
- --max-model-len=${MAX_MODEL_LEN}
- --gpu-memory-utilization=${GPU_MEMORY_UTILIZATION}
- --max-num-seqs=${MAX_NUM_SEQS}
- --max-num-batched-tokens=${MAX_NUM_BATCHED_TOKENS}
- --dtype=${DTYPE}
- --trust-remote-code
- --enable-prefix-caching
- --enable-chunked-prefill
- --quantization=fp8
env:
- name: MODEL_ID
value: ${MODEL_NAME}
- name: HUGGING_FACE_HUB_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
- name: LD_LIBRARY_PATH
value: "/usr/local/nvidia/lib64"
ports:
- name: http
containerPort: 8000
resources:
requests:
cpu: ${CPU_REQUEST}
memory: "${MEMORY_REQUEST}Gi"
nvidia.com/gpu: "1"
limits:
cpu: ${CPU_REQUEST}
memory: "${MEMORY_REQUEST}Gi"
nvidia.com/gpu: "1"
volumeMounts:
- mountPath: /root/.cache/huggingface
name: cache-volume
- mountPath: /dev/shm
name: dshm
volumes:
- name: cache-volume
persistentVolumeClaim:
claimName: model-cache-pvc
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 16Gi
Gradio UI deployment
The Gradio UI provides an interactive web interface to chat with the model. It is deployed within the cluster and connects to the vLLM service via the internal Kubernetes network.
Gradio deployment
Create a file named gradio-deployment.yaml with the following content:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gradio-deployment
namespace: ${NAMESPACE_NAME}
labels:
app: gradio
spec:
replicas: 1
selector:
matchLabels:
app: gradio
template:
metadata:
labels:
app: gradio
spec:
containers:
- name: gradio
image: us-docker.pkg.dev/google-samples/containers/gke/gradio-app:v1.0.4
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
- name: CONTEXT_PATH
value: "/v1/chat/completions"
- name: HOST
value: "http://vllm-service:8000"
- name: MODEL_ID
value: "${MODEL_NAME}"
ports:
- containerPort: 7860
Gradio service
Create a file named gradio-service.yaml with the following content:
apiVersion: v1
kind: Service
metadata:
name: gradio-service
namespace: ${NAMESPACE_NAME}
spec:
selector:
app: gradio
ports:
- protocol: TCP
port: 8080
targetPort: 7860
type: ClusterIP
Observability configuration
For details on configuring application logging and monitoring, refer to the official Application logging and monitoring guide.
vLLM monitoring
To enable scraping of vLLM metrics by Google Cloud Managed Service for Prometheus (GMP), we
deploy a PodMonitoring resource targeting the vLLM pods.
Create a file named vllm-pod-monitoring.yaml with the following content:
apiVersion: monitoring.googleapis.com/v1
kind: PodMonitoring
metadata:
name: vllm-pod-monitoring
namespace: ${NAMESPACE_NAME}
spec:
selector:
matchLabels:
app: vllm
endpoints:
- port: http
interval: 30s
GPU monitoring
To enable scraping of GPU metrics from the NVIDIA DCGM Exporter by
Google Cloud Managed Service for Prometheus (GMP), we deploy a PodMonitoring resource in the
gpu-operator namespace. For more details, refer to
Send GPU metrics to Cloud Monitoring.
Create a file named gpu-pod-monitoring.yaml with the following content:
apiVersion: monitoring.googleapis.com/v1
kind: PodMonitoring
metadata:
name: dcgm-gmp
namespace: gpu-operator
spec:
selector:
matchLabels:
app: nvidia-dcgm-exporter
endpoints:
- port: metrics
interval: 30s
Execution steps
Deploy manifests
Apply the manifests to the cluster. Since the YAML files contain environment
variable placeholders, use envsubst to substitute the variables from your
.env file before applying them.
Navigate to your working directory containing the YAML files:
cd <local-working-dir>Source the environment variables:
source <local-config-dir>/.envApply the manifests in sequence:
# Apply core serving manifests envsubst < vllm-pvc.yaml | kubectl apply -f - envsubst < vllm-service.yaml | kubectl apply -f - envsubst < vllm-deployment.yaml | kubectl apply -f - # Apply observability manifests envsubst < vllm-pod-monitoring.yaml | kubectl apply -f - kubectl apply -f gpu-pod-monitoring.yaml # Apply Gradio UI manifests envsubst < gradio-deployment.yaml | kubectl apply -f - envsubst < gradio-service.yaml | kubectl apply -f -
Verification
Monitor pod boot sequence
Stream the container logs to track the startup sequence:
# 1. Check pod status (wait until it is Running)
kubectl get pods -n ${NAMESPACE_NAME} -l app=vllm
# 2. Stream logs to verify model loading
kubectl logs -f -l app=vllm -n ${NAMESPACE_NAME}
Wait until you see the active serving ready signal in your logs:
INFO: Application startup complete.
Establish port-forwarding tunnel
To test the endpoint locally, forward the service port 8000:
kubectl port-forward service/vllm-service 8000:8000 -n ${NAMESPACE_NAME}
Keep this terminal open or run it in the background.
Query serving endpoint
From a separate terminal, test the API responsiveness:
1. Query models list
curl -s http://localhost:8000/v1/models | jq
2. Query chat completions
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "'"${MODEL_NAME}"'",
"messages": [
{"role": "user", "content": "What is AGI in 100 words"}
],
"max_tokens": 150
}' | jq
Verify GPU and VRAM allocations
To ensure the model is utilizing the hardware efficiently and that the memory footprint is correct, you can run a GPU audit.
Audit GPU via nvidia-smi inside the pod
Retrieve the name of your active vLLM pod and execute nvidia-smi inside the
container:
# Dynamically get the pod name
export VLLM_POD_NAME=$(kubectl get pods -n ${NAMESPACE_NAME} -l app=vllm -o jsonpath='{.items[0].metadata.name}')
# Run nvidia-smi inside the container
kubectl exec ${VLLM_POD_NAME} -n ${NAMESPACE_NAME} -- nvidia-smi
Expected Output: The output should report physical VRAM utilization matching
your GPU_MEMORY_UTILIZATION parameter. For example, on a workstation with an
NVIDIA RTX PRO 6000 (approx. 96 GB VRAM) running google/gemma-4-31B-it with
GPU_MEMORY_UTILIZATION=0.95, you should see ~95,800 MiB allocated to
VLLM::EngineCore:
+-----------------------------------------------------------------------------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA RTX PRO 6000 Blac... On | 00000000:05:00.0 Off | 0 |
| N/A 36C P0 85W / 600W | 95805MiB / 97887MiB | 0% Default |
+-----------------------------------------+------------------------+----------------------+
Verify KV cache allocation in logs
You can also check the vLLM internal allocation logs to verify the KV Cache size and token concurrency:
kubectl logs ${VLLM_POD_NAME} -n ${NAMESPACE_NAME} | grep -E "Available KV cache|GPU KV cache size"
Expected Output:
(EngineCore pid=362) INFO 06-18 15:03:43 [gpu_worker.py:456] Available KV cache memory: 56.53 GiB
(EngineCore pid=362) INFO 06-18 15:03:43 [kv_cache_utils.py:1316] GPU KV cache size: 61,728 tokens
This confirms that 56.53 GiB of VRAM is reserved for context KV cache, allowing a large number of concurrent tokens.
Verify Gradio UI deployment
Once the Gradio pod is running, you can access the UI by establishing a port-forwarding tunnel.
Monitor Gradio pod status
Ensure the Gradio pod is running:
kubectl get pods -n ${NAMESPACE_NAME} -l app=gradio
Expected Output:
NAME READY STATUS RESTARTS AGE
gradio-deployment-yyyyyyyy-yyyyy 1/1 Running 0 1m
Establish port-forwarding tunnel to Gradio
Forward the Gradio service port 8080 to your local port 7860:
kubectl port-forward service/gradio-service 7860:8080 -n ${NAMESPACE_NAME}
Keep this terminal open or run it in the background:
kubectl port-forward service/gradio-service 7860:8080 -n ${NAMESPACE_NAME} > pf_gradio.log 2>&1 & sleep 3
Access the web UI
- If running on a Local Workstation: Open your browser and navigate
directly to:
http://localhost:7860 - If running inside Cloud Shell: Use the Web Preview button,
select Change Port, enter
7860, and click Change and Preview.
You should see the Gradio chatbot interface. You can type a message to interact
with the Gemma model. The Gradio pod will route the request
internally to the vllm-service endpoint.
Clean up tunnels
To stop the port-forwarding tunnel:
pkill -f "port-forward service/gradio-service"
Monitoring verification
Once vLLM is deployed and the PodMonitoring resource is applied, you can
verify that metrics are being ingested into Cloud Monitoring.
Verify PodMonitoring status
Ensure the PodMonitoring resource is created and active:
kubectl get podmonitoring -n ${NAMESPACE_NAME}
Verify metric ingestion using the Google Cloud console
You can verify that metrics are being ingested using the Google Cloud console (Metrics Explorer):
- Open the Metrics Explorer in the Google Cloud console:
- Go to
https://console.cloud.google.com/monitoring/metrics-explorer(ensure you select your project${GCP_PROJECT_ID}).
- Go to
- In the Select a metric drop-down, search for and select:
prometheus.googleapis.com/vllm:num_requests_running/gaugeto verify vLLM metrics.prometheus.googleapis.com/DCGM_FI_DEV_GPU_UTIL/gaugeto verify GPU utilization metrics.
- Observe the chart to confirm that data points are being actively plotted.
Logging verification
Once the workload is running, you can verify that logs are being exported to Cloud Logging.
Verify vLLM log ingestion
Verify that vLLM container logs are being exported to Cloud Logging:
project_id=$(gcloud config get-value project)
gcloud logging read "resource.type=\"k8s_container\" AND resource.labels.namespace_name=\"${NAMESPACE_NAME}\" AND resource.labels.container_name=\"vllm-container\"" --limit=10 --project=${project_id}
Verify GPU exporter log ingestion
Verify that GPU exporter container logs are being exported to Cloud Logging:
project_id=$(gcloud config get-value project)
gcloud logging read "resource.type=\"k8s_container\" AND resource.labels.namespace_name=\"gpu-operator\" AND resource.labels.container_name=\"nvidia-dcgm-exporter\"" --limit=10 --project=${project_id}
Verify logs using the Google Cloud console (Logs Explorer)
You can also verify log ingestion using the Google Cloud console:
- Open the Logs Explorer in the Google Cloud console:
- Go to
https://console.cloud.google.com/logs/query(ensure you select your project${GCP_PROJECT_ID}).
- Go to
In the Query box, enter the following query to view vLLM logs:
resource.type="k8s_container" resource.labels.namespace_name="<NAMESPACE_NAME>" resource.labels.container_name="vllm-container"(Replace
<NAMESPACE_NAME>with your actual namespace, e.g.,your-custom-namespace).Click Run query. You should see the log entries from the vLLM container.
To verify GPU exporter logs, run the following query:
resource.type="k8s_container" resource.labels.namespace_name="gpu-operator" resource.labels.container_name="nvidia-dcgm-exporter"