1. CUDA多卡编程基础:GPU拓扑结构认知、多卡通信模式概览、NVIDIA-SMI与nvidia-smi topo -m命令详解

1.1 为什么多卡编程要先看拓扑?

做多卡编程,第一件事不是写代码。

我个人的习惯是,先跑一遍 nvidia-smi topo -m,看看我的GPU们是怎么连在一起的。你想想看,如果两块卡之间走的是PCIe,另一对走的是NVLink,那通信带宽能差一个数量级。不知道这个信息,你写的多卡代码可能从一开始就输在起跑线上。

核心观点:GPU拓扑结构决定了多卡通信的物理路径和带宽上限。不了解拓扑,就无法做正确的通信模式设计。

1.2 GPU拓扑结构认知

什么是GPU拓扑?说白了,就是你的GPU在服务器里怎么摆放、怎么连接。

常见的拓扑结构有这几种:

  • PCIe树形拓扑:所有GPU挂在PCIe Switch下面,通过CPU的PCIe控制器通信。这是最老、最慢的方式。
  • NVLink全互联拓扑:每个GPU通过NVLink直接连到其他GPU,形成全连接网络。这是NVIDIA DGX系列的做法。
  • 混合拓扑:部分GPU之间走NVLink,部分走PCIe。比如一些4卡或8卡的服务器。

我在项目中遇到过一台8卡服务器,看起来是8块A100,但跑多卡训练时发现通信特别慢。一查拓扑,发现只有4块卡之间有NVLink,另外4块只能走PCIe。嗯,这就是典型的混合拓扑坑。

为了让你更直观地理解,我画了一张图:

常见GPU拓扑结构示意图 CPU PCIe Switch GPU 0 GPU 1 GPU 2 GPU 3 PCIe NVLink 全互联拓扑(示例) GPU A GPU B GPU C GPU D PCIe拓扑:GPU通过PCIe Switch连接,带宽受限 NVLink拓扑:GPU之间直连,带宽高、延迟低

1.3 多卡通信模式概览

知道了拓扑,接下来要看通信模式。多卡编程里,GPU之间怎么交换数据?

主要有三种模式:

  1. 点对点通信(P2P):两块GPU直接读写对方显存。前提是它们之间有物理直连路径(比如NVLink或同一PCIe Switch下)。
  2. 集合通信(Collective):多块GPU一起做AllReduce、AllGather等操作。这是分布式训练的核心。
  3. 通过CPU中转:GPU先把数据拷到CPU内存,再拷到另一块GPU。这是最慢的方式,能避免就避免。

我的经验:能用P2P就别走CPU中转。我在一个项目里发现,两块卡之间走P2P延迟只有2微秒,走CPU中转直接飙到50微秒。差了25倍!

1.4 NVIDIA-SMI 命令详解

nvidia-smi 是每个做GPU编程的人最常用的命令。但很多人只用来看看显存占用和温度,其实它能看的东西远不止这些。

我常用的几个参数:

命令 作用 我的用法
nvidia-smi 查看GPU基本信息 看显存、温度、功率、进程
nvidia-smi -q 查询所有GPU详细信息 调试时看时钟频率、PCIe链路状态
nvidia-smi -l 1 每秒刷新一次 监控训练过程中的GPU状态变化
nvidia-smi pmon -c 1 进程监控模式 看哪个进程在占用GPU

注意:我曾经在排查性能问题时,发现一块GPU的PCIe链路宽度只有x8而不是x16。用 nvidia-smi -q -d PCIE 一看,果然是物理连接有问题。这种问题不查nvidia-smi根本发现不了。

1.5 nvidia-smi topo -m 命令详解

这个命令是我做多卡编程时必用的。它输出GPU之间的拓扑关系矩阵。

直接看一个例子:

$ nvidia-smi topo -m
        GPU0    GPU1    GPU2    GPU3    CPU Affinity    NUMA Affinity
GPU0     X      NV2     NV2     PHB     0-31            N/A
GPU1    NV2      X      PHB     PHB     0-31            N/A
GPU2    NV2     PHB      X      NV2     32-63           N/A
GPU3    PHB     PHB     NV2      X      32-63           N/A

这个矩阵怎么看?我来解释一下:

  • X:自己连自己,忽略
  • NV2:通过2条NVLink连接。这是最快的路径。
  • PHB:通过PCIe Host Bridge连接。速度慢一些。
  • PXB:通过PCIe Switch连接。比PHB还慢一点。
  • NODE:跨NUMA节点通信。最慢的情况。

你看上面的例子,GPU0和GPU1之间是NV2,说明它们可以直接高速通信。但GPU1和GPU2之间是PHB,只能走PCIe。如果你写多卡代码时没注意这个,把频繁通信的任务分配给了GPU1和GPU2,那性能肯定上不去。

避坑指南:我曾经在一个8卡训练任务中,默认用了GPU 0-7的连续编号。结果发现GPU 4和GPU 5之间是PHB连接,通信成了瓶颈。后来我手动把通信密集的GPU对分配到同一NVLink域内,训练速度提升了30%。

1.6 如何用拓扑信息指导编程

知道了拓扑,怎么用?我总结了几条原则:

  • 通信密集的任务:优先分配给NVLink连接的GPU对
  • 数据并行训练:尽量让同一节点内的GPU做AllReduce,跨节点走网络
  • 流水线并行:把相邻流水线阶段放在通信快的GPU上
  • 模型并行:把需要频繁交换梯度的层放在同一NVLink域

嗯,这些原则说起来简单,但实际项目中要结合你的具体场景来调整。没有银弹。

1.7 本章小结

这一章我们讲了三个核心内容:

  • GPU拓扑结构:知道你的GPU怎么连的
  • 多卡通信模式:P2P、集合通信、CPU中转
  • nvidia-smi和topo -m命令:怎么查看和解读拓扑信息

说白了,多卡编程的第一步不是写代码,而是搞清楚你的硬件环境。拓扑决定了通信的上限,而你的代码只能逼近这个上限,不可能超越它。

下一章,我们会深入P2P通信的编程实现。到时候你会看到,同样的代码在不同拓扑下跑出来的效果天差地别。