Treine um modelo mais rapidamente com torch.compile e Gradient Accumulation


Treinar um modelo de linguagem com uma arquitetura de transformador profundo consome muito tempo. No entanto, existem técnicas que você pode usar para acelerar o treinamento. Neste artigo, você aprenderá sobre:

  • Usando torch.compile() para acelerar o modelo
  • Usando acumulação de gradiente para treinar um modelo com um tamanho de lote efetivo maior

Vamos começar!

Treine um modelo mais rapidamente com torch.compile e Gradient Accumulation

Treine um modelo mais rapidamente com torch.compile e Gradient Accumulation
Foto de François Genon. Alguns direitos reservados.

Visão geral

Este artigo está dividido em duas partes; eles são:

  • Usando torch.compile()
  • Acumulação de gradiente

Usando torch.compile

Quando você escreve o código do seu modelo e o executa com PyTorch, o código é executado no modo ansioso. Isso significa que o código é executado linha por linha e os resultados são armazenados na memória. Isso é nativo do Python, pois é uma linguagem interpretada. Você sabe que esse é o caso porque, ao cometer um erro em seu código, você não verá o erro até executar essa linha de código.

Executar um modelo no modo ansioso é lento. Começando com PyTorch 2.0, você pode usar torch.compile() para compilar um modelo para melhorar o desempenho. Isso gera um novo objeto de modelo que é otimizado. Não é o mesmo objeto de modelo que você criou usando nn.Modulemas compartilha os mesmos tensores do modelo unique. Você pode usar este modelo compilado para avanço, retrocesso e atualizações do otimizador normalmente.

Construir um modelo e compilá-lo como um gráfico de computação é como o TensorFlow 1.0 deveria funcionar. Isso torna a depuração mais difícil, pois o modelo executado não pode corresponder linha por linha ao código que você escreveu. Portanto, você não deve compilar seu modelo antes de executar uma avaliação e confirmar que ele está livre de erros.

Nem todos os modelos podem ser compilados. No entanto, se o seu modelo suportar compilação, você se beneficiará imediatamente da aceleração. Para compilar um modelo, tudo o que você precisa fazer é substituir o objeto do modelo antes de estar pronto para usá-lo:

Não carregue os pesos do modelo após a compilação. Isso ocorre porque o modelo compilado é um objeto que compartilha os mesmos pesos do modelo unique. Durante a compilação, o gráfico de computação é construído referenciando os tensores de peso do modelo unique. Se você carregar os pesos após a compilação, o modelo poderá não funcionar conforme o esperado.

Da mesma forma, para salvar o modelo compilado, você deve consultar o ditado de estado do modelo unique, como segue:

O modelo unique pode ser acessado a partir do modelo compilado usando mannequin._orig_mod. No código acima, usamos getattr(mannequin, "_orig_mod", mannequin) para obter o modelo unique, se existir, ou usar mannequin em si, se isso não acontecer. Esta linha de código funciona tanto para modelos compilados quanto para modelos originais.

Acumulação de gradiente

Ao treinar um modelo, você provavelmente gasta duas a três vezes mais tempo na passagem para trás do que na passagem para frente. Isso ocorre porque a passagem para trás é mais intensiva em termos computacionais e usa mais memória.

Um truque fácil para acelerar o treinamento é realizar menos passes para trás. Isto pode ser conseguido aumentando o tamanho do lote: com o mesmo número de amostras de dados, um tamanho de lote maior significa menos lotes para processar.

No entanto, um tamanho de lote maior requer mais memória. Em um ambiente com restrição de memória, você pode imitar um tamanho de lote maior executando várias passagens de encaminhamento e acumulando gradientes. Isso é chamado acumulação de gradiente.

É mais fácil explicar essa ideia com código:

O ciclo de treinamento acima é um trecho do artigo anterior para treinar um modelo Llama em sua GPU native.

Normalmente, quando você executa um passe para frente, você calcula a perda. Então você liga loss.backward() para retropropagar o gradiente de perda através dos parâmetros do modelo. No PyTorch, o backward() O método é cumulativo, o que significa que os gradientes são somados. Portanto, você precisa ligar optimizer.zero_grad() explicitamente para limpar os gradientes antes de executar a passagem para trás.

No código acima, você deliberadamente não chama optimizer.zero_grad() em cada iteração. Em vez disso, você executa a retropropagação para a perda dividida por accumulate_steps. Dessa forma, os gradientes são reduzidos, mas acumulados ao longo accumulate_steps iterações. Uma vez a cada accumulate_steps iterações, você executa o otimizador para ajustar os parâmetros do modelo.

Essa abordagem produz resultados comparáveis ​​ao uso de um tamanho de lote maior. No entanto, como você executa menos atualizações do otimizador, a programação da taxa de aprendizagem deve ser ajustada adequadamente. Isso significa que você precisa inicializar o agendador com um número diferente de etapas:

Leitura adicional

Abaixo estão alguns materiais que você pode achar interessantes:

Resumo

Neste artigo, você aprendeu que usar torch.compile() pode ajudá-lo a acelerar o modelo compilando o gráfico de computação. Você também aprendeu que o acúmulo de gradiente é uma técnica para treinar com um tamanho de lote efetivo maior, acumulando gradientes de vários minilotes. Como você executa menos atualizações do otimizador dessa forma, você economiza tempo em passagens reversas e atualizações de parâmetros.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *