我目前正在通过计算两个向量之间的点积来学习 CUDA 流。成分是一个核函数,它接受向量x和y并返回大小等于块数的向量结果,其中每个块贡献自己的缩减和。
我还有一个宿主函数dot_gpu,它调用内核并将向量结果减少到最终的点积值。
同步版本就是这样做的:
// copy to device
copy_to_device<double>(x_h, x_d, n);
copy_to_device<double>(y_h, y_d, n);
// kernel
double result = dot_gpu(x_d, y_d, n, blockNum, blockSize);
而异步的就像:
double result[numChunks];
for (int i = 0; i < numChunks; i++) {
int offset = i * chunkSize;
// copy to device
copy_to_device_async<double>(x_h+offset, x_d+offset, chunkSize, stream[i]);
copy_to_device_async<double>(y_h+offset, y_d+offset, chunkSize, stream[i]);
// kernel
result[i] = dot_gpu(x_d+offset, y_d+offset, chunkSize, blockNum, blockSize, stream[i]);
}
for (int i = 0; i < numChunks; i++) {
finalResult += result[i];
cudaStreamDestroy(stream[i]);
}
使用流时我的性能越来越差,并试图调查原因。我尝试管道下载、内核调用和上传,但没有结果。
// accumulate the result of each block into a single value
double dot_gpu(const double *x, const double* y, int n, int blockNum, int blockSize, cudaStream_t stream=NULL)
{
double* result = malloc_device<double>(blockNum);
dot_gpu_kernel<<<blockNum, blockSize, blockSize * sizeof(double), stream>>>(x, y, result, n);
#if ASYNC
double* r = malloc_host_pinned<double>(blockNum);
copy_to_host_async<double>(result, r, blockNum, stream);
CudaEvent copyResult;
copyResult.record(stream);
copyResult.wait();
#else
double* r = malloc_host<double>(blockNum);
copy_to_host<double>(result, r, blockNum);
#endif
double dotProduct = 0.0;
for (int i = 0; i < blockNum; i ++) {
dotProduct += r[i];
}
cudaFree(result);
#if ASYNC
cudaFreeHost(r);
#else
free(r);
#endif
return dotProduct;
}
我的猜测是问题出在dot_gpu()函数内部,它不仅调用内核。告诉我是否正确理解了以下流执行
foreach stream {
cudaMemcpyAsync( device[stream], host[stream], ... stream );
LaunchKernel<<<...stream>>>( ... );
cudaMemcpyAsync( host[stream], device[stream], ... stream );
}
主机执行所有三个指令而不会被阻塞,因为 cudaMemcpyAsync 和内核会立即返回(但是在 GPU 上,它们将按顺序执行,因为它们被分配给同一流)。所以主机继续下一个流(即使stream1知道它处于哪个阶段,但谁在乎......它在GPU上做他的工作,对吧?)并再次执行三个指令而不会被阻塞......等等等等。但是,我的代码在dot_gpu()函数内部某处处理下一个流之前阻塞了主机。是因为我正在分配和释放东西,以及将内核返回的数组减少为单个值吗?