OS X 10.8上的PyCuda / 多进程问题
我正在做一个项目,目的是把计算任务分配给多个 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 个回答
这其实只是我根据经验做的一个 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 中有更多的中级用户空间处理。