← Voltar pro blog
steply / blog · como-funciona-um-llm-por-baixo-dos-panos-transformer-tokens.md
$ steply blog open como-funciona-um-llm-por-baixo-dos-panos-transformer-tokens
▸ loading article…
✓ ready

Como um LLM funciona por baixo dos panos: do token ao transformer, sem deixar detalhe fora

porSteply11 min de leitura

LLM virou commodity de produto, mas quase ninguém que monta agente, escreve prompt ou desenha RAG sabe o que está acontecendo do outro lado do POST /v1/messages. Esse post abre tudo. Tokenização, embeddings, arquitetura transformer, mecanismo de atenção, geração autorregressiva, sampling, KV cache, mixture of experts, quantização, RLHF. Wiki técnica densa, do átomo (token) ao sistema completo (modelo servido em produção). Sem mística. Matemática e engenharia.

Quem entende isso prompta melhor, debuga melhor, escolhe modelo melhor, dimensiona infraestrutura melhor. Quem não entende paga 10x pra resultado pior. A diferença entre as duas curvas é esse post.

1. Token: a unidade atômica

LLM não vê texto. Vê inteiros. O texto que entra é primeiro convertido em sequência de tokens, e cada token é um inteiro entre 0 e vocab_size (tipicamente 32k a 256k). O algoritmo padrão é BPE (Byte Pair Encoding) ou variantes (Tiktoken da OpenAI, SentencePiece do Google, tokenizer próprio Anthropic).

BPE funciona assim: começa com cada byte como token. Conta os pares de tokens mais frequentes no corpus de treino. Funde o par mais comum num token novo. Repete até atingir o vocab_size desejado. Resultado: palavras comuns viram 1 token, palavras raras viram 2 a 5 tokens, caracteres especiais viram seu próprio token.

Exemplos práticos com tokenizer GPT-4o:

  • 'hello' = 1 token
  • 'antidisestablishmentarianism' = 6 tokens
  • 'São Paulo' = 3 tokens (português é menos eficiente que inglês porque o vocab é dominado por inglês)
  • '你好' = 2 tokens (idiomas com script próprio são caros)
  • 1 token em inglês = ~4 caracteres = ~0.75 palavra

Consequência operacional: preço de API é por token, não por palavra. Conteúdo em português custa ~30% mais que mesmo conteúdo em inglês. Código compacto (Python, JS) é eficiente. Código verboso (Java, COBOL) é caro. JSON sem indentação é eficiente. JSON com indentação custa 30% a mais à toa.

2. Embedding: do inteiro ao vetor

Token é inteiro. Modelo opera em vetores. A primeira camada de qualquer transformer é a embedding table: matriz de tamanho [vocab_size, d_model], onde d_model é a dimensão interna (Llama 70B: 8192, GPT-4 classe: estimado 12288+).

Cada token vira o vetor da linha correspondente na tabela. Esse vetor é a representação inicial. Vai sofrer N transformações pelas camadas seguintes até virar predição.

Detalhe matemático que importa: a embedding table é aprendida no pré-treino. Os 12k números que representam o token 'hello' não foram escolhidos por humano, foram otimizados pra minimizar loss. Tokens semanticamente próximos ficam próximos no espaço vetorial. Por isso embedding de tokens 'rei' e 'rainha' têm diferença similar a 'homem' e 'mulher'. Geometria emerge.

Embeddings de LLM são distintos de embeddings de modelo dedicado (text-embedding-3-large da OpenAI, BGE). LLM embedding é treinado pra prever próximo token. Embedding model é treinado pra similaridade semântica. Não use embedding intermediária de LLM pra search semântico, vai pior que modelo dedicado.

3. Positional encoding: como o modelo sabe a ordem

Atenção é invariante a ordem (vai ficar claro adiante). Sem informação posicional, 'cachorro morde homem' e 'homem morde cachorro' viram a mesma sopa pro modelo. Solução: somar/concatenar informação de posição ao embedding.

Métodos em ordem cronológica:

  • Sinusoidal (Transformer original, 2017): posição vira combinação de senos e cossenos de várias frequências. Não tem parâmetro aprendido. Generaliza pra sequências mais longas que as do treino, em teoria.
  • Learned positional embedding (GPT-2): matriz [max_seq, d_model] aprendida. Simples, mas não generaliza além de max_seq visto no treino.
  • RoPE (Rotary Position Embedding): usado em Llama, Mistral, Qwen. Aplica rotação nos vetores de query/key proporcional à posição. Vantagens: codifica posição relativa naturalmente, extrapola melhor com técnicas como NTK-aware scaling.
  • ALiBi (Attention with Linear Bias): usado em alguns modelos. Adiciona bias decrescente nas scores de atenção baseado em distância. Mais simples, extrapolação boa.

Por que importa: a forma de positional encoding determina o quão bem o modelo lida com contexto longo. Llama 3.1 estendeu de 8k pra 128k com NTK-aware scaling do RoPE, sem retreinamento completo. Sem essa técnica, modelo degrada catastroficamente além do treino.

4. Atenção: o coração do transformer

Atenção é o mecanismo que permite cada token 'olhar' pra outros tokens da sequência e pegar informação relevante. Sem isso, modelo não consegue relacionar 'ela' no fim de uma frase com 'Maria' no começo.

Para cada token na posição i, computa três vetores: Query (Q), Key (K), Value (V). Todos via multiplicação por matrizes aprendidas W_Q, W_K, W_V.

Score de atenção entre token i e token j: score(i,j) = (Q_i · K_j) / sqrt(d_k). A divisão por sqrt(d_k) estabiliza gradiente. Softmax sobre todos os scores de cada i normaliza pra distribuição de probabilidade.

Output pra token i: sum_j (attention(i,j) * V_j). Cada token vira combinação ponderada de values de todos os tokens, pesada por quão relevantes são (atenção).

Causal masking: em LLM generativo (decoder-only), a atenção é mascarada. Token i só pode atender a tokens j ≤ i. Impede de 'enxergar o futuro' durante treino, que é o que torna possível treinar pra prever próximo token.

Multi-head attention: em vez de uma atenção, faz N em paralelo (h = 32, 64, 128). Cada head aprende a focar em padrões diferentes (um cuida de sintaxe, outro de coreferência, outro de semântica). Outputs concatenados e projetados pra d_model.

5. Variações da atenção que mudam custo

Atenção padrão (MHA) é O(n²) em memória e compute. Pra sequências longas, vira gargalo. Otimizações que viraram standard:

  • MQA (Multi-Query Attention): todas as heads compartilham mesma K e V, só Q é separado. Reduz drasticamente memória do KV cache, perda mínima de qualidade. Usado em PaLM.
  • GQA (Grouped-Query Attention): meio-termo. Heads agrupadas em G grupos, cada grupo compartilha K/V. G=8 é comum. Usado em Llama 2 70B+, Llama 3, Mistral.
  • FlashAttention: implementação que reorganiza compute pra minimizar leitura/escrita em memória HBM da GPU. 2x a 4x mais rápido sem mudar a matemática. Hoje é default em qualquer treino/inferência séria.
  • Sliding Window Attention: cada token só atende a uma janela local. Mistral 7B usa. Permite sequências longas com custo linear em vez de quadrático.
  • Sparse Attention: padrões pré-definidos de quais tokens atendem a quais. Longformer, BigBird.

6. Feed-forward: a outra metade do bloco

Cada bloco transformer tem dois subcomponentes: atenção e feed-forward network (FFN). FFN é uma MLP de duas camadas aplicada token-a-token (sem mistura entre tokens, isso é trabalho da atenção).

Estrutura: FFN(x) = W_2 * activation(W_1 * x + b_1) + b_2. Dimensão intermediária é tipicamente 4x d_model. Ativação clássica: ReLU. Modernos usam SwiGLU (GLU com Swish), que dá ganho mensurável sem custo adicional relevante.

FFN é onde mora a maior parte dos parâmetros do modelo. Llama 70B: ~70% dos pesos são FFN, ~25% atenção, ~5% embeddings. Por isso é o alvo principal de Mixture of Experts (próxima seção).

7. Mixture of Experts: como rodar modelo grande sem ativá-lo todo

MoE substitui o FFN denso por N FFN paralelos ('experts') mais um router que escolhe top-K experts por token (geralmente K=2). Só os K experts escolhidos rodam, os outros ficam dormentes.

Mixtral 8x7B: 8 experts, top-2 ativos. Total ~47B parâmetros, mas só ~13B ativados por forward. Inferência roda no custo de 13B, qualidade chega perto de 70B denso.

Trade-off: economiza compute, mas memória continua igual. Você precisa ter todos os experts em VRAM mesmo só usando 2. Por isso MoE é favorito em data center (muita VRAM disponível) e cai mal em edge (memória escassa).

Modelos MoE notáveis: Mixtral 8x7B/8x22B, DeepSeek-V3 (256 experts, 9 ativos), Qwen 2.5 MoE, GPT-4 (presumido MoE pela arquitetura inferida).

8. Normalização e residual: estabilidade que possibilita treinar profundo

Cada bloco tem residual connections: output = input + sublayer(input). Permite gradiente fluir através de N camadas sem vanishing. Sem residual, modelo de mais de ~10 camadas não treina.

Layer normalization: normaliza ativações por amostra. RMSNorm (versão simplificada, usada em Llama) tira a média e normaliza só pela raiz quadrada média. Mais barato, qualidade equivalente.

Posicionamento: Pre-norm (norm antes da sublayer) é o padrão moderno, treina mais estável que Post-norm (do paper original).

9. A arquitetura inteira: empilhando blocos

Modelo é N blocos transformer empilhados (Llama 70B: 80 blocos, GPT-3 175B: 96 blocos). Cada bloco tem atenção + FFN + residuals + norms. Sequência completa de um forward:

  1. Token IDs → embedding (lookup na tabela).
  2. Somar positional encoding (ou aplicar RoPE nos Q/K dentro da atenção).
  3. Para cada bloco (1 a N): atenção (multi-head, com causal mask) + residual + norm; FFN + residual + norm.
  4. Norm final.
  5. Projeção pra vocab_size via 'lm_head' (tipicamente tied com embedding pra economizar parâmetros): produz logits de tamanho vocab_size para cada posição.
  6. Logits → probabilidades via softmax.

10. Inferência autorregressiva: como o texto é gerado

LLM gera token a token. Cada token novo depende de todos os anteriores.

tokens = encode(prompt)
while not done:
 logits = model.forward(tokens)
 next_logits = logits[-1] # último token
 next_token = sample(next_logits)
 tokens.append(next_token)
 if next_token == EOS: done = True
return decode(tokens)

Sem otimização, isso é O(n²) por geração: cada novo token recomputa atenção sobre todos os anteriores. Inviável.

11. KV cache: a otimização que torna inferência viável

Observação chave: a cada novo token, Q muda mas K e V dos tokens passados são os mesmos. Cachear K e V de todos os tokens já processados elimina recomputação.

Memória ocupada: 2 * n_layers * n_heads * d_head * seq_len * 2 bytes (FP16). Llama 70B com 8k contexto: ~40GB de KV cache. Por isso modelo de 70B precisa de GPU com 80GB+ de VRAM mesmo sendo o modelo em si menor.

GQA reduz KV cache em ~8x (Llama 70B com GQA: ~5GB de KV em 8k contexto). Razão prática de a indústria toda ter adotado GQA: viabilizar contexto longo.

Quantização do KV cache (FP8, INT8) reduz mais 2x a 4x. Padrão em servir produção.

12. Sampling: como o token é escolhido entre N possibilidades

Modelo produz distribuição de probabilidade sobre vocab inteiro (32k a 256k tokens). Sampling decide qual escolher.

  • Greedy: sempre o de maior probabilidade. Determinístico. Boa pra tarefas com resposta certa única (extração, classificação).
  • Temperature: divide logits por T antes do softmax. T < 1 concentra na probabilidade alta (mais determinístico). T > 1 espalha (mais criativo). T = 0 equivale a greedy.
  • Top-k: considera só os K tokens mais prováveis, zera o resto. Tipico K=40 a 50.
  • Top-p (nucleus): considera os tokens cuja probabilidade acumulada chega em P. Padrão P=0.9 a 0.95. Adapta tamanho do conjunto à distribuição (mais flexível que top-k).
  • Repetition penalty: reduz probabilidade de tokens já gerados. Combate loops.
  • Min-p: variação recente. Considera só tokens com probabilidade ≥ P * max_prob. Mais robusto que top-p em casos de cauda longa.

Combinações típicas em produção: T=0 (extraction), T=0.7+top_p=0.9 (chat), T=1.0+top_p=0.95 (criativo).

13. Pré-treino: onde 99% do custo vive

Pré-treino é onde o modelo aprende linguagem. Objetivo: predição do próximo token sobre corpus gigante (trilhões de tokens). Loss: cross-entropy entre distribuição predita e token real.

Dados típicos: web (Common Crawl, FineWeb), código (GitHub, The Stack), livros, papers, Wikipedia. Curadoria virou diferencial: dedup, filtro de qualidade, balanceamento de domínio. DeepSeek e Llama 3 mostraram que 'qualidade do data > quantidade'.

Compute: GPT-4 classe usou estimados 10^25 FLOPs. Llama 3 405B: 3.8 * 10^25. Treinar um modelo frontier custa centenas de milhões em GPUs. Por isso o ecossistema é dominado por poucos labs.

Tempo: ordem de meses, em clusters de milhares de GPUs (Llama 3 405B: 16k H100s por ~54 dias).

14. Pós-treino: o que transforma modelo bruto em útil

Modelo pré-treinado é completer de texto. Pra responder pergunta, seguir instrução, recusar conteúdo perigoso, precisa de pós-treino. Três fases típicas.

SFT (Supervised Fine-Tuning): continua o treino com pares (prompt, resposta ideal) escritos por humano. Ensina formato de resposta, tom, capacidade de seguir instrução. Dataset: 10k a 1M exemplos.

RLHF (Reinforcement Learning from Human Feedback): humanos comparam pares de respostas, escolhem a melhor. Treina reward model que aprende a pontuar respostas. Depois, modelo principal é otimizado via PPO pra maximizar reward. Caro, instável, mas trouxe ChatGPT à existência.

DPO (Direct Preference Optimization): alternativa moderna ao RLHF. Otimiza direto sobre os pares de preferência, sem reward model intermediário. Mais simples, resultados comparáveis. Adotado em Llama 3, Mistral, Qwen.

Variações recentes: RLAIF (AI gera o feedback), Constitutional AI (Anthropic: modelo critica próprias respostas baseado em princípios), Online DPO. O campo evolui rápido.

15. Quantização: rodando modelo grande em hardware menor

Pesos do modelo são floats. Treino em BF16 (16 bits). Inferência pode usar precisão menor com perda mínima de qualidade.

  • FP16/BF16: 16 bits. Padrão pra inferência sem otimização.
  • INT8: 8 bits. ~2x menos memória, ~2x mais rápido. Perda <1% em maioria dos benchmarks.
  • INT4: 4 bits. ~4x menos memória. Perda perceptível mas usável (GPTQ, AWQ são técnicas standard).
  • FP8: 8 bits floating point. Novo, requer hardware compatível (H100, Blackwell). Qualidade melhor que INT8.
  • 1.58 bits (BitNet): extremo. Pesos em {-1, 0, 1}. Pesquisa promissora, ainda não produção.

Quantização é o que permite rodar Llama 70B em GPU de 24GB (INT4) em vez de exigir A100 80GB (FP16). Habilita LLM local em hardware de consumidor.

16. Context window: limite, custo, e degradação

Context window é o máximo de tokens que o modelo aceita. Hoje varia de 8k (modelos pequenos) a 2M (Gemini 1.5 Pro, em preview), com Claude e GPT-4 em 200k a 1M.

Limites duros: KV cache cresce linear, atenção cresce quadrático. Mesmo com FlashAttention e GQA, contexto muito longo é caro e lento. Latência de TTFT (time to first token) pode passar de 30s pra prompt de 1M tokens.

Limite suave: degradação de qualidade ao longo do contexto. Modelos esquecem informação do meio mais que do começo ou fim ('lost in the middle'). Benchmarks como needle-in-haystack medem isso. Modelos modernos passam needle, mas degradam em tarefas que exigem raciocínio sobre múltiplos pontos do contexto longo.

17. Servindo em produção: vLLM, TGI, e o que importa

Frameworks de inferência otimizada:

  • vLLM: PagedAttention (gerenciamento de KV cache em páginas, evita fragmentação), continuous batching, prefix caching. Default da indústria pra self-hosted.
  • TGI (Text Generation Inference): HuggingFace. Maduro, mas vLLM ultrapassou em features.
  • SGLang: foco em structured generation, RadixAttention pra cache compartilhado entre requests.
  • TensorRT-LLM: NVIDIA, performance máxima em hardware NVIDIA, mais trabalhoso de configurar.
  • llama.cpp: CPU/Metal/CUDA, foco em quantização e edge. Default pra rodar local.

Otimizações que entram em produção séria: continuous batching (mistura requests em diferentes estados na mesma forward pass, em vez de batch sincrônico), speculative decoding (modelo pequeno propõe próximos tokens, modelo grande valida em paralelo), prefix caching (cacheia KV de prefix comum entre requests, base do prompt caching da Anthropic).

18. O reframe: o que essa wiki te permite fazer

Conhecendo essa pilha, decisões viram técnicas em vez de chute. Por que prompt em inglês custa menos: tokenização. Por que system prompt longo cabe no cache: prefix caching. Por que GQA habilitou contextos longos: KV cache. Por que MoE tem inferência barata mas memória cara: arquitetura. Por que o modelo perde a noção no meio do contexto grande: degradação de atenção em sequências longas, não bug do prompt.

Tudo o que parecia detalhe do fornecedor passa a ser parâmetro que você pode ajustar (próprio ou cobrar do fornecedor). É a diferença entre usuário de LLM e engenheiro de LLM. Esse post inteiro é a fronteira entre os dois.