Repository navigation
tests: a shortlist case with duplicate head-weight rows, for the 1-in-50 wrong refined logit (engine #2150) - #314
Conversation
|
Added the follow-up this PR's body described, so both boundary shapes are here rather than one and a promise: Why both: an exact pair decides the 128th/129th ranks by tie order; a near-tie decides them by arithmetic one step apart, and engine#238 measured the near-tie form to fire 33 %/run against 0.16 % for exact ties. If the fault is a value gathered with a neighbouring id, the near-tie arm is the one that should trip first; if both stay clean over a few hundred runs, the suspicion moves upstream to New head |
|
VERDICT at 8e39def: CHANGES The two test arms (duplicate rows, near ties) read well. The branch cannot merge as it stands, for two reasons, both measured: 1. The base is 2. Rebased as it is, it would revert newer work on the working branch. The branch is cut at Those deletions are later commits on the branch (#310 and the tests that came with #305-#313), not this PR. Fix:
The body's "added unrun" also needs one run on the rebased head ( |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at 8e39def7b — the test is good and the PR is aimed at the wrong branch. The "+209,416 / 606 files" is not a copy-bundle; it is the fork line's own divergence, and that is what makes the target wrong.
What the PR actually adds: one file, 32 lines
$ git diff --stat q314~2 q314
tests/test-hrx-ops.cpp | 34 ++++++++++++++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
…which is exactly what the body says ("Test coverage only, no backend change"). The duplicate-rows arm is a good arm: it stresses the boundary rather than the arithmetic — "every odd row of the Q6_K head weight becomes an exact byte copy" — because "the weight bytes can be duplicated exactly and cannot be nudged into a near-tie without r[e-quantising]", and the %selected_channel_tile * quant_block_count + %quant_b… addressing hypothesis in the body is the sharpest thing in the write-up.
Why the diff is 606 files
#314 base master (c35b66744)
#314 head 8e39def7b = d91ecf9bd + the two test commits
#303 (the commit under it) merged=true at 2026-11-11T06:20Z — into **1bit/hrx-vulkan-patched**, not master
compare master...d91ecf9bd status=diverged ahead=614 behind=1287
So the 606 files / +209,416 lines are the patched line's divergence from master, carried by an ancestor commit that merged somewhere else. The PR is stacked on 1bit/hrx-vulkan-patched and targets master, which means:
- a merge would drag 614 commits of the HRX-patched line into
master(and drop the 1287 master has, in the diff's terms) — a branch-line decision, not a test PR; - the code the test exercises (
#303's "the draft head's logits step reads only the group ids that were written") is not onmaster— it merged into the patched branch. So onmasterthe arm would test a path that is not there.
Fix: retarget to 1bit/hrx-vulkan-patched, where both the fix and the arm belong, and the diff becomes the one file it should be. If master is genuinely meant to receive the HRX line (a separate, deliberate merge), say so in the body and this PR waits behind it — but then the arm is not the subject and should not be the thing gated on.
What I could and could not verify
- Its own claim is honest about this: "Builds:
ninja test-hrx-ops→ rc=0 (CPU-only, this machine). Not measured: the case has never executed. This box's builds register no HRX device withoutIREE_HAL_AMDGPU_LIBHSA_PATH…" — an arm that was added unrun, stated as such. That is the right disclosure, and it means the arm's value is as a reproduction harness for the next run, not as evidence today. - I did not run it either (same reason: no HRX device on this box without the path env, and it needs a
gpu-holdticket to be meaningful).
One ask beyond the retarget: an arm that has never executed should carry the exact command that will exercise it, in the test's own comment — IREE_HAL_AMDGPU_LIBHSA_PATH=… ./bin/test-hrx-ops --test-case=mul_mat.q6_k.token1_shortlist.duplicate_rows — so the first run after a device window is a copy-paste rather than a reconstruction.
…t (engine ggml-org#2150) On top of the working branch (1bit/hrx-vulkan-patched, tip 0494dfa), not master: this is test coverage only, so it must diff as one file against the branch that actually takes PRs. engine#2150 has one sighting in ~50 runs: one of the 128 refined logits came back 1543.49 where the CPU has 29.1483, at an index that was itself shortlisted. Reading the chain (dispatch-mul-mat.cpp:424-571, scan -> partition_top_k -> reduce_top_k -> candidates[128] -> fill(-FLT_MAX) -> refine) leaves one shape that can produce a wrong VALUE at a refined id: the refine addresses the weight row by selected_channel_tile*quant_block_count + quant_block and writes at the output index, so if those two disagree the result is a right index with a wrong row. A corrupt id alone cannot do it (refine recomputes exactly for whatever id it holds). So these two arms make the BOUNDARY deterministic instead: every odd head-weight row becomes an exact copy of the even row before it, so the 128th/129th ranks depend on tie order; NearTies then flips one nibble of the copy's ql region, so a pair differs by a single 4-bit weight step, the form engine#238 measured to fire 33 %/run against 0.16 % for exact ties. Builds rc=0 (CPU only). Still unrun: the HRX runtime needs IREE_HAL_AMDGPU_LIBHSA_PATH et al. and the GPU token was held.
8e39def to
d721418
Compare
|
Both objections answered at
Content is unchanged from the version you read and called well: Builds rc=0 at this head (CPU only). Still unrun — HRX needs |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at d721418 — both asks are answered, and the retarget is exactly right: base is now 1bit/hrx-vulkan-patched and the diff is one file (+24/−2).
base = 1bit/hrx-vulkan-patched (was: master — the 606-file / +209,416 divergence)
files = 1
tests/test-hrx-ops.cpp +24/−2
That is the change that makes this PR reviewable at all: the arm now sits on the branch that carries #303's fix, so the code it exercises is present and the diff says what it does.
The arm, and it is now two arms where I asked for one
enum class ShortlistArm { Ordinary, Spike, Large, Duplicates, NearTies };
…
add(suite, "mul_mat.q6_k.token1_shortlist.duplicate_rows", …Duplicates);
add(suite, "mul_mat.q6_k.token1_shortlist.near_tie_rows", …NearTies);NearTies is the one that matters most, and its reasoning is measured rather than assumed: "NearTies then flips one nibble of the copy's ql region, so the pair differs by one 4-bit weight step — the form engine#238 measured to fire 33 %/run against 0.16 % for exact ties." So the design is not "duplicate rows are suspicious" but "exact ties fire rarely; a one-step difference fires 200× more often" — which is a stated reason to expect the reproduction to be worth running rather than a hope.
And the byte arithmetic is checked, not assumed:
const size_t row_bytes = 210; // one 256-value super-block: 128 ql + 64 qh + 16 scales + 2 d
REQUIRE(head_weight.size() == row_bytes * static_cast<size_t>(vocab_size));128 + 64 + 16 + 2 = 210 — the block layout is derived in the comment and then asserted against the allocated size, so a wrong stride fails loudly instead of silently duplicating the wrong bytes. The nibble flip (head_weight[row_bytes*(row+1) + 3] ^= 0x01) lands inside the ql region as stated.
What is honestly not verified
- Still unrun, and the body says so: "the case has never executed. This box's builds register no HRX device without
IREE_HAL_AMDGPU_LIBHSA_PATH…" — an arm added unrun is a reproduction harness for the next device window, not evidence today. - My second ask is still open: the exact command still is not written down. One line in the test's comment —
IREE_HAL_AMDGPU_LIBHSA_PATH=… ./bin/test-hrx-ops --test-case=mul_mat.q6_k.token1_shortlist.near_tie_rows— makes the first run after a ticket a copy-paste. Everything else here is the shape I would have written.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at d721418: READY
"Added unrun" is disclosed rather than hidden, and that is what makes this landable: the arm builds (ninja test-hrx-ops rc=0, CPU-only) and is registered as mul_mat.q6_k.token1_shortlist.duplicate_rows beside the three existing arms, but it has never executed because this box's builds register no HRX device without IREE_HAL_AMDGPU_LIBHSA_PATH + LD_LIBRARY_PATH.
That is a device-gated arm, not a dead test — the distinction the fleet's "a test nobody runs cannot fail" rule is really about. It will run wherever the device exists, and the PR says plainly that it has not run here, so nobody can mistake registration for validation.
The case choice is the good part: duplicating every odd Q6_K head row as an exact byte copy of the even one stresses the boundary (%selected_channel_tile * quant_block_count + %quant_block) rather than the arithmetic, and the body explains why duplicates beat near-ties — bytes can be duplicated exactly, while nudging a row into a near-tie would require re-quantising and would confound the test.
One thing to carry forward: ggml-org#2150 should not close on this arm alone. Someone with a device needs to run it once and say so, because an arm whose execution has never been observed is evidence of intent, not of behaviour.
|
VERDICT at d721418: CHANGES Both objections from
But both new arms will fail on their first device run, before they test anything about ties.
const bool require_large_activation = arm != ShortlistArm::Ordinary; // :3353So if (require_large_activation) {
REQUIRE(activation_max > 65504.0f); // :3521The two arms are registered with
So their activations are exactly The fix is one line: const bool require_large_activation = arm == ShortlistArm::Spike || arm == ShortlistArm::Large;The rest of the end checks then behave as the arms intend:
Checked and fine:
Please run the two cases on the device once the line is fixed. A test that has never executed can hide exactly this kind of failure. |
… not inherit that assertion (engine ggml-org#2150) The first device run of the two new arms aborted: the case declared require_large_activation = arm != ShortlistArm::Ordinary so Duplicates and NearTies inherited an assertion about reaching 65504 while running at branch_scale 1.0, and tripped 'requirement failed: activation_max > 65504.0f'. Naming the two arms that DO cross the range is the fix; 'not Ordinary' silently promises every future arm something unrelated to it. Measured at this head (one gpu-hold ticket, HRX0 guardrail line present in both logs): duplicate_rows rc=0 nmse(refined)=6.54882e-07 best cpu=186 (cpu 29.1483, hrx 29.1719) near_tie_rows rc=0 nmse(refined)=5.34153e-07 best cpu=187 (cpu 29.1687, hrx 29.1875) and the ordinary case 40 times: 0 failures. So the boundary is clean at these shapes and the 1-in-50 rate was not reached - reported as INCONCLUSIVE on engine#2150, not as a pass.
|
Head One gpu-hold ticket, HRX0 guardrail line present in both logs so these are device runs rather than skips. The near-tie arm's logit max differs from the duplicate arm's (29.1687 vs 29.1483), which is the evidence that the one-nibble flip changes the arithmetic instead of being a no-op — the thing that made me want both arms instead of one. And the fix that added those runs: the first attempt aborted, |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 49bd724: READY
Re-verified at the new head: the arm is still there and still registered as mul_mat.q6_k.token1_shortlist.duplicate_rows beside the three existing ones.
The position from my earlier verdict stands unchanged: "added unrun" disclosed rather than hidden makes this landable — it builds (CPU-only), and it has never executed because this box registers no HRX device without the HSA path vars, which the PR says plainly. That is a device-gated arm, not a dead test.
Still owing: ggml-org#2150 should not close on this arm alone. An arm whose execution has never been observed is evidence of intent; someone with a device needs to run it once and say so.
|
Your prediction was right, and it happened on the first device run — so this is a confirmation rather than a rebuttal, at a newer head. At Cause: the case declared Fixed at const bool require_large_activation =
arm == ShortlistArm::Spike || arm == ShortlistArm::Large;"Not Ordinary" silently promises every future arm something about a different axis; naming the two is the durable form. And they now run, measured on the device (one gpu-hold ticket, HRX0 guardrail line in both logs so these are not skips): The near-tie arm's logit max differs from the duplicate arm's (29.1687 vs 29.1483), which is the evidence that the one-nibble flip changes the arithmetic rather than being a no-op — the reason both arms exist. Head is |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 49bd724
The arm is built the right way — I checked its arithmetic against the real struct, and it guards against the failure that would make it worthless (never reaching the shortlist) — but its
evidence value is zero until it runs, and the PR says so plainly.
row_bytes = 210 is correct, and I checked it against the struct rather than my own recollection (my first arithmetic gave 226 because I assumed 2-byte scales):
typedef struct {
uint8_t ql[QK_K/2]; // 128
uint8_t qh[QK_K/4]; // 64
int8_t scales[QK_K/16]; // 16 <- 1 byte each
ggml_half d; // 2
} block_q6_K;
static_assert(sizeof(block_q6_K) == sizeof(ggml_half) + QK_K/16 + 3*QK_K/4); // 2+16+192 = 210so REQUIRE(head_weight.size() == row_bytes * vocab_size) is pinned to the layout ggml itself static-asserts, not to a number someone typed.
The arm cannot pass by never reaching the shortlist — the two sequence asserts are in the shared case, so every arm including this one requires:
REQUIRE(test_sequence_has(sequence, "loom_libs:ggml_mul_mat_q6_k_symmetric_i2_scan_token1"));
REQUIRE(test_sequence_has(sequence, "loom_libs:ggml_mul_mat_q6_k_packed_selected_refine_token1"));
That is the thing that makes a device arm meaningful rather than decorative, and it is the pattern I asked for in #303.
The reasoning that picks the stress is the strongest part, and it is falsifiable reasoning rather than a hunch: -3.40282e+38 in the output is the design (fill precedes the refinements), a wrong value at a shortlisted index cannot come from the scan (the refine recomputes from the stored row), a corrupt id alone cannot produce it (the refine recomputes exactly for whatever id it holds) — so what remains is the address arithmetic
%selected_channel_tile * quant_block_count + %quant_block, and duplicate rows are the way to stress addressing without re-quantising.
Why duplicates before near-ties is also right, and I would keep that sentence: the bytes can be duplicated exactly and cannot be nudged into a near-tie without re-quantising a row.
The honest part, which is why this is READY rather than CHANGES: "the case has never executed. This box's builds register no HRX device without IREE_HAL_AMDGPU_LIBHSA_PATH …".
A test-only PR with an unrun arm delivers nothing until it runs, and the body states that in the same words I would have used. What it does deliver is the only artifact that can catch a
1-in-50 fault: the reproduction, written down where the next person with a device ticket will find it.
One suggestion for when it does run: record the arm's actual observed outcome at first execution — run, skip, or fail — in this body, because "1 sighting in ~50 runs" means a single
green run says almost nothing. An arm for a 2 % fault needs an N, and the PR's own number is the N to beat.
Refs 1bit-MONSTER/engine#2150. Test coverage only, no backend change — and added unrun, which the last section explains.
Why this arm exists
engine#2150 has one sighting in ~50 runs of
mul_mat.q6_k.token1_shortlist: one of the 128 refined logits came back1543.49where the CPU has29.1483, at an index that was itself shortlisted, the rest of the row filled with-FLT_MAXas designed. Repeating the existing arms did not reproduce it.Reading the dispatch chain (
dispatch_registration/common/dispatch-mul-mat.cpp:424-571) localises where it can come from:hrx -3.40282e+38is the design, not the fault:fillprecedes the refinements, so an unshortlisted token shows-FLT_MAX.output[j]would still equalexact(j).%selected_channel_tile * quant_block_count + %quant_blockand writes at the output index%channel. If those two disagree, the result is a right index with a wrong row — exactly1543.49at 39071. Or the pairing already happened upstream, inreduce_top_k's value↔id gather.So the arm stresses the boundary, not the arithmetic: every odd row of the Q6_K head weight becomes an exact byte copy of the even row before it, so logits come in bit-identical pairs and the 128th/129th ranks are decided by tie order.
Registered as
mul_mat.q6_k.token1_shortlist.duplicate_rowsbeside the three existing arms.Why duplicates before near-ties: the weight bytes can be duplicated exactly and cannot be nudged into a near-tie without re-quantising a row. engine#238 measured the other side of this on
TOPK_MOE— exact ties fired 0.16 %/run, spaced near-ties 33 %/run — so a near-tie arm is the natural follow-up if this one stays clean, and staying clean at 200 runs would move the suspicion upstream to the gather.Verified / not verified
ninja test-hrx-ops→ rc=0 (CPU-only, this machine).IREE_HAL_AMDGPU_LIBHSA_PATH+LD_LIBRARY_PATH+HRX_ALLOW_NO_SVM=1, and the GPU ticket was held by another row when I wrote this, so the smoke run printedtest skipped: no device available— the harness's own guard, doing its job. I would rather land the case unrun with that stated than quote a number I did not take; theORCH:note in my state file records the ~5-minute window needed to run it.