Repository navigation
3-D launches over odd-sized ranges are slower than 1-D ones on an Intel iGPU #814
Description
On an Intel iGPU, a memory-bound kernel launched over a 3-D ndrange with odd extents is about 20% slower than the same kernel launched over the flattened range, though both touch the same memory in the same order:
using oneAPI, KernelAbstractions
@kernel function copy_linear!(B, @Const(A))
I = @index(Global, Linear)
@inbounds B[I] = A[I]
end
function gpu_time(f; n = 200)
f(); oneAPI.synchronize()
minimum(@elapsed((f(); oneAPI.synchronize())) for _ in 1:n) * 1e3
end
k = copy_linear!(oneAPIBackend())
for dims in ((256, 256, 256), (255, 257, 129))
A = oneArray(rand(Float32, dims)); B = similar(A)
t3 = gpu_time(() -> k(B, A; ndrange = size(A)))
t1 = gpu_time(() -> k(B, A; ndrange = length(A)))
println("$dims: ndrange=size(A) $(round(t3; digits=2)) ms, ndrange=length(A) $(round(t1; digits=2)) ms")
endOn an Iris Xe (Tiger Lake), over three alternating runs of each version (timings vary by up to 10% between runs):
ndrange=size(A) |
ndrange=length(A) |
|
|---|---|---|
| 256×256×256, KernelAbstractions 0.9.43 | 6.41–7.22 ms | 6.43–7.32 ms |
| 256×256×256, KernelAbstractions 0.10 (#811) | 6.42–6.66 ms | 6.57–6.69 ms |
| 255×257×129, KernelAbstractions 0.9.43 | 3.66–4.15 ms | 3.21–3.56 ms |
| 255×257×129, KernelAbstractions 0.10 (#811) | 3.95–4.16 ms | 3.35–3.43 ms |
So it isn't a regression of the N-d launches from #797: KernelAbstractions 0.9, which launched everything on a 1-D grid but computed Cartesian indices with divisions, shows it too. With 0.10, it doesn't depend on the work-group shape or on the index computation either (255×257×129 only):
0.10, 3-D ndrange |
time |
|---|---|
| tuned work-group size (255×2×1) | 4.15 ms |
| 255×2×1, 255×1×1, 128×4×1, 64×8×1 | 4.18–4.24 ms |
| 32×16×1 | 4.40 ms |
@index(Global, Cartesian) instead of Linear |
4.12 ms |
static ndrange |
4.28 ms |
1-D ndrange = length(A), 512 work-items per group |
3.37 ms |
I don't know the cause. The sub-groups on this GPU are formed linearly (KernelInterface's partial sub-group test passes on it), so it isn't the sub-group layout. What remains is how the hardware or the driver dispatches a 3-D grid of work-groups with such extents. Intel's compute runtime chooses a hardware walk order based on the work-group shape (shared/source/helpers/hw_walk_order.h in intel/compute-runtime), although for Tiger Lake it should be linear.
The question for KernelAbstractions is whether select_launch should prefer a 1-D launch in cases like this, e.g. through a back-end hook, since only the back end can know whether N-d grids cost more. On the GPUs where it was measured for #797, the N-d launch is a clear win (CUDA: 86 → 41 µs for the same copy over 255×257×129), so it can't simply be turned off.
Measured on oneAPI.jl ka-0.10 (JuliaGPU/oneAPI.jl#653) and on the registered oneAPI.jl with KernelAbstractions 0.9.43.
Activity
maleadt commentedon Oct 5, 2026
This turns out not to be about the launch grid. On this GPU, writes that aren't aligned to 64-byte cache lines are about 20% slower, and a row-by-row tiling of a range with an odd first extent makes every SIMD thread's stores straddle cache lines.
Measured on the Iris Xe with 255×257×129 Float32: a 1-D launch with
workgroupsize=510is as slow as the 3-D launch (4.1 ms vs 3.4 ms with 512), a 3-D launch over 256×257×129 is as fast as the flat one (3.5 ms), and a plain@oneapicopy kernel slows down by the same amount when the destination is a view offset by one element (4.4 ms vs 3.4 ms). Offsetting only the source costs nothing, and forcing the SIMD width to 16 or 8 doesn't change it.So preferring a
LinearLaunchwouldn't help, since it tiles the range the same way. Only a flattened launch would, i.e. a 1-D grid overprod(ndrange)with Cartesian indices from divisions (3.6 ms here). That trades the division-free indexing for alignment on one GPU family, which doesn't seem worth a new launch kind, so I'm closing this.Separately: the 0.9 numbers above are invalid. With oneAPI.jl 2.9.2 and KernelAbstractions 0.9.43,
@index(Global, Linear)returns the raw global id, which is wrong when the tiling pads an inner dimension. For this range it skips 32640 elements and writes 32640 past the end. JuliaGPU/oneAPI.jl#653 fixes that.