🧙 Maestro Yoda Cap. 19 · Architetture

Puntata 206

Puntata 206 — Tokenizer

Livello: 🧙 Maestro Yoda · Capitolo 19 · Architetture

Prima del modello c’è il tokenizer. Sembra un dettaglio implementativo. È la decisione che condiziona ogni altra metrica.

la parola “antidisestablishmentarianism” spezzettata in 5 token colorati, accanto a “ciao” in 1 token solo. Sotto: “stessa difficoltà concettuale, costi diversi”

Perché esiste un tokenizer

I modelli non leggono testo. Leggono sequenze di interi, ognuno indice in un vocabolario fisso. Il tokenizer è la funzione str → list[int] (e l’inversa detokenizer: list[int] → str).

Le tre opzioni storiche:

  1. Caratteri / byte. Vocabolario piccolissimo (256 byte, ~150k unicode codepoint). Sequenze lunghissime. Modello molto compute-hungry.
  2. Parole intere. Vocabolario enorme (milioni). Out-of-vocabulary (OOV) costanti. Disastro per lingue agglutinanti.
  3. Sotto-parole. Compromesso. Frammenti più grandi di un byte, più piccoli di una parola. Vocabolario 30k–200k. È quello che usiamo da BERT in poi.

I tre algoritmi di sotto-parole che dominano sono BPE, WordPiece, Unigram (SentencePiece). Più una tendenza recente al byte-level.


BPE (Byte Pair Encoding)

Algoritmo nato per compressione (Gage, 1994), riproposto per NLP (Sennrich et al., 2015).

Idea: parti dai singoli caratteri come vocabolario base, poi cerca la coppia di simboli adiacenti più frequente nel corpus e fondila in un nuovo simbolo. Ripeti fino a raggiungere la dimensione di vocabolario desiderata.

Esempio giocattolo (corpus: "low low low lower lower newer newer newer wider"):

  • Iniziale: l, o, w, e, r, n, i, d, </w>
  • Merge più frequente: (l, o) → lo
  • Poi: (lo, w) → low
  • Poi: (e, r) → er

Risultato: parole comuni diventano token interi (low, er), parole rare restano frammentate (unprecedentedun, precedent, ed).

Usato in: GPT-2/3/4/5 (via tiktoken), RoBERTa, BART, e in moltissimi modelli open. Variant byte-level (BBPE): opera sui byte UTF-8, non sui codepoint, eliminando l’OOV per qualsiasi unicode.


WordPiece

Usato da BERT (e originariamente da Google Translate). Simile al BPE ma sceglie i merge massimizzando la verosimiglianza del corpus invece della frequenza.

Differenza pratica:

  • BPE: tokenizationtoken, ization
  • WordPiece: tokenizationtoken, ##ization (il ## segnala “questo è continuazione”)

Computazionalmente più costoso da addestrare, ma le decisioni di merge sono in media migliori. Oggi marginale: la maggior parte dei modelli moderni è BPE byte-level.


Unigram (SentencePiece)

Il default di Google (T5, mT5) e di Mistral, Llama, Gemma. Implementato dalla libreria SentencePiece (Kudo, 2018).

Idea opposta a BPE: parti da un vocabolario iniziale grande (tutti i sub-string frequenti), poi togli progressivamente quelli che impattano meno la likelihood del corpus, fino a target size.

Ulteriore vantaggio: SentencePiece tratta lo spazio come carattere normale (lo rappresenta con ), quindi non assume tokenizzazione preliminare. Funziona uguale per inglese, cinese, giapponese, codice, mix.

Tipico in: T5 family, Llama (1, 2, 3 — Llama-3 è passato a BPE custom), Mistral, Gemma.


Tiktoken e i tokenizer custom degli ultimi modelli

tiktoken è la libreria open di OpenAI (pip install tiktoken), molto veloce, usata da GPT-3.5+ e GPT-4/5. Implementa BPE byte-level con vocabolari diversi per modello:

ModelloEncoderVocab size
GPT-2gpt250.257
GPT-3.5 / GPT-4cl100k_base100.277
GPT-4o / GPT-5o200k_base~200.000
Claude (Anthropic)tokenizer proprietario simil-BPE~100k–200k
Llama 3 / 4BPE custom128.000
Gemma 2 / 3SentencePiece, BPE-style256.000
DeepSeek V3BPE byte-level custom~129.000

La tendenza 2024–2026: vocabolari più grandi (128k → 256k). Trade-off:

  • Pro: meno token per stessa quantità di testo → context più “denso”, inferenza più rapida.
  • Contro: matrice di embedding più grande → più memoria, più parametri “morti” su token rari.

Quanto costa il tokenizer al tuo conto

Pratico, da sapere a memoria:

  • Inglese: ~4 caratteri / token. “Hello, world.” → 3-4 token.
  • Italiano: ~3.5 caratteri / token. Penalità ~15% rispetto all’inglese.
  • Codice: variabile. Linguaggi verbose (Java, C++) sono peggio.
  • Cinese/Giapponese con cl100k: 1 token può essere 1 carattere o anche meno. Penalità anche 3-4× vs inglese.
  • Lingue agglutinanti (finnico, turco): frammentazione pesante, +30%.

Conseguenza diretta: i prezzi “$X per 1M token” non sono equi tra lingue. Chi scrive in finlandese paga di più, a parità di significato.

I tokenizer più moderni (200k+ vocab) hanno mitigato il problema per le lingue maggiori, ma persistono asimmetrie strutturali.


Effetti collaterali del tokenizer

Casi reali che hanno generato bug visibili:

  • Numeri. GPT-3 tokenizzava i numeri in modo strano (es. 53255, 325). Risultato: addizione disastrosa. Llama-3 e GPT-4o hanno specifically scelto di tokenizzare le cifre una per una per migliorare aritmetica.
  • Token “fantasma”. GPT-2/3 avevano token derivati da username di forum (SolidGoldMagikarp è il caso famoso). Prompt che contenevano questi token producevano output completamente fuori distribuzione.
  • Spazi e punteggiatura. Variazioni come " hello" vs "hello" vs "hello " sono token diversi. Prompt sensibili allo spazio iniziale è un gotcha classico.
  • Spelling. Chiedere a un modello “quante ‘r’ ci sono in strawberry?” è andato male per anni proprio per la tokenizzazione. I reasoning model recenti lo fanno bene perché ragionano carattere per carattere.

Multilingua: la frontiera del 2026

Modelli come mT5, NLLB, Aya (Cohere) usano tokenizer specifici multilingua con vocabolari grossi (~256k–512k) addestrati su corpora bilanciati.

Esiste anche la corrente byte-level pura (es. ByT5, MEGABYTE, BLT — Meta 2024): no tokenizer, il modello mangia byte UTF-8. Costo computazionale alto (sequenze ~4× più lunghe), ma:

  • Niente OOV mai, su nessuna scrittura.
  • Robustezza a typo, encoding strani, emoji nuove.
  • Pretraining “language-agnostic” più facile.

Nel 2026 BLT (Byte Latent Transformer) di Meta ha mostrato che si può recuperare il costo via patching dinamico: il modello impara da solo quando aggregare byte in unità più grandi.


Glossario lampo

  • OOV — Out Of Vocabulary. Token mai visti al training.
  • Special tokens — token di servizio: <|endoftext|>, <|im_start|>, <s>, </s>. Non testo, sono “metadata in-band”.
  • Detokenizer — l’inverso: da ID a stringa. Non sempre lossless.
  • Sub-word — pezzo di parola, intermedio tra carattere e parola intera.

TEST hands-on

  1. pip install tiktoken e prova:
    import tiktoken
    enc = tiktoken.get_encoding("o200k_base")
    print(enc.encode("Ciao, sono un assistente AI."))
    print(enc.encode("こんにちは、私はAIアシスタントです。"))
    print(enc.encode("import numpy as np; print(np.pi)"))
    Confronta le lunghezze. Spiega le differenze.
  2. Apri huggingface.co/spaces/Xenova/the-tokenizer-playground (web) e prova lo stesso testo su 5 tokenizer diversi (GPT-2, GPT-4o, Llama-3, Mistral, Gemma). Scrivi cosa cambia.
  3. Calcola il “costo italiano” del tuo prompt: prendi un testo italiano di 1000 caratteri, tokenizzalo con cl100k_base. Quanti token? Confronta col fattore atteso (~3.5 char/token = ~285 token).
  4. Bonus: addestra un BPE da zero su un corpus tuo con sentencepiece o tokenizers (HF). 1M righe sono sufficienti. Confronta il vocabolario emerso con quello di Llama-3.

Take-away

Il tokenizer non è “preprocessing”. È un’architettura nascosta: definisce l’alfabeto su cui ogni operazione successiva opera. Una decisione di tokenizzazione viaggia col modello per sempre, è quasi impossibile cambiarla a posteriori senza re-pretrain. Pensaci quando scegli un modello per una lingua o un dominio.


➡️ Prossima puntata: Positional encoding — RoPE, ALiBi, e perché senza posizioni l’attention è una nuvola.