使用PyCUDA进行Python多进程处理

17

我有一个问题,我想将其分割成多个CUDA设备,但我怀疑我的当前系统架构正在阻碍我;

我设置了一个GPU类,其中包含在GPU上执行操作的功能(奇怪的是)。这些操作的类型为

for iteration in range(maxval):
    result[iteration]=gpuinstance.gpufunction(arguments,iteration)

我原本想会有N个 gpuinstances 对应 N 个设备,但是我对多进程不够熟悉,无法想到最简单的方法来异步分配每个设备,并且我遇到的示例中很少有关于处理后汇总结果的具体演示。

有人能在这个领域给我任何指导吗?

更新 感谢Kaloyan在多进程领域的指导;如果CUDA不是特别棘手的问题,我将选择你作为答案。抱歉。

在尝试这个实现之前,gpuinstance 类使用 import pycuda.autoinit初始化 CUDA 设备,但似乎不起作用,每个(正确作用域的)线程一遇到 cuda 命令就会抛出“invalid context”错误。然后我尝试在类的 __init__ 构造函数中进行手动初始化...

pycuda.driver.init()
self.mydev=pycuda.driver.Device(devid) #this is passed at instantiation of class
self.ctx=self.mydev.make_context()
self.ctx.push()    

我的假设是在创建gpuinstances列表和线程使用它们之间,上下文是被保留的,因此每个设备都在自己的上下文中。

(我还实现了一个析构函数来处理pop/detach清理)

问题是,当线程尝试触及CUDA时,invalid context异常仍会出现。

有什么想法吗?感谢大家的帮助,将 'banana' 一词纳入答案的人将获得自动升级!:P


gpuinstance.gpufunction(arguments,iteration) 是异步的还是会阻塞执行? - ktdrv
2个回答

21
你需要先在CUDA方面将所有香蕉排成一排,然后再考虑用Python最佳方法来完成此任务[无耻的自我宣传,我知道]。
CUDA多GPU模型在4.0之前相当简单-每个GPU都有自己的上下文,并且每个上下文必须由不同的主机线程建立。 因此,伪代码的思路是:
1. 应用程序启动,进程使用API确定可用GPU的数量(请注意Linux中的计算模式等事项) 2. 应用程序为每个GPU启动一个新的主机线程,传递一个GPU ID。 每个线程隐式/显式调用类似于cuCtxCreate()的等效函数,传递它被分配的GPU ID 3. 利润!
在Python中,这可能看起来像这样:
import threading
from pycuda import driver

class gpuThread(threading.Thread):
    def __init__(self, gpuid):
        threading.Thread.__init__(self)
        self.ctx  = driver.Device(gpuid).make_context()
        self.device = self.ctx.get_device()

    def run(self):
        print "%s has device %s, api version %s"  \
             % (self.getName(), self.device.name(), self.ctx.get_api_version())
        # Profit!

    def join(self):
        self.ctx.detach()
        threading.Thread.join(self)

driver.init()
ngpus = driver.Device.count()
for i in range(ngpus):
    t = gpuThread(i)
    t.start()
    t.join()

这假设在没有对设备进行任何检查的情况下,直接建立上下文是安全的。理想情况下,您应该检查计算模式以确保尝试是安全的,然后使用异常处理程序以防设备忙碌。但希望这能够传达基本思想。


1
@talonmies一如既往地感谢,但是我有一个快速的问题:如果我理解正确,每个线程都是在同一行中“实例化”,执行和加入的。这不会导致串行执行吗?我认为简单的解决方法是将t.join()分成一个单独的循环。 - Bolster
@Andrew Bolter:是的,我想启动方法应该都在一个循环中调用,而 joins 则稍后再调用。我也对这种情况下的全局解释器锁有所疑虑... 我必须承认,我为我的 Python 多GPU 使用了 mpi4py,我还有一个 pthreads 框架可用于多GPU,但通常只与C/C++和Fortran一起使用。 - talonmies
@Andrew Bolter:我刚刚对我发布的那段代码进行了一些小改动,并添加了一些工具,但现在我开始怀疑使用 Python 线程是否明智。此时我不确定我发布的内容的正确性... - talonmies
我怀疑我将以MPI为目标重构这个问题,但我觉得这应该更加平凡。另外,为了避免线程的不足,我也一直在研究多进程。 - Bolster
@talonmies,按照我们讨论的方法调整后,仍然出现无效上下文(无论是否进行了额外的上下文推送/弹出)。现在正在查看mpi4py,但希望了解为什么这不像想象中那样工作。免责声明:我正在4.0上运行。 - Bolster
显示剩余6条评论

3
您需要的是一个多线程实现的内置函数map。这里有一个实现(链接)。稍加修改以适应您特定的需求,即可使用:
import threading

def cuda_map(args_list, gpu_instances):

    result = [None] * len(args_list)

    def task_wrapper(gpu_instance, task_indices):
        for i in task_indices:
            result[i] = gpu_instance.gpufunction(args_list[i])

    threads = [threading.Thread(
                    target=task_wrapper, 
                    args=(gpu_i, list(xrange(len(args_list)))[i::len(gpu_instances)])
              ) for i, gpu_i in enumerate(gpu_instances)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    return result

这与您上面所述的基本相同,唯一的区别在于您不需要等待每个gpufunction的单个完成。


谢谢您的评论,它引导我找到了解决方案,但是遇到了与设备上下文相关的CUDA问题。现在更新问题以反映这一点。 - Bolster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接