Julia:在多个GPU上进行并行CUSPARSE计算

Mic*_*gge 6 parallel-processing asynchronous julia julia-gpu

我有n单独的GPU,每个都存储自己的数据.我想让他们每个人同时进行一组计算.这里的CUDArt文档描述了使用流来异步调用自定义C内核以实现并行化(另请参见此处的其他示例).使用自定义内核,可以通过stream在CUDArt的launch()函数实现中使用参数来实现.但据我所知,CUSPARSE(或CUBLAS)函数没有类似的流规范选项.

这可能与CUSPARSE一起使用,或者如果我想使用多个GPU,我是否只需要深入到C?

修订后的赏金更新

好的,所以,我现在有一个相对不错的解决方案,最后.但是,我确信它可以通过百万种方式得到改善 - 现在它非常黑客.特别是,我喜欢根据我在这个 SO问题中尝试和写过的解决方案的建议(我从来没有正常工作).因此,我很高兴将赏金奖励给任何有进一步想法的人.

Mic*_*gge 4

好吧,所以,我想我终于找到了一些至少效果相对较好的东西。我仍然非常高兴向任何有进一步改进的人提供赏金。特别是,基于我尝试(但失败)实现的设计(如这个SO问题中所述)的改进将是很棒的。但是,如果对此有任何改进或建议,我很乐意提供赏金。

我发现让 CUSPARSE 和 CUBLAS 之类的东西在多个 GPU 上并行化的方法的关键突破是,您需要为每个 GPU 创建一个单独的句柄。例如,来自CUBLAS API 的文档:

应用程序必须通过调用 cublasCreate() 函数来初始化 cuBLAS 库上下文的句柄。然后,显式传递给每个后续的库函数调用。一旦应用程序完成使用库,它必须调用函数 cublasDestory() 来释放与 cuBLAS 库上下文关联的资源。

这种方法允许用户在使用多个主机线程和多个 GPU 时显式控制库设置。例如,应用程序可以使用 cudaSetDevice() 将不同的设备与不同的主机线程关联起来,并且在每个主机线程中,它可以初始化 cuBLAS 库上下文的唯一句柄,该句柄将使用与该主机线程关联的特定设备。然后,使用不同句柄进行的 cuBLAS 库函数调用将自动将计算分派到不同的设备。

(强调已添加)

请参阅此处此处以获取一些其他有用的文档。

现在,为了真正推进这件事,我不得不做一些相当混乱的黑客工作。将来,我希望与开发 CUSPARSE 和 CUBLAS 软件包的人员取得联系,了解如何将其合并到他们的软件包中。但就目前而言,这就是我所做的:

首先,CUSPARSE 和 CUBLAS 包附带了创建句柄的函数。但是,我必须对包进行一些修改才能导出这些函数(以及所需的其他函数和对象类型),以便我可以自己实际访问它们。

具体来说,我添加了CUSPARSE.jl以下内容:

export libcusparse, SparseChar
Run Code Online (Sandbox Code Playgroud)

libcusparse_types.jl以下内容:

export cusparseHandle_t, cusparseOperation_t, cusparseMatDescr_t, cusparseStatus_t
Run Code Online (Sandbox Code Playgroud)

libcusparse.jl以下内容:

export cusparseCreate
Run Code Online (Sandbox Code Playgroud)

以及sparse.jl以下内容:

export getDescr, cusparseop
Run Code Online (Sandbox Code Playgroud)

通过所有这些,我能够获得对cusparseCreate()可用于创建新句柄的函数的功能访问(我不能仅仅使用,CUSPARSE.cusparseCreate()因为该函数依赖于一堆其他函数和数据类型)。从那里,我定义了一个新版本的矩阵乘法运算,我想要它使用一个附加参数 Handle 来将其输入到ccall()CUDA 驱动程序中。下面是完整的代码:

using CUDArt, CUSPARSE  ## note: modified version of CUSPARSE, as indicated above.

N = 10^3;
M = 10^6;
p = 0.1;

devlist = devices(dev->true);
nGPU = length(devlist)

dev_X = Array(CudaSparseMatrixCSR, nGPU)
dev_b = Array(CudaArray, nGPU)
dev_c = Array(CudaArray, nGPU)
Handles = Array(Array{Ptr{Void},1}, nGPU)


for (idx, dev) in enumerate(devlist)
    println("sending data to device $dev")
    device(dev) ## switch to given device
    dev_X[idx] = CudaSparseMatrixCSR(sprand(N,M,p))
    dev_b[idx] = CudaArray(rand(M))
    dev_c[idx] = CudaArray(zeros(N))
    Handles[idx] = cusparseHandle_t[0]
    cusparseCreate(Handles[idx])
end


function Pmv!(
    Handle::Array{Ptr{Void},1},
    transa::SparseChar,
    alpha::Float64,
    A::CudaSparseMatrixCSR{Float64},
    X::CudaVector{Float64},
    beta::Float64,
    Y::CudaVector{Float64},
    index::SparseChar)
    Mat     = A
    cutransa = cusparseop(transa)
    m,n = Mat.dims
    cudesc = getDescr(A,index)
    device(device(A))  ## necessary to switch to the device associated with the handle and data for the ccall 
    ccall(
        ((:cusparseDcsrmv),libcusparse), 

        cusparseStatus_t,

        (cusparseHandle_t, cusparseOperation_t, Cint,
        Cint, Cint, Ptr{Float64}, Ptr{cusparseMatDescr_t},
        Ptr{Float64}, Ptr{Cint}, Ptr{Cint}, Ptr{Float64},
        Ptr{Float64}, Ptr{Float64}), 

        Handle[1],
        cutransa, m, n, Mat.nnz, [alpha], &cudesc, Mat.nzVal,
        Mat.rowPtr, Mat.colVal, X, [beta], Y
    )
end

function test(Handles, dev_X, dev_b, dev_c, idx)
    Pmv!(Handles[idx], 'N',  1.0, dev_X[idx], dev_b[idx], 0.0, dev_c[idx], 'O')
    device(idx-1)
    return to_host(dev_c[idx])
end


function test2(Handles, dev_X, dev_b, dev_c)

    @sync begin
        for (idx, dev) in enumerate(devlist)
            @async begin
                Pmv!(Handles[idx], 'N',  1.0, dev_X[idx], dev_b[idx], 0.0, dev_c[idx], 'O')
            end
        end
    end
    Results = Array(Array{Float64}, nGPU)
    for (idx, dev) in enumerate(devlist)
        device(dev)
        Results[idx] = to_host(dev_c[idx]) ## to_host doesn't require setting correct device first.  But, it is  quicker if you do this.
    end

    return Results
end

## Function times given after initial run for compilation
@time a = test(Handles, dev_X, dev_b, dev_c, 1); ## 0.010849 seconds (12 allocations: 8.297 KB)
@time b = test2(Handles, dev_X, dev_b, dev_c);   ## 0.011503 seconds (68 allocations: 19.641 KB)

# julia> a == b[1]
# true
Run Code Online (Sandbox Code Playgroud)