Ver como markdown

Treinamento multi-GPU

O treinamento multi-GPU no LibreYOLO é o DistributedDataParallel do PyTorch: um processo por GPU, cada um com uma réplica completa do modelo e uma fatia de cada batch, e a média dos gradientes é feita entre os ranks a cada passo.

Rodar em duas GPUs

Passe uma lista de dispositivos. Nada mais muda.

Python
from libreyolo import LibreYOLO # Este guard continua suportado; scripts comuns também funcionam sem ele.# Mantenha-o para objetos de callback/logger que precisem do fallback para pickle padrão.if __name__ == "__main__":    model = LibreYOLO("LibreYOLO9s.pt")    model.train(        data="my-dataset.yaml",        epochs=100,        batch=32,     # batch global: 16 imagens por GPU em duas GPUs        device="0,1",    )
CLI
libreyolo train model=LibreYOLO9s.pt data=my-dataset.yaml \  epochs=100 batch=32 device=0,1

Com mais de um dispositivo e sem ambiente torchrun, train() do modelo salva os pesos em um arquivo temporário, resolve autobatch se solicitado e inicia processos de workers gerenciados pelo coordenador, um por GPU. Cada worker importa novamente a classe do modelo, a reconstrói a partir dos pesos salvos e roda o caminho comum de um único dispositivo, porque as variáveis de ambiente torchrun estão definidas dentro de um worker iniciado. O melhor checkpoint do rank 0 é carregado de volta na instância do modelo de quem chamou quando a execução termina.

device aceita "0,1", [0, 1], 0, "cuda:0", "cpu", "mps" e "auto". Só uma lista com mais de um índice CUDA dispara o spawn.

libreyolo train device=0,1, ou device=[0,1], constrói o modelo na primeira GPU da lista e passa a lista para o train(), que inicia os workers do mesmo jeito.

Inicialização automática e proteção do main

model.train(device=[0, 1]) e device="0,1" usam ranks gerenciados pelo coordenador sem repetir o código de nível superior de um script comum sem proteção. Scripts protegidos e torchrun explícito continuam suportados. Jobs do coordenador usam cloudpickle; objetos de callback ou logger que exigem o fallback para pickle padrão ainda precisam da proteção if __name__ == "__main__":.

batch é o batch global

batch é o número de imagens por passo do otimizador somando todas as GPUs. O dataloader de cada rank é construído com batch // world_size e um DistributedSampler, então batch=32 em duas GPUs significa 16 imagens por GPU, não 32.

Um batch que não é divisível pelo world size levanta um erro em vez de treinar silenciosamente com um tamanho diferente:

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.

A média dos gradientes é feita pelo próprio DDP, então a loss é repassada sem escala. Multiplicá-la pelo world size além disso inflaria o learning rate efetivo por um fator próximo ao número de GPUs.

Autobatch sob DDP

batch=-1 funciona e devolve um batch global divisível pelo world size.

Python
from libreyolo import LibreYOLO if __name__ == "__main__":    model = LibreYOLO("LibreYOLO9s.pt")    # Sondado uma vez na GPU 0 e escalado para um múltiplo do world size.    model.train(data="my-dataset.yaml", batch=-1, device="0,1")

No caminho do spawn a sondagem roda no processo pai, no primeiro dispositivo, antes de qualquer worker existir, então cada worker recebe um inteiro concreto e nenhuma coordenação entre processos é necessária. Sob torchrun, o rank 0 sonda e transmite o resultado como um único tensor long.

A sondagem mede a capacidade de uma GPU e multiplica pelo world size. Quando nbs está definido, o batch global é limitado a nbs e arredondado para baixo até um múltiplo do world size, de modo que adicionar GPUs reduz o número de passos de acumulação em vez de encolher o batch por GPU. A mecânica da sondagem em si está em Hiperparâmetros.

SyncBatchNorm

Sob DDP as camadas de BatchNorm de cada rank enxergam apenas a própria fatia. Com batch // world_size essa fatia pode ficar pequena o bastante para as estatísticas acumuladas degradarem o modelo convergido em relação a uma execução em uma GPU só.

sync_bn=True converte cada BatchNorm em SyncBatchNorm para que as estatísticas sejam calculadas sobre o batch global. A conversão só acontece quando o modo distribuído está ativo, então uma execução em uma GPU só não é afetada pela flag de um jeito nem de outro.

Ela já vem ligada por padrão nas famílias convolucionais carregadas de BatchNorm: YOLOX, YOLOv7, YOLOv9 e suas variantes, YOLO-NAS, PicoDet, RTMDet e FOMO. Todas as outras famílias deixam a flag desligada por padrão. Quando um modelo contém BatchNorm, sync_bn está desligado e o batch por rank fica abaixo de 16, o trainer emite um aviso.

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

Não existe flag de CLI para sync_bn. É um argumento de Python.

Lançando com torchrun

O torchrun também funciona, e é a escolha certa quando um escalonador de cluster já cuida do lançamento dos processos. Escreva o script para um único dispositivo e deixe o torchrun definir o ambiente de rank.

train.py
from libreyolo import LibreYOLO if __name__ == "__main__":    model = LibreYOLO("LibreYOLO9s.pt")    model.train(data="my-dataset.yaml", epochs=100, batch=32)
Execução
torchrun --nproc_per_node=2 train.py

Não combine os dois. Com o ambiente do torchrun presente, device="0,1" não faz spawn; o trainer assume cuda:LOCAL_RANK e o torchrun cuida da contagem de processos.

Comportamento dos ranks

O rank 0 é dono de todo efeito colateral. Ele resolve o diretório da execução e transmite o nome resolvido para que todos os ranks concordem, escreve checkpoints e artefatos e dispara os callbacks e loggers do usuário. Os outros ranks treinam e contribuem com gradientes.

Cada rank semeia o RNG do dataloader e do data augmentation de forma diferente, a partir do seed configurado, para que os ranks não sorteiem as mesmas augmentações.

Plataforma e backend

O backend é escolhido automaticamente: NCCL quando CUDA e NCCL estão ambos disponíveis, Gloo caso contrário. O NCCL não é compilado no Windows, então execuções no Windows usam Gloo sem nenhuma configuração. O grupo de processos é inicializado com um timeout de três horas.

O que não roda sob DDP

  • Captura de CUDA graph. cuda_graph=True registra uma linha e treina em modo eager. Veja Desempenho de treinamento.
  • O profiler de treinamento. profile=True é ignorado com um aviso.

Nem toda família suporta o spawn automático. Vinte e quatro suportam, cobrindo as famílias de detecção, classificação, semântica e restauração que treinam. Uma família sem esse suporte, ao receber um dispositivo multi-GPU, levanta um erro que nomeia a API do modelo e o comando do torchrun em vez de treinar silenciosamente em uma GPU só.

Verificado com o LibreYOLO v1.6.0.