How to Share GPUs Fairly Across Teams with Kubernetes Kueue (Instead of Buying More GPUs)
A practical guide to Kubernetes Kueue - ResourceFlavors, ClusterQueues, cohorts, quota borrowing, preemption and Fair Sharing - so your AI training and inference teams share the GPUs you already own instead of buying more.
Kueue holds a Job suspended until its full quota is free, then releases it. Kueue is a Kubernetes-native job queueing controller from the kubernetes-sigs project. It decides when a workload starts and against which quota. kube-scheduler still decides which node each pod lands on.
Fair GPU sharing takes four objects. You need a ResourceFlavor per GPU type, one ClusterQueue per team with a nominalQuota on nvidia.com/gpu, a shared cohort so idle quota can be borrowed, and a LocalQueue per team namespace that users target with the kueue.x-k8s.io/queue-name label. Three teams fit in under 100 lines of YAML.
Measure two numbers before you buy hardware. Track average and p95 DCGM_FI_DEV_GPU_UTIL per namespace, plus kueue_pending_workloads and p95 time-to-admission. Low utilization with long queues is a scheduling problem. High utilization with long queues is the only case that justifies new capacity.
Kueue admits whole workloads and does not partition GPUs. A Workload is only unsuspended when quota for all its pods can be reserved, which prevents two half-placed training jobs from deadlocking. Splitting one physical GPU is the job of MIG, time-slicing or MPS, configured in the NVIDIA device plugin below Kueue.
Kueue covers admission and fairness, nothing else. Cluster provisioning, GPU node pools, drivers, per-team RBAC and self-service deployment stay your problem. That platform layer is where Qovery fits, running inside your own AWS, GCP, Azure or Scaleway account, or your existing Kubernetes cluster.
Most teams asking for more GPUs have a queueing problem dressed up as a capacity problem. I have watched companies sign multi-year H100 reservations while their existing nodes sat idle every night.
The data backs this up. In ClearML's 2024 survey with the AI Infrastructure Alliance and FuriosaAI, only 7% of companies said their GPU infrastructure exceeds 85% utilization even at peak, and 15% said fewer than half of the GPUs they bought are in use (ClearML, State of AI Infrastructure at Scale 2024). Microsoft's two-month trace of its multi-tenant Philly GPU cluster found gang scheduling and locality constraints were major drivers of queueing delay and lost utilization (Jeon et al., USENIX ATC '19).
This guide shows how to fix that with Kueue. Every YAML block below uses the kueue.x-k8s.io/v1beta2 API from Kueue v0.20.4, checked against the docs on October 31, 2026.
Why do GPUs sit idle while teams complain they cannot get any?
GPUs sit idle because teams pin themselves to dedicated node pools and hold that capacity as insurance, so average utilization collapses while other teams' jobs sit Pending with no ETA. The shortage comes from ownership and missing queueing, and buying hardware makes it worse.
I see the same four anti-patterns in almost every cluster:
One namespace per team, pinned to its own GPU node pool. Capacity cannot move between teams even when one pool is empty.
nodeSelectors hard-coded in manifests. A job can only run on "its" nodes, so a free H100 next door is invisible to it.
"My team paid for these 8 H100s" ownership. Budget lines turn into node pools, and nobody lends what they paid for.
Notebooks that reserve a GPU for 8 hours and use it for 20 minutes. The GPU counts as allocated all day.
Plain Kubernetes makes hoarding the rational move. A ResourceQuota is an admission plugin, and when a request exceeds it "the control plane rejects that request with HTTP status code 403 Forbidden" (Kubernetes docs). There is no queue, no ETA and no borrowing. If you give up your quota, you may never get it back.
Vanilla Kubernetes also has no all-or-nothing placement. Two 4-GPU distributed training jobs on a 4-GPU cluster can each get 2 pods placed and wait forever for the rest. The Kueue docs describe this exact deadlock: pods that start "hold onto resources while waiting for the rest" (Kueue all-or-nothing scheduling).
Allocation is also not utilization. A node can be 100% allocated in kubectl describe node and 8% busy. Measure real usage with NVIDIA's dcgm-exporter, whose default counters include DCGM_FI_DEV_GPU_UTIL (GPU utilization in %). Enable its Kubernetes pod mapping (DCGM_EXPORTER_KUBERNETES=true) so each series carries the pod and namespace that holds the GPU (NVIDIA DCGM Exporter docs).
The economics fit in one line. An AWS p5.48xlarge (8x H100) costs $55.04 per hour on demand in us-east-1 (AWS EC2 On-Demand pricing, checked October 31, 2026), and it bills the same at 8% and at 80% utilization. A queue with a guaranteed floor is what makes teams stop hoarding, and that is what Kueue implements.
What is Kubernetes Kueue and what problem does it actually solve?
Kueue is a Kubernetes-native job queueing controller that keeps a workload suspended until its entire resource request fits in an available quota, then unsuspends it so kube-scheduler can place the pods. It adds multi-team quota, borrowing between teams, preemption and whole-workload admission, which ResourceQuota cannot do.
The project describes itself as "a kubernetes-native system that manages quotas and how jobs consume them" and states it "does not replace any existing Kubernetes components" (Kueue overview). It lives in the kubernetes-sigs GitHub org under the Apache 2.0 licence, is developed with WG Batch and SIG Scheduling, and its core APIs are v1beta2 (Kueue on GitHub).
Mechanically, it works in four steps:
You create a Job with the kueue.x-k8s.io/queue-name label. Kueue's webhook sets spec.suspend: true (Kueue: Run a Job).
Kueue creates a Workload object that summarizes the Job's pod sets and resource requests.
When the target ClusterQueue (or its cohort) has quota for the whole Workload, Kueue admits it and injects the flavor's node labels and tolerations.
Kueue flips spec.suspend to false, and kube-scheduler places the pods as usual.
The six objects, in plain language:
ResourceFlavor: what kind of hardware (H100 on-demand, H100 spot, L4).
ClusterQueue: how much of each flavor a team is guaranteed.
LocalQueue: the team's entry point inside its namespace.
Workload: the queued unit Kueue creates for each Job.
Cohort: the group of ClusterQueues that lend and borrow from each other.
AdmissionCheck: an external gate, such as ProvisioningRequest for node autoscaling.
Kueue integrates with batch/v1 Job and CronJob, JobSet, Kubeflow Trainer v2 TrainJob, Kubeflow v1 jobs (PyTorchJob, MPIJob, TFJob, JAXJob and more), RayJob, RayCluster and RayService, LeaderWorkerSet, AppWrapper, plain Pods, Deployment and StatefulSet (Kueue integrations index). Deployment support is weaker than Job support. Kueue manages each Deployment pod as an independent Plain Pod, so under quota pressure "the Deployment might run only a subset of Pods" (Kueue: Run Deployment).
Whole-workload admission is the feature that matters for distributed training. By default, Kueue unsuspends a Workload only when quota for all its pod sets can be reserved at once. Quota is an aggregate number though, so Kueue adds waitForPodsReady (evict and requeue if pods are not all ready within a timeout) and Topology-Aware Scheduling, which checks real node capacity before admission (Kueue all-or-nothing scheduling).
Kueue is explicitly not a GPU partitioner (MIG, time-slicing and MPS do that), not a node autoscaler (that is Cluster Autoscaler or Karpenter), not a cost or chargeback tool, and not a replacement for kube-scheduler. Google built the GKE path for its Dynamic Workload Scheduler on Kueue plus the ProvisioningRequest API (Google Cloud blog).
Here is how it compares with the alternatives I get asked about most:
Kueue
Volcano
Apache YuniKorn
NVIDIA Run:ai
ResourceQuota + PriorityClass
Queueing model
Job-level queues (ClusterQueue, LocalQueue) in front of kube-scheduler
Separate batch scheduler with Volcano Queues
Separate scheduler with hierarchical queues
Commercial platform with projects and departments, scheduler open-sourced as KAI
No queue; requests over quota get 403
Gang / all-or-nothing
Whole-workload quota admission, plus waitForPodsReady and TAS
Native PodGroup minMember gang scheduling
Gang scheduling built in
Podgroups as the atomic unit
None
Fair sharing
Cohorts, hierarchical cohorts, Fair Sharing with weights
Proportional and DRF-based plugins
Hierarchical queues with FIFO or FAIR ordering
Quota plus over-quota weight per queue
None
Borrowing and reclaim
borrowingLimit, lendingLimit, reclaimWithinCohort
Queue reclaim of lent capacity
Guaranteed min, max per queue
Over-quota use with reclaim
None
Preemption
Within queue, within cohort, Fair Sharing strategies
Preempt and reclaim actions
Built-in preemption
Priority and fairness based
Pod priority only, not quota-aware
Fractional GPU
None itself; quotas MIG, time-sliced, HAMi vGPU or DRA resources
Commercial NVIDIA (acquisition announced April 2024, NVIDIA); KAI Scheduler Apache 2.0 (NVIDIA)
Kubernetes core
Best fit
Multi-team quota on the default scheduler
HPC-style gang-heavy batch
Spark and big-data shops with deep queue trees
Enterprises that want fractional GPUs and a vendor UI
Single team, no contention
My take: Volcano has the stronger gang-scheduling and HPC heritage, YuniKorn brings deeper queue hierarchies and Spark experience, and Run:ai is the pick when you want fractional GPUs with a vendor support contract. Kueue wins when you want the native option that keeps kube-scheduler, uses a small quota and cohort model, and already speaks the CRDs your ML teams use. If you run a classic Slurm shop with no Kubernetes, stay on Slurm.
How do you set up Kueue for multi-team GPU quota, step by step?
A multi-team GPU setup is five steps: install the NVIDIA GPU Operator so nvidia.com/gpu is countable, install Kueue from its release manifest, declare one ResourceFlavor per GPU type, create one ClusterQueue per team in a shared cohort, and give each team a LocalQueue. The YAML below is copy-pasteable for a 24-GPU cluster of three 8x H100 nodes.
Step 1: make GPUs countable. Install the NVIDIA GPU Operator or the device plugin so nodes advertise nvidia.com/gpu. Label nodes by GPU type and capacity type, and taint them with nvidia.com/gpu:NoSchedule. Kueue can only quota a resource Kubernetes already counts.
Step 2: install Kueue. The current release is v0.20.4 (Kueue releases):
Step 3: one ResourceFlavor per GPU type.nodeLabels must match your real node labels (on GKE that is cloud.google.com/gke-accelerator, with Karpenter karpenter.sh/capacity-type). Kueue injects the tolerations into admitted pods (Kueue ResourceFlavor):
Repeat for h100-spot, a100-80gb or l4 with the matching labels.
Step 4: one ClusterQueue per team, all in one cohort. Quota cpu and memory alongside nvidia.com/gpu, so a job is never admitted without the CPU its dataloaders need (Kueue ClusterQueue):
Create applied-ml and inference the same way, changing the namespace selector and limits from the cohort table in the next section.
Step 5: one LocalQueue per team namespace. A data scientist changes exactly one thing in their manifest, the queue label:
YAML
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
namespace: research
name: research-queue
spec:
clusterQueue: research
Submit work. The webhook suspends labelled Jobs for you. Setting spec.suspend: true yourself is harmless and keeps pods from starting if the job is ever created while the webhook is unavailable:
Watch it with kubectl -n research get workloads, kubectl -n research describe workload <name> and kubectl get clusterqueue -o wide.
Publish the wait time. Kueue exports kueue_pending_workloads (labelled by cluster_queue and status), kueue_admitted_active_workloads, kueue_admitted_workloads_total and the kueue_admission_wait_time_seconds histogram (Kueue metrics reference). A Grafana panel saying "research waits 12 minutes at p95" is what convinces a team to give up its private node pool:
PROMQL
histogram_quantile(0.95, sum by (le, cluster_queue) (rate(kueue_admission_wait_time_seconds_bucket[1h])))
Common first-run failures:
The kueue.x-k8s.io/queue-name label is missing, so Kueue ignores the Job.
ResourceFlavor nodeLabels do not match real node labels, so admitted pods stay Pending.
Quota covers nvidia.com/gpu but not cpu and memory, so the Workload is inadmissible.
Jobs are created before the Kueue webhook is ready.
Job manifests carry their own hard-coded nodeSelector that conflicts with the flavor.
Ship faster on infrastructure you control.
Qovery gives your team self-service deployments on your own AWS, GCP, Azure, or Scaleway account - or your existing Kubernetes cluster. Start deploying in under 10 minutes.
How do cohorts, borrowing and preemption make GPU sharing actually fair?
Fairness comes from putting every team's ClusterQueue in one cohort. nominalQuota is each team's guaranteed floor, borrowingLimit caps how far it can burst into idle capacity, lendingLimit caps what it gives away, and reclaimWithinCohort lets an owner take its floor back by preempting borrowers.
The documented defaults matter, so check them before you copy anyone's YAML (Kueue ClusterQueue):
nominalQuota: the resources guaranteed to the queue at any time.
borrowingLimit: if unset, the queue can borrow up to the sum of every other queue's nominal quota in the cohort.
lendingLimit: if unset, the queue lends out all its unused resources.
reclaimWithinCohort: defaults to Never. LowerPriority reclaims only from lower-priority borrowers. Any reclaims regardless of priority, and under classic preemption only when the pending workload fits in the queue's own nominal quota.
withinClusterQueue: defaults to Never. LowerPriority lets a production fine-tune evict the same team's sweep. LowerOrNewerEqualPriority also evicts newer workloads of equal priority.
Priority comes from a WorkloadPriorityClass, attached with the kueue.x-k8s.io/priority-class label:
Classic preemption ranks by priority. Fair Sharing ranks by a share value that measures how much each ClusterQueue borrows, weighted by .spec.fairSharing.weight. You enable Fair Sharing cluster-wide in the Kueue Configuration, not per ClusterQueue, and it does not support borrowWithinCohort (Kueue preemption):
I use classic priority when teams agree on a ranking of work, and Fair Sharing when the argument is about who has been hogging the cluster.
Preemption only pays off if training checkpoints. A preempted Workload is evicted and requeued, and its pods are terminated. Write checkpoints to object storage at a fixed step interval, handle SIGTERM within the pod's terminationGracePeriodSeconds, and resume from the latest checkpoint on start. Use waitForPodsReady.requeuingStrategy to control backoff, and the kueue.x-k8s.io/max-exec-time-seconds label to cap runaway jobs (Kueue Workload). Our guide to surviving spot GPU reclaim covers the checkpoint side in depth.
Spot capacity plugs in as a second flavor. List h100-ondemand first and h100-spot second, then set flavor fungibility:
With whenCanBorrow: TryNextFlavor, a job that would have to borrow on-demand quota tries the spot flavor first instead of queueing. The default for whenCanBorrow is MayStopSearch, which borrows from the first flavor.
Here is the cohort I would start with for 24 H100s across three teams:
Team
Workload type
nominalQuota (guaranteed GPUs)
borrowingLimit
lendingLimit
reclaimWithinCohort
withinClusterQueue
Guaranteed vs burst
research
Multi-node training, weekend sweeps
8
12
unset (lends all 8)
Any
LowerPriority
8 guaranteed, up to 20
applied-ml
Fine-tunes, evals, batch inference
8
8
unset (lends all 8)
Any
LowerOrNewerEqualPriority
8 guaranteed, up to 16
inference
Batch inference plus scale-out buffer
8
0
4
Any
Never
8 guaranteed, never bursts, 4 always held back
The worked example: on Saturday, applied-ml and most of inference are idle, so the research sweep borrows 8 GPUs from applied-ml and 4 from inference and runs on 20. On Monday at 9am, applied-ml submits an 8-GPU fine-tune. It fits within its own nominal quota, so on its next scheduling attempt Kueue preempts enough borrowed sweep workloads to free 8 GPUs. The sweep requeues and resumes from its last checkpoint.
For org structures, hierarchical cohorts let a department Cohort sit above team ClusterQueues using spec.parentName, and a Cohort can hold its own shared quota (Kueue Cohort). The Cohort object is part of the v1beta2 API, but the Fair Sharing docs note that the no-preemption-cycle proof does not yet cover the hierarchical case (Kueue Fair Sharing). Start flat and add levels when you have more than one department fighting over one pool.
Does Kueue work for inference, or only for training jobs?
Kueue is built for workloads that finish, such as training, fine-tuning, batch inference and evaluation sweeps. Its Deployment and StatefulSet support is narrower, so long-running online inference belongs on a ClusterQueue with lendingLimit: 0 or a dedicated node pool with a high PriorityClass, never preemptible by training.
Batch and offline inference, evals and dataset generation are just Jobs or JobSets. Queue them exactly like training, and mark them preemptible with a low WorkloadPriorityClass.
Online inference needs guaranteed capacity. The Kueue docs recommend reserving quota for critical serving with lendingLimit so it can scale out fast (Kueue: Run Deployment). Set it to 0 and training can never borrow those GPUs.
Small models waste big GPUs. MIG splits an A100 80GB or H100 80GB into up to 7 isolated 1g.10gb instances (NVIDIA MIG User Guide). Time-slicing and MPS are configured in the NVIDIA device plugin and GPU Operator (NVIDIA GPU sharing). Each method changes what the node advertises, and Kueue quotas whatever units appear. Time-slicing gives no memory isolation, so keep it for dev and small inference.
For multi-node training that must land in one rack or NVLink domain, use Topology-Aware Scheduling. It has been beta since Kueue v0.14 and is enabled by default, using the kueue.x-k8s.io/podset-required-topology annotation on the pod template (Kueue TAS).
Inference endpoints should give GPUs back when idle. Use HPA or KEDA on request rate or queue depth and scale to zero where latency allows, so the cohort can lend that capacity.
For autoscaled clusters, the ProvisioningRequest admission check delays admission until Cluster Autoscaler reports capacity, using classes such as check-capacity.autoscaling.x-k8s.io or best-effort-atomic-scale-up.autoscaling.x-k8s.io (Kueue all-or-nothing scheduling). ProvisioningRequest is a Cluster Autoscaler API. With Karpenter, check your version's support; otherwise Karpenter provisions nodes only after Kueue admits the workload, and waitForPodsReady is your safety net.
Workload type
Recommended mechanism
Preemptible
Main caveat
Distributed multi-node training
ClusterQueue + TAS + waitForPodsReady
Yes, if it checkpoints
Restarts from step zero without checkpoints
Single-node fine-tune
ClusterQueue with a production WorkloadPriorityClass
Only by higher priority
Needs the cpu and memory quota too
Hyperparameter sweep
Cohort borrowing at low priority, spot flavor fallback
Yes
Many small trials can fragment nodes
Batch / offline inference
Job or JobSet on a ClusterQueue, MIG for small models
Yes
Needs idempotent, resumable batches
Online inference endpoint
Dedicated node pool or ClusterQueue with lendingLimit: 0, HPA or KEDA
No
Kueue manages Deployment pods one by one
Interactive notebooks
Time-sliced or MIG GPUs plus auto-stop
No, stop them instead
Idle sessions hold GPUs all day
Kueue is the wrong tool for single-team clusters with no contention, pure Slurm HPC shops, and serving-only clusters with no batch work.
When is buying more GPUs actually the right answer?
Buy more GPUs only when measured DCGM utilization on the GPUs you own stays high and p95 time-to-admission stays unacceptable after quota, borrowing and preemption are tuned. Every other combination of those two numbers is cheaper to fix in software.
Collect three numbers before any purchase request:
Average and p95 DCGM_FI_DEV_GPU_UTIL per team, for example avg by (namespace) (avg_over_time(DCGM_FI_DEV_GPU_UTIL[7d])). With kube-prometheus the pod's namespace often appears as exported_namespace.
kueue_pending_workloads and p50/p95 of kueue_admission_wait_time_seconds per ClusterQueue.
GPU-hours burned by jobs that were later cancelled or crashed.
Measured DCGM GPU utilization
p95 queue wait
What it means
First action
Typical lever
Buy hardware?
Low
Long
Scheduling problem: GPUs are reserved but idle
Put every team in one Kueue cohort
Borrowing, reclaim, auto-stop for notebooks
No
High
Long
Genuine capacity need
Confirm after tuning quotas for 2-4 weeks
Spot flavor first, then reservations
Yes, after spot
Low
Short
You over-bought
Release or resell reservations
Right-size to L4 or A10G, MIG
No, shrink
High
Short
Healthy cluster
Leave it alone
Keep watching the two metrics
No
My rule of thumb: "low" means average utilization under 50% across a week. Your threshold may differ, but write it down before the budget meeting.
The cheaper levers before capex, by effort: spot or preemptible GPU flavors, right-sizing small models to L4 or A10G instead of H100, MIG for inference, auto-stopping idle dev and staging environments overnight, and moving batch work to a region with available capacity. Our GPU capacity guide covers where to find it.
The idle cost, with the arithmetic. A week has 168 hours. If a team works 10 hours a day for 5 days, 118 hours a week are nights and weekends. That is 118 × 52 / 12 ≈ 511 hours a month, so call it 500. One idle p5.48xlarge at $55.04 per hour costs 500 × $55.04 = $27,520 a month in nights and weekends alone. Three of them, the 24-GPU cluster above, cost $82,560. A p4d.24xlarge (8x A100) at $21.96 per hour costs 500 × $21.96 = $10,980 (AWS EC2 On-Demand pricing, us-east-1, checked October 31, 2026).
Spot is usually the biggest single lever. AWS advertises Spot at up to 90% off On-Demand (AWS EC2 Spot), and Google Cloud lists Spot VMs, GPUs included, at up to 91% off (Google Cloud Spot VMs). Those are ceilings. H100 spot discounts are often lower and change daily, so check current prices in your region before you build a plan on them.
The capex path, honestly: committed-use discounts, Savings Plans and multi-year GPU reservations beat on-demand for steady base load. Size them to your measured floor, the sum of nominal quotas that stay busy, and burst on-demand or spot above it. Running in your own cloud account matters here, because those reservations and discounts stay in your name and follow you, not a vendor.
Who runs the cluster that runs Kueue, and where does the platform layer fit?
Kueue assumes someone already operates a healthy Kubernetes cluster with GPU node pools, drivers, RBAC and a self-service path for teams to ship. For most companies that operational layer, not scheduling, is the real bottleneck, and it is a separate job from Kueue.
What Kueue leaves to you: cluster provisioning and version upgrades, GPU node pool and driver lifecycle, per-team RBAC, environment lifecycle, cost attribution, and a way for data scientists to deploy without filing a ticket. The division of labour in one line: Kueue owns admission and fairness, and the platform owns everything around it.
That platform layer is what we build at Qovery. Qovery deploys and operates your apps inside your own AWS, GCP, Azure or Scaleway account, or on a Kubernetes cluster you already run. Kueue, the NVIDIA GPU Operator and your node pools stay entirely yours, and the cloud bill stays in your name.
The Qovery capabilities that matter to AI teams:
Managed cluster upgrades, so the Kubernetes version under Kueue does not drift.
Per-environment RBAC, matching the team boundaries your ClusterQueues encode.
Git-push deployments for the inference APIs, feature stores and dashboards around your GPU jobs.
Preview environments per pull request, so a new inference service is tested before it reaches production.
Environment auto-stop, so GPU-backed non-production environments do not idle overnight.
The boundary, stated plainly: Qovery does not replace Kueue, does not schedule GPUs and does not partition them. You install Kueue on the cluster Qovery manages, or on your own cluster that Qovery connects to. For the broader tool map, see our multi-cloud GPU orchestration comparison.
This combination fits teams shipping AI workloads on their own cloud account who want Kubernetes without operating it directly, and who do not have a dedicated DevOps engineer.
How do I do this with Qovery from Claude Code or Cursor?
Install the Qovery Agent Skills once, and your coding agent can onboard a cluster, deploy the services around your GPU jobs and troubleshoot them in plain language. Every action is scoped and sent to you for approval. Four commands, in this order.
The skills work with Claude Code, Cursor, Codex, OpenCode and Gemini CLI. Install them globally:
Add the Qovery MCP Server to Claude Code. It uses OAuth and is read-only by default:
BASH
claude mcp add --transport http qovery https://mcp.qovery.com/mcp --callback-port 4242
Enable write access only when you want the agent to change infrastructure. This also needs write access turned on in the Qovery Console:
BASH
claude mcp add --transport http qovery "https://mcp.qovery.com/mcp?read_write=true" --callback-port 4242
The skills (qovery-onboard, qovery-deploy, qovery-troubleshoot, qovery-optimize, qovery-speedup, qovery-preview and qovery-terraform) set things up. The MCP Server operates what already exists.
Then ask in plain language, for example "deploy this inference API to my GCP account with Qovery" or "stop the non-production environments overnight". Details are in the AI agent quickstart and the Claude Code integration page.
Frequently asked questions
What is Kubernetes Kueue used for?
Kueue queues batch and AI workloads and admits them against per-team quotas. It suspends a Job labelled with kueue.x-k8s.io/queue-name, waits until its ClusterQueue or cohort has enough quota for the whole workload, then unsuspends it for kube-scheduler. Teams use it to share GPUs with guaranteed floors, borrowing and preemption.
How do I set up GPU quotas per team in Kubernetes?
Create a ResourceFlavor per GPU type, then one ClusterQueue per team with a nominalQuota on nvidia.com/gpu, cpu and memory, all sharing one cohortName. Add a LocalQueue in each team namespace that points to its ClusterQueue. Users then add the kueue.x-k8s.io/queue-name label to their Jobs.
How does Kueue differ from Kubernetes ResourceQuota and PriorityClass?
ResourceQuota rejects requests over the limit with HTTP 403 and never queues, and PriorityClass only orders pods for kube-scheduler. Kueue holds workloads in a queue, admits whole workloads, lends idle quota across a cohort with borrowingLimit and lendingLimit, and reclaims it with reclaimWithinCohort. You can run both, with ResourceQuota as a hard ceiling.
Can Kueue preempt a running GPU training job, and will the job lose its progress?
Yes. With reclaimWithinCohort or withinClusterQueue set, Kueue evicts the Workload, terminates its pods and requeues it. The job keeps its progress only if your training code writes checkpoints and resumes from the latest one on restart. Without checkpoints it starts again from step zero.
What is the difference between Kueue and Volcano for GPU scheduling?
Volcano is a separate batch scheduler with native PodGroup gang scheduling and a strong HPC heritage, and it is a CNCF Incubating project. Kueue keeps the default kube-scheduler and adds quota-based queueing, cohorts and Fair Sharing in front of it. Pick Volcano for gang-heavy HPC batch, and Kueue for multi-team quota on standard Kubernetes.
Can two teams share the same physical GPU with Kueue?
Not through Kueue alone, because Kueue counts nvidia.com/gpu units and does not partition them. Configure MIG, time-slicing or MPS in the NVIDIA GPU Operator or device plugin, which makes one physical GPU appear as several units. Kueue then quotas those units like any other resource.
Does Kueue work with Ray, Kubeflow Trainer and PyTorch distributed training, and does it support inference Deployments?
Yes for Ray (RayJob, RayCluster, RayService), Kubeflow Trainer v2 TrainJob and Kubeflow v1 jobs including PyTorchJob and MPIJob. Deployments and StatefulSets are supported too, but Kueue manages Deployment pods individually through the Plain Pod integration, so a Deployment can run partially under quota pressure. Protect online inference with lendingLimit: 0 or a dedicated node pool.
Romaric founded Qovery to make Kubernetes accessible to every engineering team. He writes about platform strategy, developer experience, and the future of cloud infrastructure.
Next step
Ship faster on infrastructure you control.
Qovery gives your team self-service deployments on your own AWS, GCP, Azure, or Scaleway account - or your existing Kubernetes cluster. Start deploying in under 10 minutes.