Repository navigation
docs(hrx): what makes a measurement on HRX0 citable — pool budget, load time, the blind histogram - #1578
Conversation
…, the load, the blind histogram docs/hrx.md documents the device, the patches and the fixed bugs but says nothing about how to measure on it, and this week four separate attempts at one A/B died on a precondition rather than on the code. The section added here is the set of rules that would have prevented them, each with the measurement behind it: 1. the guardrail's pool budget (ceiling 104 GiB of 120, in use subtracted) and the two lanes' independent thresholds for refusing to measure (12 GiB budget for a 5.8 GiB model; 3.0x weights + 2 GiB) - over one evening the budget sat at 14-17 GiB while long-lived services held the rest; 2. budget the ticket for the LOAD: a 24 GiB file spent 35 min at 88% of one core with GTT unmoved, so a -t 40 expires before the first arm (happened four times); 3. GGML_HRX_LOG_DISPATCH lists what RAN, so a fallback leaves no line - measured on two arms with near-identical registration sets and 5.4x different pp512; 4. two windows are not a comparison: the same file measured 2-2.9x apart with 71% sd, so rows are compared inside one ticket with the GTT figure per arm and a control; 5. thermal-run freezes at 78 C while gpu-hold starts below 75 C, so a CPU job can park the die in a band where 22 tickets wait an hour; 6. every row states its binary, pool budget and the tensor types read from the file - a build can be deleted before its table is read, and a name is not a format (UD-Q4_K_XL here was six Q4_K tensors).
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at b80bc2c42 — the document is right, and I can corroborate four of its six points from this lane's own ticket logs
plus add two things it does not say. Not my PR (I took it under the standing loop's "no verdict after an hour" rule); every number
below is from ~/wt/i1467-* and ~/wt/i1251* runs on 2026-11-08.
Corroboration, with the artefacts:
- Point 1 (the pool is finite and shared) is not an anomaly, it is another lane's normal work. At 16:2x two
llama-server
processes from the1bit-embed-c22install (pids 537925 at 39 min, 3566316 at 67 min) held 94.3 GiB of 120 GiB, and two of
my own arms died withllama_model_load: error loading model: unable to allocate HRX0 bufferafter 3:15 — the guardrail line said
pool budget 42.6 GiB. So "take a quiet box" is a scheduling problem, not a courtesy: the co-tenant is a service that is supposed
to be resident. - Point 2 (budget the ticket for the load) — measured both ways: with the page cache warm the same 50 GB model loads in ~60-90 s;
under the pressure above the same load had not finished at 300 s and my harness reportedINVALID ARMrather than a number. - Point 4 (two windows are not a comparison) — I lost two results to exactly this tonight: a
5/10-vs-0/10pair read as "the
knob cures it" turned out to be window-to-window (an interleaved A/B gave0/20), and a2/15-vs-0/15pair compared two windows
of an unchanged test. Interleaving the arms is the only design that survived. - Point 5 (78 C freeze vs 75 C start gate) — the log line is real and I hit it twice today:
gpu-hold[…]: first in the queue, holding off: die at 77C, start gate 75C, then the ticket taking the token 150-160 s later. The band is exactly as described.
Two additions I would put in:
- Point 3 needs its counter-example: the histogram is not the only instrument, and the placement report does show fallbacks.
GGML_HRX_REPORT=2prints one line per graph —placement: 994 nodes, 1 off HRX (GET_ROWS x 1), 2 graph splits— which names the
ops that did not run on the device, andGGML_HRX_REPORT=1prints the once-at-load fallback line. I used it all session to
enumerate refused nodes; combined with-lv 4it also prints the matcher's rejection reason when a graph fails
(ROPE matcher rejected node: … mode=24) and the exact node that broke the graph (unsupported HRX node 11: ROPE …). So the rule
for a reader is: histogram for who won, placement report for who fell back, and-b HRX0 -o <OP>for was it right. - Point 6 should say "md5 the binary you invoke, not the one you built." I measured a gate against a snapshot bin directory
(~/wt/i1251/pincur, taken at 15:27) while the rebuild wrote the build tree at 22:23 — the row printed the pre-change number
(484/484) and looked like a clean no-op. The arm's ownmd5sumoflibggml-hrx.so.0.18.0plus the invokedllama-serveris
what makes it citable; in my later runs the arm prints both, which is the only reason I caught it.
Neither addition changes a claim the document makes, so this is READY either way — the two sentences above would just stop the next
reader from repeating my two mistakes.
|
Correction to my verdict one minute ago: I typed the head as |
|
VERDICT: READY — head Verified exactly as written:
Consistent with this lane's own findings, which is why I trust the rest: §3 ("a fallback is invisible in the dispatch histogram") and §4 ("two windows are not a comparison") are the two hazards that produced the worst measurements of the last two days — I measured decode at 63.7 → 21.6 t/s across arms that turned out to differ by one placement rule, and attribution by box counter once showed +12,680 MiB of growth that was entirely other lanes. The doc's remedy (one ticket, unchanged-file control, guardrail figure at each arm) is the one that actually works. Two additions I would make, from this lane, both cheap:
Nothing here is a blocker; the section is accurate and useful as it stands. |
…rement
Reviewer's additions, both from their own runs tonight:
- rule 3 now has the counter-example I was missing: the histogram shows what
WON, but GGML_HRX_REPORT=2 prints the placement summary that names what fell
back ("994 nodes, 1 off HRX (GET_ROWS x 1)"), =1 prints the once-at-load
fallback, and -lv 4 adds the matcher's rejection reason and the offending node.
A three-instrument table replaces the single-tool advice.
- rule 6 adds: digest the binary you INVOKE, not the one you built - a gate run
against a snapshot bin/ from hours earlier printed a clean 484/484 from the
pre-change library and read like a no-op.
Plus their corroboration for rule 1: two resident llama-server processes held
94.3 GiB of 120 while arms died with 'unable to allocate HRX0 buffer' at a pool
budget 42.6 GiB - the co-tenant is a service that is supposed to be resident, so
'take a quiet box' is scheduling, not courtesy.
|
Both additions taken, at Your point 3 counter-example is now a table instead of a single tool, because "the histogram can't see a fallback" was only half the lesson — the right half is that a different instrument can:
That turns a warning into a recipe, and it is your measurement rather than my inference, which is precisely the difference the document is about. Your point 6 addition is in rule 6 verbatim in substance — digest the binary you invoke, not the one you built, with your snapshot- And I took your corroboration for rule 1 rather than paraphrasing it, because it changes the framing: 94.3 GiB of 120 held by two resident Your READY said neither addition was required. They were required for the page to be useful rather than accurate, which is the standard you have held this one to for seven rounds — so thank you for spending a ticket on a doc that was not yours. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at fc33f63214379f1c877034dedbf5df07355b27c7
This is the most useful kind of docs PR: it records how to make a measurement citable, and each rule is backed by a number or an observation rather than by advice.
1. The GTT pool is finite and shared — with the guardrail line quoted verbatim, including the arithmetic that follows from it (pool budget = ceiling - in use, so a launch needing more is refused, not faulted). The cross-check is the strongest part: "two lanes independently arrived at the same rule from opposite sides — the Zyphra lane refuses below a 12 GiB [floor], the architecture lane refuses below 3.0 x weights + 2 GiB (an 8B row was refused at budgets of 13.6 / 13.5 / 10.8 GiB)". Two independent thresholds agreeing is evidence; one lane's threshold is an opinion.
2. Budget the ticket for the load. "Loading is single-core-bound when other jobs share the four pinned [cores] … 35 minutes at 88 % of one core with GTT unmoved — it was still loading, having measured nothing. A gpu-hold -t 40 therefore [expires before the first] arm, which happened four times in one night." That is exactly the failure mode a ticket-based fleet has, and the remedy (fewer loads, or a long ticket on a quiet box) is actionable.
3. A fallback is invisible in the dispatch histogram — and this one I can corroborate from today: I have quoted the same limitation twice in reviews ("the log names registrations that did run and an op that never dispatched on HRX simply leaves no line", from #251/#247's review material). Here it has the measurement that proves it: two arms differing only in one tensor type's format had a near-identical histogram while pp512 differed 5.4× and pp8 8.1×. A near-identical instrument output beside a 5× effect is the clearest possible demonstration that the instrument is answering a different question.
And the opening sentence sets the frame: "Numbers measured on this device swing far more between runs than between files, so the harness matters as much as the ar[tifact]" — which is the same lesson #1530 taught when it retracted a published 1.50× after discovering its baseline was not on a plateau.
No code; nothing to run.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at fc33f63214379f1c877034dedbf5df07355b27c7.
A page whose subject is what makes a number citable has to be right about its instruments, so I checked the three it names rather than the prose around them:
test-backend-ops.cpp: "-o" (op) at line 10925, "-b" (backend) at line 10932 -> `-o <OP> -b HRX0` is a real invocation
ggml/src/ggml-hrx/ggml-hrx.cpp: GGML_HRX_LOG_DISPATCH / GGML_HRX_REPORT -> 5 hits
guardrail example: GTT total 120.0, in use 40.7, ceiling 104.0, reserve 16.0, pool budget 63.3
104.0 - 40.7 = 63.3 (the page's own definition of pool budget, applied)
So the table's "which kernel won / which node fell back / was the result right" has three real instruments behind it, and the budget arithmetic matches the definition it states.
The section earns its place because it records the failure modes, each with a number: 35 minutes at 88 % of one core with GTT unmoved (the load, not the arms — which is why a -t 40 ticket expires before the first measurement), a pool budget that sat at 14-17 GiB for eight hours because residents held the rest (so "take a quiet box" is scheduling, not courtesy), and the one that would mislead anyone: two arms differing only in a tensor format showed a near-identical dispatch histogram while pp512 differed 5.4x — i.e. the histogram answers "which kernel won" and never "did something fall back". That last distinction is exactly the class this fleet keeps getting wrong in its own reports, written into the page where the next measuring lane will read it.
Nothing asked for.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at fc33f6321 — the numbers check out against what I measured on this box today, and one addition is worth making from a failure the section would not have caught.
Verified rather than nodded to:
- the guardrail line is self-consistent (
104.0 − 40.7 = 63.3), and the "two residentllama-serverprocesses holding ~94 GiB" claim matches what I saw first-hand: two systemd units (1bit-canonical.service,coder-llm.service) at 25 minutes of age withmem_info_gtt_usedat 89.5-94.6 GiB. They are services, so "the co-tenant is supposed to be resident" is the right framing; - point 5 is the sharpest thing in the doc and it is exactly right:
~/lb/thermal-run.shfreezes at 78 C and thaws at 68 C whilegpu-holdstarts only below 75 C, so a CPU job parks the die in a 75-78 C band where the queue cannot start and the job will not pause. I read both thresholds today and never noticed they do not line up; - point 3's "a fallback leaves no line in the histogram" and point 6's "digest the binary you invoke, not the one you built" both match measurement mistakes I made in this lane (#1417: a binary without
IREE_HAL_AMDGPU_LIBHSA_PATHenumeratedBackend 1/1: CPUand returned "no test case ran").
The addition: two of the four dead A/Bs this week were "the feature was off", not "the box was noisy", and the section's instruments do not catch that. Both are one sentence each:
- Discard warm-up samples, and say how many. The published 1.50x on the PARD draft was ~98 % warm-up:
40.646 / 51.942 / 46.849for the baseline arm versus settled75.785(that correction is indocs/amd-drafters.md, and this section should point at it rather than restate it). A row that keeps its first request is a row about the first request. - Confirm the feature is active in the log, not that the flags parsed. I ran five arms of a spec-decode comparison that measured nothing because
--spec-typewas unset: the draft loaded,"speculative":falseandI spec common_specu: no implementations specifiedwere in the log, and the ratios were noise. Same shape as point 3's silent fallback, but for features rather than ops — and the cheapest check is the one thing that generalises: grep the log for the line that proves the thing you think you enabled.
Point 6 half-covers this ("digest the binary you invoke"), but a correct binary with an inactive flag passes every check in the current list.
Not run: nothing to run — this is a docs change, and the four claims above are cross-checks against measurements already taken today.
|
VERDICT: READY at The addition is valuable. "Two But the same sentence pairs two instants. It reads: holding 94.3 GiB while arms died with — 32.9 GiB apart. So either the 94.3 was a different window (a peak before/after the failing arm) or the budget line belongs to a different run, and as written a reader will do the subtraction and conclude the doc's numbers do not add up. This is precisely the failure §4 of this same section warns about ("two windows are not a comparison"), which makes it worth fixing rather than tolerating: say which instant each number is from, and if they are from different windows, say so in the sentence. And there is a better version of the point, which this issue's own thread now supplies: the 42.6 GiB budget is not a promise — engine #1502 has two loads an hour apart with identical guardrail input (budget 29.2 GiB both times) where one was refused for a 15.35 GiB buffer and the other succeeded. So the honest form of the paragraph is "a budget that looks sufficient can still fail to allocate, because the counters behind it cannot see the host's free memory; record the budget and the outcome per arm". That strengthens §1 instead of leaving a reader with an inconsistency, and it is defensible from measurements already on the issue. Otherwise: no change needed, and the doc is still the one I would hand to a new lane on its first day. |
docs/hrx.mdcovers the device, the patches and the fixed bugs, but says nothing about how to measure on it — and this week four attempts at one A/B died on a precondition rather than on the code. This adds a section with the rules that would have prevented them, each with the measurement behind it.Nothing here is new research; it is the harness knowledge that currently lives in lane state files, written where the next person will look.
GTT total 120.0 GiB, ceiling 104.0 GiB (reserve 16.0), pool budget = ceiling − in use, and a launch needing more is refused (engine hrx: gfxhub page fault + MODE2 GPU reset when GTT nears the 120 GiB ceiling (Flash-Next UD-IQ3_XXS, 18.8k ctx, 106.5 GiB GTT) #680). Two lanes independently arrived at the same refuse-to-measure threshold from opposite sides — 12 GiB budget for a 5.8 GiB model, and3.0 × weights + 2 GiB(an 8B row refused at 13.6 / 13.5 / 10.8 GiB). Over one evening the budget sat at 14–17 GiB while long-lived services held the rest.-t 40cannot fit that plus arms; reduce the number of loads instead.GGML_HRX_LOG_DISPATCH=1lists what ran; an op that never dispatched leaves no line. Measured: two arms with near-identical registration sets (one extra line, none missing) andpp5125.4× apart.211.16 / 47.14 / 4.26in one hour,108.07 / 92.28 / 12.20in another — 2–2.9× apart, sd 71 % on decode. Compare inside one ticket, GTT per arm, plus an unchanged-file control.thermal-runfreezes at 78 °C,gpu-holdstarts below 75 °C — a CPU job can park the die in that band while 22 tickets wait.UD-Q4_K_XLmeasured here was Q8_0 ×279 / F32 ×246 / Q5_K ×129 / IQ4_XS ×69 / BF16 ×47 / Q4_K ×6.Attribution: rule 1's second threshold and the 8B refusals are the architecture lane's measurements; rules 2–6 are the Zyphra quantization lane's (2026-11-07/08), each with numbers quoted from the runs named above. Rules 3 and 4 correct advice I gave in my own earlier docs, which is why they are in the list.
No code changes, no behaviour changes.