Содержание статьи
Как выбрать видеокарту для нейронных сетей и глубокого обучения
Чип видеокарты
До начала бума нейросетевых технологий разработка приложений для графических процессоров ограничивалась написанием функций-ядер на языке CUDA. Но, в отличие от CPU, в GPU нет сглаживающих негативные эффекты подсистем: большого кэша, малых штрафов за невыровненные адреса, предсказания ветвлений и так далее. Поэтому правильная оптимизация GPU-кода становится критически важной проблемой. Убедимся в этом на наглядном примере: ниже показан фрагмент CUDA-реализации билинейного фильтра.
Оказывается, компилятор не распознал процесс загрузки пикселей и сгенерировал ассемблерный код для однобайтовых значений вместо целого RGBA-вектора. Негативный эффект будет частично самортизирован L1-кэшем, с учётом того, в какой мере он занят поддержкой других операций. Но подобное решение всё равно может существенно замедлить приложение. На большом изображении (6496px × 6618px × 24bpp) для GPU Tesla V100 разница в скорости составляет примерно 40%:
Для повышения скорости обучения можно устанавливать несколько GPU. При обучении сверточных нейросетей можно получить ускорение в 1,9/2,8/3,5 раза, если использовать 2/3/4 GPU соответственно. Для рекуррентных сетей более важна длина последовательности. Для NLP ускорение будет примерно такое же, как для свёрточных, либо немного слабее.
Основная рекомендация при работе с Visual Profiler — использовать небольшие участки для профилирования. По умолчанию данные собираются в течение всего времени запуска, но лучше анализировать только критические места. Так вы сможете сосредоточить внимание на коде, оптимизация которого приведёт к существенному увеличению производительности. Использование Visual Profiler с большими и сложными приложениями может вызывать зависание движка JVM, поэтому лучше запускать его не более чем на 30 секунд.
Очень часто проблемы с производительностью возникают при переносе приложения с одной конфигурации сервера на другую. Обычно клиент и инженеры технической поддержки анализируют различия в hyperthreading, частотах памяти, вычислительных ядрах, версиях библиотек и прочих косвенных показателях. Более детальное исследование требует согласованных действий, часто приводящих к нарушению конфиденциальности данных, что упирается в IT-бюрократию. Поэтому получается, что «продвинутый» клиент гораздо эффективнее оказал бы помощь «сам себе». Немногие разработчики нейросетей умеют действительно «заглянуть под капот» приложения и детально проследить эффекты, влияющие на его производительность. Попробуем приоткрыть эту завесу и узнать, какие техники и приёмы следует использовать.
Также стоит присвоить пользовательские названия ресурсам CPU и CUDA, поскольку на временной шкале Visual Profiler имена по умолчанию не слишком информативные. Если использовать более понятные и говорящие наименования, то можно улучшить понимание поведения приложения, особенно когда в нём присутствует много устройств, контекстов или потоков.
Рассмотрим пример профилирования ML-приложения, которое использует мощности GPU. Предположим, перед нами стоит задача обучить LSTM-нейросеть для генерации связного текста. Для этого был написан скрипт на Python, основанный на Keras. Обратите внимание, что сам python-код явно GPU не использует. Но «выход» на GPU может происходить уже внутри функций библиотеки Keras без непосредственного участия пользователя. Поэтому смело запускайте NVIDIA Profiler, указывая в качестве приложения сам Python-скрипт, и профилировщик доберётся в нём до GPU-кода. Тем не менее, следует ограничить длительность профилирования небольшим интервалом, например, 30 секунд. Поскольку алгоритм обучения нейросети однообразен и периодичен, мы можем считать, что этот интервал характеризует поведение приложения в целом, упрощая при этом работу профилировщика по сбору данных.
Пропускная способность памяти имеет критическое значение для целей машинного обучения. Её влияние может быть даже выше, чем мощность графического процессора, например, при обучении распознаванию лиц. Данный параметр при необходимости можно самостоятельно рассчитать, умножив значение шины памяти на ее эффективную частоту, а потом разделив итоговое число на 8.
Оптимизация кода на GPU
После применения настроек Visual Profiler немедленно запустит ваше приложение и начнёт собирать данные, необходимые для первого этапа управляемого анализа (если только при создании сеанса не была выбрана опция Don’t run guided analysis). Систему управляемого анализа можно использовать для получения рекомендаций по улучшению производительности приложения.
Первый шаг в использовании Visual Profiler — создание нового сеанса профилирования. В нём будут содержаться настройки, данные и результаты анализа вашего приложения. Для этого необходимо указать исполняемый файл, а также, при желании — рабочий каталог, аргументы, параметры мультипроцессного профилирования и переменные окружения. При этом вы можете задать конкретные процессы для обработки, включить или выключить временную шкалу, а также установить различные параметры профилирования для CUDA и CPU.
Рассматриваемое приложение использует ядро умножения матриц с одинарной точностью (Single precision floating General Matrix Multiply, SGEMM). В окне свойств (справа) указано, что ядро запущено в конфигурации «16 блоков по 128 потоков». Это дало бы хороший результат на GPU начального уровня, но в мощных графических процессорах с архитектурой VOLTA доступно до тысячи таких блоков! Из этого следует, что ядро использует менее 1/10 доступных ресурсов.
Для задач, связанных с машинным обучением, необходимы большие вычислительные мощности. Грамотный выбор видеокарты для этих целей позволит с высокой скоростью вычислять оптимальные архитектуры и настройки глубоких сетей, выполнять эксперименты не за несколько месяцев, а за несколько дней и за минуты, вместо часов.
Специалисты Data Science делятся на два лагеря: те, кому кажется, что нейросети обучаются слишком медленно, и те, кому скоро тоже начнёт так казаться. В этой статье мы расскажем, как проанализировать и «прокачать» приложения машинного обучения с помощью профилирования.
В верхней части находятся горизонтальные отметки времени, прошедшего с начала профилирования приложения. В левой части отображены единицы исполнения: процесс (process), потоки (thread), GPU (device), контексты (context), ядра (kernel), стримы (stream) и т.д. (с полным списком можно ознакомиться в документации). В центре показаны строки, отражающие активность отдельных элементов. Каждая строка отображает интервалы времени между началом и окончанием каких-либо процессов. Например, строки напротив ядер показывают время начала и окончания выполнения этого ядра.
Source-Disassembly View используется для отображения результатов анализа на уровне ассемблера. В исходном коде отмечается интенсивность и эффективность выполнения отдельных команд. Соответствующие маркеры окрашиваются в разные цвета в зависимости от уровня критичности — низкого, среднего или высокого. Эта информация — основа для базового приёма оптимизации: выделение разработчиком наиболее критичных («тяжёлых») на общем фоне команд (hotpoints) и их переработка — изменение программной логики, понижение точности и так далее.
Analysis view отображает результаты анализа приложения. Доступны два режима: управляемый и неуправляемый. В управляемом режиме система проводит несколько этапов анализа, чтобы помочь вам понять слабые места в производительности и указать на возможности оптимизации приложения. В неуправляемом режиме вы можете самостоятельно запустить необходимые этапы и изучить их результаты. На рисунке ниже показан вид управляемого анализа: