
目录
背景:在多实例算法服务的时候,出现了 cuda 初始化错误
File "/usr/local/lib/python3.12/dist-packages/torch/cuda/__init__.py", line 398, in _lazy_init
raise RuntimeError(
RuntimeError: Cannot re-initialize CUDA in forked subprocess.
To use CUDA with multiprocessing, you must use the 'spawn' start method
先看两个例子
# bad_multi_fork.py
import torch
import torch.nn as nn
import multiprocessing as mp
def worker(rank):
print(f"[Worker {rank}] Starting...")
x = torch.randn(5).cuda() # 触发 CUDA 初始化
print(f"[Worker {rank}] Tensor on {x.device}")
if __name__ == "__main__":
# 主进程初始化 CUDA(关键!)
_ = torch.tensor([1]).cuda() # 或者加载模型
print("[Main] CUDA initialized in main process.")
processes = []
for i in range(3):
# 默认使用 'fork'(Linux)
p = mp.Process(target=worker, args=(i,))
p.start()
processes.append(p)
for p in processes:
p.join()
上面这个例子会报错,主进程触发了 CUDA 初始化,fork 出来的子进程中创建 tensor 的时候报错了
如果把 _ = torch.tensor([1]).cuda() 这句去掉,那么主进程就没有 CUDA 初始化的操作,子进程不会报错
# good_multi_spawn.py
import torch
import torch.nn as nn
import multiprocessing as mp
def worker(rank):
print(f"[Worker {rank}] Starting...")
x = torch.randn(5).cuda()
print(f"[Worker {rank}] Tensor on {x.device}")
if __name__ == "__main__":
# 主进程可以安全使用 CUDA
main_tensor = torch.tensor([1]).cuda()
print(f"[Main] Main tensor on {main_tensor.device}")
# 显式使用 'spawn'
ctx = mp.get_context("spawn")
processes = []
for i in range(3):
p = ctx.Process(target=worker, args=(i,))
p.start()
processes.append(p)
for p in processes:
p.join()
print("[Main] All workers finished.")
改成 spawn 方式创建子进程,不会报错
spawn 和 fork 是 Python(以及 Unix-like 系统)中 多进程(multiprocessing)的两种不同启动方式,它们在底层机制、内存共享、安全性、兼容性等方面有根本差异。 尤其在涉及 GPU(CUDA)、多线程 或 复杂库(如 PyTorch、TensorFlow) 时,选择错误的启动方法会导致程序崩溃或未定义行为。
方式 | 机制 | 描述 |
|---|---|---|
fork | 复制进程 | 调用 Unix fork() 系统调用,复制父进程的整个内存空间(包括代码、堆、栈、文件描述符等)创建子进程。子进程从 fork() 之后的代码继续执行。 |
spawn | 启动新进程 | 启动一个全新的 Python 解释器进程,被 spawn 出的新进程拥有独立的内存地址空间,与父进程隔离。父子进程之间的变量、堆栈等默认不共享,修改不会互相影响。 |
方面 | fork | spawn |
|---|---|---|
内存 | 子进程拥有父进程内存的完整副本(写时复制,Copy-on-Write) | 子进程从干净状态开始,不继承父进程的任何运行时状态(变量、对象、锁、线程等) |
全局变量 | 继承父进程的值(修改不影响父进程) | 重新执行模块,重新初始化所有全局变量 |
启动方式 | 是否支持 CUDA | 原因 |
|---|---|---|
fork | ❌ 不安全(官方禁止) | CUDA runtime 不支持 fork()。一旦父进程初始化 CUDA,子进程继承的 CUDA 上下文是无效的,任何 GPU 操作会崩溃。 |
spawn | ✅ 安全支持 | 子进程干净启动,独立初始化 CUDA,符合 NVIDIA 要求。 |
平台 | 默认启动方法 | 说明 |
|---|---|---|
Linux / macOS | fork | 传统 Unix 行为,速度快 |
Windows | spawn | Windows 没有 fork(),只能用 spawn |
macOS(新版本) | 推荐 spawn |
❝使用
spawn可让代码在 Windows / Linux / macOS 上行为一致。
上面 fork 的示例中
cuInit()(由 PyTorch 在 .cuda() 等操作中触发)后,CUDA runtime 会在主进程的地址空间中记录“已初始化”。fork() 一个已经初始化 CUDA 的进程,子进程会复制这份“已初始化”状态,后面子进程使用 CUDA 的时候又会去初始化,导致二次初始化,报错注释主进程的 cuda 操作后:
fork() 出的每个子进程都继承了“未初始化”状态;.cuda() 时,各自独立调用 cuInit() → 这是合法的!❝所以:“重复初始化”是指 同一个进程 多次初始化(不允许),而不是 多个独立进程 各自初始化(完全允许)。
from https://michael.blog.csdn.net/
不对的地方,欢迎指教,感谢!