Add spread allocation policy for maximum per-pod distinct-GPU coverage - #1936
Draft
jonathan-meiri wants to merge 2 commits into
Draft
Add spread allocation policy for maximum per-pod distinct-GPU coverage#1936jonathan-meiri wants to merge 2 commits into
spread allocation policy for maximum per-pod distinct-GPU coverage#1936jonathan-meiri wants to merge 2 commits into
Conversation
jonathan-meiri
force-pushed
the
add-spread-allocation-policy
branch
from
July 26, 2026 06:28
f5a8c89 to
31db61f
Compare
Follow-up on top of NVIDIA#1621, which introduced the shared greedyAlloc loop with a pluggable replicaComparator (distributed vs packed). The loop still sorts the full candidate slice inside the allocation loop, paying O(n log n) per iteration for n iterations and giving O(n² log n) overall. Since all annotated replicas from the same underlying physical device share the same sort key, sorting at the replica granularity is wasted work — only m (the number of distinct physical devices contributing candidates) needs to be reordered. Refactor greedyAlloc to bucket candidates by their underlying physical device into a small gpuAllocState per device, holding a shared *replicaCount, the pickedFrom counter, and the remaining candidate IDs. A gpuPriorityQueue defers to the caller-supplied replicaComparator on allocated() for primary ordering and to pickedFrom for the tie-break (unchanged semantics). Each iteration pops the best device, takes one of its remaining replicas, updates counters, and pushes it back if any remain. Total cost drops to O(n log m). Both allocation policies (distributed and packed) benefit; no behavior change — the existing test suite (TestDistributedAlloc, TestPackedAlloc, TestPackedVsDistributedContrast, TestDistributedAlloc_PartiallyAllocated_DistributesAcrossDistinctGPUs, etc.) passes unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: runatom-ai <258621014+runatom-ai@users.noreply.github.com> Signed-off-by: Jonathan Meiri <33288957+Meiri28@users.noreply.github.com>
`distributed` minimizes cluster-wide load imbalance — it prefers the GPU with the fewest replicas already allocated across all pods. `packed` does the opposite for bin-packing. Neither guarantees that a single pod's multi-slot request touches distinct physical GPUs: given GPU-0 with 5 allocated and GPU-1 with 3 allocated, a pod requesting 2 slots under `distributed` gets both slots on GPU-1, because GPU-1 has the lower cluster-wide allocated count both before and after the first pick. Add a third policy, `spread`, that primarily orders by pickedFrom (the per-allocation counter of how many slots the current pod has taken from each GPU) and only tie-breaks by allocated(). The pod's own picks therefore drive selection: after taking one slot from GPU-1, GPU-0 becomes preferred (pickedFrom=0 < 1) regardless of cluster-wide load. Result: the pod's slots span as many distinct physical GPUs as possible, which is what multi-GPU workloads (data-parallel training, NCCL, tensor-parallel) actually need. The `replicaComparator` signature is enriched from `func(i, j *replicaCount) bool` to `func(i, j *gpuAllocState) bool` so a comparator can freely mix cluster-wide state (allocated()) and per-allocation state (pickedFrom). Each policy now owns both its primary ordering and its tie-break; the queue's Less becomes a pure passthrough. distributed and packed are behavior-preserving — the primary+tie-break they used to get from greedyAlloc's wrapping is spelled out in the comparator body itself. Validate `spread` in main.go alongside distributed and packed. Add TestSpreadAlloc mirroring the existing policy suites plus TestSpreadPrefersUntouchedGPU and TestSpreadPrefersDistinctGPUsEvenWhenUnbalanced for the defining behavior. TestComparatorsOrderSolelyByAllocated narrows to distributed/packed since spread intentionally violates that invariant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: runatom-ai <258621014+runatom-ai@users.noreply.github.com> Signed-off-by: Jonathan Meiri <33288957+Meiri28@users.noreply.github.com>
jonathan-meiri
force-pushed
the
add-spread-allocation-policy
branch
from
July 28, 2026 09:45
31db61f to
0d2cb9f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
spreadas a third value for--shared-devices-allocation-policy, alongsidedistributed(default) andpacked.spreadprefers physical GPUs the current pod has not yet picked from, maximizing distinct-GPU coverage for a single multi-slot allocation.distributed) is unchanged.Depends on #1826 (heap refactor) — the current diff includes that commit until #1826 lands, then collapses to just the
spreadpolicy addition. Opened as draft for now.Contributed by @Meiri28 on behalf of @runatom-ai.
Motivation
distributedminimizes cluster-wide load imbalance — it prefers the GPU with the fewest replicas already allocated across all pods.packeddoes the opposite for bin-packing. Neither guarantees that a single pod's multi-slot request touches distinct physical GPUs.Consider a node where GPU-0 has 3 free replicas (5 allocated) and GPU-1 has 5 free (3 allocated). A pod requests 2:
Concrete workloads that benefit:
@rajatchopra explicitly floated additional policies on #1788:
This adds the third policy that #1621's abstraction was built for.
Design
Small refactor: the
replicaComparatorsignature is enriched fromfunc(i, j *replicaCount) booltofunc(i, j *gpuAllocState) bool. Each policy now owns both its primary ordering and tie-break — the queue'sLessis a pure passthrough:distributedandpackedare behavior-preserving — the primary+tie-break they used to get fromgreedyAlloc's wrapping is now spelled out in the comparator body itself. All existing tests (TestDistributedAlloc,TestPackedAlloc,TestPackedVsDistributedContrast) pass unchanged.Usage
--shared-devices-allocation-policy=spread # or SHARED_DEVICES_ALLOCATION_POLICY=spreadConfig file:
Commits are DCO-signed.