Contração De Treinamento Frequente - Contração De Treinamento Frequente - RETOEDU
Contração De Treinamento Frequente - RETOEDU

Um guia prático para quem quer treinar modelos maiores sem GPU de.datacenter

Eu trabalho com fine-tuning de LLMs desde 2022. Já quebrei cabeça com memória de VRAM e acabei encontrando a solução mais prática que existe para treinar modelos grandes em hardware limitado: a técnica de gradiente acumulado, que no Brasil costuma aparecer como "contração de treinamento frequente". Não tem hype, não tem marketing. É pura matemática aplicada e já vai resolver seu problema.

O que é contração de treinamento frequente e como funciona na prática

A contração de treinamento frequente funciona assim: você divide seu batch total em sub-batches menores, processa cada um individualmente, acumula os gradientes calculados em cada passo e só faz o update dos pesos do modelo quando tiver procesado todos os sub-batches. O resultado é matematicamente idêntico ao treino com batch grande de uma vez, mas a VRAM necessária cai drasticamente porque você nunca carrega tudo na memória simultaneamente. Eu tive um caso real que ilustra bem. Estava fine-tunando um LLaMA-2-7B com QLoRA num setup de duas RTX 4090. O batch size ideal para convergência estava em 64, mas o modelo estourava a memória com batch de 8. A solução foi usar gradient accumulation com gradient_accumulation_steps igual a 8. Resultado: treino funcional com batch efetivo de 64 usando batch_size real de 8 por step. Economizei uma placa de vídeo e uma semana de waitlist de NVIDIA.

Como implementar passo a passo

Vamos direto ao código. Se você usa Hugging Face Transformers com Trainer API, é simplesmente: gradient_accumulation_steps = total_batch_size // effective_batch_size_per_gpu

Se seu effective batch size alvo é 32 e você está processando 4 amostras por GPU por step, então gradient_accumulation_steps = 8. Pronto. O trainer acumula automaticamente. Se você tá construindo o loop de treino manualmente, a lógica é ainda mais direta. Inicialize um acumulador de gradientes como zeros, rode o forward pass por sub-batch, calcule a loss e make bachward, mas em vez de chamar optimizer.step(), some o gradiente ao acumulador. Só chame optimizer.step() quando o contador de steps atingir o valor de acumulação, aí também faz optimizer.zero_grad() e reseta o contador.

Dicas que ninguém conta

Existem dois pontos que pegam todo mundo desprevenido. Primeiro: o lr_scheduler precisa ser ajustado. Quando você aumenta gradient_accumulation_steps, você efetivamente reduz a frequência de updates de peso. Se mantiver o learning rate original, a curva de perda pode oscilar demais. A regra prática é dividir o learning rate inicial pela quantidade de acumulação, ou pelo menos reduzir em 30 a 50 por cento. Não é lei, mas funciona na maioria dos casos. Segundo ponto: métricas de logging. Se você estiver usandowandb ou tensorboard para monitorar loss, configure para logar a loss média acumulada e não a loss de cada sub-batch isolado. Loss de sub-batch com gradiente acumulado é barulhada demais e distorce a curva de aprendizado visual. Você vai ficar achando que o modelo não tá convergindo quando na verdade tá tudo certo.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Um detalhe técnico importante que iniciantes ignoram: com gradient accumulation, o batch size efetivo muda a estabilidade do gradiente. Batch muito pequeno por sub-batch (tipo 1 ou 2) gera gradientes ruidosos mesmo acumulando. O recomendado é manter pelo menos 4 samples por sub-batch. Se seu hardware for realmente apertado e você precisar usar batch de 1, considere adicionar gradient checkpointing junto. A combinação dos dois reduz ainda mais o uso de memória, mas custa uns 15 a 20 por cento em tempo de computação extra devido ao recomputation durante o backward pass.

Quando essa técnica não resolve

É importante ser honesto aqui. Contração de treinamento frequente com gradiente acumulado não milagros. Se você tem um modelo que já excede a memória mesmo com batch de 1, essa técnica sozinha não ajuda. Aí você precisa de outras abordagens: quantização (QLoRA, bitsandbytes), pipeline parallelism, ou simplesmente rodar em hardware maior. A acumulação de gradiente economiza memória de activation, não a memória dos pesos do modelo. Os pesos continuam ocupando o mesmo espaço, independente do batch size. Também não adianta esperar que isso torne o treino mais rápido. Pelo contrário, vai ser mais lento. Overhead de comunicação entre steps e a perda de parallelismo interno do batch grande tornam o tempo total de treino maior. Em muitos casos, o aumento é na faixa de 20 a 40 por cento dependendo do número de etapas de acumulação. Mas se a alternativa é não conseguir treinar de jeito nenhum, o trade-off vale a pena.

Exemplo completo com código

Aqui está um exemplo prático usando Hugging Face Trainer: training_args = TrainingArguments( output_dir="./output", per_device_train_batch_size=4, gradient_accumulation_steps=8, learning_rate=2e-5, num_train_epochs=3, logging_steps=10, save_strategy="epoch", fp16=True, )

Com batch_size 4 e accumulation steps 8, seu batch efetivo é 32. Com mixed precision (fp16), modelos de até 13 bilhões de parâmetros cabem confortavelmente numa única RTX 4090 24GB durante fine-tuning com LoRA. Se quiser uma alternativa ao GradAcc puro, dê uma olhada no gradient accumulation com micro-batching do DeepSpeed ZeRO Stage 2. Ele faz basicamente a mesma coisa mas com otimizações adicionais de memória e comunicação distribuída. A configuração inicial é mais complexa, mas em setups multi-GPU o ganho de eficiência pode ser significativo.

Contração de treinamento frequente é uma daquelas técnicas que parece complicada quando você lê a teoria mas na prática é uma linha de configuração. O importante é entender o que está acontecendo por baixo: você tá simplesmente trocando espaço por tempo. Mais tempo de computação, menos memória de GPU. E isso é perfeitamente aceitável na maioria dos cenários reais de desenvolvimento. Se você começou a ter erro de CUDA out of memory e precisa de uma solução rápida hoje, comece com gradient_accumulation_steps. Meça o uso de memória, ajuste o learning rate, e monitore a loss acumulada. Em geral você consegue fazer o modelo rodar em meia hora sem precisar pedir budget pra GPU cloud.