在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);
}
对于您提到的点积的具体示例,您可以使用
thrust::inner_product
作为起点进行原型制作。我会在原型制作阶段这样做。但请注意我的评论,关于带宽瓶颈的问题。
由于您没有提供有关运行多次点积的外部循环的足够细节,我没有尝试对其进行任何处理。
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造成的,而且你也无法在设备之间实现并发,因为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);
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);
}
float total = 0.0f;
for (int d = 0; d < devs; d++) {
cudaEventSynchronize(event[d]);
total += results[numDevs];
}