Phase 07 - Lesson 14

Construa um Transformer do Zero — O Projeto Final

Treze lições. Um modelo. Sem atalhos.

Tipo: Construção Linguagens: Python Pré-requisitos: Fase 7 · 01 a 13. Não pule. Tempo: ~120 minutos

O Problema

Você leu todos os artigos. Você implementou atenção, divisões multi-head, codificações posicionais, blocos de codificador (encoder) e decodificador (decoder), perdas do BERT e GPT, MoE, KV cache. Agora faça-os funcionar juntos em uma tarefa real.

O projeto final: treinar um pequeno transformer apenas-decodificador (decoder-only) de ponta a ponta em uma tarefa de modelagem de linguagem em nível de caractere. Ele lê Shakespeare. Ele gera novos textos no estilo de Shakespeare. Ele é pequeno o suficiente para ser treinado em um notebook em menos de 10 minutos. Ele é correto o suficiente para que a substituição por um conjunto de dados maior e um treinamento mais longo resulte em um LM real.

Este é o "nanoGPT" do curso. Não é original — o tutorial nanoGPT de 2023 do Karpathy é a implementação de referência que todo estudante escreve pelo menos uma vez. Nós adaptamos a estrutura e a reorganizamos em torno do que cobrimos.

O Conceito

Diagrama de blocos do Transformer do zero

A arquitetura, anotada:

input tokens (B, N)
   │
   ▼
token embedding + positional embedding  ◀── Lesson 04 (RoPE option)
   │
   ▼
┌──── block × L ────────────────────┐
│  RMSNorm                          │  ◀── Lesson 05
│  MultiHeadAttention (causal)      │  ◀── Lesson 03 + 07 (causal mask)
│  residual                         │
│  RMSNorm                          │
│  SwiGLU FFN                       │  ◀── Lesson 05
│  residual                         │
└────────────────────────────────── ┘
   │
   ▼
final RMSNorm
   │
   ▼
lm_head (tied to token embedding)
   │
   ▼
logits (B, N, V)
   │
   ▼
shift-by-one cross-entropy            ◀── Lesson 07

O que entregamos

  • GPTConfig — um único lugar para configurar todos os hiperparâmetros.
  • MultiHeadAttention — causal, em lotes (batched), com caminho opcional no estilo Flash (o scaled_dot_product_attention do PyTorch).
  • SwiGLUFFN — um FFN moderno.
  • Block — pré-normalização (pre-norm), atenção envolvida em resíduos (residual-wrapped) + FFN.
  • GPT — embeddings, blocos empilhados, cabeça LM (LM head), generate().
  • Loop de treinamento com AdamW, taxa de aprendizado com decaimento de cosseno (cosine LR), corte de gradiente (gradient clipping).
  • Tokenizador em nível de caractere em texto de Shakespeare.

O que não entregamos

  • RoPE — implementado conceitualmente na Lição 04. Aqui usamos embeddings posicionais aprendidos para simplificar. Os exercícios pedem para você substituí-los por RoPE.
  • KV cache durante a geração — cada etapa de geração recalcula a atenção sobre o prefixo completo. Mais lento, mas mais simples. Os exercícios pedem para você adicionar um KV cache.
  • Flash Attention — o PyTorch 2.0+ faz o auto-despacho se as entradas corresponderem; nós usamos F.scaled_dot_product_attention.
  • MoE — um único FFN por bloco. Você viu MoE na Lição 11.

Métricas alvo

Em um notebook Mac M2, um GPT com 4 camadas, 4 cabeças e d_model=128 treinado por 2.000 etapas em tinyshakespeare.txt:

  • A perda de treinamento converge de ~4,2 (aleatório) para ~1,5 em cerca de 6 minutos.
  • A saída amostrada parece no estilo de Shakespeare: surgem palavras arcaicas, quebras de linha e nomes próprios como "ROMEO:".
  • A perda de validação (últimos 10% do texto mantidos em reserva) acompanha de perto a perda de treinamento; sem overfitting neste tamanho/orçamento.

Construa

Esta lição usa PyTorch. Instale o torch (a versão de CPU é suficiente). Consulte code/main.py. O script lida com:

  • Download de tinyshakespeare.txt se estiver ausente (ou leitura de uma cópia local).
  • Tokenizador de caracteres em nível de byte.
  • Divisão de treino/val em 90/10.
  • Loop de treinamento com bf16 autocast em hardware suportado.
  • Amostragem após a conclusão do treinamento.

Passo 1: dados

text = open("tinyshakespeare.txt").read()
chars = sorted(set(text))
stoi = {c: i for i, c in enumerate(chars)}
itos = {i: c for c, i in stoi.items()}
encode = lambda s: [stoi[c] for c in s]
decode = lambda xs: "".join(itos[x] for x in xs)

65 caracteres únicos. Vocabulário minúsculo. Cabe em um vocab_size de 4 bytes. Sem BPE, sem drama com tokenizador.

Passo 2: modelo

Consulte code/main.py. O bloco é clássico da Lição 05 — pré-normalização (pre-norm), RMSNorm, SwiGLU, MHA causal. Contagem de parâmetros para 4/4/128: ~800K.

Passo 3: loop de treinamento

Obtenha um lote (batch) aleatório de janelas de tokens de comprimento 256. Forward. Entropia cruzada deslocada por um (shift-by-one cross-entropy). Backward. Etapa do AdamW. Log. Repita.

for step in range(max_steps):
    x, y = get_batch("train")
    logits = model(x)
    loss = F.cross_entropy(logits.view(-1, vocab_size), y.view(-1))
    loss.backward()
    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
    opt.step()
    opt.zero_grad()

Passo 4: amostragem

Dado um prompt, execute repetidamente o forward, faça a amostragem a partir dos logits top-p, anexe e continue. Pare após 500 tokens.

Passo 5: leia a saída

Após 2.000 etapas:

ROMEO:
Away and mild will not thy friend, that thou shalt wit:
The chief that well shame and hath been his friends,
...

Não é Shakespeare. Mas no estilo de Shakespeare. Uma vitória clara para ~800K parâmetros e 6 minutos em um notebook.

Use

Este projeto final é uma arquitetura de referência. Três extensões para torná-lo algo real:

  1. Substitua o tokenizador. Use BPE (por exemplo, tiktoken.get_encoding("cl100k_base")). O tamanho do vocabulário salta de 65 para ~50.000. A capacidade do modelo precisa aumentar para compensar.
  2. Treine em um corpus maior. Use OpenWebText ou fineweb-edu (HuggingFace). 10 bilhões de tokens em uma única GPU A100 levam cerca de 24 horas para um GPT de 125M parâmetros.
  3. Adicione RoPE + KV cache + Flash Attention. Os exercícios abaixo guiam você por cada um deles.

Isso resulta em um GPT de 125 milhões de parâmetros que gera inglês fluente. Não é um modelo de fronteira. Mas o mesmo caminho de código — apenas maior — é o que Karpathy, EleutherAI e o Allen Institute usam para treinar checkpoints de pesquisa em 2026.

Entregue

Consulte outputs/skill-transformer-review.md. A habilidade avalia a corretude de uma implementação de transformer do zero ao longo de todas as 13 lições anteriores.

Exercícios

  1. Fácil. Execute code/main.py. Verifique se a perda de validação da etapa final do seu modelo treinado está abaixo de 2,0. Altere max_steps de 2.000 para 5.000 — a perda de validação continua melhorando?
  2. Médio. Substitua os embeddings posicionais aprendidos por RoPE. Aplique a rotação a Q e K dentro de MultiHeadAttention. Treine e verifique se a perda de validação é pelo menos tão baixa quanto antes.
  3. Médio. Implemente um KV cache no loop de amostragem. Gere 500 tokens com e sem cache. O tempo real de execução (wall-clock) deve melhorar de 5 a 20 vezes em um notebook.
  4. Difícil. Adicione uma segunda cabeça ao modelo que preveja o token seguinte ao próximo (MTP — Multi-Token Prediction do DeepSeek-V3). Treine de forma conjunta. Isso ajuda?
  5. Difícil. Substitua o FFN único por bloco por um MoE de 4 especialistas. Roteador + roteamento top-2. Veja como a perda de validação muda com os mesmos parâmetros ativos.

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
nanoGPT "Repositório de tutorial do Karpathy" Código de treinamento minimalista de transformer apenas-decodificador, ~300 linhas de código (LOC); a referência canônica.
tinyshakespeare "O corpus de brinquedo padrão" ~1,1 MB de texto; todos os tutoriais de LM de caracteres desde 2015 o utilizam.
Tied embeddings "Compartilhar matriz de entrada/saída" Peso da cabeça LM = transposta da matriz de embedding de token; economiza parâmetros, melhora a qualidade.
bf16 autocast "Truque de precisão de treinamento" Executa forward/backward em bf16, mantém o estado do otimizador em fp32; padrão desde 2021.
Gradient clipping "Impede picos" Limita a norma global do gradiente em 1,0; evita explosões de treinamento.
Cosine LR schedule "O padrão pós-2020" A taxa de aprendizado (LR) aumenta linearmente (warmup) e depois decai em formato de cosseno até 10% do pico.
MFU "Utilização de FLOPs do Modelo" FLOPs alcançados / pico teórico; 40% denso, 30% MoE é considerado forte em 2026.
Val loss "Perda em dados reservados" Entropia cruzada em dados que o modelo nunca viu; detector de overfitting.

Leituras Adicionais

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).