Skip to content

ci: the f16 narrowing ratchet matched half the corpus and said it matched all of it (engine#2219) - #2225

Closed
bong-water-water-bong wants to merge 1 commit into
mainfrom
fix-2219-f16-ratchet
Closed

bong-water-water-bong wants to merge 1 commit into
mainfrom
fix-2219-f16-ratchet

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

tools/ci/hrx_f16_narrowing.py (#2114) is the ratchet that lists every .loom body narrowing f32 to f16. Its match was the plain substrings f32 to f16 and truncf, and the vector form contains neither — the op is fptrunc, not truncf, and the types are wrapped as vector<4xf32> to vector<4xf16>:

%half_values = vector.fptrunc %values : vector<4xf32> to vector<4xf16>

So every vector narrowing was invisible, and the check reported a clean tree over half a corpus.

Measured at the engine's current pin 2b7960bae

Corpus sparse-checked out (blobless, just ggml/src/ggml-hrx/kernel-corpus/kernels), then run through the tool itself:

124 .loom files, 233 fptrunc lines: 101 'f32 to f16', 132 vector form
OLD matcher:                 '25 narrowing body(ies), none new, none stale'   <- 25 of 52
NEW matcher + old baseline:  FAIL (27 new, 0 stale of 25)                     <- 52 exist
NEW matcher + --write:       52 body(ies), none new, none stale

Every figure matches the issue's own measurement: 52 bodies, 25 listed, 27 missed.

The substantive change: the guard count

--check now reports

52 narrowing body(ies), none new, none stale; 10 of them carry a guard marker

10 of 52 narrowing bodies carry a producer-side guard marker (scale_log2/prescale). That is the question engine#1962 still holds open — which narrowing consumers are protected — and the old matcher could not answer it because it was not seeing 27 of the bodies. A ratchet that lists the wrong set is worse than no ratchet: it answers the question with confidence and the wrong denominator.

The matcher, verified in both directions

Substring fixes usually over-match, so both directions are checked:

case old new
scalar fptrunc ✓ ✓
vector fptrunc ✗ ✓
truncf spelling ✓ ✓
fpext (a widening) ✗ ✗
plain fmul (no narrowing) ✗ ✗

fptrunc is LLVM's narrowing op and covers both spellings; truncf and the type-line marker stay because a hand-written or vendor body may use them without it. --self-test passes.

Re-baselined deliberately

The 27 added lines are the bodies the old matcher could not see, written by --write rather than by hand, so the diff is reviewable as a set. This is the one case where growing a baseline is correct: the baseline was wrong, not the corpus.

…ched all of it (engine#2219)

hrx_f16_narrowing.py matched the plain substrings 'f32 to f16' and 'truncf'.
The vector form contains neither -- the op is 'fptrunc', not 'truncf', and the
types are wrapped as 'vector<4xf32> to vector<4xf16>', not 'f32 to f16' -- so
every vector narrowing was invisible to the ratchet.

Measured at the engine's current pin 2b7960bae, corpus sparse-checked out and
checked with --check --root:

  124 .loom files, 233 fptrunc lines: 101 'f32 to f16', 132 vector form
  OLD matcher: '25 narrowing body(ies), none new, none stale'   (25 of 52)
  NEW matcher, old baseline: FAIL (27 new, 0 stale of 25)       (52 exist)
  NEW matcher, re-baselined: 52 bodies, none new, none stale    (--write)

'fptrunc' is LLVM's narrowing op and covers both spellings; truncf and the
type line stay in the marker list because a hand-written or vendor body may
use them without it.

The substantive change is the guard count, which is the question engine#1962
still holds open: the ratchet now reports 10 of 52 narrowing bodies carrying a
producer-side guard marker (scale_log2/prescale). The old matcher could not
answer that question because it was not seeing 27 of the bodies.

Verified: --self-test ok; and the four matcher cases behave -- scalar fptrunc
and truncf matched, vector fptrunc matched (was missed), fpext and a plain
fmul not matched (no over-match).
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Incorrect Regex Pattern

The regex pattern used to detect f32 to f16 narrowing operations was missing vector forms. Specifically, it did not account for vector.fptrunc operations which are used for vector type narrowing. This caused the ratchet to miss 27 out of 52 narrowing bodies, leading to an incorrect assessment of the corpus.

NARROW = ("fptrunc", "truncf", "f32 to f16")
Incomplete Baseline List

The baseline list of .loom files has been updated to include 27 previously missed narrowing bodies. This update is necessary to ensure that the ratchet correctly identifies all f32 to f16 narrowing operations, including those involving vector types.

loom-libs/motifs/bf16_f16.loom
loom-libs/motifs/dequant.loom
loom-libs/motifs/dequant_1bit.loom
loom-libs/motifs/dequant_prism.loom
loom-libs/motifs/f32_f16.loom
loom-libs/motifs/llm_attention_qkv_matmul_postops.loom
loom-libs/motifs/mul_mat_f32_f32_wmma_core.loom
loom-libs/motifs/mul_mat_id_f16_f16_wmma_core.loom
loom-libs/motifs/mul_mat_id_f32_f32_wmma_core.loom
loom-libs/motifs/mul_mat_id_q4k_f16_wmma_projection.loom
loom-libs/motifs/mul_mat_id_q5k_f16_wmma_projection.loom
loom-libs/motifs/mul_mat_id_q6k_f16_wmma_projection.loom
loom-libs/motifs/mul_mat_id_swiglu_f16_f16_accumulate.loom
loom-libs/motifs/mul_mat_id_swiglu_f16_f16_wmma_body.loom
loom-libs/motifs/mul_mat_quantized_f16_prefill.loom
loom-libs/motifs/publish_f32.loom
loom-libs/motifs/q4_k_f16.loom
loom-libs/motifs/q6_k_f16.loom
loom-libs/motifs/q8_0_f16.loom
loom-libs/motifs/q8_1_f16.loom
loom-libs/motifs/quantize_q8_1_x4.loom
loom-libs/motifs/unary_f32_apply.loom
loom-libs/ops/copy_f32.loom
loom-libs/ops/flash_attention_decode_split_f32_f16_wmma.loom
loom-libs/ops/flash_attention_f32_f16_wmma.loom
loom-libs/ops/gated_delta_net_f32_wmma.loom
loom-libs/ops/llm_attention_k_matmul_rope_set_rows_decode_f32_f32.loom
loom-libs/ops/llm_attention_v_matmul_set_rows_decode_f32_f32.loom
loom-libs/ops/mul_mat_id_swiglu_f32_f32_wmma.loom
loom-libs/ops/mul_mat_q6_k_packed_f16_wmma.loom
loom-libs/ops/mul_mat_swiglu_f32_f32_wmma.loom
loom-libs/ops/mul_mat_swiglu_symmetric_i4_wmma.loom
loom-libs/ops/mul_mat_symmetric_i4_wmma.loom
loom-libs/ops/mul_mat_symmetric_i8_wmma.loom
loom-libs/ops/rmsnorm_binary_f32.loom
loom-libs/ops/rope_set_rows_f32.loom
loom-libs/ops/set_rows.loom
loom-libs/ops/small_rows_f32.loom
qwen_moe/ggml/linear_q6k_q8_1_x4.loom
qwen_moe/ggml/quantize_q8_1_x4.loom
qwen_moe/qwen3_moe/attention_postprocess_f32_f16.loom
qwen_moe/qwen3_moe/attention_prepare_quantized.loom
qwen_moe/qwen3_moe/dense_linear_quantized_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_f32_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_q128_f32_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_split_f32_f16_wmma.loom
qwen_moe/qwen3_moe/flash_attention_decode_split_next_q8_test.loom
qwen_moe/qwen3_moe/flash_attention_f32_f16_wmma.loom
qwen_moe/qwen3_moe/routed_down_quantized_f16_wmma.loom
qwen_moe/qwen3_moe/routed_down_weighted_reduce_next_rmsnorm_q8_1_x4.loom
qwen_moe/qwen3_moe/routed_gate_up_swiglu_q4k.loom
qwen_moe/qwen3_moe/routed_linear_q4k_f16_wmma.loom

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: CHANGES at 0e28efa — this is a duplicate of #2220, and where they differ the other one is right. Not a style preference: measured below.

Duplicate

Same two files, and the two branches are independent (neither contains the other):

#2225  tools/ci/hrx_f16_narrowing.py  tools/ci/hrx_f16_narrowing.txt   head 0e28efa
#2220  tools/ci/hrx_f16_narrowing.py  tools/ci/hrx_f16_narrowing.txt   head 8614166
ancestry: neither is an ancestor of the other

Both fix #2219 (the matcher missed vector.fptrunc …), both add a baseline, +27 vs +28 lines. One of them should merge; two will conflict in the same two files.

Where they differ, and why it decides it

#2225:  NARROW = ("fptrunc", "truncf", "f32 to f16")                                   # substrings
#2220:  NARROW = re.compile(r"f32 to f16|vector<[0-9x]*f32>\s+to\s+vector<[0-9x]*f16>|truncf")

Run both against the bf16 vector form — the negative case that a narrowing check must not count:

text:  %h = vector.fptrunc %v : vector<4xf32> to vector<4xbf16>
  fptrunc-substring (#2225) -> 1 match    ← counted as an f16 narrowing
  #2220's regex            -> 0 matches   ← correctly excluded

Adding fptrunc as a substring is the one-word fix and it looks right, but it matches on the operation, not on the destination type. bf16 narrowing is not this defect: bf16 and f32 share an 8-bit exponent, so a value outside f16's range narrows to a bf16 that is still finite — no inf, no NaN, nothing for this inventory to report. Counting those sites makes the report noisier in exactly the direction that gets a check triaged away.

#2220 also carries that case as a self-test arm (w.loom with to vector<4xbf16> must not be found), which is what keeps the exclusion from being re-widened later.

Recommendation

Close this and keep #2220. If it is kept instead, the matcher has to test the destination type (to vector<…f16>), which is the other PR's regex — at which point the two diffs are the same diff.

Nothing is wrong with the diagnosis here: "the ratchet matched half the corpus" is the same finding #2220 makes (132 of 233 lines at pin 2b7960bae), and it arrived independently, which is worth noting for whoever triages — the defect was visible to two lanes from the artifact alone.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at 0e28efa — the ratchet shape is right and I verified all three of its modes. One limitation is real and worth one line in the tool, because it is the shape #1962's own classes are counted in.

What it is, and the modes I ran

A new tools/ci/hrx_f16_narrowing.py over #2204's inventory, with a baseline (tools/ci/hrx_f16_narrowing.txt, 52 paths) that may only shrink:

$ --check --root .            (no corpus checked out)
  skip: no .loom corpus at third_party/llama.cpp/… (check out third_party/llama.cpp)      rc 77
$ --self-test
  ok   a missing corpus returns 77, not 0                                                   rc 0

rc 77 rather than 0 for a missing corpus is the right call and the self-test pins it — the "nothing readable is not a pass" rule, with a code a harness can act on. Then, with the corpus present:

$ (off-pin corpus copied in)
  hrx_f16_narrowing: loom-libs/motifs/dequant_1bit.loom is in the baseline but no longer narrows -- drop the line …
  hrx_f16_narrowing: FAIL (0 new, 3 stale of 52)                                            rc 1
$ (a NEW file with a narrowing)
  hrx_f16_narrowing: NEW narrowing body loom-libs/motifs/zz_new_narrowing.loom -- it narrows an f32 value to f16 …
  hrx_f16_narrowing: FAIL (1 new, 3 stale of 52)                                            rc 1

Both directions fail, each with the repair named ("drop the line, the list should shrink" / "NEW narrowing body …"), which is the #1728 lesson applied — a ratchet that only counts upward cannot see a rename or a removal.

The limitation, measured

The unit is a FILE, not a site. I injected a real arith.truncf narrowing into loom-libs/motifs/f32_f16.loom — a file already in the baseline — and the run was unchanged:

$ (truncf appended to f32_f16.loom, a baselined file)
  hrx_f16_narrowing: FAIL (0 new, 3 stale of 52)        ← no mention of the new site

So a second narrowing inside an already-listed file lands silently, and 0 new reads as "no new narrowing" when it means "no new file that narrows". That matters here more than in most ratchets, because #1962/#2167 are precisely about which site narrows and how far past it the update sits — a file that gains a second truncf is exactly the case the inventory's three classes were built to distinguish.

Ask (one of two, both cheap): count sites per file in the baseline (path<TAB>count, like #1601's direction-B set) so a second narrowing in a listed file drifts; or, if file-level is deliberate for now, say so in the module docstring — "the unit is a file; a new narrowing inside a listed file does not drift" — because 0 new will otherwise be quoted as stronger than it is.

And it corroborates #2204's corpus question

On the off-pin corpus this box has, the ratchet reports 3 stale entries (dequant_1bit.loom, dequant_prism.loom, small_rows_f32.loom) — i.e. the baseline was written against a corpus where those files still narrow. That is the same divergence I flagged on #2204 (301 files short of its own HEAD, #2129), now visible in the ratchet's own output. Printing the corpus fingerprint would make this a statement rather than an inference, and it is the second PR in a row where that ask would have paid.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

/home/bcloud/.cache/scratch-pi-prs/c2225.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant