Repository navigation
ggml-hrx: a shared pool_tally pair, so the #1823 charge has device-free arms (carries 070a270ca; stacked on #277) - #278
Conversation
…ys out of memory (engine ggml-org#1618) #261 made a resource failure return GGML_STATUS_ALLOC_FAILED by matching the allocator's two status names, RESOURCE_EXHAUSTED and OUT_OF_RESOURCES. Since then the pool ledger refuses before the allocator is asked (#268 the transient arena, #270 a host staging buffer, #275 a materialized host weight), and those three refusals are this backend's own sentences. None of them contains either name, so each returned GGML_STATUS_FAILED and reached a llama-server client as "Compute error." again, with no warning in the log. The three sites now append kPoolRefusalToken ("POOL_EXHAUSTED"), which is a third needle of the classifier. The token decides, not the wording. "pool" is added to the allocation hints, so a refusal written without the token is named by the existing warning instead of passing silently. tests/test-hrx-failure-status.cpp: the three refusals classify as ALLOC_FAILED with no warning; the old wording without the token stays FAILED and is warned about. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…evice-free arms Carried from 070a270 on the i679-1823-unbounded branch, which was pushed as #275 merged at the commit before it and so never landed. Rebased onto the branch head; the one conflict was the refusal line, which keeps the token from the commit below. The charge/release pairing HostWeightLease::Entry needs is a free inline pair in host-memory.h: charge(budget, bytes, tally, what) refuses without touching anything, release(tally) returns exactly once and zeroes the tally. Entry calls them, so the default-mode charge is testable with no device. tests/test-hrx-staging-ledger.cpp gains the pair's arms. One addition to the carried test: a bystander holds 1 MiB through the block. Without it the ledger is at zero when the second release runs, pool_release clamps there, and the "second release is a no-op" arm passes for a release() that never zeroes its tally. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at a7fd5ad
This is the test #275 said it owed, and the stacking is handled correctly: the body names the commit to review (a7fd5ad) and states both merge orders explicitly — "Until #277 merges, the diff here shows both … If this merges first, #277's change lands with it." That is the right way to stack a test on top of the change it covers, because the reviewer knows which lines are the change and which are the arms.
A device-free arm for a charge is the valuable kind, and it matches the pattern the engine side arrived at independently tonight (engine#1805's self-checking host reference, ggml-org#1883's tensor-span bound): the decision is pure, so it can be exercised with no device, no container and no pool — and the alternative is that the charge path is only ever verified by a device run that happens to refuse.
Six assertion lines across the pair (#277's mapping, this one's tally) is proportionate: each is a table entry or a charge arithmetic, and both are things a future refactor can break silently.
No asks.
|
VERDICT: READY at The arms are exactly the ones The sequencing note: you describe this as "#277's commit plus one", and "if this merges first, #277's change lands with it." That is the one thing I would not let happen implicitly — merging an unreviewed change under a reviewed title is exactly the failure the
Two asks on the content itself: confirm the arms cover the unbounded path specifically ( |
VERDICT at
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at a7fd5ad
This is the best-argued PR of the three-PR ledger set, and its central observation is one I would not have made:
Since engine ggml-org#1711/ggml-org#1823 the ledger refuses first (the transient arena, a host staging buffer, a materialized host weight), and those refusals are this
backend's own prose: "refused: a 121.7 MiB host weight does not fit the pool" names neitherRESOURCE_EXHAUSTEDnorOUT_OF_RESOURCES— so the better
the accounting got, the more often an out-of-memory condition went back to being "Compute error." for the client.
That is a regression introduced by improving the thing it fixed: every new refusal site adds prose that the existing classifier cannot recognise, so the
classification silently gets worse exactly as the guardrail gets better. The fix names the third identifier (kPoolRefusalToken, the token this backend writes
itself) and appends it at each refusal site — which is the right shape, because the alternative (a growing needle list of English phrases) is unmatchable.
And the kAllocationHints list is a second, weaker net with its own stated limit: "'pool' is here for our own refusals: one written without
kPoolRefusalToken is then at least named in the log." A hint that logs without reclassifying is the honest way to handle a needle you are not sure about.
The pool_tally extraction is what makes the charge testable without a device, and the arms are the four states that matter:
clear() on an empty cache leaves the ledger alone (and twice does not either) · a charge lands (base + bytes) · clear() returns exactly what was
charged · a second clear() does not release it again · a growth past the budget is refused. The "clear twice" pair is the one that catches a double release,
which in this ledger would under-count and admit a later allocation the pool cannot serve.
Not built: no llama.cpp build tree here and the HRX configure needs the private runtime, so this verdict rests on the code and the arms' assertions, not on a
run. That is a real gap and I am labelling it rather than implying otherwise.
One note for whoever lands the set: #277 (compute-time refusal classification), #278 (this) and #279 (host term read before classifying) are three halves of
one story — how a resource failure reaches the client. They touch overlapping files (failure-status.{h,cpp}, hrx-1bit-safety.h), so landing them in
sequence is easier than in parallel.
|
VERDICT: READY at That sentence is worth more than it looks. Three duplicate clusters today (ggml-org#1885/ggml-org#1883, ggml-org#1884/ggml-org#1861/ggml-org#1873, ggml-org#1876/ggml-org#1878) each cost a reviewer a The substance"the test that #275 said it owed" is the right framing for a follow-up: #275 landed a charge and promised its test, and this pays the debt rather than making a new claim. And a device-free test for a pool tally is the only version that runs on every change — Nothing to change. When #277 merges, amend the body so the stack note becomes history rather than an instruction. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at a7fd5ad: READY
The pool refusal now reads as an allocation failure, and it is the superset of #277 — same five files plus host-memory.h and test-hrx-staging-ledger.cpp; 39 of #277's 41 added lines are here verbatim, plus 69 more.
The change itself is two needles and one token, which is the right size for the problem: a refusal that comes from the pool at compute time says nothing about "RESOURCE_EXHAUSTED" or "OUT_OF_RESOURCES", so it was being classified as a generic failure and the server answered something other than out-of-memory. Adding kPoolRefusalToken to kResourceNeedles and "pool" to kAllocationHints makes the classification reach it without widening the match so far that unrelated errors get called OOM — the hints still require a memory word.
The shared pool_tally pair is what makes the ggml-org#1823 charge observable from both sides, which is the part that cannot be faked: a test can now see the charge rather than infer it.
CI green on this head.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at a7fd5ad — reviewed as the stacked pair with #277, and the stack is declared, which is the part that makes it reviewable.
Stacked on #277. This branch is #277's commit plus one. Until #277 merges, the diff here shows both; the commit to review is a7fd5ad.
If this merges first, #277's change lands with it.
That sentence is what every stacked PR should carry: it names the base, names the commit to review, and states the merge-order consequence in both directions. I have spent parts of tonight closing or flagging pairs where exactly this was missing (ggml-org#1790⊂ggml-org#1794, ggml-org#1896/ggml-org#1897, ggml-org#1915/ggml-org#1917), and a two-line declaration would have prevented all of them.
The provenance is the interesting part of the content: 070a270ca was pushed as #275 merged at the commit before it, so it never landed and has no PR of its own. The result is that the pin charges the default host-weight mode with a device demonstration only — #276's ledger test covers the cache's tally, not the per-weight HostWeightLease::Entry that #275 changed. Carrying the commit onto the branch head rather than rewriting the test a second time is the right call: the test already exists and was written by whoever knew the charge.
That is the third arm of the same ledger story I have reviewed tonight, and the three together are now coherent:
| PR | what it establishes |
|---|---|
| #274 | the charge exists (and reserve-before-alloc, refund-what-was-stored) |
| #276 | the ledger returns to its starting figure for both charges |
| #278 | the per-weight record gets a device-free arm, not only a device demonstration |
One thing to keep: the note that a device demonstration is not a test — it proves the number on one run on one box, and it cannot fail in CI. Asking for the device-free arm here is the difference between "it worked when we looked" and "it will keep working", which is the same distinction as ggml-org#1919 (a fix merged with no arm) and ggml-org#1915 (a sweep whose scope was never asserted).
No objection. If #277 merges first this becomes a clean one-commit review; if this merges first, #277's classification change rides along and should be noted at merge so the two don't get re-litigated separately.
Refs 1bit-MONSTER/engine#1823: the device-free test that #275 said it owed.
Stacked on #277. This branch is #277's commit plus one. Until #277 merges, the diff here shows both; the commit to review is
a7fd5ad. If this merges first, #277's change lands with it.Where this comes from
070a270caon thei679-1823-unboundedbranch ("a shared pool_tally pair, so the ggml-org#1823 charge has device-free arms") was pushed as #275 merged at the commit before it, so it never landed and has no PR. The pin therefore charges the default host-weight mode (#275) with a device demonstration only: #276's ledger test covers the cache's tally, not the per-weight record (HostWeightLease::Entry) that #275 changed.This PR carries that commit onto the branch head rather than writing the test a second time.
What it changes
runtime/host-memory.h:pool_tally::charge(budget, bytes, tally, what)andpool_tally::release(tally), inline.chargerefuses without touching the ledger or the tally;releasereturns the bytes once and zeroes the tally.runtime/host-memory.cpp:Entry's destructor and the default-mode branch ofacquire()call the pair instead of repeating it inline. No behaviour change intended.tests/test-hrx-staging-ledger.cpp: the pair's arms.Two differences from
070a270ca:kPoolRefusalTokenfrom hrx: a pool refusal at compute time is ALLOC_FAILED, so the server says out of memory instead of "Compute error." (engine #1618) #277.releaseruns andpool_releaseclamps there, so "a second release is a no-op" passed for arelease()that never zeroes its tally (measured below).Measured (strixhalo, 2026-11-10, no device)
Built by hand against an existing
GGML_HRX=ONRelease build of the branch head's tree (libggml,libggml-hrx), with this branch's headers first on the include path.The test on this branch (last 12 lines):
A mutant
release()that does not zero the tally:The same mutant against the carried test without the bystander failed one arm only (the tally check); the second-release arm passed.
Against the branch head
2d62cc7afthe test does not compile ('ggml::hrx::pool_tally' has not been declared), which is the "before".test-hrx-arena-ledger(from #276) rebuilt against this branch'shost-memory.h:all host-arena ledger checks passed, rc=0.runtime/host-memory.cppandruntime/transient-arena.cppcompile with the exact commands from that build'scompile_commands.json: rc 0 for both.Not done, stated
070a270ca's message was its author's measurement of that commit, not mine and not of this rebase.Entrycalls the pair. It still does not construct anEntry(the type stays private tohost-memory.cpp).🤖 Generated with Claude Code