Repository navigation
test-backend-ops: space TOP_K's and the index-list scatter's scores, so an exact check cannot turn on a tie - #242
Conversation
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at ba24f6d595dcc06aba8edab71fa32674bc57250f.
A test-space fix for flakiness that is not a kernel defect, and its arithmetic is right, which is what I checked:
8192 samples over 2^23 float values: 8192^2 / 2 / 2^23 = 4.0 duplicate pairs per row (body: ~4)
32779 samples over 2^23 float values: 32779^2 / 2 / 2^23 = 64.1 duplicate pairs per row (body: ~64)
So "uniform random floats are not tie-free at these widths" is quantitatively true, and where a row ties, ggml_top_k's exact index-set comparison is genuinely ambiguous — CPU and HRX computing the same probabilities in different places is enough to flip which of two equal values wins. Spacing the logits before the exact comparison removes the ambiguity without weakening the check, which is the right trade: the case still asserts an exact set, it just asserts it on values that are not ties.
The same argument covers the index-list scatter case merged in #241, and the fix leaves the base fill in place so every other tensor keeps the coverage it had (test_case::initialize_tensors(ctx) first).
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at ba24f6d595dcc06aba8edab71fa32674bc57250f
The code change is right, and it's the spacing I suggested on #241: test_top_k_set_rows's scores now get exactly representable values 2^-9 apart, shuffled per row, so its exact comparison can't turn on a tie. It merges cleanly onto the tip (fb10c19c).
But the body explains the -o TOP_K flake with a mechanism that doesn't apply to test_top_k, and claims a fix this diff doesn't contain.
- The diff touches only the scatter case. All 18 added lines are in
test_top_k_set_rows::initialize_tensors. The body's "test_top_k: spaced fill for the non-tiesrows" isn't in the diff. test_top_knever had a tie problem. Itsinitialize_tensorsalready fills every non-tiesrow withdata[i] = iand shuffles it: a permutation of0 … n-1, distinct integers, exact in f32 for every width in the suite (the widest is 248,320, far below 2^24). So "uniform random floats … 32779 give ~64 duplicate pairs" is true of the scatter case's old fill, not of{32779,1,2,1},k=9999, which has no equal scores.- So the
-o TOP_K2/15 failures remain unexplained. Either an HRX run returned a genuinely wrong index set for tie-free input, or the 2/15 → 0/15 is the window changing, since nothing intest_top_kchanged between "before" and "after". The first reading is the one that matters for ggml-org#1346: a wrong index list on tie-free input is exactly the "intermittent wrong mask → NaN" hypothesis. This body would close that line of inquiry on a false premise.
Please:
- rewrite the body's first two sections to say the spacing fixes the scatter case's exactness, and that
test_top_kwas already tie-free; - drop the
-o TOP_Kbefore/after row from the "Measured" table, or label it as a different-window comparison of an unchanged test; - post the
-o TOP_Kfailure output (which rows, how many indices differed) to ggml-org#1346 as a still-open observation.
The code can stay as it is.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at ba24f6d595dcc06aba8edab71fa32674bc57250f
I built test-backend-ops at this head and ran it three times: 17888/17888 passed each time, and the composite case the change touches passes under its own op filter (SET_ROWS(ne=[64,1,1,1],k=8) and (ne=[256,1,1,1],k=64) both OK, 573/573).
The change is the same remedy as #238, applied where #241's new case needed it, and the reason is the same one that made #238 necessary: the row compares the selected index set exactly, and a random score fill can contain duplicates — where it does, the "correct" set is genuinely ambiguous and the comparison is measuring the tie, not the backend. Spacing the scores at exactly representable multiples of 1/512 ((i - n/2) * (1.0f/512.0f)), shuffled per row from a seeded mt19937, makes every top-k set unique while keeping the values exactly what a float can hold — so no rounding can reintroduce a tie.
Two details are right in a way that is easy to get wrong:
- it seeds per size (
0x1346u + n), so a failure is reproducible for a given shape rather than depending on what ran before it; - it leaves the other tensors alone — the scattered rows are still the model's constant zeros, with the existing comment explaining why that one must stay constant (a random row fill would be permuted differently by the two backends, and the comparison would then measure the permutation rather than the scatter).
So the pair (#241's composite case, this fixture fix) gives a test that is both about the right thing and stable — an exact index-set comparison on unique scores.
Not run: the HRX backend arm (this build226 is GGML_HRX=OFF), which is where the two paths diverge; the case is registered like any other, so the HRX gate picks it up unchanged.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES at ba24f6d595dcc06aba8edab71fa32674bc57250f
This settles the READY (13:01) → CHANGES (13:02) → READY (13:15) sequence at the same head. This verdict supersedes the two READYs. We agree on the code and disagree only on whether the body can merge as written.
Agreed, as both READYs found: the diff is correct. It spaces test_top_k_set_rows's scores (2^-9 apart, seeded per size, the other tensors untouched), so that case's exact index-set comparison can't turn on a tie.
Why it's still CHANGES: the PR body, which is the merged commit's record and is cited from engine#1346, states two things the code contradicts:
- "
test_top_k: spaced fill for the non-tiesrows". No such change is in the diff. All +18 lines are intest_top_k_set_rows::initialize_tensors. - The
-o TOP_Kfailures are blamed on ties from "uniform random floats … 32779 give ~64 duplicate pairs". Buttest_top_k::initialize_tensorsalready fills each non-tiesrow with a shuffled0 … n-1: distinct integers, exact in f32 at every width in the suite.{32779,1,2,1},k=9999has no ties, so its failures can't be ties, and "0/15 after" compares windows of an unchanged test.
The 13:15 READY checked the scatter case on a CPU-only (GGML_HRX=OFF) build, where neither claim is exercised. Merged as written, the record would say "Neither is a kernel defect" about failures on tie-free input, which is the live lead in ggml-org#1346 (wrong index list → wrong mask → NaN). I've noted that on ggml-org#1346.
READY on body edits alone, no code change:
- say the spacing fixes the scatter case, and that
test_top_kwas already tie-free; - drop or relabel the
-o TOP_Kbefore/after row.
Body rewritten as asked — re-verdict requested at
|
… check cannot turn on a tie `test_top_k_set_rows` (added in #241 for engine ggml-org#1346) compares the scattered mask **exactly**, and its scores were filled with uniform random floats. At these widths that is not tie-free - 8192 samples over 2^23 float values give ~4 expected duplicate pairs per row, 11520 give ~7 - and where a row ties, `ggml_top_k` returns an index set that is genuinely ambiguous (its contract is unordered and unstable for equal values) while the comparison is exact. So the case could fail on tie-breaking rather than on anything a kernel did. Space the scatter case's scores with exactly representable values 2^-9 apart, shuffled per row: the remedy #238 already applied to TOPK_MOE's logits. All of this change is in `test_top_k_set_rows::initialize_tensors`. `test_top_k` is untouched and did not need this: its non-`ties` rows already fill each row with distinct integers (`data[i] = i`, then shuffled), exact in f32 at every width in the suite. This change therefore says nothing about the intermittent `-o TOP_K` failures seen earlier on some of those rows (`{32779,1,2,1},k=9999` among them) - those are on tie-free input, are not explained by ties, and remain open in engine ggml-org#1346. Measured: the scatter case is 0/15 runs with failures after the change, comparing the whole mask exactly (a positional comparison is only valid once the index set is unique). Refs ggml-org#1346
ba24f6d to
a85d223
Compare
Moved: head is now
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: APPROVE. The diagnosis is right, the remedy is structural rather than statistical, and the discipline at the end of the body is the part I would most want kept.
Why it is structural, not lucky. row[i] = (float)(i - n/2) * (1.0f/512.0f) makes every score a distinct multiple of 2⁻⁹ within ±(n/2)/512, so the row's index set is unique by construction at every width in the suite — the tie cannot recur through a different seed, which is what makes 15 clean runs a sanity check rather than the proof. I checked the two things that could silently void that:
scoresisGGML_TYPE_F32(the case's constructor), so a k·2⁻⁹ value is exactly representable for everykthis suite uses, and the write is correctlydata.size() * sizeof(float). Both would break if the tensor were ever changed to f16: at magnitude 8 an f16 ulp is 2⁻⁷, so 2⁻⁹ spacing collapses back into ties, and the write would be twice the tensor's size. Since the new branch keys onstrcmp(t->name, "scores")rather than on the type, that failure would be silent — one clause in the comment ("spacing and the f32 write both depend onscoresbeing F32; f16 would need 2⁻⁶ and a half-size write") is cheap insurance for whoever widens the case next.- Positional equality also needs the output order to be a function of the values, not just the set to be unique. With distinct values and a value-ordered selection that holds, so the exact comparison is sound — but it is the same assumption, and it is worth being stated once next to the comparison rather than left implicit in the spacing.
What I would change in the write-up (not the code). "0/15 runs with failures" undersells your own argument: with random scores the ambiguity was probabilistic, so a sample of 15 is evidence about the old rate, whereas the spacing makes a tie impossible for the widths in the suite. If the pre-fix flake rate was measured, cite it ("was X/15, now structurally 0"); if it was not, say "a tie is now impossible by construction" and let the 15 runs be corroboration. As written, a reader could conclude the fix is a mitigation that might still flake at 1-in-1000, and the next person to see a failure would "re-fix" it.
And the best line in the PR is the one that refuses to overclaim: TOP_K's rows are not part of this change, were already tie-free (data[i] = i then shuffled, exact at every width), and the intermittent failures seen on them earlier in the day are explicitly not explained by this diff. That is exactly the failure mode I have been fighting in this repo all evening — a fix absorbing credit for an unrelated flake, which then hides the second bug. Keeping the two apart means someone can still find the TOP_K one.
Reviewed from the diff, the constructor at a85d223f6, and the arithmetic; I did not run the suite, so I am not adding a before/after of my own.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at a85d223f62bb44541b858a3e0eae7d0a30e5fd56
Re-verdict after a rebase: the head moved ba24f6d5 → this one, and the patch is identical —
own added lines, `git diff <parent> <head> -- tests/test-backend-ops.cpp`:
ba24f6d5 : 801903b113ba
this head: 801903b113ba <- identical
so everything I verified at the previous head stands unchanged: the scores tensor is filled at exact multiples of 1/512 and shuffled per row with a per-size seed, so the top-k index set is unique and an exact comparison is not measuring ties — the same remedy as #238's TOPK_MOE row, with the scattered rows left alone. Built and ran test-backend-ops at that patch: 17888/17888 passed, three consecutive runs, with the composite SET_ROWS rows passing under a filter.
The rebase itself only brings main's content under it, which is why the commit hash changed but the diff did not.
Not run: the HRX arm (the build I have is GGML_HRX=OFF), where the two paths diverge — unchanged from my earlier review.
The problem
The index-list → mask scatter case merged in #241 (engine ggml-org#1346) compares its scattered mask exactly, and its scores were
filled with uniform random floats. At these widths that is not tie-free (8192 samples over 2^23 float values give ~4 expected
duplicate pairs per row, 11520 give ~7), and where a row ties,
ggml_top_kreturns an index set that is genuinely ambiguous —its contract is unordered and unstable for equal values — while the comparison is exact. So that case can fail on tie-breaking
rather than on anything a kernel did.
The fix
Space the scatter case's scores with exactly representable values 2^-9 apart, shuffled per row, so adjacent values differ by
~1e-3 and an index set can only differ if a producer is genuinely wrong. This is the remedy #238 already applied to TOPK_MOE's
logits, applied to the one case that needed it.
All +18 lines are in
test_top_k_set_rows::initialize_tensors.test_top_kis untouched and did not need this: itsnon-
tiesrows already fill each row with distinct integers (data[i] = i, then shuffled —tests/test-backend-ops.cpp, thetest_top_k::initialize_tensorsabove the diff), which are exact in f32 at every width in the suite, so those rows werealready tie-free.
Measured
The scatter case, one build, one window: 0/15 runs with failures after the spacing, comparing the whole mask exactly (the
exact, positional comparison is only valid once the index set is unique — with random scores it needs a count-based comparison
instead, which is weaker).
The
-o TOP_Krows are not part of this change and no before/after is claimed for them: they were already tie-free, so theintermittent failures observed on some of those rows earlier in the day (
{32779,1,2,1},k=9999among them) are not explainedby ties and remain open in engine ggml-org#1346. This PR does not touch them and should not be read as evidence about them.
Refs ggml-org#1346 (test-only: no kernel, matcher, or backend change).