Running one AI inference Pod on one GPU node is relatively simple.
The architecture becomes more difficult when several models share a Kubernetes cluster and each model has different GPU, memory, latency, and scaling requirements.
One model may need an entire GPU.
Another may need only a portion of an accelerator.
A third workload may need several GPUs on the same node because its model cannot fit into the memory of one device.
At the same time, GPU nodes are expensive resources. A cluster that schedules them poorly can leave expensive accelerators idle while other workloads remain stuck waiting for capacity.
A typical CPU-oriented Kubernetes cluster often assumes that workloads are reasonably interchangeable:
Pod A -> CPU Node 1
Pod B -> CPU Node 2
Pod C -> CPU Node 3
AI workloads are different:
Model A -> GPU with 80 GB memory
Model B -> GPU with 24 GB memory
Model C -> 4 GPUs on one node
Model D -> CPU only
The scheduler therefore needs more information than simply CPU and memory requirements.
The goal is not merely to make every GPU busy.
The goal is to place workloads on appropriate hardware while preserving enough capacity for important workloads and avoiding fragmentation.
Start by Understanding GPU Capacity
Suppose a cluster contains:
Node A
4 GPUs
Node B
4 GPUs
Node C
8 GPUs
That looks like 16 GPUs.
But those 16 GPUs are not necessarily one interchangeable pool.
The GPU models may differ.
For example:
Node A
4 x GPU type A
Node B
4 x GPU type A
Node C
8 x GPU type B
A model requiring GPU type B cannot necessarily run on Node A.
Similarly, a workload requiring four GPUs on the same host cannot use:
GPU 1 from Node A
GPU 2 from Node B
GPU 3 from Node C
GPU 4 from Node C
unless the workload and communication architecture explicitly support distributed execution across those nodes.
Hardware topology therefore becomes part of scheduling.
Why GPU Fragmentation Happens
Imagine a node with four GPUs.
A workload requests one GPU:
Node A
GPU 1 -> Model A
GPU 2 -> Free
GPU 3 -> Free
GPU 4 -> Free
Now another workload requests two GPUs:
Node A
GPU 1 -> Model A
GPU 2 -> Model B
GPU 3 -> Model B
GPU 4 -> Free
The node still has one free GPU.
That sounds fine.
But suppose the next workload needs two GPUs on the same node.
It cannot fit.
The cluster might have free GPUs overall while still being unable to schedule the workload.
This is resource fragmentation.
Total free GPUs: 5
Required:
2 GPUs on one node
Available:
1 + 1 + 1 + 2 across different nodes
Result:
Depends on placement requirements
For distributed AI workloads, this can become a major scheduling problem.
Separate Workloads by Their Requirements
Before changing Kubernetes scheduling, classify the workloads.
For example:
Interactive inference
- Low latency
- Moderate concurrency
- Long-running requests
Batch inference
- High throughput
- Latency less important
- Can use spare capacity
Model training
- Multiple GPUs
- High bandwidth requirements
- Often requires coordinated startup
Embedding service
- Smaller model
- High request volume
- Short requests
These workloads should not automatically compete for capacity under the same scheduling policy.
A simple cluster may use separate node pools:
CPU Pool
|
+-- API services
+-- Controllers
+-- Data processing
Inference Pool
|
+-- Interactive models
Batch GPU Pool
|
+-- Offline workloads
Training Pool
|
+-- Multi-GPU jobs
This gives the cluster a basic capacity boundary.
Label GPU Nodes
Kubernetes labels provide a simple way to describe hardware.
For example:
kubectl label nodes gpu-node-a accelerator=nvidia
kubectl label nodes gpu-node-a gpu-memory=80gb
kubectl label nodes gpu-node-b accelerator=nvidia
kubectl label nodes gpu-node-b gpu-memory=24gb
A workload can then request suitable nodes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: large-model
spec:
template:
spec:
nodeSelector:
accelerator: nvidia
gpu-memory: 80gb
This is useful for small environments.
For larger clusters, node affinity provides more flexible selection rules.
Use Node Affinity for Hardware Selection
Instead of requiring one exact label combination, you can express preferences.
For example:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: accelerator
operator: In
values:
- nvidia
- key: gpu-memory
operator: In
values:
- 80gb
- 96gb
This tells Kubernetes that the workload requires a supported GPU class.
The important distinction is between:
Required
and:
Preferred
A required rule prevents scheduling onto unsuitable hardware.
A preferred rule influences placement but does not make the node mandatory.
Use required rules for genuine compatibility requirements.
Use preferences for optimization.
Keep General Workloads Off Expensive GPU Nodes
A common cluster design mistake is allowing ordinary workloads to run on expensive GPU nodes.
For example:
GPU Node
GPU -> AI workload
CPU -> logging Pod
CPU -> API Pod
CPU -> background worker
Those CPU workloads may consume CPU and memory needed by the GPU workload.
More importantly, they make the node part of the general scheduling pool.
Use taints to keep unrelated workloads away.
For example:
kubectl taint nodes gpu-node-a accelerator=true:NoSchedule
Then a GPU workload can use a matching toleration:
tolerations:
- key: accelerator
operator: Equal
value: "true"
effect: NoSchedule
The taint does not magically allocate a GPU.
It simply prevents workloads without the appropriate toleration from being scheduled there.
The GPU resource request still needs to be specified.
Resource Requests Matter
A GPU workload should explicitly request its accelerator resource.
For example:
resources:
requests:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: "32Gi"
nvidia.com/gpu: "1"
The exact resource name depends on the GPU integration installed in the cluster.
The important part is that Kubernetes needs a schedulable resource representing the accelerator.
Without it, the scheduler cannot reason correctly about GPU capacity.
CPU and Memory Still Matter
Requesting a GPU does not mean CPU and memory are irrelevant.
A model server may need:
GPU: 1
CPU: 8
RAM: 32 GiB
Another may need:
GPU: 1
CPU: 2
RAM: 8 GiB
If the GPU is available but the node does not have enough CPU or RAM, the Pod may remain Pending.
For example:
GPU available: Yes
CPU available: No
Memory available: Yes
Result:
Pod cannot schedule
Always inspect all resource constraints when troubleshooting placement.
Use Topology-Aware Scheduling for Multi-GPU Workloads
Some AI workloads need several GPUs.
For example:
Model
|
+-- GPU 1
+-- GPU 2
+-- GPU 3
+-- GPU 4
These GPUs may need to be on the same physical node.
The reason is communication.
A distributed model may exchange large amounts of data between accelerators.
Communication through the same host can be much faster than communication across the network.
This makes topology important.
Kubernetes has been adding workload-aware scheduling capabilities for AI and other complex workloads, including topology-aware scheduling and gang scheduling improvements in recent releases. These features are intended to help coordinate workloads whose resource requirements are more complex than independent Pods.
Understand Gang Scheduling
Imagine a job requiring four Pods:
Worker 1 -> GPU
Worker 2 -> GPU
Worker 3 -> GPU
Worker 4 -> GPU
Suppose only three GPUs are currently available.
A normal scheduler might start three workers and leave the fourth waiting.
That can be inefficient because the job may not make useful progress until all workers are available.
Gang scheduling treats the group as a unit.
Conceptually:
Required:
4 workers
Available:
3 GPUs
Result:
Do not start partial group
When four suitable GPUs become available:
GPU 1 -> Worker 1
GPU 2 -> Worker 2
GPU 3 -> Worker 3
GPU 4 -> Worker 4
This is particularly useful for distributed training and other coordinated workloads.
Do Not Use Gang Scheduling Everywhere
Gang scheduling is useful when a workload truly requires a group of resources.
It is unnecessary for a simple inference Deployment with independent replicas.
For example:
Inference
Pod A -> GPU
Pod B -> GPU
Pod C -> GPU
Each Pod can normally operate independently.
If Pod A is unavailable, Pod B can continue serving requests.
Distributed training is different:
Worker A
Worker B
Worker C
Worker D
The workers may depend on each other.
The scheduling strategy should therefore match the workload model.
Use Priority Classes
Not every AI workload has the same importance.
For example:
Priority 1
Production chat inference
Priority 2
Production embeddings
Priority 3
Batch inference
Priority 4
Experiments
Kubernetes PriorityClasses can help communicate these priorities to the scheduler.
For example:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: production-inference
value: 100000
globalDefault: false
description: "Production inference workloads"
A production inference workload can then reference it:
spec:
priorityClassName: production-inference
This becomes useful when capacity is limited.
However, priority is not a substitute for capacity planning.
If there are not enough GPUs, the highest-priority workload still cannot run until appropriate resources exist.
Use Preemption Carefully
Priority can interact with preemption.
Suppose a high-priority inference workload needs a GPU while a low-priority experimental workload occupies the available capacity.
Kubernetes may preempt the lower-priority workload if the scheduling policy allows it.
This can be useful for production protection.
But preemption has a cost.
An interrupted AI workload may have to:
Reload model weights
Rebuild caches
Restart preprocessing
Lose in-memory state
Restart a training step
For large models, that restart cost can be substantial.
Do not assign high priority to every production workload.
Use priorities to represent real operational importance.
Keep Inference and Training Separate
Inference and training can have completely different resource patterns.
Inference:
Short or medium requests
Long-lived model server
Latency-sensitive
Steady traffic
Training:
Long-running job
High GPU utilization
Multiple GPUs
Large checkpoint files
High network traffic
Running both in the same pool without scheduling controls can create problems.
A training job can consume several GPUs for hours while interactive inference requests are waiting.
Use separate node pools when the workload requires predictable isolation.
Use Resource Quotas
A namespace can have a resource quota.
For example:
apiVersion: v1
kind: ResourceQuota
metadata:
name: ai-quota
namespace: ai
spec:
hard:
requests.nvidia.com/gpu: "8"
requests.cpu: "32"
requests.memory: "256Gi"
This prevents one namespace from consuming unlimited cluster resources.
For multi-team AI platforms, quotas are especially useful.
For example:
Team A
GPU quota: 4
Team B
GPU quota: 2
Platform workloads
GPU quota: 2
The exact quota model depends on how the organization manages shared infrastructure.
Prevent One Model From Taking Everything
Suppose one deployment scales from:
2 replicas
to:
20 replicas
without limits.
The model could consume the entire GPU pool.
Other workloads would then remain Pending.
Set appropriate limits around autoscaling and namespace capacity.
For example:
Minimum replicas: 1
Maximum replicas: 4
The numbers should come from capacity planning, not arbitrary defaults.
Autoscaling should be bounded by the amount of GPU infrastructure that can actually be provided.
Watch for Pending Pods
A Pending AI Pod is not necessarily an application failure.
It may mean:
No GPU available
or:
GPU type mismatch
or:
Insufficient CPU
or:
Insufficient memory
or:
Topology requirements cannot be satisfied
Start troubleshooting with:
kubectl get pods -n ai
Then:
kubectl describe pod <pod-name> -n ai
Look at the scheduling events.
For example:
0/8 nodes are available:
2 insufficient nvidia.com/gpu
3 node affinity conflict
2 taint not tolerated
1 insufficient memory
That message is far more useful than repeatedly restarting the Pod.
Check Node Capacity
Inspect the nodes:
kubectl get nodes
Then:
kubectl describe node <node-name>
Look for:
Capacity
Allocatable
Allocated resources
The difference matters.
A node may physically contain a GPU, but Kubernetes may not advertise it as an allocatable resource.
In that situation, the problem is not scheduling policy.
The accelerator integration needs investigation.
Check GPU Device Discovery
For NVIDIA-based environments, inspect the node and GPU visibility.
A basic check might be:
nvidia-smi
From the Kubernetes side, inspect the advertised resources:
kubectl describe node <node-name>
Look for a resource such as:
nvidia.com/gpu: 4
If the node has physical GPUs but Kubernetes reports zero, investigate:
GPU drivers
Device plugin
Container runtime
Node configuration
Version compatibility
The scheduler can only schedule resources that the cluster exposes correctly.
Understand GPU Sharing
Some environments support sharing a physical GPU between workloads.
There are different approaches, including:
These approaches have different isolation and performance characteristics.
For example, time-sharing may allow several workloads to use one physical GPU, but the workloads still compete for the same underlying compute and memory resources.
Hardware partitioning can provide stronger boundaries on supported devices.
Do not assume that requesting:
0.25 GPU
automatically gives a Pod one-quarter of a GPU.
The cluster's accelerator integration determines how sharing works.
Use GPU Partitioning for Smaller Models
Suppose a small embedding model uses only part of a GPU.
Running one model per physical GPU could leave capacity unused.
If the hardware and platform support GPU partitioning, several workloads may be placed on one accelerator.
Conceptually:
Physical GPU
|
+-- Partition A -> Embedding model
|
+-- Partition B -> Reranker
|
+-- Partition C -> Small inference model
This can improve utilization.
But partitioning also introduces limits.
A workload that later needs more memory cannot necessarily borrow memory from another partition.
Choose partition sizes based on measured workloads.
Avoid Packing by GPU Count Alone
Two workloads may both request:
1 GPU
but have very different memory and throughput requirements.
For example:
Model A
1 GPU
10 GB memory
Model B
1 GPU
70 GB memory
If both are treated as identical scheduling units, placement can be inefficient.
Use hardware labels, node pools, partitioning, or other mechanisms when the distinction matters.
The Kubernetes scheduler cannot infer application-level model requirements from the number one.
Think About Network Bandwidth
Distributed AI workloads can generate large amounts of network traffic.
For example:
GPU 1
|
+---- Network ---- GPU 2
|
+---- Network ---- GPU 3
|
+---- Network ---- GPU 4
If the workload frequently exchanges large tensors, network bandwidth and latency can become important.
A cluster with enough GPUs but insufficient network performance may still deliver poor results.
For multi-node training, consider:
Do not evaluate an AI cluster based on GPU count alone.
Storage Can Become Another Bottleneck
Model weights can be large.
If every new inference Pod downloads several gigabytes before becoming ready, scaling can be slow.
For example:
Pod starts
|
v
Download model
|
v
Load model
|
v
Initialize runtime
|
v
Ready
A sudden scale-out can therefore create significant storage and network traffic.
Use appropriate image and model distribution strategies.
Where supported, consider:
Local model caches
Shared high-performance storage
Preloaded model artifacts
Efficient container images
Model repositories
The goal is to reduce the time between Pod scheduling and actual inference readiness.
Separate Model Storage From Container Images
A model does not always need to be embedded directly inside the container image.
For example:
Container image
|
+-- Model server
+-- Runtime
+-- Dependencies
Model storage
|
+-- Model weights
+-- Tokenizer
+-- Configuration
This allows the model to change independently from the serving image.
However, external model storage adds its own startup dependency.
If the storage system is slow or unavailable, the model server may never become ready.
Choose the approach based on operational requirements.
Design for GPU Node Failure
A production inference cluster should assume that a GPU node can fail.
Suppose:
Node A
4 GPUs
Node B
4 GPUs
If Node A fails, the cluster loses half its inference capacity.
Kubernetes can recreate the Pods, but only if Node B or another available node has enough capacity.
This creates a useful distinction:
Pod redundancy
versus:
Hardware redundancy
Three replicas on one GPU node do not provide the same failure protection as replicas spread across multiple GPU nodes.
Use topology spread constraints when appropriate.
Spread Replicas Across Nodes
A workload with three replicas should ideally avoid placing all three on one node when availability matters.
For example:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: inference-server
This tells Kubernetes to distribute matching Pods across the specified topology domain.
The exact policy should reflect the workload.
For a development environment, strict spreading may waste capacity.
For production inference, it can provide better fault isolation.
Do Not Over-Spread Tiny Clusters
There is a tradeoff.
Suppose the cluster contains only two GPU nodes and a workload requires four GPUs.
A strict spreading rule may make scheduling impossible.
Topology constraints should therefore be evaluated alongside:
GPU count
Node count
Model requirements
Availability goals
Cost
Scheduling rules are not free.
Every additional constraint reduces the scheduler's placement options.
Use a Dedicated Inference Namespace
A dedicated namespace provides a useful operational boundary.
For example:
ai-inference
ai-training
ai-batch
The namespace can contain:
Deployments
Services
ConfigMaps
Secrets
HPA
ResourceQuotas
NetworkPolicies
This makes it easier to apply policies consistently.
It also makes resource usage easier to monitor by workload category.
Add Network Policies
AI inference services may handle sensitive prompts or generated responses.
Do not allow every Pod in the cluster to communicate freely with the model servers.
A simple policy can restrict access to approved application namespaces.
For example:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: inference-access
namespace: ai-inference
spec:
podSelector:
matchLabels:
app: inference-server
policyTypes:
- Ingress
The exact rules depend on the cluster networking implementation.
The important design principle is to treat inference endpoints as internal services that should have controlled access.
Monitor Utilization at Multiple Levels
A useful AI capacity dashboard should show:
Cluster
|
+-- GPU capacity
+-- GPU allocated
+-- GPU free
|
+-- Pending Pods
|
+-- Node health
|
+-- Inference latency
+-- Queue length
+-- Throughput
|
+-- Training jobs
+-- Batch jobs
You need both infrastructure and application metrics.
For example:
GPU utilization: 40%
Pending Pods: 8
This could indicate that the GPUs are the wrong type for the pending workloads.
Alternatively:
GPU utilization: 95%
Pending Pods: 0
This may indicate good utilization.
The numbers only become useful when you understand the workload behind them.
Build Capacity Around Peak Traffic
Do not size the cluster only for average traffic.
Suppose:
Average:
2 inference requests/second
Peak:
20 requests/second
The cluster must handle the peak according to the required latency target.
However, buying enough GPUs to permanently handle the peak may be expensive.
Use autoscaling, batch scheduling, priority, and workload separation to manage predictable peaks.
For workloads with strict latency requirements, maintaining warm capacity may be necessary.
For batch workloads, queueing can be acceptable.
A Practical Multi-GPU Cluster Architecture
A production-oriented cluster might look like this:
AI Gateway
|
+------------+------------+
| |
v v
Interactive Pool Batch Pool
| |
+------+------+ +----+----+
| | | |
v v v v
Model A Model B Job 1 Job 2
| | | |
GPU Node GPU Node GPU Node GPU Node
| | | |
+-------------+-------------+---------+
|
v
Kubernetes Scheduler
|
+-------------+-------------+
| |
v v
Hardware Constraints Resource Policies
- GPU type - Priority
- GPU memory - Quota
- Topology - Affinity
- Network - Taints
This is more realistic than treating the entire GPU fleet as one undifferentiated resource pool.
Common Mistakes
Counting GPUs without checking their type
Eight GPUs are not necessarily equivalent to eight interchangeable GPUs.
Allowing ordinary workloads onto GPU nodes
This can waste expensive accelerator capacity and complicate scheduling.
Requesting a GPU without CPU or memory requirements
The Pod may still remain Pending because another resource is exhausted.
Treating multi-GPU jobs as independent Pods
Some distributed workloads require coordinated scheduling and topology.
Using priority for everything
If every workload is high priority, priority loses its purpose.
Assuming GPU sharing is automatic
GPU sharing depends on the accelerator platform and its configuration.
Ignoring model download time
A scale-out event can take much longer when every Pod needs to download large model artifacts.
Placing all replicas on one node
This creates a single hardware failure domain.
Adding too many scheduling constraints
Over-constrained workloads may become impossible to schedule even when the cluster has free resources.
Measuring only GPU utilization
Pending Pods, queue length, memory usage, and latency provide important context.
Production Checklist
Before running multiple AI workloads on a shared Kubernetes GPU cluster, verify:
GPU nodes are correctly registered with Kubernetes.
GPU types are labeled where hardware differences matter.
GPU memory capacity is known.
GPU nodes are protected with taints when appropriate.
AI workloads have explicit GPU requests.
CPU and memory requirements are also defined.
Interactive and batch workloads are separated when necessary.
Training workloads have appropriate scheduling rules.
Priority classes represent real business importance.
Resource quotas prevent one team from consuming the entire cluster.
Autoscaling has sensible maximum limits.
Multi-GPU jobs have appropriate topology requirements.
Gang scheduling is used only where coordinated startup is necessary.
Replicas are spread across failure domains where availability matters.
GPU sharing is configured deliberately.
Model startup and download time are measured.
Model storage is reliable and fast enough for scaling events.
Network bandwidth is sufficient for distributed workloads.
Pending Pods are monitored.
GPU, CPU, memory, and queue utilization are visible.
Inference and training traffic do not unintentionally compete for the same capacity.
Node failure scenarios have been tested.
Summary
A Kubernetes AI cluster should not be designed around GPU count alone.
The real scheduling problem is:
Workload requirements
+
Hardware capabilities
+
Topology
+
Priority
+
Capacity
A one-GPU inference workload is relatively simple.
A shared cluster containing different models, batch jobs, training workloads, and multi-GPU applications requires more careful scheduling.
Use labels and affinity to select appropriate hardware.
Use taints and tolerations to keep unrelated workloads away from expensive GPU nodes.
Use resource requests and quotas to control capacity.
Use priority for workloads that genuinely need preferential scheduling.
For distributed workloads, consider topology and gang scheduling so that the scheduler understands that several resources may need to become available together. Recent Kubernetes releases continue to add workload-aware scheduling features for AI and other resource-intensive applications.
Most importantly, monitor the entire path.
A GPU can be idle while Pods are Pending because the available GPUs are the wrong type.
A GPU can be busy while latency remains acceptable.
A cluster can have enough total GPUs but still fail to schedule a four-GPU job because those GPUs are fragmented across nodes.
Good AI infrastructure therefore comes from understanding how the workload actually uses hardware rather than simply adding more accelerators.
When Kubernetes scheduling, GPU topology, inference routing, quotas, and workload priorities are designed together, a shared GPU cluster can support more AI workloads without turning every scaling problem into another expensive hardware purchase.
Join the conversation! Your thoughts help the community grow.