两次同步
这是正确的统计时间的方法,我们打开Nsight Systems,放大kernel运行那一段可以看到下图:
其中第1和第3个框分别是cuda和torch的GPU warm up过程,这部分没有进行线程同步(上面的黄色块)。
而第2和第4个框就分别是cuda和torch的加法执行过程了,我们可以放大来看看。
可以看出,每执行一次(一个框)都经过了三个步骤:先是调用api(左上角蓝色框),然后执行kernel(下方蓝色框),最后线程同步(右上角黄色框)。
所以最后算出来的时间就是这三个步骤的耗时,也就是下图选中的范围:
时间大概在29us左右,和我们实际代码测出来的也是比较接近的:
其实我们实际想要知道的耗时并不包括api调用和线程同步的时间,但是这部分时间在python端不好去掉,所以就加上了。
第一次同步,第二次不同步
放大每次执行的过程:
可以看出,虽然长的和上一种情况几乎一模一样,但是在api调用完之后,立刻就进行计时了,所以耗时只有8us左右,实际测出来情况也是这样的:
第一次不同步,第二次同步
我们先来看一下实际统计的时间:
很奇怪是不是,第一次运行耗时非常久,那我们可视化看看到底怎么回事:
可以看出,因为第一次开始计时前没有同步线程,所以在GPU warm up调用api完毕后,第一次cuda kernel调用就开始了。然后一直等到warm up执行完毕,才开始执行第一次cuda kernel,然后是线程同步,结束后才结束计时。这个过程非常长,差不多有130us左右。然后第二次开始执行就很正常了,因为kernel结束的同步相当于是下一次执行之前的同步。
两次不同步
先来看看执行情况:
可以看出因为没有任何同步,所有GPU warm up和cuda kernel的api调用全接在一起了,执行也是。所以计时只计算到了每个api调用的时间,差不多在7us左右。
上面四种情况,torch指令情形几乎一样,因此不再赘述。
小结
通过这篇文章,应该可以大致了解PyTorch实现自定义CUDA算子并调用的方法,也能知道怎么正确的测量CUDA程序的耗时。
当然还有一些内容留作今后讲解,比如如何实现PyTorch神经网络的自定义前向和反向传播CUDA算子、如何用TensorFlow调用CUDA算子等等。