并行编程实战—CUDA编程的多GPU编程 一、多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编程的一种预期性的开发说明大家有机会自己去实践即可。一般来说大多数的普通开发者可能遇到这种机会的概率不大。