多GPU Cuda计算

时间:2012-03-04 12:38:57

标签: cuda dot-product multi-gpu

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

2 个答案:

答案 0 :(得分:6)

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

因此,从CUDA 4.0开始,您可以从单线程主机程序轻松编程多个GPU。 Here are some slides I presented last year about this

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

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

对于点积的具体示例,您可以使用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);
}

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

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

一旦你有了它的工作,如果你在设备上做的只是点积,你会发现你的带宽限制 - 主要是PCI-e,你也不会得到设备之间的并发,因为推力: :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];
}

答案 1 :(得分:1)

要创建多个线程,可以使用OpenMP或pthreads。为了做你正在谈论的事情,似乎你需要制作并启动两个线程(omp parallel section,或pthread_create),让每个线程完成计算的一部分并将其中间结果存储在单独的进程中 - wIDE变量(回想一下,全局变量在进程的线程之间自动共享,因此原始线程将能够看到两个生成的线程所做的更改)。要使原始线程等待其他线程完成,请同步(使用全局屏障或线程连接操作)并在两个生成的线程完成后将结果合并到原始线程中(如果您要将数组拆分为通过将相应的元素相乘并在一半上执行全局求和减半来计算点积,只需要从两个生成的线程中添加两个中间结果。)

您也可以使用MPI或fork,在这种情况下,可以通过类似于网络编程的方式进行通信...管道/套接字或通过(阻塞)发送和接收的通信和同步。