Сокращение времени запуска ядер в пошаговом цикле - OpenACC
Я пытаюсь реализовать OpenACC на некотором коде Фортрана, который у меня есть. Код состоит из внешнего пошагового цикла (который нельзя распараллелить), и внутри цикла есть несколько вложенных циклов. Эти вложенные циклы могут быть распараллелены, но их нужно запускать по порядку (т. Е. После A следует B, а затем C).
Я хочу переложить весь этот процесс на графический процессор, поскольку передача данных через многократные временные шаги между графическим процессором и процессором становится непомерным штрафом. Псевдокод ниже иллюстрирует мой текущий подход:
!$acc data copy(ALL THE DATA THAT I NEED)
DO iter = 1, NT
value = array_of_values(iter)
!$ acc kernels
!PART A
!$acc loop independent, private(j)
DO J=0,ymax
!$acc loop independent, private(i)
DO I=0,xmaxput
!$acc loop independent, private(l)
DO L=0,zmax
if(value == 0) then
(DO SOME COMPUTATIONS...)
elseif(value < 0) then
(DO SOME OTHER COMPUTATIONS...)
elseif(value > 0) then
(DO SOME OTHER COMPUTATIONS...)
endif
ENDDO
ENDDO
ENDDO
!NOW GO DO OTHER STUFF
!PART B
!$acc loop independent, private(j)
DO J=0,ymax
!$acc loop independent, private(i)
DO I=0,xmax
!$acc loop independent, private(l)
DO L=0,zmax
(DO SOME EVEN MORE COMPUTATIONS...)
ENDDO
ENDDO
ENDDO
!PART C
!etc...
!$acc end kernels
ENDDO
!$acc end data
У меня есть рабочий код, использующий этот подход, однако, когда я профилирую его на графическом процессоре GeForce MX150 с помощью NVIDIA Visual Profiler ( щелкните изображение), я вижу, что каждая итерация в цикле пошагового перехода по времени приводит к большим промежуткам времени, когда вычисления не выполняются. Driver Driver говорит, что в течение этого времени он выполняет "cuLaunchKernel". Если я просто скопирую весь цикл так, чтобы вместо одного временного шага выполнялись две итерации, то этот промежуток не существует в цикле временного шага, только когда начинается цикл.
У меня есть несколько (взаимосвязанных) вопросов:
1. Есть ли способ запустить эти ядра во время работы других ядер?
2. Я читал здесь и здесь, что драйвер WDDM запускает запуск ядра, который, по-видимому, происходит здесь. Означает ли это, что если я буду работать в Linux, я не должен ожидать такого поведения?
cuStreamSynchronize также блокирует запуск графического процессора, что приводит к дополнительному нулевому времени. Похоже, это связано с вопросом о том, как заставить другие ядра запускаться до конца цикла пошагового выполнения.
Я впервые использую OpenACC. Я искал ответ на этот вопрос, но, вероятно, использую неправильные ключевые слова, так как не смог ничего найти.
РЕДАКТИРОВАТЬ - решение
По предложению Мата я добавил Async, который решил проблему. Интересно, что запуск ядра по-прежнему выполняется в одно и то же время, но теперь каждое ядро, которое будет запускаться во время итерации цикла пошагового перехода по времени, запускается сразу в начале программы. Ниже приведен обновленный псевдокод, а также несколько других настроек, которые могут пригодиться кому-либо еще:
!$acc data copy(ALL THE DATA THAT I NEED)
!$acc wait
DO iter = 1, NT
value = array_of_values(iter)
!$acc update device(value, iter), async(1) !allows loop to run on cpu and sends value to GPU
!PART A
!$acc kernels, async(1)
!$acc loop independent, private(j)
DO J=0,ymax
!$acc loop independent, private(i)
DO I=0,xmaxput
!$acc loop independent, private(l)
DO L=0,zmax
if(value == 0) then
(DO SOME COMPUTATIONS...)
elseif(value < 0) then
(DO SOME OTHER COMPUTATIONS...)
elseif(value > 0) then
(DO SOME OTHER COMPUTATIONS...)
endif
ENDDO
ENDDO
ENDDO
!$acc end kernels
!NOW GO DO OTHER STUFF
!PART B
!$acc kernels, async(1)
!$acc loop independent, private(j)
DO J=0,ymax
!$acc loop independent, private(i)
DO I=0,xmax
!$acc loop independent, private(l)
DO L=0,zmax
(DO SOME EVEN MORE COMPUTATIONS...)
ENDDO
ENDDO
ENDDO
!$acc end kernels
!PART C
!$acc kernels, async(1)
!for loops, etc...
!$acc end kernels
ENDDO
!$acc wait
!$acc end data
2 ответа
Когда вы говорите "большой промежуток времени", можете ли вы быть более конкретным? Вы берете секунды, микросекунды, миллисекунды? Хотя он сильно варьируется, я ожидаю, что издержки при запуске ядра будут составлять около 40 микросекунд. Часто издержки запуска теряются в шуме, но если ядра работают особенно быстро или если ядро запускается миллионы раз, это может повлиять на относительную производительность. Использование предложений "async" может помочь скрыть издержки запуска (см. Ниже).
Хотя, если промежутки намного больше, может быть что-то еще. Например, если есть сокращение в цикле, переменная сокращения может быть скопирована обратно на хост. Обратите внимание, если вы используете PGI, взгляните на сообщения обратной связи компилятора (-Minfo=accel). Это может дать некоторые подсказки о том, что происходит.
- Есть ли способ запустить эти ядра во время работы других ядер?
Да. Используйте три отдельных области "ядра", по одному на каждую часть. Затем добавьте предложение "async(1)" в каждую вычислительную область. Async продолжит работу хоста после запуска ядер и, поскольку они используют одну и ту же очередь, 1 в этом случае, но вы можете использовать любое положительное целое число, он создаст зависимость, поэтому B не будет работать до завершения A, а C будет запускаться после B Вы захотите добавить "!$ Acc wait", где вы хотите, чтобы хост синхронизировался с устройством.
Обратите внимание, что асинхронные очереди отображаются в потоке CUDA.
cuStreamSynchronize также блокирует запуск графического процессора, что приводит к дополнительному нулевому времени. Похоже, это связано с вопросом о том, как заставить другие ядра запускаться до конца цикла пошагового выполнения.
Это время, когда хост блокируется в ожидании завершения вычислений на GPU. Оно должно быть примерно таким же, как и во время работы вашего ядра (если не используется async).
Вы можете оценить разницу между WDDM, если ваш графический процессор работает без дисплея. Если к нему не подключено ни одного дисплея, это устраняет некоторые проблемы работы с WDDM.
Попробуйте подключить ваши мониторы к материнской плате, если она поддерживает драйверы для интегрированной графики и графического процессора (проверьте BIOS).
В противном случае, вы можете добавить еще один графический процессор в ваш компьютер (может быть, старый) и использовать его для своих дисплеев.