O Modelo Mental de Deep Learning
Os tensores do PyTorch, seu motor autograd e o loop de treinamento podem parecer três tópicos separados aprendidos um após o outro.
Eles são, na verdade, uma única máquina: um grafo computacional construído a partir de operações de tensor, uma passagem backward que percorre esse grafo para calcular gradientes e um loop de treinamento que usa repetidamente esses gradientes para ajustar parâmetros em direção a uma perda menor.
Esta página conecta Noções Básicas de PyTorch, Autograd, Loops de Treinamento e GPUs e Precisão Mista em um único modelo, em vez de quatro tópicos separados para memorizar independentemente.
Resumo
- Cada execução de treinamento do PyTorch é o mesmo loop - construir um grafo computacional através de uma passagem forward, percorrê-lo para trás para obter gradientes e usar esses gradientes para atualizar parâmetros - e todo o resto nesta seção é uma variação ou otimização desse único loop.
- Por que Importa: Entender o loop como um único mecanismo, em vez de uma sequência de chamadas de API para memorizar, é o que permite depurar um modelo que não está aprendendo em vez de adivinhar correções.
- Conceitos Chave: tensor, grafo computacional, autograd, passagem backward, passo do otimizador, precisão mista.
- Quando Usar: Projetar ou depurar qualquer loop de treinamento personalizado, decidir se GPU ou precisão mista vale a complexidade adicionada, ou raciocinar sobre por que a perda de um modelo não está diminuindo.
- Limitações / Trade-offs: O grafo computacional e as ativações armazenadas consomem memória aproximadamente proporcional à profundidade do modelo e ao tamanho do batch, e a precisão mista troca precisão numérica por velocidade e economia de memória.
- Tópicos Relacionados: aprendizado de transferência, treinamento distribuído, PyTorch Lightning.
Fundamentos
Um tensor é a estrutura de dados principal do PyTorch, um array n-dimensional muito parecido com um array NumPy, mas com duas adições que importam para deep learning: ele pode residir em uma GPU e pode rastrear as operações aplicadas a ele.
Essa segunda propriedade é o que torna o autograd possível: cada operação realizada em um tensor com requires_grad=True é gravada em um grafo computacional, um grafo direcionado onde os nós são operações e as arestas são os tensores que fluem entre eles.
Uma analogia útil é uma receita com um recibo anexado.
A passagem forward é seguir a receita, misturando tensores através de operações para produzir um resultado, e o recibo é o grafo computacional, listando exatamente quais operações aconteceram em qual ordem para que você possa mais tarde percorrê-lo para trás para ver quanto cada ingrediente contribuiu para o resultado final.
A passagem forward é computação comum: tensores de entrada fluem através de camadas como multiplicações de matrizes, ativações e normalização, para produzir uma saída como uma classe prevista ou uma probabilidade do próximo token.
A passagem backward é onde a função real do autograd aparece.
Chamar .backward() em uma perda escalar percorre o grafo gravado em reverso, aplicando a regra da cadeia em cada nó para calcular o gradiente da perda em relação a cada parâmetro que contribuiu para ela.
Mecânicas e Interações
O loop de treinamento é esse par de passagens repetido muitas vezes, e cada etapa nele existe para responder a uma pergunta específica em uma ordem fixa:
para cada batch:
passagem forward -> o que o modelo previu? (constrói o grafo)
calcular perda -> quão errada foi a previsão?
passagem backward -> quanto cada parâmetro contribuiu para esse erro? (percorre o grafo)
passo do otimizador -> ajusta cada parâmetro na direção oposta ao seu gradiente
zerar gradientes -> limpa gradientes antigos antes do próximo batch
O passo do otimizador - SGD, Adam e suas variantes - é onde o aprendizado real acontece, pegando os gradientes que o autograd calculou e atualizando cada parâmetro por alguma função desse gradiente, tipicamente escalado por uma taxa de aprendizado.
Zerar gradientes existe porque o PyTorch acumula gradientes em .grad por padrão em vez de sobrescrevê-los.
Esse design suporta técnicas como acumulação de gradientes em vários batches pequenos, mas corrompe silenciosamente um loop de treinamento normal se você esquecer de limpar os gradientes antes da próxima chamada backward.
for batch in loader:
optimizer.zero_grad() # limpa os gradientes do último passo
output = model(batch.x) # a passagem forward constrói o grafo
loss = loss_fn(output, batch.y)
loss.backward() # percorre o grafo para trás
optimizer.step() # atualiza os parâmetros usando .gradO grafo computacional é reconstruído do zero em cada passagem forward e descartado após .backward() por padrão, que é por que o PyTorch é descrito como define-by-run.
O grafo é um registro do que realmente aconteceu nesta passagem forward específica, não uma estrutura estática declarada antecipadamente, o que torna o fluxo de controle dependente de dados simples em comparação com frameworks que exigem um grafo definido antes que qualquer dado passe por ele.
GPUs importam aqui porque as passagens forward e backward são quase inteiramente multiplicações de matrizes e operações elemento a elemento, que paralelizam extremamente bem nos milhares de núcleos de uma GPU em comparação com os núcleos muito menores de uma CPU otimizados para trabalho sequencial.
Precisão mista explora o fato de que a maior parte dessa aritmética não precisa de precisão total de ponto flutuante de 32 bits para convergir.
Executar multiplicações de matrizes em 16 bits, ou bfloat16, enquanto mantém uma cópia mestre de 32 bits dos parâmetros, reduz o uso de memória e muitas vezes dobra aproximadamente a taxa de transferência em núcleos de tensor de GPU modernos, com um escalonador de gradiente usado para evitar que valores de gradiente pequenos se tornem zero no formato de menor precisão.
Considerações Avançadas e Aplicações
Em escala, o custo de memória do grafo computacional se torna a restrição limitante em vez do poder de computação bruto, já que cada ativação intermediária da passagem forward tem que permanecer na memória até que a passagem backward a consuma.
Modelos mais profundos e batches maiores custam memória aproximadamente linearmente em ambas as dimensões como resultado.
Gradient checkpointing troca computação por memória diretamente: em vez de armazenar todas as ativações intermediárias, ele armazena apenas um subconjunto e recalcula o restante durante a passagem backward, uma aplicação deliberada do mesmo modelo mental forward/backward em vez de uma técnica separada.
Aprendizado de transferência e fine-tuning reutilizam esse modelo também.
Congelar as camadas iniciais de uma rede pré-treinada com requires_grad=False remove esses parâmetros completamente da caminhada backward do grafo, então gradientes são computados - e passos do otimizador são aplicados - apenas para as camadas que estão sendo realmente adaptadas, o que torna o fine-tuning mais barato do que treinar do zero.
Treinamento distribuído estende o mesmo loop para várias GPUs ou máquinas: cada dispositivo executa sua própria passagem forward e backward em uma fatia do batch, e são os gradientes, não os dados ou o modelo, que são sincronizados entre os dispositivos antes de cada passo do otimizador.
É por isso que a largura de banda da rede para sincronização de gradientes, e não o poder de computação bruto, é frequentemente o gargalo em escala.
| Estratégia de Precisão / Escala | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Precisão total de 32 bits, GPU única | Mais simples de raciocinar; sem surpresas numéricas | Opção mais lenta; limita o modelo/tamanho do batch à memória de uma GPU | Modelos pequenos, depuração, estabelecimento de uma linha de base de correção |
| Precisão mista, GPU única | Aceleração significativa e economia de memória em núcleos de tensor modernos | Adiciona um escalonador de gradiente e instabilidade numérica ocasional para raciocinar | Maioria dos treinamentos de produção após o estabelecimento da correção |
| Treinamento distribuído (multi-GPU) | Escala o modelo e o tamanho do batch além da memória de um dispositivo | A sincronização de gradientes se torna um gargalo real; adiciona complexidade operacional | Modelos ou datasets muito grandes para uma única GPU, independentemente da precisão |
Equívocos Comuns
- "O grafo computacional é construído uma vez quando o modelo é definido." Ele é reconstruído em cada passagem forward porque o PyTorch é define-by-run, não uma vez no momento da definição do modelo - isso é o que permite que o fluxo de controle de um modelo dependa de sua entrada.
- "Chamar
.backward()duas vezes seguidas apenas recalcula os mesmos gradientes." Sem limpá-los primeiro, os gradientes se acumulam em.gradem vez de serem sobrescritos, então umzero_grad()ausente corrompe silenciosamente um loop de treinamento padrão. - "Precisão mista apenas torna o treinamento mais rápido sem desvantagens." Ele troca alguma precisão numérica por velocidade e memória, que é por que um escalonador de gradiente é necessário para evitar que gradientes pequenos se tornem zero em formatos de 16 bits.
- "Mais GPUs sempre significam treinamento proporcionalmente mais rápido." O treinamento distribuído adiciona sobrecarga de sincronização de gradiente entre dispositivos, então os retornos diminuem e podem até reverter quando o custo dessa sincronização rivaliza com o tempo de computação que ele visa economizar.
- "Congelar camadas durante o fine-tuning é apenas para economizar tempo." Também remove completamente essas camadas do cálculo de gradiente da passagem backward, que é o que torna o fine-tuning mais rápido e menos propenso a sobrescrever pesos pré-treinados não intencionalmente.
FAQs
O que realmente acontece quando chamo loss.backward()?
O PyTorch percorre o grafo computacional gravado durante a passagem forward, em reverso, aplicando a regra da cadeia em cada operação para calcular o gradiente da perda em relação a cada tensor que tem requires_grad=True e contribuiu para essa perda.
Por que preciso chamar optimizer.zero_grad() a cada batch?
O PyTorch acumula novos gradientes no atributo .grad existente de um tensor por padrão, em vez de sobrescrevê-lo. Sem limpá-lo primeiro, os gradientes do batch anterior ainda seriam adicionados aos gradientes do batch atual, corrompendo a atualização.
O que é um grafo computacional, concretamente?
Um grafo direcionado gravado durante a passagem forward, onde cada nó é uma operação aplicada a um tensor e cada aresta é um tensor fluindo entre operações. É o que o autograd percorre para trás para calcular gradientes, e ele é reconstruído do zero em cada passagem forward.
Por que o PyTorch é descrito como "define-by-run"?
Porque o grafo computacional é construído dinamicamente à medida que as operações realmente executam em dados reais, em vez de ser declarado estaticamente antes que qualquer dado passe por ele. Isso torna o fluxo de controle dependente de dados, como um número diferente de iterações de loop por entrada, simples de expressar.
O que o otimizador realmente faz com os gradientes que o autograd calcula?
Ele atualiza cada parâmetro usando alguma função de seu gradiente e uma taxa de aprendizado. O SGD subtrai um gradiente escalado diretamente, enquanto otimizadores adaptativos como Adam também rastreiam estatísticas de execução por parâmetro de gradientes passados para ajustar o tamanho efetivo do passo.
Por que o treinamento com GPU ajuda tanto especificamente para redes neurais?
As passagens forward e backward são dominadas por multiplicações de matrizes e operações elemento a elemento, que paralelizam extremamente bem nos muitos núcleos simples de uma GPU, ao contrário de cargas de trabalho mais sequenciais que se adequam melhor aos poucos núcleos poderosos de uma CPU.
Que problema o treinamento com precisão mista realmente resolve?
Ele reduz o uso de memória e aumenta a taxa de transferência executando a maior parte da aritmética em um formato de menor precisão, 16 bits ou bfloat16, em vez de floats de 32 bits completos, que os núcleos de tensor de GPU modernos executam significativamente mais rápido, enquanto mantém uma cópia mestre de 32 bits dos parâmetros para estabilidade numérica.
Por que a precisão mista precisa de um escalonador de gradiente?
Valores de gradiente pequenos podem ter underflow para zero quando representados em ponto flutuante de 16 bits, perdendo informações silenciosamente. Um escalonador de gradiente multiplica a perda por um fator de escala antes da passagem backward e desescala os gradientes antes do passo do otimizador, para manter os valores em um intervalo representável.
Como o congelamento de camadas durante o fine-tuning realmente economiza computação?
Definir requires_grad=False nos parâmetros de uma camada remove-a completamente do cálculo de gradiente da passagem backward. O autograd nunca calcula um gradiente para ela e o otimizador nunca a atualiza, então tanto a passagem backward quanto o passo de atualização são mais baratos.
O que realmente é sincronizado no treinamento distribuído entre várias GPUs?
Cada dispositivo calcula sua própria passagem forward e backward em sua fatia do batch, e são os gradientes resultantes, não os dados brutos ou os pesos do modelo em meio ao treinamento, que são sincronizados, tipicamente com média, entre os dispositivos antes de cada passo do otimizador.
O que é gradient checkpointing e por que trocar computação por memória?
Ele armazena apenas um subconjunto das ativações da passagem forward em vez de todas elas, recalculando o restante durante a passagem backward quando necessário. É útil quando um modelo está limitado pela memória em vez de pela computação, já que a memória da GPU é frequentemente o limite mais difícil de contornar.
Um tamanho de batch maior é sempre melhor para a taxa de transferência de treinamento?
Não incondicionalmente. Batches maiores utilizam melhor o paralelismo da GPU até certo ponto, mas também aumentam a memória necessária para armazenar ativações para a passagem backward, e batches muito grandes podem mudar a dinâmica de treinamento em vez de simplesmente treinar mais rápido de graça.
Relacionados
- Noções Básicas de PyTorch - tensores, dispositivos e a API principal na qual este modelo se baseia
- Autograd - uma análise mais aprofundada da passagem backward e do cálculo de gradiente
- Loops de Treinamento - o loop descrito nesta página, escrito em código funcional
- GPUs e Precisão Mista - precisão prática e posicionamento de dispositivo
- Aprendizado de Transferência e Fine-Tuning - congelar camadas como uma aplicação deste modelo
- Treinamento Distribuído - estendendo o loop para vários dispositivos
Versões da Pilha: Esta página foi escrita para PyTorch 2.6+ e Python 3.14 (estável) / 3.13 (manutenção).