多GPU Cuda计算

che*_*eng 5 cuda multi-gpu dot-product

我是多GPU编程的新手,我对multi-gpu计算有一些疑问.例如,我们来看点产品示例.我正在运行一个CPU线程,它创建了2个大型数组A [N]和B [N].由于这些阵列的大小,我需要将他们的点积计算分成2个GPU,两个都是Tesla M2050(计算能力2.0).问题是我需要在由我的CPU线程控制的do-loop中多次计算这些点积.每个点积都需要前一个点的结果.我已经阅读了关于创建2个不同的线程来分别控制2个不同的GPU(如例子中的cuda所述),但我不知道如何在它们之间同步和交换数据.还有另一种选择吗?我非常感谢任何帮助/例子.谢谢!

har*_*ism 6

在CUDA 4.0之前,多GPU编程需要多线程CPU编程.这可能具有挑战性,尤其是当您需要在线程或GPU之间进行同步和/或通信时.如果您的所有并行性都在GPU代码中,那么拥有多个CPU线程可能会增加软件的复杂性,而不会超出GPU的性能.

因此,从CUDA 4.0开始,您可以从单线程主机程序轻松编程多个GPU. 以下是我去年提出的一些幻灯片.

编程多个GPU可以这么简单:

int numDevs = 0;
cudaGetNumDevices(&numDevs);
...
for (int d = 0; d < numDevs; d++) {
    cudaSetDevice(d);
    kernel<<<blocks, threads>>>(args);
}
Run Code Online (Sandbox Code Playgroud)

对于点产品的具体示例,您可以将其thrust::inner_product作为起点.我会这样做原型.但最后看到我对带宽瓶颈的评论.

由于您没有提供有关多次运行点积的外循环的足够详细信息,因此我没有尝试对此进行任何操作.

// assume the deviceIDs of the two 2050s are dev0 and dev1.
// assume that the whole vector for the dot product is on the host in h_data
// assume that n is the number of elements in h_vecA and h_vecB.

int numDevs = 0;
cudaGetNumDevices(&numDevs);
...
float result = 0.f;
for (int d = 0; d < numDevs; d++) {
    cudaSetDevice(d);
    device_vector<float> vecA(h_vecA + d*(n/d), h_vecA + (d+1)*(n/d)-1);
    device_vector<float> vecB(h_vecB + d*(n/d), h_vecB + (d+1)*(n/d)-1);
    result += thrust::inner_product(vecA.begin(), vecA.end(), vecB.begin(), 0.f);
}
Run Code Online (Sandbox Code Playgroud)

(我承认如果n不是numDevs的偶数倍,则上面的索引不正确,但我会把它作为读者练习.:)

这很简单,是一个很好的开始.让它先工作,然后优化.

一旦你有它工作,如果你在设备上做的只是点积,你会发现你是带宽限制 - 主要是PCI-e,你也不会得到设备之间的并发,因为thrust :: inner_product是同步的由于回读返回结果..所以你可以使用cudaMemcpyAsync(device_vector构造函数将使用cudaMemcpy).但更容易和更有效的方法是使用"零拷贝" - 直接访问主机内存(也在上面链接的多gpu编程演示中讨论过).由于您所做的只是读取每个值一次并将其添加到总和(并行重用发生在共享内存副本中),您可以直接从主机读取它而不是将其从主机复制到设备,然后读取它来自内核中的设备内存.此外,您希望在每个GPU上异步启动内核,以确保最大的并发性.

你可以这样做:

int bytes = sizeof(float) * n;
cudaHostAlloc(h_vecA, bytes, cudaHostAllocMapped | cudaHostAllocPortable);
cudaHostAlloc(h_vecB, bytes, cudaHostAllocMapped | cudaHostAllocPortable);
cudaHostAlloc(results, numDevs * sizeof(float), cudaHostAllocMapped | cudaHostAllocPortable);
// ... then fill your input arrays h_vecA and h_vecB


for (int d = 0; d < numDevs; d++) {
    cudaSetDevice(d);
    cudaEventCreate(event[d]));
    cudaHostGetDevicePointer(&dptrsA[d], h_vecA, 0);
    cudaHostGetDevicePointer(&dptrsB[d], h_vecB, 0);
    cudaHostGetDevicePointer(&dresults[d], results, 0);
}

...

for (int d = 0; d < numDevs; d++) {
    cudaSetDevice(d);
    int first = d * (n/d);
    int last   = (d+1)*(n/d)-1;
    my_inner_product<<<grid, block>>>(&dresults[d], 
                                      vecA+first, 
                                      vecA+last, 
                                      vecB+first, 0.f);
    cudaEventRecord(event[d], 0);
}

// wait for all devices
float total = 0.0f;
for (int d = 0; d < devs; d++) {
    cudaEventSynchronize(event[d]);
    total += results[numDevs];
}
Run Code Online (Sandbox Code Playgroud)