enhancement: The ledger frees a partition slot before the hardware does, so a request inside that window is refused
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 38/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- go, kubernetes
- Domain
- devops, infrastructure
Research direction
Start at DevicesReconciler.LivePhysicalOccupied and trace how reclaimMaxMisses, ledger remaining, and partitioned node keys affect placement before Allocate. Reproduce the allocated + remaining and reclaim-window partition e2e scenarios, then choose and implement a documented approach from the issue's options. Done means replacements no longer reach an unusable reclaimed instance, with tests covering the reclaim window and full-card behavior.
Written by the indexing model from the issue text.
Description
Summary
Deleting a partition-backed Pod frees the card in the ledger immediately, but the hardware
instance itself is only destroyed after the reclaimer's debounce (three consecutive misses). In
between, the node advertises room the card does not yet have, so a replacement scheduled into that
window is admitted by the scheduler and then refused at allocation time — surfacing on the Pod as
UnexpectedAdmissionError.
A workload that retries converges by itself. A workload that does not — restartPolicy: Never with no
owning controller — simply fails.
Mechanism
Occupancy for placement comes from DevicesReconciler.LivePhysicalOccupied, which builds the
occupied set from the allocation annotations of live Pods on the node. That set is the right one
for accounting, but it is not the physical truth during reclaim:
- the Pod object is gone, so its placement leaves
LivePhysicalOccupied, and the ledger's
remainingplus the node's*.partitioned*keys go back to the idle values; - the vendor instance still exists, because
reclaimMaxMissespasses must observe it unowned first; - a replacement therefore picks a placement the card cannot actually host, and creating it fails.
Evidence
Reproduced on a single-card NVIDIA H100 with the partition e2e scenarios:
- The single-shot form fails. In the
allocated + remainingscenario, the refill Pod submitted
right after the saturating Pod was deleted never ran:Failed/UnexpectedAdmissionError. Everything
else in that scenario passed — the freed count was advertised correctly (1 → 7), the room just was
not usable yet. - The retrying form self-heals. In the reclaim-window scenario, a same-profile replacement
converged on its second attempt, 45 s after the predecessor was deleted, with exactly one
terminal allocation failure along the way.
Also reproduced four times earlier on T-Head PPU hardware, so this is the shared reclaim path, not
one vendor's driver.
Reproduced again later on a node carrying eight accelerators with one of them partitioned, after the
stale-identity half described below was fixed: the same allocated + remaining scenario still fails
on its last check, and the same reclaim-window scenario now converges on its first attempt. The
window is therefore still open, and it is what remains of this issue.
Scope: what this issue is, and what does not close it
This issue is about one thing: for as long as the reclaimer has not destroyed the instance, the
node advertises room the card does not have, and a request that lands in that interval is refused
rather than held. Options 1 to 3 below each address that.
These do not close it, and a change that only does one of them should not be filed against this
issue:
- Making the refusal cleaner. A refusal is the correct outcome inside the window given the
current design; the complaint is that the request should never have been admitted, or should have
been queued rather than failed. - Fixing the identity comparison in the reclaimer (see below). That removes a different failure
that lives in the same interval. The window is untouched by it. - Adding a retry at the call site.
getAllocatingPodWithRetryexists and is used by only one of
three call sites; closing that asymmetry removes one class of terminal rejection and is worth doing,
but a request that names a placement the card genuinely cannot host is still refused.
A separate defect that lived in the same window, now fixed
An earlier revision of this issue stated that the replacement being handed the destroyed instance's
identity was already fixed by re-reading the card's partitions under its lock before destroying, and
that this made the failure inside the window clean — a refusal, never a dangling identity. That was
wrong, and it is worth recording why, because the reasoning looked sound.
The re-read is real and is on main: destroyMarkedInstancesOnCard re-reads the card's live
instances inside its lock and compares each against the marker with matchesMarker. But that
comparison is the GPU-instance id, the placement and the MIG-device identifier — and every one of
those is reproduced by a successor at the same slot. Placement selection is the lowest free slot and
therefore deterministic, and a MIG-device identifier is name-based, derived from the parent
accelerator and the instance's own identity, so destroying a partition and creating another at that
slot yields the same string. The re-read hands back an object whose every identity field equals the
predecessor's. It cannot separate them however often it runs.
So the window did not produce a clean refusal. It produced exactly the dangling identity the
re-read was believed to have removed: the reclaimer destroyed a partition an allocation had just
granted — measured at 0.32 s after the grant, on the same instance id, the same placement and the
same identifier — and the container then failed to start because its device could not be resolved.
Because that failed Pod left a marker of its own, the next allocation was condemned the same way, so
the first allocation after the markers were clear succeeded and every one after it did not.
Fixed by #499, which drops the liveness question entirely and asks the markers alone whether another
Pod claims the id. On hardware, three consecutive allocate-delete-allocate rounds at the same slot go
from 1/3 to 3/3, and the reclaim-window scenario's convergence cost drops from two attempts to one.
This changes nothing about the window itself. The refusal is now genuinely a refusal, which is
what the earlier revision claimed prematurely.
Options considered
- Count the not-yet-destroyed instances as occupied — union
LivePhysicalOccupiedwith the
instances the durable markers still record but no live Pod owns. Placement then avoids them, which
removes the window entirely wherever another placement is free, and turns the single-card-full case
into an honest "no room" refusal (back-pressure the scheduler and Kueue can act on) rather than a
failed create. Cheapest correct change; does not make the full-card refill succeed any sooner. - Reclaim synchronously during allocation — when the chosen placement is held by an unowned
instance, destroy it in place and carry on. Closes the window completely, including the full-card
case. Costs a vendor round trip insideAllocate, and needs the ownership test to be exactly the
reclaimer's, or it risks destroying an instance whose Pod is still arriving. - Hold the ledger until the instance is really gone — release
remainingand the node keys on
destroy rather than on Pod deletion. Most truthful, and the scheduler stops being told about room
that does not exist; makes every release visibly slower. - Leave it to callers — document the window and rely on retries. What happens today.
Worth noting that (1) also addresses the other half of the observation: an instance carved outside
GPUStack is never reflected in the node keys at all, and that never converges. Confirmed directly
since: an instance created by hand on a managed card is invisible to every per-profile key and to the
bare partition count for as long as it exists, and the count never catches up.
- Dominant language
- Go
- Stars
- 4
- Forks
- 6
- Avg merge
- 1h 4m
- Merged PRs (30d)
- 223
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from gpustack/gpustack-operator
-
enhancement: A recreated Devices ledger never restores the node's accelerator counting capacityOpenkind/enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
gpustack/gpustack-operator#146 ·
Maintainers usually reply within 1 day
-
todo
Difficulty 4/5 3-5 days Newbie friendliness 43/100
gpustack/gpustack-operator#778 ·
Maintainers usually reply within 1 day
-
area/vendor/ascend todo
Difficulty 4/5 3-5 days Newbie friendliness 28/100
gpustack/gpustack-operator#765 ·
Maintainers usually reply within 1 day
-
area/vendor/amd area/vendor/ascend area/worker todo
Difficulty 4/5 3-5 days Newbie friendliness 12/100
gpustack/gpustack-operator#763 ·
Maintainers usually reply within 1 day
-
kind/enhancement
Difficulty 5/5 Over a week Newbie friendliness 12/100
gpustack/gpustack-operator#756 ·
Maintainers usually reply within 1 day
All issues in gpustack/gpustack-operator
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
github/github-mcp-server#3581 ·
Maintainers usually reply within 2 days
-
agent-review-finding chore
Difficulty 1/5 Under an hour Newbie friendliness 85/100
jordansmall/spindrift#6033 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
hyperledger/fabric-x-common#230 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 Half a day Newbie friendliness 65/100
open-wanderer/wanderer#1443 · 3 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day