何时调用 cudaDeviceSynchronize?

什么时候真正需要调用 cudaDeviceSynchronize函数。

根据我对 CUDA 文档的理解,CUDA 内核是异步的,所以似乎我们应该在每次内核启动后调用 cudaDeviceSynchronize。然而,我已经尝试了相同的代码(训练神经网络)有和没有任何 cudaDeviceSynchronize,除了一个之前的时间测量。我发现我得到了相同的结果,但是速度在7-12x 之间(取决于矩阵大小)。

因此,问题在于是否有任何理由在时间测量之外使用 cudaDeviceSynchronize

例如:

  • 在使用 cudaMemcpy将数据从 GPU 复制回主机之前是否需要?

  • 如果我做矩阵乘法

    C = A * B
    D = C * F
    

should I put cudaDeviceSynchronize between both?

From my experiment It seems that I don't.

Why does cudaDeviceSynchronize slow the program so much?

92025 次浏览

使用 cudaDeviceSynchronize()是合适的一种情况是当您有多个 cudaStream运行时,您希望让它们交换一些信息。一个真实的例子就是并行退火量子蒙特卡罗法模拟。在这种情况下,我们希望确保每个流在开始相互传递消息之前都已经完成了一组指令的运行并获得了一些结果,否则我们将最终传递垃圾信息。使用这个命令使程序变慢的原因是 cudaDeviceSynchronize()迫使程序等待设备上所有流中以前发出的所有命令完成后再继续(来自 CUDA C 编程指南)。正如你所说,内核执行通常是异步的,所以当 GPU 设备执行内核时,CPU 可以继续处理其他命令,向设备发出更多指令,而不是等待。但是,当您使用这个同步命令时,CPU 将被迫空闲,直到所有 GPU 工作完成,然后再执行其他操作。这种行为在调试时非常有用,因为由于设备代码的异步执行(无论是在一个流中还是多个流中) ,可能会在看似“随机”的时间发生一个 Segfault。cudaDeviceSynchronize()将强制程序在继续之前确保流的内核/memcpys 完成,这可以使查找非法访问发生在哪里变得更容易(因为在同步过程中会出现故障)。

虽然 CUDA 内核启动是异步的,但是放置在一个流中的所有与 GPU 相关的任务(这是默认行为)是按顺序执行的。

比如说,

kernel1<<<X,Y>>>(...); // kernel start execution, CPU continues to next statement
kernel2<<<X,Y>>>(...); // kernel is placed in queue and will start after kernel1 finishes, CPU continues to next statement
cudaMemcpy(...); // CPU blocks until memory is copied, memory copy starts only after kernel2 finishes

因此在您的示例中,不需要 cudaDeviceSynchronize。但是,对于调试来说,检测哪个内核导致了错误(如果有的话)可能是有用的。

cudaDeviceSynchronize可能会导致一些减速,但7-12倍似乎太多了。可能是时间测量方面的问题,或者内核的速度非常快,显式同步的开销相对于实际的计算时间来说是巨大的。

当希望 GPU 开始处理某些数据时,通常需要执行内核调用。 当你这样做的时候,你的设备(GPU)将开始做你让它做的任何事情。但是,与主机(CPU)上的正常顺序程序不同,CPU 将继续执行程序中的下一行代码。CudaDeviceSynchronize 使主机(CPU)等待,直到设备(GPU)完成所有已启动的线程的执行,因此程序将继续运行,就像它是一个正常的顺序程序一样。

在小型的简单程序中,当你使用 GPU 进行计算时,你通常会使用 cudaDeviceSynchronize,以避免 CPU 请求结果和 GPU 完成计算之间的时间不匹配。使用 cudaDeviceSynchronize 使得编写程序更加容易,但是有一个主要的缺点: 你的 CPU 总是处于空闲状态,而 GPU 进行计算。因此,在高性能计算中,您通常努力让您的 CPU 在等待 GPU 完成时进行计算。

在从内核启动内核之后,您可能还需要调用 cudaDeviceSynchronize()(动态并行性)。

从这篇文章 动态并行 API 及其原理:

如果父内核需要子内核计算出的结果来执行自己的工作,那么它必须确保子网格在继续执行之前已经完成执行,方法是使用 cudaDeviceSynchronize(void)显式同步。此函数等待调用它的线程块先前启动的所有网格的完成。由于嵌套,它还确保由线程块启动的网格的任何子代都已完成。

...

请注意,在执行内核启动构造时,全局内存的视图是不一致的。这意味着在下面的代码示例中,没有定义子内核是读取并打印值1还是2。为了避免竞态条件,在启动内核之后,但是在显式同步之前,父级不应该写入子级可以读取的内存。

__device__ int v = 0;


__global__ void child_k(void) {
printf("v = %d\n", v);
}


__global__ void parent_k(void) {
v = 1;
child_k <<< 1, 1 >>>> ();
v = 2; // RACE CONDITION
cudaDeviceSynchronize();
}