69 pontos por GN⁺ 2026-02-17 | 1 comentários | Compartilhar no WhatsApp
  • Projeto artístico publicado por karpathy. Implementa todo o algoritmo do GPT em um único arquivo de 200 linhas, sem dependências externas
  • A diferença para LLMs de produção é apenas de escala e eficiência; o núcleo é o mesmo, e entender este código significa entender a essência algorítmica do GPT
  • Inclui dataset, tokenizador, motor de autograd, arquitetura Transformer semelhante ao GPT-2, otimizador Adam e também o loop de treino e inferência
  • Como resultado de 10 anos de trabalho de simplificação de LLMs em projetos anteriores como micrograd, makemore e nanogpt, concentra a essência do GPT na forma mínima que já não pode mais ser simplificada
  • Treina com 32.000 nomes para gerar novos nomes plausíveis, realizando todos os cálculos diretamente com autograd em nível escalar
  • O processo de treinamento é composto por cálculo da perda → retropropagação → atualização com Adam e pode ser executado em cerca de 1 minuto

Visão geral do microgpt

  • microgpt é um script Python de 200 linhas que implementa de forma completa o processo de treino e inferência de um modelo GPT
    • Sem bibliotecas externas, inclui dataset, tokenizador, autograd, modelo, otimizador e loop de treinamento
  • Integra projetos anteriores como micrograd, makemore e nanogpt em um único arquivo
  • Uma implementação que deixa apenas o núcleo algorítmico, em um nível de “não dá mais para simplificar”
  • O código completo está disponível no GitHub Gist, na página web e no Google Colab

Composição do dataset

  • O combustível dos grandes modelos de linguagem é um fluxo de dados de texto; em produção usam-se páginas da web da internet, mas no microgpt é usado um exemplo simples com 32.000 nomes, um por linha
  • Cada nome é tratado como um "documento", e o objetivo do modelo é aprender padrões estatísticos dos dados para gerar novos documentos semelhantes
  • Após o treinamento, o modelo "alucina" novos nomes plausíveis como "kamon", "karai" e "vialan"
  • Do ponto de vista do ChatGPT, a conversa com o usuário também é apenas um "documento com formato estranho"; ao inicializar o documento com um prompt, a resposta do modelo corresponde a uma conclusão estatística de documento

Tokenizador

  • Como redes neurais operam com números, e não com caracteres, é necessário converter o texto em uma sequência de IDs inteiros de tokens e depois restaurá-lo
  • Tokenizadores de produção como o tiktoken (usado no GPT-4) operam em blocos de caracteres por eficiência, mas o tokenizador mais simples atribui um inteiro a cada caractere único do dataset
  • As letras minúsculas de a a z são ordenadas e cada caractere recebe um ID pelo índice; o valor inteiro em si não tem significado, e cada token é um símbolo discreto distinto
  • Adiciona-se o token especial BOS (Beginning of Sequence) para indicar que "um novo documento começou/terminou", e "emma" é encapsulado como [BOS, e, m, m, a, BOS]
  • O vocabulário final tem 27 itens (26 letras minúsculas + 1 BOS)

Diferenciação automática (Autograd)

  • O treinamento de redes neurais exige gradientes: para cada parâmetro, é preciso saber "se eu aumentar um pouco este valor, a perda sobe ou desce, e quanto?"
  • O grafo computacional tem muitas entradas (parâmetros do modelo e tokens de entrada), mas converge para uma única saída escalar, a perda (loss)
  • A retropropagação (Backpropagation) começa na saída e percorre o grafo no sentido inverso, calculando os gradientes da perda em relação a todas as entradas com base na regra da cadeia do cálculo
  • É implementado com a classe Value: cada Value encapsula um único escalar (.data) e rastreia como ele foi calculado
    • Ao realizar operações como soma e multiplicação, o novo Value guarda as entradas (_children) e as derivadas locais (_local_grads) da operação correspondente
    • Ex.: __mul__ registra ∂(a·b)/∂a=b e ∂(a·b)/∂b=a
  • Blocos de operação suportados: soma, multiplicação, potência, log, exp, ReLU
  • O método backward() percorre o grafo em ordem topológica inversa, aplicando a regra da cadeia em cada etapa
    • Começa no nó da perda com self.grad = 1 (∂L/∂L=1)
    • Multiplica os gradientes locais ao longo do caminho e os propaga até os parâmetros
  • Acumula com += (não é atribuição): quando o grafo se ramifica, gradientes fluem independentemente por cada ramo e devem ser somados (resultado da regra da cadeia multivariável)
  • É algoritmicamente idêntico ao .backward() do PyTorch, mas opera em escalares em vez de tensores; por isso é muito mais simples, porém menos eficiente

Inicialização de parâmetros

  • Os parâmetros são o conhecimento do modelo: um grande conjunto de números de ponto flutuante que começa aleatoriamente e é otimizado repetidamente durante o treinamento
  • São inicializados com pequenos valores aleatórios de uma distribuição gaussiana
  • São compostos por matrizes nomeadas em um state_dict: tabela de embeddings, pesos de atenção, pesos da MLP e projeção final de saída
  • Configuração de hiperparâmetros:
    • n_embd = 16: dimensão do embedding
    • n_head = 4: número de cabeças de atenção
    • n_layer = 1: número de camadas
    • block_size = 16: comprimento máximo da sequência
  • O modelo pequeno tem 4.192 parâmetros (o GPT-2 tem 1,6 bilhão, e LLMs modernos têm centenas de bilhões)

Arquitetura

  • A arquitetura do modelo é uma função sem estado: recebe tokens, posições, parâmetros e chaves/valores em cache de posições anteriores, e retorna os logits (pontuações) do próximo token
  • Segue o GPT-2, mas com algumas simplificações: RMSNorm (em vez de LayerNorm), sem bias, ReLU (em vez de GeLU)
  • Funções auxiliares

    • linear: multiplicação matriz-vetor que calcula um produto interno para cada linha da matriz de pesos; é a transformação linear aprendida, bloco básico da rede neural
    • softmax: converte pontuações brutas (logits) em uma distribuição de probabilidade, fazendo com que todos os valores fiquem no intervalo [0,1] e somem 1; para estabilidade numérica, subtrai primeiro o valor máximo
    • rmsnorm: reajusta o vetor para ter raiz da média quadrática unitária, evitando que as ativações cresçam ou diminuam ao passar pela rede e estabilizando o treinamento
  • Estrutura do modelo

    • Embeddings: o ID do token e o ID da posição consultam linhas nas tabelas de embedding (wte, wpe) e os dois vetores são somados, codificando ao mesmo tempo o que é o token e onde ele está na sequência
      • LLMs modernos costumam pular embeddings posicionais e usar técnicas de posicionamento relativo como RoPE
    • Bloco de atenção: projeta o token atual em três vetores, Q (query), K (key) e V (value)
      • Query: “o que estou procurando?”, key: “o que eu contenho?”, value: “o que eu forneço se for selecionado?”
      • Exemplo: ao prever o próximo caractere em "emma", o segundo "m" pode aprender uma query como “qual foi a vogal mais recente?”; o "e" anterior combina bem com essa query e recebe um peso de atenção alto
      • Keys e values são adicionados ao cache KV, permitindo consultar posições anteriores
      • Cada cabeça de atenção calcula o produto interno entre a query e todas as keys em cache (escalado por √d_head), obtém pesos de atenção com softmax e calcula a soma ponderada dos values em cache
      • As saídas de todas as cabeças são concatenadas e projetadas por attn_wo
      • O bloco de atenção é o único lugar onde o token na posição t pode “ver” os tokens passados 0..t-1; a atenção é o mecanismo de comunicação entre tokens
    • Bloco MLP: uma rede feedforward de 2 camadas: expande para 4x a dimensão do embedding → aplica ReLU → reduz de volta
      • É onde ocorre a maior parte do “raciocínio” por posição
      • Diferente da atenção, é um cálculo totalmente local no tempo t
      • O Transformer intercala comunicação (atenção) e computação (MLP)
    • Conexões residuais: tanto o bloco de atenção quanto o MLP somam suas saídas de volta à entrada
      • Isso permite que os gradientes atravessem a rede diretamente, tornando possível treinar modelos profundos
    • Saída: o estado oculto final é projetado por lm_head para o tamanho do vocabulário, gerando um logit por token (aqui, 27 valores); logit alto = maior chance de aquele token vir em seguida
    • Particularidade do cache KV: usar cache KV durante o treinamento é incomum, mas como o microgpt processa apenas um token por vez, ele é construído explicitamente; as keys e values em cache são nós Value vivos no grafo computacional e participam da retropropagação

Loop de treinamento

  • O loop de treinamento repete: (1) escolher um documento → (2) executar a passagem direta do modelo sobre os tokens → (3) calcular a perda → (4) obter gradientes via retropropagação → (5) atualizar os parâmetros
  • Tokenização

    • Em cada passo de treinamento, um documento é escolhido e envolvido com BOS nos dois lados: "emma" → [BOS, e, m, m, a, BOS]
    • O objetivo do modelo é prever cada próximo token dados os tokens anteriores
  • Passagem direta e perda

    • Os tokens são alimentados ao modelo um por vez, construindo o cache KV
    • Em cada posição, o modelo produz 27 logits, convertidos em probabilidades com softmax
    • A perda em cada posição é a probabilidade logarítmica negativa do próximo token correto: −log p(target), chamada de perda de entropia cruzada
    • A perda mede o quanto o modelo ficou surpreso com o que realmente veio: se atribui probabilidade 1,0, a perda é 0; se atribui probabilidade perto de 0, a perda tende a +∞
    • Faz-se a média das perdas de todas as posições do documento para obter uma única perda escalar
  • Passagem para trás

    • Uma única chamada a loss.backward() executa a retropropagação em todo o grafo computacional
    • Depois disso, o .grad de cada parâmetro informa como ele deve mudar para reduzir a perda
  • Otimizador Adam

    • Em vez de descida de gradiente simples (p.data -= lr * p.grad), usa-se Adam
    • Mantém duas médias móveis por parâmetro:
      • m: média dos gradientes recentes (momentum)
      • v: média dos quadrados dos gradientes recentes (adaptação da taxa de aprendizado por parâmetro)
    • m_hat e v_hat são as versões com correção de viés de m e v, inicializados em 0
    • A taxa de aprendizado decresce linearmente durante o treinamento
    • Após a atualização, .grad = 0 para zerar
  • Resultados do treinamento

    • Ao longo de 1.000 passos, a perda cai de cerca de 3,3 (chute aleatório entre 27 tokens: −log(1/27)≈3,3) para cerca de 2,37
    • Quanto menor, melhor, e o mínimo é 0 (previsão perfeita), então ainda há espaço para melhorar, mas já fica claro que o modelo está aprendendo padrões estatísticos de nomes

Inferência

  • Após o treinamento, é possível amostrar novos nomes do modelo; com os parâmetros fixos, executa-se a passagem direta em loop e cada token gerado é realimentado como próxima entrada
  • Processo de amostragem

    • Cada amostra começa com o token BOS (“início de um novo nome”)
    • O modelo gera 27 logits → converte em probabilidades → amostra aleatoriamente um token de acordo com essas probabilidades
    • Esse token é realimentado como próxima entrada, repetindo até o modelo gerar BOS novamente (“fim”) ou atingir o comprimento máximo da sequência
  • Temperatura (Temperature)

    • Antes do softmax, os logits são divididos pela temperatura
    • Temperatura 1,0: amostragem direta da distribuição aprendida pelo modelo
    • Temperatura baixa (ex.: 0,5): deixa a distribuição mais aguda, tornando o modelo mais conservador e mais propenso a escolher opções do topo
    • Temperatura próxima de 0: sempre escolhe o único token mais provável (decodificação gulosa)
    • Temperatura alta: achata a distribuição, gerando saídas mais diversas, porém menos consistentes

Como executar

  • Só precisa de Python (sem pip install, sem dependências): python train.py
  • Leva cerca de 1 minuto em um MacBook
  • Exibe a perda a cada passo: de ~3,3 (aleatório) para ~2,37
  • Após o treinamento, gera novos nomes alucinados: "kamon", "ann", "karai" etc.
  • Também pode ser executado em um notebook do Google Colab, e você pode fazer perguntas ao Gemini
  • É possível testar outros datasets, treinar por mais tempo aumentando num_steps e obter resultados melhores aumentando o tamanho do modelo

Etapas de evolução do código

arquivo conteúdo adicionado
train0.py tabela de contagem de bigramas — sem rede neural, sem gradientes
train1.py MLP + gradientes manuais (numéricos e analíticos) + SGD
train2.py Autograd (classe Value) — substitui gradientes manuais
train3.py embedding posicional + atenção de uma cabeça + rmsnorm + residual
train4.py atenção multi-head + loop de camadas — arquitetura GPT completa
train5.py otimizador Adam — este é o train.py
  • Nas Revisions do Gist build_microgpt.py, é possível ver todas as versões e o diff entre cada etapa

Diferenças em relação a LLMs de produção

  • O microgpt inclui a essência algorítmica completa do treinamento e da execução de um GPT; a diferença para LLMs de produção como o ChatGPT não muda o algoritmo central, mas sim os elementos que fazem isso funcionar em escala
  • Dados

    • Em vez de 32 mil nomes curtos, ele é treinado com trilhões de tokens de texto da internet (páginas web, livros, código etc.)
    • Remoção de duplicatas dos dados, filtragem de qualidade e mistura cuidadosa entre domínios
  • Tokenizador

    • Em vez de caracteres isolados, usa-se um tokenizador de subpalavras como BPE (Byte Pair Encoding)
    • Sequências de caracteres que aparecem com frequência juntas são fundidas em um único token; palavras comuns como "the" viram um único token, enquanto palavras raras são divididas em partes
    • Vocabulário de ~100 mil tokens, vendo mais conteúdo por posição, então é muito mais eficiente
  • Autograd

    • Em vez do objeto escalar Value em Python puro, usam-se tensores (grandes arrays multidimensionais de números), executados em GPUs/TPUs que realizam bilhões de operações de ponto flutuante por segundo
    • O PyTorch cuida do autograd sobre tensores, e kernels CUDA como o FlashAttention fundem várias operações
    • A matemática é a mesma; muitos escalares são processados em paralelo
  • Arquitetura

    • microgpt: 4.192 parâmetros; um modelo nível GPT-4: centenas de bilhões
    • No geral, a rede neural Transformer é muito parecida, mas muito mais larga (dimensão de embedding 10.000+) e muito mais profunda (100+ camadas)
    • Tipos extras de blocos LEGO e mudanças na ordem:
      • RoPE (embeddings posicionais rotativos) — em vez de embeddings posicionais aprendidos
      • GQA (grouped-query attention) — reduz o tamanho do cache KV
      • Ativações lineares com gate — em vez de ReLU
      • Camadas MoE (mixture of experts)
    • A estrutura central de atenção (comunicação) e MLP (cálculo) alternando sobre o fluxo residual é bem preservada
  • Treinamento

    • Em vez de um documento por etapa, usam-se grandes lotes (milhões de tokens por etapa), acumulação de gradiente, precisão mista (float16/bfloat16) e ajuste cuidadoso de hiperparâmetros
    • O treinamento de modelos de fronteira roda em milhares de GPUs por vários meses
  • Otimização

    • microgpt: Adam + redução linear simples da taxa de aprendizado
    • Em larga escala, otimização é uma área própria: precisão reduzida (bfloat16, fp8), treinamento em grandes clusters de GPU
    • As configurações do otimizador (taxa de aprendizado, weight decay, parâmetros beta, cronogramas de warmup/decay) precisam de ajuste fino, e os valores corretos variam conforme o tamanho do modelo, o tamanho do lote e a composição do dataset
    • Leis de escala (por exemplo, Chinchilla) orientam como distribuir um orçamento fixo de computação entre o tamanho do modelo e a quantidade de tokens de treinamento
    • Errar esses detalhes em larga escala pode desperdiçar milhões de dólares em computação, por isso as equipes fazem muitos experimentos pequenos antes de um treinamento completo
  • Pós-treinamento (Post-training)

    • O modelo base que sai do treinamento (modelo pré-treinado) é um completador de documentos, não um chatbot
    • O processo para transformá-lo em ChatGPT tem duas etapas:
      • SFT (ajuste fino supervisionado): substitui documentos por conversas curadas e continua o treinamento, sem mudança algorítmica
      • RL (aprendizado por reforço): o modelo gera respostas → recebe pontuação (de humanos, modelos "juízes" ou algoritmos) → aprende com esse feedback
    • No fundo, ele ainda está aprendendo sobre documentos, mas agora os documentos são compostos por tokens gerados pelo próprio modelo
  • Inferência

    • Para servir o modelo a milhões de usuários, é necessário um stack de engenharia próprio: batching de requisições, gerenciamento e paginação de cache KV (como no vLLM), decodificação especulativa para velocidade, quantização para reduzir memória (execução em int8/int4) e distribuição do modelo entre várias GPUs
    • Fundamentalmente, ele ainda prevê o próximo token da sequência, mas há muito esforço de engenharia para torná-lo mais rápido

FAQ

  • O modelo "entende" alguma coisa?

    • É uma questão filosófica, mas mecanicamente: nenhuma mágica acontece
    • O modelo é uma grande função matemática que mapeia tokens de entrada para uma distribuição de probabilidade sobre o próximo token
    • Durante o treinamento, os parâmetros são ajustados para tornar o próximo token correto mais provável
    • Se isso constitui "entendimento" depende de cada pessoa, mas o mecanismo está totalmente contido nessas 200 linhas
  • Por que isso funciona?

    • O modelo tem milhares de parâmetros ajustáveis, e o otimizador os move um pouco a cada etapa para reduzir a perda
    • Ao longo de muitas etapas, os parâmetros se estabilizam em valores que capturam regularidades estatísticas dos dados
    • No caso dos nomes: muitos começam com consoante, "qu" tende a aparecer junto, três consoantes seguidas são raras etc.
    • O modelo aprende uma distribuição de probabilidade que reflete isso, não regras explícitas
  • Qual é a relação com o ChatGPT?

    • O ChatGPT pega esse mesmo loop central (prever o próximo token, amostrar, repetir), amplia isso enormemente e acrescenta pós-treinamento para torná-lo interativo
    • Ao conversar, prompt de sistema, mensagem do usuário e resposta são todos apenas tokens em uma sequência
    • O modelo completa um documento um token por vez, exatamente como o microgpt completa nomes
  • O que é "alucinação"?

    • O modelo gera tokens amostrando de uma distribuição de probabilidade
    • Ele não tem conceito de verdade; conhece apenas sequências estatisticamente plausíveis à luz dos dados de treinamento
    • Quando o microgpt "alucina" um nome como "karia", é o mesmo fenômeno de o ChatGPT afirmar um fato falso com confiança
    • Em ambos os casos, são completações plausíveis, não necessariamente reais
  • Por que é tão lento?

    • O microgpt processa um escalar por vez em Python puro, e uma única etapa de treinamento leva vários segundos
    • Em GPUs, a mesma matemática roda milhões de escalares em paralelo, ficando várias ordens de grandeza mais rápida
  • Dá para fazer gerar nomes melhores?

    • Sim: treinando por mais tempo (num_steps maior), aumentando o tamanho do modelo (n_embd, n_layer, n_head) ou usando um dataset maior
    • São as mesmas alavancas importantes em larga escala
  • E se eu trocar o dataset?

    • O modelo aprende qualquer padrão presente nos dados
    • Se você trocar por nomes de cidades, nomes de Pokémon, palavras em inglês ou arquivos curtos de poesia, ele aprenderá a gerar isso
    • O restante do código não precisa ser alterado

1 comentários

 
mhj5730 2026-02-19

Obrigado pelo ótimo artigo.