如何理解Nvidia英伟达的Multi-GPU多卡通信框架NCCL?

深度学习中常常需要多GPU并行训练,而Nvidia的NCCL库 NVIDIA/nccl 在各大深度学习框架(Caffe/Tensorflow/Torc…
关注者
1,247
被浏览
502,245

10 个回答

NCCL是Nvidia Collective multi-GPU Communication Library的简称,它是一个实现多GPU的collective communication通信(all-gather, reduce, broadcast)库,Nvidia做了很多优化,以在PCIe、Nvlink、InfiniBand上实现较高的通信速度。

下面分别从以下几个方面来介绍NCCL的特点,包括基本的communication primitive、ring-base collectives、NCCL在单机多卡上以及多机多卡实现、最后分享实际使用NCCL的一些经验。

(1)communication primitive

并行任务的通信一般可以分为Point-to-point communication和Collective communication。P2P通信这种模式只有一个sender和一个receiver,实现起来比较简单。第二种Collective communication包含多个sender多个receiver,一般的通信原语包括broadcast,gather,all-gather,scatter,reduce,all-reduce,reduce-scatter,all-to-all等。简单介绍几个常用的操作:

Reduce:从多个sender那里接收数据,最终combine到一个节点上。

All-reduce:从多个sender那里接收数据,最终combine到每一个节点上。

而传统Collective communication假设通信节点组成的topology是一颗fat tree,如下图所示,这样通信效率最高。但实际的通信topology可能比较复杂,并不是一个fat tree。因此一般用ring-based Collective communication。


(2) ring-base collectives

ring-base collectives将所有的通信节点通过首尾连接形成一个单向环,数据在环上依次传输。以broadcast为例, 假设有4个GPU,GPU0为sender将信息发送给剩下的GPU,按照环的方式依次传输,GPU0-->GPU1-->GPU2-->GPU3,若数据量为N,带宽为B,整个传输时间为(K-1)N/B。时间随着节点数线性增长,不是很高效。

下面把要传输的数据分成S份,每次只传N/S的数据量,传输过程如下所示:

GPU1接收到GPU0的一份数据后,也接着传到环的下个节点,这样以此类推,最后花的时间为

S*(N/S/B) + (k-2)*(N/S/B) = N(S+K-2)/(SB) --> N/B,条件是S远大于K,即数据的份数大于节点数,这个很容易满足。所以通信时间不随节点数的增加而增加,只和数据总量以及带宽有关。其它通信操作比如reduce、gather以此类推。

那么在以GPU为通信节点的场景下,怎么构建通信环呢?如下图所示:

单机4卡通过同一个PCIe switch挂载在一棵CPU的场景:

单机8卡通过两个CPU下不同的PCIe switch挂载的场景:


(3)NCCL实现

NCCL实现成CUDA C++ kernels,包含3种primitive operations: Copy,Reduce,ReduceAndCopy。目前NCCL 1.0版本只支持单机多卡,卡之间通过PCIe、NVlink、GPU Direct P2P来通信。NCCL 2.0会支持多机多卡,多机间通过Sockets (Ethernet)或者InfiniBand with GPU Direct RDMA通信。

下图所示,单机内多卡通过PCIe以及CPU socket通信,多机通过InfiniBand通信。

同样,在多机多卡内部,也要构成一个通信环

下面是单机 4卡(Maxwel GPU)上各个操作随着通信量增加的带宽速度变化,可以看到带宽上限能达到10GB/s,接近PCIe的带宽。


下图是Allreduce在单机不同架构下的速度比较:

先不看DGX-1架构,这是Nvidia推出的深度学习平台,带宽能达到60GB/s。前面三个是单机多卡典型的三种连接方式,第三种是四张卡都在一个PCIe switch上,所以带宽较高,能达到>10GB/s PCIe的带宽大小,第二种是两个GPU通过switch相连后再经过CPU连接,速度会稍微低一点,第一种是两个GPU通过CPU然后通过QPI和另一个CPU上的两块卡相连,因此速度最慢,但也能达到>5GB/s。

下图是Allreduce多机下的速度表现,左图两机8卡,机内PCIe,机间InfiniBand能达到>10GB/s的速度,InfiniBand基本上能达到机内的通信速度。


下图是NCCL在CNTK ResNet50上的scalability,32卡基本能达到线性加速比。


(4)我们的实测经验

首先,在一台K40 GPU的机器上测试了GPU的连接拓扑,如下:

可以看到前四卡和后四卡分别通过不同的CPU组连接,GPU0和GPU1直接通过PCIe switch相连,然后经过CPU与GPU2和GPU3相连。

下面是测试PCIe的带宽,可以看到GPU0和GU1通信能达到10.59GB/s,GPU0同GPU2~3通信由于要经过CPU,速度稍慢,和GPU4~7的通信需要经过QPI,所以又慢了一点,但也能达到9.15GB/s。


而通过NVlink连接的GPU通信速度能达到35GB/s:


NCCL在不同的深度学习框架(CNTK/Tensorflow/Torch/Theano/Caffe)中,由于不同的模型大小,计算的batch size大小,会有不同的表现。比如上图中CNTK中Resnet50能达到32卡线性加速比,Facebook之前能一小时训练出ImageNet,而在NMT任务中,可能不会有这么大的加速比。因为影响并行计算效率的因素主要有并行任务数、每个任务的计算量以及通信时间。我们不仅要看绝对的通信量,也要看通信和计算能不能同时进行以及计算/通信比,如果通信占计算的比重越小,那么并行计算的任务会越高效。NMT模型一般较大,多大几十M上百M,不像现在image的模型能做到几M大小,通信所占比重会较高。

下面是NMT模型单机多卡加速的一个简单对比图:


以上就是对NCCL的一些理解,很多资料也是来自于NCCL的官方文档,欢迎交流讨论。

0. 开始前的吐槽

最近还是在研究机间和机内带宽的问题,在用DGX V100的时候碰上了一些机内通信的离谱现象——2张卡的AllReduce竟然比8张卡的Allreduce慢上好几倍,而DGX A100却不会有类似的问题。经过一番研究,总算搞清楚了问题出在了哪里,同时也发现NCCL可太厉害了。以前听过一个师兄关于NCCL源码的报告,他说NCCL根据数据量的大小,会分别采用各种各样的通信方法,包括Tree-Allreduce, Ring-Allreduce, Broadcast, Collnet等等。于是后来实际使用上NCCL的时候,我也没太在意nccl-tests 结果和预期不一致的问题。后来发现,这个通信算法分类的玩意不适用于NVLink(看nccl issue里头写的,出处以后有人问我再找吧)!人家NVLink环境下,在机内用nccl的时候就是规规矩矩的Ring-Allreduce。只有机间才可能出现Tree算法。加上NCCL这帮子人吧,留了一手,没有把技术细节明明白白的给你写出来,就指着你自个儿去看源码,或者等你提issue再跟你解释,所以也是折腾了不少时间。行了,赶紧开始正文吧。


1. 背景知识

1.1 Ring Allreduce

Ring Allreduce的原理就看看大佬写的吧,不赘述了。我这篇随笔会在大佬的基础上重点分析一下NCCL的性能。

1.2 NVLink

每根NVLink的速度我们默认是25GB/s。

2. DGX-1 V100

2.1 拓扑

首先,DGX-1的NVLink链接拓扑是这样的,我把这个链接DGX Validation 里的nvidia-smi topo -m画成了图(虽然他也提供了)

DGX V100 NVLink 拓扑

2.2 p2pBandwidthLatency Test

p2pBandWidthLatency Test的结果如下,这个test可以在cuda/samples/1_Utility里找到,需要make后执行。结果没什么太大问题,因为是双工链路,所以基本上就是NVLink的速度X NVLink数量 X 2.

举个例子, 下图GPU0和GPU1之间的双向带宽为48GB/s,约为25GB/s * 2;GPU0和3之间的约为79GB/s,这个稍微有点低了,我在我手头上的机器上测试,这个结果可以到96GB/s,约为50GB/s *2。

p2pBandwidthLatencyTest


3. NCCL工作原理

NCCL在机内通信的时候采用的是Ring-Allreduce算法,不过是若干个Ring-Allreduce。NCCL在初始化的时候,会检查系统中的链路拓扑,并创建若干个环路,以达到最优的性能。以2.1中的拓扑为例,NCCL会创建4种环路,分别是下面四种。这四种环相互不会影响,都是独立的链路带宽(不同方向,或者不同链路)。

Ring Channel#NVLinksBus Bandwidth
0->4->7->6->5->1->2->3250 GB/s
0<-4<-7<-6<-5<-1<-2<-3250 GB/s
0->1->3->7->5->4->6->2125 GB/s
0<-1<-3<-7<-5<-4<-6<-2125 GB/s

接下来,NCCL会把需要通信的数据进行切片,每一个Channel负责通信部分的切片数据。这么一来,就同时有多个环在工作。我们知道一块V100可以插6根NVLink,NCCL这么一做,直接把6根NVLink的双向带宽全部拉满了,这理论的通信速度就达到了150GB/s,我只能说,NVIDIA,不愧是你。

我在实际测试中,allreduce的性能最高达到了130GB/s,已经是一个很不错的结果了。

3.1 NCCL创建通信环路实例:DGX-1 2卡,4卡通信

DGX V100在2卡和4卡的情况下,通信时非常的尴尬的。我直接抛一个结论,这玩意就只配跑8卡通信。

2卡:在2.1给出的拓扑中,双卡通信有两种情况,如0-1的单NVLink连接,还有0-3的双NVLink连接。他们的Allreduce总线带宽上界分别是25GB/s,以及50GB/s。在我的实际测试中,也确实如此。这会导致个啥结果,我用的卡少了,通信占比的时间反而更长了,就会让人感觉非常weird。

4卡:以0,1,2,3 4张卡举例,可以形成4种回路,如下。

Ring Channel#NVLinksBus Bandwidth
0->1->2->3125GB/s
0<-1<-2<-3125GB/s
0->3->1->2125GB/s
0<-3<-1<-2125GB/s

其聚合带宽为100GB/s,在我的机器上实测出来nccl-tests的峰值性能是83GB/s,符合预期。通过看NCCL的Debug log,也确实是有四种环。

3.2 小结

如果你的服务器是V100卡,还是尽可能的同时多用点卡吧,这样对NVLink的带宽使用才更舒服。(TAT,论文要重新改了)

Appendix. 伟大的DGX A100

DGX A100的 NVSwitch真的太牛了!因为他的存在,我的论文可以少很多修改工作!

A100 SXM版本可以插12根NVLink,乍一看还是会有DGX-1 的问题,但是在NVSwitch的加持下,A100服务器中就相当于每两块卡之间都是连接了12根NVLink。
简单来说,不管你是用2卡训练,还是4卡训练,我的NCCL通信都可以跑到最高值,那可是300GB/s呀,比V100双卡通信快了十几倍有没有!不过实际应用中,p2p通信只有255GB/s,NCCL应用层再减掉一些,大概就只有200GB/s了。我在实际运用中,最高也只能达到205GB/s的速度。

齐刷刷的单向p2p结果。

Unidirectional P2P=Enabled Bandwidth (P2P Writes) Matrix (GB/s)
   D\D     0      1      2      3      4      5      6      7
     0 1180.14 254.47 258.80 254.13 257.67 247.62 257.21 251.53
     1 255.35 1173.05 261.04 243.97 257.09 247.20 258.64 257.51
     2 253.79 260.46 1155.70 241.66 260.23 245.54 259.49 255.91
     3 256.19 261.29 253.87 1142.18 257.59 248.81 250.10 259.44
     4 252.35 260.44 256.82 249.11 1169.54 252.46 257.75 255.62
     5 256.82 257.64 256.37 249.76 255.33 1142.18 259.72 259.95
     6 261.78 260.25 261.81 249.77 258.47 248.63 1173.05 255.47
     7 259.47 261.96 253.61 251.00 259.67 252.21 254.58 1169.54

Nvidia-smi topo -m,每张卡之间都是NV12,爽!

	GPU0	GPU1	GPU2	GPU3	GPU4	GPU5	GPU6	GPU7	mlx5_0	mlx5_1	mlx5_2	mlx5_3	mlx5_4	mlx5_5	mlx5_6	mlx5_7	mlx5_8	mlx5_9	CPU Affinity	NUMA Affinity
GPU0	 X 	NV12	NV12	NV12	NV12	NV12	NV12	NV12	PXB	PXB	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	48-63,176-191	3
GPU1	NV12	 X 	NV12	NV12	NV12	NV12	NV12	NV12	PXB	PXB	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	48-63,176-191	3
GPU2	NV12	NV12	 X 	NV12	NV12	NV12	NV12	NV12	SYS	SYS	PXB	PXB	SYS	SYS	SYS	SYS	SYS	SYS	16-31,144-159	1
GPU3	NV12	NV12	NV12	 X 	NV12	NV12	NV12	NV12	SYS	SYS	PXB	PXB	SYS	SYS	SYS	SYS	SYS	SYS	16-31,144-159	1
GPU4	NV12	NV12	NV12	NV12	 X 	NV12	NV12	NV12	SYS	SYS	SYS	SYS	PXB	PXB	SYS	SYS	SYS	SYS	112-127,240-255	7
GPU5	NV12	NV12	NV12	NV12	NV12	 X 	NV12	NV12	SYS	SYS	SYS	SYS	PXB	PXB	SYS	SYS	SYS	SYS	112-127,240-255	7
GPU6	NV12	NV12	NV12	NV12	NV12	NV12	 X 	NV12	SYS	SYS	SYS	SYS	SYS	SYS	PXB	PXB	SYS	SYS	80-95,208-223	5
GPU7	NV12	NV12	NV12	NV12	NV12	NV12	NV12	 X 	SYS	SYS	SYS	SYS	SYS	SYS	PXB	PXB	SYS	SYS	80-95,208-223	5

Legend:
  X    = Self
  SYS  = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
  NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
  PXB  = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
  PIX  = Connection traversing at most a single PCIe bridge
  NV#  = Connection traversing a bonded set of # NVLinks


唯一的一个缺点在于,太贵了。咱们实验室买不起呀,只能借别人的玩玩了。加上现在老米限购了,想买也买不到了,哭哭。


PS:NCCL在多机通信的时候,有GDRDMA和GDRDMA是两个东西,IB太快啦!不过GDRDMA的驱动是要另外装的,想要用的话要注意一下(nv_peer_mem,好像是叫这个来着)。一般高性能服务器,用的是Mellanox网卡,一个Mellanox IB端口的带宽可达100Gb/s,一个A100服务器就可以上8块Mellanox,全部插满的话,机间通信应该能到100GB/s。但是Mellanox太贵啦,又是一个我们买不起的东西TAT。



更新:2023-01-06 17:24:49

现在是2023年1月份,NCCL已经发2.16版本了。但是我看知乎上没啥人讲清楚GPU多机通信的事情,我就稍微讲一讲吧。有不对的地方麻烦评论一下,我们一起探讨探讨。

这篇文章的分析是基于DGX-A100和Infiniband进行的。因为我手上没有可以用的机器,性能数据都是参考的nccl和nccl-test的github库里的issue。

一、Double Binary Tree

Ring算法的细节我这里不赘述了,在知乎上可以搜到很多。NCCL 2.4以后,多机通信就默认采用Double Binary Tree了,因为这种方法的可扩展性要比Ring好很多。下面这篇英伟达的博客介绍了关于Double Binary Tree和Ring的性能对比,我简单提一下为什么这种通信方式比Ring好。

Double Binary Tree
  1. 使用Double Binary Tree可以很好的将intra-node和inter-node通信给pipeline起来。实际运行过程中,NCCL每个channel会做intra-node reduce -> inter-node all-reduce -> intra-node broadcast,这三个步骤是可以流水线处理的。
  2. 通过构建Double Binary Tree,每个节点至多是一个叶子和一个非叶子结点。因此每个节点至多只需要收发两倍的数据量,这点跟Ring的实现也是一样的。因此Double Binary Tree也是一种带宽最优(近似)的算法。之所以说是近似,是因为根节点只需要收发一倍的数据量。
  3. 随着节点数量的增加,Ring算法所带来的latency是成线性增加的。而Double Binary Tree的latency是log(N)。

关于Ring和Tree的性能差异,目前只看到这里有一个issue提到了。这个issue的测试环境是两台DGX-A100服务器,每台上面有4张200Gbps的IB NIC。他测出来Ring的算法带宽是49GB/s,总线带宽是91GB/s。Tree的算法带宽是57GB/s。Ring的总线带宽已经非常接近理论上限了,但是这个例子里面,Ring算法的NIC之间一共收发了2倍数据量的数据,而Tree算法的NIC之间只收发了1倍数据量(因为只有2个节点,Tree不够大),所以只做参考。如果这里把Ring算法改成hierarchical的,性能估计会比Tree相近或者更好。


二、CollNet

CollNet其实不是一种算法,而是一种自定义的网络通信方式,需要加载额外的插件来使用。说下基于SHArP协议的,具体加载方法这个链接里有写。

目前NVIDIA官方的CollNet实现应该是只有基于SHArP的这一种,需要搭配Infiniband以及Infiniband交换机一起食用(yummy),好一个NVIDIA全家桶。怕大家没接触过SHArP,先给大家扫个盲(因为我就没接触过,研究HPC的同学倒是熟得很)。

1. 什么是SHArP

这个是介绍SHArP的论文:SHArP 论文,其实没必要细看。简单来说,SHArP是一个软硬结合的通信协议,实现在了NVIDIA Quantum HDR Switch的ASIC里。它可以把从各个node收到的数据进行求和,并发送回去。再说的通俗一点,通过使用SHArP,我们把求和(聚合/Reduce,随便怎么叫)的操作交由交换机完成了。这种做法,业界叫做In-network Computing(在网计算)。用术语展开来讲,就是将计算卸载到网络中进行。

更多相关的知识可以看这个英伟达的汇报

2. 为什么要SHArP?

通过把计算卸载到网络里,每个node(图片里为host)只需要收发一倍的数据量就可以完成AllReduce了。相比Tree和Ring算法,收发的数据量直接就减半了。这使得SHArP的潜力比Tree和Ring高了一倍。而且SHArP因为把计算卸载给交换机去做了,因此小数据的latency也会减少很多。

SHArP原理

3. 举个例子

这个链接SHArP nccl-tests里有一个nccl-test的例子,应该是用128个DGX A100服务器测的。他测出来的算法带宽有95GB/s。机间达到了95GB/s,这是个多么恐怖的数据,要知道机内A100也就120GB/s左右。

4. SHArP的性能分析

关于这个,我在issue向nccl的作者提问了很多次,也得到了大佬的热心回复。具体可以看这里github.com/NVIDIA/nccl/

简单来说,作者的经验法则是每张NIC可以带来约20GB/s的带宽。但是超过一定的NIC数量后,NVLink会成为瓶颈,这时候理论上限就是NVLink NCCL Bandwidth/2 ≈ 110GB/s了。关于瓶颈我在issue里给了详细的分析,感兴趣可以看一看。