Хочешь сэкономить на облаке? Не игнорируй, утилизацию GPU при тренировке сетей
Современные сети тренируются на GPU пока данные подготавливает CPU.
При этом программисты, обращают внимание на то как уменьшается со временем функция потерь, игнорируя не менее важную метрику — утилизацию GPU, которая про $$$ причем часто не малые.
Рекомендуется стремимся к тому, чтобы утилизация составляла как минимум 80%. Если показатель ниже, стоит искать причины.
История из практики про много денег
На прошлой работе был случай. Наша команда и соседи решали похожие задачи на одних и тех же данных. Но наши модели были заметно точнее.
Начали разбираться. Выяснилось, что коллеги недотренировывают сети. Тренировка останавливалась не позже чем три дня, хотя функция потерь имела потенциал в понижению, так как более длительная замедляла итерации.
Но и у нас тренировка останавливалась через три дня. Полезли глубже.
Посмотрели на функцию потерь — начало одинаковое, но наша в 4 раза длиннее и этот дополнительный кусок уходит ниже к нулю. То есть через нашу сеть данные шли в в 4 раза быстрее.
Тут догадались проверить утилизацию GPU. У нас 80%, у них 20%. При этом разница между нашими командами во фреймворака мы использовать PyTorch, а они Tensorflow.
А на тот момент TensorFlow очень неэффективно работал в Multi GPU режиме. Тут я без претензий к ребятам, так карта легла, все могло быть ровно наоборот.
Если сильно не вру, годовой бюджет на AWS у нашего подразделения был около $10,000,000 в год, из которых больше половины планировалось потратить на GPU.
Любители арифметики могут пересчитать в бутылках пива чего нам стоило игнорирование важной метрики.
Низкая утилизация бьет сразу по нескольким финансовым болевым точкам. Разберем случай 20% против 80%. Тренируем в 4 раза медленнее => для достижения того же результата надо тренировать в 4 раза дольше, что:
стоит в 4 раза больше денег, так как на AWS оплата за GPU / час
занимает в 4 раза больше времени, то есть либо сети недотренированные, либо в 4 раза более медленные итерации
дает коcвенные затраты на зарплаты программистам. Сеть тренируется — зарплата капает.
Так что если работник просит больше вычислительных GPU ресурсов, или есть вопросы к скорости итераций вашего ML отдела — уточните, а лучше проверьте, какая текущая утилизация GPU ресурсов.
Варианты:
Средняя утилизация за весь период когда сервер с GPU запущен. Позволяет находить ситуации когда когда работник запустил сервер с GPU, не свернул и пошел домой.
Низкая утилизация GPU при тренировке говорит, что есть узкие места, которые можно устранить.
А теперь действия, которые помогут определить и улучшить утилизацию GPU, снизив тем самым затраты и ускорив процесс обучения.
(Ниже опишу грабли на которые сам наступал. Кто наступал на другие — поделитесь в комментариях, пожайлуста, очень любопытно где еще может прилететь.)
Первые шаг — посмотреть что происхот сейчас
Как и в любой диагностике надо понять где мы сейчас.
Есть продвинутые инструменты, например, Weights & Biases, там можно собирать данные, агрегировать, экспортировать, создавать всевозможные отчеты для разных команд — круто, масштабируемо, но надо настраивать.
Если хочется быстро и дешево, то можно запустить утилиту nvtop, которая присуствует в любом современном линуксе.
Экран nvtop. Пример хорошей ситуации. На всех четырех GPU утилизация 80%+
Тренировка запускается, функция потерь идет вниз, нет ошибок в логах, просто заметно медлееннее чем ожидалось.
Может получиться, что используются не все GPU по причине что
В коде закралась строчка которая меняет значения переменной системной
CUDA_VISIBLE_DEVICES
.Используются фреймворки для тренировок типа Pytorch Lightning и в конфиге указано неправильное число GPU.
Используются все GPU, но утилизация ниже требуемой
Подозреваемый — CPU.
Надо понять, что происходит с подготовкой данных.
При тренировке сети GPU отвечает за forward и backward pass, а подготовкой этих данных занимается CPU.
Как только GPU отработал с батчем, CPU пересылает новый.
Q: А что случается когда CPU не успевает за GPU?
A: GPU простаивает и утилизация падает.
Почему такое случается?
И тут снова два варианта.
Медленный CPU и / или задача по подготовке данных требует больших вычислительных мощностей.
Для диагностики — утилита htop, также доступна во всех современных дистрибутивах линукса.
Если nvtop
показывает низкую утилизацию, а htop
показывает 100% утилизацию CPU, то это оно.
Случается при совпадении факторов:
Медленный для данной задачи CPU
Тяжелый препроцессинг или аугментационный пайплайн
С такой ситуацией мы столкнулись в одном из соревнований на Kaggle. Для аугментации использовали библиотеку imgaug. В ней замечательный функционал, но под скорость не оптимизирована.
Почти каждый в команде имел комрьютер с несколькими GPU и CPU не справлялся.
Начали писать свои велосипеды и так появилась библиотека для аугментации изображений, которая как раз и оптимизированная под скорость вычислений — Albumentations.
Подготовка данных происходит в DataLoader’е и именно там и надо смотреть.
Посмотреть на аугментационный пайплайн. Если есть кусок, который уменьшает размер изображения за счет Crop или Resize, лучше это делать первой операцией, а не последней. Изображение 4096×4096 в 64 раза более ресурсоемко обрабатывать чем 512×512.
Может быть тяжелый препроцессинг, например, данные это ЭЭГ это временные ряды на 20 каналов и вы каждый раз считываете их и преобразуете в 20 канальную спектрограмму. Это дорогая повторяющаяся операция. Можно было подготовить спектрограммы заранее и загружать сразу их.
Где-то поможет смена библиотеки для аугментаций на более быструю.
Сколько-то знакомых пожалели когда купили компьютеры для тренирвки сетей и взяли с несколькими мощными GPU, но не очень мощным CPU, ибо «самое важное — это GPU, так как тренировка проходит на них» и потом долго пытались придумать как повышать утилизацию и понижать простаивание.
Не вытягивает I/O
Если nvtop
и htop
не на максимуме, то подозреваемым становится чтение данных.
Под этот случай есть утилита iotop, но я сам ей толком не пользовался.
Данные надо откуда-то брать.
При этом оптимизировать можно то, откуда вы их берете и как вы их декодируете.
Данные на лету считываются с облачного хранилища, скажем S3
Пример:
знакомые тренировали разные модели на одних и тех же данных, которые хранились на S3.
Чтобы упростить код и ускорить onboarding новых членов команды, написали DataLoader который загружал картинки на лету из хранилища.
Скорости сети не хватало, GPU простаивали, пришлось менять.
Надо:
Либо перед тренировкой копировать средствами bash данные локально.
Либо Data Loader с кэшированием, который при первом запросе к изображению считывает с S3 и сохраняеть локально, а при последующих запросах подгражает из кэша.
Если данные уже хранятся л
Локально, но скорости считывания не хватает.
Поместить данные в оперативную память.
Оперативная память дешевая, докупить в свой компьютер или выбрать машину в облаке где ее много — это вариант.
Случайный доступ к данным в оперативной памяти очень быстрый. Проблема может решиться без изменений в кодовой базе.
HDD => SSD
Тот же аргумент, что и выше. SSD недорогие, что при покупке в личное пользоваение, что при выборе машины в облаке.
Уменьшение размера изображений
Обработать заранее. Как упоминалось выше — изображение размером 4096×4096 в 64 раза тяжелее чем 512×512.
Это, кстати, не всегда приемлимо.
Криминалистика — определяют это исходное изображение или был монтаж и внего был вставлен кусок другого, там важны не видимые глазу микроартефакты, характерной для модели телефона. При масштабировании могут пропаcть.
По selfie определять глюкозу в крови, пульс, уровень увлажненности кожи.
Но это, скорее, узкие ниши и в большинстве случаев менять масштаб можно.
При тренировке ImageNet картинки считываются с диска, после чего им на лету меняют масштаб с сохранением отношения сторон так, чтобы минимальная сторона была 256, и после этого при тренировке случайным образом выделяют регионы 224×224.
Можно это сделать заранее.
Смена уровня компрессии
В JPEG переход от 90 качества до 70 уменьшает размер изображений в два раза.
Группировка изображений в мини батчи
Случайный доступ к данным на диске медленее, чем последовательный. При этом тренировочный батч создается из случайного подмножества данных.
Можно собирать изображения в небольшие группы с помощью tfrecords или просто tar файлов. А сам тренировочный батч собирать из таких мини групп. На практике работает неотличимо от полностью случайных батчей.
Смена библиотеки для считывани JPEG файлов с диска
[Честно говоря, изначально текст я писал под эту секцию, но вступление что-то затянулось]
Изображения хранятся в сжатом формате, соответственно их надо не только считать, но и декодировать. И разные библиотеки делают это с разной скоростью.
Я написал код для benchmark [ссылка на Github], где сравнил с какой скоростью библиотеки:
skimage: 0.22.0
imageio: 2.34.0
opencv: 4.9.0.80
PIL (pillow): 10.2.0
jpeg4py: 0.1.4
torchvision: 0.17.1
tensorflow: 2.15.0.post1
считывают 2000 файлов с валидационного сета ImageNet, делая 1 разогревочный прогон и 5 боевых, значения по которым усреднялись.
с трех HDD и трех SSD, которые стоят в моем десктопе
QVO 870: SAMSUNG 870 QVO SATA III 2.5» SSD 8TB (MZ-77Q8T0B)
Samsung 960Pro: Samsung 960 PRO Series — 512GB PCIe NVMe — M.2 (MZ-V6P512BW)
WDC 8TB: WD Red Pro 8TB NAS Internal Hard Drive — 7200 RPM, SATA 6 Gb/s (WD8003FFBX)
WDC 6TB: WD Red Pro 6TB 3.5-Inch SATA III 7200rpm NAS Internal Hard Drive (WD6002FFWX)
Toshiba 8TB: Toshiba X300 8TB Performance Desktop Gaming Hard Drive 7200 RPM 128MB Cache SATA 6.0Gb/s 3.5 inch (HDWF180XZSTA)
EVO 850: Samsung 850 EVO 1 TB 2.5-Inch SATA III Internal SSD (MZ-75E1T0B/AM)
на CPU: AMD Ryzen Threadripper 3970×32-Core Processor. В коде я постарался для вычислений выделить только один поток, так как это масксимально приближено к сетапу в котором подгатавливается батч для сети.
Что вообще неожиданно, так как opencv быстрая библиотека и я использую как раз ее для чтения файлов. А в Tensorflow и Torchvision мы дополнительно конвертируем тензоры в массивы numpy.
Заключение
Тем кто до сюда дочитал пожелаю обращать внимание не только на то софтверную состовляющую процесса, но и на железячную. Там уходит вникуда очень много денег.