Skip to content

ggml-cpu: restore the src1 guard on the F16 Hadamard matmul path - #147

Open
bri-prism wants to merge 1 commit into
prism-v7from
fix/fwht-f16-src1-guard
Open

ggml-cpu: restore the src1 guard on the F16 Hadamard matmul path#147
bri-prism wants to merge 1 commit into
prism-v7from
fix/fwht-f16-src1-guard

Conversation

@bri-prism

Copy link
Copy Markdown

What

Two follow-ups to the F16 src1 support on the Hadamard-hinted matmul path in
ggml_compute_forward_mul_mat. One restores an assertion, one replaces a hand-rolled loop with
the vectorised routine that already exists for it.

Why

The assertion. The conversion block the F16 branch lives in is entered whenever
src1->type != vec_dot_type, and that branch writes plain floats into wdata. That is only
valid when the wdata rows are floats. With a quantized src0 the vec_dot_type is
Q8_0/Q8_1/Q8_K, whose rows are about one byte per element (34 bytes per 32 for Q8_0), so writing
four bytes per element would run roughly 3.8x past the end of the buffer.

Nothing reaches that today. supports_op admits an F16 src1 only when src0 and dst are
both F32, so the scheduler never routes the bad combination to the CPU backend. The point is
narrower: the assertion here used to be src1->type == GGML_TYPE_F32 and would have caught it,
and widening it to admit F16 unconditionally left the invariant undefended for any caller that
reaches forward_mul_mat without going through supports_op. This asserts what the code
actually handles.

The loop. ggml_cpu_fp16_to_fp32 is declared in ggml-cpu.h and defined further down this
same file with F16C and AVX512F paths, so the branch can call it instead of converting element by
element. That needs the header included, since the definition sits below the call site.

Testing

test-backend-ops on CPU:

  • MUL_MAT 1249/1249
  • MUL_MAT_HADAMARD 18/18, including the four type_b=f16 cases and the type_x=f16 FWHT case

Worth stating plainly: the suite passes identically before and after, which is the expected
result. The converter swap is behaviour-preserving, and the assertion defends a path no test
drives, because supports_op refuses a quantized src0 with an F16 src1 and the harness skips
it as unsupported. So these tests show nothing is broken; they would not have caught the widened
assertion in the first place. Testing that invariant properly needs a direct call into
forward_mul_mat outside the scheduler, which is a larger change than this.

No performance claim. The F16 branch only runs for Hadamard-hinted F32 matmuls, so it is off the
hot path for ordinary decode, and I did not measure it.

Notes

clang-format is not applied. Adding an include makes it alphabetize and reflow the whole include
block, which this file's order does not follow, so the formatting change would be larger and less
reviewable than the fix. The new include is grouped with the other ggml-cpu headers.

The same two issues exist in the corresponding upstream PR, ggml-org#27779, which is
still open. If that lands as-is, upstream inherits both.

Two things about the F16 src1 support in forward_mul_mat.

The conversion block it lives in runs whenever src1->type != vec_dot_type, and
the F16 branch writes plain floats into wdata. That is only valid when the wdata
rows are floats. With a quantized src0 the vec_dot_type is Q8_0/Q8_1/Q8_K, whose
rows are about one byte per element (34 bytes per 32 for Q8_0), so writing four
bytes per element would run roughly 3.8x off the end of the buffer.

Nothing reaches that today: supports_op only admits an F16 src1 when src0 and dst
are F32, so the scheduler never routes the bad combination to the CPU backend.
But the assertion here used to be src1->type == GGML_TYPE_F32, which would have
caught it, and widening it to admit F16 unconditionally left the invariant
undefended for any caller that reaches forward_mul_mat without consulting
supports_op. Assert what the code actually handles instead.

Second, the branch hand-rolled a scalar F16 to F32 loop. ggml_cpu_fp16_to_fp32 is
declared in ggml-cpu.h and defined further down this same file with F16C and
AVX512F paths, so call it. Behaviour-preserving; no speedup measured or claimed,
since this path only runs for Hadamard-hinted F32 matmuls.

test-backend-ops on CPU: MUL_MAT 1249/1249, MUL_MAT_HADAMARD 18/18 including the
four f16 src1 cases and the f16 FWHT case. Note the suite would not have caught
the widened assert: no case drives a quantized src0 with an F16 src1, because
supports_op refuses it and the harness skips it as unsupported.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The functional changes are sound; only a non-blocking comment-format issue remains.

Pull request overview

Restores safety checks and uses the existing optimized F16 conversion routine in CPU Hadamard matmul.

Changes:

  • Restricts F16 src1 conversion to F32 workspace rows.
  • Replaces scalar conversion with ggml_cpu_fp16_to_fp32.
File summaries
File Description
ggml/src/ggml-cpu/ggml-cpu.c Adds the guard, converter call, and required header.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1338 to +1340
// an F16 src1 is converted straight to float below, so it is only valid when the
// wdata rows are floats. A quantized vec_dot_type would size them at about one
// byte per element and the conversion would run off the end of the buffer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants