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
- 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.00000: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.00000: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) |
分配策略:
-
GPU0 优先选择 NIC1(距离 1,PXB)
-
GPU1 也优先选择 NIC1(距离 1,PXB)
-
但 NIC1 被两个 GPU 共享 → 负载均衡调整
-
最终:GPU0→NIC1, GPU1→NIC0(如果 NIC0 负载更轻)