Treinamento em Escala

Treinamento em Escala (Training at Scale)

Práticas de engenharia para treinar modelos grandes de forma confiável.

O que significa “Treinamento em Escala”

Treinamento em escala é o conjunto de práticas de engenharia que permitem treinar modelos grandes (large models) (bilhões de parâmetros, trilhões de tokens, entradas multimodais (multi-modal inputs)) de forma confiável, eficiente e repetível em múltiplos aceleradores (accelerators) (GPUs/TPUs) em uma ou várias máquinas.

Escalar o treinamento não é apenas “rodar o mesmo código em mais GPUs”. À medida que modelos e clusters (clusters) crescem, os problemas dominantes mudam de aprendizado de máquina (machine learning) puro para engenharia de sistemas (systems engineering):

  • Gargalos de vazão (throughput) (pipeline de dados, rede, pré-processamento em CPU)
  • Gargalos de memória (estado do otimizador, ativações, contexto longo)
  • Problemas numéricos (NaNs/infs, overflow em precisão reduzida)
  • Falhas distribuídas (preempção de nó, travamentos do NCCL, trabalhadores lentos (stragglers))
  • Reprodutibilidade e rastreamento de experimentos sob não determinismo (nondeterminism)
  • Controle de custos e utilização (manter hardware caro ocupado)

Este artigo foca nas práticas orientadas à confiabilidade que tornam grandes execuções de treinamento previsíveis. Para aprofundamentos, veja:

Por que Escalar Quebra: Os Principais Modos de Falha

Treinamento em larga escala tende a falhar de algumas formas recorrentes:

  1. O modelo diverge

    • A loss dispara, gradientes explodem, NaNs aparecem.
    • Frequentemente causado por taxa de aprendizado (learning rate, LR) agressiva demais, inicialização ruim, overflow de precisão mista, ou configuração incorreta de distribuição/otimizador.
  2. A execução entra em deadlock ou trava

    • Um worker é lento (straggler), levando a esperas de sincronização.
    • Bibliotecas de comunicação (por exemplo, NCCL) podem travar com instabilidades de rede ou coletivas incompatíveis.
  3. Estouro de memória (OOM, out-of-memory)

    • Ativações, estado do otimizador ou buffers temporários excedem a memória do dispositivo.
    • Frequentemente ocorre ao escalar comprimento de sequência, tamanho de lote, ou ao habilitar logging/métricas adicionais.
  4. Baixa utilização

    • GPUs ociosas enquanto esperam por dados, pré-processamento em CPU, tokenização, leituras de disco, ou comunicação.
  5. Execuções que não podem ser reiniciadas

    • Uma execução de 2 semanas que não consegue retomar a partir de um checkpoint consistente é, na prática, frágil e cara.

Treinamento em escala é sobre projetar um sistema em que esses modos de falha sejam detectados cedo, mitigados automaticamente e recuperáveis quando ocorrerem.

Fundamentos Teóricos que Importam na Prática

Treinamento com lote grande e escalonamento da taxa de aprendizado

Quando você escala horizontalmente, em geral aumenta o tamanho de lote global (global batch size) (soma em todos os dispositivos). Isso muda a dinâmica de otimização em Descida do Gradiente (Gradient Descent):

  • Um lote maior reduz o ruído do gradiente, o que pode permitir uma taxa de aprendizado maior.
  • Mas lotes grandes demais podem prejudicar a generalização ou levar a treinamento instável se a LR for escalada de forma ingênua.

Heurísticas comuns:

  • Regra de escalonamento linear (linear scaling rule): se o lote aumenta por k, tente aumentar a taxa de aprendizado por k (frequentemente com aquecimento (warmup)).
  • Aquecimento: aumentar gradualmente a LR ao longo dos primeiros N passos/tokens para evitar instabilidade inicial quando os pesos ainda não estão calibrados.
  • Acumulação de gradientes (gradient accumulation): manter o microlote (microbatch) por dispositivo pequeno e acumular gradientes para alcançar o lote global desejado.

Na prática, você valida essas heurísticas empiricamente e monitora sinais de divergência (veja “Observabilidade (Observability)”).

Custos de comunicação e eficiência de escalonamento

Treinamento distribuído é limitado por:

  • Tempo de computação (forward/backward)
  • Tempo de comunicação (sincronização de gradientes, fragmentação de parâmetros)
  • Tempo de entrada (pipeline de dados)

Se adicionar GPUs não reduz o tempo por passo proporcionalmente, você está perdendo eficiência de escalonamento (scaling efficiency). O culpado usual é a sobrecarga de redução total (all-reduce) ou o carregamento de dados. É por isso que Treinamento Distribuído é tanto sobre sobrepor computação e comunicação e escolher a estratégia certa de paralelismo quanto sobre adicionar hardware.

Escalonamento de memória: parâmetros não são a única coisa

Para muitos otimizadores, o uso de memória é dominado por:

  • Parâmetros (pesos)
  • Gradientes
  • Estado do otimizador (optimizer state) (por exemplo, Adam mantém 1º e 2º momentos—frequentemente ~2× o tamanho dos parâmetros)
  • Ativações (crescem com tamanho de lote e comprimento de sequência)

Por isso o treinamento de modelos grandes frequentemente depende de:

  • Estados do otimizador fragmentados (sharded optimizer states) (por exemplo, fragmentação no estilo ZeRO/FSDP)
  • Checkpointing de ativações (activation checkpointing) (recomputar ativações em vez de armazená-las)
  • Precisão mista (Precisão Mista)

Escolhendo uma Estratégia de Paralelismo

Paralelismo é a principal alavanca para acomodar e acelerar modelos grandes. Os blocos mais comuns:

Paralelismo de dados (DP)

Cada worker tem uma cópia completa do modelo e processa diferentes shards de dados. Os gradientes são sincronizados (all-reduce).

  • Prós: conceitualmente simples, excelente quando o modelo cabe em um dispositivo.
  • Contras: custo de comunicação aumenta com o tamanho do modelo; estados do otimizador são replicados a menos que sejam fragmentados.

No PyTorch, o DP clássico é DistributedDataParallel (DDP).

Paralelismo de modelo (MP)

Divide o modelo entre dispositivos. Útil quando um único dispositivo não consegue armazenar o modelo.

Variantes comuns:

  • Paralelismo de tensores (tensor parallelism): divide multiplicações de matrizes entre dispositivos.
  • Paralelismo de sequência/contexto (sequence/context parallelism): divide a dimensão de sequência para reduzir memória de ativações.

Paralelismo de pipeline (PP)

Divide camadas em estágios; microlotes fluem através do pipeline.

  • Prós: pode escalar para modelos muito profundos.
  • Contras: “bolhas” do pipeline (pipeline “bubbles”) reduzem a utilização se não forem ajustadas; complexidade de agendamento.

Em sistemas reais, execuções grandes frequentemente combinam DP + MP + PP (às vezes chamado de “paralelismo 3D”). Uma visão prática está em Paralelismo de Dados/Modelo/Pipeline.

Fragmentação para reduzir memória: abordagens no estilo FSDP/ZeRO

Essas abordagens fragmentam parâmetros, gradientes e estado do otimizador entre ranks de paralelismo de dados, reduzindo drasticamente a memória por GPU.

  • Prós: permite treinar modelos que, de outra forma, não caberiam.
  • Contras: padrões de comunicação mais complexos; podem ser sensíveis à rede e à configuração.

Pipelines de Entrada: Mantendo os Aceleradores Abastecidos

Em escala, é comum ter GPUs esperando por dados. Um pipeline robusto tem:

  • Fragmentação determinística (deterministic sharding): cada worker recebe amostras únicas (ou sobreposição controlada).
  • Formato eficiente: por exemplo, registros binários fragmentados em vez de milhões de arquivos pequenos.
  • Pré-busca assíncrona (asynchronous prefetch): sobrepor decodificação/tokenização na CPU com computação na GPU.
  • Memória limitada (bounded memory): evitar filas sem limite que causam explosões de RAM.
  • Correção estatística: bom embaralhamento entre shards e épocas.

Práticas-chave:

  • Medir: acompanhar “tempo de dados” vs “tempo de computação” em cada passo.
  • Fazer cache com critério: cache local em NVMe para armazenamento remoto de objetos pode ajudar, mas deve lidar com expulsão de cache e consistência.
  • Tokenização em escala: pré-tokenize quando possível; se não, paralelize a tokenização e fixe (pin) threads de CPU.

Veja Carregamento de Dados e Pipelines de Entrada para detalhes de implementação e armadilhas.

Precisão Mista e Estabilidade Numérica

O treinamento grande moderno normalmente usa BF16 ou FP16 por velocidade e economia de memória:

  • BF16 geralmente é preferido em hardware recente porque tem uma faixa de expoente mais ampla (menos overflows) do que FP16.
  • FP16 frequentemente precisa de escalonamento da perda (loss scaling) para evitar underflow/overflow de gradientes.

Mesmo com BF16, algumas operações podem permanecer em FP32 (por exemplo, reduções, estabilização de softmax) para segurança numérica.

Dicas práticas de estabilidade:

  • Habilite detecção de anomalias apenas para depuração (é lento).
  • Adicione verificações para loss/gradientes não finitos e implemente recuperação automática (pular passo, reduzir LR, recarregar o último checkpoint).
  • Use implementações de otimizadores conhecidas por serem estáveis em precisão mista.

Um exemplo mínimo de PyTorch AMP:

scaler = torch.cuda.amp.GradScaler(enabled=True)  # often needed for FP16

for batch in loader:
    optimizer.zero_grad(set_to_none=True)
    with torch.cuda.amp.autocast(dtype=torch.bfloat16):  # or float16
        loss = model(**batch).loss

    scaler.scale(loss).backward()
    scaler.unscale_(optimizer)
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    scaler.step(optimizer)
    scaler.update()

Para um tratamento mais aprofundado, veja Precisão Mista.

Estabilizando a Otimização em Grande Escala

Execuções grandes amplificam “pequenos” erros de otimização. Práticas que ajudam:

Cronogramas de taxa de aprendizado e aquecimento

  • Aquecimento é especialmente importante para lotes grandes e modelos no estilo Arquitetura Transformer (Transformer Architecture).
  • Cronogramas baseados em tokens (token-based schedules) (LR em função de tokens vistos) costumam ser mais estáveis do que cronogramas baseados em passos (step-based schedules) quando você muda o tamanho de lote global.

Recorte de gradientes (gradient clipping)

O recorte ajuda quando lotes ocasionais produzem gradientes grandes (por exemplo, sequências longas, exemplos raros). Ele pode impedir que um único pico corrompa o estado do otimizador.

Regularização e normalização

  • Decaimento de peso (weight decay) é comum para modelos do tipo transformer.
  • Camadas de normalização (LayerNorm/RMSNorm) são sensíveis à precisão e à estabilidade de kernels em escala.

Acumulação de gradientes para tamanho de lote efetivo

Isso é frequentemente usado quando:

  • O lote por dispositivo é limitado por memória
  • Você quer um lote global maior sem aumentar o número de GPUs
accum_steps = 8
optimizer.zero_grad(set_to_none=True)

for i, batch in enumerate(loader):
    with autocast():
        loss = model(**batch).loss / accum_steps
    loss.backward()

    if (i + 1) % accum_steps == 0:
        clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        optimizer.zero_grad(set_to_none=True)

Execuções de verificação (“checagens de voo (flight checks)”)

Antes de gastar milhares de horas de GPU:

  • Superajuste (overfit) um dataset minúsculo (confirmar que a loss pode ir perto de zero)
  • Rode 100–500 passos com o setup distribuído completo
  • Verifique se a loss diminui de forma similar em 1 GPU, 8 GPUs e 64 GPUs (após ajustar lote/LR)

Checkpointing, Tolerância a Falhas e Semântica de Retomada

Em escala, falhas são normais: preempções, reinicializações de nós, instabilidades de filesystem. Seu treinamento precisa ser retomável.

Um checkpoint confiável normalmente inclui:

  • Pesos do modelo (possivelmente fragmentados)
  • Estado do otimizador (frequentemente o maior componente)
  • Estado do agendador de LR (LR scheduler)
  • Estados de RNG (Python/NumPy/framework/CUDA) se reprodutibilidade for importante
  • Posição do data-loader / época e índices de shard
  • Qualquer estado de escalonador de gradiente (gradient scaler) (para FP16)

Decisões de projeto:

  • Frequência: salvar checkpoint frequentemente o suficiente para limitar o pior caso de trabalho perdido (por exemplo, a cada 15–60 minutos).
  • Checkpointing assíncrono (async checkpointing): escrever em segundo plano para reduzir a pausa do treinamento.
  • Atomicidade (atomicity): escrever em um local temporário e “confirmar” (renomear) quando concluir.
  • Validação: testar periodicamente a restauração em um job separado.

Veja Checkpointing e Tolerância a Falhas.

Observabilidade: Monitorando o que Importa

Escalar com segurança exige instrumentação. No mínimo, acompanhe:

Sinais de treinamento

  • Loss (e loss suavizada)
  • Taxa de aprendizado
  • Normas de gradiente, normas de parâmetros
  • % de eventos de gradientes/loss não finitos
  • Contadores de overflow/underflow (se estiver usando escalonamento)

Sinais de sistema

  • Tempo por passo e sua decomposição: tempo de dados vs computação vs comunicação
  • Utilização da GPU, uso de memória, ocupação de SM (onde disponível)
  • Utilização da CPU e profundidade da fila do dataloader
  • Vazão de rede e tempo de redução total
  • I/O de disco e taxa de acerto de cache

Sinais de qualidade

  • Avaliação online em conjuntos de validação
  • Métricas específicas de tarefa (acurácia, perplexidade, BLEU, reward etc.)
  • Detecção de deriva (drift detection) para mudanças na mistura de dados

Exemplos práticos:

  • Registre histogramas de ativações/gradientes ocasionalmente (não a cada passo).
  • Dispare alertas em caso de:
    • aumentos súbitos no tempo por passo (straggler ou travamento de dados)
    • losses não finitas repetidas
    • regressão na validação além da tolerância

Um bom modelo mental: trate uma execução de treinamento multinó como um serviço em produção—se você não consegue observá-la, você não consegue operá-la.

Reprodutibilidade em Treinamento Distribuído com Precisão Mista

Determinismo perfeito frequentemente é impossível em escala devido a:

  • kernels não determinísticos
  • execução assíncrona
  • ordens diferentes de redução em all-reduce
  • não associatividade da aritmética de ponto flutuante

Ainda assim, você pode obter reprodutibilidade prática (practical reproducibility):

  • Fixe seeds aleatórias e registre-as.
  • Versione tudo: commit de código, configs, imagem de contêiner (container image), driver/CUDA, versões de bibliotecas.
  • Salve manifests da mistura de dados e pesos de amostragem.
  • Registre a topologia distribuída exata (tamanho do mundo (world size), ranks, configuração de fragmentação).

Para orientações concretas, veja Reprodutibilidade.

Engenharia para Confiabilidade: Padrões Comuns

Disciplina de configuração

Sistemas de treinamento grandes são pesados em configuração. Boas práticas:

  • Config com fonte única de verdade (YAML/JSON/Hydra) versionada em controle de versão
  • Validar configs no startup (checs de esquema)
  • Registrar a config totalmente resolvida no início da execução
  • Tornar mudanças explícitas (sem comportamento oculto dependente de ambiente)

Validação “falhar rápido (fail fast)” no startup

Antes de iniciar o loop caro:

  • Validar acesso ao dataset e contagens de shards
  • Aquecer alguns lotes através do pipeline completo
  • Verificar se comunicações coletivas funcionam (teste simples de all-reduce)
  • Confirmar permissões de escrita no diretório de checkpoint e a largura de banda esperada

Lidando com valores não finitos de forma robusta

Em vez de deixar NaNs se espalharem silenciosamente:

  • Detectar loss não finita
  • Pular o passo do otimizador
  • Reduzir LR ou recarregar o último checkpoint
  • Emitir um alerta com metadados do lote e IDs de amostras, se possível

Mitigação de stragglers

Treinamento síncrono é limitado pelo worker mais lento.

  • Garanta hardware homogêneo no job (evite misturar tipos de GPU)
  • Fixe afinidade de CPU e ajuste workers do dataloader
  • Observe throttling térmico e processos em segundo plano
  • Em casos extremos, considere abordagens elásticas ou assíncronas (mais complexas)

Juntando Tudo: Uma Receita Prática de “Escala Confiável”

Ao passar de um único nó para um grande cluster, uma sequência pragmática é:

  1. Correção em GPU única

    • Loss diminui; sem NaNs; avaliação funciona.
  2. Um nó com múltiplas GPUs (DDP/FSDP)

    • Validar a matemática do lote global e a sincronização de gradientes.
    • Comparar curvas com GPU única com lote efetivo equivalente.
  3. Multinó pequeno (2–8 nós)

    • Confirmar estabilidade de rede, configurações do NCCL e checkpointing.
  4. Escala total

    • Aumentar a escala gradualmente, medindo:
      • eficiência de escalonamento
      • trade-offs de qualidade do modelo vs vazão
      • taxa de falhas e tempo de recuperação
  5. Endurecimento operacional (operational hardening)

    • Testes regulares de restauração
    • Alertas em métricas críticas
    • Resumos automatizados de execução e relatórios de anomalias

Relação com Outros Tópicos de Deep Learning

Treinamento em escala é a “camada de sistemas” que sustenta fluxos de trabalho modernos de deep learning:

Principais Conclusões

  • Escalar o treinamento é limitado por comunicação, memória, vazão de entrada e confiabilidade, não apenas por FLOPs.
  • As práticas mais valiosas são tolerância a falhas, observabilidade e procedimentos de escalonamento validados.
  • Precisão mista, fragmentação e estratégias de paralelismo são poderosas, mas exigem ajuste cuidadoso e proteções (guardrails).
  • Trate grandes execuções de treinamento como sistemas de produção: instrumente-as, teste recuperação de falhas e controle mudanças de configuração.

Se você quiser se aprofundar em um subsistema específico, as próximas leituras mais acionáveis são Carregamento de Dados e Pipelines de Entrada, Treinamento Distribuído e Checkpointing e Tolerância a Falhas.