OS X 10.8上的PyCuda / 多进程问题

2 投票
1 回答
1154 浏览
提问于 2025-04-17 14:53

我正在做一个项目,目的是把计算任务分配给多个 Python 进程,每个进程都对应一个自己的 CUDA 设备。

在创建这些子进程时,我使用了以下代码:

import pycuda.driver as cuda

class ComputeServer(object):
    def _init_workers(self):
        self.workers = []
        cuda.init()
        for device_id in range(cuda.Device.count()):
            print "initializing device {}".format(device_id)
            worker = CudaWorker(device_id)
            worker.start()
            self.workers.append(worker)

而 CudaWorker 的定义在另一个文件中,如下所示:

from multiprocessing import Process
import pycuda.driver as cuda

class CudaWorker(Process):
    def __init__(self, device_id):
        Process.__init__(self)
        self.device_id = device_id

    def run(self):
        self._init_cuda_context()
        while True:
            # process requests here

    def _init_cuda_context(self):
        # the following line fails
        cuda.init()
        device = cuda.Device(self.device_id)
        self.cuda_context = device.make_context()

当我在 Windows 7 或 Linux 上运行这段代码时,一切正常。但是在我的 MacBook Pro 上,使用的是 OSX 10.8.2、Cuda 5.0 和 PyCuda 2012.1 时,我遇到了以下错误:

Process CudaWorker-1:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
  File "/Users/tombnorwood/pymodules/computeserver/worker.py", line 32, in run
    self._init_cuda_context()
  File "/Users/tombnorwood/pymodules/computeserver/worker.py", line 38, in _init_cuda_context
    cuda.init()
RuntimeError: cuInit failed: no device

在我的 Mac 上运行 PyCuda 脚本时,如果不创建新进程,就没有任何问题。只有在创建新进程时,我才会遇到这个问题。

有没有人之前遇到过这个问题?

1 个回答

2

这其实只是我根据经验做的一个 educated guess,但我怀疑 OS X 上的 CUDA 实现(或者可能是 PyCuda)依赖了一些在 fork 之后不能安全使用的 API,而 Linux 的实现则没有这个问题。因为 POSIX 的 multiprocessing 是通过 fork 来创建子进程,而没有使用 exec,这就解释了为什么在 OS X 上会失败,但在 Linux 上却没问题。(在 Windows 上是没有 fork 的,只有类似 spawn 的东西,所以这不是个问题。)

最简单的解决办法就是不使用 multiprocessing。如果 CUDA 和 PyCUDA 是线程安全的(我不知道它们是否安全),而你的代码不是 CPU 密集型的(只是 GPU 密集型),那么你可以直接用 threading.Thread 替代 multiprocessing.Process,这样就可以解决问题了。或者你可以考虑其他一些并行处理库,它们提供类似于 multiprocessing 的 API。(有些人只用 pp,因为它总是使用 exec……)

不过,修改 multiprocessing 让它使用 exec/spawn 启动一个新的 Python 解释器,然后像 Windows 那样处理事情,其实是比较简单的。(要做到每一种情况都正确是有点难,但处理一个特定的用例就简单多了。)

另外,如果你查看一下 bug #8713,会发现正在进行一些工作,以便让这个问题得到更好的解决。而且已经有一些有效的补丁。这些补丁是针对 3.3 版本的,而不是 2.7,所以你可能需要稍微调整一下,但应该不会太麻烦。所以,只需执行 cp $MY_PYTHON_LIB/multiprocessing.py $MY_PROJECT_DIR/mymultiprocessing.py,然后打上补丁,使用 mymultiprocessing 替代 multiprocessing,在做其他事情之前,添加适当的调用来选择 spawn/fork+exec/最新补丁中所称的模式。


* 原作者说他也怀疑过同样的事情,所以我可能不需要向他解释,但为了未来的读者:这并不是 Darwin 和其他 Unix 之间的差异,而是因为苹果公司提供了很多非 Unix 风格的中级库,比如 CoreFoundation.framework、Accelerate.framework 等,这些库在 fork 后使用了不安全的功能(或者只是声称它们在 fork 后没有被使用,因为苹果不想进行严格的测试,以便在说“从 10.X 开始,Foo.framework 在 fork 后是安全的”之前,能够有足够的依据)。此外,如果你比较一下 OS X 和 Linux 处理图形和其他硬件的方式,会发现 OS X 中有更多的中级用户空间处理。

撰写回答