1. 多GPU计算环境搭建
多GPU并行计算的第一步不是写kernel,而是让一台机器上的多块GPU真正“可见、可用、可验证”。很多初学者在运行多卡程序时遇到“只识别到一块卡”“device count为0”“两块卡算力不一致导致通信失败”等问题,根源往往不在代码,而在工具链、驱动、运行时或环境变量。本章围绕CUDA工具链安装、多GPU硬件识别、驱动与运行时配置、设备属性查询、环境验证与常见问题排查展开,目标是搭建一个可复现、可诊断的多GPU开发环境。
一、CUDA工具链的组成与安装
CUDA工具链并非单一软件,而是一组协同工作的组件。理解它们的层次关系,是排查问题的前提。最底层是NVIDIA显卡驱动,它负责操作系统与GPU之间的通信;驱动之上是CUDA运行时(cudart),应用程序通过它调用GPU;再往上是CUDA驱动API(cuda driver API),提供更底层的控制;最上层是nvcc编译器、cuBLAS/cuDNN等库以及nsight调试工具。
在Linux上安装CUDA Toolkit,推荐使用官方runfile或包管理器。以Ubuntu为例,使用包管理器安装的典型流程如下:
# 查看系统与显卡
uname -m
lspci | grep -i nvidia
# 添加NVIDIA包仓库并安装
sudo apt update
sudo apt install -y cuda-toolkit-12-4
# 配置环境变量
export PATH=/usr/local/cuda-12.4/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH
# 验证编译器
nvcc --version
这里有一个关键点:驱动版本与CUDA Toolkit版本必须匹配。CUDA 12.4要求驱动版本不低于525.60.13(Linux)。如果驱动过旧,即使安装了新Toolkit,运行时也会报“CUDA driver version is insufficient for CUDA runtime version”。因此安装前应先执行 nvidia-smi 查看驱动版本,再决定安装哪个CUDA版本。
二、多GPU硬件识别
硬件识别是确认“机器上到底有几块GPU、它们是什么型号”的过程。最直接的工具是 nvidia-smi,它会列出所有NVIDIA GPU的编号、型号、显存、温度、功耗和当前占用进程。执行后应重点关注:GPU数量是否与物理插卡一致、每块卡的型号是否相同、显存容量是否一致。
如果 nvidia-smi 只显示一块卡,而机器上确实插了多块,常见原因包括:BIOS中PCIe插槽被禁用、显卡未插紧、供电不足、或者驱动未正确加载。此时可用 lspci | grep -i nvidia 确认操作系统是否在PCI层面看到了所有卡。如果lspci能看到但nvidia-smi看不到,问题通常在驱动或内核模块。
另一个重要工具是 nvidia-smi topo -m,它展示GPU之间的拓扑连接关系,包括是否通过NVLink互联、是否跨PCIe Switch、是否跨NUMA节点。多GPU通信性能高度依赖这个拓扑:同一PCIe Switch下的卡通信延迟低,跨NUMA节点的卡通信延迟高。后续做多卡任务调度时,应优先把通信密集的任务分配到拓扑距离近的GPU上。
三、驱动与运行时配置
驱动加载后,CUDA运行时通过设备文件与GPU通信。Linux下常见设备节点为 /dev/nvidia0、/dev/nvidia1 等,以及控制节点 /dev/nvidiactl。如果这些节点缺失,运行时将无法枚举设备。可通过以下命令检查:
ls -l /dev/nvidia*
# 正常应看到 nvidia0 nvidia1 nvidiactl nvidia-uvm 等
# 若缺失,可尝试重新加载内核模块
sudo modprobe nvidia
sudo modprobe nvidia_uvm
环境变量也会显著影响多GPU行为。CUDA_VISIBLE_DEVICES 用于限制程序可见的GPU集合,例如 CUDA_VISIBLE_DEVICES=0,1 表示只使用前两块卡。这个变量在任务调度中非常有用:调度器可以通过它把不同任务绑定到不同GPU上,避免相互干扰。注意,设置后程序内部的设备编号会重新从0开始映射,例如原本的GPU 2在设置后可能变成逻辑设备0。
此外,CUDA_DEVICE_ORDER 决定设备枚举顺序。默认是 FASTEST_FIRST,按性能排序;设为 PCI_BUS_ID 则按PCI总线顺序排列。在多卡型号不一致的机器上,建议统一设为 PCI_BUS_ID,使设备编号与物理插槽对应,便于运维和排查。
四、设备属性查询与环境验证
确认硬件可见后,还需要在代码层面查询设备属性,判断每块卡是否满足计算需求。CUDA运行时提供 cudaGetDeviceCount、cudaGetDeviceProperties 等接口。下面是一段典型的多GPU属性查询示例:
#include <cstdio>
#include <cuda_runtime.h>
int main() {
int count = 0;
cudaGetDeviceCount(&count);
printf("GPU count = %d\n", count);
for (int i = 0; i < count; ++i) {
cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, i);
printf("GPU %d: %s, SM %d.%d, SMs=%d, mem=%.1f GB, "
"clock=%.2f GHz, bus=%d\n",
i, prop.name, prop.major, prop.minor,
prop.multiProcessorCount,
prop.totalGlobalMem / 1073741824.0,
prop.clockRate / 1e6,
prop.pciBusID);
}
return 0;
}
编译并运行:
nvcc -o query_gpu query_gpu.cu
./query_gpu
假设输出如下(示例数据,非实测):
GPU count = 2
GPU 0: NVIDIA A100-SXM4-40GB, SM 8.0, SMs=108, mem=39.6 GB, clock=1.41 GHz, bus=3
GPU 1: NVIDIA A100-SXM4-40GB, SM 8.0, SMs=108, mem=39.6 GB, clock=1.41 GHz, bus=4
判断环境是否合格,应关注几个关键属性:major/minor 表示计算能力,决定可用的指令集和特性;multiProcessorCount 决定并行度上限;totalGlobalMem 决定单卡可处理的数据规模;pciBusID 用于与拓扑信息对应。如果两块卡的算力或显存差异过大,做数据并行时会出现“木桶效应”,整体性能受最慢卡限制。
五、常见问题排查思路
环境搭建中最常见的四类问题,可以按“从下到上”的顺序排查。第一类是硬件层:lspci 看不到卡,说明问题在BIOS、PCIe插槽或供电,与CUDA无关。第二类是驱动层:lspci 能看到但 nvidia-smi 报错,通常是内核模块未加载或驱动版本与内核不匹配,可尝试 dmesg | grep -i nvidia 查看内核日志。第三类是运行时层:nvidia-smi 正常但程序报“no CUDA-capable device”,往往是 CUDA_VISIBLE_DEVICES 设置错误或设备节点权限不足。第四类是版本层:程序能跑但报版本不匹配,需要对照NVIDIA官方版本矩阵调整驱动或Toolkit。
PATH 和 LD_LIBRARY_PATH 必须指向同一个版本,否则会出现编译用12.4、运行用11.8的隐蔽错误。完成以上检查后,建议写一个最小验证程序:枚举设备数量、打印每块卡属性、在每块卡上各分配一小块显存并做一次简单kernel调用。如果所有卡都能正确执行且结果一致,说明多GPU环境已经就绪,可以进入后续的多卡任务划分与调度环节。