Skip to content

Commit 52aacfe

Browse files
maleadtclaude
andcommitted
Test the device heap
Cover allocations that reach the heap (a `Ref` handed to a `@noinline` callee, a boxed `Any` field), independence of the per-work-item heaps, objects staying valid across later allocations, the reported out-of-memory condition on exhaustion, and that only allocating kernels carry an arena. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fy9hCZkRFuf9fbRQNagCxf
1 parent a601696 commit 52aacfe

1 file changed

Lines changed: 85 additions & 0 deletions

File tree

test/execution.jl

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,88 @@ end
742742
end for _ in 1:2])
743743
@test all(results)
744744
end
745+
746+
############################################################################################
747+
748+
# Device-side allocation: Julia objects that survive optimization are served by a
749+
# per-work-item heap in private memory (src/device/runtime.jl). Top-level definitions, as a
750+
# closure capturing test state would not be a bitstype.
751+
752+
@noinline heap_consume(r::Base.RefValue{Float32}) = r[] + 1f0
753+
754+
struct HeapAnyBox
755+
x::Any
756+
end
757+
@noinline heap_consume(b::HeapAnyBox) = (b.x::Float32) * 2f0
758+
759+
@testset "device heap" begin
760+
# a Ref handed to a @noinline callee is heap-allocated
761+
function ref_kernel(a)
762+
i = get_global_id()
763+
@inbounds a[i] = heap_consume(Ref(a[i]))
764+
return
765+
end
766+
a = oneArray(Float32[41])
767+
@oneapi ref_kernel(a)
768+
@test Array(a) == [42]
769+
770+
# so is a struct whose `Any` field boxes its value
771+
function anybox_kernel(a)
772+
i = get_global_id()
773+
@inbounds a[i] = heap_consume(HeapAnyBox(a[i]))
774+
return
775+
end
776+
a = oneArray(Float32[21])
777+
@oneapi anybox_kernel(a)
778+
@test Array(a) == [42]
779+
780+
# every work-item has its own heap
781+
n = 4096
782+
a = oneArray(Float32.(1:n))
783+
@oneapi items=256 groups=n÷256 ref_kernel(a)
784+
@test Array(a) == Float32.(2:n+1)
785+
786+
# objects stay valid across later allocations by the same work-item
787+
function select_kernel(a, idx)
788+
i = get_global_id()
789+
refs = ntuple(j -> Ref(a[i] * j), Val(4))
790+
@inbounds a[i] = heap_consume(refs[idx])
791+
return
792+
end
793+
a = oneArray(Float32[1, 2, 3, 4])
794+
@oneapi items=4 select_kernel(a, 3)
795+
@test Array(a) == Float32[4, 7, 10, 13]
796+
797+
# nothing is freed: a work-item that allocates more than the heap holds runs out of
798+
# memory, which is reported, and exits without writing its result
799+
function loop_kernel(a, n)
800+
i = get_global_id()
801+
@inbounds x = a[i]
802+
for _ in 1:n
803+
x = heap_consume(Ref(x))
804+
end
805+
@inbounds a[i] = x
806+
return
807+
end
808+
fits = oneAPI.HEAP_SIZE ÷ oneAPI.HEAP_ALIGNMENT
809+
a = oneArray(Float32.(1:256))
810+
@oneapi items=256 loop_kernel(a, fits)
811+
@test Array(a) == Float32.(1:256) .+ fits
812+
a = oneArray(Float32[1])
813+
_, out = @grab_output begin
814+
@oneapi loop_kernel(a, fits + 1)
815+
synchronize()
816+
end
817+
@test occursin("Out of dynamic GPU memory", out)
818+
@test Array(a) == [1]
819+
820+
# only kernels that allocate carry a heap
821+
function plain_kernel(a)
822+
i = get_global_id()
823+
@inbounds a[i] += 1f0
824+
return
825+
end
826+
T = Tuple{oneDeviceVector{Float32, oneAPI.AS.CrossWorkgroup}}
827+
@test occursin(r"%heap\d* = alloca", sprint(io -> oneAPI.code_llvm(io, ref_kernel, T; kernel=true)))
828+
@test !occursin("alloca", sprint(io -> oneAPI.code_llvm(io, plain_kernel, T; kernel=true)))
829+
end

0 commit comments

Comments
 (0)