Run several pods on one GPU, each with a hard GPU-memory limit enforced by the NVIDIA driver, using KAI Scheduler and gpu-fractioning on Crusoe Managed Kubernetes (CMK).
Good fits: development workspaces and notebooks, lightweight or bursty inference services, CI jobs that need a little GPU. Not a fit: workloads that need a whole GPU's compute or memory.
What you get
- Pods request GPU memory (
16Gi), not whole GPUs. KAI packs them onto shared GPUs. - The driver enforces each container's limit. Going over it raises a normal CUDA out-of-memory error in that container only; neighbours are unaffected.
- Inside the container, CUDA reports the limit as the GPU's total memory, so frameworks such as PyTorch and vLLM size themselves to their share automatically.
- Two compute modes per container:
time-slicing(default) orsm-sharing(concurrent, via MPS).
Note — as of 11/03/26, NVIDIA driver r615 and
nvidia-smi. GPU fractioning requires driver r615 or newer. In r615, thenvidia-smiheader readsKMD VersionandCUDA UMD Versioninstead ofDriver VersionandCUDA Version. Update any scripts or health checks that parse that header;nvidia-smi --query-gpu=driver_version --format=csv,noheaderis stable across versions.
Prerequisites
- A CMK cluster with GPU node pools on a node pool version whose image runs containerd 2.x with NRI enabled (verified:
1.35.5-cmk.42). -
Do not enable the
nvidia_gpu_operatoradd-on on this cluster; you will install the NVIDIA GPU Operator yourself with the r615 driver. -
kubectlandhelm3.x with cluster-admin access.
Step 1 — Install the NVIDIA GPU Operator with driver r615
helm upgrade -i gpu-operator gpu-operator \
--repo https://helm.ngc.nvidia.com/nvidia --version v26.7.1 \
-n gpu-operator --create-namespace \
--set driver.version=615.71.09 \
--wait --timeout 20m
kubectl get clusterpolicies.nvidia.com cluster-policy -o jsonpath='{.status.state}' # ready
GPU Operator v26.7.1 already ships the container toolkit (v1.20.1) and device plugin (v0.20.1) that GPU fractioning needs; only the driver version has to be set. The first install takes about 10–15 minutes while it builds and loads the driver.
Driver support notice. GPU fractioning requires the NVIDIA data center driver branch R615 or later. R615 is not a Crusoe-supported driver version, so the Crusoe support team can only help with issues on clusters or node pools using it on a best-effort basis. R615 is an NVIDIA New Feature Branch, which NVIDIA provides for early adopters. Unlike Production and Long-Term Support branches, it carries no committed period of bug-fix or security updates. Fixes may require moving to a newer driver branch rather than applying a patch, and new branches are released about every quarter. Before using GPU fractioning in production, validate it in your environment. Pin your driver and component versions, and plan for periodic driver upgrades that require draining the affected nodes. We recommend running fractional workloads on a dedicated node pool, and keeping other GPU workloads on a Crusoe-supported driver version. If a node runs a driver older than R615, GPU fractioning marks that node as not ready and does not schedule fractional workloads on it. Workloads therefore never run without their memory limits enforced. We will update this guidance when an NVIDIA Production Branch supports GPU fractioning.
Step 2 — Raise the MPS daemon memory limit (8-GPU nodes)
gpu-fractioning 0.1.8 limits its MPS daemon container to 256 MiB of memory, which is not enough for sm-sharing on 8-GPU nodes. Until this is configurable upstream, raise it with a Kyverno mutating policy (skip this step if you only use time-slicing):
helm upgrade -i kyverno kyverno --repo https://kyverno.github.io/kyverno/ \
--version 3.9.2 -n kyverno --create-namespace --wait
kubectl apply -f - <<'EOF'
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: gpu-fractioning-mpsd-memory
spec:
failurePolicy: Ignore
autogen:
podControllers:
controllers: []
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: gpu-fractioning}
objectSelector:
matchLabels: {app.kubernetes.io/component: mpsd}
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >-
Object{spec: Object.spec{containers: [Object.spec.containers{
name: "mpsd",
resources: Object.spec.containers.resources{limits: {"memory": "2Gi"}}}]}}
EOF
Install the policy before Step 3 so the daemon starts with the higher limit.
Step 3 — Install gpu-fractioning
helm upgrade -i gpu-fractioning oci://ghcr.io/kai-scheduler/gpu-fractioning \
--version 0.1.8 -n gpu-fractioning --create-namespace --wait
kubectl wait gpufractioningconfig/default --for=condition=Ready --timeout=10m
Step 4 — Install KAI Scheduler
helm upgrade -i kai-scheduler oci://ghcr.io/kai-scheduler/kai-scheduler/kai-scheduler \
--version v0.18.7 -n kai-scheduler --create-namespace \
--set global.gpuSharingMode=NvFractions \
--set global.nvFractions.set=false \
--wait
Install KAI after GPU-fractioning. If you install it first, restart the KAI operator afterwards.
Step 5 — Give the default queue capacity
KAI's default queue starts with zero quota, so Deployments stay Pending with NonPreemptibleOverQuota. To get started, make it unlimited:
for q in default-parent-queue default-queue; do
kubectl patch queue $q --type=merge \
-p '{"spec":{"resources":{"cpu":{"quota":-1},"memory":{"quota":-1},"gpu":{"quota":-1}}}}'
done
For teams, create separate queues with quotas (CPU in millicores, memory in MB, GPU in GPUs) — see the KAI queues documentation. Edits apply live with kubectl apply.
Step 6 — Verify
kubectl get gpufractioningconfig default \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} {end}'
# FractiondReady=True MpsdReady=True Ready=True ...
# sm-sharing: the shared MPS server must have started on every GPU
kubectl -n gpu-fractioning logs -l app.kubernetes.io/component=mpsd --tail=-1 \
| grep -E "Server shared (ERROR|INFO.*Creating server context)"
Expect one Creating server context on device N line per GPU and no ERROR.
Step 7 — Run a fractional workload
apiVersion: v1
kind: Pod
metadata:
name: fractional-demo
labels:
kai.scheduler/queue: default-queue
annotations:
nvidia.com/container.app.gpu-memory.request: 16Gi # guaranteed, used for scheduling
nvidia.com/container.app.gpu-memory.limit: 24Gi # optional hard cap; defaults to request
nvidia.com/container.app.gpu-compute.mode: time-slicing # or sm-sharing
spec:
schedulerName: kai-scheduler
restartPolicy: Never
containers:
- name: app # must match the name in the annotation keys
image: pytorch/pytorch:2.14.1-cuda13.0-cudnn9-runtime
command: ["python", "-c", "import torch; print(torch.cuda.mem_get_info())"]
The output shows a total of 24 GiB — the container's limit. nvidia-smi inside the container also reports the limit as the GPU's total memory.
Rules:
- Do not request
nvidia.com/gpuin a fractional pod. - One fractional container per pod. All processes share the limit in that container.
- Use absolute sizes (
Gi) so the same manifest behaves the same on different GPU models. - On Blackwell GPUs (B200/B300), use images built for CUDA 12.8 or newer.
Choosing a compute mode
| Mode | Behaviour | Use for |
|---|---|---|
time-slicing (default) |
containers take turns on the whole GPU | interactive dev, notebooks, bursty jobs |
sm-sharing |
containers run concurrently on the GPU's SMs (MPS) | steady or latency-sensitive inference |
Containers using different modes are never placed on the same GPU.
Using GPU fractioning alongside InfiniBand workloads
GPU fractioning works on nodes that also run the NVIDIA Network Operator for InfiniBand. Multi-node training jobs that request whole GPUs (nvidia.com/gpu) and InfiniBand devices can run on the same nodes as fractional pods: KAI keeps shared GPUs out of whole-GPU allocations, and in our tests neither workload affected the other's performance. Schedule those jobs with schedulerName: kai-scheduler as well.
The NVIDIA Network Operator runs side by side with a GPU Operator on driver r615. In our validation on H100 nodes (NVIDIA GPU Operator v26.7.1 with driver 615.71.09, NVIDIA Network Operator 26.1.0 with the DOCA driver), installing the Network Operator left the GPU driver and GPU fractioning untouched. Multi-node NCCL jobs used GPUDirect RDMA over InfiniBand at full bandwidth, whether or not fractional pods were running on the same nodes. Keep the GPU Operator defaults for this setup: open kernel modules and driver.rdma.enabled=false. GPUDirect RDMA then works through DMA-BUF and does not need the nvidia-peermem module. Installing the Network Operator reloads the InfiniBand drivers on each node, which briefly interrupts node networking, so add it to a running cluster during a maintenance window.
Troubleshooting
| Symptom | Fix |
|---|---|
Deployment Pending, NonPreemptibleOverQuota
|
Step 5 |
Ready=False, GPUDriverVersionUnsupported
|
driver below r615 — Step 1 |
sm-sharing pods fail; mpsd logs Failed to create server contexts: out of memory
|
Step 2 missing, or raise 2Gi and restart ds/gpu-fractioning-mpsd
|
Script cannot parse nvidia-smi
|
r615 header change — see note above |
Clean up
helm uninstall kai-scheduler -n kai-scheduler
helm uninstall gpu-fractioning -n gpu-fractioning
kubectl delete mutatingpolicy gpu-fractioning-mpsd-memory && helm uninstall kyverno -n kyverno
helm uninstall gpu-operator -n gpu-operator