Проблема с PyCuda/многопроцессорностью в OS X 10.8

Я работаю над проектом, в котором я распределяю вычислительные задачи по нескольким процессам 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

У меня нет проблем с запуском сценариев PyCuda без разветвления новых процессов на моем Mac. Я получаю эту проблему только при создании нового процесса.

Кто-нибудь сталкивался с этой проблемой раньше?


person tnorwood    schedule 05.02.2013    source источник
comment
Я подозреваю, что это связано с тем, что в OS X есть целая куча базовых фреймворков, которые нельзя использовать после fork, и либо PyCuda, либо сама CUDA опирается на один из них…   -  person abarnert    schedule 06.02.2013
comment
Я вообще-то тоже так думал. Есть ли способ обойти это? Это действительно очень раздражает.   -  person tnorwood    schedule 06.02.2013
comment
Если это так, самый простой обходной путь — запустить новый интерпретатор Python вместо того, чтобы продолжать использовать разветвленный. Где-то есть пропатченная версия multiprocessing, которая делает это. (Возможно, когда-нибудь его добавят в магистраль в качестве опции, но он никогда не будет использоваться по умолчанию, потому что это заставит OS X multiprocessing работать больше как Windows, чем как POSIX.) Если вы хотите, и вы не можете его найти или выясните, как сделать это самостоятельно (на самом деле это довольно просто), я могу покопаться в этом.   -  person abarnert    schedule 06.02.2013
comment
Ничего, нашел. Проблема, характерная для Mac, была зарегистрирована как ошибка, которая была закрыта как дубликат #8713 (который говорит Linux, но на самом деле это касается всех POSIX-платформ). Если вы скопируете multiprocessing.py из исходников, примените патч, переименуете его во что-то другое, а затем просто вызовете mymultiprocessing.forking_disable() прежде всего, это должно сработать. (Патч, возможно, потребует немного помассировать для 2.7, но это не должно быть слишком сложно.)   -  person abarnert    schedule 06.02.2013
comment
На самом деле, простейший обходной путь — это, вероятно, просто использование потоков вместо процессов. Если только CUDA не является потокобезопасным или ваш код не привязан к ЦП (а не только к ГП), все должно работать нормально, верно?   -  person abarnert    schedule 06.02.2013
comment
Процессы CudaWorker определенно больше привязаны к GPU, чем к CPU, поэтому решение Threading, скорее всего, будет работать. Если я правильно помню, с тех пор, как ядро ​​​​PyCuda 0.9 CUDA вызывает выпуск GIL, поэтому использование Threading также не должно вызывать никаких проблем.   -  person tnorwood    schedule 06.02.2013
comment
Замена многопроцессорного подхода на многопроцессорный подход сделала свое дело. Спасибо за помощь.   -  person tnorwood    schedule 06.02.2013


Ответы (1)


На самом деле это всего лишь обоснованное предположение, основанное на моем опыте, но я подозреваю, что реализация CUDA в OS X (или, возможно, PyCuda) опирается на некоторые API, которые нельзя безопасно использовать после fork, в то время как реализация в Linux — нет.* реализация POSIX multiprocessing использует fork без exec для создания дочерних процессов, это объясняет, почему он не работает в OS X, но не в Linux. (А в Windows нет fork, только эквивалент spawn, так что это не проблема.)

Самым простым решением было бы удалить multiprocessing. Если CUDA и PyCUDA потокобезопасны (я не знаю, являются ли они таковыми), а ваш код не привязан к процессору (только к графическому процессору), вы можете просто вставить threading.Thread вместо multiprocessing.Process и все готово. с этим. Или вы можете рассмотреть одну из других библиотек параллельной обработки, которые предоставляют API-интерфейсы, аналогичные multiprocessing. (Есть несколько человек, которые используют pp только потому, что это всегда execs…)

Тем не менее, довольно легко взломать multiprocessing до exec/spawn нового интерпретатора Python, а затем сделать все в стиле Windows, а не в стиле POSIX. (Правильно подобрать каждый случай сложно, но правильно подобрать один конкретный вариант использования легко.)

Или, если вы посмотрите на ошибку № 8713, то проделаете некоторую работу, чтобы сделать эту работу правильной в целом. И есть рабочие патчи. Эти патчи предназначены для 3.3, а не 2.7, поэтому вам, вероятно, потребуется немного помассировать, но это не должно быть слишком много. Итак, просто cp $MY_PYTHON_LIB/multiprocessing.py $MY_PROJECT_DIR/mymultiprocessing.py, исправьте его, используйте mymultiprocessing вместо multiprocessing и добавьте соответствующий вызов, чтобы выбрать spawn/fork+exec/независимо от того, какой режим вызывается в последнем патче, прежде чем делать что-либо еще.


* ОП говорит, что подозревал то же самое, поэтому мне, вероятно, не нужно объяснять это ему, но для будущих читателей: речь идет не о разнице между Дарвином и другими Unix, а о том, что Apple поставляет много не Библиотеки среднего уровня Unix-y, такие как CoreFoundation.framework, Accelerate.framework и т. д., которые используют небезопасную функциональность после форка (или просто утверждают, что они не используются после форка, потому что Apple не хочет вставлять тщательное тестирование, которое будет оправдано, прежде чем они смогут сказать: «Начиная с 10.X, Foo.framework безопасен после форка»). Кроме того, если вы сравните то, как OS X и Linux работают с графикой и другим оборудованием, в OS X происходит гораздо больше пользовательского пространства среднего уровня в каждом процессе.

person abarnert    schedule 06.02.2013