Transfer buffer ownership at conversion time instead of a per-launch locked phase - #1050
Merged
Conversation
Every kernel launch currently allocates a Vector{Managed}, pushes each
argument's buffer into it during rocconvert, then runs a with_managed
phase: unique + sort by objectid + lock/unlock every buffer's
ReentrantLock around the launch. CUDA.jl performs the equivalent
stream-ownership tracking inline in pointer conversion with no
per-launch collection phase.
Do the same here: Runtime.Adaptor now carries the launching stream, and
the ROCArray adapt rule transfers ownership inline through
take_ownership_fast! — an unlocked fast path for the common case (buffer
already owned by this stream and already dirty), falling back to the
locked take_ownership! otherwise. The unsynchronized read only races
with patterns that were already racy under the locked protocol.
Measured on MI300A (ROCm 7.2.4, Julia 1.12.6): no-argument kernel-object
launches go from 144 to 0 bytes allocated (4.78 -> 4.49 us), launches
with 3 arrays + 4 scalars from 1232 to 896 bytes (7.35 -> 6.81 us).
with_managed/lock_managed stay for any external users.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
AMDGPU.jl Benchmarks
Details
| Benchmark suite | Current: 56a4329 | Previous: 16f5974 | Ratio |
|---|---|---|---|
amdgpu/synchronization/context/device |
552.5 ns |
755 ns |
0.73 |
amdgpu/synchronization/stream/blocking |
235 ns |
317.5 ns |
0.74 |
amdgpu/synchronization/stream/nonblocking |
317.5 ns |
432.25 ns |
0.73 |
array/accumulate/Float32/1d |
74421 ns |
97092.25 ns |
0.77 |
array/accumulate/Float32/dims=1 |
278646.25 ns |
299718.5 ns |
0.93 |
array/accumulate/Float32/dims=1L |
79666.25 ns |
121446.75 ns |
0.66 |
array/accumulate/Float32/dims=2 |
70461 ns |
130896.75 ns |
0.54 |
array/accumulate/Float32/dims=2L |
2757744.75 ns |
2806321.5 ns |
0.98 |
array/accumulate/Int64/1d |
77241 ns |
113217 ns |
0.68 |
array/accumulate/Int64/dims=1 |
242483.25 ns |
269376.25 ns |
0.90 |
array/accumulate/Int64/dims=1L |
84176.25 ns |
144501.75 ns |
0.58 |
array/accumulate/Int64/dims=2 |
84701.25 ns |
127879.25 ns |
0.66 |
array/accumulate/Int64/dims=2L |
2897086.5 ns |
2948585.75 ns |
0.98 |
array/broadcast |
73251 ns |
64054.75 ns |
1.14 |
array/construct |
2065 ns |
3220 ns |
0.64 |
array/copy |
37335.5 ns |
39815 ns |
0.94 |
array/copyto!/cpu_to_gpu |
111581.5 ns |
94539.5 ns |
1.18 |
array/copyto!/gpu_to_cpu |
120241.5 ns |
95039.5 ns |
1.27 |
array/copyto!/gpu_to_gpu |
59270.75 ns |
42115 ns |
1.41 |
array/iteration/findall/bool |
131351.75 ns |
198036.5 ns |
0.66 |
array/iteration/findall/int |
148492 ns |
207411.5 ns |
0.72 |
array/iteration/findfirst/bool |
145439.25 ns |
167311.75 ns |
0.87 |
array/iteration/findfirst/int |
144769.25 ns |
155156.75 ns |
0.93 |
array/iteration/findmin/1d |
109584 ns |
179444.25 ns |
0.61 |
array/iteration/findmin/2d |
96596.25 ns |
161089.25 ns |
0.60 |
array/iteration/logical |
234700.75 ns |
317088.25 ns |
0.74 |
array/iteration/scalar |
302924 ns |
343653.25 ns |
0.88 |
array/permutedims/2d |
71433.5 ns |
82694.75 ns |
0.86 |
array/permutedims/3d |
71073.5 ns |
82232.25 ns |
0.86 |
array/permutedims/4d |
73741 ns |
84369.75 ns |
0.87 |
array/random/rand/Float32 |
46008.25 ns |
53677.5 ns |
0.86 |
array/random/rand/Int64 |
54220.75 ns |
66274.75 ns |
0.82 |
array/random/rand!/Float32 |
66036 ns |
49389.75 ns |
1.34 |
array/random/rand!/Int64 |
73293.5 ns |
59654.75 ns |
1.23 |
array/random/randn/Float32 |
78516.25 ns |
93112.25 ns |
0.84 |
array/random/randn!/Float32 |
77758.5 ns |
69269.75 ns |
1.12 |
array/reductions/mapreduce/Float32/1d |
86956.25 ns |
141249.25 ns |
0.62 |
array/reductions/mapreduce/Float32/dims=1 |
81748.5 ns |
105462 ns |
0.78 |
array/reductions/mapreduce/Float32/dims=1L |
834063.5 ns |
862533.5 ns |
0.97 |
array/reductions/mapreduce/Float32/dims=2 |
82713.5 ns |
108059.5 ns |
0.77 |
array/reductions/mapreduce/Float32/dims=2L |
143542 ns |
155446.75 ns |
0.92 |
array/reductions/mapreduce/Int64/1d |
86626 ns |
140932 ns |
0.61 |
array/reductions/mapreduce/Int64/dims=1 |
81926.25 ns |
104764.5 ns |
0.78 |
array/reductions/mapreduce/Int64/dims=1L |
830286 ns |
860696 ns |
0.96 |
array/reductions/mapreduce/Int64/dims=2 |
82653.75 ns |
107757 ns |
0.77 |
array/reductions/mapreduce/Int64/dims=2L |
144024.5 ns |
163496.75 ns |
0.88 |
array/reductions/reduce/Float32/1d |
86826 ns |
145184.25 ns |
0.60 |
array/reductions/reduce/Float32/dims=1 |
81471 ns |
105042 ns |
0.78 |
array/reductions/reduce/Float32/dims=1L |
831651 ns |
856898.25 ns |
0.97 |
array/reductions/reduce/Float32/dims=2 |
82553.5 ns |
106659.5 ns |
0.77 |
array/reductions/reduce/Float32/dims=2L |
143539.25 ns |
161129.25 ns |
0.89 |
array/reductions/reduce/Int64/1d |
86686 ns |
141302 ns |
0.61 |
array/reductions/reduce/Int64/dims=1 |
81928.5 ns |
105144.5 ns |
0.78 |
array/reductions/reduce/Int64/dims=1L |
832378.5 ns |
851721 ns |
0.98 |
array/reductions/reduce/Int64/dims=2 |
82113.75 ns |
107839.5 ns |
0.76 |
array/reductions/reduce/Int64/dims=2L |
144034.5 ns |
162989.25 ns |
0.88 |
array/reverse/1d |
45635.75 ns |
53687.25 ns |
0.85 |
array/reverse/1dL |
72140.75 ns |
78502.25 ns |
0.92 |
array/reverse/1dL_inplace |
60095.75 ns |
62952.25 ns |
0.95 |
array/reverse/1d_inplace |
54553.25 ns |
45130 ns |
1.21 |
array/reverse/2d |
49178.25 ns |
55499.75 ns |
0.89 |
array/reverse/2dL |
81723.75 ns |
111822 ns |
0.73 |
array/reverse/2dL_inplace |
92346.25 ns |
74057 ns |
1.25 |
array/reverse/2d_inplace |
44235.5 ns |
49312.25 ns |
0.90 |
array/sorting/1d |
332882.25 ns |
359238.75 ns |
0.93 |
integration/byval/reference |
39380 ns |
41860 ns |
0.94 |
integration/byval/slices=1 |
40310 ns |
42700 ns |
0.94 |
integration/byval/slices=2 |
125802 ns |
133939 ns |
0.94 |
integration/byval/slices=3 |
234613 ns |
239880 ns |
0.98 |
integration/volumerhs |
4999428 ns |
4916200 ns |
1.02 |
kernel/indexing |
34753 ns |
44594.75 ns |
0.78 |
kernel/indexing_checked |
49023 ns |
47782.25 ns |
1.03 |
kernel/launch |
1345 ns |
1785 ns |
0.75 |
kernel/rand |
97633.75 ns |
110472 ns |
0.88 |
latency/import |
1702522741 ns |
2242598207 ns |
0.76 |
latency/precompile |
39534644771 ns |
52102215931 ns |
0.76 |
latency/ttfp |
2328242218 ns |
3070234127 ns |
0.76 |
This comment was automatically generated by workflow using github-action-benchmark.
luraess
approved these changes
Aug 25, 2026
luraess
left a comment
Member
There was a problem hiding this comment.
LGTM. Thanks, maybe here as well, one could trim inline comments to the minimum before merging.
Member
|
Thanks! |
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.
While comparing host-side overheads against CUDA.jl on an MI300A, the kernel-launch path stood out: every launch allocates a
Vector{Managed},rocconvertpushes each argument's buffer into it, and awith_managedphase then doesunique+sort(by=objectid)+ lock/unlock of every buffer'sReentrantLockaround the launch. CUDA.jl performs the equivalent stream-ownership tracking inline in pointer conversion, with no per-launch collection phase.This PR adopts that design:
Runtime.Adaptorcarries the launching stream instead of aManagedcollector (nothingskips ownership handling, for barerocconvertused in reflection/argconvert);ROCArrayadapt rule transfers ownership inline via a newtake_ownership_fast!: an unlocked fast path for the common case (buffer already owned by this stream and already dirty), falling back to the lockedtake_ownership!otherwise. The unsynchronized read only races with usage patterns that were already racy under the locked protocol (concurrent use of one array from tasks on different streams);HIPKernelcall operator drops the vector and thewith_managedwrapper.with_managed/lock_managedremain for external users.Measured on MI300A (gfx942, ROCm 7.2.4, Julia 1.12.6), empty kernel / 3-array+4-scalar kernel:
c .= a .+ bCorrectness validated on hardware including the cross-stream case (array owned by the default stream, used from a second stream via
stream!— the locked slow path still synchronizes the previous stream).For context from the same comparison: with this change the Julia-side launch overhead is essentially at parity with CUDA.jl (burst submission 3.7 vs 3.1 µs, and raw
hipModuleLaunchKernelcosts as much as the full AMDGPU.jl path) — the remaining gaps vs CUDA (launch throughput, 17 µs launch+sync round trip, and 10 µshipMallocFromPoolAsync+hipFreeAsyncpairs vs 1.5 µs on CUDA) are in the HIP runtime, not in this package.