Pico-vLLM 开发日志 #11 张量并行(续) 数据对比分析

正如之前所说,在b200上用单卡和两卡重新测试了相同的1.5B模型的结果,进而得到了一些有意思的小结论。数据删去繁杂的细节之后(感谢ai),整理在下表中。

性能数据汇总

wall-clock端到端时间(含CPU开销)
硬件                                tp    延迟        tok/s      加速比
───────────────────────────────────────────────────────────────────────
5090 PCIe (Py3.12+Xeon 8470Q)       1     2.74ms      365         —
5090 PCIe (Py3.12+Xeon 8470Q)       2     2.13ms      470        1.29x

B200 NVLink (Py3.10+Xeon 6960P)     1     1.64ms      610         —
B200 NVLink (Py3.10+Xeon 6960P)     2     2.08ms      480        0.79x

可以看到,前者可以达到一定程度的加速,而后者不仅没有加速,反而被拖慢了。这可能是什么原因?第一反应肯定是因为B200本身算力太高,分摊GEMM计算量的好处相当有限,而通信带来的提升不足以被PCIe到NVLink的配置升级所补偿。算力的升级幅度远大于通信的升级幅度,因此前者可以加速,而后者就不行了。

这个解释本身就是可以接受的。不过,还有一个疑惑:NVLink的性能比PCIe按理来说高很多,B200的性能,无论是算力还是带宽,按理来说也比5090高很多,但为什么在tp=2的情况下,两者的延迟如此接近,几乎完全相等(只有不到3%)?接下来的分析揭示了对小模型(以及不够大的大模型)来说另一个很隐蔽,但是可能同样也很重要的开销:CPU调度开销。

纯GPU replay时间(无CPU开销)

为了解答这个问题,设计了纯基于CUDA Graph的连续replay测试。这部分测试是为了揭示在没有CPU控制流开销的情况下GPU本身的性能如何,以反过来倒推CPU开销的大小如何对性能产生影响(这个的测试没有那么直截了当)。

见下表。可以看到一个非常有趣的细节:在5090+PCIe的配置组合上,端到端的开销和纯GPU开销相对来说较小:CPU侧的开销要么很小,要么可以被部分的overlap住。而对于B200+NVLink来说,其平均的CPU侧开销几乎是5090+PCIe的两倍以上。这主要有这样几个原因:

1、CPU主频不同。CPU侧的代码并没有那么高的并行度,堆叠核数的意义小很多。而Xeon 8470Q的睿频是约3.8GHz,与此同时Xeon 6960P的单核睿频是约3.2GHz。这两者的性能差异造成了很大的CPU侧时延不同,因此最终在并行度增加的时候抹平了GPU侧的性能优势。

2、Python版本的不同。更新的版本具有更完善的解释器优化,因此能够更好的降低CPU侧阻塞时间。

因此,CPU在现代大模型推理里真的不重要吗?其实不完全是。在模型较小、或者并行度够高以至于单步推理时间较为短的时候(以我们的测试为例,小于2.0ms的时候),CPU的overhead并不一定总是能够忽略的。因此,CPU侧代码的优化和恰当实现同样有其必要性。

硬件                     tp    纯GPU延迟    GPU加速比
────────────────────────────────────────────────────
5090 PCIe                1     2.62ms       —
5090 PCIe                2     2.12ms       1.24x

B200 NVLink              1     1.46ms       —
B200 NVLink              2     1.89ms       0.77x
AllReduce 延迟(单次3KB传输)

可以看到,NVLink的单次通信固定延迟大约是PCIe的三分之一,而带宽是20倍,这也是一个有助于建立直感的数字。它也再次验证了计算机学科几乎通用的一个真理:降低延迟永远比提高带宽困难多了。小数据量的AllReduce是纯latency-bound,其对性能造成的损害于取得的收益相比不成比例(至少不是线性的),因此只适合在足够大的模型、足够好的配置下才值得用,尽管它是实现起来最直接也最方便的。

                    固定延迟      数据传输      总延迟      56次/步
─────────────────────────────────────────────────────────────────
5090 PCIe           ~28μs        0.06μs       ~28μs       1.59ms
B200 NVLink         ~10μs        0.003μs      ~10μs       0.57ms

值得一提的细节

benchmark环境的隐藏变量不一定可以忽略,有时候影响显著。在最初测试的时候,B200 NVLink (Py3.10+Xeon 6960P)的配置在tp=2的时候甚至慢于5090 PCIe (Py3.12+Xeon 8470Q)的tp=2的情况,而这几乎说不通。经过检查,发现是环境变量中,OMP_NUM_THREADS设置不同(B200侧未指定,默认为1),autodl平台进行了专门的预先设置(指定为了50)但B200的环境中没有,这造成了明显的性能改变,通过拖累CPU侧的性能进而拖累了整个端到端的性能。

此外,这也验证了另一个问题:TP不是万能药,甚至有时候有害。对于小模型、网络延迟不够低的机器配置,尤其如此。