Laboratório GPT-2 · RoBERTa · Parte I — A fundação
01

Tokenização
e o algoritmo BPE

Como um texto vira uma lista de números — e por que o modelo não enxerga letras nem palavras, mas pedaços.

Apostila 01 de 15 Antes: o mapa do curso e o glossário

1 Intuição: o modelo não lê o que você escreve

Uma rede neural é, no fundo, uma máquina de multiplicar matrizes. Matrizes são feitas de números. Logo, a primeira coisa que precisa acontecer com o seu texto é uma tradução: letras viram números. Essa etapa se chama tokenização, e ela acontece antes do modelo — não é aprendida junto com ele, é uma tabela fixa, decidida uma vez e congelada para sempre.

Qual "token"

A palavra token é mais antiga que os modelos de linguagem, e tem outros sentidos na TI: a credencial que um servidor emite depois do login, como um JWT; a unidade que o analisador léxico de um compilador separa, como uma palavra-chave; o ativo registrado num contrato de blockchain. Neste curso, token é sempre um pedaço de texto — uma palavra, parte de palavra ou byte — com um número inteiro associado. O glossário do curso faz essa distinção para todos os termos que têm mais de um sentido.

A pergunta é: números por quê? Existem três respostas históricas, e entender por que as duas primeiras falharam é a única forma de entender por que a terceira ganhou.

Tentativa 1 — um número por palavra

Simples: monte um dicionário. "casa"→41, "cachorro"→892, e assim por diante. Funciona até o dia em que aparece uma palavra que não está no dicionário — um nome próprio, um erro de digitação, uma gíria nova, uma palavra em outro idioma. Aí o modelo escreve <UNK> ("desconhecido") e perde a informação para sempre. E o dicionário do inglês real, com nomes e variações, passa de um milhão de entradas — o que faria a última camada do modelo (que precisa ter um neurônio por palavra possível) sozinha ser maior que o modelo inteiro.

Tentativa 2 — um número por caractere

Resolve o desconhecido: são só ~100 caracteres, e qualquer texto cabe. Mas cria um problema pior. A palavra "internacionalização" vira 19 posições em vez de 1. Como o custo da atenção cresce com o quadrado do número de posições (você vai ver isso na apostila 03), textos ficam muito mais caros. E o modelo precisa gastar várias camadas só para redescobrir que c-a-s-a junto significa alguma coisa.

Tentativa 3 — um número por pedaço frequente

Aqui está a sacada. E se o vocabulário fosse feito de pedaços de palavra, escolhidos automaticamente pela estatística do texto? Palavras comuns ("the", "casa") ganham um token só. Palavras raras se decompõem em pedaços que existem ("Rafhael" → R + af + ha + el). Nada nunca é desconhecido, e o texto continua curto.

Esse é o BPE — Byte Pair Encoding. É o que o GPT-2 usa, é o que o RoBERTa usa, e variantes dele seguem em uso em muitos modelos atuais.

Analogia

Pense numa loja de peças de LEGO que precisa vender o menor número possível de tipos de peça, mas ainda permitir montar qualquer coisa. Ela não vai vender só tijolinhos 1×1 (você montaria tudo, mas gastaria 10 mil peças por modelo). Também não vai vender uma peça única "Millennium Falcon montado" (só serve para um modelo). Ela vende peças médias que aparecem muito: paredes, janelas, rodas. O BPE é o algoritmo que descobre, olhando milhares de modelos já montados, quais peças médias valem a pena existir.

2 Como o BPE constrói o vocabulário

O BPE é um algoritmo de treino do tokenizador. Ele roda uma vez, sobre um corpus gigante, e produz duas coisas: uma lista de tokens (vocab.json) e uma lista ordenada de fusões (merges.txt). Depois disso, ele nunca mais muda.

O procedimento

  1. Comece com o alfabeto. Cada símbolo isolado é um token. No GPT-2, esse alfabeto são os 256 bytes possíveis (voltaremos a isso na seção 3).
  2. Conte todos os pares vizinhos no corpus inteiro. Quantas vezes t aparece seguido de h? E h seguido de e?
  3. Funda o par mais frequente em um token novo. Se t+h é o campeão, cria-se o token th, e todo t h do corpus vira th.
  4. Repita — agora th+e pode virar o campeão, criando the.
  5. Pare quando o vocabulário atingir o tamanho desejado. No GPT-2: 50 257 tokens.

Um exemplo minúsculo, feito à mão

Corpus: a palavra banana repetida 5 vezes, e bandana 2 vezes. Vamos acompanhar três fusões.

Cada linha é uma iteração do BPE. O par vencedor vira um token novo, e o texto encurta.
PassoEstado do textoPar mais frequenteContagemNovo token
0b a n a n a ×5   b a n d a n a ×2——alfabeto: a,b,d,n
1conta pares: an=10+4=14, na=10+2=12, ba=7…a+n14an
2b an an a ×5   b an d an a ×2an+a7ana
3b an ana ×5   b an d ana ×2b+an7ban
fimban ana ×5   ban d ana ×2"banana" custa 2 tokens; "bandana" custa 3.
O que isso revela

O vocabulário do modelo é um retrato estatístico do corpus de treino. O GPT-2 foi treinado no WebText — 8 milhões de páginas de internet em inglês, coletadas de links votados no Reddit. Por isso o inglês é eficiente (1 token ≈ 0,75 palavra) e o português é caro (1 token ≈ 0,4 palavra): o BPE nunca teve motivo estatístico para criar o token ção. Você vai medir isso no laboratório, e vai doer um pouco.

3 A jogada do GPT-2: BPE em nível de byte

Aqui está a contribuição específica do GPT-2, e é elegante. O BPE original (usado em tradução automática, 2016) partia de caracteres Unicode. Problema: existem mais de 149 mil caracteres Unicode. Se o alfabeto inicial já é enorme, sobra pouco espaço no vocabulário para as fusões úteis — e emojis ou ideogramas chineses ainda podiam cair fora e virar <UNK>.

A solução do GPT-2: não comece de caracteres, comece de bytes. Todo texto digital, em qualquer idioma, é uma sequência de bytes UTF-8. E bytes só existem em 256 valores possíveis. Então:

O detalhe esquisito: por que aparecem "Ġ" e "Ċ"

Quando você inspecionar os tokens do GPT-2, vai ver coisas como Ġthe e ĠcasaĊ. Parece estranho, mas é uma convenção simples.

Alguns bytes são caracteres de controle ou espaço em branco — imprimir eles direto quebraria os arquivos de vocabulário. Então o GPT-2 remapeia esses bytes problemáticos para caracteres Unicode visíveis e inofensivos:

Byte realValorViraSignifica
espaço32 (0x20)Ġ"aqui começou uma palavra nova"
quebra de linha10 (0x0A)Ċfim de linha
tabulação9 (0x09)ĉtab

É puramente cosmético — o Ġ é literalmente o caractere espaço com um chapéu. Mas ele carrega informação real: o espaço faz parte do token. Ġcasa (" casa", precedida de espaço) e casa (colada na palavra anterior, como em "minhacasa") são tokens diferentes, com IDs diferentes e embeddings diferentes.

Uma consequência prática que costuma surpreender

Se você escrever um prompt terminando em espaço — "O Brasil é " — o modelo já recebeu o Ġ sozinho e agora precisa completar um token que começa sem espaço. Isso o joga para uma região estranha do espaço de tokens e a qualidade cai visivelmente. Nunca termine um prompt com espaço. Escreva "O Brasil é" e deixe o modelo produzir o Ġ ele mesmo.

4 A matemática (é pouca, mas é a que importa)

O treino do BPE é uma sequência de escolhas gulosas. Em cada passo t, sendo C o corpus atual e P o conjunto de pares de tokens vizinhos:

(a*, b*) = argmax(a,b) ∈ P   contagemC(a, b) Escolhe-se o par vizinho (a,b) que aparece o maior número de vezes no corpus. "argmax" = "o argumento que maximiza", ou seja, qual par ganha, não quantas vezes ele apareceu.

Esse par é fundido num token novo ab, o corpus é reescrito, e o par entra na lista merges.txt na posição t. Repete-se até |V| = 50 257.

A aplicação (o que roda quando você digita)

Tokenizar um texto novo não refaz contagens. Ele apenas replica as fusões, na mesma ordem em que foram aprendidas:

x0 = bytes(texto)   →   xt = aplica(xt−1, merget) A ordem é sagrada: a fusão nº 1 é tentada antes da nº 2, sempre. Trocar a ordem produz uma tokenização diferente — por isso merges.txt é um arquivo ordenado, não um conjunto.

E o vocabulário final do GPT-2 se decompõe assim:

|V| = 256 + 50 000 + 1 = 50 257 256 bytes-base  +  50 000 fusões aprendidas  +  1 token especial <|endoftext|> (ID 50256), usado para separar documentos no treino e sinalizar fim de geração.

5 GPT-2 vs RoBERTa vs GPT-1: três tokenizadores diferentes

Este é um ponto que confunde muita gente: RoBERTa usa exatamente o mesmo algoritmo do GPT-2 — byte-level BPE, 50 000 fusões. Foi uma decisão explícita dos autores do RoBERTa, que trocaram o WordPiece do BERT pelo BPE do GPT-2. Mas os tokens especiais e o corpus são diferentes, então os IDs não batem.

Comparação dos três tokenizadores do curso. Note que o mesmo texto vira números completamente diferentes em cada um.
GPT-1 (openai-gpt)GPT-2 (toda a família)RoBERTa (detectores)
AlgoritmoBPE sobre caracteres + spaCy/ftfyBPE sobre bytesBPE sobre bytes
Vocabulário40 47850 25750 265
Token desconhecido<unk> existenão existenão existe
Caixa altatudo minúsculo (lowercased)preservadapreservada
Tokens especiais<unk><|endoftext|><s> </s> <pad> <unk> <mask>
Marca de espaço</w> no fim da palavraĠ no inícioĠ no início
Envolve a frasenãonão<s> … </s>

Os 8 tokens a mais do RoBERTa (50 265 vs 50 257) são justamente os especiais que ele precisa e o GPT-2 não: <pad> para completar lotes de tamanhos diferentes, <mask> para o objetivo de treino dele (apostila 10), e <s>/</s> para marcar início e fim — e o <s> vira, mais tarde, o vetor que carrega o resumo da frase inteira para a classificação (apostila 12).

6 O código, linha a linha

# tokenizar_na_mao.py — o BPE do GPT-2 sem mágica
from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("openai-community/gpt2")

texto = "O Brasil é gigante pela própria natureza."

# 1) texto -> lista de IDs (o que o modelo realmente recebe)
ids = tok.encode(texto)
print(ids)
#    [46, 8290, 2634, 20692, 12427, 27638, 355, 1050, 16864, 3450, 46844, 13]

# 2) cada ID de volta ao pedaço de texto que ele representa
for i in ids:
    print(f"{i:>6}  {tok.decode([i])!r:<12}  {tok.convert_ids_to_tokens([i])[0]}")
#    o 3o campo mostra a forma crua, com o Ġ do espaço

# 3) a métrica que interessa: quantos tokens por palavra?
palavras = len(texto.split())
print(f"{len(ids)} tokens para {palavras} palavras = {len(ids)/palavras:.2f} tokens/palavra")

A saída da linha 3 é o momento em que a ficha cai. Em inglês, esse número fica perto de 1,3. Em português, perto de 2,4. Ou seja: o mesmo texto custa quase o dobro de tokens em português — dobro de memória, dobro de tempo, metade do contexto útil, e (em APIs pagas) o dobro do preço. Não é o modelo que "não gosta" de português; é o merges.txt que nunca viu português suficiente para criar peças boas.

7 No laboratório

cd GPT-2
.venv\Scripts\python.exe lab\01_tokenizer.py

Esse script faz quatro coisas. Observe cada uma:

  1. Decompõe uma frase em português token a token, mostrando ID, texto e a forma crua com Ġ. Procure onde as palavras se quebram — quase sempre no sufixo.
  2. Compara a mesma ideia em inglês e português e imprime a razão tokens/palavra dos dois. Essa é a medida do viés do corpus.
  3. Passa o mesmo texto pelos três tokenizadores (GPT-1, GPT-2, RoBERTa) e mostra que os IDs não têm nenhuma relação entre si. Isso prova, na prática, que não dá para misturar tokenizador de um modelo com pesos de outro.
  4. Testa casos patológicos: emoji, número grande, nome próprio, espaço duplo. O resultado mais revelador é o dos números: " 2024" custa 1 token (era um ano frequente na internet de 2018), mas " 1234567" vira 3 tokens, quebrados em 123+45+67 — pedaços sem nenhum significado aritmético. O modelo nunca enxerga o número inteiro. É literalmente por isso que modelos dessa geração erravam contas simples: eles não fazem aritmética sobre números, e sim sobre fragmentos arbitrários de dígitos escolhidos por frequência de texto.
O que você deve conseguir explicar depois de rodar

Por que "Rafhael" custa mais tokens que "the"; por que Ġcasa e casa são coisas diferentes; e por que um modelo que enxerga 1+000 em vez de 1000 tem dificuldade estrutural com contas.

Exercício 1 — preveja antes de rodar

  1. Quantos tokens você acha que o GPT-2 gasta para "antidisestablishmentarianism" (28 letras, inglês)? E para "pneumoultramicroscopicossilicovulcanoconiótico" (46 letras, português)? Escreva os dois palpites, depois rode. A diferença vai ser maior do que você espera.
  2. Tokenize "casa", " casa" e "Casa". Quantos IDs distintos aparecem? Por quê?
  3. Encontre uma palavra em inglês de 6+ letras que o GPT-2 represente com um único token. Dica: pense em palavras muito comuns na internet de 2018.
  4. Difícil: o token de ID 50256 é o <|endoftext|>. Rode tok.decode([50256]). Agora explique por que ele é o último ID, e não o primeiro.
Referências

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.

  1. Gage, P. (1994). A New Algorithm for Data Compression. The C Users Journal, 12(2), 23–38.
  2. Sennrich, R., Haddow, B. e Birch, A. (2016). Neural Machine Translation of Rare Words with Subword Units. ACL 2016. arXiv:1508.07909
  3. Radford, A., Wu, J., Child, R., Luan, D., Amodei, D. e Sutskever, I. (2019). Language Models are Unsupervised Multitask Learners. Relatório técnico, OpenAI. cdn.openai.com

Agora o texto é uma lista de inteiros. Mas um inteiro não tem significado — o ID 41 não é "mais" nem "menos" que o 40.
Na apostila 02 esses inteiros viram vetores, e é aí que o significado nasce.