Como funciona o GPT Transformer: Explicação da Autoatenção Causal

Um diagrama detalhado da arquitetura técnica do Transformer decoder-only do GPT mostrando mascaramento causal e geração autoregressiva de tokens

Como funciona o GPT Transformer: Explicação da Autoatenção Causal

Nos últimos anos, os Transformers Generativos Pré-treinados (GPT) revolucionaram a inteligência artificial. De assistentes de programação a agentes conversacionais, os modelos baseados em GPT alimentam as aplicações generativas mais avançadas da atualidade. Mas como essa tecnologia realmente funciona?

Enquanto modelos como o BERT usam a parte do Encoder (Codificador) do Transformer para compreender o texto de forma bidirecional, o GPT é uma arquitetura Decoder-only (apenas decodificador) projetada para a previsão autorregressiva do próximo token. Neste blog, vamos desmistificar como o GPT Transformer funciona, mergulhar no mecanismo de autoatenção causal e implementá-lo em código.


1. O Loop de Geração Autorregresiva

Em sua essência, o GPT is um modelo autorregresivo. Isso significa que, para gerar uma sequência de texto, ele prevê o próximo token um por um, usando os tokens que já gerou como contexto para a próxima previsão.

O fluxo de trabalho segue estes passos:

  1. Entrada (Input): O modelo recebe uma instrução (prompt): "Deep learning is".
  2. Previsão: O modelo processa esse prompt e gera uma distribuição de probabilidade sobre todo o seu vocabulário. Ele seleciona o próximo token: "awesome".
  3. Loop: O novo token é anexado à entrada, tornando-a: "Deep learning is awesome". Essa sequência se torna a entrada para a próxima etapa.
  4. Terminação: O processo se repete até que o modelo gere um token especial de fim de sequência ([EOS]) ou atinja um limite de comprimento predefinido.

2. Mascaramento Causal: O Coração do Decodificador

Em um modelo baseado apenas no codificador, como o BERT, cada token pode prestar atenção a qualquer outro token, olhando tanto para o passado quanto para o futuro. No entanto, para um modelo generativo que prevê o próximo token, olhar para o futuro durante o treinamento seria “trapaça”.

Para evitar que o modelo olhe para tokens futuros, o GPT usa a Autoatenção Causal (ou autoatenção mascarada).

A Matriz de Máscara Causal

Durante o cálculo da autoatenção, calculamos as pontuações de similaridade entre tokens fazendo o produto escalar das consultas (Queries, $Q$) e das chaves (Keys, $K$):

$$\text{Scores} = QK^T$$

Para impor a causalidade, aplicamos uma matriz de máscara $M$ onde todos os valores acima da diagonal são definidos como $-\infty$ (infinito negativo), e os valores sobre e abaixo da diagonal são 0. Adicionamos essa máscara às pontuações antes de aplicar a função softmax:

$$\text{Masked Scores} = \frac{QK^T}{\sqrt{d_k}} + M$$

$$M = \begin{pmatrix} 0 & -\infty & -\infty & \dots & -\infty \\ 0 & 0 & -\infty & \dots & -\infty \\ 0 & 0 & 0 & \dots & -\infty \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & 0 & \dots & 0 \end{pmatrix}$$

Quando aplicamos a função softmax, $e^{-\infty}$ torna-se $0$. Consequentemente, os pesos de atenção para quaisquer tokens futuros tornam-se exatamente 0, tornando os tokens futuros invisíveis para o token atual.


3. Blocos Arquitetônicos Principais de uma Camada GPT

Um modelo GPT consiste em camadas de decodificador Transformer empilhadas. Cada camada contém vários componentes cruciais:

A. Embeddings de Entrada e Codificação Posicional

  • Tokenização: O texto bruto é dividido em tokens de subpalavras usando a codificação de pares de bytes (BPE).
  • Token Embeddings: Cada token é mapeado para um vetor de alta dimensão.
  • Learned Positional Embeddings: Como a autoatenção não possui um sentido de ordem inerente, o GPT adiciona vetores de embeddings posicionais aprendidos aos embeddings de tokens, permitindo que o modelo saiba a posição de cada token na sequência.

B. Normalização Pré-Camada (Pre-LN)

Ao contrário da arquitetura original do Transformer, que aplicava a normalização de camada após a adição residual (Post-LN), as arquiteturas GPT modernas aplicam a normalização de camada antes das camadas de atenção e de alimentação para frente (feed-forward):

$$x_{l+1} = x_l + \text{Attention}(\text{LayerNorm}(x_l))$$

A Pre-LN estabiliza os gradientes durante o treinamento, permitindo o treinamento estável de redes muito profundas com centenas de bilhões de parâmetros.

C. Rede de Alimentação para Frente (Feed-Forward Network / FFN)

Seguindo o bloco de atenção, a representação de cada token passa por um perceptron multicamadas (MLP) que consiste em duas transformações lineares e uma função de ativação (normalmente GeLU):

$$\text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2$$


4. A Mecânica de Amostragem (de Logits a Tokens)

O bloco decodificador final gera um vetor de pontuações brutas chamadas Logits para cada posição. Convertemos esses logits em probabilidades usando a função softmax. Para controlar a aleatoriedade do texto gerado, aplicamos parâmetros durante a amostragem:

  • Temperatura ($T$): Escala os logits antes da softmax. Uma temperatura mais baixa (por exemplo, $T = 0.2$) torna o modelo determinista e focado, enquanto uma temperatura mais alta (por exemplo, $T = 0.8$) aumenta a criatividade e a diversidade.
  • Top-K: Limita as escolhas do próximo token aos $K$ tokens mais prováveis.
  • Top-P (Amostragem de Núcleo / Nucleus Sampling): Acumula a distribuição de probabilidade e escolhe a partir do menor conjunto de tokens cuja probabilidade cumulativa excede $P$ (por exemplo, $P = 0.9$).

5. Implementação em PyTorch de Autoatenção Causal

Abaixo está uma implementação PyTorch independente que demonstra a autoatenção causal com mascaramento causal:

import torch
import torch.nn as nn
import torch.nn.functional as F

class CausalSelfAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        assert d_model % n_heads == 0
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        
        # Projeções para Query, Key e Value
        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)
        self.out_proj = nn.Linear(d_model, d_model)
        
    def forward(self, x):
        batch_size, seq_len, d_model = x.size()
        
        # 1. Projetar entradas para Q, K, V
        Q = self.q_proj(x).view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        K = self.k_proj(x).view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        V = self.v_proj(x).view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        
        # 2. Calcular pontuações de atenção brutas
        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
        
        # 3. Criar e aplicar máscara causal
        # Máscara triangular superior preenchida com infinito negativo
        mask = torch.triu(torch.ones(seq_len, seq_len, device=x.device), diagonal=1).bool()
        scores = scores.masked_fill(mask, float('-inf'))
        
        # 4. Softmax converte -inf em probabilidade 0
        attn_weights = F.softmax(scores, dim=-1)
        
        # 5. Calcular soma ponderada de valores e saída
        context = torch.matmul(attn_weights, V)
        context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
        
        return self.out_proj(context)

# Execução de verificação rápida
if __name__ == "__main__":
    # Tamanho do lote = 1, Comprimento da sequência = 4, Dimensão do modelo = 8, 2 cabeças
    x = torch.randn(1, 4, 8)
    attention_layer = CausalSelfAttention(d_model=8, n_heads=2)
    output = attention_layer(x)
    print("Formato de Entrada:", x.shape)
    print("Formato de Saída:", output.shape)

6. Comparação Arquitetônica

Característica BERT (apenas Codificador) GPT (apenas Decodificador) Transformer Original
Tarefa Principal Compreensão / Extração Geração / Síntese Tradução / Sequência para Sequência
Tipo de Atenção Autoatenção Bidirecional Autoatenção Causal Mascarada Atenções Cruzadas Bidirecionais e Causais
Mascaramento Tokens mascarados ([MASK]) Mascaramento triangular causal Mascaramento causal no decodificador
Processamento Processa toda a sequência de uma vez Geração autorregresiva de tokens O codificador processa uma vez, o decodificador gera

Conclusão

Ao descartar o codificador e focar inteiramente na autoatenção mascarada causal, o GPT abriu o caminho para a escala generativa. A regra simples de prever o próximo token, combinada com o treinamento em paralelo massivo, permite que os modelos GPT capturem ricas representações lógicas, de programação e de linguagem, formando a base da inteligência artificial cognitiva moderna.


Explore mais insights técnicos no Blog da Ghaznix →