NCCL 算法解析
NCCL 算法选择过程
算法与拓扑
nccl 目前有六种算法
|
|
目前共有 6 种拓扑
|
|
算法与拓扑的映射关系如下:
| Aglo | Topo | Topo Pattern |
|---|---|---|
| NCCL_ALGO_TREE | treeGraph | NCCL_TOPO_PATTERN_BALANCED_TREE |
| NCCL_ALGO_RING | ringGraph | NCCL_TOPO_PATHERN_RING |
| NCCL_ALGO_COLLNET_DIRECT | collNetGraph | NCCL_TOPO_PATTERN_TREE |
| NCCL_ALGO_COLLNET_CHAIN | collNetGraph | NCCL_TOPO_PATTERN_TREE |
| NCCL_ALGO_NVLS | nvlsGraph | NCCL_TOPO_PATTERN_NVLS |
| NCCL_ALGO_NVLS_TREE | nvlsGraph | NCCL_TOPO_PATTERN_NVLS |
|
|
|
|
拓扑建立
在建立拓扑时,会建立 ring, tree, collnet, nvls 4种拓扑结构,4种拓扑均会建联, 此处注意 nvls 拓扑会占用大量的 GPU 内存(参见 ncclNvlsInit 的内存申请)
|
|
这里会通过 ncclTopoCompute 计算每种拓扑结构的带宽,并记录在 ncclTopoGraph 结构中,包括了各种机内与机间的通讯带宽和路径。
最终算法选择
nccl 会对各个算法(NCCL_ALGO)的时间有个估计,并根据数据量和集合通讯类型(allreduce, allgather) 选择耗时最小的算法
|
|
拓扑带宽获取过程
ncclTopoCompute 的时候,会记录每种拓扑结构的带宽,并存储在 ncclTopoGraph 结构中
这里就是实际搜索 channel 的过程,目标是搜索出来尽可能多,带宽尽可能大的一系列 channel,本质就是暴力搜索,先设置一系列的条件搜答案,如果搜不出来则降低条件继续搜。
由于此时没有 NET 节点,所以 crossNic 为 0,然后初始化 graph,首先设置最高的条件,限制节点内部只能使用不超过 PATH_NVL 路径,节点间只能使用不超过 PATH_PIX 的路径,然后通过 system-maxWidth 设置 speedIntra 和 speedInter,接着执行 ncclTopoSearchRec 搜索出一个答案存储到 tmpGraph 中。
|
|
NVLink
|
|
NET
|
|
P2P
|
|
SYS TOPO
|
|
|
|
|
|
Fill
|
|
首先需要构建一个 Topo 图:
ncclXmlNode表示一个节点,记录了父节点和所有子节点,节点有 name 和 attr,通过xmlSetAttr进行设置属性ncclXml预先分配了所有的 node,maxIndex 表示分配到了哪里
|
|
关于 ncclXml 的几个 API 如下:
|
|
首先通过 xmlAddNode 创建根节点 system(后续使用双引号表示 xml 树节点),并设置根节点属性 system["version"] = NCCL_TOPO_XML_VERSION,然后遍历每个 rank 的 hosthash,如果相等的话说明在同一个机器,然后执行 ncclTopoFillGpu,将 gpu 加入到 xml 树
|
|
|
|
通过 ncclTopoGetPciNode 获取 xml 中的有没有创建当前卡的 xml node,此时没有,所以就新建一个 xml node 叫做 “pci”,表示当前 gpu 卡,设置 pci["busid"]=busId
|
|
然后执行 ncclTopoGetXmlFromSys,这个函数主要逻辑就是在 sysfs 中获取 gpu 节点到 cpu 的路径,通过这个路径转成 xml 树,并读取该路径下相关属性设置到 xml 里
然后从 pciNode 开始往上跳,因为一个 switch 的上游端口和下游端口分别对应了一个 bridge,NCCL 使用上游端口 bridge 的 busid 表示这个 switch,因此这里要向上跳两次再建立一个 xml node 表示这个 switch,往上找到一个 PCI 设备就将 slashCount 加一,当 slashCount 2 就找到了一个 switch 上游端口,这个时候创建一个新的 xml pci 节点 parent 表示当前 switch,然后将当前节点 pciNode 链接到 parent,此时 parent 仍然是 xml pci 节点,因此继续递归执行 ncclTopoGetXmlFromSys,直到遇到 RC,此时给"system"创建一个子节点"cpu",停止递归,然后执行 ncclTopoGetXmlFromCpu,设置"cpu"的各种属性,比如 arch(比如 x 86 还是 arm),affinity(该 cpu 的 numa 都有哪些 cpu core),numaid 等。
然然后通过 wrapNvmlSymbols 加载动态库 libnvidia-ml.so.1,用来获取 gpu 的相关信息
首先在 xml gpu 节点"pci"下创建节点"gpu",然后设置"gpu"节点的属性,比如 dev,计算能力 sm,然后开始查询 nvlink 相关信息,遍历所有可能的 nvlink,通过 nvmlDeviceGetNvLinkCapability 查询 nvlink 信息,如果这个 nvlink 被启用,那么在"gpu"节点下新建一个"nvlink"节点,设置"target"属性表示 nvlink 对端的 PCIe busId,将"target"相同的"nvlink"节点表示为一个,用"count"表示起止点之间有多少条 nvlink,然后设置属性"tclass"表示"target"是什么类型的 PCI 设备
到这里 ncclTopoFillGpu 就执行结束了,此时 xml 如下所示,图里只展示了一张网卡的情况,其中"gpu"和他的父节点其实都是指的同一个 gpu
|
|
Graph
|
|
-
No backlinks found.