1. NVLink拓扑发现与设备枚举

任务目标

编写一个C语言程序,枚举系统中所有NVLink设备(GPU与DPU),识别设备类型,并输出设备间的NVLink连接拓扑。程序通过调用NVIDIA Management Library (NVML) API实现设备发现与链路查询,最终以文本形式打印拓扑结构。

环境依赖

  • NVIDIA驱动(支持NVML)已安装
  • NVML开发头文件(通常位于 /usr/local/cuda/include/nvml.h)
  • 链接库:-lnvml
  • 编译环境:gcc

关键完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <nvml.h>

#define MAX_DEVICES 64
#define MAX_LINKS 18

typedef struct {
    nvmlDevice_t handle;
    char uuid[NVML_DEVICE_UUID_BUFFER_SIZE];
    char name[NVML_DEVICE_NAME_BUFFER_SIZE];
    int isDPU;
    int linkCount;
    int linkDevices[MAX_LINKS];
} DeviceInfo;

static int is_dpu(nvmlDevice_t dev) {
    nvmlFieldValue_t val;
    val.fieldId = NVML_FI_DEVICE_TYPE;
    if (nvmlDeviceGetFieldValues(dev, 1, &val) != NVML_SUCCESS)
        return 0;
    return (val.value.uiVal == NVML_DEVICE_TYPE_DPU);
}

int main() {
    if (nvmlInit() != NVML_SUCCESS) {
        fprintf(stderr, "NVML初始化失败,请检查驱动\n");
        return 1;
    }

    unsigned int devCount = 0;
    if (nvmlDeviceGetCount(&devCount) != NVML_SUCCESS || devCount == 0) {
        fprintf(stderr, "未发现任何NVML设备\n");
        nvmlShutdown();
        return 1;
    }
    if (devCount > MAX_DEVICES) devCount = MAX_DEVICES;

    DeviceInfo devices[MAX_DEVICES];
    memset(devices, 0, sizeof(devices));

    // 枚举所有设备
    for (unsigned int i = 0; i < devCount; i++) {
        nvmlDevice_t dev;
        if (nvmlDeviceGetHandleByIndex(i, &dev) != NVML_SUCCESS)
            continue;
        devices[i].handle = dev;
        nvmlDeviceGetUUID(dev, devices[i].uuid, NVML_DEVICE_UUID_BUFFER_SIZE);
        nvmlDeviceGetName(dev, devices[i].name, NVML_DEVICE_NAME_BUFFER_SIZE);
        devices[i].isDPU = is_dpu(dev);
    }

    // 查询NVLink连接关系
    for (unsigned int i = 0; i < devCount; i++) {
        if (devices[i].handle == NULL) continue;
        for (unsigned int link = 0; link < NVML_NVLINK_MAX_LINKS; link++) {
            nvmlPciInfo_t pci;
            if (nvmlDeviceGetNvLinkRemotePciInfo(devices[i].handle, link, &pci) == NVML_SUCCESS) {
                // 通过PCI bus ID匹配远端设备
                for (unsigned int j = 0; j < devCount; j++) {
                    if (j == i || devices[j].handle == NULL) continue;
                    nvmlPciInfo_t localPci;
                    nvmlDeviceGetPciInfo(devices[j].handle, &localPci);
                    if (strcmp(pci.busId, localPci.busId) == 0) {
                        devices[i].linkDevices[devices[i].linkCount++] = j;
                        break;
                    }
                }
            }
        }
    }

    // 输出拓扑
    printf("=== NVLink拓扑发现结果 ===\n");
    printf("发现设备总数: %u\n\n", devCount);

    for (unsigned int i = 0; i < devCount; i++) {
        if (devices[i].handle == NULL) continue;
        printf("[%u] %s\n", i, devices[i].name);
        printf("    UUID: %s\n", devices[i].uuid);
        printf("    类型: %s\n", devices[i].isDPU ? "DPU" : "GPU");
        printf("    NVLink连接: ");
        if (devices[i].linkCount == 0) {
            printf("无\n");
        } else {
            for (int k = 0; k < devices[i].linkCount; k++) {
                int peer = devices[i].linkDevices[k];
                printf("%s[%u](%s)%s",
                       k == 0 ? "" : ", ",
                       peer,
                       devices[peer].name,
                       k == devices[i].linkCount - 1 ? "\n" : "");
            }
        }
    }

    nvmlShutdown();
    return 0;
}

编译与运行

gcc -o nvlink_topology nvlink_topology.c -lnvml
sudo ./nvlink_topology

需要root权限以访问NVML设备信息。

验证方法

程序运行后,观察输出是否满足以下条件即判定通过:

  • 输出设备总数与 nvidia-smi 中看到的GPU数量一致(若系统无DPU,则全部显示为GPU)
  • 每台设备均显示正确的类型(GPU或DPU)
  • 存在NVLink连接的设备对,在彼此的连接列表中互相可见(例如设备0连接列表包含设备1,设备1连接列表也包含设备0)
  • 对于无NVLink连接的设备,显示“无”

可通过 nvidia-smi nvlink -s 命令交叉验证实际链路连接关系,程序输出的连接对应与之一致。

常见问题

  • NVML初始化失败:确认驱动已安装且版本匹配,运行 nvidia-smi 能正常输出
  • 编译找不到nvml.h:确认CUDA工具包已安装,必要时添加 -I/usr/local/cuda/include
  • 链接错误:确认 libnvml.so 在库搜索路径中,可添加 -L/usr/local/cuda/lib64
  • DPU识别不准确:部分早期DPU可能不支持 NVML_FI_DEVICE_TYPE 字段,此时会误判为GPU,可结合设备名称关键字(如包含“BlueField”)辅助判断