Об асинхронности вычислений на карточке



Вычисления на карточке на самом деле проходят асинхронно. Мы "сбрасываем" на нее тензора и идём дальше, не блокируя основной поток программы (рис. 1). Когда результат понадобится, тогда его и спросим (проведем синхронизацию). Благодаря этому наш код ускоряется, так как пока тензора молотятся на GPU, мы можем заняться полезным на CPU или скинуть ещё данных на свободную часть GPU.

Если забыть про асинхронность, можно получать неправильные бенчмарки (рис. 2). При бенчмарках нас спасает torch.cuda.synchronize() , который заблокирует основной поток до тех пор, пока операции на карточке не завершатся.

Также для бенчмарков можно пользоваться torch.utils.benchmark (рис. 3).



Думаю, у каждого хоть раз вылетала такая ошибка: CUDA error 59: Device-side assert triggered. Чаще всего она происходит, когда что-то напутали с шейпами. С кем не бывает 🙂 Обычно трейсбек этой ошибки показывает на строчку, которая не имеет отношения к реальной проблеме и в нём непонятно, что конкретно поломалось. Это как раз из-за асинхронности. В потоке программы мы уже убежали вперед, а ошибка от CUDA из прошлого нас догнала. Обычно при этой ошибке советуют перевести все вычисления на CPU, отдебагать, а потом опять перевести на GPU. Но если вам лень лезть в код/конфиг и что-то поправлять или вы сожалеете о каждой секунде жизни, которую у вас отняли вычисления на CPU, то есть простой хак. Запустите код с нужной переменной окружения: CUDA_LAUNCH_BLOCKING=1 python your_code.py. При ней все CUDA-вычисления станут синхронными, а ошибка трейсбека начнет указывать на нужную строчку кода 😉 Но эту переменную прокидываем только при дебаге! Принудительная синхронизация замедляет вычисления