feat(rescheduling): gpuFragmentation drains multi-pod nodes via simulated whole-node repack - #16
Conversation
…ated whole-node repack Assisted-by: Devin:claude-opus-4.6
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Assisted-by: Devin:claude-opus-4.6
Assisted-by: Devin:claude-opus-4.6
|
Review of the whole-node drain planner. Read the planner against 1. Allowing equally-full destinations removes the only thing steering the replacement away from the node you just drainedThis is the change I'd want resolved before this runs non-dry-run. An evicted task stays in Repro of the exact motivating case (two equally-empty 4-GPU nodes, one 1-GPU pod each), stepping the planned victim to Identical inputs → identical score → Under the old strictly-fuller rule the destination always outscored the source and this couldn't happen. The deterministic source ordering (L245-L255) makes the planner non-swapping, but it says nothing about where the replacement lands; the pool cooldown only rate-limits the coin flip to one per 30 min, it doesn't make it converge. To be precise about what I did and didn't verify: the tie and the random tie-break are confirmed in code; whether it actually bounces depends on the rest of your nodeorder stack (weights, other plugins) and on the delete-vs-reschedule race, neither of which I can see from this repo. So I'd treat "does the replacement land on the destination?" as the thing the dry-run rollout has to answer, and it can't answer it today — the dry-run only logs the plan, not the outcome. Options, roughly in order of how much I'd trust them:
2. "All-or-nothing" is a planning property only; execution is per-victim and unrollbackable
3. Nothing gates this on PDBs, and the default
|
…fractional fullness Address review findings: stamp source+destinations once per drain set and abort the set when the cooldown cannot be recorded; penalize recently drained sources via a node-order fn (separate drain-source annotation so destinations are not steered away from); compare node fullness as a fraction of allocatable for mixed-size pools; break instead of falling through to a fuller source under budget pressure; memoize probe and PrePredicate per victim; log drains at node level; document the predicate-simulation limitation and the 7-victim max on 8-GPU nodes. Assisted-by: Devin:claude
|
Re: Heron review (comment 5446343722) — addressed in a015c06, point by point:
|
What type of PR is this?
/kind feature
What this PR does / why we need it:
The
gpuFragmentationstrategy only repacked nodes whose entire GPU usage was a single pod, and only onto a strictly fuller node. Multi-pod partial nodes (e.g. two nodes each running 1-2 low-priority 1-GPU pods) never consolidated, leaving a steady stranded-GPU residue.The planner now considers all pods on a node and drains whole nodes:
movableSoleGpuTask→movableGpuTasks: every GPU task on the node must be movable (same per-task eligibility checks: running, opt-out label,do-not-disrupt, priority ceiling, controller-owned, single-member PodGroup, unspent eviction cap). One immovable GPU pod disqualifies the node — evicting the others would churn without freeing it.findDestination→simulateDrain: first-fit-decreasing placement of the whole victim set over cloned idle capacity, so two victims can't both claim the same free GPU. All-or-nothing: no partial drains. The simulation covers resources; predicates run against live node state, so placed-pod-dependent constraints (anti-affinity, topology spread) are not co-simulated — a drain can leave replacements Pending in that case rather than misplacing them.exa.ai/repack-drain-sourceand a node-order penalty steers GPU replacements away from it for the cooldown window (a penalty, not a veto — it can still be used if nothing else fits). Destinations get the pool-cooldown stamp (exa.ai/repack-last-eviction) too, so the anti-thrash clock survives Karpenter deleting the emptied source; only the source gets the penalized annotation.maxVictimscaps total evictions per pass with node move sets taken atomically; default raised 1 → 8. Note the practical per-node max on an 8-GPU node is 7 one-GPU victims — a fully used node is never a source.PrePredicateFnare memoized per victim;PredicateFnstill runs per candidate destination.Which issue(s) this PR fixes:
NONE
Special notes for your reviewer:
Execution is per drain set: the source (and destinations) are stamped once before any eviction, and a source-stamp failure aborts the whole set — no evictions without a recorded cooldown. Per-victim PodGroup stamping can still partially fail; that victim is skipped and the remainder drains on a later pass under the already-recorded cooldown.
Does this PR introduce a user-facing change?
Link to Devin session: https://app.devin.ai/sessions/9f5f310b988c4566869471b54b1154dd
Open in Devin Desktop: https://app.devin.ai/desktop/session/9f5f310b988c4566869471b54b1154dd?variant=devin
Requested by: @pfernandes21