Conversation
KernelAbstractions 0.10 builds on KernelInterface, which defines what a back end provides: memory and device management, compiling and launching kernels, and the device-side intrinsics. KernelAbstractions then launches `@kernel` kernels itself on any KernelInterface back end, which replaces Metal's copy of that launch path: partitioning the ndrange, building the kernel's context, and sizing the threadgroups. `MetalBackend` now implements KernelInterface, and Metal depends on it instead of on KernelAbstractions. What KernelAbstractions still needs from a back end moves to an extension: the stack allocation behind `@private`, and the Adapt rule for moving arrays to the CPU. KernelInterface's default `launch_configuration` uses the pipeline's `maxTotalThreadsPerThreadgroup`, as Metal's launch did. `KI.kernel_function` receives the callable unconverted, and keeps it as the kernel's source, which is converted again at every launch, as with `@metal`: the buffers a closure captures are declared to the encoder and kept alive. `KI.launch` passes the kernel arguments on as a tuple, to the same launch function `HostKernel` calls, so kernels with many arguments aren't splatted. It passes Metal's `queue` and `submit` options on, and rejects `threads` and `groups`, which would override the launch geometry that KernelInterface validated. `KI.copyto!` also accepts contiguous views of host arrays, and `adapt(MetalBackend(), x)` moves any array to the GPU, as `adapt(MtlArray, x)` does, rather than only `Array`s. KernelAbstractions now computes indices in 32 bits where they fit, so its conversion tests pass and are no longer skipped. Its test of a kernel with 41 arguments is skipped instead, since a Metal kernel takes at most 31 buffers. Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
Sub-groups are SIMD-groups: implement the sub-group queries, `sub_group_barrier` and `shfl_down`, and report their support to KernelInterface, with `shfl_down` for the types `simd_shuffle_down` supports. The sub-group width that `KI.sub_group_size` promises is 32, which `kernel_function` checks against the pipeline's `threadExecutionWidth`. KernelInterface leaves unspecified how work-items are grouped into sub-groups; Metal forms SIMD-groups from consecutive linear thread indices, so the last one of a threadgroup can be partial, which a test checks. Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
Implement `KI.record_event` with an `MTLSharedEvent` that a command buffer signals after the task's queued work, and `KI.wait_event` by encoding a wait for it in the task's open batch of work, so that the GPU waits instead of the host. `KernelAbstractions.@spawn` uses them to order a new task's work after the work its parent had queued, without synchronizing the parent; the default is a full synchronization. KernelInterface's testsuite checks them once `record_event` returns an event. Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
`KI.versioninfo(MetalBackend())` prints `Metal.versioninfo()`.
Contributor
There was a problem hiding this comment.
Metal Benchmarks
Details
| Benchmark suite | Current: 5d61e33 | Previous: d209fe6 | Ratio |
|---|---|---|---|
array/accumulate/Float32/1d |
389500 ns |
388291 ns |
1.00 |
array/accumulate/Float32/dims=1 |
364708 ns |
350375 ns |
1.04 |
array/accumulate/Float32/dims=1L |
8766209 ns |
8763333 ns |
1.00 |
array/accumulate/Float32/dims=2 |
426333 ns |
428458 ns |
1.00 |
array/accumulate/Float32/dims=2L |
2513625 ns |
2526958 ns |
0.99 |
array/accumulate/Int64/1d |
839666 ns |
846375 ns |
0.99 |
array/accumulate/Int64/dims=1 |
911042 ns |
904042 ns |
1.01 |
array/accumulate/Int64/dims=1L |
9440542 ns |
9422041 ns |
1.00 |
array/accumulate/Int64/dims=2 |
1211167 ns |
1211708 ns |
1.00 |
array/accumulate/Int64/dims=2L |
6427834 ns |
6424250 ns |
1.00 |
array/broadcast |
240834 ns |
228459 ns |
1.05 |
array/construct |
2291 ns |
2291 ns |
1 |
array/permutedims/2d |
338333 ns |
452666 ns |
0.75 |
array/permutedims/3d |
463291 ns |
1027541 ns |
0.45 |
array/permutedims/4d |
616041 ns |
1098416 ns |
0.56 |
array/private/copy |
221959 ns |
226750 ns |
0.98 |
array/private/copyto!/cpu_to_gpu |
218167 ns |
211583 ns |
1.03 |
array/private/copyto!/gpu_to_cpu |
219875 ns |
208917 ns |
1.05 |
array/private/copyto!/gpu_to_gpu |
216666 ns |
220375 ns |
0.98 |
array/private/iteration/findall/bool |
1056584 ns |
1048542 ns |
1.01 |
array/private/iteration/findall/int |
1213084 ns |
1224750 ns |
0.99 |
array/private/iteration/findfirst/bool |
1152916 ns |
1153792 ns |
1.00 |
array/private/iteration/findfirst/int |
1151333 ns |
1156375 ns |
1.00 |
array/private/iteration/findmin/1d |
1189208 ns |
1218292 ns |
0.98 |
array/private/iteration/findmin/2d |
1028292 ns |
1024250 ns |
1.00 |
array/private/iteration/logical |
1653167 ns |
1661084 ns |
1.00 |
array/private/iteration/scalar |
1382375 ns |
1381000 ns |
1.00 |
array/random/rand/Float32 |
438584 ns |
414334 ns |
1.06 |
array/random/rand/Int64 |
519042 ns |
494209 ns |
1.05 |
array/random/rand!/Float32 |
428083 ns |
402500 ns |
1.06 |
array/random/rand!/Int64 |
443792 ns |
431167 ns |
1.03 |
array/random/randn/Float32 |
364625 ns |
388208 ns |
0.94 |
array/random/randn!/Float32 |
355334 ns |
367166 ns |
0.97 |
array/reductions/mapreduce/Float32/1d |
447084 ns |
450000 ns |
0.99 |
array/reductions/mapreduce/Float32/dims=1 |
353959 ns |
349250 ns |
1.01 |
array/reductions/mapreduce/Float32/dims=1L |
623666 ns |
611750 ns |
1.02 |
array/reductions/mapreduce/Float32/dims=2 |
352875 ns |
355334 ns |
0.99 |
array/reductions/mapreduce/Float32/dims=2L |
1236000 ns |
1220750 ns |
1.01 |
array/reductions/mapreduce/Int64/1d |
632709 ns |
628041 ns |
1.01 |
array/reductions/mapreduce/Int64/dims=1 |
634458 ns |
631541 ns |
1.00 |
array/reductions/mapreduce/Int64/dims=1L |
1007958 ns |
1006459 ns |
1.00 |
array/reductions/mapreduce/Int64/dims=2 |
789209 ns |
788791 ns |
1.00 |
array/reductions/mapreduce/Int64/dims=2L |
2194542 ns |
2200959 ns |
1.00 |
array/reductions/reduce/Float32/1d |
437542 ns |
449833 ns |
0.97 |
array/reductions/reduce/Float32/dims=1 |
357542 ns |
356375 ns |
1.00 |
array/reductions/reduce/Float32/dims=1L |
615750 ns |
607625 ns |
1.01 |
array/reductions/reduce/Float32/dims=2 |
245417 ns |
245042 ns |
1.00 |
array/reductions/reduce/Float32/dims=2L |
484875 ns |
482709 ns |
1.00 |
array/reductions/reduce/Int64/1d |
632917 ns |
628250 ns |
1.01 |
array/reductions/reduce/Int64/dims=1 |
637000 ns |
637625 ns |
1.00 |
array/reductions/reduce/Int64/dims=1L |
1006333 ns |
1004292 ns |
1.00 |
array/reductions/reduce/Int64/dims=2 |
270250 ns |
269292 ns |
1.00 |
array/reductions/reduce/Int64/dims=2L |
667583 ns |
665459 ns |
1.00 |
array/shared/copy |
130333 ns |
128833 ns |
1.01 |
array/shared/copyto!/cpu_to_gpu |
37625 ns |
37834 ns |
0.99 |
array/shared/copyto!/gpu_to_cpu |
36959 ns |
37542 ns |
0.98 |
array/shared/copyto!/gpu_to_gpu |
37458 ns |
37583 ns |
1.00 |
array/shared/iteration/findall/bool |
1049125 ns |
1046041 ns |
1.00 |
array/shared/iteration/findall/int |
1221125 ns |
1209208 ns |
1.01 |
array/shared/iteration/findfirst/bool |
975167 ns |
980292 ns |
0.99 |
array/shared/iteration/findfirst/int |
988042 ns |
988542 ns |
1.00 |
array/shared/iteration/findmin/1d |
1052667 ns |
1055334 ns |
1.00 |
array/shared/iteration/findmin/2d |
1027083 ns |
1032833 ns |
0.99 |
array/shared/iteration/logical |
1528750 ns |
1528625 ns |
1.00 |
array/shared/iteration/scalar |
3765.625 ns |
3671.875 ns |
1.03 |
array/sorting/1d |
2096000 ns |
2083750 ns |
1.01 |
array/sorting/2d |
8420542 ns |
8302083 ns |
1.01 |
integration/byval/reference |
1116542 ns |
1117666 ns |
1.00 |
integration/byval/slices=1 |
1119833 ns |
1119833 ns |
1 |
integration/byval/slices=2 |
2029041 ns |
2010584 ns |
1.01 |
integration/byval/slices=3 |
6540208 ns |
6549916 ns |
1.00 |
integration/metaldevrt |
396958 ns |
393166 ns |
1.01 |
kernel/indexing |
214125 ns |
211500 ns |
1.01 |
kernel/indexing_checked |
395500 ns |
394709 ns |
1.00 |
kernel/launch |
2051 ns |
2050.8888888888887 ns |
1.00 |
kernel/rand |
405083 ns |
408041 ns |
0.99 |
latency/import |
1796217750 ns |
1797481542 ns |
1.00 |
latency/precompile |
32871589291 ns |
32287978750 ns |
1.02 |
latency/ttfp |
2291828875 ns |
2252476334 ns |
1.02 |
metal/synchronization/context |
537.2592592592592 ns |
536.4052631578948 ns |
1.00 |
metal/synchronization/stream |
339.58181818181816 ns |
343.70506912442397 ns |
0.99 |
This comment was automatically generated by workflow using github-action-benchmark.
… main branch Neither is registered yet. Julia 1.10 ignores [sources] and Julia 1.11 doesn't support workspaces, so Buildkite sets up the test environment by hand there, and runs the tests in it directly. Drop this commit once both are registered.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
KernelAbstractions 0.10 moves its back-end API into a separate package, KernelInterface. A back end implements KernelInterface: allocating and copying memory, selecting devices, compiling and launching kernels, and the device-side intrinsics. KernelAbstractions then launches
@kernelkernels the same way on every back end (JuliaGPU/KernelAbstractions.jl#801). This PR ports Metal.jl to that model in one step. It replaces #917, which added KernelInterface next to the existing KernelAbstractions 0.9 back end, and thekaintrbranch, which ported an earlier version of it to KernelAbstractions 0.10.MetalBackendnow implements KernelInterface, and Metal depends on KernelInterface instead of KernelAbstractions. That makes the KernelInterface layer usable on its own, without KernelAbstractions' macros:KernelAbstractions becomes a weak dependency. Its extension only provides what KernelAbstractions still needs from a back end: the stack allocation behind
@private, and the rule for moving anMtlArrayto the CPU withadapt. Metal's own copy of the KernelAbstractions launch path goes away: partitioning the ndrange, building the kernel's context, sizing the threadgroups, calling the kernel, and the overrides of KernelAbstractions' index functions.KI.copyto!now also accepts contiguous views of host arrays, andadapt(MetalBackend(), x)moves any array to the GPU, asadapt(MtlArray, x)does, not onlyArrays. Threadgroups are still as large as the pipeline'smaxTotalThreadsPerThreadgroupallows, which is what KernelInterface's defaultlaunch_configurationpicks, so Metal doesn't override it.Kernels behave the same, but they are launched differently. KernelAbstractions now launches them on a 3-D grid, and computes
@indexin 32 bits when the iteration space fits (JuliaGPU/KernelAbstractions.jl#797). So a kernel over a 3-Dndrangedoesn't need divisions to compute its index, which the test suite checks for the generated LLVM IR. On Metal this matters a lot, since 64-bit integer division is slow on Apple GPUs. With a KA kernel that copies aFloat32array, on an M1 (GPU time of the command buffer):@index(Global, Cartesian)@index(Global, Linear)@index(Global, Cartesian)@index(Global, Linear)The kernel times vary by about 10% between runs of this PR. The launch benchmark uses 28 arguments rather than the 40 of CUDA.jl's, because a Metal kernel takes at most 31 buffers, and every argument, the kernel's context and Metal's kernel state each take one.
KernelInterface 0.4 passes the arguments to the back end as a tuple (JuliaGPU/KernelAbstractions.jl#811). Metal has no public launch function that takes a tuple, but calling a
HostKernelforwards its arguments as one to an internal function, andKI.launchcalls that function directly, so nothing is splatted.KI.launchrejectsthreadsandgroups, which would override the launch geometry KernelInterface has validated, but passes Metal's other launch options on:The first commit is the port. The next ones implement parts of KernelInterface that are optional: sub-groups (SIMD-groups, including a partial last one), ordering work across tasks with Metal events for
KernelAbstractions.@spawn, where the waiting task's command buffer waits on the GPU instead of blocking the host, andKI.versioninfo.KernelAbstractions' conversion tests, skipped until now, pass with 32-bit indices. Its test that launches a kernel with 41 arguments is skipped, since a Metal kernel takes at most 31 buffers, one per argument. That limit isn't new, but it now shows in KernelAbstractions' test suite. Unlike CUDA.jl, Metal already converted the arguments twice (once for the types to compile for, and again while encoding them), so KernelAbstractions' generic launch doesn't add a conversion.
This needs a breaking release, since it requires KernelAbstractions 0.10, which isn't registered yet. Until then, the last commit takes KernelAbstractions and KernelInterface from their development branch through
[sources]. Julia 1.10 ignores[sources]and Julia 1.11 doesn't support workspaces, so on Buildkite those two set up the test environment by hand and run the tests in it directly. That commit is dropped before merging.