De um inteiro sem sentido a um vetor de 768 números que sabe que "rei" e "rainha" são parentes.
Saímos da apostila 01 com uma lista de inteiros. Digamos que gato = 4899 e cachorro = 5257. Esses números são etiquetas de armário, não medidas. O armário 5257 não é "maior" nem "mais quente" que o 4899. Se o vocabulário fosse embaralhado, gato seria 12 e cachorro seria 40 001, e nada mudaria.
Mas uma rede neural só sabe fazer aritmética. Se ela recebe 4899 e 5257, ela vai inevitavelmente calcular diferenças, médias, produtos — e vai extrair relações que não existem. Alimentar IDs crus numa rede é um erro conceitual grave.
O jeito clássico de dizer "esta é a categoria 4899, e as categorias não têm ordem" é o vetor one-hot: um vetor de 50 257 posições, todas zero, exceto um 1 na posição 4899.
Isso resolve o problema da falsa ordem. Mas cria dois novos:
E se, em vez de 50 257 posições com um 1 solitário, cada palavra tivesse 768 números contínuos? Não escolhidos por nós — aprendidos pelo modelo durante o treino, ajustados a cada erro cometido.
Esses 768 números são as coordenadas da palavra num espaço de 768 dimensões. E aqui está o ponto central: se o treino for bem feito, palavras que aparecem em contextos parecidos acabam com coordenadas parecidas. Elas se agrupam. O espaço ganha geografia semântica.
Pense num mapa do Brasil. Cada cidade é um par de números (latitude, longitude). Esses dois números não são arbitrários: cidades com números parecidos são de fato próximas, e você pode fazer aritmética útil com eles ("qual cidade fica a 200 km ao norte?").
Um embedding é a mesma coisa, com 768 coordenadas em vez de 2. E as "direções" desse mapa não são norte-sul, mas coisas que o modelo descobriu sozinho: talvez uma direção seja "formalidade", outra "singular↔plural", outra "concreto↔abstrato". Ninguém programou esses eixos — eles emergiram porque ajudavam a prever a próxima palavra.
Em espaços de embedding bem treinados, relações semânticas viram deslocamentos vetoriais constantes:
Formalmente, a camada de embedding é uma matriz:
Matematicamente, buscar o embedding do token
Na implementação real, ninguém faz essa multiplicação. Faz-se indexação direta: W_e[4899]. É por isso que a camada se chama lookup table — tabela de consulta. O one-hot existe só na explicação matemática; no código ele nunca é construído.
No GPT-2 essa matriz se chama wte — word token embedding. Você vai vê-la nos nomes dos pesos: transformer.wte.weight. E vai reencontrá-la no fim do modelo, de um jeito surpreendente (seção 6).
Existe um buraco no que construímos até agora, e ele é grave. A atenção, que você vai ver na apostila 03, é uma operação de conjunto: ela olha todos os tokens ao mesmo tempo, sem noção nenhuma de ordem. Para ela, "o cachorro mordeu o homem" e "o homem mordeu o cachorro" são idênticos — mesmos tokens, mesmo conjunto.
Isso é inaceitável para linguagem. A ordem é significado. Então precisamos injetar a posição de volta, explicitamente.
O artigo original do Transformer (2017) usou senos e cossenos de frequências diferentes — uma fórmula fixa, elegante, que permite extrapolar para comprimentos nunca vistos. O GPT-2 abandonou isso e fez a escolha mais simples possível: uma segunda tabela de consulta, aprendida do zero.
No GPT-2 essa matriz se chama wpe — word position embedding.
Como wpe tem exatamente 1024 linhas e cada uma foi aprendida individualmente, o GPT-2 é fisicamente incapaz de processar mais de 1024 tokens. Não é uma configuração conservadora que dá para aumentar — a posição 1025 simplesmente não existe na matriz. Tentar usá-la é um erro de índice.
Isso contrasta com o Transformer senoidal original (que extrapola) e com técnicas modernas como RoPE (usada em Llama, Qwen, Mistral), que permitem esticar o contexto. É uma limitação de projeto de 2019 que você vai encostar na prática.
Os dois embeddings são simplesmente somados, elemento a elemento:
Essa é a pergunta certa, e é comum fazê-la. Concatenar (768 de conteúdo + 768 de posição = 1536) parece mais limpo, mas dobraria a largura do modelo e o custo de tudo depois.
A soma funciona porque 768 dimensões é muito espaço. Em alta dimensão, dois vetores aleatórios são quase sempre praticamente ortogonais — eles ocupam "direções" diferentes e não se atrapalham. O modelo aprende a usar certas direções para conteúdo e outras para posição, e as camadas seguintes conseguem separar de volta o que foi somado. É contraintuitivo, mas é uma propriedade real de espaços de alta dimensão.
Entrada: "O Brasil é gigante" -> 5 tokens
wte[ids] -> (5, 768) o QUE cada token é
wpe[0..4] -> (5, 768) ONDE cada token está
---------
h_0 = soma -> (5, 768) entrada do bloco 1
Esse (5, 768) atravessa os 12 blocos SEM MUDAR DE FORMA.
Sai (5, 768) do bloco 12. A forma e um invariante do Transformer.
Aqui está um detalhe de engenharia bonito, e que muita explicação de GPT-2 esquece.
No fim do modelo, você precisa converter o vetor de 768 dimensões da última posição de volta em uma pontuação para cada um dos 50 257 tokens possíveis. Isso pede uma matriz de saída
O GPT-2 não cria essa matriz. Ele reusa a matriz de embedding, transposta:
Isso se chama weight tying (amarração de pesos), e tem duas justificativas:
# o que existe dentro de wte e wpe import torch from transformers import AutoModel, AutoTokenizer tok = AutoTokenizer.from_pretrained("openai-community/gpt2") m = AutoModel.from_pretrained("openai-community/gpt2") wte = m.wte.weight # (50257, 768) conteúdo wpe = m.wpe.weight # (1024, 768) posição # 1) o embedding de um token e só uma linha da tabela id_gato = tok.encode(" gato")[0] v = wte[id_gato] # vetor de 768 números # 2) similaridade de cosseno = "quão parecidas são duas palavras?" def prox(a, b): va, vb = wte[tok.encode(a)[0]], wte[tok.encode(b)[0]] return torch.cosine_similarity(va, vb, dim=0).item() print(f"king x queen {prox(' king',' queen'):+.3f}") # alto print(f"king x banana {prox(' king',' banana'):+.3f}") # baixo # 3) a prova do weight tying: as duas matrizes são o MESMO objeto lm = AutoModelForCausalLM.from_pretrained("openai-community/gpt2") print(lm.lm_head.weight.data_ptr() == lm.transformer.wte.weight.data_ptr()) # True -> mesmo endereço de memória. Não é cópia, é o mesmo tensor.
.venv\Scripts\python.exe lab\02_embeddings.py
" queen" sai em +0,709 e " princess" logo atrás. Mas o script também mostra a ressalva que as demonstrações bonitas costumam esconder — " king" aparece em primeiro lugar, porque partimos do vetor dele e mexemos pouco. Quase toda demo famosa dessa analogia remove as palavras de entrada do ranking antes de mostrar o resultado.wte é a maior peça isolada em todos eles.O wte dá uma linha fixa por token, antes de qualquer contexto. Ou seja: "banco" de praça e "banco" de dinheiro recebem exatamente o mesmo vetor. Nesta camada, a ambiguidade é irresolúvel por construção.
E o GPT-2 não precisa que ela resolva. Ele tem 12 blocos de atenção depois dela justamente para construir o significado contextual — o "banco" vai se especializar conforme os vizinhos. O wte é um ponto de partida bruto; o mapa semântico de verdade mora nas camadas do meio. Guarde isso: é a diferença entre embeddings estáticos (Word2Vec, GloVe, wte) e contextuais (a saída de qualquer camada do Transformer), e é a razão pela qual o BERT foi uma revolução em 2018.
" cat" e " cats", e entre " cat" e " dog". Qual par você espera que seja mais próximo — o de forma ou o de sentido? Rode e veja se sua intuição está certa.wte + wpe em MB no GPT-2 XL (vocabulário 50 257, wpe tem 1024 linhas. Pegue as linhas 0, 1, 2 e 500, e calcule o cosseno entre elas. Posições vizinhas deveriam ter vetores parecidos? Você acha que o modelo aprendeu isso sozinho? (Rode: a resposta é sim, e é bonita.)As fontes primárias dos conceitos deste capítulo. Os números do texto foram medidos no laboratório; as ideias vêm daqui.
Os termos técnicos têm aqui o sentido de aprendizado de máquina. O glossário do curso mostra esse sentido ao lado dos outros que as mesmas palavras têm na TI.
Agora cada token é um vetor que sabe o que é e onde está — mas ainda está sozinho, sem falar com os vizinhos.
Na apostila 03 eles finalmente conversam. É o coração do Transformer.