我有 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.
(强调)
见 here和 here一些额外的有用文档。
现在,为了真正向前推进,我不得不做一些相当困惑的黑客攻击。将来,我希望与开发 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/