Skip to content

Vectorize ggml_fp16_to_fp32_row / ggml_fp32_to_fp16_row with F16C hardware conversion - #19

Open
codspeed-hq[bot] wants to merge 2 commits into
masterfrom
codspeed-optim-vectorize-ggml-fp16-to-fp32-row-ggml-fp32-to-fp16-1785127385353
Open

Vectorize ggml_fp16_to_fp32_row / ggml_fp32_to_fp16_row with F16C hardware conversion#19
codspeed-hq[bot] wants to merge 2 commits into
masterfrom
codspeed-optim-vectorize-ggml-fp16-to-fp32-row-ggml-fp32-to-fp16-1785127385353

Conversation

@codspeed-hq

@codspeed-hq codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Adds F16C-accelerated fast paths to the two FP16↔FP32 row-conversion functions in the ggml base library (ggml/src/ggml.c), converting 8 elements per instruction (vcvtph2ps / vcvtps2ph) instead of the scalar software bit-manipulation fallback.

Analysis

Flamegraphs of the baseline showed both row-conversion benchmarks were spending ~100% of their time in the scalar software converters from ggml-impl.h:

  • fp16_to_fp32_row: ggml_compute_fp16_to_fp32 + fp32_to_bits bit-manipulation.
  • fp32_to_fp16_row: the equivalent scalar GGML_FP32_TO_FP16 path.

These functions live in the portable base library, which is intentionally compiled without -mf16c (only the ggml-cpu backend gets the AVX2/F16C arch flags), so they could not simply use __F16C__-guarded intrinsics.

Change

Compiles two dedicated F16C helpers with a GCC/Clang function target attribute (__attribute__((target("f16c,avx")))) so the intrinsics are available even though the translation unit is built with a conservative baseline, then dispatches to them at runtime via __builtin_cpu_supports("f16c"). On any CPU or compiler without F16C support the code falls back to the original scalar loop, so the base library stays fully portable. A scalar tail handles counts that are not a multiple of 8.

Correctness

vcvtph2ps is an exact IEEE-754 fp16→fp32 widening, and vcvtps2ph with round-to-nearest-even matches the software ggml_compute_fp32_to_fp16 rounding (the same instruction/mode the CPU backend already uses).

Verified with a standalone A/B harness linked against the compiled base library (confirmed built without -mf16c, so the runtime-dispatch path is genuinely exercised): compared the optimized functions against independent scalar references over sizes 1, 7, 8, 9, 15, 16, 31, 32, 33, 63, 64, 100, 4096 and the full 32768 fp16 range — bit-exact, 0 mismatches in both directions.

Performance (CodSpeed, CPU simulation)

Measured locally via the CodSpeed CLI, comparing the unmodified base-library build against the optimized build:

Benchmark Before After Speedup
fp32_to_fp16_row 399.4 µs 99.1 µs ~4.0×
fp16_to_fp32_row 262.9 µs 97.9 µs ~2.7×

No regressions in the other 13 benchmarks — this path is untouched by the graph-compute and quantization kernels.

…sion

Add F16C-accelerated fast paths to ggml_fp16_to_fp32_row() and
ggml_fp32_to_fp16_row() in the base library. These functions live in the
portable base library, which is not compiled with -mf16c, so dedicated F16C
helpers are compiled via a GCC/Clang function target attribute
(__attribute__((target("f16c,avx")))) and dispatched at runtime through
__builtin_cpu_supports("f16c"). CPUs and compilers without F16C support fall
back to the original scalar loop, and a scalar tail handles counts that are
not a multiple of 8.

vcvtph2ps is an exact fp16->fp32 widening and vcvtps2ph with
round-to-nearest-even matches the scalar GGML_FP32_TO_FP16 rounding, so the
results are bit-exact with the scalar path (verified over the full fp16 range
and a range of sizes).

fp32_to_fp16_row: 399.4us -> 99.1us (~4x)
fp16_to_fp32_row: 262.9us -> 97.9us (~2.7x)
@github-actions github-actions Bot added the ggml label Jul 27, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown
Author

Merging this PR will improve performance by ×3.2

⚡ 2 improved benchmarks
✅ 13 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
fp32_to_fp16_row 381.2 µs 99 µs ×3.9
fp16_to_fp32_row 262.8 µs 97.9 µs ×2.7

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing codspeed-optim-vectorize-ggml-fp16-to-fp32-row-ggml-fp32-to-fp16-1785127385353 (7cfa1bf) with master (048bb65)

Open in CodSpeed

@codspeed-hq
codspeed-hq Bot marked this pull request as ready for review July 27, 2026 04:53
On Windows, ggml-base.dll is linked with clang/lld-link using -nostdlib, and
__builtin_cpu_supports() references the __cpu_features2 compiler-runtime symbol
which is unavailable in that link, producing an undefined-symbol link error.
Exclude _WIN32 from the F16C runtime-dispatch guard so Windows keeps the
portable scalar path; Linux/macOS x86-64 still use the accelerated path.
@codspeed-hq
codspeed-hq Bot requested a review from coco-speed July 27, 2026 06:12
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.

1 participant