CUDA: 流和并发
CUDA提供了一致的抽象,来控制并发访问,以便用户最大化、完整地利用单块GPU设备的资源能力。
为了最有效的使用GPU设备,我们希望:
- 单位硬件资源能承载更多的业务请求量
- GPU尽可能满载(前提:关联资源使用量小,时延达标)
为了达到此目的,我们简单分析下CUDA的编程模型。
硬件
Cuda Core是显卡主要的运算单元。
采用Pascal及以上架构的显卡拥有上千的CUDA核心,对应着多组SM
(Streaming Multiprocessor),可共用于一个任务,也可承载不同的运算任务。
从volta架构开始,NVIDIA引入了专为深度学习设计的Tensor Core.
在Turing架构的 Tesla T4中,一共有40个SM, 共享6MB的L2缓存。
一个SM
由64个FP32 算数单元,和8个Tensor Core组成。
对于模型的算子级优化,需要关注较为底层的优化。而对于业务使用场景,既需要算子级优化(选取针对性的计算后端负责),也需要整体视角的分析。
参考链接:
- GPU Architecture(图片取自此处)
软件
CUDA流
CUDA流表示一个GPU操作队列,所有提交给GPU的任务,均指定了执行流。存在一个默认流,也就是stream 0
, 作为默认的队列。提交任务
这个操作本身可以是异步的,对流进行同步化,则意味着需要阻塞cpu线程,直至所有已经提交至该队列中的任务执行完毕。
不同流之间的任务可以借助硬件的不同单元并行执行或者时分并发执行。
CUDA上下文(CUDA Context)
CUDA-Stream/CUDA-Context可以类比于线程/进程:多线程分配调用的GPU资源同属一个CUDA Context下,有自己的隔离的地址空间,资源不能跨Context共享。
默认情况下,一个进程中,在初次调用CUDA runtime软件库中的任何一个API时,会自动初始化当前进程中唯一的一个CUDA上下文。
GPU在同一时刻只能切换到一个context,而默认情况下一个进程有一个上下文,故多个进程使用GPU,无法同时利用硬件。
虚拟化
由于GPU无法同时执行跨CUDA context的任务,导致硬件利用率可能不高,此时可采用一些虚拟化手段。典型的如NVIDIA官方的MPS(Multi Process Service),它实际上启动了一个独立进程去转发所有的任务。采用此方法的坏处是隔离性收到了一定破坏:一旦此进程失效,所有关联任务都将受到影响。
小结
为了充分利用GPU的性能,可以采取一些措施:
- 运行多个并行实例去执行计算;
- 将单个显卡的任务限制在单个进程中,去克服CUDA上下文分时特性带来的资源利用率可能不足的问题。