Нужна помощь в понимании скорости передачи ядра на графическом процессоре (numba, cupy, cuda) - PullRequest
0 голосов
/ 04 декабря 2018

Хотя математические вычисления ускорения выполняются графическими процессорами, есть фиксированные накладные расходы на перемещение ядра в графический процессор для выполнения, которое является высоким.

Я использую Cupy и Numba.В первый раз, когда я выполняю вызов функции, которая использует версию numy для графического процессора Cupy, это довольно медленно.Но во второй раз это быстро.

Я понял, что не понимаю, как ядро ​​или код GPU попадает в GPU для запуска.В оперативном плане я хочу лучше понять это, чтобы знать, когда то, что я делаю, случайно создаст медленный шаг из-за некоторой передачи ядра.Поэтому мне нужны некоторые правила или практические правила, чтобы понять эту концепцию.

Например, если я умножу два массива Cupy, которые уже спрятаны на GPU, я мог бы написать C = A * B

В какой-то момент перегрузка Cupy при * умножении должна быть закодирована на графическом процессоре, и для этого автоматически потребуются также циклы, которые разбивают его на блоки и потоки.Предположительно, этот код представляет собой ядро, которое передается в графический процессор.Я предполагаю, что в следующий раз, когда я позвоню C * D, графическому процессору больше не нужно учить, что означает *, и поэтому он будет быстрым.

Но в какой-то момент я думаю, что графический процессор нужно очиститьиз старого кода, чтобы * или другие неиспользуемые в этот момент операции могли быть выгружены из памяти, и поэтому позже, когда снова будет вызван вызов A * B, будет вовремя наложен штраф за его повторную компиляцию на GPU.

Или я так себе представляю.Если я прав, как я узнаю, когда эти ядра останутся или исчезнут?

Если я ошибаюсь, и это не так, как работает, или есть какой-то другой медленный шаг (яесли предположить, что данные уже перенесены в массивы на GPU), тогда что это за медленный шаг и как упорядочить вещи, чтобы платить как можно меньше?

Я стараюсь избегать написания явных ядер управления потоками numbaкак это делается в cuda ++, но просто используйте стандартные декораторы numba @njit, @vectorize, @stencil.Точно так же в Cupy я хочу просто работать на уровне обалденного синтаксиса, а не погружаться в управление потоками.

Я читал много документов по этому вопросу, но они просто ссылаются на издержки ядра, а не когдазаплатил и как кто-то контролирует это, поэтому я в замешательстве.

1 Ответ

0 голосов
/ 05 декабря 2018

У меня пока нет полного ответа на это.Но до сих пор самая большая подсказка, которую я получил, пришла от чтения недокументированной в настоящее время функции @cupy.fuse(), которая делает ее более понятной, чем документы @numba.jit, где оплачиваются затраты на запуск ядра.Я еще не нашел связь с Контекстами, как рекомендовано @talonmies.

см. https://gist.github.com/unnonouno/877f314870d1e3a2f3f45d84de78d56c

Ключевой пример:

c = cupy.arange(4)
#@cupy.fuse()
def foo(x):
    return x+x+x+x+x

foo (.) Будет в три раза медленнее с @ cupy.fuse (), потому что каждый«+» подразумевает загрузку ядра и освобождение ядра.Fusion объединяет все дополнения в одно ядро, так что выпуски и запуск платныеДля матриц размером менее 1 миллиона на типичном графическом процессоре 2018 add () настолько быстр, что время запуска и освобождения являются доминирующими.

Я хотел бы найти некоторую документацию по @fuse.Например, разворачивает ли он внутренние функции, как @jit.Могу ли я добиться этого, складывая @jit и @fuse?

Однако я все еще в значительной степени в неведении относительно того, когда расходы оплачиваются в numba.

...