Quando você manda um prompt pra uma LLM, o modelo não lê o seu texto. Antes de qualquer coisa, um tokenizador quebra a sua frase em pedaços e troca cada pedaço por um número. O que o modelo recebe é isso: uma lista de inteiros. Nunca as letras.

seu prompt "me explica SIMD" tokenizador tokens [1859, 40151, …] modelo o passo que ninguém cronometra

Pra um prompt só, esse passo é instantâneo. Você nem percebe que aconteceu.

Só que você quase nunca tokeniza uma coisa só. Pensa num RAG: você pega os documentos da sua empresa e, antes de indexar, tokeniza cada um. Ou um fine-tune: tokeniza o dataset inteiro antes de treinar. Ou uma avaliação rodando sobre milhões de exemplos. Aí aquele passo invisível vira hora de máquina.

E ninguém cronometra ele. A gente aceita a lentidão da tokenização como se fosse lei da física - “é o preço de trabalhar com LLM em escala”. Não é. Quando eu fui medir, tinha 200x parados ali, esperando alguém olhar.

Esse 200x mora num lugar específico: pipeline que tokeniza em massa e grava os ids no disco. Se o seu caso é o prompt de um usuário em produção, a conta muda. E entre as duas pontas tem o TTFT, o tempo até o primeiro token, onde a tokenização reaparece. A gente chega em todas.

Primeiro: quanto do tempo é tokenização?

Antes de vender solução, vale medir o problema. Porque otimizar o que não pesa é perda de tempo - a gente volta nisso lá no final.

Montei o cenário mais simples possível: 281.664 documentos de texto real, 863 MB do dataset fineweb. Ler do disco, passar pelo tokenizador do Qwen3-8B, e escrever os token ids. Só isso, nenhum modelo rodando no meio.

O prepare.py do nanoGPT faz exatamente isso: tokeniza o corpus uma vez e grava os ids num .bin que o treino lê depois. Tokenizar de novo a cada época seria desperdício.

Depois cronometrei cada etapa. O resultado:

etapa tempo
ler 863 MB do disco 0,8 s
tokenizar 146 s
escrever 760 MB de ids 0,1 s

A tokenização era 99,4% do tempo. Não era um gargalo no meio de outros. Era a pipeline inteira. Ler e escrever quase um gigabyte cada era ruído perto dela.

Isso muda o jogo. Quando um passo come 99% do tempo, otimizar ele deixa de ser refino e vira a prioridade número um.

POR ISSO: matar a tokenização acelera a pipeline inteira

Aqui entra o Gigatoken, um tokenizador que o Marcel Roed escreveu em Rust. A promessa dele é agressiva: até ~1000x mais rápido que o tokenizers da HuggingFace.

Número redondo demais, desconfiei. Então rodei o meu próprio benchmark - mesmo corpus, mesmo tokenizador, na minha máquina (um MacBook M4 Max). Só a tokenização:

ferramenta tempo throughput
HuggingFace tokenizers 146 s 0,01 GB/s
Gigatoken 0,74 s ~1,2 GB/s

Cerca de 200x na minha máquina. Não é o 1000x do README deles, mas o 1000x foi num servidor de 144 núcleos - quanto mais núcleo, mais a diferença abre. No meu laptop, 200x já basta pra doer.

E a pipeline inteira, com I/O e tudo? Caiu de 147 segundos pra menos de 2. 88x mais rápido no total. Dois minutos e meio de espera viraram menos de dois segundos, no mesmo corpus e com o mesmo resultado.

NOTA: o número mais importante dessas tabelas não é o tempo. É que os dois produziram exatamente os mesmos 190.429.497 tokens. Byte por byte. Um é 200x mais rápido fazendo o trabalho idêntico. Isso não é atalho, é engenharia.

Isso não é otimização prematura

Uma objeção apareceu na minha cabeça antes de aparecer na sua: trocar de tokenizador pra raspar segundos não é o retrato da otimização prematura?

Era o que eu pensava, e é por isso que nunca tinha cronometrado esse passo. O tokenizers da HuggingFace ainda ajuda a não olhar: é escrito em Rust, é multithreaded, mantido por gente séria. Qualquer pessoa razoável bate o olho e diz “isso está otimizado”. E ainda tinha 200x na mesa.

Otimização prematura seria eu ter ido mexer no I/O que gastava 0,8 segundo. Atacar o passo que come 99% do tempo é o óbvio, e só parece ousado porque ninguém olhou o relógio antes.

O que existe, e ninguém batizou, é o problema inverso: otimização tardia, quando você aceita um gargalo como intocável sem nunca ter cronometrado ele. “Tokenização é lenta” era isso. Uma parede que todo mundo via, até alguém enxergar uma porta.

“Mas você não escolheu o tokenizador mais lento de propósito?”

Boa pergunta. Se eu testasse só o Qwen, você teria todo o direito de desconfiar. Então testei mais.

Com o GPT-2, o clássico: a HuggingFace levou 129 segundos, o Gigatoken levou 0,22. Os 193.502.159 tokens, de novo, idênticos. Trocar de tokenizador dentro da HuggingFace não fecha o abismo - abre.

Mas o teste que importa mesmo é contra o tiktoken, da OpenAI. É o tokenizador com fama de rápido, o que roda por trás do GPT-4. E ele é rápido: fez o mesmo corpus em 12 segundos, uns 10x mais veloz que a HuggingFace.

Só que o Gigatoken fez em 0,22 segundo. 57x mais rápido que o tokenizador rápido, com resultado idêntico token a token.

E o mais interessante: a lentidão da HuggingFace não é “tokenizar é lento”. O tiktoken faz a mesma tokenização 10x mais rápido só trocando um mecanismo interno. O gargalo tem nome específico, e não é o BPE.

Como? SIMD na parte que ninguém olhava

O nome do gargalo é pretokenization.

Antes do algoritmo de tokenização de verdade (o BPE), tem uma etapa boba: quebrar o texto em pré-pedaços. Quase todo mundo faz isso com expressão regular. E regex varrendo gigabytes de texto é lento - ela lê meio que caractere por caractere, procurando o padrão.

O que o Marcel fez foi trocar o regex por uma implementação que faz a mesma coisa com SIMD. E é aqui que a mágica desmonta.

Imagina que você precisa achar todas as vírgulas num texto de mil páginas. Você pode ler palavra por palavra, procurando. Ou pode abrir o texto em 64 colunas paralelas e perguntar de uma vez: “tem vírgula em algum destes 64 caracteres agora?”. Mesmo resultado. Tempo bem diferente.

SIMD é isso. Single Instruction, Multiple Data. Uma instrução, vários dados. O processador pega 16, 32, 64 bytes e faz a mesma operação em todos num único ciclo.

NOTA: se você quiser puxar esse fio, o SIMD é um eixo de concorrência inteiro que a maioria de nós ignora - não é thread nem processo, é a própria ALU do processador trabalhando em lote. O Paul Butcher dedica um capítulo a isso no Seven Concurrency Models in Seven Weeks (o data parallelism), e o Mitchell Hashimoto tem um ensaio ótimo defendendo que todo dev deveria conhecer. Mas pro que interessa aqui, basta saber: é essa técnica que transforma 146 segundos em 0,7.

A parte honesta: 200x no passo não é 200x na sua vida

Agora o aviso que separa este post de um panfleto de vendas.

Eu disse que a tokenização era 99% do tempo. Repara: 99% do tempo DAQUELE teste. Isso vale pra qualquer pipeline onde nenhum modelo roda junto: você lê texto, tokeniza e grava os ids. Foi só isso que eu medi, e ali a tokenização domina sozinha.

No momento em que um modelo entra na mesma pipeline, a conta muda. Num RAG você tokeniza e depois joga cada pedaço num modelo de embedding - esse forward pass é trabalho de verdade, e a fatia da tokenização encolhe.

E se você tá tokenizando o prompt de um usuário antes de chamar um modelo em produção, o cenário vira de vez. Na geração em si, a tokenização é uma migalha perto do forward pass - 200x ali não muda quase nada, porque ela nunca foi o problema.

A exceção fina fica no tempo até o primeiro token, o TTFT. O próprio autor do Gigatoken mostrou, na discussão no Hacker News, que em modelos menores dá pra raspar 5-10% do TTFT. É real - mas é outro universo perto do 88x do pré-processamento. O tamanho do prêmio depende de onde a tokenização entra na sua conta. Isso é a Lei de Amdahl.

O ganho total de otimizar uma parte é limitado por quanto aquela parte pesava no todo. Otimizar 99% do tempo transforma tudo. Otimizar 1% não muda nada, por mais espetacular que seja o 200x.

Eu aprendi isso na marra. Esse ano fiz a rinha de backend e, pela primeira vez na vida, escrevi um kernel com SIMD na mão - a aplicação fazia muita conta repetitiva, o candidato perfeito. A matemática ficou bem mais rápida. E o ganho final foi decepcionante.

Porque a matemática nunca foi o meu gargalo. O tempo estava em tudo que rodava em volta dela. Eu tinha acelerado brilhantemente um pedaço que não pesava. A mesma lei que faz o Gigatoken parecer mágica me deu uma rasteira na rinha - só que na direção contrária.

O que fica

O trabalho de verdade não é “instalar o Gigatoken”. É medir onde o seu tempo mora antes de sair otimizando.

Se você faz pré-processamento de dados pra LLM e nunca cronometrou a tokenização, cronometre. Se o seu caminho for parecido com o do teste - ler texto, tokenizar, gravar os ids - ela pode estar comendo quase tudo. E se estiver, tem um 200x ali:

pip install gigatoken

A API dele imita a dos tokenizadores que já existem, então a troca em si é uma linha. Quanto desse ganho aparece na sua ponta é outra conversa, e depende de quanto do seu tempo estava na tokenização. Só o cronômetro responde isso.

Mas se a tokenização for migalha na sua pipeline, guarde o Gigatoken pro dia certo e vá caçar o gargalo real. Porque no fim é sempre a mesma história: o tempo mora em algum lugar específico, e quase nunca é onde a gente acha.

Por hoje é só.