6.0 KiB
Multi-GPU-Vendor Deployment Example
Deploy the same model across NVIDIA and AMD GPUs using two LLMInferenceService instances that share a single scheduler/EPP via workload label propagation.
Problem
A cluster has both NVIDIA and AMD GPU nodes. A single LLMInferenceService can only target one GPU type because all replicas share the same pod template. Without this pattern, one GPU vendor's capacity sits idle.
Solution
- NVIDIA instance (
qwen2-7b-instruct-nvidia) — creates the scheduler/EPP, InferencePool, HTTPRoute, and Gateway. The InferencePool uses the default selector (app.kubernetes.io/name: qwen2-7b-instruct-nvidia,app.kubernetes.io/part-of: llminferenceservice,kserve.io/component: workload). - AMD instance (
qwen2-7b-instruct-amd) — has no router (no scheduler, no route, no gateway). Its pods carry the NVIDIA instance's workload labels viaspec.labels, so the EPP discovers and routes traffic to them.
Architecture
┌──────────────┐
│ Gateway │
└──────┬───────┘
│
┌──────┴───────┐
│ HTTPRoute │
└──────┬───────┘
│
┌───────────┴───────────┐
│ Scheduler / EPP │
│ (from NVIDIA isvc) │
└───────────┬───────────┘
│
InferencePool selector:
app.kubernetes.io/name: qwen2-7b-instruct-nvidia
app.kubernetes.io/part-of: llminferenceservice
kserve.io/component: workload
│
┌─────────────────┼─────────────────┐
│ │
┌──────────┴──────────┐ ┌──────────┴──────────┐
│ NVIDIA vLLM Pods │ │ AMD vLLM Pods │
│ (3 replicas) │ │ (2 replicas) │
│ nvidia.com/gpu: 1 │ │ amd.com/gpu: 1 │
└─────────────────────┘ └─────────────────────┘
How It Works
The controller sets the InferencePool selector using the NVIDIA instance's default workload labels
(app.kubernetes.io/name, app.kubernetes.io/part-of, kserve.io/component). To make the AMD pods
discoverable by the same InferencePool, the AMD instance overrides these labels via spec.labels:
# AMD instance — no router, labels match the NVIDIA InferencePool selector
spec:
labels:
app.kubernetes.io/name: qwen2-7b-instruct-nvidia
app.kubernetes.io/part-of: llminferenceservice
kserve.io/component: workload
# no router section
Because spec.labels is propagated to the pod template after the controller sets its own default labels,
the AMD pods' app.kubernetes.io/name is overwritten from qwen2-7b-instruct-amd to
qwen2-7b-instruct-nvidia, making them match the NVIDIA InferencePool's selector.
The two Deployments do not conflict despite sharing label values because Kubernetes injects a unique
pod-template-hash label into each Deployment's ReplicaSet selector, keeping pod ownership isolated.
Prerequisites
- Kubernetes cluster with both NVIDIA and AMD GPU nodes
- Nodes labeled appropriately (e.g.,
nvidia.com/gpu.present: "true",amd.com/gpu.present: "true") - Model weights accessible via HuggingFace
Deployment
# 1. Deploy the NVIDIA instance (creates InferencePool, EPP, HTTPRoute, Gateway)
kubectl apply -f llm-inference-service-qwen2-7b-nvidia-with-scheduler.yaml
# 2. Deploy the AMD instance (pods join the existing InferencePool via shared labels)
kubectl apply -f llm-inference-service-qwen2-7b-amd-no-scheduler.yaml
Configuration Summary
| Feature | NVIDIA Instance | AMD Instance |
|---|---|---|
| Replicas | 3 | 2 |
| GPU | 1x NVIDIA per replica | 1x AMD per replica |
| Scheduler / EPP | Yes (creates InferencePool + EPP) | No |
| Route / Gateway | Yes | No |
| Labels override | None (uses defaults) | Overrides app.kubernetes.io/name etc. |
Verification
# Check both services
kubectl get llminferenceservice
# Verify pods are on the correct GPU nodes
kubectl get pods -o wide -l app.kubernetes.io/name=qwen2-7b-instruct-nvidia
# Confirm the InferencePool selects pods from both instances
kubectl get inferencepool -o yaml
# Check scheduler logs for routing across all replicas
kubectl logs -l app.kubernetes.io/component=llminferenceservice-scheduler -f
# Send a test request
curl -k https://<route-url>/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2.5-7B-Instruct",
"prompt": "What is Kubernetes?",
"max_tokens": 100
}'
Scaling
Each instance can be scaled independently:
# Scale NVIDIA replicas
kubectl patch llmisvc qwen2-7b-instruct-nvidia --type merge -p '{"spec":{"replicas":5}}'
# Scale AMD replicas
kubectl patch llmisvc qwen2-7b-instruct-amd --type merge -p '{"spec":{"replicas":4}}'
The EPP automatically discovers new pods as they match the workload labels.