Julia CUDA - 在没有 CPU 的情况下保存中间内核结果

Julia CUDA - Saving intermediate kernel results without CPU

考虑以下 CUDA 内核,它计算二维矩阵每一行的平均值。

using CUDA

function mean!(x, n, out)
    """out = sum(x, dims=2)"""
    row_idx = (blockIdx().x-1) * blockDim().x + threadIdx().x
    for i = 1:n
        @inbounds out[row_idx] += x[row_idx, i]
    end
    out[row_idx] /= n
    return
end
using Test

nrow, ncol = 1024, 10
x = CuArray{Float64, 2}(rand(nrow, ncol))
y = CuArray{Float64, 1}(zeros(nrow))
@cuda threads=256 blocks=4 row_sum!(x, size(x)[2], y)
@test isapprox(y, sum(x, dims=2))  # test passed

还要考虑下面的CUDA内核

function add!(a, b, c)
    """ c = a .+ b """
    i = (blockIdx().x-1) * blockDim().x + threadIdx().x
    c[i] = a[i] + b[i]
    return
end
a = CuArray{Float64, 1}(zeros(nrow))
b = CuArray{Float64, 1}(ones(nrow))
c = CuArray{Float64, 1}(zeros(nrow))
@cuda threads=256 blocks=4 add!(a, b, c)
@test all(c .== a .+ b)  # test passed

现在,假设我想编写另一个使用 mean!() 的中间结果的内核。例如,

function g(x, y)
    """ mean(x, dims=2) + mean(y, dims=2) """
    xrow, xcol = size(x)
    yrow, ycol = size(y)
    
    mean1 = CuArray{Float64, 1}(undef, xrow)
    @cuda threads=256 blocks=4 mean!(x, xcol, mean1)

    mean2 = CuArray{Float64, 1}(zeros(yrow))
    @cuda threads=256 blocks=4 mean!(y, ycol, mean2)

    out = CuArray{Float64, 1}(zeros(yrow))
    @cuda threads=256 blocks=4 add!(mean1, mean2, out)
    return out
end

(当然,g() 在技术上不是内核,因为它 returns 什么的。)

我的问题是 g() 是否“正确”。特别是 g() 在 GPU/CPU 之间传输数据是否浪费时间?

例如,如果我的理解是正确的,可以优化 g() 的一种方法是像初始化 mean1 一样初始化 mean2。这是因为在构造 mean2 时,我们实际上首先在 CPU 上创建 zeros(yrow),然后将其传递给 CuArray 构造函数以复制到 GPU。相反,mean1 被构造但未初始化(由于 undef)因此避免了这种额外的传输。

总而言之,我如何 save/use 中间内核结果,同时尽可能避免 CPU/GPU 之间的数据传输?

您可以直接在 GPU 上生成零数组或向量!

尝试:

CUDA.zeros(Float64, nrow)

一些基准:

julia> @btime CUDA.zeros(Float64, 1000,1000)
  12.600 μs (26 allocations: 1.22 KiB)
1000×1000 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
...

julia> @btime CuArray(zeros(1000,1000))
  3.551 ms (8 allocations: 7.63 MiB)
1000×1000 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
...