Python Portal
Фотография
Я думал, что понимаю, как работает загрузка GPU, пока не начал писать это руководство.
Я постоянно смешивал три разных понятия:
- Объём работы, который мы запускаем.
- Объём работы, который фактически размещён на GPU.
- Объём работы, готовый к выполнению прямо сейчас.
Но это совершенно разные вещи.
Возьмём CUDA-ядро с 240 блоками по 256 потоков в каждом. Всего получается 61 440 логических потоков, или 1920 варпов. Однако количество запущенных потоков ничего не говорит о том, сколько варпов может одновременно находиться на GPU.
Блоки размещаются на потоковых мультипроцессорах (SM) с учётом доступных регистров, разделяемой памяти и ограничений на количество потоков, варпов и блоков.
Даже после размещения на SM варп может ожидать завершения операции с памятью, разрешения зависимости между арифметическими операциями или синхронизации. Планировщик может выбирать только те варпы, которые действительно готовы к выдаче инструкций на выполнение.
Это изменило и мой подход к анализу показателей производительности.
Occupancy — показатель, отражающий отношение количества активных варпов на SM к максимально поддерживаемому количеству варпов на этом SM.
GPU utilization измеряет другое. В NVML этот показатель отражает долю интервала измерения, в течение которой выполнялось хотя бы одно CUDA-ядро.
Таким образом, GPU может показывать загрузку 100%, даже если Tensor Cores не используются на полную мощность, пропускная способность HBM не исчерпана, а производительность ограничивается одним из компонентов системы. При этом значительная часть ресурсов GPU может оставаться недозагруженной.
С памятью возникают похожие заблуждения. Регистры, разделяемая память, L1, L2 и HBM — это не просто уровни памяти, последовательно отличающиеся скоростью. У каждого из них свои области доступности, ёмкость, особенности управления и способы доступа.
Если модель помещается в HBM, это означает лишь то, что для неё достаточно видеопамяти. Но практически ничего не говорит о том, сколько байтов приходится передавать, насколько эффективно объединяются обращения к памяти, как часто данные используются повторно и действительно ли память ограничивает производительность CUDA-ядра.
Хороший пример — FlashAttention. Алгоритм вычисляет точный механизм внимания (attention), но использует разбиение на блоки (tiling) и повторные вычисления, чтобы сократить объём передачи данных между HBM и внутренней памятью GPU.
Tensor Cores оказались ещё одной технологией, которую я раньше воспринимал слишком упрощённо.
Это специализированные вычислительные блоки для матричных операций умножения с накоплением, играющие огромную роль в задачах ИИ. Однако они не «выполняют модель» целиком.
Операции редукции, индексации, поэлементные вычисления, синхронизация и перемещение данных по-прежнему задействуют другие компоненты GPU. Кроме того, запуск CUDA-ядер тоже создаёт дополнительные накладные расходы.
Со временем я перестал спрашивать себя: «Почему GPU не загружен на 100%?» Вместо этого я начал задавать более правильный вопрос: «Что именно сейчас ограничивает скорость выполнения полезной работы?»
В итоге всё это превратилось в руководство на 42 страницы.
В нём рассматриваются потоковые мультипроцессоры (SM), варпы и планировщики, иерархия памяти и скрытие задержек, Tensor Cores, распределение вычислений GEMM по аппаратным ресурсам GPU, точность вычислений и показатели производительности, которые очень легко интерпретировать неправильно.
https://drive.google.com/file/d/17tyvxr__8vZ6o995GNq6I5PC5JOiv0ql/view
👉 @PythonPortal
❤5
47 · 2.4K ·