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

标签 asynchronous parallel-processing julia julia-gpu

我有 n独立的 GPU,每个 GPU 存储自己的数据。我想让他们每个人同时执行一组计算。 CUDArt 文档 here描述了使用流异步调用自定义 C 内核以实现并行化(另请参见另一个示例 here)。对于自定义内核,这可以通过使用 stream 来完成。 CUDArt 实现 launch() 中的参数功能。但是,据我所知,CUSPARSE(或 CUBLAS)函数没有类似的流规范选项。

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

修改后的赏金更新

好的,所以,我现在终于有了一个相对不错的解决方案。但是,我相信它可以以一百万种方式进行改进 - 现在它很hacky。特别是,我喜欢按照我在 this 中尝试和写的内容提出解决方案的建议。所以问题(我从来没有正常工作)。因此,我很高兴将赏金奖励给在这里有进一步想法的任何人。

最佳答案

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

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

The application must initialize the handle to the cuBLAS library context by calling the cublasCreate() function. Then, the is explicitly passed to every subsequent library function call. Once the application finishes using the library, it must call the function cublasDestory() to release the resources associated with the cuBLAS library context.

This approach allows the user to explicitly control the library setup when using multiple host threads and multiple GPUs. For example, the application can use cudaSetDevice() to associate different devices with different host threads and in each of those host threads it can initialize a unique handle to the cuBLAS library context, which will use the particular device associated with that host thread. Then, the cuBLAS library function calls made with different handle will automatically dispatch the computation to different devices.



(强调)

herehere一些额外的有用文档。

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

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

具体来说,我添加到CUSPARSE.jl下列:
export libcusparse, SparseChar

libcusparse_types.jl下列:
export cusparseHandle_t, cusparseOperation_t, cusparseMatDescr_t, cusparseStatus_t

libcusparse.jl下列:
export cusparseCreate

并到 sparse.jl下列:
export getDescr, cusparseop

通过所有这些,我能够获得对 cusparseCreate() 的功能访问。可用于创建新句柄的函数(我不能只使用 CUSPARSE.cusparseCreate(),因为该函数依赖于一堆其他函数和数据类型)。从那里,我定义了我想要的矩阵乘法运算的新版本,它带有一个额外的参数句柄,以输入 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

关于asynchronous - Julia:在多个 GPU 上进行并行 CUSPARSE 计算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38293587/

相关文章:

julia - 如何简洁地计算 Julia 数组中行项的差异百分比

dataframe - 如何在 Julia 中加入列?

.NET:如何让后台线程信号主线程数据可用?

javascript - ajax异步删除帖子 - php

c++ - 什么 shared_ptr 策略用于异步方案?

java - 使用 ExecutorService 在具有通过循环传递的不同参数的类中同时执行方法时出现问题

c - 带 MPI 的 Fox 算法

c# - 如果 GetOrAdd 正忙于使用相同的键检索值,它会等待吗?

java - JavaFx 中的 UI 没有响应

package - 如何将 Quandl 包添加到 Julia