Skip to content

KernelInterface: sub-group shuffles, votes, reductions and scans - #831

Merged
maleadt merged 6 commits into
mainfrom
vc/ki-subgroup-ops
Oct 12, 2026
Merged

maleadt merged 6 commits into
mainfrom
vc/ki-subgroup-ops

Conversation

@vchuravy

@vchuravy vchuravy commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Sub-group communication for KernelInterface, for kernels written against KI (e.g. Molly's
force kernels) and for kernel languages built on it.

Contract

  • Which work-items form a sub-group is not specified. Three new host queries say when more
    is guaranteed:
    • supports_linear_subgroups(backend): 1-D work-groups, or ones whose x extent is a
      multiple of W, form sub-groups from consecutive work-items, x fastest. CUDA, AMDGPU,
      Metal and PoCL report it; OpenCL runtimes generally don't promise it.
    • supports_independent_subgroups(backend): communication in control flow that is uniform
      per sub-group but not per work-group. PoCL reports false (its sub-group operations are
      work-group barriers).
    • supports_partitionable_subgroups(backend): a sub-group can be partitioned into
      segments that diverge independently: the functions with a width only need the caller's
      segment of width lanes (narrow tiles sharing a sub-group). Implies
      supports_independent_subgroups. CUDA (segment masks), AMDGPU, Intel GPUs and Metal report it. Metal and OpenCL
      have no barrier for part of a sub-group, so on Intel and Apple GPUs the segment barrier is
      a sub-group-scope fence that relies on the hardware executing a sub-group in lock-step
      (validated on M1 and M3 with a stress test of diverging segments). PoCL never reports it:
      its sub-group operations are work-group barriers.
      KI 0.3's "a 1-D work-group of at most W work-items is one sub-group" moves under
      supports_linear_subgroups.
  • Availability: ids, sub_group_barrier and the votes under supports_subgroups; shuffles
    per type under supports_shuffle; the collectives need their type's shuffles or a native
    implementation.
  • get_max_sub_group_size() SHOULD be a constant of the generated code; it is never known
    during inference (pass sub_group_size(backend) from the host).

Device functions

Function Back ends
shfl, shfl_down, shfl_up, shfl_xor MUST, for their native primitive types incl. UInt32; past the width shfl_down/shfl_up return the caller's value; shfl_xor needs mask < W
width variants shfl(val, x, width) etc. fallback via shfl; CUDA's width semantics; only the segment participates with supports_partitionable_subgroups
sub_group_ballot/any/all(pred, width), sub_group_reduce/scan/exclusive_scan(..., width), sub_group_barrier(width) fallbacks via the functions without a width (at width == W), the ballot and the width shuffles; back ends with partitionable sub-groups make them work for a segment alone
sub_group_any/all, sub_group_ballot MUST; ballot bit i-1 iff lane i exists and its predicate holds
sub_group_reduce, sub_group_scan, sub_group_exclusive_scan(op, val, init) fallback via shuffles; lane order, associative op; native overrides must give the documented result, except float +/* may be reassociated

Other primitive types are shuffled as UInt32/UInt64 words, isbits structs packed into
UInt32 words (small fields share one), through an internal hook a back end can implement to
exchange several words at once. supports_shuffle reports transport, not arithmetic.

Not included (no consumer yet, can be added without breaking anything):
sub_group_match_any, sub_group_broadcast.

POCL

Shuffles and votes through SPIRVIntrinsics, native + reductions/scans (and integer
min/max), widths ≤ 64. supports_linear_subgroups = true,
supports_independent_subgroups = supports_partitionable_subgroups = false. Packed structs are
exchanged with the vector shuffles of SPV_INTEL_subgroups, a work-group barrier per up to 16
words instead of per word (4.6 → 1.7 ms for four SVector{4, Float32} shuffles over 4M
work-items). That needs pocl/pocl#2394 in pocl_jll.

Documentation

The KernelInterface manual's Sub-groups section describes the contract and the per-back-end capabilities, Implementing a backend says what a back end implements and when it may claim each capability, and the 0.10 release notes have an entry.

Testing

The KI testsuite records each work-item's sub-group and lane and computes expected results
per observed sub-group, so it runs on back ends without the linear layout; layout and
independence tests are gated on the queries. Float64 is only moved through UInt64 storage
where the device lacks fp64.

The last commit is [TEMP]: Buildkite tests the back ends' vc/ki-subgroup-ops /
tb/ki-subgroup-ops branches (JuliaGPU/CUDA.jl#3331, JuliaGPU/Metal.jl#998,
JuliaGPU/AMDGPU.jl#1133, JuliaGPU/OpenCL.jl#554, JuliaGPU/oneAPI.jl#680).

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark Results

Show table
main 7615cb3... main / 7615cb3...
const/@Const/Float32/262144 0.347 ± 0.019 ms 0.348 ± 0.019 ms 0.996 ± 0.078
const/@Const/Float32/65536 0.116 ± 0.012 ms 0.116 ± 0.012 ms 0.999 ± 0.15
const/@Const/Float64/262144 0.575 ± 0.021 ms 0.603 ± 0.021 ms 0.954 ± 0.048
const/@Const/Float64/65536 0.244 ± 0.012 ms 0.253 ± 0.014 ms 0.964 ± 0.071
const/unmarked/Float32/262144 0.609 ± 0.021 ms 0.61 ± 0.022 ms 0.998 ± 0.05
const/unmarked/Float32/65536 0.181 ± 0.012 ms 0.18 ± 0.015 ms 1 ± 0.1
const/unmarked/Float64/262144 1.09 ± 0.022 ms 1.09 ± 0.018 ms 1 ± 0.026
const/unmarked/Float64/65536 0.319 ± 0.02 ms 0.355 ± 0.02 ms 0.897 ± 0.075
dgsem/2D/Float32/fluxdiff/element 0.53 ± 0.015 ms 0.536 ± 0.019 ms 0.989 ± 0.045
dgsem/2D/Float32/fluxdiff/node E=8 0.692 ± 0.014 ms 0.696 ± 0.014 ms 0.994 ± 0.028
dgsem/2D/Float32/fluxdiff/node halfsweep E=8 0.552 ± 0.016 ms 0.568 ± 0.015 ms 0.971 ± 0.038
dgsem/2D/Float32/fluxdiff/node halfsweep v-last E=8 0.579 ± 0.015 ms 0.583 ± 0.018 ms 0.994 ± 0.041
dgsem/2D/Float32/weak/element 0.172 ± 0.014 ms 0.183 ± 0.015 ms 0.939 ± 0.11
dgsem/2D/Float32/weak/element private 0.181 ± 0.011 ms 0.195 ± 0.013 ms 0.926 ± 0.082
dgsem/2D/Float32/weak/node localmem E=1 0.19 ± 0.022 ms 0.192 ± 0.021 ms 0.992 ± 0.16
dgsem/2D/Float32/weak/node localmem E=8 0.19 ± 0.011 ms 0.201 ± 0.015 ms 0.945 ± 0.091
dgsem/2D/Float32/weak/node localmem v-last E=1 0.251 ± 0.018 ms 0.258 ± 0.019 ms 0.974 ± 0.1
dgsem/2D/Float32/weak/node localmem v-last E=8 0.242 ± 0.022 ms 0.242 ± 0.017 ms 1 ± 0.11
dgsem/2D/Float32/weak/node recompute E=8 0.439 ± 0.015 ms 0.439 ± 0.011 ms 1 ± 0.043
dgsem/2D/Float64/fluxdiff/element 0.801 ± 0.017 ms 0.79 ± 0.019 ms 1.01 ± 0.032
dgsem/2D/Float64/fluxdiff/node E=8 0.736 ± 0.015 ms 0.732 ± 0.012 ms 1.01 ± 0.026
dgsem/2D/Float64/fluxdiff/node halfsweep E=8 0.56 ± 0.027 ms 0.553 ± 0.023 ms 1.01 ± 0.065
dgsem/2D/Float64/fluxdiff/node halfsweep v-last E=8 0.599 ± 0.019 ms 0.595 ± 0.016 ms 1.01 ± 0.043
dgsem/2D/Float64/weak/element 0.232 ± 0.013 ms 0.238 ± 0.019 ms 0.974 ± 0.095
dgsem/2D/Float64/weak/element private 0.204 ± 0.013 ms 0.21 ± 0.016 ms 0.972 ± 0.097
dgsem/2D/Float64/weak/node localmem E=1 0.207 ± 0.025 ms 0.202 ± 0.018 ms 1.02 ± 0.15
dgsem/2D/Float64/weak/node localmem E=8 0.207 ± 0.019 ms 0.203 ± 0.014 ms 1.02 ± 0.11
dgsem/2D/Float64/weak/node localmem v-last E=1 0.26 ± 0.014 ms 0.26 ± 0.019 ms 1 ± 0.09
dgsem/2D/Float64/weak/node localmem v-last E=8 0.26 ± 0.013 ms 0.282 ± 0.019 ms 0.921 ± 0.077
dgsem/2D/Float64/weak/node recompute E=8 0.579 ± 0.018 ms 0.585 ± 0.021 ms 0.989 ± 0.046
dgsem/3D/Float32/fluxdiff/element 1.84 ± 0.014 ms 1.85 ± 0.02 ms 0.994 ± 0.013
dgsem/3D/Float32/fluxdiff/node E=2 3.1 ± 0.017 ms 3.11 ± 0.019 ms 0.998 ± 0.0082
dgsem/3D/Float32/fluxdiff/node halfsweep E=1 1.99 ± 0.01 ms 1.99 ± 0.014 ms 1 ± 0.0087
dgsem/3D/Float32/fluxdiff/node halfsweep v-last E=1 2.22 ± 0.018 ms 2.21 ± 0.014 ms 1.01 ± 0.01
dgsem/3D/Float32/weak/element 0.663 ± 0.019 ms 0.662 ± 0.013 ms 1 ± 0.034
dgsem/3D/Float32/weak/element private 0.64 ± 0.021 ms 0.648 ± 0.034 ms 0.988 ± 0.061
dgsem/3D/Float32/weak/node localmem E=1 0.677 ± 0.014 ms 0.69 ± 0.026 ms 0.98 ± 0.042
dgsem/3D/Float32/weak/node localmem E=2 0.737 ± 0.015 ms 0.732 ± 0.016 ms 1.01 ± 0.031
dgsem/3D/Float32/weak/node localmem v-last E=1 0.982 ± 0.02 ms 0.978 ± 0.024 ms 1 ± 0.032
dgsem/3D/Float32/weak/node localmem v-last E=2 1.01 ± 0.024 ms 1.01 ± 0.044 ms 0.994 ± 0.049
dgsem/3D/Float32/weak/node recompute E=2 2.05 ± 0.015 ms 2.05 ± 0.02 ms 0.999 ± 0.012
dgsem/3D/Float32/weak/slab E=1 2.19 ± 0.015 ms 2.19 ± 0.033 ms 0.999 ± 0.017
dgsem/3D/Float32/weak/slab E=8 4.42 ± 0.021 ms 4.39 ± 0.024 ms 1.01 ± 0.0073
dgsem/3D/Float32/weak/slab double-buffered E=1 1.47 ± 0.036 ms 1.44 ± 0.013 ms 1.02 ± 0.027
dgsem/3D/Float32/weak/slab double-buffered E=8 1.66 ± 0.024 ms 1.72 ± 0.013 ms 0.964 ± 0.016
dgsem/3D/Float32/weak/slab double-buffered v-last E=1 1.46 ± 0.019 ms 1.46 ± 0.025 ms 0.994 ± 0.021
dgsem/3D/Float32/weak/slab double-buffered v-last E=8 1.73 ± 0.022 ms 1.75 ± 0.03 ms 0.986 ± 0.021
dgsem/3D/Float32/weak/slab v-last E=1 2.2 ± 0.015 ms 2.19 ± 0.016 ms 1 ± 0.0099
dgsem/3D/Float32/weak/slab v-last E=8 4.4 ± 0.019 ms 4.41 ± 0.02 ms 0.997 ± 0.0063
dgsem/3D/Float64/fluxdiff/element 1.93 ± 0.018 ms 1.94 ± 0.023 ms 0.996 ± 0.015
dgsem/3D/Float64/fluxdiff/node E=2 3.04 ± 0.023 ms 3.03 ± 0.014 ms 1 ± 0.0088
dgsem/3D/Float64/fluxdiff/node halfsweep E=1 2.1 ± 0.046 ms 2.11 ± 0.048 ms 0.995 ± 0.031
dgsem/3D/Float64/fluxdiff/node halfsweep v-last E=1 2.34 ± 0.048 ms 2.33 ± 0.046 ms 1 ± 0.028
dgsem/3D/Float64/weak/element 0.763 ± 0.018 ms 0.764 ± 0.018 ms 0.998 ± 0.033
dgsem/3D/Float64/weak/element private 0.734 ± 0.016 ms 0.741 ± 0.028 ms 0.99 ± 0.043
dgsem/3D/Float64/weak/node localmem E=1 0.752 ± 0.015 ms 0.766 ± 0.022 ms 0.983 ± 0.035
dgsem/3D/Float64/weak/node localmem E=2 0.788 ± 0.018 ms 0.786 ± 0.017 ms 1 ± 0.032
dgsem/3D/Float64/weak/node localmem v-last E=1 1 ± 0.012 ms 1 ± 0.011 ms 0.998 ± 0.016
dgsem/3D/Float64/weak/node localmem v-last E=2 1.02 ± 0.016 ms 1.03 ± 0.012 ms 0.999 ± 0.019
dgsem/3D/Float64/weak/node recompute E=2 2.25 ± 0.018 ms 2.28 ± 0.032 ms 0.989 ± 0.016
dgsem/3D/Float64/weak/slab E=1 2.23 ± 0.016 ms 2.21 ± 0.012 ms 1.01 ± 0.0091
dgsem/3D/Float64/weak/slab E=8 4.72 ± 0.052 ms 4.67 ± 0.047 ms 1.01 ± 0.015
dgsem/3D/Float64/weak/slab double-buffered E=1 1.48 ± 0.038 ms 1.52 ± 0.014 ms 0.969 ± 0.026
dgsem/3D/Float64/weak/slab double-buffered E=8 1.78 ± 0.023 ms 1.77 ± 0.02 ms 1.01 ± 0.017
dgsem/3D/Float64/weak/slab double-buffered v-last E=1 1.48 ± 0.029 ms 1.65 ± 0.019 ms 0.895 ± 0.02
dgsem/3D/Float64/weak/slab double-buffered v-last E=8 1.82 ± 0.023 ms 1.79 ± 0.013 ms 1.01 ± 0.014
dgsem/3D/Float64/weak/slab v-last E=1 2.24 ± 0.014 ms 2.28 ± 0.022 ms 0.982 ± 0.011
dgsem/3D/Float64/weak/slab v-last E=8 4.72 ± 0.095 ms 4.63 ± 0.02 ms 1.02 ± 0.021
launch/3D static workgroup, dynamic ndrange 19.4 ± 10 μs 20.3 ± 9.4 μs 0.957 ± 0.68
launch/3D static workgroup, static ndrange 14.5 ± 4.9 μs 14.8 ± 7.6 μs 0.984 ± 0.61
launch/dynamic workgroup, dynamic ndrange 21.4 ± 9.4 μs 22.5 ± 9.6 μs 0.953 ± 0.58
launch/dynamic workgroup, dynamic ndrange, workgroupsize given 17 ± 8.3 μs 14 ± 0.85 μs 1.21 ± 0.6
launch/static workgroup, dynamic ndrange 16.5 ± 3.5 μs 24.8 ± 7.3 μs 0.666 ± 0.24
launch/static workgroup, static ndrange 22.8 ± 7.2 μs 25.3 ± 7.5 μs 0.9 ± 0.39
narrow workgroup/masked/W=16 0.771 ± 0.017 ms 0.767 ± 0.016 ms 1 ± 0.03
narrow workgroup/masked/W=4 0.77 ± 0.015 ms 0.773 ± 0.017 ms 0.996 ± 0.029
narrow workgroup/unmasked/W=16 0.77 ± 0.015 ms 0.767 ± 0.014 ms 1 ± 0.027
narrow workgroup/unmasked/W=4 0.768 ± 0.015 ms 0.768 ± 0.015 ms 1 ± 0.028
partition/dynamic workgroup, dynamic ndrange 0.0537 ± 0.02 μs 0.0542 ± 0.02 μs 0.991 ± 0.53
partition/static workgroup, dynamic ndrange 0.0628 ± 0.01 μs 0.0636 ± 0.0099 μs 0.988 ± 0.22
partition/static workgroup, static ndrange 1.35 ± 0.005 ns 1.35 ± 0.007 ns 0.999 ± 0.0064
saxpy/default/Float16/1024 17.2 ± 5.2 μs 17.1 ± 3.3 μs 1.01 ± 0.36
saxpy/default/Float16/1048576 0.556 ± 0.031 ms 0.553 ± 0.03 ms 1.01 ± 0.078
saxpy/default/Float16/16384 24.1 ± 1.3 μs 24.2 ± 0.96 μs 0.996 ± 0.067
saxpy/default/Float16/2048 17.4 ± 1.2 μs 17.6 ± 1.4 μs 0.992 ± 0.11
saxpy/default/Float16/256 25.4 ± 6.8 μs 24.1 ± 7.3 μs 1.05 ± 0.43
saxpy/default/Float16/262144 0.177 ± 0.017 ms 0.171 ± 0.015 ms 1.03 ± 0.14
saxpy/default/Float16/32768 0.0339 ± 0.0017 ms 0.0332 ± 0.0016 ms 1.02 ± 0.072
saxpy/default/Float16/4096 18.7 ± 2.3 μs 19.1 ± 2.8 μs 0.979 ± 0.19
saxpy/default/Float16/512 24.2 ± 7.4 μs 23.9 ± 7 μs 1.01 ± 0.43
saxpy/default/Float16/64 24.6 ± 6.9 μs 24.6 ± 7.5 μs 0.998 ± 0.42
saxpy/default/Float16/65536 0.0509 ± 0.0027 ms 0.0504 ± 0.0034 ms 1.01 ± 0.086
saxpy/default/Float32/1024 17.7 ± 3.3 μs 17.3 ± 2.2 μs 1.03 ± 0.23
saxpy/default/Float32/1048576 0.667 ± 0.065 ms 0.685 ± 0.073 ms 0.973 ± 0.14
saxpy/default/Float32/16384 26.4 ± 1.6 μs 26.2 ± 5.9 μs 1.01 ± 0.23
saxpy/default/Float32/2048 18.4 ± 2.2 μs 18.8 ± 5.9 μs 0.981 ± 0.33
saxpy/default/Float32/256 17.5 ± 1.5 μs 20.3 ± 7.9 μs 0.866 ± 0.35
saxpy/default/Float32/262144 0.222 ± 0.031 ms 0.223 ± 0.025 ms 0.996 ± 0.18
saxpy/default/Float32/32768 0.0367 ± 0.0017 ms 0.0363 ± 0.0023 ms 1.01 ± 0.079
saxpy/default/Float32/4096 19.3 ± 1.7 μs 19.1 ± 1.5 μs 1.01 ± 0.12
saxpy/default/Float32/512 16.9 ± 1.4 μs 15.4 ± 5.7 μs 1.1 ± 0.42
saxpy/default/Float32/64 17.6 ± 6 μs 15.9 ± 6.8 μs 1.11 ± 0.61
saxpy/default/Float32/65536 0.0556 ± 0.0038 ms 0.0542 ± 0.018 ms 1.03 ± 0.36
saxpy/default/Float64/1024 18.2 ± 2.2 μs 16.1 ± 4.1 μs 1.13 ± 0.32
saxpy/default/Float64/1048576 1.4 ± 0.16 ms 1.42 ± 0.13 ms 0.981 ± 0.14
saxpy/default/Float64/16384 0.0368 ± 0.0017 ms 0.0365 ± 0.0017 ms 1.01 ± 0.066
saxpy/default/Float64/2048 15.4 ± 3.6 μs 19.1 ± 4.4 μs 0.805 ± 0.26
saxpy/default/Float64/256 17.2 ± 6.7 μs 22.3 ± 8.2 μs 0.769 ± 0.41
saxpy/default/Float64/262144 0.424 ± 0.078 ms 0.368 ± 0.071 ms 1.15 ± 0.31
saxpy/default/Float64/32768 0.0557 ± 0.019 ms 0.0562 ± 0.0035 ms 0.991 ± 0.35
saxpy/default/Float64/4096 20.6 ± 5.7 μs 21.2 ± 1.2 μs 0.971 ± 0.27
saxpy/default/Float64/512 17.2 ± 1.3 μs 19.4 ± 7.8 μs 0.889 ± 0.36
saxpy/default/Float64/64 24.2 ± 7 μs 22.3 ± 8.7 μs 1.08 ± 0.53
saxpy/default/Float64/65536 0.123 ± 0.013 ms 0.123 ± 0.011 ms 1 ± 0.14
saxpy/static workgroup=(1024,)/Float16/1024 17.6 ± 6.2 μs 22.2 ± 7.4 μs 0.791 ± 0.38
saxpy/static workgroup=(1024,)/Float16/1048576 0.56 ± 0.039 ms 0.562 ± 0.033 ms 0.998 ± 0.091
saxpy/static workgroup=(1024,)/Float16/16384 23.6 ± 1.1 μs 23.8 ± 1.1 μs 0.991 ± 0.063
saxpy/static workgroup=(1024,)/Float16/2048 16.6 ± 5 μs 17.7 ± 1.4 μs 0.939 ± 0.29
saxpy/static workgroup=(1024,)/Float16/256 25 ± 7.2 μs 21.6 ± 13 μs 1.16 ± 0.75
saxpy/static workgroup=(1024,)/Float16/262144 0.175 ± 0.015 ms 0.174 ± 0.018 ms 1.01 ± 0.14
saxpy/static workgroup=(1024,)/Float16/32768 0.0333 ± 0.0016 ms 0.0333 ± 0.0018 ms 0.998 ± 0.072
saxpy/static workgroup=(1024,)/Float16/4096 18.4 ± 1.2 μs 18.8 ± 3.7 μs 0.982 ± 0.2
saxpy/static workgroup=(1024,)/Float16/512 17.8 ± 1.4 μs 19.6 ± 7.2 μs 0.908 ± 0.34
saxpy/static workgroup=(1024,)/Float16/64 17.8 ± 1.8 μs 18.3 ± 7.3 μs 0.973 ± 0.4
saxpy/static workgroup=(1024,)/Float16/65536 0.051 ± 0.0034 ms 0.0501 ± 0.0036 ms 1.02 ± 0.1
saxpy/static workgroup=(1024,)/Float32/1024 23 ± 12 μs 20.8 ± 7.1 μs 1.1 ± 0.67
saxpy/static workgroup=(1024,)/Float32/1048576 0.691 ± 0.076 ms 0.673 ± 0.061 ms 1.03 ± 0.15
saxpy/static workgroup=(1024,)/Float32/16384 26.8 ± 1.9 μs 25.8 ± 5.6 μs 1.04 ± 0.24
saxpy/static workgroup=(1024,)/Float32/2048 17.7 ± 3.1 μs 17.6 ± 2.9 μs 1.01 ± 0.24
saxpy/static workgroup=(1024,)/Float32/256 23.5 ± 8.2 μs 18.6 ± 6.1 μs 1.26 ± 0.6
saxpy/static workgroup=(1024,)/Float32/262144 0.224 ± 0.036 ms 0.219 ± 0.033 ms 1.02 ± 0.23
saxpy/static workgroup=(1024,)/Float32/32768 0.0364 ± 0.011 ms 0.0366 ± 0.0021 ms 0.994 ± 0.29
saxpy/static workgroup=(1024,)/Float32/4096 19.1 ± 2.7 μs 19.1 ± 1.5 μs 0.996 ± 0.16
saxpy/static workgroup=(1024,)/Float32/512 17.6 ± 6.7 μs 16.8 ± 3.7 μs 1.05 ± 0.46
saxpy/static workgroup=(1024,)/Float32/64 23.8 ± 7.2 μs 24.3 ± 7.2 μs 0.979 ± 0.42
saxpy/static workgroup=(1024,)/Float32/65536 0.0556 ± 0.019 ms 0.0411 ± 0.021 ms 1.35 ± 0.83
saxpy/static workgroup=(1024,)/Float64/1024 18.4 ± 5.1 μs 17.8 ± 1.7 μs 1.03 ± 0.31
saxpy/static workgroup=(1024,)/Float64/1048576 1.41 ± 0.14 ms 1.43 ± 0.13 ms 0.981 ± 0.13
saxpy/static workgroup=(1024,)/Float64/16384 0.0364 ± 0.011 ms 0.0362 ± 0.0016 ms 1 ± 0.32
saxpy/static workgroup=(1024,)/Float64/2048 18.6 ± 1.4 μs 19.3 ± 3 μs 0.964 ± 0.17
saxpy/static workgroup=(1024,)/Float64/256 22.3 ± 8.2 μs 18.9 ± 7.8 μs 1.18 ± 0.65
saxpy/static workgroup=(1024,)/Float64/262144 0.375 ± 0.068 ms 0.37 ± 0.049 ms 1.01 ± 0.23
saxpy/static workgroup=(1024,)/Float64/32768 0.0562 ± 0.0045 ms 0.0561 ± 0.0034 ms 1 ± 0.1
saxpy/static workgroup=(1024,)/Float64/4096 21.3 ± 1.7 μs 21.4 ± 1.4 μs 0.997 ± 0.1
saxpy/static workgroup=(1024,)/Float64/512 17.2 ± 4.3 μs 20.8 ± 7.2 μs 0.829 ± 0.35
saxpy/static workgroup=(1024,)/Float64/64 18.1 ± 2.1 μs 24.4 ± 8.2 μs 0.742 ± 0.26
saxpy/static workgroup=(1024,)/Float64/65536 0.124 ± 0.013 ms 0.121 ± 0.013 ms 1.02 ± 0.16
time_to_load 0.531 ± 0.011 s 0.522 ± 0.004 s 1.02 ± 0.022
main 7615cb3... main / 7615cb3...
const/@Const/Float32/262144 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/@Const/Float32/65536 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/@Const/Float64/262144 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/@Const/Float64/65536 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/unmarked/Float32/262144 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/unmarked/Float32/65536 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/unmarked/Float64/262144 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
const/unmarked/Float64/65536 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
dgsem/2D/Float32/fluxdiff/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/2D/Float32/fluxdiff/node E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/fluxdiff/node halfsweep E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/fluxdiff/node halfsweep v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/weak/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/2D/Float32/weak/element private 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/2D/Float32/weak/node localmem E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/weak/node localmem E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/weak/node localmem v-last E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/weak/node localmem v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float32/weak/node recompute E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/fluxdiff/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/2D/Float64/fluxdiff/node E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/fluxdiff/node halfsweep E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/fluxdiff/node halfsweep v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/weak/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/2D/Float64/weak/element private 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/2D/Float64/weak/node localmem E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/weak/node localmem E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/weak/node localmem v-last E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/weak/node localmem v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/2D/Float64/weak/node recompute E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/fluxdiff/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/3D/Float32/fluxdiff/node E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/fluxdiff/node halfsweep E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/fluxdiff/node halfsweep v-last E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/weak/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/3D/Float32/weak/element private 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/3D/Float32/weak/node localmem E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/weak/node localmem E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/weak/node localmem v-last E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/weak/node localmem v-last E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/weak/node recompute E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float32/weak/slab E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab double-buffered E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab double-buffered E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab double-buffered v-last E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab double-buffered v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab v-last E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float32/weak/slab v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/fluxdiff/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/3D/Float64/fluxdiff/node E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/fluxdiff/node halfsweep E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/fluxdiff/node halfsweep v-last E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/weak/element 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/3D/Float64/weak/element private 10 allocs: 0.219 kB 10 allocs: 0.219 kB 1
dgsem/3D/Float64/weak/node localmem E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/weak/node localmem E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/weak/node localmem v-last E=1 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/weak/node localmem v-last E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/weak/node recompute E=2 13 allocs: 0.391 kB 13 allocs: 0.391 kB 1
dgsem/3D/Float64/weak/slab E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab double-buffered E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab double-buffered E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab double-buffered v-last E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab double-buffered v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab v-last E=1 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
dgsem/3D/Float64/weak/slab v-last E=8 10 allocs: 0.234 kB 10 allocs: 0.234 kB 1
launch/3D static workgroup, dynamic ndrange 9 allocs: 0.219 kB 9 allocs: 0.219 kB 1
launch/3D static workgroup, static ndrange 9 allocs: 0.219 kB 9 allocs: 0.219 kB 1
launch/dynamic workgroup, dynamic ndrange 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
launch/dynamic workgroup, dynamic ndrange, workgroupsize given 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
launch/static workgroup, dynamic ndrange 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
launch/static workgroup, static ndrange 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
narrow workgroup/masked/W=16 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
narrow workgroup/masked/W=4 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
narrow workgroup/unmasked/W=16 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
narrow workgroup/unmasked/W=4 9 allocs: 0.203 kB 9 allocs: 0.203 kB 1
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 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float16/1048576 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/default/Float16/16384 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float16/2048 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float16/256 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/default/Float16/262144 8 allocs: 0.141 kB 12 allocs: 0.25 kB 0.562
saxpy/default/Float16/32768 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float16/4096 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float16/512 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float16/64 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/default/Float16/65536 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/1024 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/1048576 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/default/Float32/16384 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/2048 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/256 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/default/Float32/262144 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/default/Float32/32768 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/4096 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/512 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float32/64 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/default/Float32/65536 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/1024 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/1048576 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/default/Float64/16384 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/2048 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/256 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/default/Float64/262144 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/default/Float64/32768 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/4096 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/512 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/default/Float64/64 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/default/Float64/65536 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/1024 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/1048576 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/static workgroup=(1024,)/Float16/16384 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/2048 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/256 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/static workgroup=(1024,)/Float16/262144 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/static workgroup=(1024,)/Float16/32768 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/4096 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/512 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float16/64 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/static workgroup=(1024,)/Float16/65536 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/1024 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/1048576 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/static workgroup=(1024,)/Float32/16384 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/2048 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/256 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/static workgroup=(1024,)/Float32/262144 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/static workgroup=(1024,)/Float32/32768 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/4096 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/512 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float32/64 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/static workgroup=(1024,)/Float32/65536 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/1024 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/1048576 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/static workgroup=(1024,)/Float64/16384 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/2048 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/256 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/static workgroup=(1024,)/Float64/262144 12 allocs: 0.25 kB 12 allocs: 0.25 kB 1
saxpy/static workgroup=(1024,)/Float64/32768 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/4096 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/512 8 allocs: 0.141 kB 8 allocs: 0.141 kB 1
saxpy/static workgroup=(1024,)/Float64/64 5 allocs: 0.0938 kB 5 allocs: 0.0938 kB 1
saxpy/static workgroup=(1024,)/Float64/65536 8 allocs: 0.141 kB 8 allocs: 0.141 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).

Comment thread src/pocl/device/subgroups.jl Outdated
@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.31%. Comparing base (2c67ccb) to head (7615cb3).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #831      +/-   ##
==========================================
+ Coverage   80.82%   81.31%   +0.48%     
==========================================
  Files          26       27       +1     
  Lines        2227     2280      +53     
==========================================
+ Hits         1800     1854      +54     
+ Misses        427      426       -1     

☔ 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.

Comment thread src/pocl/compiler/compilation.jl Outdated
@vchuravy

vchuravy commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Rebased onto main and addressed the review:

  • SPIRVIntrinsics: require the released 1.3 instead of the deleted vc/subgroup-votes branch, in [sources] and in CI. The OpenCL.jl Buildkite job installs 1.3 rather than the ka-0.10 copy (1.1.4), which lacks the new intrinsics.
  • sub_group_match_any on PoCL: the fallback takes a step per distinct value, which differs between the sub-groups of a work-group, and PoCL needs them to take the same steps. It gave wrong masks with several sub-groups (4 of 20 random trials). Added a PoCL override that loops to a bound that is the same for the whole work-group, and a test with several sub-groups that fails without it.
  • PoCL fallbacks: dropped the reduce/scan loops for PoCL without the replica fix. Main requires pocl_standalone_jll 7.2.2, whose Yggdrasil build carries the same backport of WorkitemLoops: share local memory across region replicas pocl/pocl#2239, so the native collectives are always used. NATIVE_COLLECTIVES and POCL_REPLICA_FIX are gone.
  • 64-bit shuffles: a primitive type the backend doesn't shuffle natively and that has 8 bytes (e.g. Ptr) is now shuffled as a UInt64, so a backend with native 64-bit shuffles does one shuffle instead of two UInt32 ones. Backends without them still split it into two words. 16-byte values become two UInt64 words. A codegen check covers the 64-bit case on PoCL (128-bit integers don't compile with PoCL's SPIR-V back-end at all).
  • Small fixes: supports_shuffle of a type without fields follows UInt32, and the docs on which primitive types are shuffled as words are corrected.

The full test suite passes locally on Julia 1.10 and 1.12.

Still open from the review: Float16/Float64 on PoCL devices without fp16/fp64 are reported as unsupported for shuffles, even though the UInt32-word fallback could handle them.

#830 is based on this branch and needs a rebase.

Comment thread .buildkite/pipeline.yml
Comment thread lib/KernelInterface/src/device.jl Outdated
Comment thread lib/KernelInterface/src/device.jl Outdated
Comment thread lib/KernelInterface/test/interface.jl Outdated
Comment thread lib/KernelInterface/test/interface.jl
Comment thread src/pocl/backend.jl
Comment thread src/pocl/backend.jl
Comment thread src/pocl/compiler/compilation.jl Outdated
Comment thread .buildkite/pipeline.yml Outdated
Comment thread lib/KernelInterface/src/device.jl Outdated
@maleadt
maleadt force-pushed the vc/ki-subgroup-ops branch from d53946a to 0d37f80 Compare October 10, 2026 16:59
maleadt pushed a commit to JuliaGPU/CUDA.jl that referenced this pull request Oct 10, 2026
…roup-ops branch

Test against JuliaGPU/KernelAbstractions.jl#831. Drop this commit (going
back to KernelAbstractions' main branch) once that is merged.

Assisted-by: Claude Code (Opus 5.5)
@maleadt maleadt changed the title KernelInterface: more sub-group communication (shuffles, votes, constant width) KernelInterface: sub-group shuffles, votes, reductions and scans Oct 10, 2026
@maleadt
maleadt force-pushed the vc/ki-subgroup-ops branch from 0d37f80 to b730e69 Compare October 10, 2026 18:31
maleadt pushed a commit to JuliaGPU/CUDA.jl that referenced this pull request Oct 10, 2026
…roup-ops branch

Test against JuliaGPU/KernelAbstractions.jl#831. Drop this commit (going
back to KernelAbstractions' main branch) once that is merged.

Assisted-by: Claude Code (Opus 5.5)
maleadt added a commit to JuliaGPU/Metal.jl that referenced this pull request Oct 10, 2026
Get KernelAbstractions and KernelInterface from KernelAbstractions.jl's `vc/ki-subgroup-ops`
branch instead of `main`. Julia 1.10 ignores [sources], so Buildkite checks out and develops
them there.

Drop this commit once JuliaGPU/KernelAbstractions.jl#831 is merged.

Co-authored-by: Tim Besard <tim@juliahub.com>
maleadt added a commit to JuliaGPU/Metal.jl that referenced this pull request Oct 10, 2026
Implement the sub-group communication of KernelInterface
(JuliaGPU/KernelAbstractions.jl#831):

- `shfl`, `shfl_down`, `shfl_up` and `shfl_xor` map to the SIMD-group shuffles, only for the
  primitive types Metal supports natively, so that other types reach KernelInterface's
  fallbacks (64-bit integers as `UInt32` words, structs field by field). Offsets past the
  SIMD-group width return the work-item's own value, also ones that don't fit in the
  intrinsics' `Int16`.
- `sub_group_ballot` is `simd_ballot` masked to the lanes of the SIMD-group, since MSL leaves
  the upper bits undefined, and `sub_group_any`/`sub_group_all` are derived from it.
- `get_max_sub_group_size` returns the constant `SIMD_WIDTH`, which `kernel_function` checks
  the pipeline's execution width against.
- Sub-groups are reported as linear and independent: Metal doesn't specify the layout, but
  Apple GPUs form SIMD-groups from consecutive threads, which the testsuite checks.

Co-authored-by: Tim Besard <tim@juliahub.com>
maleadt added a commit to JuliaGPU/Metal.jl that referenced this pull request Oct 10, 2026
Get KernelAbstractions and KernelInterface from KernelAbstractions.jl's `vc/ki-subgroup-ops`
branch instead of `main`. Julia 1.10 ignores [sources], so Buildkite checks out and develops
them there.

Drop this commit once JuliaGPU/KernelAbstractions.jl#831 is merged.

Co-authored-by: Tim Besard <tim@juliahub.com>
maleadt added a commit to JuliaGPU/Metal.jl that referenced this pull request Oct 10, 2026
Implement the sub-group communication of KernelInterface
(JuliaGPU/KernelAbstractions.jl#831):

- `shfl`, `shfl_down`, `shfl_up` and `shfl_xor` map to the SIMD-group shuffles, only for the
  primitive types Metal supports natively, so that other types reach KernelInterface's
  fallbacks (64-bit integers as `UInt32` words, structs field by field). Offsets past the
  SIMD-group width return the work-item's own value, also ones that don't fit in the
  intrinsics' `Int16`.
- `sub_group_ballot` is `simd_ballot` masked to the lanes of the SIMD-group, since MSL leaves
  the upper bits undefined, and `sub_group_any`/`sub_group_all` are derived from it.
- `get_max_sub_group_size` returns the constant `SIMD_WIDTH`, which `kernel_function` checks
  the pipeline's execution width against.
- Sub-groups are reported as linear and independent: Metal doesn't specify the layout, but
  Apple GPUs form SIMD-groups from consecutive threads, which the testsuite checks.

Co-authored-by: Tim Besard <tim@juliahub.com>
maleadt added a commit to JuliaGPU/oneAPI.jl that referenced this pull request Oct 10, 2026
Get KernelAbstractions and KernelInterface from JuliaGPU/KernelAbstractions.jl#831, which adds the sub-group operations implemented here. Drop this commit when it is merged.
@maleadt
maleadt force-pushed the vc/ki-subgroup-ops branch from b730e69 to e65d2c8 Compare October 11, 2026 10:01
@maleadt maleadt mentioned this pull request Oct 11, 2026
@maleadt
maleadt force-pushed the vc/ki-subgroup-ops branch from e65d2c8 to 5d12c31 Compare October 12, 2026 07:52
maleadt added a commit to JuliaGPU/Metal.jl that referenced this pull request Oct 12, 2026
Implement the sub-group communication of KernelInterface
(JuliaGPU/KernelAbstractions.jl#831):

- `shfl`, `shfl_down`, `shfl_up` and `shfl_xor` map to the SIMD-group shuffles, only for the
  primitive types Metal supports natively, so that other types reach KernelInterface's
  fallbacks (64-bit integers as `UInt32` words, structs field by field). Offsets past the
  SIMD-group width return the work-item's own value, also ones that don't fit in the
  intrinsics' `Int16`.
- `sub_group_ballot` is `simd_ballot` masked to the lanes of the SIMD-group, since MSL leaves
  the upper bits undefined, and `sub_group_any`/`sub_group_all` are derived from it.
- `get_max_sub_group_size` returns the constant `SIMD_WIDTH`, which `kernel_function` checks
  the pipeline's execution width against.
- Sub-groups are reported as linear and independent: Metal doesn't specify the layout, but
  Apple GPUs form SIMD-groups from consecutive threads, which the testsuite checks.

Co-authored-by: Tim Besard <tim@juliahub.com>
maleadt pushed a commit to JuliaGPU/CUDA.jl that referenced this pull request Oct 12, 2026
…roup-ops branch

Test against JuliaGPU/KernelAbstractions.jl#831. Drop this commit (going
back to KernelAbstractions' main branch) once that is merged.

Assisted-by: Claude Code (Opus 5.5)
@maleadt
maleadt force-pushed the vc/ki-subgroup-ops branch 3 times, most recently from d2339f4 to c46beae Compare October 12, 2026 09:43
Comment thread docs/src/index.md
`GPUCompiler.method_tables` instead.
- `foreach_index(f, A)` runs `f` once per index of the array `A` without writing a kernel out,
and `foreach_index(f, backend, indices)` once per index in a range or `CartesianIndices`.
- KernelInterface 0.4 adds sub-groups: ids, shuffles, votes, reductions and scans, also

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
- KernelInterface 0.4 adds sub-groups: ids, shuffles, votes, reductions and scans, also
- KernelInterface 0.4.1 adds sub-groups: ids, shuffles, votes, reductions and scans, also

@vchuravy vchuravy left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

LGTM

vchuravy and others added 6 commits October 12, 2026 18:19
Add `shfl`, `shfl_up` and `shfl_xor` next to `shfl_down`, their variants within segments of
a `width`, and the votes `sub_group_any`, `sub_group_all` and `sub_group_ballot`. Back ends
implement the shuffles for their native primitive types; other primitive types are shuffled
as `UInt32` or `UInt64` words and `isbits` structs field by field, with `supports_shuffle`
derived accordingly.

Which work-items form a sub-group stays unspecified. Back ends that form them from
consecutive work-items report `supports_linear_subgroups`, and those whose sub-groups can
communicate in control flow that differs between the sub-groups of a work-group report
`supports_independent_subgroups`. The testsuite computes the expected results from the
sub-groups that formed, so that it runs without either guarantee.

The POCL back end implements the shuffles and votes with SPIRVIntrinsics, for widths of up
to 64 so that the ballot covers every lane. PoCL forms sub-groups from consecutive
work-items, but executes sub-group operations as work-group barriers, so it reports linear
but not independent sub-groups.

Co-authored-by: Tim Besard <tim@juliahub.com>
Add `sub_group_reduce`, `sub_group_scan` and `sub_group_exclusive_scan`, in the order of
the lanes, for associative operators that needn't be commutative. The fallbacks are built
on the shuffles. Back ends may implement them natively per operator and type, as long as the
result is the documented one, except that floating-point `+` and `*` may be reassociated.

The POCL back end uses PoCL's native reductions and scans where they have Julia's
semantics: `+` on integers and floats, and `min`/`max` on integers.

Co-authored-by: Tim Besard <tim@juliahub.com>
Structs and tuples were shuffled field by field, a shuffle per primitive
field, so that e.g. an `NTuple{2, Float16}` took two. Pack their fields
into `UInt32` words instead, with fields of less than 4 bytes sharing a
word, and shuffle the words through `shfl_word_tuple`. A backend can
implement that to exchange all words at once: on PoCL, where every
shuffle is a work-group barrier, that would help, once its CPU device
accepts vector shuffles from SPIR-V.

The generated fallbacks also weren't inlined: `@inline` on a generated
function doesn't apply to a body that the generator returns with an
explicit `return`, so shuffling an `SVector` in a larger kernel left a
call, with the value passed through memory. They now mark their bodies
for inlining.
Tiles narrower than a sub-group have to share it, and to be useful they
have to be able to diverge from each other, like CUDA's
`tiled_partition<N>`. Add the votes, collectives and barrier within
segments of `width` lanes (the shuffles already had a `width`), and
`supports_partitionable_subgroups`: whether a sub-group can be
partitioned into segments that diverge independently, so that only the
work-items of a segment have to execute these together. It extends
`supports_independent_subgroups` to segments, and implies it.

The fallbacks build on the functions without a `width`, so that they
are correct wherever the whole sub-group executes them. Backends that
report partitionable sub-groups have to make them work for a segment
alone: CUDA needs a mask of the segment's lanes, other backends a ballot
that only reports active lanes and a barrier that a segment can execute.
OpenCL and Metal have no barrier for part of a sub-group, so there it
takes hardware that executes a sub-group in lock-step, like Intel's and
Apple's GPUs, and a fence. PoCL never reports it, as its sub-group
functions are work-group barriers.

The shuffles with a `width` of other types now shuffle `UInt32` words
with a `width`, so that a backend's shuffles with a `width` handle them.
Every shuffle on PoCL is a work-group barrier, so a struct packed into
words cost a barrier per word: tile kernels shuffling an `SVector` were
several times slower than ones using local memory. Exchange the words
with the vector shuffles of `SPV_INTEL_subgroups`, a barrier per up to
16 words, which PoCL's CPU device accepts with `cl_intel_subgroups`
since pocl/pocl#2394. Four shuffles of an `SVector{4, Float32}` over 4M
work-items take 1.7 ms instead of 4.6 ms.

KernelInterface gets `shuffle_source`, the lane a shuffle reads from,
for backends that exchange the words with a shuffle from a lane.
@maleadt
maleadt force-pushed the vc/ki-subgroup-ops branch from c46beae to 7615cb3 Compare October 12, 2026 16:46
@maleadt
maleadt merged commit d6392b6 into main Oct 12, 2026
108 of 147 checks passed
@maleadt
maleadt deleted the vc/ki-subgroup-ops branch October 12, 2026 19:21
vchuravy pushed a commit that referenced this pull request Oct 12, 2026
* Fix the import conflicts warned about when precompiling on Julia 1.10

`using .POCL` brought in POCL's `@device_code_*` macros, which clash with
KernelAbstractions' own, and POCL's `using LLVM.IR` clashed with its
`context`. Import only the names that are used.

* Reword the 0.10 release notes on KernelInterface sub-groups

KernelInterface 0.3 already had the sub-group ids, `sub_group_barrier` and
`shfl_down`; say what 0.4 adds, and that the layout guarantee moved behind
`supports_linear_subgroups`. Also fix the `cpu=` option's deprecation
version, which said 0.11, and a few typos.

* Raise the GPUToolbox floor, and tag KernelInterface releases

GPUToolbox 3.3.2 requires LLVM 9 or earlier, so with LLVM 10 the effective
lower bound was already 3.3.3. TagBot only tagged the root package; add a job
for lib/KernelInterface, whose 0.2 and 0.3 releases went untagged.

* CI: test the back ends' main branches

The back ends' KernelInterface 0.4 support is merged into their default
branches, so stop testing the sub-group branches (added temporarily in
#831) and the ka-0.10 branches before them.
maleadt added a commit to JuliaGPU/Metal.jl that referenced this pull request Oct 13, 2026
Implement the sub-group communication of KernelInterface
(JuliaGPU/KernelAbstractions.jl#831):

- `shfl`, `shfl_down`, `shfl_up` and `shfl_xor` map to the SIMD-group shuffles, only for the
  primitive types Metal supports natively, so that other types reach KernelInterface's
  fallbacks (64-bit integers as `UInt32` words, structs field by field). Offsets past the
  SIMD-group width return the work-item's own value, also ones that don't fit in the
  intrinsics' `Int16`.
- `sub_group_ballot` is `simd_ballot` masked to the lanes of the SIMD-group, since MSL leaves
  the upper bits undefined, and `sub_group_any`/`sub_group_all` are derived from it.
- `get_max_sub_group_size` returns the constant `SIMD_WIDTH`, which `kernel_function` checks
  the pipeline's execution width against.
- Sub-groups are reported as linear and independent: Metal doesn't specify the layout, but
  Apple GPUs form SIMD-groups from consecutive threads, which the testsuite checks.

Co-authored-by: Tim Besard <tim@juliahub.com>
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.

3 participants