Поиск по сайту Поиск

Анализ производительности нейросетей на GPU с помощью NVIDIA Visual Profiler (часть 1)

Специалисты Data Science делятся на два лагеря: те, кому кажется, что нейросети обучаются слишком медленно, и те, кому скоро тоже начнёт так казаться. В этой статье мы расскажем, как проанализировать и «прокачать» приложения машинного обучения с помощью профилирования.

Очень часто проблемы с производительностью возникают при переносе приложения с одной конфигурации сервера на другую. Обычно клиент и инженеры технической поддержки анализируют различия в hyperthreading, частотах памяти, вычислительных ядрах, версиях библиотек и прочих косвенных показателях. Более детальное исследование требует согласованных действий, часто приводящих к нарушению конфиденциальности данных, что упирается в IT-бюрократию. Поэтому получается, что «продвинутый» клиент гораздо эффективнее оказал бы помощь «сам себе». Немногие разработчики нейросетей умеют действительно «заглянуть под капот» приложения и детально проследить эффекты, влияющие на его производительность. Попробуем приоткрыть эту завесу и узнать, какие техники и приёмы следует использовать.

Оптимизация кода на GPU

До начала бума нейросетевых технологий разработка приложений для графических процессоров ограничивалась написанием функций-ядер на языке CUDA. Но, в отличие от CPU, в GPU нет сглаживающих негативные эффекты подсистем: большого кэша, малых штрафов за невыровненные адреса, предсказания ветвлений и так далее. Поэтому правильная оптимизация GPU-кода становится критически важной проблемой. Убедимся в этом на наглядном примере: ниже показан фрагмент CUDA-реализации билинейного фильтра.

Такое ядро скомпилируется и будет правильно работать. Однако, если бы мы умели читать ассемблерные листинги, то увидели бы кое-что интересное:

Оказывается, компилятор не распознал процесс загрузки пикселей и сгенерировал ассемблерный код для однобайтовых значений вместо целого RGBA-вектора. Негативный эффект будет частично самортизирован L1-кэшем, с учётом того, в какой мере он занят поддержкой других операций. Но подобное решение всё равно может существенно замедлить приложение. На большом изображении (6496px × 6618px × 24bpp) для GPU Tesla V100 разница в скорости составляет примерно 40%:

В этом случае чтение пикселей оптимизировано вручную с помощью union:

В новой версии приложения однобайтовые загрузки превратились в одну четырёхбайтовую:

Но главный вопрос — как разработчику заметить такой случай? Опыт и знания стоят времени и денег. Регулярное чтение ассемблерного кода могло бы помочь, но не очень реалистично на практике. Поэтому существуют более универсальные средства анализа, такие как:

  1. Аппаратные счётчики в GPU для сбора статистики о работе программы;
  2. Метрики, которые обрабатывают данные счётчиков и объединяют их в качественные характеристики приложения. 

Грубо говоря, такая важная метрика, как GPU Occupancy, — это что-то похожее на рейтинг вашего смартфона в программе AnTuTu. Сбор и анализ метрик называется профилированием. Сведения о параметрах работы приложения, полученные в результате профилирования, гораздо легче анализировать и использовать для повышения эффективности. Так, программа NVIDIA Visual Profiler превращает счётчики и метрики в графики и диаграммы, и даже даёт советы по оптимизации. Рассмотрим её основные возможности.

Пример анализа использования памяти в NVIDIA Visual Profiler

Обзор NVIDIA Visual Profiler

NVIDIA Visual Profiler — это графический инструмент профилирования, который отображает хронологию загрузки CPU и GPU во время работы вашего приложения. Программа автоматически анализирует GPU-ядра и помогает определить возможности для оптимизации.

Подготовка приложения к профилированию

Инструменты профилирования CUDA не требуют каких-либо изменений приложения, но несколько несложных действий могут значительно повысить удобство и эффективность процесса.

Основная рекомендация при работе с Visual Profiler — использовать небольшие участки для профилирования. По умолчанию данные собираются в течение всего времени запуска, но лучше анализировать только критические места. Так вы сможете сосредоточить внимание на коде, оптимизация которого приведёт к существенному увеличению производительности. Использование Visual Profiler с большими и сложными приложениями может вызывать зависание движка JVM, поэтому лучше запускать его не более чем на 30 секунд.

Также стоит присвоить пользовательские названия ресурсам CPU и CUDA, поскольку на временной шкале Visual Profiler имена по умолчанию не слишком информативные. Если использовать более понятные и говорящие наименования, то можно улучшить понимание поведения приложения, особенно когда в нём присутствует много устройств, контекстов или потоков.

Создание сеанса профилирования

Первый шаг в использовании Visual Profiler — создание нового сеанса профилирования. В нём будут содержаться настройки, данные и результаты анализа вашего приложения. Для этого необходимо указать исполняемый файл, а также, при желании — рабочий каталог, аргументы, параметры мультипроцессного профилирования и переменные окружения. При этом вы можете задать конкретные процессы для обработки, включить или выключить временную шкалу, а также установить различные параметры профилирования для CUDA и CPU.

После применения настроек Visual Profiler немедленно запустит ваше приложение и начнёт собирать данные, необходимые для первого этапа управляемого анализа (если только при создании сеанса не была выбрана опция Don't run guided analysis). Систему управляемого анализа можно использовать для получения рекомендаций по улучшению производительности приложения.

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

Разбор результатов

Временная шкала

В Visual Profiler можно одновременно открыть несколько временных шкал на разных вкладках. На следующем рисунке показана шкала для приложения CUDA:

В верхней части находятся горизонтальные отметки времени, прошедшего с начала профилирования приложения. В левой части отображены единицы исполнения: процесс (process), потоки (thread), GPU (device), контексты (context), ядра (kernel), стримы (stream) и т.д. (с полным списком можно ознакомиться в документации).  В центре показаны строки, отражающие активность отдельных элементов. Каждая строка отображает интервалы времени между началом и окончанием каких-либо процессов. Например, строки напротив ядер показывают время начала и окончания выполнения этого ядра.

Анализ

Analysis view отображает результаты анализа приложения. Доступны два режима: управляемый и неуправляемый. В управляемом режиме система проводит несколько этапов анализа, чтобы помочь вам понять слабые места в производительности и указать на возможности оптимизации приложения. В неуправляемом режиме вы можете самостоятельно запустить необходимые этапы и изучить их результаты. На рисунке ниже показан вид управляемого анализа: 

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

Неуправляемый анализ содержит список доступных процессов, каждый из которых можно запустить вручную и увидеть результат:

Дизассемблер

Source-Disassembly View используется для отображения результатов анализа на уровне ассемблера. В исходном коде отмечается интенсивность и эффективность выполнения отдельных команд. Соответствующие маркеры окрашиваются в разные цвета в зависимости от уровня критичности — низкого, среднего или высокого. Эта информация — основа для базового приёма оптимизации: выделение разработчиком наиболее критичных («тяжёлых») на общем фоне команд (hotpoints) и их переработка — изменение программной логики, понижение точности и так далее.

Просмотр сведений о GPU

GPU Details View показывает таблицу с информацией о каждом копировании памяти (memcpy) и запуске ядра (kernel) в профилируемом приложении. Для ядер в столбцах показаны соответствующие метрики и события.

Подробный справочник по метрикам можно найти в документации.

Анализ производительности ML-приложения на GPU 

Рассмотрим пример профилирования ML-приложения, которое использует мощности GPU. Предположим, перед нами стоит задача обучить LSTM-нейросеть для генерации связного текста. Для этого был написан скрипт на Python, основанный на Keras. Обратите внимание, что сам python-код явно GPU не использует. Но «выход» на GPU может происходить уже внутри функций библиотеки Keras без непосредственного участия пользователя. Поэтому смело запускайте NVIDIA Profiler, указывая в качестве приложения сам Python-скрипт, и профилировщик доберётся в нём до GPU-кода. Тем не менее, следует ограничить длительность профилирования небольшим интервалом, например, 30 секунд. Поскольку алгоритм обучения нейросети однообразен и периодичен, мы можем считать, что этот интервал характеризует поведение приложения в целом, упрощая при этом работу профилировщика по сбору данных.

Для эффективной загрузки GPU должны выполняться два условия:

  1. GPU не должна простаивать: необходимо, чтобы какое-либо ядро всегда было загружено;
  2. Программы-ядра должны использовать как можно больше вычислительных единиц, например, перемножать громоздкие матрицы.

На рисунке ниже показан процесс профилирования нашего ML-скрипта: 

Слева направо идёт время выполнения, маленькие жёлтые «кирпичики» в центре — API-вызовы в потоке CPU, который запускает расчёты, а зелёные отрезки снизу — загрузка GPU. 100% использования GPU, как нетрудно догадаться, отображалось бы не маленькими квадратиками, а одним непрерывным блоком. Но на практике этого добиться невозможно, и лучшее, на что можно рассчитывать — чтобы между «кирпичиками» почти не было зазоров. Таким образом, условие (1) в этом примере не выполняется.

Рассматриваемое приложение использует ядро умножения матриц с одинарной точностью (Single precision floating General Matrix Multiply, SGEMM). В окне свойств (справа) указано, что ядро запущено в конфигурации «16 блоков по 128 потоков». Это дало бы хороший результат на GPU начального уровня, но в мощных графических процессорах с архитектурой VOLTA доступно до тысячи таких блоков! Из этого следует, что ядро использует менее 1/10 доступных ресурсов.

⌘⌘⌘

Теперь вы знаете, как написать неэффективное приложение для GPU (лучше, конечно же, этого не делать). Ваш заказчик может возразить, что код всё-таки работает на 10% быстрее с видеокартой NVIDIA RTX. Такой аргумент звучит как «Приора оказалась чуть быстрее Феррари в гонке по болоту». Другими словами, если приложение не раскрывает преимуществ графического процессора, то производительность может быть почти любой, а в абсолютных значениях — слабой во всех вариантах. Чтобы получить существенное ускорение на мощных GPU, необходимо решать по-настоящему высоконагруженные задачи. Во второй части статьи мы расскажем, как оптимизировать загрузку графического процессора и оценить успехи через профилировщик.

Domains weekly: конец стабильности .COM, детали продажи .ORG и сделки недели

Domains weekly: конец стабильности .COM, детали продажи .ORG и сделки недели

Из нового доменного дайджеста вы узнаете, может ли проигрыш спора за домен стать победой, чего ждать от повышения цен на...
Read More
PHP 7 или Как ускорить сайт за минуту без регистрации и СМС

PHP 7 или Как ускорить сайт за минуту без регистрации и СМС

Вы наверняка в курсе, что PHP удерживает лидерство среди языков, применяющихся для создания сайтов. В конце 2019 года версия PHP...
Read More
Приключенческая игра с самым опасным ИИ в мире

Приключенческая игра с самым опасным ИИ в мире

Долгие праздничные каникулы уже позади, но многим наверняка хочется ещё немного отдохнуть от серьёзных тем. Поэтому сегодня мы расскажем вам...
Read More
Новогодний чек-лист: не пропразднуй свой домен

Новогодний чек-лист: не пропразднуй свой домен

Не все задачи и дела подходят под фразу «А давайте уже после праздников». И те, что «горят прям горят», мы...
Read More
Domains weekly: рост Интернета, зона .AMAZON и домен на миллион

Domains weekly: рост Интернета, зона .AMAZON и домен на миллион

Приближаются праздники, и в доменной индустрии всё становится спокойнее… Или нет? Из последней подборки в этом году вы узнаете, насколько...
Read More
Итоги 2019 года в блоге REG.RU: создание сайтов, бэкапы, нейросети и UX‑дизайн

Итоги 2019 года в блоге REG.RU: создание сайтов, бэкапы, нейросети и UX‑дизайн

Год приближается к финишной прямой, и редакция блога, следуя тренду, подводит его итоги. Сегодня мы поделимся материалами, которые вы больше...
Read More
Стэнфордский курс: лекция 10. Рекуррентные нейронные сети

Стэнфордский курс: лекция 10. Рекуррентные нейронные сети

В прошлый раз мы рассказали о нескольких популярных свёрточных архитектурах и узнали об их влиянии на развитие машинного обучения. В...
Read More
Domains weekly: карта Интернет-мира, перспективы Китая и доменная зависть

Domains weekly: карта Интернет-мира, перспективы Китая и доменная зависть

Новая подборка новостей будет очень полезной для домейнеров. Насколько активно страны мира регистрируют сайты в своих национальных доменных зонах? Есть...
Read More
Shared или VPS — какой хостинг выбрать для сайта?

Shared или VPS — какой хостинг выбрать для сайта?

Один из наших читателей попросил рассказать, какой же вариант хостинга стоит использовать для своего онлайн-проекта. Этим вопросом задаётся почти каждый...
Read More
С чего начать SEO-продвижение сайта

С чего начать SEO-продвижение сайта

Вы создали красивый и яркий сайт, грамотно расписали контент, добавили акценты для целевой аудитории, но трафик не растёт? Скорее всего,...
Read More