在 RTX 5090 上启用 PCIe P2P 通信支持

 

免责声明: 本文涉及对 GPU 驱动的修改,作者不对任何因此产生的后果负责。

PCIe P2P 访问是 GPU 之间进行通信的重要途径。它使得不同设备间的通信无需经过内存中转,直接通过 PCIe 总线传输,在很多时候能显著提升通信性能。当然,现实中的 P2P 性能也受到很多因素影响:比如 AMD EPYC 7002 家族的 PCIe Root Complex 转发性能就比较差;而如果主板上有 PCIe Switch 芯片,那么很多情况下几乎就能打满线速。但总体来说,P2P 的性能几乎总是比经过 GPU-内存-GPU 的路径要好。

然而众所周知,老黄精准的刀法砍掉了所有家用机显卡的 PCIe P2P 通信支持。换句话说:

harry@gpu:~$ nvidia-smi topo -p2p r
        GPU0    GPU1    GPU2    GPU3    GPU4    GPU5    GPU6    GPU7
 GPU0   X       GNS     GNS     GNS     GNS     GNS     GNS     GNS
 GPU1   GNS     X       GNS     GNS     GNS     GNS     GNS     GNS
 GPU2   GNS     GNS     X       GNS     GNS     GNS     GNS     GNS
 GPU3   GNS     GNS     GNS     X       GNS     GNS     GNS     GNS
 GPU4   GNS     GNS     GNS     GNS     X       GNS     GNS     GNS
 GPU5   GNS     GNS     GNS     GNS     GNS     X       GNS     GNS
 GPU6   GNS     GNS     GNS     GNS     GNS     GNS     X       GNS
 GPU7   GNS     GNS     GNS     GNS     GNS     GNS     GNS     X

这里的 GNS 表示 GPU Not Supported。但真的是硬件不支持吗?似乎并不是这样。

前置工作

在计划使用 P2P 通信之前,首先要确认一些前置条件是否具备:

  • 硬件拓扑:通常来说,同一个服务器 CPU Socket 上安装的 GPU 之间共享同一个 Root Complex,一般都能支持 P2P;跨 socket 通常也是可行的。而如果有 PCIe Switch,则可能有更复杂的拓扑(比如特定 GPU 之间有更高的带宽)。nvidia-smi topo -m 输出的结果很多时候未必准确:如在我的 AMD EPYC 9354 上,给出的所有结果都是 SYS,或许和 NPS4 有关。
  • BIOS 配置:关闭 PCIe ACS (Access Control Service),建议关闭 IOMMU 和虚拟化,前者亦可在内核选项中配置 iommu=pt 绕过。这些功能虽然能提供更高的安全性,但也会让 PCIe 上的通信变得更复杂,很可能导致 P2P 性能急剧下降或者完全不可用。

魔改驱动

著名 iPhone 越狱黑客 Geohot 在几年前发起了一个精简的深度学习框架 tinygrad,力争通过最小的软件 overhead 实现深度学习框架。而这个框架的一个副产品就是,魔改过的 NVIDIA 的开源 GPU 驱动(tinygrad/open-gpu-kernel-modules),在不少家用级显卡上启用了 PCIe P2P 通信支持。所有的修改只有不到一百行的改动,大部分都是关掉判断,或者 patch 掉某些函数虚表,使用对应的数据中心卡的实现。所以可以推断,很大程度上,这些限制完全是来自于软件。真有你的,老黄。

虽说这个仓库的最新版本只到 565,另有高人搞出了基于 585 和 595 的版本(aikitoria/open-gpu-kernel-modules),顺便也就支持了 RTX 5090,其实核心改动基本还是差不多。

作者给出的安装脚本有些简单粗暴,直接当场构建模块并 insmod。这显然有点脏了。考虑到目前的模块都是 dkms 安装的,最好的办法其实是解包 NVIDIA 提供的 nvidia-kernel-open-dkms 包并替换文件。如果不打算频繁更新驱动的话,也可以采用一种略微少脏一些的办法:

# 安装官方驱动
apt install nvidia-driver-pinning-590.48.01
apt install nvidia-open=590.48.01-1 nvidia-kernel-open-dkms=590.48.01-1
# 卸载模块
rmmod nvidia_drm nvidia_modeset nvidia_uvm nvidia
# 下载魔改驱动
git clone [email protected]:aikitoria/open-gpu-kernel-modules.git
# 备份原有的驱动源代码
cd /usr/src/nvidia-590.48.01/
mkdir backup
mv kernel-open src backup/
# 打补丁
cd ~/open-gpu-kernel-modules/
cp -r kernel-open src /usr/src/nvidia-590.48.01/
# 重新构建驱动并安装
dkms build -m nvidia/590.48.01 --force
dkms install -m nvidia/590.48.01 --force
# 重新加载模块
modprobe nvidia

此时可以再次测试,是否已经化腐朽为神奇:

harry@gpu:~$ nvidia-smi topo -p2p r
        GPU0    GPU1    GPU2    GPU3    GPU4    GPU5    GPU6    GPU7
 GPU0   X       OK      OK      OK      OK      OK      OK      OK
 GPU1   OK      X       OK      OK      OK      OK      OK      OK
 GPU2   OK      OK      X       OK      OK      OK      OK      OK
 GPU3   OK      OK      OK      X       OK      OK      OK      OK
 GPU4   OK      OK      OK      OK      X       OK      OK      OK
 GPU5   OK      OK      OK      OK      OK      X       OK      OK
 GPU6   OK      OK      OK      OK      OK      OK      X       OK
 GPU7   OK      OK      OK      OK      OK      OK      OK      X

性能测试

带宽延迟测试

CUDA Samples 中的 p2pBandwidthLatencyTest 可以测试 GPU 之间的通信带宽和延迟。启用 P2P 前:

Unidirectional P2P=Disabled Bandwidth Matrix (GB/s)
   D\D     0      1      2      3      4      5      6      7
     0 1528.86  31.57  31.12  30.91  28.82  29.39  29.51  29.86
     1  30.66 1556.32  31.00  30.29  29.12  29.57  29.39  29.87
     2  30.69  31.64 1556.27  30.29  31.22  31.26  31.37  31.14
     3  31.12  31.58  30.96 1550.15  29.04  29.55  29.41  29.69
     4  29.99  31.49  31.22  29.60 1553.28  31.22  31.44  31.13
     5  30.18  31.72  31.42  29.80  30.61 1559.38  31.42  29.78
     6  30.31  31.72  31.55  29.96  30.47  31.38 1559.38  30.73
     7  30.03  31.39  31.04  29.63  30.83  31.05  31.41 1553.18

Bidirectional P2P=Disabled Bandwidth Matrix (GB/s)
   D\D     0      1      2      3      4      5      6      7
     0 1528.82  32.56  32.29  32.40  31.64  31.93  31.88  31.96
     1  32.53 1540.88  32.61  32.53  31.77  32.06  32.08  31.68
     2  32.28  32.52 1539.34  32.18  32.21  32.29  32.23  32.23
     3  32.47  32.46  32.08 1540.88  31.50  31.80  31.89  31.83
     4  31.54  32.03  32.36  31.41 1537.82  32.26  32.53  32.22
     5  31.66  32.23  32.38  31.81  32.16 1539.36  32.52  32.07
     6  31.80  32.19  32.53  31.78  32.34  32.65 1539.36  32.29
     7  31.76  32.30  32.39  31.81  32.44  32.16  32.66 1540.88

P2P=Disabled Latency Matrix (us)
   GPU     0      1      2      3      4      5      6      7
     0   2.07  14.25  14.34  12.65  14.08  14.30  14.30  14.30
     1  14.30   2.07  13.91  12.45  14.25  14.16  14.25  14.25
     2  14.19  14.26   2.07  14.33  14.33  14.31  14.33  14.33
     3  14.32  14.32  13.81   2.07  14.25  14.26  14.16  14.32
     4  14.34  14.32  14.33  14.31   2.07  14.32  14.32  14.32
     5  14.31  14.31  14.33  14.33  14.33   2.07  14.33  14.33
     6  14.32  14.32  14.35  14.32  14.33  14.34   2.07  14.33
     7  14.32  14.31  14.34  14.31  14.32  14.31  14.35   2.07

启用 P2P 后:

Unidirectional P2P=Enabled Bandwidth (P2P Writes) Matrix (GB/s)
   D\D     0      1      2      3      4      5      6      7
     0 1516.99  48.54  48.50  48.53  48.45  48.54  48.56  48.48
     1  48.76 1543.97  48.64  48.61  48.62  48.58  48.62  48.57
     2  48.75  48.74 1537.89  48.76  48.76  48.76  48.76  48.76
     3  48.75  48.76  48.74 1543.97  48.76  48.74  48.76  48.74
     4  48.75  48.76  48.76  48.76 1540.93  48.74  48.74  48.76
     5  48.74  48.76  48.76  48.76  48.76 1547.03  48.76  48.74
     6  48.73  48.65  48.64  48.64  48.64  48.65 1547.03  48.66
     7  48.75  48.69  48.55  48.64  48.61  48.61  48.60 1544.02

Bidirectional P2P=Enabled Bandwidth Matrix (GB/s)
   D\D     0      1      2      3      4      5      6      7
     0 1530.32  97.42  97.44  97.46  97.41  97.45  97.44  97.44
     1  97.43 1540.88  97.44  97.44  97.45  97.45  97.44  97.44
     2  97.43  97.44 1539.34  97.44  97.45  97.44  97.44  97.45
     3  97.44  97.44  97.41 1539.34  97.44  97.45  97.45  97.45
     4  97.45  97.45  97.47  97.44 1537.82  97.45  97.46  97.47
     5  97.46  97.45  97.45  97.44  97.44 1540.88  97.46  97.46
     6  97.43  87.75  97.44  97.44  97.44  97.44 1540.86  97.45
     7  97.44  97.45  97.45  97.45  97.46  97.44  97.44 1540.86

P2P=Enabled Latency (P2P Writes) Matrix (us)
   GPU     0      1      2      3      4      5      6      7
     0   2.07   0.37   0.35   0.37   0.43   0.37   0.36   0.36
     1   0.36   2.07   0.35   0.37   0.36   0.43   0.43   0.42
     2   0.38   0.43   2.07   0.38   0.38   0.44   0.37   0.38
     3   0.38   0.43   0.37   2.07   0.36   0.36   0.37   0.37
     4   0.45   0.37   0.44   0.38   2.07   0.45   0.37   0.43
     5   0.36   0.35   0.36   0.36   0.37   2.07   0.43   0.35
     6   0.43   0.42   0.43   0.36   0.36   0.43   2.07   0.43
     7   0.36   0.44   0.36   0.36   0.36   0.36   0.36   2.07

也就是说,在启用 P2P 前后,各个指标都有了显著提升:

  • 单向带宽:31.5 GB/s(由于需要经过内存,理论上限为 PCIe 5.0 x16 带宽的一半即 32 GB/s)-> 48.5 GB/s(理论上限为 64 GB/s)
  • 双向带宽:32 GB/s(理论值为 64 GB/s)-> 97.4 GB/s(理论值为 128 GB/s)
  • 通信延迟:14.3 us -> 0.4 us

集合通信测试

下面用 NCCL 来测试对多卡深度学习影响非常显著的 AllReduce 性能。

八卡启用 P2P 前:

#                                                              out-of-place                       in-place
#       size         count      type   redop    root     time   algbw   busbw  #wrong     time   algbw   busbw  #wrong
#        (B)    (elements)                               (us)  (GB/s)  (GB/s)             (us)  (GB/s)  (GB/s)
        4096          1024     float     sum      -1    51.03    0.08    0.14       0    50.82    0.08    0.14       0
        8192          2048     float     sum      -1    51.58    0.16    0.28       0    51.36    0.16    0.28       0
       16384          4096     float     sum      -1    52.67    0.31    0.54       0    52.63    0.31    0.54       0
       32768          8192     float     sum      -1    54.57    0.60    1.05       0    54.74    0.60    1.05       0
       65536         16384     float     sum      -1    59.77    1.10    1.92       0    60.10    1.09    1.91       0
      131072         32768     float     sum      -1    62.38    2.10    3.68       0    62.12    2.11    3.69       0
      262144         65536     float     sum      -1    71.12    3.69    6.45       0    70.86    3.70    6.47       0
      524288        131072     float     sum      -1   104.97    4.99    8.74       0   104.45    5.02    8.78       0
     1048576        262144     float     sum      -1   163.53    6.41   11.22       0   161.47    6.49   11.36       0
     2097152        524288     float     sum      -1   275.28    7.62   13.33       0   273.02    7.68   13.44       0
     4194304       1048576     float     sum      -1   534.66    7.84   13.73       0   537.23    7.81   13.66       0
     8388608       2097152     float     sum      -1  1015.74    8.26   14.45       0  1010.40    8.30   14.53       0
    16777216       4194304     float     sum      -1  1999.61    8.39   14.68       0  2002.71    8.38   14.66       0
    33554432       8388608     float     sum      -1  4011.43    8.36   14.64       0  4013.64    8.36   14.63       0
    67108864      16777216     float     sum      -1  7972.72    8.42   14.73       0  7970.34    8.42   14.73       0
   134217728      33554432     float     sum      -1  15721.4    8.54   14.94       0  15733.6    8.53   14.93       0
   268435456      67108864     float     sum      -1  31422.7    8.54   14.95       0  31431.6    8.54   14.95       0
   536870912     134217728     float     sum      -1  62893.2    8.54   14.94       0  62859.7    8.54   14.95       0
  1073741824     268435456     float     sum      -1   128117    8.38   14.67       0   127351    8.43   14.75       0

由于 NCCL 默认并不愿意为 SYS 连接的 GPU 启用 P2P,因此需要通过 NCCL_P2P_LEVEL=SYS 强制打开。判断是否真的启用了 P2P 的方法是,设置 NCCL_DEBUG=INFO 后,查看日志中是否出现了类似的字样:

[0] NCCL INFO Channel 00/0 : 0[0] -> 1[1] via P2P/IPC
[5] NCCL INFO Channel 01/0 : 1[5] -> 0[4] via P2P/IPC
[1] NCCL INFO Channel 00/0 : 1[1] -> 0[0] via P2P/IPC
[0] NCCL INFO Channel 01/0 : 0[0] -> 1[1] via P2P/IPC
[4] NCCL INFO Channel 01/0 : 0[4] -> 1[5] via P2P/IPC
[1] NCCL INFO Channel 01/0 : 1[1] -> 0[0] via P2P/IPC
[7] NCCL INFO Channel 00/0 : 1[7] -> 0[6] via P2P/IPC
[7] NCCL INFO Channel 01/0 : 1[7] -> 0[6] via P2P/IPC

此时性能结果为:

#                                                              out-of-place                       in-place
#       size         count      type   redop    root     time   algbw   busbw  #wrong     time   algbw   busbw  #wrong
#        (B)    (elements)                               (us)  (GB/s)  (GB/s)             (us)  (GB/s)  (GB/s)
        4096          1024     float     sum      -1    38.81    0.11    0.18       0    36.53    0.11    0.20       0
        8192          2048     float     sum      -1    38.20    0.21    0.38       0    37.00    0.22    0.39       0
       16384          4096     float     sum      -1    39.47    0.42    0.73       0    38.28    0.43    0.75       0
       32768          8192     float     sum      -1    40.94    0.80    1.40       0    39.82    0.82    1.44       0
       65536         16384     float     sum      -1    42.48    1.54    2.70       0    41.36    1.58    2.77       0
      131072         32768     float     sum      -1    43.65    3.00    5.25       0    42.99    3.05    5.34       0
      262144         65536     float     sum      -1    49.97    5.25    9.18       0    47.18    5.56    9.72       0
      524288        131072     float     sum      -1    63.10    8.31   14.54       0    62.07    8.45   14.78       0
     1048576        262144     float     sum      -1    84.45   12.42   21.73       0    85.24   12.30   21.53       0
     2097152        524288     float     sum      -1   149.68   14.01   24.52       0   149.70   14.01   24.52       0
     4194304       1048576     float     sum      -1   280.51   14.95   26.17       0   278.13   15.08   26.39       0
     8388608       2097152     float     sum      -1   545.43   15.38   26.91       0   552.92   15.17   26.55       0
    16777216       4194304     float     sum      -1  1091.57   15.37   26.90       0  1090.04   15.39   26.93       0
    33554432       8388608     float     sum      -1  2169.79   15.46   27.06       0  2162.90   15.51   27.15       0
    67108864      16777216     float     sum      -1  4336.85   15.47   27.08       0  4327.66   15.51   27.14       0
   134217728      33554432     float     sum      -1  8640.99   15.53   27.18       0  8627.71   15.56   27.22       0
   268435456      67108864     float     sum      -1  17226.8   15.58   27.27       0  17207.1   15.60   27.30       0
   536870912     134217728     float     sum      -1  34359.1   15.63   27.34       0  34359.3   15.63   27.34       0
  1073741824     268435456     float     sum      -1  68660.4   15.64   27.37       0  68652.1   15.64   27.37       0

也就是说,八卡 AllReduce 在 PCIe 上的带宽(busbw)从 14.75 GB/s 提升到了 27.34 GB/s。如果在一个 Socket 的四张 GPU 上测试,则提升会更明显,从 16.33 GB/s 提升到了 46.31 GB/s,大概是原来的 2.8 倍。

神秘修复

那么这些性能提升是完全免费的吗?是,也不是。我发现在魔改驱动后,一旦进程使用了 P2P 功能,那么有概率存在部分 GPU,在所有进程均退出后依旧维持 100% 的利用率和相当高的功耗。但只要再次在这些 GPU 上创建 CUDA Context,什么都不做并立刻销毁,就能使得它恢复正常静息状态。可以合理推测,魔改后的驱动应该存在一定的资源泄露问题。

为了缓解此问题,我在系统中增加了一个 systemd timer,定期检查是否有 GPU 无进程但高占用的情况,如果有则运行上述的简单修复程序。虽然方法比较土,但可以有效地缓解这个问题。

One More Thing

虽然目前的驱动已经支持 P2P,但是还是不能使用 GPUDirect RDMA 功能,而这是提升跨机通信性能的关键。既然 GPU 之间能互相通信,那么理论上 IB 卡和 GPU 通信应该也不困难。此外,从 580 开始, NVIDIA 驱动已经切换到了 Linux 官方支持的 dma-buf 来提供此支持,而不需要 nvidia-peermem 模块了。但很遗憾,这个魔改的驱动还没有打开此支持,而已经有人许下了良好的愿望。此外,GPUDirect Storage 也有着相同的原理,但它也还是一个愿望

此外,近日我在小红书上还刷到了国内的神人通过 Claude 魔改驱动,还给 5090 增加了 NVLink 支持,甚至涉及到了关于 BAR 相关逻辑的修改。虽说确实 5090 的 PCB 上有此物理接口,但真的要支持并且用起来,又是另一个级别的工作了。如果此事真的可行,那只能说老黄的刀在当前的 vibe coding 时代,可能要再磨一磨了。