
1. 这个报错到底在说什么先读懂 CUDA 设备不匹配问题很多第一次遇到 cuda2 but found one of them on device cuda 0 的朋友第一反应是懵的。明明代码里写了用 GPU报错却只说了一半后半句还指向另一个设备编号让人摸不着头脑。先把这个错误翻译成人话你的代码或者某个深度学习框架最常见的是 PyTorch试图在cuda:2这个设备上加载或者运行某个张量、模型参数但实际检测发现这个张量、模型参数已经存在于cuda:0上两边对不上于是直接抛异常。这类报错的完整形式通常是这样的RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:2 and cuda:0!或者是RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.device_count() is 1. Please use torch.load with map_location to map your storages to an existing device.不管哪种形式本质都是同一个问题设备号不匹配。这里要先理清一个非常容易混淆的概念——cuda:0、cuda:1、cuda:2到底代表什么。在你的机器上如果你用nvidia-smi查看会看到一块或者多块 GPU。cuda:0通常对应第一块 GPUcuda:1对应第二块以此类推。但这里有个坑CUDA 的设备编号并不总是等于物理 GPU 的编号它取决于 CUDA 运行时的设备枚举顺序而这个顺序又受环境变量CUDA_VISIBLE_DEVICES的影响。比如你设置了CUDA_VISIBLE_DEVICES2,3那么在你程序里cuda:0对应的是物理上的 2 号卡cuda:1对应物理上的 3 号卡。也就是说你在代码里写的cuda:2在这个环境下根本不存在因为可见设备只有两张编号只有 0 和 1。这就是为什么很多人明明有两张卡代码里却写了cuda:2一运行就报错。这个问题在多卡机器、容器环境、远程服务器上尤其常见因为你在终端里看到的环境变量和你代码运行时的环境变量可能完全不一样。还有一种情况单卡机器上某些预训练模型的权重文件保存时用了多卡训练权重文件里记录了module.cuda:2之类的设备信息加载时你只有一张卡自然就报设备不存在。所以解决这个问题的第一步不是到处去找代码里的每个.cuda()调用而是先搞清楚你的机器到底有几张卡、当前进程能看到几张卡、代码运行时用的设备编号是什么。这三件事确认清楚了问题基本就解决了一半。2. 先查环境再动代码设备可见性与 CUDA 版本排查清单遇到设备相关报错我的一贯建议是先别急着改代码先做一轮环境体检。很多设备报错根本不是代码逻辑的问题而是环境配置的问题。2.1 确认当前机器到底有几张 GPU在终端里执行nvidia-smi这个命令会列出所有物理 GPU包括型号、显存占用、驱动版本、CUDA 版本。接着在 Python 里确认 PyTorch 实际能看到几张卡import torch print(torch.cuda.is_available()) print(torch.cuda.device_count()) print(torch.cuda.get_device_name(0))如果你在 Python 里看到的设备数和你nvidia-smi看到的物理 GPU 数不一致那基本可以确定是CUDA_VISIBLE_DEVICES环境变量在影响可见性。查看当前环境变量echo $CUDA_VISIBLE_DEVICES在 Windows 上则是echo %CUDA_VISIBLE_DEVICES%2.2 理解 CUDA_VISIBLE_DEVICES 的映射规则这个环境变量是 CUDA 设备管理中最重要的机制没有之一。假设你的机器有 4 张 GPU物理编号是 0、1、2、3。你设置了export CUDA_VISIBLE_DEVICES2,3那么你的程序里cuda:0对应物理 GPU 2cuda:1对应物理 GPU 3不存在cuda:2因为可见设备只有两张这个映射关系是很多人犯错的重灾区。尤其是多卡训练时看了别人的代码直接抄代码里写死了cuda:0、cuda:1结果自己机器上物理 GPU 编号顺序不一样或者用了容器设备枚举顺序变了直接就崩。还有一种情况是代码里用了torch.cuda.set_device(2)但你没设置CUDA_VISIBLE_DEVICES机器上却只有一张物理 GPU那就会报device 2 does not exist之类的错误。2.3 检查 CUDA Toolkit 和 PyTorch 版本兼容性设备报错有时候会掩盖版本问题。PyTorch 对 CUDA 版本是有要求的比如 PyTorch 2.x 需要 CUDA 11.7 或更高版本。如果你的 PyTorch 是用 CUDA 12.1 编译的但系统里实际安装的驱动只支持到 CUDA 11.4那有些显存操作就会出问题报错的方式千奇百怪。检查当前 CUDA 版本nvcc --version检查 PyTorch 编译时的 CUDA 版本import torch print(torch.version.cuda)检查驱动支持的 CUDA 版本nvidia-smi | grep CUDA Version这里重点强调一下驱动支持的 CUDA 版本必须大于等于 PyTorch 编译时使用的 CUDA 版本否则会出问题。驱动是向下兼容的高版本驱动可以运行低版本 CUDA 的程序反过来不行。2.4 容器环境下的特殊排查点如果你在 Docker 或者 Kubernetes 环境里跑还要额外注意nvidia-container-toolkit是否安装正确。容器里nvidia-smi能看到 GPU 不代表一切正常还要确认容器里能访问 GPU 的计算能力。最简单的测试方法是在容器里跑一段 PyTorch 代码import torch x torch.randn(3, 3).cuda() print(x)如果这段代码能正常运行并输出张量说明 GPU 环境基本可用。如果报错那问题可能出在容器配置层面而不是代码本身。注意容器环境下的CUDA_VISIBLE_DEVICES映射规则和物理机上一样但容器编排工具比如 K8s 的资源调度可能会自动设置这个变量你在代码里自定义设备编号前先打印一下环境变量看清楚再动手。2.5 一个快速定位问题的脚本我平时排查这类问题会直接跑一个综合检查脚本一次性把关键信息打全#!/bin/bash echo nvidia-smi nvidia-smi echo CUDA_VISIBLE_DEVICES echo $CUDA_VISIBLE_DEVICES echo nvcc version nvcc --version echo python torch info python -c import torch; print(torch version:, torch.__version__); print(cuda available:, torch.cuda.is_available()); print(device count:, torch.cuda.device_count()); print(device name:, torch.cuda.get_device_name(0) if torch.cuda.is_available() else N/A)跑完这个脚本80% 的设备报错原因就清楚了。3. 代码层面根治设备不匹配tensor 和 model 的常见坑位环境没问题的情况下报错就出在代码逻辑上。设备不匹配的代码问题归根结底就一句话手脚不一致。创建张量的时候在cuda:0用的时候却被要求放到cuda:2或者模型在cuda:0输入数据却在cuda:2两者碰面的时候框架检查发现设备不同直接报错。3.1 用 device 变量统一管理设备而不是硬编码最经典的反面教材就是代码里到处写.cuda()model MyModel().cuda(2) # 写到 cuda:2 data data.cuda() # 默认放到 cuda:0 output model(data) # 报错正确做法是定义一个统一的设备变量然后用它来管理所有设备相关的操作import torch device torch.device(cuda:0 if torch.cuda.is_available() else cpu) model MyModel().to(device) data data.to(device) output model(data)这样做的好处是代码里只有一个地方控制设备改环境的时候只需要改一个变量就行不用到处找.cuda()。3.2 张量创建时的设备指定很多人会在创建张量的时候忽略了设备参数# 错误写法默认在 CPU 上创建 mask torch.zeros(128, 128) # 正确写法直接指定设备 mask torch.zeros(128, 128, devicedevice)或者用torch.ones_like、torch.zeros_like等函数创建新张量时如果like的源张量在 GPU 上新张量也会自动在 GPU 上这个倒是没问题。但如果你混用了不同设备的源张量a torch.zeros(3, 3, devicecuda:0) b torch.zeros(3, 3, devicecuda:1) c a b # 报错两个张量在不同设备这种跨设备的运算操作框架是拒绝执行的因为显存数据没法直接跨卡求和。3.3 多卡训练时的设备分配策略使用torch.nn.DataParallel或DistributedDataParallel时设备管理更要小心。DataParallel的用法相对简单model MyModel() model torch.nn.DataParallel(model, device_ids[0, 1, 2])然后你的模型会被复制到多张卡上但输入数据只需要放在主卡默认device_ids[0]也就是cuda:0上即可DataParallel会自动把数据分发到其他卡上。反而是如果你手动把数据放到了cuda:1或cuda:2可能会出奇怪的问题。DistributedDataParallel的坑更多因为每个进程负责一张卡你需要通过dist.init_process_group和torch.cuda.set_device(local_rank)来正确设置当前进程使用的设备。很多人写代码时直接把其他单卡教程里的.cuda()搬到多卡场景结果就是设备的对应关系全乱了。3.4 检查并定位代码中所有设备操作如果你用的是 PyTorch可以在代码里加一行环境变量来帮助排查export CUDA_LAUNCH_BLOCKING1这个变量会让 CUDA 操作同步执行报错时能给出更精确的调用栈方便你定位到底哪一行触发了设备问题。另外在模型 forward 函数里添加设备检查也是一个好习惯def forward(self, x): print(fx device: {x.device}) print(fmodel device: {next(self.parameters()).device}) return self.conv(x)把输入数据的设备号和各层参数的设备号打出来一目了然。3.5 模型加载时的设备映射如果你加载的是别人训练好的权重特别是用多卡训练的模型经常会在加载时碰到设备错误。典型报错就是文章开头提到的那个。解决办法是用map_location参数# 加载时把所有张量映射到 cuda:0 checkpoint torch.load(model.pth, map_locationcuda:0) # 或者映射到 CPU 再转到目标设备 checkpoint torch.load(model.pth, map_locationcpu) model.load_state_dict(checkpoint[state_dict]) # 更推荐的做法先确认当前设备再动态传入 device torch.device(cuda if torch.cuda.is_available() else cpu) checkpoint torch.load(model.pth, map_locationdevice)map_location的作用就是把权重文件里的设备信息重写到目标设备上相当于给权重文件里的张量做了一次 搬家。这是解决权重和设备不匹配最直接有效的手段。4. 单卡机器跑多卡模型的完整自救指南接着上一节说单卡机器加载多卡训练权重这是所有设备报错里遇到频率最高的一种。其实解法很简单但很多人不知道或者知道了却用错了。多卡训练时PyTorch 会默认给模型包一层module权重文件的键名会从conv.weight变成module.conv.weight。加载到单卡环境时模型结构里没有module这个前缀load_state_dict就会因为键名对不上而报错。解法是加载时去掉module前缀checkpoint torch.load(model.pth, map_locationcpu) # 去除 module 前缀 from collections import OrderedDict new_state_dict OrderedDict() for k, v in checkpoint[state_dict].items(): name k[7:] if k.startswith(module.) else k new_state_dict[name] v model.load_state_dict(new_state_dict)这段代码的逻辑是遍历权重文件里所有键值对如果键名以module.开头就截掉前 7 个字符再加载到模型里。另一种更省事的办法是在保存权重的时候就不带module前缀# 保存时取 model 而不是 model.module torch.save({ state_dict: model.module.state_dict() if isinstance(model, torch.nn.DataParallel) else model.state_dict(), }, model.pth)这个方法一行代码解决但需要你在保存权重的时候就想好后续要兼容单卡加载。我之前在实际项目里遇到过一个问题权重文件里既有module.前缀的键又有不带前缀的键混合在一起直接用上面的循环处理会把不带前缀的键也截掉前 7 个字符把键名截坏了。所以更稳妥的写法是加一个判断new_state_dict {k.replace(module., ) if k.startswith(module.) else k: v for k, v in checkpoint[state_dict].items()}这样只会处理以module.开头的键其他键原样保留。提示除了权重键名前缀问题多卡模型的 buffer比如 BatchNorm 里的running_mean也可能带上module.前缀处理逻辑是一样的上面的代码已经覆盖了。5. 热词背后隐藏的 CUDA 安装与卸载高频问题文章开头列了一长串 CUDA 相关的热搜词我看了下其中出镜率最高的其实是环境安装问题。这些安装帖子和我们的设备报错有一定关联——很多时候设备报错的根源就是 CUDA 和 PyTorch 的版本不匹配。所以这里把安装、卸载、多版本共存这几个高频痛点一次讲透。5.1 为什么会有.run安装报错热搜词里有一条很典型cuda .run gzip: stdin: invalid compressed>export PATH/usr/local/cuda-12.1/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH一个常用的做法是写一个切换脚本# ~/switch_cuda.sh #!/bin/bash export PATH/usr/local/cuda-$1/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda-$1/lib64:$LD_LIBRARY_PATH使用的时候source ~/switch_cuda.sh 11.3另外/usr/local/cuda这个软链接比较特殊很多工具链默认指向它。切换版本时可以顺手把这个软链接指到当前要用的版本sudo ln -sfn /usr/local/cuda-12.1 /usr/local/cuda5.3 彻底卸载 CUDA避免残留热搜词里还有cuda卸载。这个看似简单其实不少人栽在残留问题上。如果是用.run文件安装的可以用自带的卸载脚本sudo /usr/local/cuda/bin/cuda-uninstaller或者找到安装目录下的卸载脚本执行。如果是用包管理器安装的# Ubuntu sudo apt-get --purge remove *cuda* *cublas* *cufft* *cufile* *curand* *cusolver* *cusparse* *gds-tools* *npp* *nvjpeg* nsight* *nvvm*卸载之后检查一下/usr/local/下是否还有残留的 CUDA 目录以及~/.bashrc里是否还留着旧的 CUDA 路径有就一并清理。5.4 安装 CUDA 之后 PyTorch 依然不可用怎么办很多人按照教程装完 CUDA跑nvcc --version也正常但 PyTorch 里torch.cuda.is_available()还是返回False。这种情况最常见的原因是你只装了 CUDA Toolkit但 PyTorch 是 CPU 版本。PyTorch 官方对 CPU 版和 GPU 版的区分在安装命令上装错版本的人不在少数。检查当前 PyTorch 是否为 GPU 版import torch print(torch.__version__) print(torch.version.cuda)如果torch.version.cuda是None说明你装的是 CPU 版。需要卸载后重新安装 GPU 版pip uninstall torch pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121第二个命令里的cu121表示 CUDA 12.1 版本具体参数根据你需要的 CUDA 版本选择。5.5 WSL2 安装 CUDA 的注意点热搜词里有两条和 WSL 相关wsl安装cuda、wsl2安装cuda。WSL2 里跑 GPU 加速现在很成熟了但有几点要注意不需要在 WSL2 里安装 NVIDIA 驱动直接使用 Windows 侧的驱动即可需要安装 CUDA Toolkit for WSL不要装 Linux 原生版装完后在 WSL2 里跑nvidia-smi应该能正常显示 GPU 信息。如果nvidia-smi在 WSL 里看不到 GPU先确认 Windows 侧的显卡驱动是否更新到最新版本然后重启 WSLwsl --shutdown再重新进入 WSL。5.6 检查 CUDA、cuDNN 版本组合的命令热搜词里有查看 cuda cudnn 版本。这个简单一次性命令收好# 查看 CUDA 版本 nvcc --version # 查看 cuDNN 版本 cat /usr/include/x86_64-linux-gnu/cudnn_version.h | grep CUDNN_MAJOR -A 2Windows 下查看 cuDNN 版本可以这样python -c import cudnn; print(cudnn.__version__)6. 常见相邻报错Kernel Errors、ADB Device 等周边排查设备相关的报错不止 CUDA 一个方向。热搜词里还躺着一批看似无关、实则同源的设备类问题这里选几个有代表性的说一下因为它们经常和 CUDA 问题一起出现容易混淆排查方向。6.1 CUDA kernel errors 和异步报错的定位技巧热搜词里有一条cuda kernel errors might be完整的报错是CUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect.这个报错本身不是原因而是结果。CUDA 的 kernel 执行是异步的kernel 内部出错后错误不会立刻抛出而是等到下一个同步点比如.item()、.cpu()、.detach().numpy()才报告。所以当你看到这个报错时真正的错误发生位置可能和堆栈信息显示的完全不在同一个地方。定位这种问题的方法启用CUDA_LAUNCH_BLOCKING1强制同步执行让错误在发生的那一行就抛出来在可疑的 kernel 调用后面加torch.cuda.synchronize()手动触发同步检查是否有非法内存访问、数组越界、NaN 值等情况这些最容易引发异步 kernel 错误。6.2nvidia-smi: Unable to determine the device handle的含义nvidia-smi unable to determine the device handle for gpu0000:41:00.0: unknown error这个报错说明 NVIDIA 驱动和 GPU 之间的通讯出了问题。常见原因有显卡驱动损坏或者没装好GPU 进入了异常状态需要重启机器虚拟机直通模式下 GPU 状态异常GPU 掉驱动了。排查手段就是先重启机器重启后如果还报错重装显卡驱动。如果是服务器检查 GPU 是否因为过热或者供电问题被强制下线。6.3 ADB 设备未授权的处理热搜词里的adb.exe: device unauthorized, 这个一般不是 CUDA 的问题但如果你在开发环境里同时跑移动端推理会遇到。原因就是手机没开启 USB 调试授权。在手机上弹窗时点允许或者重新插拔 USB 线。6.4device ens33和device association service等系统级设备问题这些和 CUDA 没有直接关系但都属于设备报错的大范畴一并说下。device ens33 not available because device has no carrier虚拟机的网卡没有连接检查虚拟网络配置device association service占内存Windows 设备关联服务占用过高可以在服务管理器里临时停止但不建议直接禁用会影响外设管理apple mobile device 服务未启动错误 1053iTunes 相关服务未正常启动重装 Apple Mobile Device Support 组件default boot device missing or boot failed硬盘启动项丢失进 BIOS 检查启动顺序inaccessible boot deviceWindows 启动时无法访问系统盘通常是磁盘驱动问题进入安全模式修复。这些报错和 CUDA 设备报错虽然技术栈不同但排查思路类似先确认硬件状态再查驱动最后查软件配置。7. 压箱底的排查顺序和几个能直接抄的解决方案文章最后分享一套我自己的排查顺序以及几个可以直接复制的解决方案模板按顺序操作大概率能解决大部分 CUDA 设备不匹配问题。7.1 我的排查顺序第一步跑综合检查脚本第 2 节那个确认环境基本信息 第二步确认CUDA_VISIBLE_DEVICES设置搞清楚代码里每个设备编号实际指向哪张卡 第三步检查代码里所有.cuda()和.to(device)操作统一设备变量 第四步检查模型加载逻辑用map_location处理权重文件的设备映射 第五步如果是多卡权重加载到单卡处理module.前缀问题 第六步确认 PyTorch 是 GPU 版、CUDA 版本与驱动兼容。7.2 代码模板一统一的设备管理import torch # 全局统一设备 device torch.device(cuda:0 if torch.cuda.is_available() else cpu) # 模型的设备管理 def get_device(): return device def to_device(data, target_deviceNone): if target_device is None: target_device device if isinstance(data, (list, tuple)): return [to_device(x, target_device) for x in data] return data.to(target_device, non_blockingTrue)这套模板把设备管理封装成函数之后在数据加载、模型 forward 前后统一调用避免散落的.cuda()。7.3 代码模板二兼容单卡和多卡加载import torch def load_model_weights(model, checkpoint_path, devicecuda:0): checkpoint torch.load(checkpoint_path, map_locationdevice) if state_dict in checkpoint: state_dict checkpoint[state_dict] else: state_dict checkpoint # 移除 module. 前缀 fixed_state_dict {} for k, v in state_dict.items(): new_key k.replace(module., ) if k.startswith(module.) else k fixed_state_dict[new_key] v model.load_state_dict(fixed_state_dict) return model这个模板在加载时做了两层保险一层是map_location把权重搬到目标设备另一层是处理module.前缀无论权重来自单卡还是多卡训练都能加载。7.4 代码模板三多卡训练时的设备分配import os import torch import torch.distributed as dist def setup_distributed(local_rank): dist.init_process_group(backendnccl) torch.cuda.set_device(local_rank) device torch.device(cuda, local_rank) return device def cleanup(): dist.destroy_process_group()配合启动命令torchrun --nproc_per_nodeNUM_GPUS train.py使用torchrun启动后local_rank会自动传入每个进程管理一张卡设备和进程一一对应不会出现设备错乱。7.5 环境变量速查表环境变量作用常用值CUDA_VISIBLE_DEVICES控制 CUDA 可见的 GPU0,0,1,2,3CUDA_LAUNCH_BLOCKING让 CUDA kernel 同步执行帮助定位错误1CUDA_CACHE_DISABLE禁用 CUDA kernel 缓存调试用1TORCH_DISTRIBUTED_DEBUG分布式训练调试输出DETAILNCCL_DEBUGNCCL 通信库调试输出INFO,WARN7.6 最后一个建议显存不够也会触发设备问题最后补充一个容易忽略的场景。有时候你代码逻辑全对设备编号也对但依然报错。这时看下是不是显存爆了。显存不足时分配失败PyTorch 有时会报out of memory有时会报其他奇怪的异常容易被误判为设备问题。nvidia-smi看一下显存占用情况如果快满了要么减小 batch size要么清理残留进程nvidia-smi | grep python kill -9 pid我自己在一台 8 卡机器上调代码时曾因为之前跑的进程没清理干净占了 7 张卡新任务只能申请到一张且编号还是乱的当时排查了很久设备映射最后发现是显存不足导致调度器给了不一样的卡。这种低级错误花两分钟看一眼显存就能避开。CUDA 设备相关的报错绝大多数都不是什么高深难题核心就是把环境、设备可见性、代码这三层按顺序排查清楚。按文章里的步骤走一遍大部分问题都能当场解决。如果照着排查完还搞不定建议把nvidia-smi、echo $CUDA_VISIBLE_DEVICES、torch.cuda.device_count()、torch.version.cuda这几条命令的输出贴到社区求助信息够全别人也能快速帮你定位。