Skip to content

POCL: vectorizable kernels without bounds checks, and FMA for muladd - #922

Merged
maleadt merged 2 commits into
tb/pocl-launch-cachefrom
tb/pocl-codegen
Oct 26, 2026
Merged

maleadt merged 2 commits into
tb/pocl-launch-cachefrom
tb/pocl-codegen

Conversation

@maleadt

@maleadt maleadt commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Stacked on #923.

On one CPU core, KA kernels on the POCL back-end were 1.15–2.9x (x86) and 2.6–10.6x (Apple M1) slower than the equivalent serial Julia loop. Two things caused almost all of it:

  • The bounds check. Every @kernel is compiled with DynamicCheck, so PoCL's loop over the work-items has a conditional body. On x86 it gets vectorized with masked loads and stores; on AArch64 it doesn't get vectorized at all. Launches whose workgroups cover the ndrange exactly now run a separate kernel compiled without the check. A launch only compiles the kernel it runs: with a tuned workgroup size, that size is predicted from the device's limits (falling back to the checked kernel if the kernel's own limits are smaller and its tuned workgroups don't cover the ndrange). Both kernels share the host code that looks them up and launches them, the unchecked one getting the checked one's context, which only differs in a type parameter; the unchecked kernel is cached separately from the checked one, so launches alternating between both find either on the fast path. Custom iteration spaces keep the check, since they may map work-items outside of the ndrange. Only the POCL back-end changes: on GPUs the check is cheap, and a second kernel would cost compile time.
  • muladd doesn't contract. The SPIR-V back-end can only express contraction with SPV_KHR_float_controls2, which we don't emit yet (POCL: contract fast-math flags lost through SPIR-V, no FMAs emitted #865, SPIR-V: declare FloatControls2 for kernels with SPV_KHR_float_controls2 GPUCompiler.jl#1007) and the PoCL JLL can't read yet (Enable SPV_KHR_float_controls2 for the CPU devices pocl/pocl#2393 needs LLVM 23), so every muladd became a separate multiplication and addition. Until then, as a stopgap, muladd of Float32/Float64 now compiles to fma when the host CPU has hardware FMA (x86 with fma, all AArch64), through an overlay table selected by a compiler parameter, so the choice is part of the cache key. GPUCompiler's have_fma doesn't answer this, since OpenCL's fma may be implemented in software.

Queue-path timings, main → this PR (µs; best of 3; serial Julia loop in brackets):

Xeon E3-1245 v6, 1 thread -t 4,0 M1, 1 thread -t 4,0
axpy 2^17 54.0 → 36.1 [19.2] 19.5 → 12.9 86.3 → 17.4 [14.5] 28.2 → 8.8
axpy 2^20 356 → 206 [174] 110 → 75 767 → 150 [120] 235 → 78
128 dependent muladds, 1e6 25760 → 9606 [9105] 6737 → 2564 136324 → 51905 [12865] 36995 → 14029
7-point stencil, 128³ ndrange 1340 → 918 [934] 825 → 817 3722 → 930 [1030] 2297 → 274

Choosing between the two kernels adds 0–6 ms (up to 21M instructions) to the first launch of a new kernel on top of #923, while kernels that PoCL compiles faster without the check launch sooner: the reduction's first divisible launch takes 12–15 ms less. Median over 9 fresh kernels on the Xeon with -t 4, for an elementwise kernel, a 2D stencil and an AK-style reduction with a fresh closure, each with divisible and non-divisible ndranges: #923 → this PR takes 105–137 ms → 110–143 ms with a workgroup size that is tuned (or, for the reduction, given at launch), and 101–132 ms → 106–135 ms with one in the kernel. Against main, both PRs together take it from 158–192 ms to 110–143 ms, and from 142–174 ms to 106–135 ms.

The remaining difference at 1 thread on small arrays is the queue's launch cost. Launches whose tuned workgroup size doesn't divide the ndrange (e.g. 1e5 elements, or a 126³ stencil) keep the check and are unchanged. The muladd row on M1 is one of those. Running their full workgroups without the check is part of the host-kernel work (#906).

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark Results

Show table
main 4f49ec0... main / 4f49ec0...
const/@Const/Float32/262144 0.629 ± 0.018 ms 0.317 ± 0.02 ms 1.99 ± 0.14
const/@Const/Float32/65536 0.215 ± 0.011 ms 0.132 ± 0.0094 ms 1.64 ± 0.14
const/@Const/Float64/262144 0.828 ± 0.017 ms 0.583 ± 0.022 ms 1.42 ± 0.062
const/@Const/Float64/65536 0.365 ± 0.017 ms 0.194 ± 0.0086 ms 1.88 ± 0.12
const/unmarked/Float32/262144 2.57 ± 0.019 ms 0.463 ± 0.024 ms 5.54 ± 0.29
const/unmarked/Float32/65536 0.632 ± 0.025 ms 0.164 ± 0.019 ms 3.84 ± 0.47
const/unmarked/Float64/262144 3.13 ± 0.033 ms 0.868 ± 0.029 ms 3.6 ± 0.12
const/unmarked/Float64/65536 0.806 ± 0.014 ms 0.257 ± 0.025 ms 3.14 ± 0.32
dgsem/2D/Float32/fluxdiff/element 0.592 ± 0.019 ms 0.544 ± 0.03 ms 1.09 ± 0.068
dgsem/2D/Float32/fluxdiff/node E=8 0.835 ± 0.016 ms 1.51 ± 0.015 ms 0.552 ± 0.012
dgsem/2D/Float32/fluxdiff/node halfsweep E=8 0.666 ± 0.018 ms 0.604 ± 0.025 ms 1.1 ± 0.054
dgsem/2D/Float32/fluxdiff/node halfsweep v-last E=8 0.622 ± 0.028 ms 0.602 ± 0.023 ms 1.03 ± 0.061
dgsem/2D/Float32/weak/element 0.209 ± 0.019 ms 0.208 ± 0.027 ms 1 ± 0.16
dgsem/2D/Float32/weak/element private 0.216 ± 0.015 ms 0.363 ± 0.024 ms 0.594 ± 0.057
dgsem/2D/Float32/weak/node localmem E=1 0.188 ± 0.014 ms 0.178 ± 0.018 ms 1.05 ± 0.13
dgsem/2D/Float32/weak/node localmem E=8 0.207 ± 0.026 ms 0.175 ± 0.017 ms 1.18 ± 0.19
dgsem/2D/Float32/weak/node localmem v-last E=1 0.271 ± 0.024 ms 0.253 ± 0.028 ms 1.07 ± 0.15
dgsem/2D/Float32/weak/node localmem v-last E=8 0.25 ± 0.02 ms 0.245 ± 0.017 ms 1.02 ± 0.11
dgsem/2D/Float32/weak/node recompute E=8 0.516 ± 0.026 ms 0.479 ± 0.025 ms 1.08 ± 0.078
dgsem/2D/Float64/fluxdiff/element 0.601 ± 0.028 ms 0.581 ± 0.026 ms 1.03 ± 0.066
dgsem/2D/Float64/fluxdiff/node E=8 0.797 ± 0.015 ms 1.8 ± 0.04 ms 0.443 ± 0.013
dgsem/2D/Float64/fluxdiff/node halfsweep E=8 0.614 ± 0.02 ms 0.576 ± 0.027 ms 1.07 ± 0.061
dgsem/2D/Float64/fluxdiff/node halfsweep v-last E=8 0.634 ± 0.026 ms 0.626 ± 0.024 ms 1.01 ± 0.058
dgsem/2D/Float64/weak/element 0.236 ± 0.017 ms 0.245 ± 0.021 ms 0.964 ± 0.11
dgsem/2D/Float64/weak/element private 0.244 ± 0.014 ms 0.284 ± 0.019 ms 0.86 ± 0.076
dgsem/2D/Float64/weak/node localmem E=1 0.214 ± 0.026 ms 0.191 ± 0.026 ms 1.12 ± 0.2
dgsem/2D/Float64/weak/node localmem E=8 0.187 ± 0.026 ms 0.185 ± 0.025 ms 1.01 ± 0.2
dgsem/2D/Float64/weak/node localmem v-last E=1 0.272 ± 0.023 ms 0.265 ± 0.029 ms 1.03 ± 0.14
dgsem/2D/Float64/weak/node localmem v-last E=8 0.264 ± 0.024 ms 0.259 ± 0.025 ms 1.02 ± 0.13
dgsem/2D/Float64/weak/node recompute E=8 0.656 ± 0.022 ms 0.491 ± 0.021 ms 1.34 ± 0.073
dgsem/3D/Float32/fluxdiff/element 1.93 ± 0.018 ms 1.84 ± 0.052 ms 1.05 ± 0.031
dgsem/3D/Float32/fluxdiff/node E=2 3.39 ± 0.022 ms 4.48 ± 0.035 ms 0.757 ± 0.0077
dgsem/3D/Float32/fluxdiff/node halfsweep E=1 2.25 ± 0.022 ms 1.7 ± 0.021 ms 1.32 ± 0.021
dgsem/3D/Float32/fluxdiff/node halfsweep v-last E=1 2.03 ± 0.033 ms 2.05 ± 0.031 ms 0.99 ± 0.022
dgsem/3D/Float32/weak/element 0.678 ± 0.024 ms 0.656 ± 0.025 ms 1.03 ± 0.054
dgsem/3D/Float32/weak/element private 0.698 ± 0.02 ms 0.707 ± 0.028 ms 0.987 ± 0.048
dgsem/3D/Float32/weak/node localmem E=1 0.66 ± 0.026 ms 0.641 ± 0.02 ms 1.03 ± 0.052
dgsem/3D/Float32/weak/node localmem E=2 0.744 ± 0.017 ms 0.61 ± 0.024 ms 1.22 ± 0.056
dgsem/3D/Float32/weak/node localmem v-last E=1 0.854 ± 0.022 ms 0.833 ± 0.022 ms 1.03 ± 0.038
dgsem/3D/Float32/weak/node localmem v-last E=2 0.921 ± 0.0094 ms 0.951 ± 0.016 ms 0.969 ± 0.019
dgsem/3D/Float32/weak/node recompute E=2 2.06 ± 0.02 ms 1.94 ± 0.035 ms 1.06 ± 0.021
dgsem/3D/Float32/weak/slab E=1 2.68 ± 0.023 ms 0.86 ± 0.029 ms 3.12 ± 0.11
dgsem/3D/Float32/weak/slab E=8 5.53 ± 0.028 ms 1.07 ± 0.035 ms 5.17 ± 0.17
dgsem/3D/Float32/weak/slab double-buffered E=1 1.65 ± 0.014 ms 0.977 ± 0.036 ms 1.69 ± 0.063
dgsem/3D/Float32/weak/slab double-buffered E=8 2.04 ± 0.029 ms 1.16 ± 0.047 ms 1.76 ± 0.075
dgsem/3D/Float32/weak/slab double-buffered v-last E=1 1.72 ± 0.022 ms 0.818 ± 0.031 ms 2.11 ± 0.083
dgsem/3D/Float32/weak/slab double-buffered v-last E=8 2.14 ± 0.017 ms 1.09 ± 0.024 ms 1.96 ± 0.045
dgsem/3D/Float32/weak/slab v-last E=1 2.71 ± 0.02 ms 0.866 ± 0.032 ms 3.13 ± 0.12
dgsem/3D/Float32/weak/slab v-last E=8 5.48 ± 0.026 ms 1.12 ± 0.057 ms 4.9 ± 0.25
dgsem/3D/Float64/fluxdiff/element 2.01 ± 0.024 ms 1.97 ± 0.11 ms 1.02 ± 0.057
dgsem/3D/Float64/fluxdiff/node E=2 3.06 ± 0.034 ms 8.25 ± 0.037 ms 0.37 ± 0.0045
dgsem/3D/Float64/fluxdiff/node halfsweep E=1 2.19 ± 0.019 ms 1.97 ± 0.083 ms 1.11 ± 0.048
dgsem/3D/Float64/fluxdiff/node halfsweep v-last E=1 2.16 ± 0.022 ms 2.24 ± 0.076 ms 0.964 ± 0.034
dgsem/3D/Float64/weak/element 0.816 ± 0.016 ms 0.707 ± 0.035 ms 1.15 ± 0.062
dgsem/3D/Float64/weak/element private 0.861 ± 0.02 ms 0.744 ± 0.028 ms 1.16 ± 0.051
dgsem/3D/Float64/weak/node localmem E=1 0.676 ± 0.024 ms 0.677 ± 0.024 ms 0.999 ± 0.05
dgsem/3D/Float64/weak/node localmem E=2 0.778 ± 0.011 ms 0.643 ± 0.025 ms 1.21 ± 0.05
dgsem/3D/Float64/weak/node localmem v-last E=1 0.864 ± 0.0079 ms 0.854 ± 0.021 ms 1.01 ± 0.027
dgsem/3D/Float64/weak/node localmem v-last E=2 0.964 ± 0.011 ms 0.987 ± 0.024 ms 0.977 ± 0.027
dgsem/3D/Float64/weak/node recompute E=2 2.14 ± 0.014 ms 2.05 ± 0.071 ms 1.04 ± 0.037
dgsem/3D/Float64/weak/slab E=1 2.77 ± 0.042 ms 0.965 ± 0.038 ms 2.87 ± 0.12
dgsem/3D/Float64/weak/slab E=8 6.28 ± 0.042 ms 1.79 ± 0.17 ms 3.51 ± 0.33
dgsem/3D/Float64/weak/slab double-buffered E=1 1.75 ± 0.015 ms 0.886 ± 0.018 ms 1.98 ± 0.043
dgsem/3D/Float64/weak/slab double-buffered E=8 2.15 ± 0.018 ms 1.75 ± 0.2 ms 1.23 ± 0.14
dgsem/3D/Float64/weak/slab double-buffered v-last E=1 1.72 ± 0.017 ms 0.849 ± 0.019 ms 2.03 ± 0.049
dgsem/3D/Float64/weak/slab double-buffered v-last E=8 2.25 ± 0.028 ms 1.8 ± 0.21 ms 1.25 ± 0.15
dgsem/3D/Float64/weak/slab v-last E=1 2.8 ± 0.025 ms 0.935 ± 0.031 ms 2.99 ± 0.1
dgsem/3D/Float64/weak/slab v-last E=8 6.2 ± 0.038 ms 1.87 ± 0.41 ms 3.31 ± 0.73
launch/3D static workgroup, dynamic ndrange 17.9 ± 6.1 μs 10.9 ± 3.6 μs 1.63 ± 0.78
launch/3D static workgroup, static ndrange 14.9 ± 0.63 μs 14.6 ± 6.1 μs 1.02 ± 0.43
launch/dynamic workgroup, dynamic ndrange 11.8 ± 0.73 μs 14.6 ± 1.3 μs 0.812 ± 0.09
launch/dynamic workgroup, dynamic ndrange, workgroupsize given 11.9 ± 0.72 μs 12.8 ± 3.6 μs 0.931 ± 0.27
launch/static workgroup, dynamic ndrange 10.5 ± 0.36 μs 13.5 ± 2.3 μs 0.781 ± 0.13
launch/static workgroup, static ndrange 14.3 ± 3.5 μs 10.7 ± 0.48 μs 1.34 ± 0.34
narrow workgroup/masked/W=16 0.71 ± 0.019 ms 0.491 ± 0.054 ms 1.45 ± 0.16
narrow workgroup/masked/W=4 0.675 ± 0.028 ms 0.468 ± 0.041 ms 1.44 ± 0.14
narrow workgroup/unmasked/W=16 0.435 ± 0.021 ms 0.491 ± 0.05 ms 0.888 ± 0.1
narrow workgroup/unmasked/W=4 0.425 ± 0.019 ms 0.489 ± 0.046 ms 0.869 ± 0.091
partition/dynamic workgroup, dynamic ndrange 0.0606 ± 0.0029 μs 0.0611 ± 0.014 μs 0.992 ± 0.23
partition/static workgroup, dynamic ndrange 0.0641 ± 0.012 μs 0.0644 ± 0.012 μs 0.996 ± 0.27
partition/static workgroup, static ndrange 1.55 ± 0 ns 1.55 ± 0 ns 1 ± 0
saxpy/default/Float16/1024 10.9 ± 0.69 μs 9.53 ± 0.94 μs 1.14 ± 0.13
saxpy/default/Float16/1048576 1.46 ± 0.026 ms 0.279 ± 0.15 ms 5.25 ± 2.8
saxpy/default/Float16/16384 0.0367 ± 0.015 ms 24.7 ± 2.3 μs 1.48 ± 0.63
saxpy/default/Float16/2048 16.1 ± 2.7 μs 20.4 ± 7.8 μs 0.79 ± 0.33
saxpy/default/Float16/256 11.4 ± 0.59 μs 15.2 ± 4.3 μs 0.75 ± 0.21
saxpy/default/Float16/262144 0.405 ± 0.019 ms 0.111 ± 0.025 ms 3.63 ± 0.84
saxpy/default/Float16/32768 0.0948 ± 0.016 ms 28.9 ± 9 μs 3.28 ± 1.2
saxpy/default/Float16/4096 22.1 ± 11 μs 18.6 ± 7.5 μs 1.19 ± 0.77
saxpy/default/Float16/512 11.4 ± 0.24 μs 16.6 ± 9.7 μs 0.688 ± 0.4
saxpy/default/Float16/64 15.8 ± 0.64 μs 11.6 ± 0.25 μs 1.36 ± 0.063
saxpy/default/Float16/65536 0.141 ± 0.015 ms 0.0386 ± 0.01 ms 3.66 ± 1
saxpy/default/Float32/1024 10.3 ± 4.2 μs 9.7 ± 0.58 μs 1.06 ± 0.43
saxpy/default/Float32/1048576 0.886 ± 0.059 ms 0.387 ± 0.15 ms 2.29 ± 0.9
saxpy/default/Float32/16384 31.5 ± 15 μs 24 ± 4.7 μs 1.31 ± 0.68
saxpy/default/Float32/2048 15.4 ± 0.95 μs 15.6 ± 7.3 μs 0.99 ± 0.47
saxpy/default/Float32/256 11.2 ± 0.91 μs 15.1 ± 4.6 μs 0.743 ± 0.23
saxpy/default/Float32/262144 0.249 ± 0.024 ms 0.135 ± 0.06 ms 1.84 ± 0.84
saxpy/default/Float32/32768 0.0411 ± 0.0071 ms 29.1 ± 5.8 μs 1.41 ± 0.37
saxpy/default/Float32/4096 18.2 ± 10 μs 15.8 ± 2.1 μs 1.15 ± 0.68
saxpy/default/Float32/512 10.1 ± 0.45 μs 10.3 ± 0.96 μs 0.983 ± 0.1
saxpy/default/Float32/64 15.3 ± 4.4 μs 15.5 ± 0.65 μs 0.988 ± 0.29
saxpy/default/Float32/65536 0.112 ± 0.02 ms 0.0399 ± 0.0052 ms 2.82 ± 0.63
saxpy/default/Float64/1024 15.2 ± 1.1 μs 22 ± 1.2 μs 0.692 ± 0.064
saxpy/default/Float64/1048576 1.22 ± 0.24 ms 1.29 ± 0.64 ms 0.941 ± 0.5
saxpy/default/Float64/16384 30.6 ± 13 μs 0.0319 ± 0.0074 ms 0.96 ± 0.48
saxpy/default/Float64/2048 16.1 ± 0.8 μs 15.7 ± 6.8 μs 1.02 ± 0.44
saxpy/default/Float64/256 22.4 ± 8.1 μs 20.2 ± 7.2 μs 1.11 ± 0.56
saxpy/default/Float64/262144 0.264 ± 0.04 ms 0.231 ± 0.21 ms 1.14 ± 1
saxpy/default/Float64/32768 0.0454 ± 0.045 ms 0.0413 ± 0.01 ms 1.1 ± 1.1
saxpy/default/Float64/4096 18 ± 0.68 μs 22.5 ± 3.3 μs 0.801 ± 0.12
saxpy/default/Float64/512 13.5 ± 0.86 μs 19.1 ± 0.49 μs 0.708 ± 0.049
saxpy/default/Float64/64 11.9 ± 0.16 μs 16.7 ± 0.3 μs 0.715 ± 0.016
saxpy/default/Float64/65536 0.106 ± 0.02 ms 0.122 ± 0.1 ms 0.867 ± 0.73
saxpy/static workgroup=(1024,)/Float16/1024 11.2 ± 12 μs 14.2 ± 10 μs 0.789 ± 1
saxpy/static workgroup=(1024,)/Float16/1048576 1.49 ± 0.03 ms 0.324 ± 0.17 ms 4.59 ± 2.4
saxpy/static workgroup=(1024,)/Float16/16384 0.0368 ± 0.015 ms 24 ± 3 μs 1.54 ± 0.66
saxpy/static workgroup=(1024,)/Float16/2048 15.9 ± 0.94 μs 13.5 ± 3.8 μs 1.18 ± 0.34
saxpy/static workgroup=(1024,)/Float16/256 16.3 ± 0.77 μs 18.5 ± 6.7 μs 0.884 ± 0.32
saxpy/static workgroup=(1024,)/Float16/262144 0.417 ± 0.023 ms 0.119 ± 0.021 ms 3.51 ± 0.66
saxpy/static workgroup=(1024,)/Float16/32768 0.0964 ± 0.014 ms 29.3 ± 2.2 μs 3.29 ± 0.55
saxpy/static workgroup=(1024,)/Float16/4096 19.8 ± 0.6 μs 16 ± 4.8 μs 1.23 ± 0.37
saxpy/static workgroup=(1024,)/Float16/512 11.6 ± 0.42 μs 11.7 ± 0.46 μs 0.998 ± 0.053
saxpy/static workgroup=(1024,)/Float16/64 16.2 ± 0.48 μs 16.1 ± 0.41 μs 1.01 ± 0.04
saxpy/static workgroup=(1024,)/Float16/65536 0.146 ± 0.018 ms 0.0365 ± 0.007 ms 4 ± 0.91
saxpy/static workgroup=(1024,)/Float32/1024 23.6 ± 1.1 μs 20.4 ± 6.9 μs 1.16 ± 0.4
saxpy/static workgroup=(1024,)/Float32/1048576 0.897 ± 0.088 ms 0.424 ± 0.4 ms 2.12 ± 2
saxpy/static workgroup=(1024,)/Float32/16384 26.8 ± 0.99 μs 24.9 ± 2.1 μs 1.08 ± 0.098
saxpy/static workgroup=(1024,)/Float32/2048 15.6 ± 2.1 μs 18 ± 8.4 μs 0.866 ± 0.42
saxpy/static workgroup=(1024,)/Float32/256 12.4 ± 0.57 μs 16.9 ± 0.76 μs 0.735 ± 0.047
saxpy/static workgroup=(1024,)/Float32/262144 0.251 ± 0.025 ms 0.134 ± 0.032 ms 1.88 ± 0.49
saxpy/static workgroup=(1024,)/Float32/32768 0.0605 ± 0.0033 ms 29.5 ± 4.8 μs 2.05 ± 0.35
saxpy/static workgroup=(1024,)/Float32/4096 27.1 ± 9.7 μs 19.1 ± 3.8 μs 1.42 ± 0.58
saxpy/static workgroup=(1024,)/Float32/512 10.3 ± 0.47 μs 10.5 ± 0.76 μs 0.978 ± 0.084
saxpy/static workgroup=(1024,)/Float32/64 11.8 ± 0.51 μs 16.6 ± 0.39 μs 0.712 ± 0.035
saxpy/static workgroup=(1024,)/Float32/65536 0.0952 ± 0.011 ms 0.0378 ± 0.0058 ms 2.52 ± 0.49
saxpy/static workgroup=(1024,)/Float64/1024 15.1 ± 0.62 μs 21.6 ± 8 μs 0.701 ± 0.26
saxpy/static workgroup=(1024,)/Float64/1048576 1.17 ± 0.18 ms 0.912 ± 0.25 ms 1.28 ± 0.4
saxpy/static workgroup=(1024,)/Float64/16384 29 ± 2.3 μs 29.4 ± 3.9 μs 0.986 ± 0.15
saxpy/static workgroup=(1024,)/Float64/2048 16 ± 0.66 μs 22 ± 1.2 μs 0.729 ± 0.05
saxpy/static workgroup=(1024,)/Float64/256 10.8 ± 9.1 μs 14.2 ± 1.8 μs 0.761 ± 0.65
saxpy/static workgroup=(1024,)/Float64/262144 0.268 ± 0.039 ms 0.231 ± 0.11 ms 1.16 ± 0.57
saxpy/static workgroup=(1024,)/Float64/32768 0.0589 ± 0.05 ms 0.0383 ± 0.0081 ms 1.54 ± 1.3
saxpy/static workgroup=(1024,)/Float64/4096 18.4 ± 8.6 μs 22.7 ± 6.3 μs 0.811 ± 0.44
saxpy/static workgroup=(1024,)/Float64/512 15.9 ± 10 μs 15 ± 9.6 μs 1.06 ± 0.97
saxpy/static workgroup=(1024,)/Float64/64 12.1 ± 0.21 μs 15.8 ± 7.3 μs 0.763 ± 0.35
saxpy/static workgroup=(1024,)/Float64/65536 0.104 ± 0.02 ms 0.0619 ± 0.053 ms 1.68 ± 1.5
time_to_load 0.541 ± 0.0086 s 0.546 ± 0.087 s 0.992 ± 0.16
main 4f49ec0... main / 4f49ec0...
const/@Const/Float32/262144 0 allocs: 0 B 0 allocs: 0 B
const/@Const/Float32/65536 0 allocs: 0 B 0 allocs: 0 B
const/@Const/Float64/262144 0 allocs: 0 B 0 allocs: 0 B
const/@Const/Float64/65536 0 allocs: 0 B 0 allocs: 0 B
const/unmarked/Float32/262144 0 allocs: 0 B 0 allocs: 0 B
const/unmarked/Float32/65536 0 allocs: 0 B 0 allocs: 0 B
const/unmarked/Float64/262144 0 allocs: 0 B 0 allocs: 0 B
const/unmarked/Float64/65536 0 allocs: 0 B 0 allocs: 0 B
dgsem/2D/Float32/fluxdiff/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/2D/Float32/fluxdiff/node E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/fluxdiff/node halfsweep E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/fluxdiff/node halfsweep v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/weak/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/2D/Float32/weak/element private 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/2D/Float32/weak/node localmem E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/weak/node localmem E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/weak/node localmem v-last E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/weak/node localmem v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float32/weak/node recompute E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/fluxdiff/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/2D/Float64/fluxdiff/node E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/fluxdiff/node halfsweep E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/fluxdiff/node halfsweep v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/weak/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/2D/Float64/weak/element private 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/2D/Float64/weak/node localmem E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/weak/node localmem E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/weak/node localmem v-last E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/weak/node localmem v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/2D/Float64/weak/node recompute E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/fluxdiff/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/3D/Float32/fluxdiff/node E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float32/fluxdiff/node halfsweep E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float32/fluxdiff/node halfsweep v-last E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float32/weak/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/3D/Float32/weak/element private 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/3D/Float32/weak/node localmem E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float32/weak/node localmem E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float32/weak/node localmem v-last E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float32/weak/node localmem v-last E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float32/weak/node recompute E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float32/weak/slab E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab double-buffered E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab double-buffered E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab double-buffered v-last E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab double-buffered v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab v-last E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float32/weak/slab v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/fluxdiff/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/3D/Float64/fluxdiff/node E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float64/fluxdiff/node halfsweep E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float64/fluxdiff/node halfsweep v-last E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float64/weak/element 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/3D/Float64/weak/element private 1 allocs: 16 B 1 allocs: 16 B 1
dgsem/3D/Float64/weak/node localmem E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float64/weak/node localmem E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float64/weak/node localmem v-last E=1 1 allocs: 0.0469 kB 1 allocs: 0.0469 kB 1
dgsem/3D/Float64/weak/node localmem v-last E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float64/weak/node recompute E=2 4 allocs: 0.188 kB 4 allocs: 0.188 kB 1
dgsem/3D/Float64/weak/slab E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab double-buffered E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab double-buffered E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab double-buffered v-last E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab double-buffered v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab v-last E=1 1 allocs: 32 B 1 allocs: 32 B 1
dgsem/3D/Float64/weak/slab v-last E=8 1 allocs: 32 B 1 allocs: 32 B 1
launch/3D static workgroup, dynamic ndrange 4 allocs: 0.125 kB 4 allocs: 0.125 kB 1
launch/3D static workgroup, static ndrange 4 allocs: 0.125 kB 4 allocs: 0.125 kB 1
launch/dynamic workgroup, dynamic ndrange 0 allocs: 0 B 0 allocs: 0 B
launch/dynamic workgroup, dynamic ndrange, workgroupsize given 0 allocs: 0 B 0 allocs: 0 B
launch/static workgroup, dynamic ndrange 0 allocs: 0 B 0 allocs: 0 B
launch/static workgroup, static ndrange 0 allocs: 0 B 0 allocs: 0 B
narrow workgroup/masked/W=16 0 allocs: 0 B 0 allocs: 0 B
narrow workgroup/masked/W=4 0 allocs: 0 B 0 allocs: 0 B
narrow workgroup/unmasked/W=16 0 allocs: 0 B 0 allocs: 0 B
narrow workgroup/unmasked/W=4 0 allocs: 0 B 0 allocs: 0 B
partition/dynamic workgroup, dynamic ndrange 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
partition/static workgroup, dynamic ndrange 2 allocs: 32 B 2 allocs: 32 B 1
partition/static workgroup, static ndrange 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float16/1024 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/1048576 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/16384 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/2048 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/256 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float16/262144 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/32768 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/4096 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/512 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float16/64 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float16/65536 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/1024 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/1048576 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/16384 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/2048 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/256 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float32/262144 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/32768 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/4096 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/512 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float32/64 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float32/65536 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/1024 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/1048576 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/16384 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/2048 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/256 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float64/262144 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/32768 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/4096 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/512 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/default/Float64/64 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float64/65536 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/1024 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/1048576 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/16384 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/2048 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/256 0 allocs: 0 B 0 allocs: 0 B
saxpy/static workgroup=(1024,)/Float16/262144 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/32768 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/4096 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/512 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float16/64 0 allocs: 0 B 0 allocs: 0 B
saxpy/static workgroup=(1024,)/Float16/65536 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/1024 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/1048576 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/16384 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/2048 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/256 0 allocs: 0 B 0 allocs: 0 B
saxpy/static workgroup=(1024,)/Float32/262144 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/32768 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/4096 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/512 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float32/64 0 allocs: 0 B 0 allocs: 0 B
saxpy/static workgroup=(1024,)/Float32/65536 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/1024 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/1048576 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/16384 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/2048 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/256 0 allocs: 0 B 0 allocs: 0 B
saxpy/static workgroup=(1024,)/Float64/262144 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/32768 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/4096 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/512 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
saxpy/static workgroup=(1024,)/Float64/64 0 allocs: 0 B 0 allocs: 0 B
saxpy/static workgroup=(1024,)/Float64/65536 3 allocs: 0.0469 kB 3 allocs: 0.0469 kB 1
time_to_load 0.2 k allocs: 11.8 kB 0.2 k allocs: 11.8 kB 1

Benchmark Plots

A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR.
Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).

@maleadt
maleadt force-pushed the tb/pocl-codegen branch 5 times, most recently from 2ee7481 to c155692 Compare October 25, 2026 18:13
@maleadt
maleadt changed the base branch from main to tb/pocl-launch-cache October 25, 2026 18:13
@codecov

codecov Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.05%. Comparing base (35c64db) to head (4f49ec0).

Additional details and impacted files
@@                   Coverage Diff                    @@
##           tb/pocl-launch-cache     #922      +/-   ##
========================================================
+ Coverage                 82.50%   83.05%   +0.54%     
========================================================
  Files                        31       31              
  Lines                      2510     2556      +46     
========================================================
+ Hits                       2071     2123      +52     
+ Misses                      439      433       -6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Every `@kernel` was compiled with `DynamicCheck`, so PoCL's loop over the
work-items had a conditional body. On x86 it is vectorized with masked loads
and stores, and on AArch64 it isn't vectorized at all, which made e.g. an axpy
2x (x86) to 5x (M1) slower than a serial Julia loop.

On the POCL back-end, launches whose workgroups cover the ndrange exactly now
run a separate kernel compiled with `NoDynamicCheck`. Custom iteration spaces
keep the check, as they may map work-items outside of the ndrange. Other
back-ends are unchanged: there the check is cheap, and a second kernel costs
compile time.

A launch only compiles the kernel it runs. With a tuned workgroup size, which
kernel that is depends on the tuned size, so it is predicted from the device's
limits; PoCL's kernels report those too, unless their estimated stack use
limits their workgroup size. If the workgroups tuned for such a kernel don't
cover the ndrange, the launch compiles the checked kernel after all.

The unchecked kernel is launched with the context of the checked one, which
only differs in a type parameter, and looked up by the same code: the kernel
lookup takes the types to compile for as an argument it doesn't specialize on,
and caches the unchecked kernel separately, so that launches alternating
between both kernels find either on the fast path. The host code
launching a kernel is thus compiled once per kernel, and choosing the kernel
adds 12-21M instructions (3-6 ms) to the first launch of a kernel on a Xeon
E3-1245 v6. Kernels that PoCL compiles faster without the check, like a
reduction, launch for the first time 12-15 ms sooner.
Julia marks the multiplication and addition of `muladd` as contractable, but
the SPIR-V back-end can only carry that with `SPV_KHR_float_controls2`, which
PoCL doesn't accept. PoCL thus computed every `muladd` as a separate
multiplication and addition, which made a chain of dependent `muladd`s 2.6x
(x86) to 4x (M1) slower than in Julia.

As a stopgap, compile `muladd` of `Float32` and `Float64` as `fma` when the
host CPU, which PoCL's CPU device runs on, has hardware FMA: on x86 with the
`fma` feature, and always on AArch64. The overlays live in a separate method
table that only jobs with the new `fuse_muladd` compiler parameter use, so the
choice is part of the cache key.
@maleadt
maleadt added this pull request to stack #926 October 26, 2026 09:51
@maleadt
maleadt merged commit 6b111ac into main Oct 26, 2026
64 of 107 checks passed
@maleadt
maleadt deleted the tb/pocl-codegen branch October 26, 2026 09:51
maleadt added a commit that referenced this pull request Oct 27, 2026
Since #922, `launch_kernel` looks up every kernel through
`kernel_function_as`, which hides the kernel's type from inference so
that PoCL compiles the lookup once for both checked and unchecked
kernels. On other backends, this made every launch dispatch
dynamically. Call `KI.kernel_function` directly there; the branch on
`skip_bounds_check` folds at compile time.

Per-launch host cost, ns (Julia 1.12, best of 200 batches of 64
launches, median of 6 interleaved runs): CUDA (RTX 5080) 1749 -> 1598
for a 1-d kernel and 1838 -> 1635 for a 3-d kernel (raw `@cuda`: 1468);
AMDGPU (gfx1036) 1364 -> 1121 and 1351 -> 1124 (raw `@roc`: 727). A
new kernel's first launch takes 6-17 ms less.
maleadt added a commit that referenced this pull request Oct 27, 2026
Since #922, `launch_kernel` looks up every kernel through
`kernel_function_as`, which hides the kernel's type from inference so
that PoCL compiles the lookup once for both checked and unchecked
kernels. On other backends, this made every launch dispatch
dynamically. Call `KI.kernel_function` directly there; the branch on
`skip_bounds_check` folds at compile time.

Per-launch host cost, ns (Julia 1.12, best of 200 batches of 64
launches, median of 6 interleaved runs): CUDA (RTX 5080) 1749 -> 1598
for a 1-d kernel and 1838 -> 1635 for a 3-d kernel (raw `@cuda`: 1468);
AMDGPU (gfx1036) 1364 -> 1121 and 1351 -> 1124 (raw `@roc`: 727). A
new kernel's first launch takes 6-17 ms less.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant