Фон:
Я написал программу CUDA, которая выполняет обработку последовательности символов. Программа обрабатывает все последовательности символов параллельно с условием, что все последовательности имеют одинаковую длину. Я сортирую свои данные по группам, каждая из которых состоит из последовательностей одинаковой длины. Программа обрабатывает 1 группу за раз.
Вопрос:
Я выполняю свой код на машине Linux с 4 графическими процессорами и хотел бы использовать все 4 графических процессора, запустив 4 экземпляра моей программы (по 1 на графический процессор). Возможно ли, чтобы программа выбрала графический процессор, который не используется другим приложением CUDA для запуска? Я не хочу жестко кодировать что-либо, что могло бы вызвать проблемы в будущем, когда программа работает на другом оборудовании с большим или меньшим количеством графических процессоров.
переменная окружения CUDA_VISIBLE_DEVICES
твой друг.
Я предполагаю, что у вас открыто столько терминалов, сколько у вас графических процессоров. Допустим, ваше приложение называется myexe
Тогда в одном терминале вы можете сделать:
CUDA_VISIBLE_DEVICES="0" ./myexe
В следующем терминале:
CUDA_VISIBLE_DEVICES="1" ./myexe
и так далее.
Затем первый экземпляр будет запущен на первом графическом процессоре, перечисленном CUDA. Второй экземпляр будет работать на втором GPU (только) и так далее.
Предполагая bash, и для данного терминального сеанса вы можете сделать это «постоянным», экспортируя переменную:
export CUDA_VISIBLE_DEVICES="2"
после этого все приложения CUDA, запущенные в этом сеансе, будут наблюдать только третий перечисляемый графический процессор (перечисление начинается с 0), и они будут наблюдать этот графический процессор как будто это устройство 0 в их сессии.
Это означает, что вам не нужно вносить какие-либо изменения в ваше приложение для этого метода, при условии, что ваше приложение использует графический процессор по умолчанию или графический процессор 0.
Вы также можете расширить это, чтобы сделать доступными несколько графических процессоров, например:
export CUDA_VISIBLE_DEVICES="2,4"
означает, что графические процессоры, которые обычно перечисляют как 2 и 4, теперь будут единственными графическими процессорами, «видимыми» в этом сеансе, и они будут перечислять как 0 и 1.
На мой взгляд, вышеуказанный подход является самым простым. Выбор графического процессора, который «не используется», проблематичен, потому что:
Так что лучший совет (IMO) — явное управление графическими процессорами. В противном случае вам понадобится планировщик заданий какой-либо формы (выходящий за рамки этого вопроса, IMO), чтобы иметь возможность запрашивать неиспользуемые графические процессоры и «резервировать» один, прежде чем другое приложение попытается сделать это упорядоченным образом.
Есть лучший (более автоматический) способ, который мы используем в PIConGPU, который работает на огромных (и разных) кластерах.
Смотрите реализацию здесь: https://github.com/ComputationalRadiationPhysics/picongpu/blob/909b55ee24a7dcfae8824a22b25c5aef6bd098de/src/libPMacc/include/Environment.hpp#L169
В основном: Звоните cudaGetDeviceCount
чтобы узнать количество графических процессоров, переберите их и вызовите cudaSetDevice
установить это как текущее устройство и проверить, сработало ли это. Эта проверка может включать создание тестового потока из-за некоторой ошибки в CUDA, из-за которой setDevice успешно выполнялся, но все последующие вызовы не выполнялись, поскольку устройство фактически использовалось.
Примечание. Может потребоваться установить для графических процессоров монопольный режим, чтобы графический процессор мог использоваться только одним процессом. Если у вас недостаточно данных для одного «пакета», вы можете захотеть обратного: несколько процессов отправляют работу на один графический процессор. Так что настройтесь в соответствии с вашими потребностями.
Другие идеи: Запустите MPI-приложение с тем же числом процессов на ранг, что и для графических процессоров, и используйте тот же номер устройства, что и локальный ранг. Это также поможет в приложениях, подобных вашему, которые имеют различные наборы данных для распространения. Так вы можете, например, иметь данные длины 1 процесса MPI 0 и данные длины 2 процесса MPI и т. д.