Skip to content

pgw#992 (P0): "free right now" is not a budget for K children's simultaneous peak - #515

Merged
PaulFidika merged 3 commits into
masterfrom
992-simultaneity-bound
Aug 7, 2026
Merged

pgw#992 (P0): "free right now" is not a budget for K children's simultaneous peak#515
PaulFidika merged 3 commits into
masterfrom
992-simultaneity-bound

Conversation

@PaulFidika

Copy link
Copy Markdown
Contributor

Fixes the P0 that killed the first AOT mint ever to reach inductor_compile on the real path, deterministically, at entry 2 of 36.

What A4 got right, and the one thing it got wrong

pgw#868 A4 was right that the pool's per-entry device ask was a guess (9.9 GiB, ~56 % never observed) and right that the entry children's own measured high-water (6.02 GiB) is the truth. It then divided a momentary free-VRAM sample by that truth and called the quotient a simultaneous budget:

width_reason 'K=2 (vram-bound, goals=mint): 29.5 GiB VRAM (sampled) / 9.9 GiB per entry (estimated) -> 2'
peak_child_device_bytes 6461325312   pool_workers 2 -> 4   peak_concurrency 4

OutOfMemoryError: tried to allocate 14.00 MiB; 2.69 MiB free of 44.39 GiB
   9.54 GiB  eager-serving parent   <- resident, by pgw#784's contract
  16.20 GiB  mint child's pipeline  <- resident, this process
  18.61 GiB  four entry children

Two facts make the division wrong, and neither is a shortage of card:

  • the free sample is taken before the widened children exist, so it prices none of their growth — its own comment already says it is read between tenant forwards;
  • the residents keep growing against the same card — 14.9 GiB at the sample, 25.7 GiB at the OOM. A momentary reading cannot bound a future peak.

The fix

_rewiden's grant is capped by a budget taken against the card, not against a moment:

budget = total
       - resident co-tenant      (CardCensus, read at pool construction)
       - own device high-water   (this process, measured)
       - tenant reserve          (only when a serve goal exists)

CardCensus is read once, at construction, and can never be retaken: total − free − own names the co-tenant only while the pool's own children are absent from the card. Taken mid-run it would price the pool's own children as co-tenants and narrow forever. That is asserted as a test row.

This is the filing's fixes 1 and 2 together — the simultaneous peak is priced, and the serving parent's occupancy is subtracted by measurement rather than assumed static. Explicitly not fix 4: DEVICE_RESERVE_BYTES is untouched. Padding a constant (§4.24) moves the same unpriced simultaneity onto the next card.

Fail-closed everywhere: an unreadable census refuses the widen rather than assuming an empty card.

RED / GREEN — the incident's own numbers, through the real policy

master:  K 2 -> 4    K*ask + residents = 53.81 GiB vs 44.39 GiB card  -> OOM
this:    K 2 -> 2    K*ask + residents = 39.77 GiB vs 44.39 GiB card  -> FITS

Not a revert. The same measurement on a card that can hold it still widens to K=4 (asserted). The control row asserts that A4's own question, unchanged, still answers 4 — so if the incident ever stops being reproduced, the fix's row stops proving anything.

Acceptance (from the filing)

  • _rewiden records the simultaneity bound it applied — every term, whether or not the widen happens; a refused widen is the interesting row
  • a test drives a widen against a card whose free sample permits K but whose simultaneous peak does not, and asserts the pool refuses
  • the refusal path is untouched — it worked perfectly and is not in this diff
  • a mint pod with a resident eager-serving parent completes all 36 sdxl entries on an L40S — needs a pod; this PR is the precondition

Tests

tests/test_pool_simultaneity_pgw992.py — 9 rows, all on the incident's bytes: the incident reproduced as a control, the bound holding K at 2, a roomy card still widening, every term named, both unreadable-census bases refusing, the tenant reserve as a term of the budget on a serving pod, the census read exactly once, and a driver reading that does not add up yielding no free capacity.

tests/test_pool_rewiden_pgw868_a4.py gains one autouse fixture holding the card generous — those 16 rows are about A4's divisor, and the card is now the other file's variable. No A4 assertion changed.

Verification

  • tests/ 3318 passed, 37 skipped, 1 xfailed (-n 4 --dist loadfile) — no new skips
  • mypy clean (231 files), ruff clean

Release

Forward-port lands here; the deployable fix rides 0.93.3 off v0.93.2 (package cozy.scheduler;, pre-v1 — master carries the wire-v1 cut and cannot talk to the deployed hub). No collision with #512: that touches cell_key/fleet_cells/aot_cells; this touches aot_compile_pool only.

…us peak

pgw#868 A4 was right that the pool's per-entry device ask was a guess — 9.9 GiB
of which ~56 % was never observed — and right that the entry children's own
6.02 GiB high-water is the truth. It then divided a MOMENTARY free-VRAM sample
by that truth and called the quotient a simultaneous budget:

    'K=2 (vram-bound, goals=mint): 29.5 GiB VRAM (sampled) / 9.9 GiB (estimated) -> 2'
    peak_child_device_bytes 6461325312    pool_workers 2 -> 4    peak_concurrency 4

On the first AOT mint ever to reach `inductor_compile` on the real path, that
killed it deterministically at entry 2 of 36:

    44.39 GiB card, 2.69 MiB free, OOM on a 14 MiB alloc
       9.54 GiB  eager-serving parent   (resident, by pgw#784's contract)
      16.20 GiB  mint child's pipeline  (resident, this process)
      18.61 GiB  four entry children

Two facts make the division wrong, and neither is a shortage of card. The free
sample is taken BEFORE the widened children exist, so it prices none of their
growth — its own comment already says it is read "between tenant forwards". And
the two resident consumers keep growing against the same card: 14.9 GiB at the
sample, 25.7 GiB at the OOM.

So the grant is now capped by a budget taken against the CARD:

    budget = total
           - resident co-tenant       (CardCensus, read at pool construction)
           - own device high-water    (this process, measured)
           - tenant reserve           (only when a serve goal exists)

`CardCensus` is read once, at construction, and can never be retaken: the
subtraction `total - free - own` names the co-tenant only while the pool's own
children are absent from the card. Every term is an observation; the bound is
recorded whether or not the widen happens, so a future OOM names which term was
wrong. Deliberately NOT a larger DEVICE_RESERVE_BYTES (§4.24) — padding a
constant moves the same unpriced simultaneity onto the next card. An unreadable
census refuses the widen rather than assuming an empty card.

RED/GREEN on the incident's own numbers, driven through the real policy:

    master:  K 2 -> 4,  K*ask + residents = 53.81 GiB vs 44.39 GiB card -> OOM
    this:    K 2 -> 2,  K*ask + residents = 39.77 GiB vs 44.39 GiB card -> FITS

Not a revert: the same measurement on a card that can hold it still widens to
K=4 (asserted). `test_pool_rewiden_pgw868_a4` gains one autouse fixture that
holds the card generous — those rows are about A4's DIVISOR, and the card is
now the other file's variable.

Tests: tests/test_pool_simultaneity_pgw992.py, 9 rows; the A4 file's 16 stay
green. mypy clean (231 files), ruff clean.
…ent about the divisor

The z-image contrast specimen (same `_rewiden` code, a different pod) says the
first version of this fix aimed one notch off:

    free_device 16.2 GiB / per_entry 25.0 GiB (ESTIMATED) -> K=1, underwidth=3

That pod's ESTIMATE accidentally protected it, while the L40S died because its
MEASURED peak shrank the denominator. So the fix must not be "prefer the
measured peak" — that is exactly what turned the safe case into the OOM — and
it must not be "distrust the measurement" either. Both are statements about the
DIVISOR, and the divisor was never what was wrong. The invariant is: K
children's simultaneous peak against the residents' future peaks, READ FROM THE
DEVICE, whichever basis supplies the per-entry figure.

That makes it a bound on every width this pool runs, not a patch on the one
path that widens — `entry_workers` divides a momentary free SAMPLE at
construction with precisely the same blind spot. `_apply_simultaneity_bound`
now narrows the constructed width too, floored at 1 (K=1 is the serial path the
pool degrades to; a bound that could forbid it would forbid minting).

On the incident pod the two now compose, visibly:

    narrowing the CONSTRUCTED K 2 -> 1  (18.65 GiB of room, 9.90 GiB estimated ask)
    capping K 1 -> 2 (A4 asked for 4)   (18.65 GiB of room, 7.02 GiB measured ask)
    final K = 2, 39.77 GiB vs a 44.39 GiB card -> FITS   (master: K=4, 53.81 GiB -> OOM)

The measurement still moves K. It just cannot move it past the card.

Also measured on the z-image pod and the reason this reads the DEVICE rather
than summing what the pool believes is loaded: 16.2 GiB free on an 80 GB card
whose static slot sum is 53.3 GiB — ~9 GiB of CUDA context, allocator
fragmentation and child overhead that no catalog arithmetic can see.

DEFECT FOUND BY THE NEW ROWS: `entry_budget_bytes` returned `-1` for
"unreadable", which collided with a real NEGATIVE budget — a card already
oversubscribed by its residents took the unreadable branch and KEPT its width.
It now returns `None` for unreadable, and an oversubscribed card correctly
falls to the floor of 1.

Tests: 12 rows in test_pool_simultaneity_pgw992.py (three new: the z-image
specimen across both bases, the constructed width bounded, the floor never
breached). Suite 3321 passed, 37 skipped, 1 xfailed. mypy clean, ruff clean.
@PaulFidika

Copy link
Copy Markdown
Contributor Author

Updated after the z-image contrast specimen (coordinator input, recorded in the pgw#992 filing).

The first version capped only _rewiden. z-image — same code, different pod, 16.2 GiB free / 25.0 GiB per entry **ESTIMATED** -> K=1, underwidth=3 — shows that pod was protected by its estimate happening to be larger than the truth. So "prefer the measured peak" is exactly the change that turned the safe case into the OOM, and "distrust the measurement" is the same error mirrored. Both are statements about the DIVISOR, and the divisor was never what was wrong.

The bound is therefore basis-agnostic and applies to every width the pool runs — entry_workers divides a momentary free sample at construction with the identical blind spot — floored at K=1 (the serial path the pool degrades to).

On the incident pod the two compose:

narrowing the CONSTRUCTED K 2 -> 1   (18.65 GiB of room, 9.90 GiB estimated ask)
capping K 1 -> 2 (A4 asked for 4)    (18.65 GiB of room, 7.02 GiB measured ask)
final K = 2, 39.77 GiB vs 44.39 GiB card -> FITS      (master: K=4, 53.81 GiB -> OOM)

The measurement still moves K. It just cannot move it past the card.

Also folded in, as the reason the bound reads the DEVICE rather than summing loaded slots: 16.2 GiB free on an 80 GB card whose static slot sum is 53.3 GiB — ~9 GiB of CUDA context, allocator fragmentation and child overhead no catalog arithmetic can see.

Defect the new rows caught in the fix itself: entry_budget_bytes returned -1 for "unreadable", which collided with a real NEGATIVE budget — a card already oversubscribed by its residents took the unreadable branch and kept its width. Now None for unreadable; an oversubscribed card falls to the floor of 1.

Suite 3321 passed / 37 skipped / 1 xfailed; mypy + ruff clean. 12 rows in test_pool_simultaneity_pgw992.py.

Release vehicle: release/0.93.3 is cut from the v0.93.2 tag (package cozy.scheduler;, verified pre-v1) and carries pgw#992 + pgw#989, suite re-run green on that branch (3319). It is not tagged — pgw#993 rides the same cut; mechanics recorded in the tracker.

@PaulFidika
PaulFidika merged commit a4b6bc1 into master Aug 7, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant