Cloud Native

HAMi Goes CNCF Incubating and Pivots to Kubernetes DRA for GPU Scheduling

For years, Kubernetes platform teams that wanted to share GPUs between workloads had to build around an API that could only count whole devices. The device plugin interface exposed one primitive: nvidia.com/gpu: 1. It meant one card, take it or leave it. Projects like HAMi spent years engineering elaborate pipelines—mutating webhooks, scheduler extenders, annotations, and in-container enforcement libraries—to express what the API could not: give this pod 8,000 MiB and 10% of a GPU, and make the limit stick.

That workaround era is ending. Kubernetes Dynamic Resource Allocation (DRA) reached general availability in v1.34 and is enabled by default since v1.35. With the consumable capacity feature now in beta, pods can request fractional slices of GPU memory and compute directly through the scheduler, natively, without annotations. And HAMi, newly promoted to a CNCF incubating project, is rebuilding its core scheduling layer on top of DRA rather than fighting it.

But the project is also making something clear: DRA replaces the request-and-schedule half of GPU sharing, not the runtime-enforcement half. The future of Kubernetes GPU virtualization is a two-layer stack—upstream scheduling plus community-enforced limits—and HAMi intends to own the second layer.

How the Workaround Worked

To understand why DRA matters, it helps to trace what HAMi built when the scheduler could not help. A HAMi user writes resource limits that the default scheduler does not understand:

resources:
  limits:
    nvidia.com/gpu: 1
    nvidia.com/gpumem: 8000
    nvidia.com/gpucores: 10

Extended resources in Kubernetes are opaque integers. The scheduler knows to subtract 8000 from a node total, but it does not know that gpumem and gpucores must land on the same physical card, or that two pods with 8,000 MiB each fit on a 24 GiB card while a third asking for 12,000 MiB does not. So HAMi routes the pod through a mutating webhook to its own scheduler extender, which filters nodes, scores cards, picks a specific device UUID, and records the decision in an annotation.

The device plugin on the node later reads that annotation during Allocate(), injects environment variables like CUDA_DEVICE_MEMORY_LIMIT_0=8000m and CUDA_DEVICE_SM_LIMIT=10, and preloads libvgpu.so into the container so the limits get enforced at CUDA-call granularity. It works at scale—DaoCloud runs HAMi across more than 10,000 GPUs in over 10 data centers—but the architecture is fundamentally a workaround. The webhook exists because the scheduler cannot parse the request. The annotation exists because the API has no field for “which card and how much.” The entire agreement between scheduler and kubelet rides on a string format that only HAMi’s components understand.

Every fractional-GPU project of that era made the same trade, each with its own private annotation dialect. DRA was built to end that era.

What DRA Changes Underneath

DRA replaces integer counting with a claims model shaped deliberately like PersistentVolumeClaims. Four objects in the resource.k8s.io/v1 API group carry the flow:

  • ResourceSlice — published by the device driver, describing actual hardware per node with structured attributes (model, memory, architecture) so the scheduler sees devices instead of a bare count.
  • DeviceClass — written by the cluster admin, defining categories of devices filtered with Common Expression Language (CEL) expressions over those attributes.
  • ResourceClaim and ResourceClaimTemplate — written by the workload owner, requesting devices by class, selector, and constraint; a template stamps out one claim per pod so each replica gets its own allocation.

The scheduler allocates a concrete device to a claim before binding the pod, and the result lives in the claim’s status as a typed API object. Compared to HAMi’s annotation string, the “which card and how much” decision now has a first-class home that kubectl can read, RBAC can guard, and other controllers can build on.

Core DRA went GA in Kubernetes v1.34 and is locked on since v1.35. Extensions are graduating at their own pace: prioritized device lists reached stable in v1.36, while partitionable devices and consumable capacity are beta as of v1.36.

Consumable Capacity Is the Piece That Matters

Core DRA alone does not give you HAMi-style sharing. Its baseline sharing model is multiple pods referencing one ResourceClaim, which means they share the same allocation rather than each getting an accounted slice. The piece that maps onto HAMi’s model is consumable capacity, introduced as alpha in v1.34 behind the DRAConsumableCapacity feature gate and beta (on by default) since v1.36.

It adds two things. A driver can mark a device with allowMultipleAllocations, declaring that independent claims, even from different namespaces, may land on it simultaneously. And a claim can carry a capacity request, asking for a specific quantity of a named resource on the device instead of claiming the device in full. The scheduler then does for GPU memory what it has always done for node memory: bookkeeping, guaranteeing that the sum of granted capacity never exceeds what the device advertised.

Lined up against HAMi’s extended resources, the mapping is almost mechanical. nvidia.com/gpumem: 8000 becomes a capacity request for memory; nvidia.com/gpucores: 10 becomes a capacity request for compute; HAMi’s scheduler-extender filter step turns into the upstream scheduler’s own math. The rejection HAMi users know as CardInsufficientMemory becomes a standard unschedulable claim.

This is why HAMi’s maintainers treat DRA as convergence rather than competition. Upstream Kubernetes adopted the same model the workaround had been implementing all along, and the project’s 2026 roadmap names complete DRA standard adaptation as a goal.

Scheduling Is Only Half the Problem

Here is the boundary that decides whether you still need HAMi at all. DRA, consumable capacity included, is a promise tracker. It guarantees the scheduler never promises more than a device has. It does nothing about a container that breaks the promise at runtime, and with GPUs that is the failure mode that actually hurts. CUDA does not care what a ResourceClaim says, and one greedy cudaMalloc() loop will happily take VRAM a neighbor was counting on.

Enforcement is HAMi’s second job, and it lives in HAMi-core, a C library (libvgpu.so) preloaded into the container that intercepts CUDA and NVIDIA Management Library (NVML) calls and applies the granted limits from user space. The behavior is easy to verify: give two pods 8,000 MiB grants each, then have one deliberately allocate past its limit. The offender gets a CUDA out-of-memory at exactly its 8,000 MiB boundary while the neighbor keeps running untouched, even if the physical card still has free VRAM.

This is software enforcement via library interposition. For adversarial multi-tenancy, hardware partitioning (NVIDIA Multi-Instance GPU) is the stronger answer. But for cooperative teams sharing expensive cards, interception wins on granularity: 1 MiB memory steps and 1% compute steps against MIG’s fixed profiles. Nothing in DRA replaces this layer. The DRA driver’s job ends at the Container Device Interface (CDI): telling the runtime which device nodes to mount and which environment to set. What happens after the process starts calling CUDA is out of scope by design.

The realistic architecture pairs the two: DRA as the request-and-scheduling language, HAMi-core as the runtime muscle, and a driver in between translating one into the other.

The DRA Stack HAMi Ships Today

That driver exists, and the work is spread across three repositories solving different operational problems.

k8s-dra-driver is the foundation: a DRA driver that publishes each GPU’s memory and compute as consumable capacity in ResourceSlices, runs the kubelet plugin that resolves allocations on the node, and wires containers up through CDI with HAMi-core enforcement attached. HAMi describes it as the first open source DRA driver for NVIDIA GPUs with consumable capacity enabled.

HAMi-DRA is the migration bridge for teams with hundreds of existing manifests written against nvidia.com/gpu and nvidia.com/gpumem. It is a mutating admission webhook that strips the classic extended resources out of incoming pods and generates equivalent ResourceClaims on the fly, preserving familiar annotations for targeting.

HAMi-core remains unchanged in its job: intercepting CUDA calls inside the container to enforce the limits allocated by whichever scheduler—classic HAMi or DRA—put the pod on the node.

What This Means for Platform Teams

For teams running GPU workloads on Kubernetes, the HAMi-DRA convergence means three things.

First, the scheduling stack is becoming native. Platform teams will no longer need custom scheduler extenders and annotation-based communication to allocate fractional GPUs. The tooling gets simpler, more observable, and more interoperable with the rest of the Kubernetes ecosystem.

Second, runtime enforcement is not going away. DRA does not replace HAMi-core, and teams that need per-container limits on shared GPUs will still need a layer like it. The gap between scheduler promise and runtime reality is a structural feature of the Kubernetes device model, not a temporary bug.

Third, the operational surface is splitting cleanly. Scheduling becomes upstream Kubernetes. Enforcement stays in the community. Platform teams can reason about each layer separately, upgrade them independently, and expect the boundary between them to stabilize over time.

HAMi’s incubation and its DRA pivot are not an admission that the workaround was wrong. They are a sign that the ecosystem has grown large enough—and important enough—to graduate its primitives into upstream Kubernetes, while keeping the hard problems of shared GPU enforcement where they belong: in a focused, vendor-neutral open source project.

Sources