【NVSHMEM】PCIe 距离类型(PIX,PXB,PHB,NOD,SYS)和判断

PCIe 距离类型

PCIe 距离类型(从优到差):

  • PATH_PIX (0): 相同设备,最优

  • PATH_PXB (1): 通过 PCIe 交换机连接

  • PATH_PHB (2): 通过 Host 桥接

  • PATH_NODE (3): 同一 NUMA 节点

  • PATH_SYS (4): 跨系统,最差

通过比较 PCIe 路径字符串的公共前缀长度判断距离。

典型的 PCIe 路径格式

PCIe 路径字符串示例

在 Linux 系统中,GPU 和网卡的 PCIe 路径通常如下:

bash 复制代码
/sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.0
│                         │            │
│                         │            └─ 设备号 (Device)
│                         └─ 总线号 (Bus)
└─── domain:bus 前缀

具体例子(假设我们有一个双 GPU 系统,连接到一个 Mellanox 网卡):

bash 复制代码
GPU 0: /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:02:00.0
GPU 1: /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:03:00.0
NIC:   /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:04:00.0
  1. PCI地址格式 :严格遵循domain:bus:device.function,例如0000:02:00.0
    • 0000 = domain(16位,通常用4位十六进制表示)
    • 02 = bus(8位总线号)
    • 00 = device(5位设备号)
    • 0 = function(3位功能号)

1. 层级结构图示(修正版)

复制代码
`[CPU/Root Complex]
    │
    ├─ Bus 00 (0000:00)          # 根总线(Domain 0000)
    │   └─ Device 02.0 (0000:00:02.0)  # PCIe 桥接器(扩展下级总线)
    │       └─ Bus 02 (0000:02)  # 通过桥接器扩展的下级总线
    │           └─ Device 00.0 (0000:02:00.0)  # 终端设备(如 GPU)
`

2. 关键组件解析

(1) Root Complex(根控制器)
  • 作用:直接连接 CPU 的 PCIe 根控制器,是 PCIe 拓扑的起点。
  • 路径/sys/class/pci_bus/0000:00/ 对应根总线 Bus 00
(2) PCIe 桥接器(Bridge)
  • 标识0000:00:02.0
    • 0000:Domain(通常为 0000)。
    • 00:上级总线(Bus 00)。
    • 02:设备编号(桥接器在 Bus 00 上的设备号)。
    • 0:Function(单功能设备)。
  • 作用
    • Bus 00 的信号转发到下级总线 Bus 02
    • 允许 Bus 02 上的设备独立于 Bus 00(如电源管理、地址空间隔离)。
  • 路径/sys/class/pci_bus/0000:00/device/0000:00:02.0/
(3) 终端设备(Endpoint)
  • 标识0000:02:00.0
    • 0000:02:通过桥接器扩展的下级总线。
    • 00:设备编号(通常是该总线上的第一个设备)。
    • 0:Function。
  • 作用:实际功能设备(如 GPU、网卡、存储控制器)。
  • 路径/sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.0/

3. 验证方法

(1) 使用 lspci 查看拓扑
复制代码
`# 查看桥接器和终端设备的层级关系
lspci -t -s 0000:02:00.0
`

输出示例

复制代码
`-+-[0000:00]-+-02.0  [0000:00:02.0]  # 桥接器
  \-00.0  [0000:02:00.0]              # 终端设备
`
bash 复制代码
# 查看设备0000:02:00.0的上级设备
lspci -t -s 0000:02:00.0

# 示例输出(树形结构)
-+-[0000:00]-+-00.0  [0000:00:00.0]  # 根桥
  \-1c.0  [0000:00:1c.0]  # PCIe桥接器
    \-02.0  [0000:02:00.0]  # 目标设备

路径比较详细示例

示例场景 1:最优情况 - PIX (PATH_PIX)
bash 复制代码
GPU:  /sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.0
NIC:  /sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.1
      └─────────────────────────────┘ └──────────────┘
           相同前缀 (score=5)          不同设备

计算过程:

  • "/" 出现次数 (depth) = 6

  • 相同前缀的" /" 数量 (score) = 5

  • (depth - 1) - score = 0 → (距离 0,最优) PATH_PIX

物理意义: GPU 和网卡在同一个 PCIe 交换机下,直接相连。


示例场景 2:较好情况 - PXB (PATH_PXB)
bash 复制代码
GPU:  /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:02:00.0
NIC:  /sys/class/pci_bus/0000:00/device/0000:00:03.0/0000:05:00.0
      └─────────────────────────────┘ └──┘ └──┘
           相同前缀 (score=4)        不同总线

计算过程:

  • depth = 6

  • score = 4

  • (depth - 1) - score = 1 → (距离 1) PATH_PXB

物理意义: GPU 和网卡通过 PCIe 交换机连接,但不是直连。


示例场景 3:一般情况 - PHB (PATH_PHB)
bash 复制代码
GPU:  /sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.0
NIC:  /sys/class/pci_bus/0000:80/device/0000:80:01.0/0000:82:00.0
      └──────────────────┘ └─┘
         相同前缀 (score=4)  不同 CPU socket

计算过程:

  • depth = 6

  • score = 4(但跨越了不同的 CPU socket)

  • (depth - 1) - score = 2 → (距离 2) PATH_PHB

物理意义: GPU 和网卡在不同的 PCIe root complex 下,通过 Host 桥接。


示例场景 4:较差情况 - NODE/SYS (PATH_NODE 或 PATH_SYS)
复制代码
bash 复制代码
GPU:  /sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.0
NIC:  /sys/class/pci_bus/0000:40/device/0000:40:01.0/0000:41:00.0
      └──────────────┘ └┘
       相同前缀 (score=3)  完全不同

计算过程:

  • depth = 6

  • score = 3 (≤ 3)

  • 检查 NUMA ID:

cpp 复制代码
numaId1 = get_numa_id("/sys/class/pci_bus/0000:00/device/...")  // 返回 0
numaId2 = get_numa_id("/sys/class/pci_bus/0000:40/device/...")  // 返回 1

if (numaId1 == numaId2) return PATH_NODE;  // 同一 NUMA 节点
else return PATH_SYS;                       // 跨 NUMA 节点

物理意义:

  • PATH_NODE (距离 3):同一 NUMA 节点内

  • PATH_SYS (距离 4):跨 NUMA 节点,性能最差


实际字符串比较演示

假设两个具体路径:

bash 复制代码
cuda_path = "/sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:02:00.0"
mlx_path  = "/sys/class/pci_bus/0000:00/device/0000:00:02.0/0000:04:00.0"

逐字符比较过程:

|-----|----------------|---------------|------|-------|-------|
| 位置 | cuda_path[i] | mlx_path[i] | same | '/'计数 | score |
| 0 | '/' | '/' | 1 | 1 | 1 |
| 4 | 's' | 's' | 1 | | |
| ... | ... | ... | ... | | |
| 37 | '/' | '/' | 1 | 2 | 2 |
| 42 | 'd' | 'd' | 1 | | |
| ... | ... | ... | ... | | |
| 59 | '/' | '/' | 1 | 3 | 3 |
| 64 | '0' | '0' | 1 | | |
| ... | ... | ... | ... | | |
| 70 | '/' | '/' | 1 | 4 | 4 |
| 75 | '0' | '4' | 0 | | |
| ... | ... | ... | 0 | 5 | |

结果: score=4, depth=6 → PATH_PHB


在 nvshmemi_get_devices_by_distance 中的应用

假设有 2 个 GPU 和 2 个网卡:

bash 复制代码
GPU0: /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:02:00.0
GPU1: /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:03:00.0
NIC0: /sys/class/pci_bus/0000:00/device/0000:00:01.0/0000:04:00.0
NIC1: /sys/class/pci_bus/0000:00/device/0000:00:05.0/0000:06:00.0

距离矩阵

|------|--------|--------|
| | NIC0 | NIC1 |
| GPU0 | PHB(2) | PXB(1) |
| GPU1 | PHB(2) | PXB(1) |

分配策略:

  1. GPU0 优先选择 NIC1(距离 1,PXB)

  2. GPU1 也优先选择 NIC1(距离 1,PXB)

  3. 但 NIC1 被两个 GPU 共享 → 负载均衡调整

  4. 最终:GPU0→NIC1, GPU1→NIC0(如果 NIC0 负载更轻)

相关推荐
三点水-here25 天前
04 - 分布式大模型推理实战:TP/PP/EP并行策略深度解析
分布式·rdma·nccl·moe·流水线并行·张量并行·专家并行
容沁风2 个月前
lk_llama.cpp启用nccl
nccl·v100·lk_llama.cpp
predawnlove2 个月前
【NCCL】8 PAT AllGather 设备端实现详解3
nccl·通信库
predawnlove2 个月前
【NCCL】4 AllGather-PAT算法
算法·gpu·nccl
predawnlove2 个月前
【NCCL】5 GPU 间链路 Preconnect 机制
gpu·nccl
predawnlove3 个月前
【NCCL】3. ncclPrepareTasks 到 scheduleCollTasksToPlan 的衔接机制
gpu·nccl·通信库
Luchang-Li6 个月前
sglang pytorch NCCL hang分析
pytorch·python·nccl
小马敲马7 个月前
[4.2-2] NCCL新版本的register如何实现的?
开发语言·c++·人工智能·算法·性能优化·nccl
caodongwang10 个月前
【NCCL】transport建立(一)
p2p·rdma·nccl·transport