Criando um GPT com 200 linhas em Python
Um GPT em Python puro, com 200 linhas e nenhuma dependência externa, é exatamente o que Andrej Karpathy publicou em fevereiro de 2026 com o projeto microgpt. Recentemente, tive o prazer de estudar esse arquivo único, que implementa o ciclo completo de treinamento e inferência, desde o dataset até a geração de novos textos.
O projeto resume mais de uma década de esforço do Andrej para destilar a essência algorítmica dos modelos de linguagem ao mínimo indispensável. Por isso, ele virou uma das melhores portas de entrada para quem quer entender os LLMs de verdade.
Por que estudar um GPT em Python puro
O que me chamou atenção imediatamente foi a afirmação central de Karpathy: "Everything else is just efficiency". Em outras palavras, tudo o mais é apenas eficiência.
Ou seja, toda a engenharia que envolve modelos como o GPT-4 ou o ChatGPT não altera a estrutura algorítmica fundamental. Ela apenas executa essa estrutura em escala.
Compreender o microgpt é, portanto, compreender os LLMs em sua forma mais pura. Além disso, um GPT em Python sem bibliotecas obriga você a ver cada conta acontecendo, sem nenhuma caixa preta no caminho.
O código-fonte desse GPT em Python está disponível no Gist oficial do microgpt e no Google Colab. Recomendo abrir o arquivo em uma aba ao lado enquanto lê este artigo.
Se você ainda não conhece a ideia geral desses modelos, leia antes o artigo sobre como funciona uma LLM.
O dataset do GPT em Python
O combustível de qualquer modelo de linguagem é um fluxo de dados textuais. Em aplicações de produção, esse fluxo corresponde a bilhões de páginas da internet.
No microgpt, por outro lado, Karpathy utiliza um conjunto de 32.000 nomes próprios, um por linha:
emma
olivia
ava
isabella
...
Cada nome funciona como um documento independente. Assim, o objetivo do modelo é aprender os padrões estatísticos presentes nesses documentos.
Ao final do treinamento, o GPT em Python gera novos nomes que compartilham essas regularidades. Na prática, são nomes que soam plausíveis, mas que não existem no dataset original.
Essa simplificação é pedagogicamente brilhante. Ela elimina o pré-processamento de dados massivos, mas preserva a lógica fundamental: o GPT em Python aprende a completar sequências com base em probabilidades.
O tokenizador
Redes neurais operam sobre números, não sobre caracteres ou palavras. Por isso, precisamos de um mecanismo de conversão entre texto e identificadores inteiros, o que chamamos de tokenizador.
No microgpt, o tokenizador é intencionalmente simples: cada caractere único no dataset recebe um identificador inteiro sequencial. Como o conjunto de nomes tem 26 letras minúsculas, o vocabulário final tem 27 tokens.
Ou seja, são os 26 caracteres mais um token especial chamado BOS, sigla de Beginning of Sequence. Esse token serve como delimitador de documento.
Durante o treinamento, o código envolve cada nome com esse token nos dois lados: [BOS, e, m, m, a, BOS]. Dessa forma, o modelo aprende que BOS sinaliza o início e o fim de um nome.
É o mesmo mecanismo pelo qual modelos de produção distinguem o início e o fim de uma conversa.
Tokenizadores industriais, como o tiktoken utilizado pelo GPT-4, trabalham com vocabulários de cerca de 100.000 tokens. Além disso, eles usam algoritmos de compressão como o BPE (Byte Pair Encoding), que representa palavras comuns como tokens únicos e palavras raras como composição de subtokens.
Mesmo assim, a diferença é de eficiência, não de princípio. Um GPT em Python com tokens de caractere aprende exatamente o mesmo tipo de tarefa que um modelo com BPE.
O autograd do GPT em Python
Esta é, sem dúvida, a seção mais matematicamente densa do projeto, e também a mais elegante. Afinal, para treinar uma rede neural, precisamos de gradientes.
Para cada parâmetro, precisamos saber se aumentar aquele número faz a perda subir ou descer, e em que magnitude. É exatamente essa pergunta que o autograd do GPT em Python responde.
A classe Value
Karpathy implementa um motor de autodiferenciação escalar do zero, na classe Value:
class Value:
__slots__ = ('data', 'grad', '_children', '_local_grads')
def __init__(self, data, children=(), local_grads=()):
self.data = data
self.grad = 0
self._children = children
self._local_grads = local_grads
Cada instância de Value encapsula um único número escalar (.data) e registra como ele surgiu. Então, cada operação matemática cria um novo nó no grafo computacional.
Adição, multiplicação, potência, logaritmo, exponencial e ReLU seguem a mesma regra. Cada nova operação armazena os filhos do nó e os gradientes locais daquela conta.
| Operação | Forward | Gradiente local |
|---|---|---|
a + b |
a + b |
∂/∂a = 1, ∂/∂b = 1 |
a * b |
a · b |
∂/∂a = b, ∂/∂b = a |
a ** n |
aⁿ |
∂/∂a = n · aⁿ⁻¹ |
log(a) |
ln(a) |
∂/∂a = 1/a |
exp(a) |
eᵃ |
∂/∂a = eᵃ |
relu(a) |
max(0,a) |
∂/∂a = 1 se a > 0, senão 0 |
Para visualizar o papel dos gradientes no aprendizado, o vídeo abaixo, do canal 3Blue1Brown, explica o gradient descent de forma muito visual:
Backward e regra da cadeia
O método backward() percorre o grafo em ordem reversa, da saída (a perda) em direção aos parâmetros. Em cada passo, ele aplica a regra da cadeia:
∂L/∂c += (∂v/∂c) · (∂L/∂v)
A analogia proposta pelo próprio Karpathy ajuda muito aqui. Se um carro viaja duas vezes mais rápido que uma bicicleta, e a bicicleta é quatro vezes mais rápida que um pedestre, então o carro é 2 × 4 = 8 vezes mais rápido que o pedestre.
A regra da cadeia é exatamente esse raciocínio de composição de taxas de variação. Por isso, basta multiplicar os gradientes locais ao longo do caminho.
Note também que o gradiente usa acumulação (+=) e não atribuição. Isso acontece porque, quando um nó participa de vários caminhos do grafo, as contribuições de cada caminho precisam ser somadas.
Essa soma é uma consequência direta da regra da cadeia multivariável.
Este é o mesmo algoritmo executado pelo loss.backward() do PyTorch. No entanto, o GPT em Python de Karpathy opera sobre escalares, enquanto o PyTorch opera sobre tensores processados em paralelo por GPUs.
Os parâmetros do modelo
Os parâmetros são o conhecimento do modelo. Em outras palavras, são uma coleção de números de ponto flutuante que começam aleatórios e passam por otimização a cada iteração do treinamento.
O microgpt usa a seguinte configuração:
n_embd = 16: dimensão dos embeddings.n_head = 4: número de cabeças de atenção.n_layer = 1: número de camadas.block_size = 16: comprimento máximo de sequência.
Além disso, o código organiza os parâmetros em matrizes nomeadas:
wte: tabela de embeddings de tokens (vocab_size × n_embd).wpe: tabela de embeddings de posição (block_size × n_embd).attn_wq,attn_wk,attn_wv,attn_wo: matrizes de projeção de atenção.mlp_fc1,mlp_fc2: camadas da rede feed-forward.lm_head: projeção final para o vocabulário.
Com essa configuração, o modelo totaliza 4.192 parâmetros. Para comparação, o menor GPT-2 tinha 117 milhões de parâmetros, e modelos modernos chegam a centenas de bilhões.
Mesmo assim, cada número do GPT em Python segue o mesmo caminho de um parâmetro em um modelo gigante.
A arquitetura do GPT em Python
A função gpt(token_id, pos_id, keys, values) processa um único token por vez. Ela recebe o identificador do token atual, sua posição na sequência e o KV Cache acumulado dos tokens anteriores.
Como resultado, a função retorna logits, ou seja, pontuações brutas sobre qual token deve vir a seguir. A arquitetura segue o GPT-2, mas com simplificações: RMSNorm no lugar de LayerNorm, ReLU no lugar de GeLU e nenhum bias nas camadas.
Embeddings
A rede neural não consegue processar diretamente um identificador inteiro como 5. Ela opera sobre vetores.
Assim, o modelo mapeia cada token e cada posição para vetores densos de dimensão n_embd = 16. Em seguida, ele soma os dois vetores para produzir uma representação conjunta de o que é o token e onde ele está na sequência.
Atenção multi-head no GPT em Python
O mecanismo de atenção é onde os tokens se comunicam. Para isso, o modelo projeta o token atual em três vetores:
- Query (Q): o que estou procurando?
- Key (K): o que eu contenho?
- Value (V): o que ofereço se for selecionado?
Para cada cabeça de atenção, o modelo calcula o produto escalar entre a query atual e todas as keys armazenadas no KV Cache. Depois, divide o resultado por √d_head para manter a estabilidade numérica.
O softmax então transforma esses produtos em pesos de atenção, ou seja, em uma distribuição de probabilidade. A saída é a soma ponderada dos values correspondentes.
O KV Cache guarda os pares (K, V) de todos os tokens anteriores e, dessa forma, evita recomputação. No GPT em Python, esse cache é explícito e sequencial, enquanto implementações de produção o escondem dentro de operações matriciais paralelizadas.
Essa é a mesma ideia descrita no artigo Attention Is All You Need, que deu origem ao Transformer. Contudo, aqui você enxerga cada multiplicação acontecendo de forma isolada.
Bloco MLP
O bloco MLP (Multilayer Perceptron) é onde o modelo realiza a maior parte de seu raciocínio por posição. Trata-se de uma rede feed-forward de duas camadas.
Primeiro, ela projeta o vetor para 4× a dimensão de embedding. Depois, aplica a ativação ReLU e, por fim, projeta o resultado de volta para a dimensão original.
Diferentemente do bloco de atenção, o MLP opera estritamente de forma local, sem acesso a tokens anteriores. Portanto, um Transformer intercala comunicação (atenção) com computação (MLP).
Conexões residuais e normalização
Tanto o bloco de atenção quanto o MLP somam sua saída à entrada original. Essas são as chamadas conexões residuais (x = output + input).
Esse mecanismo facilita o fluxo de gradientes em redes profundas e, com isso, deixa o treinamento mais estável.
Além disso, a normalização RMSNorm (Root Mean Square Normalization) reescala os vetores de ativação antes de cada bloco. Assim, ela impede que os valores cresçam ou diminuam de forma descontrolada ao longo das camadas.
Projeção final
Por fim, a matriz lm_head projeta o estado oculto final para a dimensão do vocabulário. O resultado são 27 números, um por token possível.
O token com maior logit é aquele que o GPT em Python considera mais provável de vir a seguir.
Como treinar um GPT em Python
Na prática, o treinamento do GPT em Python repete um mesmo ciclo por num_steps = 1000 iterações. Cada iteração segue estes passos:
- Seleção e tokenização: o código escolhe um documento e o envolve com
BOSnos dois lados. - Forward pass: os tokens passam pelo modelo um a um, construindo o grafo computacional até a perda.
- Cálculo da perda: a perda em cada posição é a entropia cruzada, ou seja, o logaritmo negativo da probabilidade do token correto.
- Backward pass:
loss.backward()propaga gradientes por todo o grafo. - Atualização com Adam: o otimizador Adam atualiza os parâmetros.
A fórmula da perda é loss = -log(p(target)). Uma perda igual a 0 significa previsão perfeita, enquanto um modelo surpreso com o token correto produz uma perda alta.
O otimizador Adam
Em vez de simplesmente subtrair o gradiente (p.data -= lr * p.grad), o Adam mantém dois buffers por parâmetro:
m: média móvel dos gradientes recentes, o chamado momentum.v: média móvel dos quadrados dos gradientes, que adapta a taxa de aprendizado.
As correções de viés m_hat e v_hat compensam o fato de que m e v começam em zero. Por isso, o otimizador precisa de um aquecimento gradual nas primeiras iterações.
Além disso, a taxa de aprendizado decai linearmente ao longo do treinamento. Dessa forma, os passos ficam menores à medida que o modelo se aproxima de uma boa solução.
Evolução da perda
Ao longo das 1.000 iterações, a perda cai de aproximadamente 3,3 para cerca de 2,37. O valor inicial equivale à adivinhação aleatória entre 27 tokens, já que -log(1/27) ≈ 3,3.
Essa queda é uma evidência clara de que o GPT em Python está aprendendo a estrutura estatística dos nomes. Segundo Karpathy, todo esse treino leva cerca de um minuto em um notebook comum.
A inferência no GPT em Python
Após o treinamento, os parâmetros ficam fixos e usamos o modelo para gerar novos nomes. O processo começa sempre com o token BOS e segue em loop.
Em cada volta, o GPT em Python gera logits, que o softmax converte em probabilidades. Em seguida, o código sorteia um token de acordo com essa distribuição.
O parâmetro temperature controla a aleatoriedade, porque o código divide os logits pela temperatura antes do softmax:
- Temperatura baixa (ex.: 0,5): aguça a distribuição e deixa o modelo mais conservador.
- Temperatura próxima de 0: vira decodificação gulosa, que sempre escolhe o token mais provável.
- Temperatura alta: achata a distribuição e gera saídas mais diversas e menos coerentes.
O processo termina quando o modelo gera outro BOS, indicando fim do documento, ou quando atinge o comprimento máximo de sequência. Entre os exemplos gerados estão nomes como "karia", "kamon" e "vialan", que parecem reais, mas não existem no dataset.
Do GPT em Python à produção
O microgpt implementa a essência algorítmica completa de um LLM. Portanto, o que separa este script de 200 linhas de um sistema como o ChatGPT não é a estrutura, e sim a escala e a engenharia de eficiência.
Karpathy sistematiza essa diferença de forma clara:
| Componente | microgpt | Produção |
|---|---|---|
| Dataset | 32K nomes | Trilhões de tokens de texto da internet |
| Tokenizador | Caracteres (vocab: 27) | BPE, vocab de ~100K |
| Autograd | Escalares em Python puro | Tensores em PyTorch + CUDA |
| Parâmetros | 4.192 | Centenas de bilhões |
| Arquitetura | 1 camada, emb. 16 | 100+ camadas, emb. 10.000+ |
| Treinamento | 1 doc/step, CPU | Milhões de tokens/step, clusters de GPUs |
| Pós-treinamento | Não aplicável | SFT + RLHF/DPO |
Modelos de produção também incorporam variações arquitetônicas. Entre elas estão o RoPE (Rotary Position Embeddings) no lugar de embeddings posicionais aprendidos e o GQA (Grouped Query Attention) para reduzir o tamanho do KV Cache.
Além disso, eles usam ativações lineares com gate e camadas MoE (Mixture of Experts). Mesmo assim, a estrutura de atenção e MLP intercalados em um fluxo residual continua a mesma do GPT em Python.
Se quiser aprofundar a parte arquitetural com uma visão mais conceitual, leia também o artigo O que é um GPT e como sua arquitetura funciona. Os dois textos se complementam bem.
Conclusão
O microgpt é um projeto raro, porque une rigor técnico e clareza pedagógica. Além disso, ele fica em uma escala que qualquer desenvolvedor com noções de Python e cálculo consegue acompanhar.
Ao estudá-lo, compreendi de forma visceral que os LLMs são otimizadores de distribuições de probabilidade sobre sequências de tokens. Toda a engenharia monumental dos sistemas de produção existe para executar esse princípio em escala industrial.
Encorajo fortemente qualquer desenvolvedor que trabalhe com .NET, Python ou qualquer outra linguagem a percorrer o código desse GPT em Python. Então, abra o Gist, rode no Colab, mude n_embd ou n_layer e observe o efeito na perda.
É uma das experiências mais esclarecedoras sobre o funcionamento interno dos modelos de linguagem.
FAQ
O que é o microgpt?
É um GPT em Python puro, publicado por Andrej Karpathy em fevereiro de 2026, com cerca de 200 linhas e sem dependências. Ele implementa dataset, tokenizador, autograd, Transformer, treino e inferência.
Preciso de GPU para rodar o microgpt?
Não, porque o código roda em CPU com Python puro. Segundo Karpathy, o treino completo leva cerca de um minuto em um notebook.
Quantos parâmetros o microgpt tem?
O modelo tem 4.192 parâmetros, com uma camada, quatro cabeças de atenção e embeddings de dimensão 16.
Qual a diferença entre o microgpt e o ChatGPT?
O algoritmo é o mesmo, mas o ChatGPT usa trilhões de tokens, bilhões de parâmetros, GPUs e etapas de pós-treinamento como SFT e RLHF.
O microgpt usa PyTorch?
Não, já que ele implementa o próprio autograd na classe Value. O PyTorch faz a mesma coisa, mas com tensores e aceleração por GPU.
O modelo entende o que está fazendo?
Mecanicamente, não há mágica: o modelo é uma função matemática que mapeia tokens para uma distribuição de probabilidade sobre o próximo token. Se isso constitui compreensão é uma questão filosófica, mas o mecanismo inteiro cabe nas 200 linhas do script.
Por que o modelo alucina?
Ele amostra tokens de uma distribuição de probabilidade e não possui conceito de verdade. O nome "karia" gerado pelo microgpt e uma afirmação falsa do ChatGPT são o mesmo fenômeno: uma continuação plausível que não corresponde à realidade.
Por que o microgpt é tão lento?
Ele processa um escalar por vez em Python puro, enquanto uma GPU processa milhões de escalares em paralelo. Ou seja, a lentidão é o preço da clareza, e não um defeito do algoritmo.