Por que 16 mil tokens servem melhor a este corpus que 50 mil, o que ganhamos piorando os números de propósito, e o efeito colateral que não estava no plano.
O BPE do GPT-2 foi treinado no WebText: 40 GB de páginas em inglês. O algoritmo, como vimos no curso anterior, escolhe os pedaços que aparecem muito. Em inglês, tion aparece muito. Em português, ção aparecia pouco naquele corpus — então ele não virou token.
A consequência é cirúrgica. Veja a palavra mais banal do português técnico:
'ação' nosso 1: ['ação']
gpt2 3: ['a', 'ç', 'ão']
'implementação' nosso 3: ['im', 'plement', 'ação']
gpt2 5: ['im', 'plement', 'a', 'ç', 'ão']
Repare no detalhe: o GPT-2 conhece plement. Ele aprendeu a raiz latina pelo inglês implementation. Mas na hora do sufixo, ele quebra o ç sozinho — porque a cedilha, para ele, é um caractere estranho que aparece em textos estranhos.
Cada token custa três coisas: uma posição no contexto (que é finito), uma passada pela atenção (cujo custo é quadrático no número de posições), e um passo de geração (que é serial — 93,3% do tempo de uma requisição, como medimos na apostila 08).
Um texto em português que custa 1,6× mais tokens custa 1,6× mais contexto, ~2,5× mais atenção e 1,6× mais tempo para gerar. O modelo não fica só mais caro: ele fica com menos memória útil, porque o mesmo assunto ocupa mais espaço da janela.
Treinamos um BPE próprio sobre o nosso corpus — português, inglês e código. Quatro escolhas separam o nosso do original, e cada uma tem um custo.
Contraintuitivo: vocabulário menor não deveria significar textos mais longos? Significa, tudo o mais constante. Mas tudo o mais não é constante, porque o nosso corpus é o corpus certo. Um vocabulário de 16 k ajustado ao seu texto bate um de 50 k ajustado a outro texto.
E há um segundo ganho, que só aparece quando você conta os parâmetros. A tabela de embedding tem
| Vocabulário | Tabela | Blocos | Total | % em dicionário |
|---|---|---|---|---|
| 50 257 (GPT-2) | 12,9 M | 3,2 M | 16,1 M | 80,1% |
| 16 392 (nosso) | 4,2 M | 3,2 M | 7,4 M | 56,6% |
Com o vocabulário do GPT-2, 80% do nosso modelo seria dicionário. Quem aprende é a coluna "blocos". Reduzir o vocabulário não economiza só memória — libera orçamento para as camadas, que é onde mora o raciocínio.
Esta é a única mudança que piora uma métrica de propósito. O GPT-2 trata números como qualquer texto: os pedaços saem da frequência.
' 1234567' nosso 8: [' ', '1', '2', '3', '4', '5', '6', '7']
gpt2 3: [' 123', '45', '67']
Três tokens contra oito. O GPT-2 parece mais eficiente — e está, em bytes. Mas olhe o que ele fez: quebrou 1 234 567 em 123, 45, 67. Esses pedaços não têm nada a ver com a estrutura do número. O 45 ali são as centenas de milhar e as dezenas de milhar; noutro número, o mesmo token 45 serão as unidades.
Para somar dois números, é preciso alinhar as casas. Se 45 significa uma coisa num número e outra noutro, o modelo precisa aprender a aritmética separadamente para cada padrão de corte do BPE — e os cortes mudam conforme o número.
Com dígitos separados, cada posição é explícita e sempre igual. É a escolha de Llama, Mistral e Qwen, e ajuda a explicar por que eles fazem contas melhor que o GPT-2 em tamanhos comparáveis.
Pagamos +58,3% de tokens em texto com números por essa consistência. É um custo real, e a apostila 06 explica por que o aceitamos sem ter medido o benefício: porque medir exigiria treinar até o modelo saber somar, o que está muito acima do nosso orçamento atual. É uma aposta fundamentada na literatura, não um resultado nosso.
Código Python é indentação. Adicionamos tokens explícitos para 4, 8, 12 e 16 espaços:
' return' nosso 2: [' ', 'return']
gpt2 4: [' ', ' ', ' ', ' return']
O GPT-2 gasta um token por espaço. Num arquivo Python com aninhamento profundo, isso é metade do arquivo. Pior: o modelo precisa contar espaços para saber o nível de indentação, o que é exatamente o tipo de tarefa em que a atenção é ruim.
tok.pre_tokenizer = pre_tokenizers.Sequence([ pre_tokenizers.Split(pattern=Regex(r"'s|'t|'re|…| ?\p{L}+| ?\p{N}+|…"), behavior="isolated"), pre_tokenizers.Digits(individual_digits=True), # a decisão 2 pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=False), ])
A ordem importa. O Split separa palavras de pontuação; o Digits quebra os números antes do BPE poder fundi-los; o ByteLevel garante que qualquer byte seja representável — nenhum <UNK>, nunca.
| Amostra | Nosso | GPT-2 | Diferença |
|---|---|---|---|
| Português corrente | 11 | 21 | −47,6% |
| Português com acentos | 4 | 15 | −73,3% |
| Código Python | 22 | 25 | −12,0% |
| Inglês | 9 | 9 | 0,0% |
| Com números | 19 | 12 | +58,3% |
A linha dos acentos é a mais reveladora: "ação coração informação atenção" custa 4 tokens para nós e 15 para o GPT-2. Quase quatro vezes. E o inglês empata — com um vocabulário três vezes menor, o que já é um resultado por si.
Tokens são meio, não fim. O que importa é se o modelo prevê melhor. A métrica honesta entre tokenizadores diferentes é bits por caractere — perplexidade é medida por token, e comparar PPL entre vocabulários diferentes não significa nada.
| Modelo | Português | Inglês | Código |
|---|---|---|---|
| nosso mini (7,4 M) | 2,406 | 2,038 | 2,405 |
| gpt2 (124 M) | 3,139 | 1,261 | 1,864 |
Em português, 7,4 milhões de parâmetros gastam menos bits que 124 milhões — 23% menos bits, com 17× menos modelo. Em inglês e código o GPT-2 vai bem melhor, e faz sentido: ele viu ~10 bilhões de tokens contra os nossos 5,7 milhões.
Seria fácil anunciar "nosso modelo de 7 M supera o GPT-2" e parar aí. Seria desonesto. A vantagem existe só onde o tokenizador cobra a diferença, e some onde o GPT-2 tem corpus. O que a tabela prova não é que somos melhores — é que tokenização mal ajustada custa mais que 17× de parâmetros.
Ao medir a velocidade de treino, apareceu algo que não esperávamos: o modelo com vocabulário de 16 k treina a 4 000 tokens/s, contra 857 tok/s da medição anterior com 50 k. Cinco vezes mais rápido.
A causa está na última camada. A projeção para os logits é uma multiplicação
É como um exame de múltipla escolha. Responder uma questão com 50 mil alternativas não é 3× mais lento que uma com 16 mil por causa do raciocínio — o raciocínio é o mesmo. É mais lento porque você precisa ler todas as alternativas antes de escolher. O modelo faz isso a cada token gerado.
Consequência prática: o modelo mini passou de "dias de CPU" para ~4 horas. Uma decisão tomada por qualidade de tokenização acabou sendo, também, a maior otimização de velocidade do projeto. Não previmos isso — e é por isso que medimos em vez de estimar.
Com o texto virando números do jeito certo, a apostila 02 monta o modelo —
nas duas versões, a de 2019 e a de hoje, no mesmo arquivo para a diferença ser legível.