一、多GPU编程
在硬件技术发展到现在的情况下,单机发展的极限短期内看不到任何明显提高的可能。但正如现实世界,多生孩子好打架,一个好汉三个帮。一个GPU搞不定,那就两个,三个,直到…能多少就多少。
其实说白了,就是“单线程”不行就上“多线程”,从单机到集群展开大规模的并行处理,而且这种并行处理是从多个维度上的并行。
二、CUDA的支持
对于CUDA来说,作为GPU上层的抽象框架,它应该天然的支持这种情况。毕竟GPU是自己造的,也只有自己才能更了解底层GPU如何管理和通信会更高效。
CUDA可以过主机API,驱动程序框架以及支持GPU硬件本身来支持多GPU编程,其相关的技术点主要有:
- 支持主机线程的CUDA上下文管理
- 支持所有GPU的统一内存寻址
- GPU间的P2P大内存传送
- 更低粒度的P2P的GPU内存加载和存储访问
- 通过进程间通信、NCCL并行归约、MPI及GPU-Direct RDMA的通信(类似DMA)等支持更高层的抽象及支持系统软件
并行编程的难度大家都非常清楚,就是如何有效的管理多个不同GPU间的资源和任务的匹配,重点就在于不同物理空间的CUDA上下文的管理。它涉及到任务的协调、分配以及不同硬件间的通信以及任务资源的同步等等。
三、多GPU下的设备和内存管理
在多GPU编程的场景下,需要重点关注两个方面:
-
设备的管理
多个GPU如何获取,如何发现。CUDA提供了相关的枚举接口cudaGetDeviceCount和cudaGetDeviceProperties等等。同样,在多个GPU间如何进行设备的处理,CUDA也提供了相关的接口如cudaSetDevice。进一步,每个设备都会有自己的流、事件及相关的控制接口。常见的有cudaEventRecord、cudaEventSynchronize及cudaStreamWaitEvent等。 -
内存的管理
其实在所有的设备上编程,不管是CPU、GPU还是ARM、DSP啥的。内存管理都是最重要的环节之一。而对于多GPU编程,就类似于分布式编程,如何在指定的GPU间进行通信就是一个重中之重。也就是P2P内存传输、访问以及其内存数据的一致性。如接口cudaMemcpy、cudaMemcpyDeviceToDevice、cudaMemcpyPeer、cudaMemcpyPeerAsync及cudaMemcpy3DPeerAsync等等。
如何在多个GPU间进行内存的管理(如内存的分配机制、类型等),如何在不同的平台(Win或Linux)上处理IOMMU设备、PCI访问控制服务以及虚拟机制等。这方面在CUDA的最新的文档中,会有专门的细节说明,不是只通过一两个接口就能够描述清楚的。有兴趣的可以参看最新的官方文档。
四、开发方式
谈到多GPU的开发方式,其实和面对分布式编程或者说多核编程的本质其实是类似的。只不过CUDA编程分成了主机和设备两端,所以就形成了CPU端的N与GPU间的N如何处理的问题。
- 一个主机线程操作多个GPU
这是最容易理解的。一个大“总管”管理着多个GPU完成相关的设备、内存以及任务的分配处理和结果处理 - 多个主机线程管理多个GPU,线程与GPU一一映射
这种是并行编程中相对简单的一种机制,即任务并行但任务的间交互很少。抽象来理解,其实就是一个GPU编程的模型。而且同一线程的内存地址空间是共享的,相对简单 - 多个主机进程,但进程与GPU一一映射
这个也好理解,和上面的不同在于它使用进程作为GPU的映射单元。但进程的内存地址空间是独立的,所以其进行卡间通信时复杂。它可能更适合于某些特定场景,如MPS - 多进程多线程管理多GPU
这是相对复杂的情况,即主机通过多个进程,每个进程又包含多个线程,而其中的每个线程与GPU一一映射操作 - 多节点的NVLink连接集群,在系统级别上进行线程、进程与GPU的驱动管理映射
它基于就可以类比为分布式编程了,这也算是多GPU最复杂的情况了。一般情况下普通开发者很难遇到这种场景
五、总结
多GPU编程的环境目前没有,所以就不给相关的例程了。本文算是CUDA编程的一种预期性的开发说明,大家有机会自己去实践即可。一般来说,大多数的普通开发者可能遇到这种机会的概率不大。
223

被折叠的 条评论
为什么被折叠?



