## 1 CUDA计算是否可以并发?

CUDA程序运行中CPU端一般叫做host,GPU端一般叫做Device。

  1. CPU端对计算资源进行划分的单位为进程,GPU端对计算资源进行划分的单位为context。多核心CPU可以同一时刻多个进程/线程并发执行,GPU一个时刻只允许一个context允许,也就是说GPU端一个时刻只允许被CPU端的一个进程所调用执行(没开启mps的情况下)。
  2. 一个CPU端的一个调用CUDA的进程在GPU上可以有一个或多个context。如果CPU端某个进程调用的cuda代码是runtime api级别的,那么在GPU上该进程对应的context只有一个;如果CPU端进程调用的cuda代码是driver api级别的,那么在GPU上该进程对应的context可以有多个。但是需要注意不论CPU端进行调用的进程所对应的GPU端的context有一个还是多个,GPU上一个时刻只能运行一个context。
  3. 由于GPU在一个时刻只能运行一个context,但是往往一个context的运行并不能使整个GPU计算资源得到利用,所以nvidia推出了mps服务。mps服务可以把同一个用户名下的多个CPU进程所对应的GPU端的多个context融合成一个context,这样一个GPU在开启mps服务后虽然依然是一个时刻只能运行一个context,但是此时的context是多个CPU端进程所对应的多个context融合成的,那么此时一个时刻下就可以运行多个之前的同用户的多个进程所对应的context,这样就能同时刻多个kernel运行,提高GPU的利用效率。
  4. CPU端同一进程下的多个线程同时刻并发调用GPU端的同一个context,此时GPU端在同一个context下是否能同时刻运行多个cuda调用就需要看此时的多个cuda调用是否在同一个steam队列中。一般情况下cuda代码中都是默认使用一个stream队列,因此即使CPU端多线程同时调用cuda最终也只能对请求进行排队运行。如果cuda代码中明确把多线程的调用操作写为不同stream队列,那么便可实现GPU端同时刻下多个kernel的运行。
  5. CPU端一个进程(单进程单线程)内对多个kernel进行异步调用,并且这些kernel的操作都是写在了不同的stream队列中,并且这个cuda代码(.cu文件)使用 --default-stream per-thread 参数进行nvcc的编译。

在GPU端默认一个时刻下只能运行一个kernel,但以下三种情况可以有多个kernel

  1. 开启了mps服务后同用户的多个进程调用可以同时刻在GPU上运行,实现kernel的并发执行;
  2. 一个进程的多个线程调用写明不同stream队列的kernel操作,并且代码使用 --default-stream per-thread 参数进行nvcc的编译。
  3. 一个进程(单线程)异步调用写明不同stream队列的kernel操作,并且代码使用 --default-stream per-thread 参数进行nvcc的编译。

不过这三种方式都有极大的缺点,开启mps服务后显卡只能被单个用户所独占,只有该用户的所有cuda任务结束释放掉所有GPU资源后才可以给其他用户调用该显卡的cuda;将cuda操作写为多个stream队列往往需要额外的编码开销,该种方式比较适合小规模的开发任务,对于一些大型的cuda计算框架难以实现,比如TensorFlow、pytorch等基本都没有实现cuda调用的多stream操作。因为CPU端多进程/多线程调用CUDA并发的一些限制(写明不同的stream队列以及编译时的参数指定等),现在几乎所有的CUDA应用都是单进程单线程调用cuda并且kernel排队运行,也就是说绝大部分的cuda应用都是同一个时刻只有一个cpu线程可以调用GPU端的CUDA(一个时刻只有一个kernel在运行),而一个GPU端的多个CPU进程的调用都是通过分时系统进行的。

不管GPU端是否可以多kernel并发,HOST和Device间的数据传输在一个时刻下都只能是单个操作,也就是说一个时刻下host端向device的数据传输只能是一个cuda调用,但是在host向device传输数据的同时device可以向host同时传输数据,同理同一时刻下device向host传输数据也只能是以cuda操作,不论GPU端的cuda操作是否有多个stream队列,单向的数据传输都是不能并发的。

CPU端同时刻可以运行同进程的多个线程,GPU端同时刻只能运行一个context。在mps没有开启的情况下,如果GPU端一个context内的多个kernel操作写在不同stream队列中并且加入特定编译参数才可以实现GPU端的多kernel并发。

参考