KAI Scheduler vs HAMi
Credits to Scicom (MSC) Berhad and Scicom AI Enterprise for the resources.
KAI Scheduler and HAMi both decide which GPU a pod gets, and both can put several pods on one card. They solve different problems. KAI Scheduler decides who gets GPUs and when: queues, quotas, fair share and gang scheduling. HAMi decides which card and how much of it: it slices cards, caps memory per container, and lets a pod name the exact cards it runs on.
In short, they work at different levels. KAI is a Kubernetes scheduler: it works at the workload level and decides which pod or job runs, when, and on which node. HAMi works at the GPU level: it decides which card a pod gets and how much of it, and HAMi-core enforces that share inside the container. HAMi does not replace the Kubernetes scheduler, it extends it.
We run several workloads on the same GPU nodes, and we had trouble putting each of them on specific GPUs. We tried both. This post compares them. The versions are HAMi 2.9.0 and KAI Scheduler v0.18.6.
What each one is
HAMi:
- It is the device plugin, plus a scheduler extender and a webhook. It hands out the cards itself.
- A pod can name the exact cards it runs on, by UUID.
- A pod asks for a share of each card's memory, and HAMi-core can enforce that share inside the container.
- It has no queues, quotas or gang scheduling.
KAI Scheduler:
- It is a scheduler only. The cards come from the NVIDIA device plugin or from DRA.
- A pod cannot name its cards. The device plugin picks them.
- Card sharing works through reservation pods that hold whole cards. Memory is not enforced by default.
- It has queues with quotas, fair share, preemption and gang scheduling.
- It is bigger: 7 controllers, 6 CRDs and 4 PriorityClasses, against HAMi's DaemonSet, Deployment and webhook.
What each one is good at
KAI Scheduler (v0.18.6) is built around queues and gangs. A pod-grouper puts related pods into PodGroups that start together or not at all. The groups wait in a tree of queues, each with a guaranteed quota, a limit, an over-quota weight and a priority. Every scheduling cycle allocates first, then consolidates running pods to cut fragmentation, reclaims over-quota usage between queues, and preempts lower-priority work inside a queue, with fairness by DRF and optionally by usage over time. Priority and preemptibility are separate settings, and a minimum runtime protects a pod from early preemption. On top of that it places by binpack or spread, can be topology-aware, runs elastic jobs with a minimum and maximum size, shares GPUs with gpu-fraction or gpu-memory, schedules DRA ResourceClaims, works with cluster autoscalers, and records each decision as a Kubernetes Event.
HAMi (v2.9.0) is built around the device. Its device plugin splits each card into slots, and a pod asks for N different cards and a share of each: memory in MiB or percent, and compute as a percent of the SMs. The pod can name its cards by UUID (nvidia.com/use-gpuuuid) or by model, or keep away from some. HAMi-core enforces those shares inside the container with no change to the application, and the container sees its cap as the card's memory; CUDA_DISABLE_CONTROL=true turns it off for one container. Placement is binpack or spread, for nodes and for cards, chosen per pod, and dynamic MIG is available per pod. It supports accelerators from several vendors, exports what each pod holds on each card, and needs no CRDs: a scheduler extender on kube-scheduler, a device plugin and a webhook.
Things to know before choosing
KAI cannot pin a pod to given cards. The device plugin picks them, so the only way to land on specific cards is to have them be the only free ones when the pod binds, and the placement can change after a restart. Its GPU sharing needs the nvidia.com/gpu.memory node label from GPU Feature Discovery; without it every bind of a gpu-fraction pod is rolled back. In v0.18.6 it also counts admin-access DRA claims as GPU usage, so with the GPU Operator's DRA driver a node looks fuller than it is. Uninstalling it through ArgoCD leaves its CRDs and the namespace of its reservation pods behind, and leftover reservation pods keep holding cards.
HAMi cannot run next to the NVIDIA device plugin, since both advertise nvidia.com/gpu. It counts slots, not cards: with deviceSplitCount: 10, an 8-GPU node shows nvidia.com/gpu: 80.
With either tool, engines that share a card should start one at a time. vLLM measures free GPU memory when it starts, and two engines measuring at once count each other's memory wrong.
Why we use HAMi
Our problem was placement: several workloads, each meant for specific cards, some sharing a card. HAMi covers that in one component.
- Pinning. A pod lists the card UUIDs it may use. The spec is the placement, and it survives restarts.
- Sharing by memory share. A pod asks for N cards and a share of each. HAMi will not place a pod past 100 % of a card.
- Enforcement per container. vLLM sizes its own memory, so vLLM pods run with HAMi-core off and the share is bookkeeping. Small services run with HAMi-core on, so they cannot grow into a neighbour's memory.
- Small footprint. A device plugin, a scheduler extender and a webhook, no CRDs.
A vLLM engine pinned to two cards that it shares with another engine:
metadata:
annotations:
nvidia.com/use-gpuuuid: "GPU-aaaa...,GPU-bbbb..." # exactly these two cards
spec:
runtimeClassName: nvidia
containers:
- name: vllm
env:
- name: CUDA_DISABLE_CONTROL # HAMi-core off: vLLM manages its own memory
value: "true"
resources:
limits:
nvidia.com/gpu: "2" # two different cards
nvidia.com/gpumem-percentage: "45" # of each card
When KAI is the better choice
KAI is built for a shared training or batch cluster: many teams, jobs that come and go, quotas that must hold, gang scheduling for multi-node training, and preemption between priorities. If which card a pod lands on does not matter and the problem is who gets GPUs first, KAI solves that and HAMi does not.
If the problem is which card, and every card has a planned owner, HAMi does it directly. KAI gets there only through rollout order, with a reservation-pod layer and a GPU Feature Discovery dependency on top.