Skip to main content
Version: v1.9 ๐Ÿšง

NUMA and Device Topology Allocation Strategies

Koordinator has two topology-aware scheduler plugins that decide where a Pod's resources physically land on a node:

  • NodeNUMAResource allocates CPU and memory across NUMA nodes, and orchestrates fine-grained CPUSet binding.
  • DeviceShare allocates heterogeneous devices โ€” GPU, RDMA, FPGA and others โ€” across the device topology (device / PCIe / NUMA node).

A single Pod often requests both ordinary compute resources and accelerators, so the two plugins must agree on one consistent hardware placement. Each plugin exposes a set of allocation strategies through Pod annotations, Node labels and plugin arguments โ€” these are the "protocols" that users configure. This topic catalogs every strategy in both plugins, then explains how they are kept consistent โ€” and where they can appear to conflict โ€” through the shared Topology Manager.

Why Two Plugins Must Cooperateโ€‹

Modern servers are non-uniform. CPUs and memory are grouped into NUMA nodes; GPUs and RDMA NICs hang off PCIe switches that belong to specific NUMA nodes. If the scheduler places a Pod's CPUs on NUMA node 0 but its GPU on NUMA node 1, every DMA transfer crosses the inter-socket link and performance collapses.

NodeNUMAResource and DeviceShare therefore cannot decide independently. They converge at a single coordination point โ€” frameworkext/topologymanager โ€” which collects NUMA topology hints from both plugins and merges them under one policy before any resource is bound.

Pod being scheduled onto a node
โ”‚
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ frameworkext/topologymanager โ”‚
โ”‚ Resolves ONE NUMATopologyPolicy for the Pod: โ”‚
โ”‚ pod annotation > node label > kubelet/plugin arg โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ GetPodTopologyHints() (both plugins implement
โ”‚ NUMATopologyHintProvider)
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ–ผ โ–ผ
NodeNUMAResource DeviceShare
hints for cpu / memory hints for gpu / rdma / ...
{affinity, Preferred, Score} {affinity, Preferred, Score}
โ”‚ โ”‚ (best device layout
โ”‚ โ”‚ scores 500 to win ties)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
Merge all provider hints
narrowest affinity wins; equal width โ†’ higher Score wins
โ”‚
โ–ผ
best NUMATopologyHint (a NUMA node bitmask)
โ”‚
โ–ผ Allocate(affinity) on every provider
NodeNUMAResource binds CPUs; DeviceShare binds devices

Everything below is a refinement of one of the boxes in this diagram.

The Topology Manager: Where the Protocols Convergeโ€‹

NUMATopologyPolicy โ€” the single governing policyโ€‹

The NUMATopologyPolicy decides how strictly all resources of a Pod must be aligned to the same NUMA node(s). It mirrors the upstream kubelet Topology Manager and applies to CPU, memory and devices at once.

PolicyBehavior
None (empty)No NUMA alignment. Hints are still produced but any placement is admitted.
BestEffortPrefer the narrowest NUMA alignment across all resources, but still admit the Pod when no aligned placement exists (falls back to all NUMA nodes).
RestrictedLike BestEffort, but reject the node when no suitably narrow alignment can be found.
SingleNUMANodeAdmit the node only if a single NUMA node can satisfy all requested resources (CPU, memory and devices together).

The policy is resolved with a clear precedence, and contradictions are rejected rather than silently merged:

  • Pod annotation koordinator.sh/numa-topology-spec โ†’ NUMATopologySpec.numaTopologyPolicy
  • Node label node.koordinator.sh/numa-topology-policy
  • kubelet / plugin argument default

If the Pod and the Node both specify a policy and the two differ, scheduling fails with ErrNotMatchNUMATopology โ€” the plugins never guess which one wins. When the Pod specifies a policy, it overrides the Node label.

NUMATopologyHint โ€” the shared currencyโ€‹

Both plugins emit hints of the same shape, which is what makes merging possible:

FieldMeaning
NUMANodeAffinityA bitmask of the NUMA nodes that can satisfy the resource (e.g. 0b0011 = NUMA 0 and 1).
Preferredtrue when this affinity is a preferred placement for the Pod.
ScoreTie-breaker weight. For the same affinity width, the higher score wins.
Unsatisfiedtrue when no affinity can satisfy the request.

Merge and tie-breakingโ€‹

The Topology Manager enumerates every combination of provider hints and picks the best merged hint:

  1. A narrower affinity (fewer NUMA nodes) always beats a wider one.
  2. Among hints of equal width, the one with the higher Score wins.
  3. The result is checked against the exclusive policy (see SingleNUMANodeExclusive below); a violation demotes the hint to non-preferred or rejects it.

DeviceShare deliberately assigns its best device layout a Score of 500, well above the CPU hints, so that when CPU and devices can both fit the same NUMA width, the device layout drives the final choice. This is an intentional, documented precedence โ€” not an accident of ordering.

NodeNUMAResource Allocation Strategiesโ€‹

NodeNUMAResource answers three questions: which NUMA nodes, how to bind CPUs inside them, and how exclusive the placement must be.

NUMAAllocateStrategy โ€” which NUMA nodes to pickโ€‹

It selects among the NUMA nodes that already satisfy the request, and is resolved with a clear precedence:

  • Pod annotation koordinator.sh/numa-topology-spec โ†’ NUMATopologySpec.numaAllocateStrategy
  • Node label node.koordinator.sh/numa-allocate-strategy
  • plugin argument default (an empty value inherits it)
StrategyMeaningEffect
MostAllocatedAllocate from the NUMA node with the least available resource.Bin-pack / consolidate; leaves whole NUMA nodes free for large Pods.
LeastAllocatedAllocate from the NUMA node with the most available resource.Spread load; balances usage across NUMA nodes.
DistributeEvenlySpread the pod's cpu/memory across as many NUMA nodes as feasible (prefer the widest NUMA affinity) instead of packing into the fewest.Interleave-style placement for workloads whose memory is interleaved across NUMA nodes at runtime (e.g. mbind MPOL_INTERLEAVE), keeping per-NUMA capacity balanced.

Unlike NUMATopologyPolicy โ€” an admission constraint whose pod/node mismatch is rejected with ErrNotMatchNUMATopology โ€” the allocate strategy is a placement preference: a pod annotation overrides the node label instead of conflicting with it. At the pod level only DistributeEvenly is honored (MostAllocated / LeastAllocated remain node-level strategies), and PreFilter rejects any other pod-level value with a diagnosable status. The even spread is gated โ€” it applies only when the node actually exposes NUMA topology and the pod is not confined to a single NUMA node, so a topology-less node keeps its prior None behavior and Restricted / SingleNUMANode admission is never changed by the spread.

Naming note: MostAllocated means "pick the node that is already most allocated", i.e. the one with the least free resource. This is bin-packing, and the same wording is reused consistently by the scoring strategies below.

CPUBindPolicy โ€” how to bind logical CPUsโ€‹

Set on the Pod through koordinator.sh/resource-spec, either as requiredCPUBindPolicy (strict โ€” scheduling fails if it cannot be honored) or preferredCPUBindPolicy (best-effort).

PolicyMeaning
DefaultNo strong preference on how logical CPUs are chosen.
FullPCPUsPack the allocation into as few physical cores as possible (allocate whole cores, keep hyper-thread siblings together).
SpreadByPCPUsEvenly spread logical CPUs across physical cores (one sibling per core first).
ConstrainedBurstConstrain the CPU Shared Pool range used by a Burstable Pod.

NodeCPUBindPolicy โ€” the node-level required bind policyโ€‹

Set on the Node with the label node.koordinator.sh/cpu-bind-policy. It constrains what the scheduler must do for CPUSet Pods on that node, and is aligned with the kubelet CPU Manager options.

PolicyMeaning
NoneNo node-level constraint.
FullPCPUsOnlyMust allocate full physical cores (equivalent to kubelet full-pcpus-only=true).
SpreadByPCPUsMust evenly spread logical CPUs across physical cores (equivalent to kubelet distribute-cpus-across-numa).

A Pod's requiredCPUBindPolicy takes precedence; otherwise the node policy (combined with the kubelet CPU Manager policy reported on the node) determines the effective bind policy.

CPUExclusivePolicy โ€” exclusivity granularityโ€‹

Set on the Pod through koordinator.sh/resource-spec โ†’ preferredCPUExclusivePolicy.

PolicyMeaning
NoneNo exclusivity.
PCPULevelMutual exclusion at the physical core dimension โ€” exclusive Pods do not share a physical core.
NUMANodeLevelMutual exclusion at the NUMA node dimension โ€” exclusive Pods do not share a NUMA node.

SingleNUMANodeExclusive and NUMA node statusโ€‹

koordinator.sh/numa-topology-spec also carries singleNUMANodeExclusive, which governs whether a NUMA node already used exclusively by one Pod may be shared by another:

ValueMeaning
PreferredPrefer not to co-locate a single-NUMA Pod with a multi-NUMA Pod (and vice versa), but allow it if necessary.
RequiredForbid such co-location.

To enforce this, each NUMA node tracks a status โ€” idle, shared, or single โ€” and the Topology Manager checks the merged hint against it during Admit. A node whose CPUs are fully bound to one Pod becomes single; a node used by resource-only Pods becomes shared.

Scoring strategiesโ€‹

NodeNUMAResource exposes two independent scoring knobs through NodeNUMAResourceArgs:

ArgumentScopeTypes
scoringStrategyRank nodesMostAllocated, LeastAllocated, BalancedAllocation
numaScoringStrategyRank NUMA nodes within a nodeMostAllocated, LeastAllocated, BalancedAllocation

BalancedAllocation favors nodes whose resource usage rates are balanced across resource kinds. As with NUMAAllocateStrategy, MostAllocated = least available (bin-pack) and LeastAllocated = most available (spread).

DeviceShare Allocation Strategiesโ€‹

DeviceShare allocates accelerators and other devices. Its strategies operate on the device topology (device โ†’ PCIe โ†’ NUMA node โ†’ node), and it participates in NUMA alignment unless explicitly disabled.

DeviceHint โ€” per-device-type allocation hintsโ€‹

Set on the Pod through koordinator.sh/device-allocate-hint, keyed by device type (gpu, rdma, ...).

FieldValuesMeaning
selectorlabel selectorRestrict which device instances are eligible.
vfSelectorlabel selectorRestrict which SR-IOV virtual functions (VFs) are eligible.
allocateStrategyApplyForAll, RequestsAsCountApplyForAll: allocate every matching device. RequestsAsCount: interpret the resource request value as the count of devices.
requiredTopologyScopeDevice, PCIe, NUMANode, NodeThe tightest topology scope the allocated devices must share (level 4 โ†’ 1).
exclusivePolicyDeviceLevel, PCIeLevelMutual exclusion at the device-instance or PCIe dimension.

DeviceJointAllocate โ€” co-locating different device typesโ€‹

Set on the Pod through koordinator.sh/device-joint-allocate. It groups multiple device types so they are allocated within a shared topology scope โ€” the classic case being a GPU and an RDMA NIC on the same PCIe switch.

FieldMeaning
deviceTypesThe device types to allocate jointly (e.g. [gpu, rdma]).
requiredScopeThe scope they must share. SamePCIe requires all joint devices under one PCIe.

GPU partitioningโ€‹

For multi-GPU Pods, the inter-GPU link topology (e.g. NVLink) matters more than raw count. Partitions are described on the Device CR with the annotation koordinator.sh/gpu-partitions, and requested by the Pod with koordinator.sh/gpu-partition-spec.

GPUPartitionSpec (Pod):

FieldValuesMeaning
allocatePolicyRestricted, BestEffortRestricted: consider only partitions with the highest allocationScore. BestEffort (default): try best to pursue a higher allocationScore but accept lower ones.
ringBusBandwidthquantityMinimum required ring-bus bandwidth of the partition.

GPUPartitionPolicy (Node/Device label node.koordinator.sh/gpu-partition-policy):

ValueMeaning
HonorThe partition table annotated on the Device CR must be honored.
Prefer (default)The partition table is preferred but not mandatory.

Naming collision to be aware of: GPUPartitionSpec.allocatePolicy uses Restricted / BestEffort, but these are unrelated to the NUMATopologyPolicy values of the same name. Here they describe how strictly to pursue the best GPU-partition score; there they describe NUMA alignment strictness. The two axes are independent and never cross-check each other.

Device scoring strategyโ€‹

DeviceShareArgs.scoringStrategy ranks nodes by device availability using MostAllocated (bin-pack devices) or LeastAllocated (spread devices), with the same semantics as NodeNUMAResource scoring.

DisableDeviceNUMATopologyAlignment โ€” opting out of NUMA alignmentโ€‹

DeviceShareArgs.disableDeviceNUMATopologyAlignment decouples devices from the NUMA merge. When true, DeviceShare returns no hints, so device placement no longer constrains (or is constrained by) the Pod's NUMA alignment. Use it only when devices are intentionally allowed to sit on different NUMA nodes than the CPUs.

Consistency and Conflict Analysisโ€‹

The two plugins look like they define a lot of overlapping knobs. In practice they are layered so that most "conflicts" are either impossible by construction or resolved deterministically. This section makes the guarantees โ€” and the genuine tension points โ€” explicit.

Shared vocabulary (truly consistent)โ€‹

These concepts are defined once and consumed by both plugins through the Topology Manager, so they cannot drift apart:

ConceptWhere definedConsumed by
NUMATopologyPolicyapis/extensionBoth, via a single merged decision
NUMATopologyHint (affinity / Preferred / Score)frameworkext/topologymanagerBoth hint providers
NumaTopologyExclusive (Preferred / Required)apis/extensionTopology Manager Admit
NumaNodeStatus (idle / shared / single)apis/extensionTopology Manager Admit

Parallel-but-distinct vocabulary (the confusing part)โ€‹

Several strategies share a word but govern different axes. They are orthogonal, not competing:

TermPluginAxis it controlsValues
NUMAAllocateStrategyNodeNUMAResourceWhich NUMA nodes to fillMostAllocated / LeastAllocated / DistributeEvenly
scoringStrategyNodeNUMAResourceHow to rank nodesMostAllocated / LeastAllocated / BalancedAllocation
numaScoringStrategyNodeNUMAResourceHow to rank NUMA nodesMostAllocated / LeastAllocated / BalancedAllocation
scoringStrategyDeviceShareHow to rank nodes by deviceMostAllocated / LeastAllocated
DeviceHint.allocateStrategyDeviceShareHow to read a device requestApplyForAll / RequestsAsCount
DeviceHint.requiredTopologyScopeDeviceShareDevice grouping granularityDevice / PCIe / NUMANode / Node
GPUPartitionSpec.allocatePolicyDeviceShareGPU-partition selectionRestricted / BestEffort

Two consistency guarantees hold across this table:

  • MostAllocated / LeastAllocated mean the same thing everywhere โ€” bin-pack vs. spread โ€” whether applied to NUMA nodes, whole nodes, or devices.
  • CPUBindPolicy and DeviceHint.exclusivePolicy express the same idea (exclusivity at a topology level) but over different domains (CPU cores vs. device/PCIe), so their value sets intentionally differ.

How conflicts are prevented or resolvedโ€‹

Potential conflictResolution
Pod and Node disagree on NUMATopologyPolicyRejected with ErrNotMatchNUMATopology; never silently merged.
CPU hints and device hints favor different NUMA nodesMerged under one policy; narrower affinity wins, ties broken by Score (devices score 500, so device layout leads at equal width).
Devices should ignore NUMA entirelydisableDeviceNUMATopologyAlignment removes device hints from the merge.
A single-NUMA Pod meets an already-exclusive NUMA nodeSingleNUMANodeExclusive + NumaNodeStatus checked during Admit.

Genuine tension points and guidanceโ€‹

These combinations are allowed by the APIs but are logically opposed or redundant. The scheduler does not reject them, so operators should avoid pairing them:

CombinationWhy it is tenseGuidance
numa-allocate-strategy: DistributeEvenly + NUMATopologyPolicy: SingleNUMANodeSingleNUMANode confines everything to one NUMA node, so "distribute evenly across NUMA nodes" has nothing to distribute over. The policy wins and the strategy becomes a no-op.Do not set DistributeEvenly on nodes/pods that also require SingleNUMANode.
requiredTopologyScope: NUMANode (devices) + NUMATopologyPolicy: NoneDevices still try to share a NUMA node, but global alignment is off, so CPUs may land elsewhere.Raise the policy to at least BestEffort if cross-resource NUMA locality matters.
GPUPartitionSpec.allocatePolicy: Restricted + gpu-partition-policy: PreferThe Pod demands only top-score partitions while the node treats partitions as optional; a strict Pod on a lenient node can be harder to place.Align the two: use Honor on the node when Pods request Restricted.

The one-line mental modelโ€‹

There is exactly one NUMA decision per Pod, made by the Topology Manager under one NUMATopologyPolicy. NodeNUMAResource and DeviceShare only propose hints into that decision; every other strategy (bind policy, allocate strategy, scoring, GPU partition, topology scope) refines how each plugin builds its proposal or how it binds resources once the NUMA bitmask is fixed.

What's Nextโ€‹

Here are some recommended next steps: