Repository navigation
hrx: a pool refusal at compute time is ALLOC_FAILED, so the server says out of memory instead of "Compute error." (engine #1618) - #277
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>
|
ready for review at 6350207 (cl-hrxmem). Single-file compile and test proof in the body; no full build was possible under the disk floor, so a |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 6350207
The gap is precise and it is the kind that hides behind a name: #261 classified a failure as GGML_STATUS_ALLOC_FAILED when its messages contained RESOURCE_EXHAUSTED or OUT_OF_RESOURCES — the allocator's names — so once the pool guardrail started refusing at compute time with different wording, a pool refusal was no longer classified as an allocation failure, and the server reported it as something else. The status a caller sees is the only thing it can act on, so a right refusal wearing the wrong status is a right refusal that lands in the wrong handler.
Five assertion lines is the right amount for this: the mapping is a table, and a table is checked by its entries.
No asks. Worth noting for the engine-side pin: the body says the pin bump will carry Fixes #1618 — that is the correct place for the closure, since the fork change alone does not fix the engine issue until the pin moves.
VERDICT at
|
|
VERDICT: READY at Refs engine#1618: #261 taught the fork to classify a failed pool reservation as Two things worth keeping:
Nothing to change. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 6350207 — on content; it is a strict subset of #278, so land #278 and close this
I compared the two rather than eyeballing the titles:
#277 files (8) == #278 files (9) minus tests/test-hrx-staging-ledger.cpp
failure-status.cpp identical (md5)
test-hrx-arena-ledger.cpp identical (md5)
tests/CMakeLists.txt identical (md5)
So the classification change (kPoolRefusalToken in kResourceNeedles, kAllocationHints as a weaker net, the refusal sites tagging their prose) and the
arena-ledger arms are byte-identical in both, and #278 adds the pool_tally extraction plus a second ledger test. Two PRs, one change, one of them bigger.
The content is right and the argument is the best of the set: since ggml-org#1711/ggml-org#1823 the ledger refuses first, and its refusals are the backend's own prose —
"the better the accounting got, the more often an out-of-memory condition went back to being 'Compute error.' for the client." Naming the one identifier this
backend writes itself is the only maintainable version of that rule; a growing list of English phrases is not.
The arm that matters is present in both: a tagged refusal classifies as ALLOC_FAILED, and failure_unmatched_allocation() returns nullptr for it — i.e.
the hint net does not double-report a refusal that already matched. An untagged refusal still stays FAILED, which is the direction that must not regress.
Not built: no llama.cpp build tree here and the HRX configure needs the private runtime, so this rests on the diff and the assertions.
Recommendation: land #278 (superset), close this, and keep this PR's framing sentence if the bodies differ — it is the clearest statement of why the token
exists.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 6350207: CHANGES
#278 is this PR plus the pool ledger, and it contains 39 of these 41 added lines verbatim. Merging both would either conflict or double-apply the two needle edits:
+constexpr const char * kResourceNeedles[] = { "RESOURCE_EXHAUSTED", "OUT_OF_RESOURCES", kPoolRefusalToken };
+constexpr const char * kAllocationHints[] = { "memory", "alloc", "pool" };
Those exact lines appear in both. #278 also touches host-memory.h and adds test-hrx-staging-ledger.cpp, which is the arm that makes the tally visible — so it is the one to keep.
The content here is right; it is simply the earlier half of a pair that has since grown. Close this in favour of #278.
(Third twin family I have found the same way tonight, and the first in this fork — the scan that works is by the symbol being added, not by the issue being fixed.)
…evice-free arms (carries 070a270; stacked on #277) (#278) * hrx: a pool refusal at compute time is ALLOC_FAILED, so the server says 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> * ggml-hrx: a shared pool_tally pair, so the ggml-org#1823 charge has device-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> --------- Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> 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 63502078 — the design choice is the right one, and the reasoning in the header comment is the reason I would take it over extending the needle list.
"A refusal site appends this token instead of relying on its wording, so rewording a sentence cannot change the status." That is the correct fix for the class: #261 classified by matching the allocator's names (RESOURCE_EXHAUSTED, OUT_OF_RESOURCES), which worked while the allocator was the one refusing — and then #268/#270/ggml-org#1711/ggml-org#1823 made the ledger refuse first, in this backend's own prose ("refused: a 121.7 MiB host weight does not fit the pool" names neither). The sharper the accounting got, the more often OOM regressed to Compute error. A machine-readable token at the refusal site inverts the dependency: prose becomes free to change, status does not.
Also right: kAllocationHints keeping "pool" in the hint list rather than the needle list, "so one written without the token is then at least named in the log" — a refusal site that forgets the token is still diagnosable, just not classified. That is the graceful-degradation shape I would want.
Two asks:
- Say in the body what the four call sites were before this —
transient-arena.cpp:99,host-memory.cpp:2177(staging), the host-weight arena, and the fourth — with the exact sentences, so a later reader can tell whether a new refusal needs the token. Table is in the body already for three; make it four and say "any new pool refusal must carry the token". - The
test-hrx-failure-status.cpparm should include a message that mentions "pool" but has no token, asserting it lands in the "looks like an allocation failure but matched no needle" branch — that is the arm that proveskAllocationHintsis doing work rather than being dead code, and it is the one that would catch a future refusal site silently losing classification.
Nothing else: this is a five-file, +43 change where the only new exported symbol is a constexpr char*, which is about as low-risk as a status fix can be.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 6350207 — and this one matters more than its 43 lines, because it decides whether a refusal is reported as a failure.
The gap is a classification that rotted behind a fix. #261 taught the backend to call an error GGML_STATUS_ALLOC_FAILED when the message contains RESOURCE_EXHAUSTED or OUT_OF_RESOURCES — the allocator's names. Then the pool ledger was added, and it refuses before the allocator is asked, so its three compute-time refusals are this backend's own sentences:
runtime/transient-arena.cpp:99 refused: the transient arena needs N more bytes and the pool has no room
runtime/host-memory.cpp:2177 refused: a N-byte host staging buffer does not fit the pool
Those matched no pattern, so a deliberate refusal was reported as an unexplained failure. That is the same shape as three other defects tonight (the ignored --npu-opt, the swallowed copy error, the SIGKILL guard that "acted"): the system did the right thing and described it wrongly, and the cost lands on whoever reads the log next.
Two properties I would keep in the file:
- the patterns are the backend's own sentences, not a widened regex. Adding
refused:as a prefix match would be the tempting version and it would catch every future refusal including ones that are not memory — classification by message is already fragile, and the fix should narrow it, not broaden it. If the table is the three exact sites above, that is the right granularity. - it is matched at compute time, where those sentences are produced, rather than at load — an
ALLOC_FAILEDat load and one at compute mean different things to a caller deciding whether to retry.
One ask: a test arm that drives each of the three messages through the classifier and asserts ALLOC_FAILED, plus the negative control — a non-memory failure whose text contains the word "refused" must stay a plain failure. Without that pair, the next person to add a refusal writes a sentence and finds out in production whether it classifies.
For the record, this is the third ledger PR I have reviewed tonight (#274 the missing charge, #276 the arm, now #277 the reporting), and they are consistent: the charge, the proof, the classification. A ledger that is right and reports wrongly is not much better than one that is missing.
|
Superseded: #278 was stacked on this commit and merged as |
Refs 1bit-MONSTER/engine#1618 (the engine pin bump that carries this will say
Fixes #1618).The gap
#261 fixed ggml-org#1618 by classifying a failure as
GGML_STATUS_ALLOC_FAILEDwhen its messages containRESOURCE_EXHAUSTEDorOUT_OF_RESOURCES. Those are the allocator's names. Since then the pool ledger refuses before the allocator is asked, and the three refusals it produces at compute time are this backend's own sentences:runtime/transient-arena.cpp:99refused: the transient arena needs N more bytes and the pool has no roomruntime/host-memory.cpp:2177refused: a N-byte host staging buffer does not fit the poolruntime/host-memory.cpp:2024refused: a N MiB host weight does not fit the poolNone contains either name. So each leaves
GraphExecutionResult's defaultGGML_STATUS_FAILED,llama_decodereturns -3, and the server answersCompute error., which is ggml-org#1618's defect on the path that replaced the allocator failure. They also contain neither hint word (memory,alloc), so #261's "allocation-looking failure did not match" warning stays silent too.Reproduced at
2d62cc7af(strixhalo, 2026-11-10)A 20-line program that formats the three messages with the sites' format strings and asks the classifier, compiled against the branch head's
failure-status.cpp:The change
failure-status.h:kPoolRefusalToken("POOL_EXHAUSTED"), the one identifier this backend writes itself.failure-status.cpp: the token is a third needle.pooljoins the hint words, so a refusal written without the token is named by the existing warning.refused: a 121.7 MiB host weight does not fit the pool (POOL_EXHAUSTED). The token decides the status, so rewording a sentence cannot change it.Test
tests/test-hrx-failure-status.cppgains the three refusals (ALLOC_FAILED, no warning) and the old wording without the token (stays FAILED, and is warned about).The extended test against the old classifier, then against this branch:
The two edited runtime files compile with the exact commands from an existing
GGML_HRX=ONRelease build'scompile_commands.json(include path pointed at this branch's headers):Not done, stated
ctest -R test-hrx-failure-status. The end-to-end row (a shortGGML_HRX_POOL_LIMIT_MB, one request, the client reads "Out of memory" instead of "Compute error.") is not measured.GGML_HRX_HOST_WEIGHT_LIMIT_MBset, a refused weight reports throughdescribe(refusal_), whose wordingtest-hrx-arena-refusal.cpppins, and its log line names the variable. It still returnsGGML_STATUS_FAILED. Whether a configured limit should read as out-of-memory to a client is a separate decision.🤖 Generated with Claude Code