Pular para o conteúdo
mnzes

Como o texto vira número dentro do modelo?

PorDiógenes MenezesAprendendo IA em público

12 min de leitura

O texto vira número em dois passos, e nenhum dos dois é rede neural. Primeiro um tokenizador quebra a frase em pedaços de um vocabulário fechado e devolve a posição de cada pedaço nesse vocabulário, uma lista de inteiros. Depois cada inteiro indexa uma linha de uma matriz grande, e essa linha é o vetor que entra na primeira camada. O mesmo texto sai sempre nos mesmos números.

Este é o primeiro trecho do percurso descrito em como um LLM funciona por dentro, e o único em que ainda existe texto. Depois da matriz de embedding não há mais palavra, letra nem espaço: há vetor. O que a matriz faz com o id, e o que acontece na volta, está em embedding e unembedding.

Se a sua pergunta ainda é o que conta como um token, comece por lá; e a comparação entre os tokenizadores que existem hoje, com o que cada um cobra por português, está em tokenizers.

O caminho, com valores reais

textoO modelo lê números.subtokens"O" " modelo" " l""ê" " números" "."6 tokensids46 37042 3265615 70526 13posição no vocabuláriovetores6 linhas da matriz Ed números em cadatokenizadorvocabulárioconsulta
Figura 1Nenhuma dessas etapas é rede neural. As três primeiras são tabela de consulta, e é por isso que o mesmo texto sai sempre na mesma lista de números.

Os números da figura são reais, medidos com o cl100k_base, um vocabulário público de 100.256 entradas. A frase “O modelo lê números.” vira seis pedaços, e vale olhar quais:

"O" " modelo" " l" "ê" " números" "."

Duas coisas saltam. A primeira é que o espaço vem colado no pedaço seguinte: " modelo" com espaço é uma entrada diferente de "modelo" sem espaço, e as duas ocupam posições distintas do vocabulário. A segunda é que números, com oito letras e um acento, é um token único, enquanto , com duas letras, vira dois: " l" e "ê". Tamanho não decide nada. Frequência no corpus de treino decide.

Os ids correspondentes são 46, 37042, 326, 5615, 70526 e 13. São endereços, não medidas: o id 46 não é “menor” que o 70526 em nenhum sentido útil, e dois ids vizinhos costumam ser pedaços sem relação nenhuma entre si. Todo o significado mora na linha da matriz que cada id endereça. Um id baixo diz uma coisa, e só uma: aquele pedaço foi fundido cedo, ou seja, era frequente no corpus de onde o vocabulário saiu.

Antes do BPE existe um pré-tokenizador

A etapa mais esquecida do caminho é a primeira. Antes de qualquer fusão, uma expressão regular corta o texto em pedaços que o algoritmo de fusão nunca poderá atravessar. Ela decide, entre outras coisas, que o espaço à esquerda faz parte da palavra, que sinal de pontuação se agrupa com sinal de pontuação, e que sequências de dígito são cortadas em grupos de no máximo três.

Esse último detalhe tem consequência direta. 1234567 não chega ao modelo como sete dígitos, e nem como um número: chega como 123, 456 e 7. O corte é da esquerda para a direita, que é exatamente a direção contrária à que um humano agrupa milhar. O modelo recebe uma decomposição que não corresponde ao valor posicional de nada.

Vale registrar em que unidade tudo isso acontece. Os tokenizadores modernos operam sobre bytes, não sobre caracteres. Uma letra acentuada ocupa dois bytes em UTF-8, um emoji ocupa quatro, e o algoritmo de fusão trabalha nessa camada. É por isso que ê pode acabar como um símbolo próprio enquanto a sílaba inteira não tem entrada no vocabulário: a fusão que juntou os dois bytes do ê era frequente o bastante, e a que juntaria l com ê não era.

O pré-tokenizador também explica por que dois prompts visualmente idênticos podem gerar contagens diferentes. Uma quebra de linha a mais, dois espaços onde havia um, um caractere invisível colado de um editor: tudo isso muda o corte antes de qualquer fusão acontecer, e portanto muda a lista de ids.

Como o vocabulário é aprendido

b a i x a rb a i x arb a ix arba ix ar+ ar+ ix+ bacomeça em bytes soltostrês fusões na tabela
Figura 2O vocabulário não é escolhido, é aprendido: parte de bytes soltos e junta o par mais frequente do corpus até chegar ao tamanho pedido.

O vocabulário é construído uma vez, antes do treino, e depois nunca muda. O algoritmo mais usado é o byte pair encoding, e ele é mais simples do que o nome sugere: comece com o texto quebrado em bytes, conte todos os pares adjacentes, funda o par mais frequente em um símbolo novo, anote essa fusão numa lista ordenada e repita até a lista ter o tamanho que você pediu.

A ideia veio da tradução automática, onde palavra rara quebrava o modelo. Sennrich e colegas propuseram representar palavra rara como sequência de unidades menores em vez de recorrer a um símbolo genérico de “desconhecido”1. Como no pior caso sobram os bytes, nada fica de fora, e o símbolo de desconhecido deixa de existir.

Na hora de tokenizar, o processo é o mesmo ao contrário: aplique as fusões aprendidas, na ordem em que foram aprendidas. Por isso o resultado é determinístico e por isso a lista de fusões é tão importante quanto a lista de símbolos — sem ela o vocabulário é inútil.

Existe uma família alternativa. Kudo propôs treinar um modelo de linguagem unigrama sobre subpalavras e escolher a segmentação por probabilidade, o que abre espaço para amostrar segmentações diferentes da mesma palavra durante o treino como forma de regularização, com ganho consistente em cenário de poucos dados e fora do domínio2. O SentencePiece, do mesmo autor com Richardson, empacotou isso num tokenizador que trabalha direto sobre o texto cru, sem depender de separação por espaço, o que era condição para treinar em línguas que não separam palavras com espaço3.

O tamanho do vocabulário

50 mil entradasmatriz E: 206 M pesosmesmo texto, pt: 17.617mesmo texto, en: 11.390200 mil entradasmatriz E: 819 M pesosmesmo texto, pt: 11.646mesmo texto, en: 10.314matriz calculada com d = 4.096; contagem medida nos três artigos-pilar deste site
Figura 3Dobrar o vocabulário corta um terço dos tokens em português e quase nada em inglês, e custa 613 milhões de pesos a mais na matriz de embedding.

A escolha do tamanho é uma troca, e dá para medir os dois lados dela.

Do lado do benefício, tokenizei os três artigos-pilar deste site, nas versões em português e em inglês, com três vocabulários públicos de 50.257, 100.256 e 199.998 entradas. Os mesmos textos em português somam 17.617, 13.359 e 11.646 tokens; em inglês, 11.390, 10.400 e 10.314. Quadruplicar o vocabulário cortou 34% dos tokens em português e 9% em inglês.

A razão entre os dois idiomas conta a mesma história por outro ângulo: com o vocabulário de 50 mil, o português custa 55% a mais que o inglês para dizer a mesma coisa; com o de 200 mil, 13% a mais. O vocabulário maior não ficou melhor em geral — ele ficou melhor para quem estava mal servido.

Do lado do custo, a matriz de embedding tem uma linha por entrada do vocabulário. Com vetores de 4.096 números, os três tamanhos dão 206, 411 e 819 milhões de pesos. Num modelo de 70 bilhões de parâmetros isso é ruído; num modelo de 1 bilhão, o vocabulário passa a ser uma fatia grande do orçamento.

Foi mais ou menos essa conta que Tao e colegas formalizaram em 2024. Treinando modelos de 33 milhões a 3 bilhões de parâmetros com vocabulários variados, eles concluíram que o tamanho ótimo cresce com o orçamento de computação e que a maioria dos modelos usa vocabulário pequeno demais: pela previsão deles, o Llama2-70B deveria ter pelo menos 216 mil entradas em vez de 32 mil. Na verificação empírica, subir de 32 mil para 43 mil melhorou o ARC-Challenge de 29,1 para 32,0 com o mesmo orçamento de FLOPs4.

Onde isso falha

Contar letra fica impossível. O modelo nunca recebe as letras separadas. Em cl100k_base, carrossel chega como car, ross e el, com um erre no fim de um pedaço e outro no começo do seguinte. Perguntar quantos erres tem é pedir a inspeção de algo que não existe na entrada, e o que sai é um padrão memorizado sobre a palavra, não uma contagem.

A aritmética herda o corte. Singh e Strouse investigaram isso diretamente e mostraram que a escolha de tokenização numérica muda o desempenho: separar os números com vírgula no momento da consulta, o que força o agrupamento da direita para a esquerda, melhorou bastante o resultado em tarefas aritméticas. Eles também notaram que os erros seguem padrões estereotipados, sinal de que a conta que o modelo faz é sistemática e não aproximada, e que a diferença entre as duas direções diminui conforme o modelo cresce5.

O custo por idioma é desigual, e não é pouco. Petrov e colegas mediram o mesmo texto traduzido para vários idiomas e encontraram diferenças de até 15 vezes no número de tokens, inclusive em tokenizadores treinados de propósito para suporte multilíngue; modelos de caractere e de byte ainda mostravam mais de 4 vezes de diferença em alguns pares6. Como a cobrança é por token, isso vira preço: Ahia e colegas analisaram 22 idiomas tipologicamente diversos numa API comercial e mostraram que falantes de boa parte dos idiomas suportados pagam mais e recebem resultado pior, e que costumam vir justamente das regiões onde a API já era menos acessível7.

A desigualdade também aparece palavra a palavra, e é fácil de conferir. Ainda no cl100k_base, correspondence é um token só e correspondência são dois; internationalization são dois e internacionalização são três. Não é o comprimento: internacionalização tem uma letra a menos que o cognato em inglês. É que a forma inglesa apareceu vezes suficientes no corpus para virar entrada do vocabulário, e a portuguesa não. Cada palavra dessas cobra 50% a mais, toda vez que aparece, para sempre.

Tokenizador ruim custa qualidade, não só dinheiro. Rust e colegas isolaram a variável treinando modelos monolíngues sobre os mesmos dados, uns com tokenizador monolíngue e outros com tokenizador multilíngue, e concluíram que o tokenizador dedicado pesa tanto quanto o tamanho do corpus de pré-treino; trocar o tokenizador multilíngue pelo especializado melhorou o desempenho em quase toda tarefa e todo idioma que eles testaram8.

O tokenizador é congelado. Cada id endereça uma linha específica da matriz de embedding, e essa linha foi aprendida durante o pré-treino. Trocar o tokenizador embaralha o endereçamento e invalida a matriz inteira. É a única decisão do projeto de um modelo que não dá para revisar depois sem retreinar, e é por isso que um vocabulário mal dimensionado acompanha o modelo até a aposentadoria dele.

Nada é desconhecido, e isso tem preço. Como sobram os bytes, qualquer entrada é representável. Um emoji raro, um símbolo matemático ou um alfabeto pouco presente no corpus não dão erro: viram uma sequência longa de pedaços de um byte cada. O texto passa, e a fatura também.

O que fazer com isso

  1. Meça em tokens com o tokenizador do modelo que você usa. Contagem de caractere dividida por quatro é um chute que erra por 30% em português.
  2. Não peça contagem de caractere ao modelo. Conte no seu código e mande o número pronto, se ele precisar do número.
  3. Separe os milhares com vírgula ou ponto quando a tarefa for aritmética. É uma mudança de uma linha no prompt e ela ataca a causa.
  4. Orce texto em português com folga sobre a estimativa em inglês. Quanto depende do vocabulário: com os menores, algo perto de 50% a mais; com os maiores, algo perto de 15%.
  5. Refaça a contagem ao trocar de modelo. O mesmo texto muda de tamanho quando o tokenizador muda, e limite de janela e preço são contados na saída dele.
  6. Estabilize o prefixo do prompt até o caractere. Um espaço a mais no começo muda a lista de ids e derruba o cache de prompt.

Footnotes

  1. Sennrich et al. (2016) propuseram representar palavra rara como sequência de unidades menores em vez de recorrer a um símbolo de “desconhecido”, com o vocabulário construído por fusões sucessivas do par de símbolos mais frequente do corpus.

  2. Kudo (2018) tratou a segmentação em subpalavras como ambígua por natureza e propôs amostrar segmentações diferentes durante o treino, com um modelo unigrama escolhendo entre candidatas; o ganho apareceu sobretudo em pouco dado e fora do domínio.

  3. Kudo e Richardson (2018) empacotaram tokenização e destokenização num componente que trabalha direto sobre o texto cru, sem depender de separação por espaço, o que permite treinar o vocabulário em qualquer idioma.

  4. Tao et al. (2024) treinaram modelos de 33M a 3B de parâmetros com vocabulários variados e concluíram que o tamanho ótimo cresce com o orçamento de computação, prevendo pelo menos 216 mil entradas para um modelo do porte do Llama2-70B.

  5. Singh e Strouse (2024) compararam tokenização numérica da esquerda para a direita e da direita para a esquerda em tarefas aritméticas, e encontraram ganho relevante na segunda, com padrões de erro estereotipados nos dois casos.

  6. Petrov et al. (2023) mediram o mesmo texto traduzido para vários idiomas e encontraram diferenças de até 15 vezes no comprimento em tokens, com mais de 4 vezes de diferença mesmo em modelos de caractere e de byte.

  7. Ahia et al. (2023) analisaram custo e utilidade de uma API comercial em 22 idiomas tipologicamente diversos e mostraram que falantes de boa parte deles pagam mais por resultado pior.

  8. Rust et al. (2021) separaram o efeito do tokenizador do efeito do tamanho dos dados e concluíram que um tokenizador monolíngue dedicado pesa tanto quanto o corpus de pré-treino no desempenho final.

Perguntas frequentes

Como o modelo lê texto?
Ele não lê. Um tokenizador quebra o texto em pedaços de um vocabulário fechado e devolve a posição de cada pedaço, uma lista de inteiros. Cada inteiro indexa uma linha de uma matriz grande, e essa linha é o vetor que entra na primeira camada. Letra e palavra não existem mais depois disso.
O que é um id de token?
É a posição de um pedaço de texto dentro do vocabulário do modelo, um número inteiro entre zero e o tamanho do vocabulário. Ele não carrega significado nenhum: ids vizinhos podem ser pedaços sem relação. O significado aparece só na linha da matriz de embedding que aquele id endereça.
Por que uma palavra curta vira dois tokens e uma longa vira um?
Porque o vocabulário guarda o que era frequente no corpus de treino, não o que é curto. Em `cl100k_base`, `números` é um token só e `lê` vira dois, `" l"` e `"ê"`. Frequência no corpus decide, e corpus majoritariamente em inglês deixa acento e flexão do português de fora.
Vocabulário maior é sempre melhor?
Não, é uma troca. Vocabulário maior corta a contagem de tokens do mesmo texto e economiza janela e latência, mas engorda a matriz de embedding em proporção direta. Um trabalho de 2024 argumenta que a maioria dos modelos usa vocabulário pequeno demais para o tamanho que tem.
Dá para trocar o tokenizador de um modelo pronto?
Não sem retreinar. Cada id endereça uma linha específica da matriz de embedding, aprendida durante o pré-treino. Trocar o tokenizador embaralha esse endereçamento e invalida a matriz inteira. É a decisão mais cara de reverter em todo o projeto de um modelo.
Por que o modelo erra ao contar letras?
Porque ele nunca recebeu as letras separadas. Em `cl100k_base`, `carrossel` chega como `car`, `ross` e `el`, com os dois erres em pedaços diferentes. Perguntar quantos erres tem é pedir a inspeção de algo que não está na entrada, e a resposta sai de padrão memorizado.

Referências

  1. Sennrich, R.; Haddow, B.; Birch, A.. Neural Machine Translation of Rare Words with Subword Units (2016)arXiv:1508.07909
  2. Kudo, T.. Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates (2018)arXiv:1804.10959
  3. Kudo, T. e Richardson, J.. SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing (2018)arXiv:1808.06226
  4. Petrov, A. et al.. Language Model Tokenizers Introduce Unfairness Between Languages (2023)arXiv:2305.15425
  5. Ahia, O. et al.. Do All Languages Cost the Same? Tokenization in the Era of Commercial Language Models (2023)arXiv:2305.13707
  6. Rust, P. et al.. How Good is Your Tokenizer? On the Monolingual Performance of Multilingual Language Models (2021)arXiv:2012.15613
  7. Tao, C. et al.. Scaling Laws with Vocabulary: Larger Models Deserve Larger Vocabularies (2024)arXiv:2407.13623
  8. Singh, A. K. e Strouse, D.. Tokenization counts: the impact of tokenization on arithmetic in frontier LLMs (2024)arXiv:2402.14903