Skip to content

POCL: cheaper host kernel launches - #941

Open
maleadt wants to merge 7 commits into
tb/pocl-host-kernel-benchfrom
tb/pocl-launch-cost-906
Open

maleadt wants to merge 7 commits into
tb/pocl-host-kernel-benchfrom
tb/pocl-launch-cost-906

Conversation

@maleadt

@maleadt maleadt commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Stacked on #906.

The KernelInterface size-check optimization is in #938, which bumps KernelInterface to 0.4.4.

Reduce PoCL host-kernel launch overhead: return the session by reference, avoid 64-bit divisions in launch geometry, tune work-group sizes once, and sample short-launch timings. Unsplit launches on the calling thread call PoCL directly, avoiding launch materialization. Sharing argument generation and conversion between queue and host-kernel paths reduces first-launch compilation.

Inline launches (icount.jl): median ns over 6 runs, #906 → this PR with #938 (Julia 1.12.7, ThreadTeams 1.3.0):

Workload Xeon E3-1245 v6, -t 1 Xeon E3-1245 v6, -t 4,0 Ryzen 9 5950X, -t 1 Ryzen 9 5950X, -t 16,0
1 element 155.8 → 112.4 156.1 → 112.8 100.4 → 72.2 96.1 → 72.5
64 elements 175.0 → 115.2 220.8 → 136.4 101.7 → 74.3 116.6 → 80.8
store 4356 402.8 → 354.9 450.1 → 374.5 250.9 → 226.1 268.6 → 248.1
axpy 1e3 215.9 → 161.8 340.6 → 265.4 136.3 → 112.2 216.7 → 182.8
2-d 30² 342.1 → 272.5 456.9 → 366.6 203.4 → 175.9 285.8 → 248.2

Trivial launch on the Xeon: 1706 → 1401 instructions, 633 → 459 cycles.

Without #938, a launch on the Xeon is ~31 instructions, ~24 cycles (4–6 ns) slower (e.g. trivial launch: 1432 instructions, 486 cycles, 119.4 ns).

First launches in a fresh process (Ryzen 9950X, -t 4, median of 3, ms): inline 667 → 573, stencil 795 → 617, axpy 237 → 161. Subsequent new kernels are unchanged (65–70 ms each).

Trade-off: after a sample below 1 µs, time one in 16 launches. Detecting longer work within a grid can lag by 16 launches; work varying with that period can evade sampling entirely. Always time grids with no estimate or a changed work-group count.

Avoid repeated struct copies and store-forwarding stalls. A trivial launch
on a Xeon E3-1245 v6 drops from 602 to 566 cycles.
Use shifts and masks for power-of-two work-group sizes; skip division by
the extent in preliminary partitions. This reduces repeated divisions in
partitioning, coverage checks and PoCL tuning. On a Xeon E3-1245 v6, where
64-bit division costs tens of cycles, a trivial launch drops from 566 to
493 cycles.
Reuse the predicted size and coverage result when the backend confirms
the prediction. PoCL checks that kernel and device limits match. Avoiding
repeated tuning and coverage checks saves about 85 instructions per launch.
Use the vector-based helper shared with `launch_converted` to avoid
recompiling the `host_launch_converted` generator for each argument count.
Its expression tuples cost 15-20 ms per compilation, 59 times in an
Oceananigans model's first time step.
Cycle-counter reads cost 40 of a trivial launch's 383 cycles on a Ryzen 9
5950X; on a Xeon E3-1245 v6, they overlap with the launch. After a sample
below 1 µs, time one in 16 launches. Always time grids with no estimate or
a changed work-group count. Detecting longer work within a grid can lag
by 16 launches; work varying with that period can evade sampling entirely.
Share argument conversion between queue and host-kernel paths to avoid
duplicating most per-kernel launch code. Launch inference for the 71
kernels of an Oceananigans time step drops from 5.6 to 3.1 s.
Call PoCL directly for unsplit launches. This avoids the memory traffic
from passing `HostLaunch` by reference to `host_run`.
@maleadt
maleadt force-pushed the tb/pocl-launch-cost-906 branch from 138e60c to 3aa7c96 Compare October 28, 2026 09:31
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

Compared with c10ff8cf4, the latest measured ancestor of the merge base (9 commits before it).
The environment differs from the baseline's: GPUCompiler 2.18.0 → 2.19.0, KernelInterface 0.4.3 → 0.4.4, LinearAlgebraExt 0.10.3 → 0.1.46.

Times are the minimum time of every benchmark. A change is unusual when it exceeds both three times the benchmark's typical change between consecutive runs on the default branch and a minimum per category (performance 3%, latency 5%, applications 3%, other 3%). A benchmark that starts or stops allocating is listed however few the allocations. A single run can still be a fluke, so rerun before drawing conclusions.

Geometric mean Unusual: slower Unusual: faster Compared
Performance +58.3% 0 0 31
Latency −55.8% 0 0 13
Applications +162.7% 0 0 5

No unusual changes.

All benchmarks
Benchmark Baseline This PR Change
Performance
julia/serial/arithmetic/muladd-reference 655 µs 655 µs +0.0% noise unknown
julia/serial/math/exp-f64-reference 290 µs 284 µs −2.1% noise unknown
julia/serial/narrow/masked-reference 689 µs 708 µs +2.8% noise unknown
pocl/parallel/atomics/histogram 5.13 ms 6.20 ms +20.7% noise unknown
pocl/parallel/groupreduce/add-f64 1.65 ms 13.0 ms +688.2% noise unknown
pocl/parallel/groupscan/add-i32 2.50 ms 12.9 ms +415.8% noise unknown
pocl/parallel/indexing/cartesian3d 73.7 µs 239 µs +224.8% noise unknown
pocl/parallel/indexing/linear3d 81.0 µs 265 µs +227.5% noise unknown
pocl/parallel/indexing/ntuple3d 80.7 µs 230 µs +185.5% noise unknown
pocl/parallel/indexing/ntuple4d 162 µs 970 µs +498.5% noise unknown
pocl/parallel/localmem/transpose-naive 455 µs 3.30 ms +625.8% noise unknown
pocl/parallel/localmem/transpose-tiled 125 µs 416 µs +232.8% noise unknown
pocl/parallel/private/across-barrier 164 µs 1.04 ms +532.7% noise unknown
pocl/parallel/scheduling/default 185 µs 922 µs +398.6% noise unknown
pocl/parallel/scheduling/explicit 181 µs 922 µs +408.4% noise unknown
pocl/parallel/stream/copy/cache 786 ns 689 ns −12.3% noise unknown
pocl/parallel/stream/copy/dram 6.16 ms 6.31 ms +2.4% noise unknown
pocl/parallel/stream/triad/cache 6.84 µs 1.12 µs −83.6% noise unknown
pocl/parallel/stream/triad/dram 9.34 ms 8.51 ms −8.9% noise unknown
pocl/parallel/util/copyto-small 24.0 ns 25.0 ns +4.2% noise unknown
pocl/parallel/util/zeros-large 995 µs 1.04 ms +4.7% noise unknown
pocl/parallel/util/zeros-small 651 ns 579 ns −11.1% noise unknown
pocl/serial/arithmetic/contract 1.91 ms 1.85 ms −3.2% noise unknown
pocl/serial/arithmetic/muladd 764 µs 705 µs −7.7% noise unknown
pocl/serial/const/marked 722 µs 668 µs −7.4% noise unknown
pocl/serial/const/unmarked 922 µs 860 µs −6.7% noise unknown
pocl/serial/math/exp-f64 146 µs 118 µs −19.1% noise unknown
pocl/serial/math/sin-f64 186 µs 164 µs −11.5% noise unknown
pocl/serial/narrow/masked 824 µs 633 µs −23.2% noise unknown
pocl/serial/narrow/unmasked 789 µs 630 µs −20.2% noise unknown
pocl/serial/stream/triad/dram 8.59 ms 8.50 ms −1.1% noise unknown
Latency
host/import/ka 509 ms 591 ms +16.1% noise unknown
host/precompile/ka 44.6 s 46.0 s +3.0% noise unknown
host/startup 132 ms 131 ms −1.1% noise unknown
pocl/parallel/launch/dynamic-default 4.30 µs 161 ns −96.3% noise unknown
pocl/parallel/launch/groupreduce-f64 5.03 µs 1.04 µs −79.4% noise unknown
pocl/parallel/launch/many-args 12.4 µs 666 ns −94.6% noise unknown
pocl/parallel/launch/partial-workgroup 4.19 µs 214 ns −94.9% noise unknown
pocl/serial/compile/groupreduce 1.88 s 1.97 s +4.7% noise unknown
pocl/serial/compile/localmem-barrier 719 ms 749 ms +4.2% noise unknown
pocl/serial/compile/new-kernel 110 ms 133 ms +21.1% noise unknown
pocl/serial/compile/new-workgroupsize 13.6 ms 15.0 ms +10.3% noise unknown
pocl/serial/first-kernel/cold-cache 2.05 s 1.23 s −39.9% noise unknown
pocl/serial/first-kernel/warm-cache 392 ms 438 ms +11.7% noise unknown
Applications
pocl/parallel/dgsem/fluxdiff-node 291 µs 1.97 ms +576.3% noise unknown
pocl/parallel/dgsem/weak-element 203 µs 729 µs +258.4% noise unknown
pocl/parallel/dgsem/weak-node-localmem 148 µs 644 µs +336.8% noise unknown
pocl/parallel/stencil/timestep 7.80 ms 10.4 ms +32.9% noise unknown
pocl/serial/stencil/timestep 11.1 ms 9.91 ms −11.0% noise unknown

History: https://juliagpu.org/perf/?pkg=kernelabstractions

@codecov

codecov Bot commented Oct 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.10390% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.70%. Comparing base (d24bda8) to head (3aa7c96).

Files with missing lines Patch % Lines
src/pocl/compiler/execution.jl 95.00% 2 Missing ⚠️
src/backend_launch.jl 93.33% 1 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                      @@
##           tb/pocl-host-kernel-bench     #941      +/-   ##
=============================================================
+ Coverage                      83.51%   83.70%   +0.18%     
=============================================================
  Files                             31       31              
  Lines                           2881     2902      +21     
=============================================================
+ Hits                            2406     2429      +23     
+ Misses                           475      473       -2     

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

@vchuravy
vchuravy added this pull request to stack #908 October 30, 2026 16:19
Comment thread src/pocl/backend.jl
Comment on lines 375 to 376
# `nextpow(2, n)` for `n >= 1`, inlined
@inline nextpow2(n::Integer) = n <= 1 ? one(n) : one(n) << (8 * sizeof(n) - leading_zeros(n - one(n)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isnt there a version of this in Julia base that we could call with @inline?

Comment thread src/nditeration.jl
Comment on lines +256 to +259
# `fldmod(n, w)` for `w > 0`, without division for powers of two or `w == n`.
# tuning uses powers of two unless range or kernel limits intervene; preliminary
# partitions use `w == n`. repeated divisions cost tens of cycles each on some CPUs.
@inline function fldmod_extent(n::Int, w::Int)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We now have a whole libraries of helper functions. We should centralize them and make sure that they are readable. fldmod_extent is better than cld_items

This branch has not been deployed

No deployments
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.

2 participants