Многопроцессор Python с помощью PyCUDA
У меня есть проблема, что я хочу разделить несколько устройств CUDA, но я подозреваю, что моя нынешняя архитектура системы удерживает меня;
То, что я настроил, - это класс GPU с функциями, которые выполняют операции на графическом процессоре (это странно). Эти операции имеют стиль
for iteration in range(maxval):
result[iteration]=gpuinstance.gpufunction(arguments,iteration)
Я бы предположил, что для N устройств будет N gpuinstances, но я не знаю достаточно о многопроцессорности, чтобы увидеть самый простой способ применения этого, чтобы каждое устройство было асинхронно назначено, и странно мало примеров, которые я натолкнулись на конкретные демонстрации результатов сопоставления после обработки.
Может ли кто-нибудь дать мне какие-либо указатели в этой области?
UPDATE
Спасибо Калоян за руководство в плане многопроцессорной области; если бы CUDA не был специфическим моментом, я бы назвал вас ответом. Сожалею.
В первую очередь, играя с этой реализацией, класс gpuinstance инициировал CUDA-устройство с помощью import pycuda.autoinit
Но это, похоже, не срабатывало, бросая invalid context
ошибки, как только каждый (правильно облачный) поток встретил команду cuda. Затем я попробовал ручную инициализацию в конструкторе __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
)
Проблема в том, что invalid context
исключения по-прежнему появляются, как только поток пытается коснуться CUDA.
Любые идеи? И благодаря тому, что это так далеко. Автоматическая оптимизация для людей, работающих "бананом" в их ответе!: P
Ответы
Ответ 1
Вам нужно сначала собрать все ваши бананы на стороне CUDA, а затем подумать о том, как это сделать в Python [бесстыдной ревью, я знаю].
Модель с несколькими GPU CUDA довольно проста до 4.0 - каждый графический процессор имеет свой собственный контекст, и каждый контекст должен быть установлен другим хост-потоком. Итак, идея в псевдокоде:
- При запуске приложения процесс использует API для определения количества используемых GPUS (остерегайтесь таких вещей, как вычислительный режим в Linux).
- Приложение запускает новый хост-поток на один GPU, передавая идентификатор GPU. Каждый поток неявно/явно вызывает эквивалент cuCtxCreate(), передавая идентификатор GPU, которому он был назначен
- Profit!
В 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()
Это предполагает, что безопасно просто установить контекст без предварительной проверки устройства. В идеале вы должны проверить режим вычисления, чтобы убедиться, что это безопасно, затем используйте обработчик исключений, если устройство занято. Но, надеюсь, это дает основную идею.
Ответ 2
Вам нужна многопоточная реализация встроенной функции 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
.