何时调用cudaDeviceSynchronize?


69

何时cudaDeviceSynchronize真正需要调用该函数?

据我从CUDA文档中了解,CUDA内核是异步的,因此似乎我们应该cudaDeviceSynchronize在每次内核启动后调用。但是,我尝试了相同的代码(训练神经网络),无论有无cudaDeviceSynchronize,除了时间测量之前的代码。我发现我得到了相同的结果,但是速度提高了7-12倍(取决于矩阵大小)。

因此,问题是是否有任何理由需要使用cudaDeviceSynchronize时间测量。

例如:

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

  • 如果我做矩阵乘法

    C = A * B
    D = C * F
    

我应该cudaDeviceSynchronize介于两者之间吗?

从我的实验看来,我没有。

为什么cudaDeviceSynchronize程序会这么慢?


1
一个实例是,如果您的内核中有任何打印语句,则只有在发生同步事件后,缓冲区才会打印。
Daniel B.

Answers:


60

尽管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倍似乎太多了。时间测量可能存在一些问题,或者内核确实非常快,并且显式同步的开销相对于实际计算时间而言是巨大的。


nvcc并不始终保持“除非另有说明,否则只有单个默认GPU流”。我只是调试了一个程序,在该程序中,我将一个内核的冗长计算分解为分段计算,该计算在for()循环中一次启动一个内核。连续的for()循环内核启动将从上一个for()循环内核离开设备端的地方开始。错误是nvcc编译器无法仅从主机代码中看到此错误,而是试图同时启动每个内核。这意味着除第一个内核外,所有内核都在计算垃圾。
opetrenko 2014年

@AleksandrDubinsky请更仔细地阅读我的评论。我非常明确地表示“不总是被nvcc坚持”。然后,我给出了一个使用cuda-gdb追踪的特定错误的示例,该示例正是证明这一点的示例。我绝对会同意,根据Nvidia的文献,这不是CUDA的工作原理……但是我所说的并不是一种意见:这是在调试过程中对它在特定实例中的工作方式的观察。
opetrenko

@opetrenko很抱歉,您不相信您,但是您发现了一个非常关键的错误,或者代码中有其他错误。您是否在SO上发布了问题?
Aleksandr Dubinsky

1
@opetrenko NVCC不是主机代码的编译器。它是一个预处理器,可移交给系统的编译器(gcc)。您对CUDA的许多方面都有误解。而不是发布有关SO的误导性信息并伤害他人,您应该发布有关您不知道的事情或遇到的问题的问题。也许有人可能已经弄清楚了为什么您的代码似乎由于添加了多余的cudaDeviceSynchronize调用而变得固定了。
Aleksandr Dubinsky

1
@ user3667089和其他任何人。可能的情况远比以前更可能是opetrenko和user3667089的问题是由于代码错误引起的,而不是NVCC和CUDA API中的错误。由于缺少可复制的工作示例代码,因此这种争论减少了。
泰森·希尔默

17

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


8

当您希望GPU开始处理某些数据时,通常会执行内核调用。当您这样做时,您的设备(GPU)将开始执行您要求执行的任何操作。但是,与主机(CPU)上的常规顺序程序不同,它将继续执行程序中的下一行代码。cudaDeviceSynchronize使主机(CPU)等待,直到设备(GPU)执行完您已启动的所有线程为止,因此您的程序将像正常的顺序程序一样继续执行。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.