Skip to content

3-D launches over odd-sized ranges are slower than 1-D ones on an Intel iGPU #814

Closed

Description

@maleadt

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")
end

On 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

  1. maleadt commented on Oct 5, 2026

    @maleadt
    MemberAuthor

    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=510 is 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 @oneapi copy 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 LinearLaunch wouldn't help, since it tiles the range the same way. Only a flattened launch would, i.e. a 1-D grid over prod(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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions