好的,我认为我终于找到了一些工作得比较好的东西。我仍然很乐意向任何有更多改进的人提供赏金。特别是基于我尝试(但失败)按照
this SO问题描述实现的设计的改进将是很棒的事情。但是,对于此类任何改进或建议,我都很高兴提供赏金。
我发现让CUSPARSE和CUBLAS等工具在多个GPU上并行化的关键突破是需要为每个GPU创建一个单独的处理程序。例如,从CUBLAS API
documentation 可以得到这样的结论:
应用程序必须通过调用cublasCreate()函数来初始化cuBLAS库上下文的句柄。然后,该句柄显式地传递给每个随后的库函数调用。一旦应用程序使用完库,它必须调用cublasDestory()函数释放与cuBLAS库上下文相关联的资源。
这种方法允许用户在使用多个主机线程和多个GPU时显式控制库设置。例如,应用程序可以使用cudaSetDevice()将不同设备与不同主机线程关联,并在每个主机线程中初始化一个唯一的cuBLAS库上下文句柄,该句柄将使用与该主机线程关联的特定设备。然后,使用不同句柄进行的cuBLAS库函数调用将自动将计算分派到不同的设备。
有关更多有用的文档,请参见
此处和
此处。
现在,为了真正推进这个项目,我不得不进行一些相当混乱的黑客攻击。将来,我希望能够与开发CUSPARSE和CUBLAS软件包的人联系,以了解将其纳入软件包的情况。但是,眼下,这就是我所做的:
首先,CUSPARSE和CUBLAS软件包配有创建句柄函数。但是,我必须稍微修改软件包以导出这些函数(以及其他所需的函数和对象类型),以便我自己能够访问它们。
具体而言,在 CUSPARSE.jl 中添加了以下内容:
export libcusparse, SparseChar
to libcusparse_types.jl
the following:
export cusparseHandle_t, cusparseOperation_t, cusparseMatDescr_t, cusparseStatus_t
to libcusparse.jl
the following:
export cusparseCreate
并将以下内容传递给
sparse.jl
:
export getDescr, cusparseop
通过这些,我能够获得对
cusparseCreate()
函数的功能访问权,该函数可用于创建新句柄(我不能仅使用
CUSPARSE.cusparseCreate()
,因为该函数依赖于一堆其他函数和数据类型)。从那里,我定义了一个新版本的矩阵乘法操作,它需要一个额外的参数,即Handle,以向CUDA驱动程序中的
ccall()
提供输入。以下是完整代码:
using CUDArt, CUSPARSE
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)
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))
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])
end
return Results
end
@time a = test(Handles, dev_X, dev_b, dev_c, 1);
@time b = test2(Handles, dev_X, dev_b, dev_c);