Обучение на нескольких GPU
Обучение на нескольких GPU в LibreYOLO — это PyTorch DistributedDataParallel: один процесс на GPU, у каждого полная копия модели и своя часть каждого батча, а градиенты усредняются по рангам на каждом шаге.
Запуск на двух GPU
Передайте список устройств. Больше ничего менять не нужно.
from libreyolo import LibreYOLO # Эта защита поддерживается; обычные скрипты работают и без неё.# Сохраните её для колбэков и логгеров, которым нужен стандартный pickle.if __name__ == "__main__": model = LibreYOLO("LibreYOLO9s.pt") model.train( data="my-dataset.yaml", epochs=100, batch=32, # глобальный батч: 16 изображений на GPU при двух GPU device="0,1", )libreyolo train model=LibreYOLO9s.pt data=my-dataset.yaml \ epochs=100 batch=32 device=0,1Если указано больше одного устройства и окружение torchrun отсутствует, train() модели сохраняет веса во временный файл, разрешает autobatch, если он запрошен, и запускает рабочие процессы под управлением координатора, по одному на GPU. Каждый процесс заново импортирует класс модели, восстанавливает её из сохранённых весов и выполняет обычный путь для одного устройства, поскольку внутри запущенного процесса выставлены переменные окружения torchrun. Когда запуск завершается, лучший чекпойнт ранга 0 загружается обратно в экземпляр модели вызывающего кода.
device принимает "0,1", [0, 1], 0, "cuda:0", "cpu", "mps" и
"auto". Порождение процессов запускается только для списка из нескольких
индексов CUDA.
libreyolo train device=0,1 или device=[0,1] строит модель на первом GPU из
списка и передаёт список в train(), который порождает воркеры так же.
Автоматический запуск и защита main
model.train(device=[0, 1]) и device="0,1" используют ранги под управлением координатора без повторного выполнения кода верхнего уровня обычного скрипта без защиты main. Скрипты с защитой и явный torchrun по-прежнему поддерживаются. Задачи координатора используют cloudpickle; объекты колбэков или логгеров, которым нужен запасной путь через стандартный pickle, всё ещё требуют защиты if __name__ == "__main__":.
batch — это глобальный батч
batch — это число изображений на один шаг оптимизатора по всем GPU. Загрузчик
данных каждого ранга строится с размером batch // world_size и
DistributedSampler, поэтому batch=32 на двух GPU означает 16 изображений на
GPU, а не 32.
Батч, который не делится нацело на world size, приводит к ошибке, а не к тихому обучению с другим размером:
batch=6 is the global batch and must be divisible by world_size=4: each ranktrains at batch // world_size, so this value would silently train at adifferent global batch than requested. Use batch=4 or batch=8.Градиенты усредняет сам DDP, поэтому значение функции потерь передаётся дальше без масштабирования. Домножение его на world size сверх этого раздуло бы эффективную скорость обучения примерно во столько раз, сколько GPU.
Autobatch под DDP
batch=-1 работает и возвращает глобальный батч, делящийся на world size.
from libreyolo import LibreYOLO if __name__ == "__main__": model = LibreYOLO("LibreYOLO9s.pt") # Замеряется один раз на GPU 0, результат кратен world size. model.train(data="my-dataset.yaml", batch=-1, device="0,1")На пути с порождением процессов замер идёт в родительском процессе на первом устройстве, ещё до появления воркеров, поэтому каждый воркер получает конкретное целое число и межпроцессная координация не нужна. Под torchrun замер делает ранг 0 и рассылает результат одним тензором типа long.
Замер определяет, сколько помещается на одной GPU, и умножает результат на world
size. Если задан nbs, глобальный батч ограничивается сверху значением nbs и
округляется вниз до кратного world size, поэтому добавление GPU уменьшает число
шагов накопления, а не батч на одну GPU. Как устроен сам замер, описано на
странице Гиперпараметры.
SyncBatchNorm
При DDP слои BatchNorm каждого ранга видят только свою часть батча. При размере
batch // world_size эта часть может оказаться настолько маленькой, что
накопленная статистика ухудшит сошедшуюся модель по сравнению с запуском на
одной GPU.
sync_bn=True превращает каждый BatchNorm в SyncBatchNorm, так что статистика
считается по глобальному батчу. Преобразование происходит только при активном
распределённом режиме, поэтому на запуск с одной GPU этот флаг не влияет ни в
одном из положений.
Для свёрточных семейств с большим числом слоёв BatchNorm он и так включён по
умолчанию: YOLOX, YOLOv7, YOLOv9 и его варианты, YOLO-NAS, PicoDet, RTMDet и
FOMO. У всех остальных семейств по умолчанию выключен. Если модель содержит
BatchNorm, sync_bn выключен, а батч на ранг меньше 16, тренер выдаёт
предупреждение.
from libreyolo import LibreYOLO if __name__ == "__main__": model = LibreYOLO("LibreRTDETRr18.pt") model.train( data="my-dataset.yaml", batch=32, device="0,1", sync_bn=True, )У sync_bn нет флага в CLI. Это аргумент Python.
Запуск через torchrun
torchrun тоже работает и подходит лучше, когда запуском процессов уже управляет планировщик кластера. Напишите скрипт под одно устройство и дайте torchrun выставить переменные окружения с рангами.
from libreyolo import LibreYOLO if __name__ == "__main__": model = LibreYOLO("LibreYOLO9s.pt") model.train(data="my-dataset.yaml", epochs=100, batch=32)torchrun --nproc_per_node=2 train.pyНе совмещайте эти два способа. Если окружение torchrun присутствует, device="0,1"
процессы не порождает: тренер берёт cuda:LOCAL_RANK, а количеством процессов
управляет torchrun.
Поведение рангов
Все побочные эффекты — за рангом 0. Он определяет каталог запуска и рассылает выбранное имя, чтобы все ранги сошлись на одном, пишет чекпойнты и артефакты, вызывает пользовательские колбэки и логгеры. Остальные ранги обучаются и отдают градиенты.
Каждый ранг инициализирует генератор случайных чисел загрузчика данных и
аугментаций своим значением, выведенным из заданного seed, поэтому ранги не
выбирают одинаковые аугментации.
Платформа и бэкенд
Бэкенд выбирается автоматически: NCCL, когда доступны и CUDA, и NCCL, иначе Gloo. Под Windows NCCL не собирается, поэтому запуски на Windows получают Gloo без какой-либо настройки. Группа процессов инициализируется с таймаутом в три часа.
Что не работает под DDP
- Захват CUDA-графа.
cuda_graph=Trueпишет одну строку в лог, и обучение идёт в eager-режиме. См. Производительность обучения. - Профилировщик обучения.
profile=Trueигнорируется с предупреждением.
Автоматическое порождение процессов поддерживают не все семейства. Таких двадцать четыре — все обучаемые семейства детекции, классификации, семантической сегментации и восстановления. Семейство без такой поддержки, получив устройство из нескольких GPU, выбрасывает ошибку, называя API модели и команду torchrun, а не обучается молча на одной GPU.
Смотрите также
- Гиперпараметры — про
batch,nbsи возобновление. - Логгеры экспериментов — про требование picklable к колбэкам.
- Обучение на арендованной GPU — про аренду машины с несколькими GPU.