1. NVLink拓扑发现与可视化
任务说明
本案例目标为:在装有NVIDIA GPU(Volta及以上架构,支持NVLink)的服务器上,通过系统工具与NVML库识别GPU之间的NVLink连接关系,并生成拓扑可视化图。最终交付物为一张反映真实物理连接的拓扑图(SVG格式)及对应的连接关系清单。
实现方案
采用两层实现:命令行快速探测(使用nvidia-smi)与NVML编程接口(获取精确的P2P链路与带宽信息)。两者互为验证,确保拓扑数据准确。核心逻辑为:枚举所有GPU设备,对每一对GPU查询NVML的P2P链路状态与带宽,筛选出有效NVLink连接,再输出为Graphviz DOT格式并渲染为SVG。
代码实现
#!/usr/bin/env python3
"""
NVLink拓扑发现与可视化工具
依赖: pynvml (pip install pynvml), graphviz (apt install graphviz 或 pip install graphviz)
"""
import os
import subprocess
import tempfile
from graphviz import Digraph
from pynvml import (
nvmlInit, nvmlShutdown, nvmlDeviceGetHandleByIndex,
nvmlDeviceGetCount, nvmlDeviceGetName,
nvmlDeviceGetP2PStatus, nvmlDeviceGetP2PLinkInfo,
NVML_P2P_CAPS_INDEX_LINK, NVML_P2P_LINK_STATUS_ACTIVE,
NVML_P2P_CAPS_INDEX_PCI, NVML_P2P_CAPS_INDEX_PHYSICAL
)
def get_nvlink_topology():
"""通过NVML获取NVLink连接矩阵"""
nvmlInit()
try:
device_count = nvmlDeviceGetCount()
if device_count < 2:
print("检测到少于2块GPU,无法构成NVLink拓扑")
return None
# 收集设备信息
devices = []
for i in range(device_count):
handle = nvmlDeviceGetHandleByIndex(i)
name = nvmlDeviceGetName(handle)
devices.append({'index': i, 'name': name, 'handle': handle})
# 构建连接矩阵
links = [] # (gpu_i, gpu_j, link_count, bandwidth_MBps)
for i in range(device_count):
for j in range(i+1, device_count):
handle_i = devices[i]['handle']
handle_j = devices[j]['handle']
try:
# 查询P2P链路状态
status = nvmlDeviceGetP2PStatus(
handle_i, handle_j, NVML_P2P_CAPS_INDEX_LINK
)
if status == NVML_P2P_LINK_STATUS_ACTIVE:
# 获取链路信息(NVLink链路数与总带宽)
link_info = nvmlDeviceGetP2PLinkInfo(
handle_i, handle_j,
NVML_P2P_CAPS_INDEX_LINK
)
# link_info包含num_links与pcie_path等信息
# 此处简化:每条NVLink链路带宽为25GB/s(H100)或根据设备调整
# 实际带宽可从link_info中解析,但不同驱动版本字段不同
# 为保持兼容,使用nvidia-smi辅助获取带宽
links.append((i, j, link_info.num_links))
except Exception as e:
# 部分驱动不支持P2P状态查询,回退到nvidia-smi
print(f"NVML查询GPU {i}-{j}失败: {e},尝试nvidia-smi")
pass
# 使用nvidia-smi补充带宽信息(更稳定)
try:
smi_output = subprocess.check_output(
['nvidia-smi', 'nvlink', '-s'], text=True
)
# 解析nvidia-smi输出补充带宽
# 格式示例: GPU0-GPU1: 4 NVLink Links, 100.0 GB/s
for line in smi_output.splitlines():
if 'NVLink Links' in line:
parts = line.split(':')[0].strip() # "GPU0-GPU1"
gpu_pair = parts.split('-')
if len(gpu_pair) == 2:
g0 = int(gpu_pair[0].replace('GPU', ''))
g1 = int(gpu_pair[1].replace('GPU', ''))
bw_str = line.split(',')[1].replace('GB/s', '').strip()
bw = float(bw_str)
# 更新links中的带宽
for idx, (a, b, _) in enumerate(links):
if (a == g0 and b == g1) or (a == g1 and b == g0):
links[idx] = (a, b, bw)
except Exception as e:
print(f"nvidia-smi带宽获取失败: {e},使用默认带宽")
return devices, links
finally:
nvmlShutdown()
def render_topology(devices, links, output_file='nvlink_topology'):
"""生成SVG拓扑图"""
if not devices or not links:
print("无有效拓扑数据")
return
dot = Digraph(comment='NVLink Topology', format='svg')
dot.attr(rankdir='LR', bgcolor='#1a1a2e')
dot.attr('node', shape='box', style='filled',
fontname='Helvetica', fontsize='12')
# 添加GPU节点
for dev in devices:
label = f"GPU{dev['index']}\n{dev['name']}"
dot.node(f"gpu{dev['index']}", label,
fillcolor='#16213e', fontcolor='#e94560')
# 添加NVLink连接
for (i, j, bw) in links:
# 带宽单位:GB/s(来自nvidia-smi)或链路数(来自NVML)
if isinstance(bw, float):
label = f"NVLink\n{bw:.0f} GB/s"
else:
label = f"NVLink\n{bw} links"
dot.edge(f"gpu{i}", f"gpu{j}", label=label,
color='#00d2ff', penwidth='2')
# 渲染SVG
dot.render(output_file, cleanup=True)
print(f"拓扑图已生成: {output_file}.svg")
def verify_with_nvidia_smi():
"""使用nvidia-smi验证拓扑(独立验证路径)"""
print("=== nvidia-smi NVLink 验证 ===")
try:
result = subprocess.run(
['nvidia-smi', 'nvlink', '-s'],
capture_output=True, text=True, check=True
)
print(result.stdout)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"nvidia-smi执行失败: {e.stderr}")
return None
if __name__ == '__main__':
# 第一步:nvidia-smi快速验证
smi_result = verify_with_nvidia_smi()
# 第二步:NVML精确拓扑
result = get_nvlink_topology()
if result:
devices, links = result
print("\n=== NVML检测到的NVLink连接 ===")
for (i, j, bw) in links:
if isinstance(bw, float):
print(f"GPU{i} <-> GPU{j}: {bw:.1f} GB/s")
else:
print(f"GPU{i} <-> GPU{j}: {bw}条链路")
render_topology(devices, links)
else:
print("NVML未检测到有效拓扑,请检查GPU是否支持NVLink")
运行与验证
运行方式:
# 安装依赖
pip install pynvml graphviz
# Ubuntu/Debian需安装graphviz系统包: sudo apt install graphviz
# 执行脚本
python3 nvlink_topology.py
验证方法与判定标准:
- 命令行验证:执行
nvidia-smi nvlink -s,观察输出中是否列出GPU间的NVLink连接及带宽。若输出为空或提示“不支持”,则当前环境无NVLink或驱动不支持。 - 脚本输出验证:运行脚本后,终端应打印出与
nvidia-smi一致的GPU连接对(编号与带宽数值需匹配)。若NVML与nvidia-smi结果不一致,以nvidia-smi为准排查驱动问题。 - SVG产物验证:打开生成的
nvlink_topology.svg,图中节点数应等于nvidia-smi -L列出的GPU数量;每一条边对应一条实际NVLink连接,且边上的带宽标注与nvidia-smi nvlink -s输出一致。 - 判定通过标准:SVG中展示的连接关系与
nvidia-smi nvlink -s输出的连接对完全一致(GPU编号、连接存在性),且无遗漏或多余连接。
拓扑可视化示例
下图展示4块GPU通过NVLink全互联的典型拓扑(实际生成图取决于当前服务器硬件):
说明:上图为4块A100 GPU全互联拓扑示意。实际执行脚本时,SVG将根据当前服务器真实硬件生成——若GPU间无NVLink物理连接,则对应边不会出现;若为部分互联(如仅相邻GPU连接),则边数相应减少。判定拓扑正确性的唯一依据是nvidia-smi nvlink -s的实测输出与SVG边集合完全一致。