【Bug已解决】How to solve “RuntimeError: CUDA error: invalid device ordinal“ 解决方案

发布时间:2026/8/31 10:02:36
【Bug已解决】How to solve “RuntimeError: CUDA error: invalid device ordinal“ 解决方案 【Bug已解决】How to solve RuntimeError: CUDA error: invalid device ordinal 解决方案问题描述在 PyTorch 深度学习开发中当使用多 GPU 环境或进行 GPU 设备管理时开发者经常会遇到以下错误RuntimeError: CUDA error: invalid device ordinal这个错误的字面含义是无效的设备序号即 PyTorch 试图访问一个不存在的 GPU 设备。device ordinal指的是 GPU 的编号如cuda:0,cuda:1等当指定的编号超出了系统实际可用的 GPU 数量时就会触发这个错误。这个错误通常在以下场景中出现代码中硬编码了 GPU 编号但实际运行的机器没有那么多 GPU使用torch.cuda.device()上下文管理器指定了错误的设备号分布式训练中local_rank配置错误在没有 GPU 的机器上运行 GPU 代码CUDA 驱动或 GPU 热插拔导致设备数量变化Docker 容器中 GPU 可见性配置错误多进程环境中 GPU 设备号冲突错误复现import torch # # 错误复现invalid device ordinal # # 场景1访问不存在的 GPU print(f可用 GPU 数量: {torch.cuda.device_count()}) try: # 假设只有 1 个 GPU (cuda:0)但试图访问 cuda:1 device torch.device(cuda:1) x torch.tensor([1.0]).to(device) except RuntimeError as e: print(f错误: {e}) # 场景2在上下文管理器中指定错误设备 try: with torch.cuda.device(5): # 假设没有 5 号 GPU x torch.tensor([1.0]).cuda() except RuntimeError as e: print(f错误: {e}) # 场景3CUDA_VISIBLE_DEVICES 配置错误 import os os.environ[CUDA_VISIBLE_DEVICES] 0,1,2 # 假设只有 1 个 GPU # 重新导入 torch 后试图访问 cuda:2 会失败 # 场景4DataParallel 中 GPU 数量不足 import torch.nn as nn model nn.Linear(10, 5) try: # 如果只有 1 个 GPU但指定了 device_ids[0,1,2] model nn.DataParallel(model, device_ids[0, 1, 2]) x torch.randn(32, 10).cuda() model(x) except RuntimeError as e: print(fDataParallel 错误: {e}) # 场景5DistributedDataParallel 中 local_rank 错误 try: # local_rank 超出 GPU 数量 torch.cuda.set_device(3) # 假设没有 3 号 GPU except RuntimeError as e: print(fset_device 错误: {e})根因分析1. GPU 设备编号机制PyTorch 使用从 0 开始的整数编号来标识 GPU# 系统有 N 个 GPU编号为 0 到 N-1 # cuda:0 — 第一个 GPU # cuda:1 — 第二个 GPU # ... # cuda:N-1 — 最后一个 GPU # 如果系统有 2 个 GPU: # 有效编号: 0, 1 # 无效编号: 2, 3, 4, ... num_gpus torch.cuda.device_count() print(fGPU 数量: {num_gpus}) print(f有效编号: 0 到 {num_gpus - 1})2. CUDA_VISIBLE_DEVICES 的影响CUDA_VISIBLE_DEVICES环境变量控制哪些 GPU 对 PyTorch 可见import os # 场景A系统有 4 个 GPU (物理编号 0,1,2,3) # 设置 CUDA_VISIBLE_DEVICES2,3 os.environ[CUDA_VISIBLE_DEVICES] 2,3 # 现在 PyTorch 只能看到 2 个 GPU # 物理 GPU 2 → PyTorch 编号 cuda:0 # 物理 GPU 3 → PyTorch 编号 cuda:1 # 访问 cuda:2 或 cuda:3 会报错 # 场景B设置 CUDA_VISIBLE_DEVICES 为不存在的 GPU os.environ[CUDA_VISIBLE_DEVICES] 5 # 假设没有 5 号 GPU # torch.cuda.is_available() 返回 False # 任何 CUDA 操作都会失败3. Docker 容器中的 GPU 可见性# Docker 运行时通过 --gpus 控制容器内可见的 GPU docker run --gpus all ... # 所有 GPU 可见 docker run --gpus 2 ... # 只能看到 2 个 GPU docker run --gpus device1,2 ... # 只看到物理 GPU 1 和 2 # 容器内的 PyTorch 看到的 GPU 编号从 0 开始重新编号 # 如果 --gpus device1,2容器内 cuda:0 对应物理 GPU 14. 常见触发模式模式1硬编码设备号# 错误硬编码 GPU 编号 device torch.device(cuda:2) # 如果没有 2 号 GPU 就会报错 # 正确动态检测 device torch.device(cuda:0 if torch.cuda.is_available() else cpu)模式2配置文件中的设备号# 配置文件中指定了 GPU 编号 config { gpu_id: 3, # 如果实际没有 3 号 GPU num_gpus: 4 # 如果实际只有 2 个 GPU }模式3分布式训练中的 rank# 分布式训练中 local_rank 与实际 GPU 不匹配 # 例如4 个进程但只有 2 个 GPU torch.cuda.set_device(local_rank) # local_rank3 但只有 2 个 GPU解决方案方案一动态检测 GPU 可用性def get_device(preferred_gpu0): 安全地获取设备 if not torch.cuda.is_available(): print(CUDA 不可用使用 CPU) return torch.device(cpu) num_gpus torch.cuda.device_count() if preferred_gpu num_gpus: print(f警告: 请求 GPU {preferred_gpu}但只有 {num_gpus} 个 GPU) preferred_gpu 0 return torch.device(fcuda:{preferred_gpu}) device get_device(preferred_gpu0)方案二正确设置 CUDA_VISIBLE_DEVICESimport os # 在导入 torch 之前设置 os.environ[CUDA_VISIBLE_DEVICES] 0,1 # 只使用 GPU 0 和 1 import torch print(f可见 GPU 数量: {torch.cuda.device_count()}) # 2方案三安全的设备设置def safe_set_device(device_id): 安全地设置 GPU 设备 if not torch.cuda.is_available(): return torch.device(cpu) num_gpus torch.cuda.device_count() if device_id num_gpus: raise ValueError( f无效的 GPU 编号 {device_id} f系统只有 {num_gpus} 个 GPU (编号 0-{num_gpus-1}) ) torch.cuda.set_device(device_id) return torch.device(fcuda:{device_id})方案四DataParallel 的安全配置def setup_data_parallel(model, num_gpusNone): 安全配置 DataParallel if not torch.cuda.is_available(): return model available_gpus torch.cuda.device_count() if num_gpus is None: num_gpus available_gpus else: num_gpus min(num_gpus, available_gpus) if num_gpus 1: return model.cuda() device_ids list(range(num_gpus)) model model.cuda() model nn.DataParallel(model, device_idsdevice_ids) print(fDataParallel 使用 GPU: {device_ids}) return model完整修复代码import torch import torch.nn as nn import torch.optim as optim import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP import os import subprocess from typing import Optional, List # # 完整修复代码解决 CUDA error: invalid device ordinal # class DeviceManager: GPU 设备管理器 staticmethod def get_gpu_info(): 获取 GPU 详细信息 if not torch.cuda.is_available(): return {available: False, message: CUDA 不可用} info { available: True, count: torch.cuda.device_count(), current_device: torch.cuda.current_device(), devices: [] } for i in range(info[count]): props torch.cuda.get_device_properties(i) info[devices].append({ id: i, name: props.name, total_memory: f{props.total_memory / 1024**3:.1f} GB, major: props.major, minor: props.minor, }) return info staticmethod def get_safe_device(device_id0, fallback_to_cpuTrue): 安全获取设备 if not torch.cuda.is_available(): if fallback_to_cpu: print([DeviceManager] CUDA 不可用使用 CPU) return torch.device(cpu) else: raise RuntimeError(CUDA 不可用) num_gpus torch.cuda.device_count() if device_id num_gpus: print(f[DeviceManager] 警告: 请求 cuda:{device_id} f但只有 {num_gpus} 个 GPU (0-{num_gpus-1})) if fallback_to_cpu: print(f[DeviceManager] 回退到 cuda:0) device_id 0 else: raise RuntimeError( f无效的 GPU 编号 {device_id} f系统只有 {num_gpus} 个 GPU ) device torch.device(fcuda:{device_id}) print(f[DeviceManager] 使用设备: {device}) return device staticmethod def setup_visible_gpus(gpu_ids): 设置可见的 GPU if isinstance(gpu_ids, list): gpu_ids ,.join(map(str, gpu_ids)) os.environ[CUDA_VISIBLE_DEVICES] gpu_ids print(f[DeviceManager] CUDA_VISIBLE_DEVICES {gpu_ids}) staticmethod def validate_device_ids(device_ids): 验证设备 ID 列表 if not torch.cuda.is_available(): raise RuntimeError(CUDA 不可用) num_gpus torch.cuda.device_count() valid_ids [] for dev_id in device_ids: if dev_id 0: print(f[DeviceManager] 跳过无效 ID: {dev_id}) continue if dev_id num_gpus: print(f[DeviceManager] 跳过不存在的 GPU: {dev_id}) continue valid_ids.append(dev_id) if not valid_ids: raise RuntimeError( f没有有效的 GPU ID。请求: {device_ids} f可用: 0-{num_gpus-1} ) return valid_ids class SafeMultiGPUModel: 安全的多 GPU 模型封装 def __init__(self, model, num_gpusNone, gpu_idsNone): self.model model self.device DeviceManager.get_safe_device(0) if not torch.cuda.is_available(): self.model model return available torch.cuda.device_count() if gpu_ids is not None: self.gpu_ids DeviceManager.validate_device_ids(gpu_ids) elif num_gpus is not None: self.gpu_ids list(range(min(num_gpus, available))) else: self.gpu_ids list(range(available)) if len(self.gpu_ids) 1: self.model model.to(self.device) self.model nn.DataParallel( self.model, device_idsself.gpu_ids, output_deviceself.gpu_ids[0] ) print(f[SafeMultiGPU] DataParallel: GPU {self.gpu_ids}) else: self.model model.to(self.device) print(f[SafeMultiGPU] 单 GPU: {self.device}) def forward(self, x): return self.model(x) def __call__(self, x): return self.forward(x) class DistributedTrainer: 分布式训练器 staticmethod def setup(rank, world_size, backendnccl): 初始化分布式训练 os.environ[MASTER_ADDR] localhost os.environ[MASTER_PORT] 12355 if not torch.cuda.is_available(): raise RuntimeError(分布式训练需要 CUDA) num_gpus torch.cuda.device_count() if rank num_gpus: raise RuntimeError( frank {rank} 超出 GPU 数量 {num_gpus} ) dist.init_process_group(backend, rankrank, world_sizeworld_size) torch.cuda.set_device(rank) print(f[DistributedTrainer] Rank {rank} 使用 GPU {rank}) staticmethod def cleanup(): if dist.is_initialized(): dist.destroy_process_group() staticmethod def train(rank, world_size, model_fn, dataset, epochs10): 分布式训练 DistributedTrainer.setup(rank, world_size) device torch.device(fcuda:{rank}) model model_fn().to(device) ddp_model DDP(model, device_ids[rank]) sampler torch.utils.data.distributed.DistributedSampler(dataset) dataloader torch.utils.data.DataLoader( dataset, batch_size32, samplersampler) criterion nn.CrossEntropyLoss() optimizer optim.Adam(ddp_model.parameters(), lr0.001) for epoch in range(epochs): sampler.set_epoch(epoch) ddp_model.train() for batch_idx, (data, target) in enumerate(dataloader): data, target data.to(device), target.to(device) optimizer.zero_grad() output ddp_model(data) loss criterion(output, target) loss.backward() optimizer.step() if batch_idx % 50 0 and rank 0: print(fEpoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}) DistributedTrainer.cleanup() class SimpleModel(nn.Module): 简单分类模型 def __init__(self, input_dim100, hidden_dim64, num_classes10): super().__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.relu nn.ReLU() self.fc2 nn.Linear(hidden_dim, num_classes) def forward(self, x): return self.fc2(self.relu(self.fc1(x))) # # 演示 # def demonstrate_device_management(): 演示设备管理 print( * 60) print(GPU 设备管理演示) print( * 60) # 1. GPU 信息 print(\n1. GPU 信息:) info DeviceManager.get_gpu_info() if info[available]: print(f GPU 数量: {info[count]}) for dev in info[devices]: print(f GPU {dev[id]}: {dev[name]} ({dev[total_memory]})) else: print(f {info[message]}) # 2. 安全获取设备 print(\n2. 安全获取设备:) device DeviceManager.get_safe_device(0) print(f 设备: {device}) # 3. 测试无效设备 print(\n3. 测试无效设备:) device DeviceManager.get_safe_device(99, fallback_to_cpuTrue) # 4. 多 GPU 模型 print(\n4. 多 GPU 模型:) model SimpleModel() safe_model SafeMultiGPUModel(model, gpu_ids[0]) # 5. 前向传播 x torch.randn(32, 100).to(safe_model.device) output safe_model(x) print(f 输入: {x.shape}, 输出: {output.shape}) def demonstrate_error_handling(): 演示错误处理 print(\n * 60) print(错误处理演示) print( * 60) # 模拟各种错误场景 # 场景1无效设备号 print(\n1. 无效设备号处理:) try: torch.cuda.set_device(99) except RuntimeError as e: print(f 捕获错误: {e}) # 修复 device DeviceManager.get_safe_device(0) print(f 修复后设备: {device}) # 场景2DataParallel 设备不足 print(\n2. DataParallel 设备不足:) model SimpleModel() try: model nn.DataParallel(model, device_ids[0, 1, 2, 3]) except (RuntimeError, AssertionError) as e: print(f 捕获错误: {e}) # 修复 safe_model SafeMultiGPUModel(SimpleModel()) # 场景3张量移动到无效设备 print(\n3. 张量移动到无效设备:) x torch.randn(10, 10) try: x x.to(cuda:99) except RuntimeError as e: print(f 捕获错误: {e}) # 修复 device DeviceManager.get_safe_device(0) x x.to(device) print(f 修复后设备: {x.device}) def demonstrate_best_practices(): 演示最佳实践 print(\n * 60) print(最佳实践演示) print( * 60) # 1. 始终检查 CUDA 可用性 print(\n1. 检查 CUDA:) use_cuda torch.cuda.is_available() device torch.device(cuda if use_cuda else cpu) print(f 设备: {device}) # 2. 动态获取 GPU 数量 print(\n2. 动态 GPU 数量:) if use_cuda: num_gpus torch.cuda.device_count() print(f GPU 数量: {num_gpus}) device_ids list(range(num_gpus)) print(f 设备列表: {device_ids}) # 3. 安全的模型部署 print(\n3. 安全模型部署:) model SimpleModel() if use_cuda: if torch.cuda.device_count() 1: model nn.DataParallel(model) model model.cuda() print(f 模型设备: {next(model.parameters()).device}) # 4. 训练循环 print(\n4. 训练循环:) optimizer optim.Adam(model.parameters(), lr0.001) criterion nn.CrossEntropyLoss() for epoch in range(3): data torch.randn(32, 100).to(device) target torch.randint(0, 10, (32,)).to(device) optimizer.zero_grad() output model(data) loss criterion(output, target) loss.backward() optimizer.step() print(f Epoch {epoch1}, Loss: {loss.item():.4f}) if __name__ __main__: demonstrate_device_management() demonstrate_error_handling() demonstrate_best_practices() print(\n * 60) print(所有演示完成!) print( * 60)常见陷阱与注意事项1. CUDA_VISIBLE_DEVICES 必须在导入 torch 前设置# 正确 import os os.environ[CUDA_VISIBLE_DEVICES] 0,1 import torch # 错误无效 import torch import os os.environ[CUDA_VISIBLE_DEVICES] 0,1 # 太晚了2. Docker 中的 GPU 可见性# 使用 nvidia-container-toolkit docker run --gpus all ... # 所有 GPU docker run --gpus 2 ... # 2 个 GPU docker run --gpus device0,1 ... # 指定 GPU # 容器内 GPU 从 0 开始重新编号3. 多进程中的设备分配# 每个进程使用不同的 GPU import torch.multiprocessing as mp def worker(rank, world_size): torch.cuda.set_device(rank) # 确保不超出范围 # ... # world_size 不能超过 GPU 数量 num_gpus torch.cuda.device_count() mp.spawn(worker, args(num_gpus,), nprocsnum_gpus)4. 模型保存和加载的设备问题# 保存时不绑定设备 torch.save(model.state_dict(), model.pth) # 加载时指定设备 device torch.device(cuda:0) model.load_state_dict(torch.load(model.pth, map_locationdevice))5. nvidia-smi 与 PyTorch 的 GPU 编号# nvidia-smi 显示的 GPU 顺序可能与 PyTorch 不同 # 特别是当使用 CUDA_VISIBLE_DEVICES 时 # 使用 PyTorch 自己的 API 确认 for i in range(torch.cuda.device_count()): print(fPyTorch GPU {i}: {torch.cuda.get_device_name(i)})6. GPU 热插拔# GPU 热插拔后PyTorch 可能无法检测到变化 # 需要重启 Python 进程 # 不要在运行时动态拔插 GPU总结RuntimeError: CUDA error: invalid device ordinal的根本原因是试图访问不存在的 GPU 设备。解决此问题的核心方法快速修复# 1. 检查 GPU 数量 num_gpus torch.cuda.device_count() # 2. 使用安全的设备获取 device torch.device(cuda:0 if torch.cuda.is_available() else cpu) # 3. 动态设置 DataParallel if torch.cuda.device_count() 1: model nn.DataParallel(model, device_idslist(range(torch.cuda.device_count())))预防措施不要硬编码 GPU 编号始终动态检测在导入 torch 前设置 CUDA_VISIBLE_DEVICES使用设备管理工具类封装设备操作添加错误处理捕获并优雅处理设备错误验证配置训练前检查 GPU 配置文档化 GPU 需求说明代码需要多少 GPU调试技巧# 快速诊断脚本 print(fCUDA available: {torch.cuda.is_available()}) print(fGPU count: {torch.cuda.device_count()}) print(fCurrent device: {torch.cuda.current_device()}) print(fCUDA_VISIBLE_DEVICES: {os.environ.get(CUDA_VISIBLE_DEVICES, not set)}) for i in range(torch.cuda.device_count()): print(fGPU {i}: {torch.cuda.get_device_name(i)})通过理解 GPU 设备编号机制和使用本文提供的设备管理工具你可以彻底解决invalid device ordinal错误确保代码在各种 GPU 环境中正确运行。