Переглянути як Markdown

Навчання на кількох GPU

Навчання на кількох GPU в LibreYOLO використовує PyTorch DistributedDataParallel: по одному процесу на GPU, кожен із повною реплікою моделі та частиною кожного батча, а градієнти усереднюються між рангами на кожному кроці.

Запуск на двох GPU

Передайте список пристроїв. Більше нічого не змінюється.

Python
from libreyolo import LibreYOLO # Захисна умова __main__ обов'язкова: кожен породжений робочий процес повторно імпортує# цей модуль, а без умови рекурсивно перезапускав би навчання.if __name__ == "__main__":    model = LibreYOLO("LibreYOLO9s.pt")    model.train(        data="my-dataset.yaml",        epochs=100,        batch=32,     # глобальний батч: 16 зображень на GPU для двох GPU        device="0,1",    )

Якщо задано кілька пристроїв без середовища torchrun, метод train() моделі зберігає ваги в тимчасовий файл, визначає autobatch за відповідного запиту й породжує по одному робочому процесу на GPU через torch.multiprocessing.spawn. Кожен робочий процес повторно імпортує клас моделі, відтворює її зі збережених ваг і виконує звичайний шлях для одного пристрою, оскільки всередині породженого робочого процесу вже задано змінні середовища torchrun. Після завершення запуску найкраща контрольна точка рангу 0 завантажується назад до екземпляра моделі виклику.

device приймає "0,1", [0, 1], 0, "cuda:0", "cpu", "mps" і "auto". Автоматичний запуск активує лише список із кількох індексів CUDA.

Захисна умова __main__ обов'язкова

Породжені робочі процеси повторно імпортують модуль, з якого походять. Без умови if __name__ == "__main__": цей імпорт повторно виконує виклик навчання, а кожен робочий процес породжує власні процеси. Бібліотека виявляє це й породжує помилку, не допускаючи рекурсії:

spawn_ddp_train() was called from inside a spawned subprocess. This usually
means your script calls model.train(device=...) at the top level without a
'if __name__ == "__main__":' guard.

Усе, що передається до робочого процесу, серіалізується через pickle, тому значення callbacks= має підтримувати pickle. Клас рівня модуля підходить, а замикання чи lambda не підходять. Повідомлення про помилку пояснює це й указує на вбудовані логери як альтернативу.

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 rank
trains at batch // world_size, so this value would silently train at a
different global batch than requested. Use batch=4 or batch=8.

DDP сам усереднює градієнти, тому функція втрат передається без масштабування. Додаткове множення на world size збільшило б ефективну швидкість навчання приблизно в кількість разів, що дорівнює кількості GPU.

Autobatch у DDP

batch=-1 працює й повертає глобальний батч, кратний world size.

Python
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, засіб навчання виводить попередження.

Python
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,    )

Прапорця CLI для sync_bn немає. Це аргумент Python.

Запуск за допомогою torchrun

Torchrun також підтримується й є правильним вибором, коли планувальник кластера вже керує запуском процесів. Напишіть скрипт для одного пристрою, а torchrun налаштує середовище рангів.

train.py
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, тому ранги не отримують однакових аугментацій.

Платформа й backend

Backend вибирається автоматично: NCCL, коли доступні CUDA й NCCL, інакше Gloo. NCCL не збирається на Windows, тому без додаткового налаштування запуски Windows використовують Gloo. Група процесів ініціалізується з тайм-аутом три години.

Що не працює в DDP

  • Захоплення графа CUDA. cuda_graph=True записує один рядок у лог і навчається в eager-режимі. Див. розділ Продуктивність навчання.
  • Профайлер навчання. profile=True ігнорується з попередженням.

Не кожне сімейство підтримує автоматичне породження процесів. Його підтримують двадцять чотири сімейства, що охоплюють навчувані сімейства виявлення, класифікації, семантичної сегментації та відновлення. Якщо передати кілька GPU сімейству без такої підтримки, воно породжує помилку з назвою API моделі й командою torchrun, а не непомітно навчається на одному GPU.

Пов'язані матеріали

Перевірено з LibreYOLO v1.5.0.