libverbs 是 RDMA 通信的通用库,它完成了 RNIC Driver 层的抽象,给用户层提供了一个统一的接口。libverbs 总体上是一个异步通知的队列逻辑。在不考虑 RNIC 网卡性能极限的情况下,一套比较优秀的异步事件交互逻辑能够更加有效地利用网卡的性能,达到更低的延迟和更高的吞吐量。在 NCCL 中源码中,有一些实现上的细节能够提供给我们优化 RDMA 通信逻辑的相关思路。
IBV_SENDLINE 的使用问题
IBV_SEND_LINE
是 libverbs 中针对小数据包的优化处理,它能够明显改善 128 字节以下数据包的发送延迟,略微提升吞吐量。这一优化的具体实现是使用 CPU 而非 RNIC 的 DPU 进行发送。但是使用IBV_SEND_INLINE
时,必须要使用IBV_SEND_SIGNALED
来处理事件的完成通知,否则可能会导致 SQE 用尽,无法发送数据。
在 NCCL 中,会定期使用IBV_SEND_SIGNALED
标识位来处理 inline 包的通知问题,即发送一轮 slot 时:
1 | if (slot == 0) { |
这种思路同样也可以用到其余地方,每隔几次 SR 再带上一次标识位,这样能够降低与 comp channel 之间的通信频率。
多 QP 利用 DPU 性能
在 RC 模式下,为了保证每一个事件的完成是顺序的,单一 SQ 中的事件只能够被 RNIC 中的一个 DPU 处理。当连接较少、数据包较少时,可能无法充分利用 RNIC 的 DPU,导致网络性能下降。 NCCL 中会尝试将一次用户请求的数据分解到多个 QP 当中去,从而更加充分地利用 RNIC 的 DPU 性能:
1 | for (int q = 0; q < nqps; q++) { |
通信分组通知
RC 模式下,为了检验 SR 是否完成,必须要使用IBV_SEND_SIGNALED
与 comp channel 进行交互。但是使用这一标识位意味着应用层需要处理来自于 comp channel 的事件通知,如果每一个请求都使用该标识位,就会导致事件通知过于频繁,应用层需要频繁处理事件完成通知,这会在一定程度上导致 CPU 占用率的上升。由于 NCCL 中分割发送的数据包,在短时间可能就会出现频繁的事件通知,但这种事件通知是不必要的,NCCL 中对此做出了优化:只在分组的最后一个 SR 上使用标识位IBV_SEND_SIGNALED
。这样可以利用 RC 的可靠性保证,确保所有请求发送完毕,并且能够有效地减少事件通知的次数,降低 CPU 占用率:
1 | struct ibv_send_wr *lastWr = comm->wrs + nreqs - 1; |
一些思考
NCCL 所做出的优化,主要还是针对于小数据包的带宽和延迟问题,而针对大数据包的优化比较少。根据我的理解,在数据包较小时,RDMA 的网络通信次数会更加频繁,相应地 comp channel 的通知也比较多。在这种情况下,应用层与 RNIC 驱动之间的交互的耗时占总耗时的比例就比较高,针对这一场景进行优化,效果就比较明显。而数据包较大时,数据的发送耗时大大增加,应用处与 RNIC 驱动交互的耗时就可以忽略不计了。并且当 RNIC 网卡的带宽吃满之后,限制 RDMA 网络通信性能的是网卡本身,而非通信库的处理逻辑。