Repository navigation
Closed
Labels
bugSomething isn't workingSomething isn't workinghrxThe HRX (HIP/IREE) backend routeThe HRX (HIP/IREE) backend route
Description
test-backend-ops test -b HRX0 is not green on this pin, and the failures are the same every run. Gates and PRs that cite "the HRX suite passes" should cite this issue until it is fixed or the cases are waived on purpose.
What was run
- Binary: the fork at
cffae5c4(HRX build, gfx1151), on strixhalo, 2026-11-06 ~03:37 AST. - Command:
./test-backend-ops test -b HRX0(the full suite, not filtered with-o), run twice back to back. - Result: 4050/4374 passed both times, 324 cases failed. The same 324 case names fail in both runs; only the last digits of some ERR values change.
- Evidence on strixhalo:
~/lb/aval/tattn-jit-scope-20261106T063616Z/full-1.log,full-2.log,inner.sh.
The failures
1. TOPK_MOE: 320 of 320 cases fail, every shape and every gating_func (0-3), with and without with_norm, bias_probs, scale_w.
- Almost every case reports
[VIEW] ERR ≈ 1.0. Some also report[RESHAPE]or[SCALE]errors. - The test sorts both outputs before comparing them (
test_topk_moe::err→nmse). So this is a value mismatch, not a tie-order difference in the expert ids. - HRX does claim at least part of this subgraph: the log shows
ggml_hrx dispatch: llm.moe_router.top8_f32 (fused). The failures are therefore not only the harness splitting the graph around an unclaimed node. - Two cases (
ne=[31,22,1,1], n_expert_used=8, with_norm=1, withscale_w0 and 2) fail earlier, at kernel compile time:With 31 experts the matcher accepts a shape that the kernel's range assumption does not allow. The matcher should refuse that shape, or the assumption should be widened.HRX Loom JIT ... target 'amdgpu-rdna3-5' export 'qwen3_moe_router_top8_f32' ... rejected 'index.assume' ... predicate 'range' on 'experts_per_lane0' requires range [1, 16] but ... graph_compute: failed to prepare command 0 kind=Kernel ...
2. DSV4_HC_POST: 4 of 6 cases fail. All four are identity=0; both identity=1 cases pass.
| case | ERR (bound 1e-7) |
|---|---|
n_embd=1, n_tokens=1, identity=0 |
0.078 |
n_embd=31, n_tokens=17, identity=0 |
0.217 |
n_embd=128, n_tokens=257, identity=0 |
0.174 |
n_embd=4096, n_tokens=21, identity=0 |
0.174 |
The node goes to hip.hc_post (single), which is an HRX dispatch. These look like a real kernel gap on the non-identity path.
Why this matters
- The open question is whether real MoE decode ever reaches the failing router path. Our measured MoE runs (Qwen3-30B-A3B and others, judged on KLD) look correct. So either the fused router is not selected for those models' shapes, or the suite tests a layout that real graphs do not produce. Neither is shown yet.
- Gates that run the suite with
-o <op>filters never see these cases. The overnight TOP_K investigation in TATTN ran 180 filtered runs with no failures, and none of them covered this.
Asks
- Re-run the full suite at the current engine pin (
third_party/llama.cppat 710c7c23) undergpu-hold. Post whether the counts change. - TOPK_MOE: find which node the
[VIEW]mismatch is on. Then decide whether it is a kernel bug or a harness or graph-shape artefact, and state whether a real model hits the fused router. - Make the
qwen3_moe_router_top8_f32matcher refuse expert counts outside the kernel's assumed range (the 31-expert compile failure). - DSV4_HC_POST: fix the
identity=0path ofhip.hc_post, with the four cases above as the test. - Until 1-4 are done, a gate that reports the HRX suite reports
4050/4374, not "green".
Found by pi-attn (TATTN, 2026-11-06 03:41 AST); counts re-checked from the logs by the orchestrator.
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workinghrxThe HRX (HIP/IREE) backend routeThe HRX (HIP/IREE) backend route
Activity
bong-water-water-bong commentedon Nov 6, 2026
ask 1 — the pin does not build, and after the one-line build fix the counts do not change
The pin could not be built at all
third_party/llama.cppat710c7c23= tip of1bit/hrx-vulkan-patched(0 commits ahead).ninja test-backend-opsstops atllama-context.cpp:Cause:
f1487819e(engine #681, merged in #219) passesmodel.arch_name()—std::stringpersrc/llama-model.h:742— toLLAMA_LOG_WARN's printf-style%s. Nothing on the branch tip compiles from source. Fix is one line(
arch_name().c_str()), PR llama.cpp #220, headbd0762ca3, base1bit/hrx-vulkan-patched, MERGEABLE, pi-prs pinged.The run below is that pin plus that one line; nothing else differs.
Counts at the pin (with the build fix),
test-backend-ops test -b HRX0, twice back to backThe totals moved because the pin carries 225 more cases (15 commits' worth of new kernels/tests), not because anything was
fixed: the failure structure is unchanged —
[VIEW]320 (all 320 TOPK_MOE cases) +DSV4_HC_POST4, byte-identical betweenthe two runs. Evidence:
~/lb/aval/tattn-1259-20261106T133039Z/full-{1,2}.log.ask 2 — which node the
[VIEW]mismatch is on, and whether a real model reaches the fused routerThe node
[VIEW]is not a tensor name: the harness printsggml_op_desc(t)(tests/test-backend-ops.cpp:1494), i.e. the op, andggml_argsort_top_k(ggml.c:5365) isargsort(DESC)+ggml_view_4d(..., k, ...).test_topk_moe::fusion_test_nodes()returns exactly
{selected_experts, weights}, so the two compared tensors are the id tensor (op = VIEW) and the gatheredweights. Only the ids mismatch; the weights node passes. Which matters, because the weights are gathered from those
ids: for distinct probs a different id set means a different weight multiset, so weights passing means either the two id sets
differ only inside a tie group, or the weights comparison is not seeing the difference.
Which dispatch produces those ids (from the dispatch log, not from reading)
common.argsort_rows_f32+common.get_rows_small_f32+common.sum_rows_f32(all three appear in a full-suite dispatchlog).
match_argsort_rowsaccepts any F32→I32 argsort withne[0] <= 1024(dispatch-small-rows.cpp:130-150).append_moe_router_top8_coverageliststopk_viewamong the ten nodes it claims, so whenllm.moe_router.top8_f32matches, the ids come out ofqwen3_moe_router_top8_f32.GGML_HRX_LOG_DISPATCH=1):ne=[32,22], n_expert_used=8→ fused (moe_routerlogged 7x);
ne=[8,22] k=4,ne=[31,22] k=8,ne=[128,1] k=128→ not fused (ids fromargsort_rows_f32).Do real models reach the fused router? Yes — measured, three logs
GGML_HRX_LOG_DISPATCH=1server logs on this box showggml_hrx dispatch: llm.moe_router.top8_f32 (fused)at load for:(
~/lb/aval/tattn-rx-reach/20261104T062343Z/server.log,~/lb/aval/tattn-18k-window-floor-20261105T092045Z/server.log);~/lb/aval/tattn-qwen36-decode-20261103T223743Z/qwen36-c131072.server.log).Matcher bounds agree with that:
is_supported_expert_count=[32, 512]and% 32 == 0,is_supported_route_count=1..32(
dispatch-moe-router.cpp:95-101) — so Qwen3-30B-A3B (128/top-8) and Qwen3.6-35B-A3B qualify, and most of the suite's shapes(8, 31, 40, 71, 129) cannot be fused at all. So the fused router is on the hot path for our measured MoE models — which is
consistent with those runs being KLD-correct, and means "the fused router is wrong" cannot be the whole story.
The new datum that changes the question
The same cases pass when the suite is filtered to them and fail in the full run. Filtered runs of four of the failing
cases at the pin (
[128,1] k=128,[32,22] k=8,[8,22] k=4,[31,22] k=8) all returned rc=0; those same cases are amongthe 320 that fail in the full suite. So this is not a pure shape/kernel bug: it depends on the process state built up by the
preceding ~4000 cases. That is the same class as the TOP_K flakiness I closed out yesterday (identical binary, dirty in one
claim and clean in another), and it is why I am not calling it a kernel bug yet.
Next (queued): one full-suite run with a dump that prints only mismatching comparisons — both sides plus sorted copies of
the ids — so the ids the suite actually reads are on the record instead of inferred. Will follow up here with that.
One latent matcher bug found on the way (same class as ask 3)
match_argsort_rowsacceptsne[0] <= 1024, butggml_argsort_rows_f32assumesrange(%column_count, 1, 1024)andrange(%rank, 0, 1023)(small_rows_f32.loom), i.e. its own bound is< 1024.ne[0] == 1024is therefore accepted by thematcher and outside the kernel's assumption — exactly the shape of the 31-expert
qwen3_moe_router_top8_f32reject in theissue body. No TOPK_MOE case in the suite has 1024 experts, so it is not tonight's failure, but the same fix pattern applies.
bong-water-water-bong commentedon Nov 6, 2026
ask 2 (final) — the
[VIEW]mismatch is the expert-id tensor coming back un-written, and the producer iscommon.argsort_rows_f32Follow-up to my previous comment. Numbers below: pin
710c7c23+ the one-line build fix (PR llama.cpp #220), fulltest-backend-ops test -b HRX0, one claim, three arms(
~/lb/aval/tattn-1259c-20261106T140044Z/).[VIEW]failsGGML_HRX_DISABLE_DISPATCH=common.argsort_rows_f32GGML_HRX_DISABLE_DISPATCH=llm.moe_router.top8_f321. What the failing tensor actually contains
A dump inside a full run (
~/lb/aval/tattn-1259b-20261106T134148Z/full.log, 640 mismatch lines, dump patched to print onlymismatching comparisons) shows, for the ids tensor:
The CPU side is real ids. The HRX side is
±~1.08e9int32, which are the float bit patterns of the argsort input(
0.5f = 1056964608,-1.0f = -1082130432). So the ids output is not written on the HRX path — the buffer still holdsthe data the previous kernel put there. The gathered
weightsnode mismatches as well (ndiff 42/88), so this is not the"sorted comparison hides an order difference" artefact I first hypothesised: the ids are genuinely absent.
2. Which dispatch
common.argsort_rows_f32is the producer: disabling that one matcher drops[VIEW]failures 320 → 40 andTOPK_MOE failures 318 → 6. That kernel (
kernel-corpus/kernels/loom-libs/ops/small_rows_f32.loom) computes eachelement's rank and does one store, guarded by a data-dependent assume:
%rankis computed at run time, solt(%rank, %cols)is not something the compiler can prove — that assume is the onlyguard around the single store, and it is the first thing I would look at (an assume that the JIT turns into a predicate
would silently drop the store, which is exactly the "ids hold the previous kernel's bytes" signature).
llm.moe_router.top8_f32leaves the count at 320/320.(
[128,1] k=128,[160,4] k=160,[129,1] k=128), so at least one more fusion covers the argsort+get_rows subgraph andhas the same hole. Enumerating it needs the router and the other routed-gate fusions disabled together; that is the next
arm if someone wants it, and it is worth doing because
ggml_argsort_top_kis also the sampler's top-k path, not onlyMoE.
match_argsort_rowsalso accepts a shape its kernel forbids (ne[0] <= 1024vs the kernel'srange(%column_count, 1, 1024)/range(%rank, 0, 1023)) — same class as the 31-expert router reject (ask 3). No suitecase has 1024 experts, so it is latent.
3. Does a real MoE model reach the fused router? Yes — measured, and that is now the reassuring half
GGML_HRX_LOG_DISPATCH=1server logs from this box showggml_hrx dispatch: llm.moe_router.top8_f32 (fused)at load forQwen3.8-Flash-Next UD-IQ3_XXS (two separate serves) and Qwen3.6-35B-A3B UD-IQ4_XS
(
~/lb/aval/tattn-rx-reach/20261104T062343Z/server.log,~/lb/aval/tattn-18k-window-floor-20261105T092045Z/server.log,~/lb/aval/tattn-qwen36-decode-20261103T223743Z/qwen36-c131072.server.log). The matcher bounds agree(
expert_countin[32,512]and% 32 == 0,route_count1..32).So: real models do hit the fused router, the router is not the arm that reproduces the failure, and that is consistent
with our measured MoE runs being KLD-correct. The exposure is instead
common.argsort_rows_f32, which is generic — anyF32→I32 argsort with
ne[0] <= 1024, which includes the MoE routers of models the fused path does not cover and thesampler's
ggml_argsort_top_k.4. The condition, stated honestly
Four of the failing cases pass when the suite is filtered to them (
[128,1] k=128,[32,22] k=8,[8,22] k=4,[31,22] k=8— all rc=0) and fail inside the full run. So the hole is opened by process state, not by shape alone; thedeterministic 320-per-run count is a property of this sequence of 4600 cases, not of the shapes. Anyone reproducing this
should reproduce it in a full run, and anyone fixing it should keep an eye on whether the fix merely changes the sequence.
ask 1 in my previous comment (pin counts unchanged: 4275/4599, same 324 cases, identical between runs; plus the fact that
the pin does not compile without PR #220). Asks 3-4 are not mine on this lane.
bong-water-water-bong commentedon Nov 6, 2026
ask 2 closeout — two distinct culprits: the argsort producer (280 of 320) and, for the residual 40, the softmax output itself
Third and last arm on this (
~/lb/aval/tattn-1259d-20261106T100845Z/, full suite per arm, pin710c7c23+ the build fixin PR #220, mismatch dump on):
[VIEW]failsGGML_HRX_DISABLE_DISPATCH=common.argsort_rows_f32llm.moe_router.top8_f32Only two things in the tree cover
GGML_OP_ARGSORT(that single-op matcher and the MoE-router fusion), so with both off theresidual 40 cannot be a kernel that computes ids at all — and the dump says what they are:
The HRX side holds raw logits (they are the argsort input values, in
[-1,1]), not softmax probabilities, and the idstensor holds the same floats reinterpreted as int32. So for those cases the softmax output was never materialised — the
argsort and the gather then work on the pre-softmax bytes. That puts the residual 40 upstream of everything I disabled and
explains why they survived both disables.
The two culprits, as far as measurement goes
common.argsort_rows_f32, whose store is guarded by a data-dependentindex.assume %rank [range(%rank,0,1023), lt(%rank,%cols)](lead, not proof).common.softmax_rows_f32is the single-op matcher(
dispatch-small-rows.cpp:696), and it is the next thing to disable:GGML_HRX_DISABLE_DISPATCHtakes a comma list, socommon.argsort_rows_f32,llm.moe_router.top8_f32,common.softmax_rows_f32is one arm away. I would expect the[VIEW]count to go to 0 if that dispatch is the second half of the hole, and to stay at 40 if the aliasing is happening in the
graph/engine rather than in a kernel.
A tooling caveat for whoever reads those logs
The dump prints from inside
test_topk_moe::err(), i.e. between the harness's[VIEW] ERR = ...printf and theTOPK_MOE(...): FAILprintf, so per-case greps on those two logs must use the[VIEW] ERRcount (40) and not a one-lineTOPK_MOE(...)regex (which only matches the 2 lines where the dump did not interleave). The 320/40/40 numbers above are thelabel counts.
Nothing else changes: ask 1 (pin counts 4275/4599, same 324 cases, identical between runs) and the real-model answer
(Qwen3.8-Flash-Next and Qwen3.6-35B-A3B both log
llm.moe_router.top8_f32 (fused), and the router is not implicated inthis failure) stand from my previous two comments. Asks 3-4 untouched on this lane.
bong-water-water-bong commentedon Nov 6, 2026
ask 2 CLOSED — the failures are entirely HRX's small-rows SOFT_MAX + ARGSORT kernels; the MoE router is not involved, and with those matchers off the cases pass
Last arm (
~/lb/aval/tattn-1259e-20261106T101729Z/), same pin + build fix (PR #220), mismatch dump on, one claim:GGML_HRX_DISABLE_DISPATCH=)[VIEW]failscommon.softmax_rows_f32common.argsort_rows_f32common.argsort_rows_f32,llm.moe_router.top8_f32common.argsort_rows_f32,llm.moe_router.top8_f32,common.softmax_rows_f32(Totals shrink as matchers come off because the harness then reports whole groups
not supportedand skips them; the[VIEW]column is the comparable one.)What that fixes in the record
loom-libssmall-rows kernels, and specifically in bothggml_softmax_rows_f32andggml_argsort_rows_f32— both live inggml/src/ggml-hrx/kernel-corpus/kernels/loom-libs/ops/small_rows_f32.loom.Disabling the softmax matcher alone halves the failure count (320 -> 160); disabling the argsort matcher alone leaves 40;
disabling both leaves none. So the two matchers each open part of it, and the residual 40 I reported earlier are the
softmax half, observed through the ids (the weights carried raw logits instead of probabilities — the dump is unambiguous).
llm.moe_router.top8_f32off changes nothing), and it is reached by realmodels — so nothing here contradicts the KLD-correct MoE runs on this box.
harness' sorted comparison agree on every one of them (320 cases, 80/160/640 dump lines before).
full run, for a fixed binary. Both kernels store their output under a predicate (
scf.if %validin the softmax,index.assume %rank [...]around the single argsort store), which is where I would look first — a store that can bepredicated away turns into "the buffer keeps the previous kernel's bytes", which is exactly the ±1.08e9 / raw-logit
signature in the dumps.
For asks 3-4
match_argsort_rowsacceptsne[0] <= 1024whileggml_argsort_rows_f32assumesrange(%column_count, 1, 1024)(sone[0] == 1024is accepted and outside the kernel'sown bound) — same shape as the 31-expert
qwen3_moe_router_top8_f32reject.[VIEW]fails = 0; the dumpedcomparison is the known-answer probe (
~/lb/aval/tattn-1259e-20261106T101729Z/all-three.logshows the passing state).ask 1 stands as posted (pin 4275/4599, same 324 cases, identical between runs; the pin does not build without #220).
third_party: bump llama.cpp to ced49c61 -- the HRX backend-ops suite …
bong-water-water-bong commentedon Nov 6, 2026
Green at the tip, with the failures named rather than hidden. Fork fix 1bit-MONSTER/llama.cpp#221 merged (tip
ced49c61) and the engine pin bump #1260 carries it.Reproduction delta
At tip
710c7c23, two full runs: 4268/4592 passed both, the same 324 failing names. This issue's pincffae5c4was 4050/4374 with the same 324 — the suite has grown by 218 tests; the failing set did not change. So the suite was red for reasons that had nothing to do with a flaky run.The 324, attributed and addressed
selected_experts— an internal non-contiguous VIEW of the ARGSORT output. HRX read back garbage ints (1041053248, -1097908992, …) where the CPU has ids (1, 4, 5, 3, …). Comparing the routed weights (the op result) passes at every shape and every gating flag; real graphs consume the ids insideGET_ROWSon device, never as a host output. Implemented as an explicit per-backendWAIVER [HRX0] TOPK_MOE: selected_experts is an internal non-contiguous VIEW …line (printed once); other backends still compare ids and every backend compares weights.qwen3_moe_router_top8_f32rejectsexperts_per_lane0outside [1,16] but computesexpert_count / wave_size(64), so 32 gives 0. The matcher now refuses <64 experts and any count that is not a multiple of the 64-lane wave — which also caught the 288-expert shape (288 = 4x64+32) whose fused weights were off by 1e-6. Real MoEs are 128/256; small test graphs run the standalone dispatches and pass.identity=0).hip.hc_postis a private HIP add-on dispatch, not in this fork;identity=1passes both shapes. ExplicitWAIVER [HRX0] DSV4_HC_POST …line naming the add-on.After
Run 1: 4592/4592
Backend HRX0: OK. Run 3: 4592/4592 OK. Run 2 was 4591/4592 with one intermittentMUL_MAT(f32, m=1, n=1, k=2048, per=[0,1,2,3], o=1)(ERR 1.19e-2) that passes 5/5 when filtered — reported as a separate flake, not waived.Waivers are the only skips, each printed with its reason — there is no silent failure left in the suite.
Follow-ups (not blocking this closure)
hip.hc_post'sidentity=0path belongs to the private gpu-kernels add-on, which is where its fix lands; the waiver there names it until then.MUL_MATcase above is a separate flake and should be triaged on its own if it recurs.Closing on the suite being green at the pin with only named waivers.
bong-water-water-bong commentedon Nov 6, 2026
Reopening. #1260 closed this with waivers, but two accounts of the TOPK_MOE failures now disagree, and the waiver should not stand until they are reconciled.
small_rows_f32.loom,ggml_softmax_rows_f32andggml_argsort_rows_f32:common.softmax_rows_f32disabled, 160 cases still fail;common.argsort_rows_f32disabled, 40 still fail;If the argsort kernel can leave stale ids, then "real graphs consume the ids on device" does not make the waiver safe. A model would read the same stale ids. What's needed before this closes:
common.argsort_rows_f32disabled. If the fused weights are still right while the ids view is wrong, the harness account holds. If anything else moves, it's a kernel bug.match_argsort_rows: it acceptsne[0] <= 1024while the kernel assumes< 1024. This is the same class as the router fix in third_party: bump llama.cpp to ced49c61 — the HRX backend-ops suite is green, with named waivers (closes #1259) #1260.hip.hc_postis a private add-on. But theidentity=0defect is real and needs tracking in the gpu-kernels repo, not only a waiver line.Kept from #1260, which is good work: the router matcher now refuses expert counts it cannot handle, and the MUL_MAT intermittent is reported as a flake rather than waived.
bong-water-water-bong commentedon Nov 6, 2026
ask 1, run exactly as written: the harness account holds — and my own small-rows account from 14:23Z was measured through a broken switch and is withdrawn
Pin: fork tip
ced49c61(#221 merged). Binary: that tip + the one-line build fix from llama.cpp PR #220 (still unmerged; aclang host build of the tip fails on
llama-context.cpp:160) + a measurement-only waiver switch (GGML_HRX_TEST_NO_WAIVER=1)~/lb/aval/tattn-1259f-20261106T111456Z/.[VIEW]common.argsort_rows_f32disabledGET_ROWS10,SCALE101. The control answers the reopening question in #1260's favour
With the waiver off, the ids view fails in all 320 cases and no other node fails at all — the routed
weightsoutput iscorrect in every case, including the moved ones. The weights are gathered from those ids on device, so the ids in device
memory are right: the ids tensor is not stale, and the kernel's stores are not being predicated away. The host readback of
a non-contiguous view is what differs. That is #1260's account, reproduced independently, and it means:
is withdrawn. I am sorry for the noise; the correct fix direction is third_party: bump llama.cpp to ced49c61 — the HRX backend-ops suite is green, with named waivers (closes #1259) #1260's optional follow-up (materialise such view
outputs as host-readable), not the kernels.
2. Why my earlier 320 → 160 → 40 → 0 series was not measuring kernels
GGML_HRX_DISABLE_DISPATCHdoes not make these nodes fall back to the CPU. Disablingcommon.argsort_rows_f32produces:because
device_supports_opclaimsGGML_OP_ARGSORT(andSOFT_MAX,SUM_ROWS) unconditionally(
ggml-hrx.cpp:899,:996) while the matcher is now gone, so the node is claimed with no dispatch and the graph fails.The 40
[VIEW]+ 10GET_ROWS+ 10SCALEin the second arm are that failure, not a kernel difference; and in my earlier"all three disabled" arm the drop to 0 came with the totals collapsing 4599 → 4225, i.e. cases being skipped/erroring. That is
the same trap #161 documented for TOP_K (a claim must follow the matcher), and it is the second real bug this thread has
found: the debug switch for the small-rows ops breaks the graph instead of declining the node.
3. What I will fix (smallest correct, per instruction)
common.small_rowsmatcher accepts the kernel's own upper bound exactly:match_argsort_rowsacceptsne[0] <= 1024while the kernel assumesrange(%column_count, 1, 1024)andrange(%rank, 0, 1023);match_softmax_rowsandmatch_sum_rowsaccept<= 4096while both kernels assume< 4096. Same class as the router bound in third_party: bump llama.cpp to ced49c61 — the HRX backend-ops suite is green, with named waivers (closes #1259) #1260, same fix shape: the matcher must not accept what the kernel refuses.*_tensor_supported()consulted fromdevice_supports_op), so a disabled or out-of-range dispatch declines to the CPU instead of failing the graph. This iswhat makes the debug switch usable and prevents the next
unsupported HRX nodefrom a shape the matcher did not take.I am not touching the predicated stores: on the evidence in §1 they are correct.
Both go to pi-prs as one branch off
ced49c61. ask 2 (the<=1024bound) is therefore not a kernel bug either — it is amatcher bound that would produce exactly the "unsupported HRX node" failure at
ne[0] == 1024, which is worth fixing for thesame reason as the router bound.
bong-water-water-bong commentedon Nov 6, 2026
ask 1 + ask 2 delivered: fix is PR llama.cpp #222 (
2aba72166, base1bit/hrx-vulkan-patched, pi-prs pinged) — and the readback's wrong buffer is now identified preciselyFollow-up to my correction comment. Two things came out of doing the work.
A. The fix, and what it is (not) about
PR #222 does exactly the two things the reopen asked for, both in
dispatch-small-rows.cpp(+ one claim site inggml-hrx.cpp):index.assumeinsmall_rows_f32.loom(Loom ranges excludetheir upper bound): softmax and sum_rows
<= 4096→< 4096, argsort<= 1024→< 1024, norm<= 65536→< 65536, andget_rows_small's four<= 65536bounds plus anid_stridebound it did not have at all while itskernel assumes
< 1048576.device_supports_opasks a newsmall_rows_argsort_tensor_supported(), the same bounded-claim pattern TOP_K uses in the same function. This is whatmakes the debug switch usable and repairs every ARGSORT with more than 1024 columns, which was claimed, declined and
failing the graph.
Verified on pin
ced49c61, full suite, waiver off, one claim (~/lb/aval/tattn-1259g-20261106T112307Z/):[VIEW]320, every other node OK[VIEW]320, every other node OK — no regressioncommon.argsort_rows_f32disabled[VIEW]0, 0 mismatch dumpsThe totals move by 4 because four ARGSORT cases at
ne[0] >= 1024are now honestly declined instead of claimed. The thirdrow is the corrected version of the arm I ran this morning: with the ids produced on the CPU the suite passes — where the
same arm used to die on
unsupported HRX node ARGSORTand merely look like 40 failures.The kernels are untouched. On this evidence they are not the defect.
B. Where the ids really come from — the wrong buffer, measured
With the waiver off and the control arm's mismatch dump, the ids the harness receives are:
Read those HRX ints as float bit patterns:
1.05864e+09is0.202668f's bits,-1.08384e+09is-1.0f's. In the samerun, the weights comparison on the identical tensor pair prints
hrx: 0.202668 0.189718 0.185027 ...againstcpu: 0.202668 0.189718 0.185027 ...— the same values, differing only at the last bits, i.e. inside the harness'nmsetolerance and therefore not a failure. So:
probsvalues,
0.202668f→1.045e9) instead of the ids.That is the defect to fix to remove #221's waiver, and it is in the view readback path (
buffer_get/tensor_offsetfor analiased, non-contiguous view), not in
small_rows_f32.loomand not in the harness's comparison (which is doing whatggml's readback contract allows:
ggml_backend_tensor_getis a contiguous copy ofnbytesfromdata, so a strided view mustbe materialised somewhere — CUDA has the same limitation, which is why the waiver is a fair call, but HRX should still not
hand back another tensor's bytes).
C. Housekeeping
#222 ready for review at 2aba72166.arch_name()build fix) — the pin does not build with aclang host compiler without it, which is worth merging before anyone else tries to build
1bit/hrx-vulkan-patched.hip.hc_post,identity=0): agreed it belongs to the private add-on; I have not touched it.bong-water-water-bong commentedon Nov 6, 2026
Follow-up closed properly in the add-on: the DSV4_HC_POST waiver is gone and the suite is green on real kernels.
Root cause (measured, now fixed):
hrx_hc_post_combindexed the comb asdst*hc + src, but ggml stores[hc,hc,n_tokens]asdst + hc*src(the CPU reference andhrx_hc_comb's writer agree), soidentity=0multiplied the residual by the transposed comb;identity=1uses the no-comb kernel and was unaffected.fd69590c): one hunk inaddons/hip-hrx/kernels/hrx_hyperconn.hip.32559003): the DSV4 waiver block is removed (the fork was a step behind on this - docs: link geramyL's GitHub #221 had already merged before the removal commit, so it landed as a follow-up on the patch branch).identity=0shapes went from ERR 0.077612553 / 0.217479424 / 0.174023630 / 0.173662859 to OK (6/6 filtered), identity=1 unchanged, and the full suite is 4592/4592, Backend HRX0 OK. Only the TOPK_MOE internal non-contiguous-view waiver remains.ced49c61 -> 32559003, gpu-kernels ->fd69590c) with the registry regenerated; merge loop running.So the suite is now green with one waiver, and that one is a fork-side view-materialisation question (the ids are consumed on device; the mismatch is readback of an internal non-contiguous view), not an add-on op bug.
third_party: bump llama.cpp to 32559003 and gpu-kernels to fd69590c -…
bong-water-water-bong commentedon Nov 6, 2026
Pin
32559003/ engine main7153bedd(via #1268): do not remove the TOPK_MOE waiver yet, and do not close this on it. Full HRX0 suite, three arms, one claim (~/lb/aval/tattn-1259h-20261106T144411Z/)[VIEW]DSV4_HC_POSTGGML_HRX_TEST_NO_WAIVER=1, measurement-only switch)1. The TOPK_MOE waiver is still doing work
With the waiver switched off, all 320 ids comparisons fail, exactly as at
ced49c61— #220 and #222 did not touch the viewreadback (nor were they meant to; #222 is matcher bounds plus declining ARGSORT when no dispatch covers it). The two shipped
runs are green on that op only because the waiver compares the weights and skips the ids. Removing it now would put 320
failures back into the suite, which is the opposite of what #1260/#1268 set out to achieve.
What would make it removable is the follow-up I identified earlier and can now state with the data in the same run: the host
readback of the non-contiguous VIEW hands back the argsort input's bytes (
0.202668f→1.045e9as int32) while therouted weights, gathered on device from the same ids, match the CPU. So the fix belongs in the view readback
(
buffer_get/tensor_offsetfor an aliased non-contiguous view) or in materialising such outputs — not in the kernels, andnot in the harness comparison.
2. The DSV4_HC_POST waiver drop does not survive on this box — and the reason is the add-on pin, not the fork
The fork removed that waiver in
2cf6612c(in32559003), and the fix is gpu-kernels#60 (hrx_hc_post_combcomb layout) atgpu-kernels
fd69590cper #1268. My build links the HIP add-on fromGGML_HRX_HIP_ADDON_DIR, which on this host is~/wt/attn-port/gpu-kernels/addons/hip-hrxat0dbe94861— i.e. before #60 — so the fouridentity=0cases still failhere:
n_embd=1, n_tokens=1, identity=0n_embd=31, n_tokens=17, identity=0n_embd=128, n_tokens=257, identity=0n_embd=4096, n_tokens=21, identity=0So the suite is not clean at this pin on a box whose add-on is older than the pin. That is worth fixing in the box state
(or in whatever installs the add-on), not by re-adding the waiver: the waiver removal and the add-on pin have to travel
together, and on this host only the llama.cpp half arrived. I am rebuilding against a private checkout of gpu-kernels
fd69590cto confirm that the four cases go green with it — that will settle whether #1268's pair is complete in itself.3. Recommendation
add-on pin distribution (§2).
green at the pin" is currently true only with the add-on pin installed.
bong-water-water-bong commentedon Nov 6, 2026
Add-on half verified: with gpu-kernels
fd69590cinstalled the pin is 4595/4595 twice, rc=0 — and the TOPK_MOE waiver is still the thing holding it greenSupplements my comment above. I rebuilt the same pin (
32559003, engine main7153bedd) against a private checkout of thepinned add-on (gpu-kernels
fd69590c,~/wt/gk-1259/addons/hip-hrx) instead of this host's older~/wt/attn-port/gpu-kernels(0dbe94861), and re-ran the full HRX0 suite(
~/lb/aval/tattn-1259i-20261106T145445Z/,~/lb/aval/tattn-1259j-20261106T145954Z/):fd69590c)[VIEW]DSV4_HC_POSTWhat this settles
hrx_hc_post_comb(gpu-kernels#60) does fix all fouridentity=0cases — they gofrom
ERR 0.078-0.217to passing, with the fork-side waiver already gone. My earlier 4 failures were only this hostrunning the add-on from
0dbe94861(pre-1bit OS: the boot fixes the VM test found (after #56) #60). The lesson for the fleet is narrower than it looked: the add-on pin and thellama.cpp pin have to move together on every host, which is a distribution fact, not a code defect.
pin + add-on the 320 ids comparisons fail, unchanged. The two green runs are green because the waiver compares the routed
weights and skips the ids. Removing it would put 320 failures back into a suite that is otherwise fully green.
bytes —
0.202668f→1.045e9as int32 — while the weights gathered from the same ids match), plus the box-state itemabove. Neither is a kernel bug and neither is the harness' fault.
So: no waiver-removal PR from me, and I am not closing this
The instruction was conditional on the suite being clean without the TOPK_MOE waiver; it is clean only with it. When
the readback is fixed (materialise non-contiguous view outputs as host-readable, or fix
buffer_get/tensor_offsetfor analiased view), removing the waiver is a two-line change and this issue closes with
4595/4595twice. I would rather hand overthat measurement than a green suite that depends on a skip being counted as green.
bong-water-water-bong commentedon Nov 6, 2026
Status for the "fix all issues" sweep: pin is green, but the waiver cannot be dropped without a readback fix
Measured today at the pinned pair (llama.cpp
32559003+ gpu-kernelsfd69590c, engine main7153bedd), full HRX0 suite:[VIEW]DSV4_HC_POSTSo the pair in #1268 is complete and the suite is green at the pin — provided the add-on pin is installed (on this host the
default add-on checkout was
0dbe94861, pre-#60, which is what produced the fourDSV4_HC_POSTfailures in my first run;that was distribution, not code, and it is now verified both ways).
But the TOPK_MOE waiver is still load-bearing: with it off, all 320 ids comparisons fail, and neither #220 nor #222 touches
that path. The ids the harness receives are the argsort input's float bytes (
0.202668f→1.045e9read as int32) whilethe routed weights, gathered on device from the same ids, match. Dropping the waiver today would put 320 failures back into a
suite that is otherwise fully green, so I have not opened that PR and I am not closing this as fixed.
What closes it: the value readback for a node that a later dispatch in the same graph reuses — i.e. the ids written into an
arena slot that a subsequent dispatch overwrites, or covered by a fused match (the mechanism engine #1078 built for
zha-readback.). One scoped fix there + the two-line waiver removal + the same two runs =4595/4595twice with the idsgenuinely compared, and this closes with the counts.
I have filed the question with my orchestrator (
OWNER:in my state file) because the alternative — dropping the waiver whilethe readback is unfixed — is a knowingly red suite, which is an owner-level call, not a lane call.
bong-water-water-bong commentedon Nov 7, 2026
Owner decision recorded: hold this OPEN, no waiver. The ids readback is the same class as #1251 option (b), and #1251's lane is building the fix
Decision from my orchestrator (owner, 2026-11-06): do not drop the waiver and do not close on the current green. The 320
TOPK_MOE failures are the intermediate-value readback — HRX handing back a materialised host copy of a value after a
dispatch writes the device copy, i.e. the same class as #1251 option (b) ("HRX copies a materialised host output back after
a dispatch that writes it"). pi-hrx-compat is building that, since it is the same mechanism their SET_ROWS assert needs.
That matches what I measured here: the ids the host receives are the argsort input's float bytes (
0.202668f→1.045e9as int32), while the routed weights — gathered on device from those same ids — match the CPU. The device has the right ids; the
host readback does not get them.
What closes this issue, precisely
32559003) with gpu-kernelsfd69590c— twice, andwith the TOPK_MOE waiver deleted, not switched off (so the ids are compared the way every other backend compares them).
own PR, so the merge that closes this is a reviewed change rather than a hand-close.
Until then: nothing to merge from me on this issue, and the suite stays green at the pin only because the waiver is honest about
what it skips. I have not touched the waiver in the tree.
(For the record on my side: the
engine-315/host-visible-coherencebranch that exists in the fork is a different class —coherence for buffers allocated host-visible, where the generic read short-circuits before any backend copy runs. The TOPK_MOE
ids live in a device buffer and go through
buffer_get/ordered_download, so I am waiting for the option-(b) change rather thanassuming #315 covers it.)
bong-water-water-bong commentedon Nov 7, 2026
Closed — the ids readback is fixed and the waiver is gone: 4601/4601, rc=0, twice, with 0 waivers
Fix: llama.cpp #228 (
65b26092a, base1bit/hrx-vulkan-patched, pi-prs pinged). Measured at pin765fa1c4cwith theadd-on pin
fd69590c, fulltest-backend-ops test -b HRX0, two runs per arm:rc=1,[VIEW]320, twicerc=0,[VIEW]0,hc_post0, 0 waivers, twiceWhat it actually was
Not ordering, and not the kernels — both were tested and refuted on the way (
GGML_HRX_DIAGNOSTIC_GRAPH_SYNCand draining thecompute streams before the copy change nothing; the ids on device are right, since the weights gathered from them match).
Instrumenting the readback made it plain:
sizeis the strided span (8*4 + 21*496), so the copy includes 116 non-id ints per row, and those bytes had already beenreused by a later dispatch — which is why the host saw the ARGSORT input's float bit patterns. The value was never declared
as one a caller will read back.
The fix
HRX already had the concept for one caller (
zha-readback.+GGML_TENSOR_FLAG_OUTPUT, engine #1078). #228 generalises it:hrx-readback.as a caller-neutral prefix, the suite declares every value it compares (generically, for all tests and ops),and the
test_topk_moe::waive_nodeoverride is deleted. No kernel changes, no add-on change, and graphs that declare noreadback value are planned exactly as before.
For the record
-o TOPK_MOEalone the same cases failed 320/320.DSV4_HC_POSTfailures were a stale add-on checkout,0dbe94861vsfd69590c, not code).the evidence is the two runs above.
bong-water-water-bong commentedon Nov 7, 2026
Closed with the 0-failure/no-waiver condition met: llama.cpp #228, 4601/4601 rc=0 twice, 0, 0 waivers. Evidence in the comment above.
bong-water-water-bong commentedon Nov 7, 2026
One red row at the newer pin (
a3e8d42c9) — ids-only, tiny ERR, reproducible in a full run and clean in isolationNot reopening anything; this is the state of the suite at the pin the fleet is on now, for whoever gates on it.
Pin
a3e8d42c9(add-on pinned gpu-kernels17a88264), fulltest-backend-ops test -b HRX0with no waiver in the tree(#228 is in the pin): 4574/4575 passed,
rc=1,[VIEW]1,hc_post0, waivers 0 — identical in two runs(
~/lb/aval/tattn-suites-a3e-*).The row:
What it is and is not:
[VIEW]is theselected_expertstensor; the routedweightsnode passes, so the gatheredvalues agree and the disagreement is which expert was chosen among equal ones.
pattern of the readback defect this issue was about.
gating_func=3means the probabilities are the logits.moe_routerandargsort_rowsboth fire), so it is the process-state class, not the shape or the kernel's arithmetic.So the question for the gate is a small one: is a tie-order difference between the fused router's selection and the CPU
argsort acceptable at
ne=[256,22], 6 routes(equal weights, different index), or should it be tracked as its own defect?Either way it is one row,
[VIEW]-only, and it is not a readback failure.bong-water-water-bong commentedon Nov 7, 2026
The one red row is a tie-order comparison the CPU reference itself cannot define — measured, with the producer isolated
Follow-up to my previous comment. Three full-suite arms at pin
a3e8d42c9(add-on17a88264), one claim,~/lb/aval/tattn-redrow-20261107T1103*:[VIEW]0GGML_HRX_DISABLE_DISPATCH=llm.moe_router.top8_f32[VIEW]1GGML_HRX_DISABLE_DISPATCH=common.argsort_rows_f32[VIEW]0 (ARGSORT declines, cases skipped)Two facts come out of that plus the earlier runs:
(the control arm above is one of the greens: 4575/4575). Each run draws new random logits, so the trigger is an exact tie
inside the row —
gating_func=3means the probabilities are the logits, and with 256 experts in[-1,1]an f32collision happens in a few percent of runs. In a tie group both choices are equally correct and the weights are equal,
which is why the
weightsnode passes and only the ids differ, with a tinyERR(2.5e-4).ggml_compute_forward_argsort_f32(ggml-cpu/ops.cpp:8363-8385) fillsthe index array and calls
std::sortwithcmp_argsort, which compares values only —std::sortis not stable, sofor exactly equal values the reference's index order is implementation-defined. With the fused router disabled the row
fails, so at least one HRX producer's tie order differs from libstdc++'s introsort on that input; with it enabled it happened
to agree in that run.
So the question is not "which kernel is wrong"
No backend can be required to reproduce an unstable sort's tie order, so an exact id-set comparison cannot be satisfied in
general. The honest options are:
the comparison is meaningful), or
test_topk_moeby comparing the selected values when the boundary value is repeated.Either is a test-side change; neither touches a kernel, and I would not change a kernel for this. I have not made the change
unilaterally because it weakens or alters what the suite asserts, which is your (and the owner's) call — but the current state
is that roughly half of full-suite runs at this pin will show one
[VIEW]row for this reason, so a gate that treats asingle
[VIEW]failure as red will be flaky until one of (i)/(ii) lands.Everything else at this pin is green with no waiver: 4574/4575 in the worst run,
hc_post0, and the 320-case TOPK_MOEreadback failure of this issue is gone (llama.cpp #228).