LLM e tokenizzazione

banner

Ora che il focus del costo di utilizzo dei modelli è sempre più stringente sui token, è importante capire cosa sono, come vengono calcolati e perché sono così cruciali per l’utilizzo efficiente dei modelli di linguaggio.

In breve

Non c’è nessuna “stima statistica” né una formula segreta: è sempre un conteggio diretto sulla tokenizzazione usata dal modello, che dipende dal testo, dalla lingua, dai simboli usati, e da come il tokenizer del modello suddivide il testo in token, e quindi dal modello stesso.

In dettaglio

Cosa sono i token

Un token è un’unità di testo che il modello di linguaggio utilizza per comprendere e generare testo. Può essere una parola intera, una parte di parola, o anche un singolo carattere, a seconda del tokenizer specifico del modello. Ad esempio, la parola “tokenizzazione” potrebbe essere suddivisa in token come “token”, “izz”, “azione” a seconda del modello e del suo vocabolario.

Il concetto di tokenizzazione è più “vecchio” di quanto si possa pensare: viene usato storicamente in tutte le attività di NLP (Natural Language Processing) per decenni per dare una forma anche alla sintassi più complessa, ma con l’avvento dei modelli di linguaggio di grandi dimensioni, è diventato un elemento centrale per comprendere come funzionano questi modelli e come vengono calcolati i costi associati al loro utilizzo.

Ad esempio, in spaCy, una delle più popolari librerie di NLP, la tokenizzazione è basata su regole linguistiche e può suddividere il testo in token in modi diversi a seconda della lingua e del contesto.

Si tratta infatti dello step primario per qualsiasi attività di NLP, perché i modelli di linguaggio non lavorano direttamente con il testo, ma con queste unità discrete (token) che rappresentano il testo in modo più gestibile per il modello.

Come descritto anche nella documentazione, spaCy utilizza un approccio basato su regole per la tokenizzazione, che include:

  • Suddivisione del testo in token basata su spazi bianchi, punteggiatura e altre regole linguistiche.
  • Gestione di casi speciali come contrazioni, abbreviazioni e simboli.
  • Personalizzazione della tokenizzazione attraverso l’uso di regole specifiche per determinate lingue o contesti.

Il processo segue più o meno questo flusso:

  • Il testo “grezzo” viene separato in base agli spazi bianchi, creando una serie di sottostringhe.
  • Il tokenizer esamina ciascuna sottostringa da sinistra a destra, applicando regole per identificare se la sottostringa corrisponde a un’eccezione di tokenizzazione (ad esempio, “don’t” dovrebbe essere diviso in “do” e “n’t”, mentre “U.K.” dovrebbe rimanere un token unico).
  • Se viene identificata un’eccezione o se è possibile suddividere un prefisso, suffisso o infisso (ad esempio, punteggiatura come virgole, punti, trattini o virgolette), il tokenizer applica la regola e continua a esaminare le nuove sottostringhe risultanti, consentendo di gestire token complessi e nidificati come combinazioni di abbreviazioni e più segni di punteggiatura.

Mentre la punteggiatura ha delle regole che sono tutto sommato simili per le diverse lingue con cui avremo a che fare, il funzionamento del tokenizer dipende da una serie di eccezioni basate sulle singole lingue. Dal momento che la tokenizzazione dipende dal modello e dal suo vocabolario, il numero di token per una data stringa può variare notevolmente a seconda del modello utilizzato. Ad esempio, un modello per la lingua inglese potrebbe tokenizzare una frase in modo diverso rispetto a un modello per la lingua italiana, a causa delle differenze linguistiche e dei simboli utilizzati.

Un esempio pratico è il seguente:

import spacy

nlp = spacy.load("it_core_web_sm")
doc = nlp("Quanti pianeti ci sono nel sistema solare?")
for token in doc:
    print(token.text)

In questo esempio, la frase “Quanti pianeti ci sono nel sistema solare?” viene tokenizzata in unità discrete (token) che il modello di linguaggio può comprendere e utilizzare per generare risposte o eseguire altre operazioni di NLP. L’output sarà una serie di token come “Quanti”, “pianeti”, “ci”, “sono”, “nel”, “sistema”, “solare”, e “?”.

Come cambia con i LLM

Il numero di token non è stimato a occhio: viene calcolato in modo deterministico dal tokenizer del modello, sia per l’input che per l’output. Questo vuol dire che, rispetto a quanto previsto da librerie come spaCy, il numero di token per una data stringa può variare notevolmente a seconda del modello utilizzato, perché ogni modello ha un tokenizer specifico che suddivide il testo in token in modo diverso.

Come vengono calcolati i token di input

Per l’input (prompt, messaggi, istruzioni, tool, immagini ecc.) il flusso è:

  • Prendi tutto quello che stai inviando al modello (testo lineare o strutturato, eventuali contenuti multimodali convertiti in una rappresentazione interna).
  • Usa la libreria/API lo passa al tokenizer del modello, che spezza il contenuto in una sequenza di token secondo il suo vocabolario e ne restituisce il numero.

In questo caso, entra in gioco il Byte Pair Encoding (BPE) o un altro algoritmo di tokenizzazione specifico del modello. Il tokenizer prende il testo e lo suddivide in unità più piccole (token) che il modello può comprendere. Questi token possono essere parole intere, parti di parole o anche caratteri singoli, a seconda della complessità del testo e del vocabolario del modello.

Non c’è una formula per il calcolo dei token o una regola tipo “1 parola ≈ 1 token”, perché il conteggio reale è sempre fatto dal tokenizer, che dipende dal modello e dalle librerie utilizzate.

Ad esempio, Claude ad oggi mette a disposizione una funzione che, durante l’invio della richiesta, mostra in tempo reale il conteggio dei token di input e output, basato sulla tokenizzazione effettiva del modello.

Un esempio di utilizzo è il seguente:

from anthropic import Anthropic

client = Anthropic()
response = client.completions.create(
    model="claude-2",
    max_tokens=100,
    messages=[
        {"role": "user", "content": "Quanti pianeti ci sono nel sistema solare?"}
    ],
)
print(response.usage.prompt_tokens)  # Numero di token di input
print(response.usage.completion_tokens)  # Numero di token di output
print(response.usage.total_tokens)  # Totale token (input + output)

In alternativa, è possibile usare il metodo count_tokens() del client per ottenere il numero di token di una stringa o di un messaggio, senza dover inviare una richiesta completa al modello:

from anthropic import Anthropic
client = Anthropic()
input_text = "Ciao, come stai?"
client = anthropic.Anthropic()

response = client.messages.count_tokens(
    model="claude-opus-4-7",
    system="Sei un esperto di astronomia.",
    messages=[{"role": "user", "content": "Quanti pianeti ci sono nel sistema solare?"}],
)

print(response.json())

Parlando invece di OpenAI e dei suoi modelli, è possibile usare il tokenizer online per calcolare il numero di token di una stringa o di un messaggio, senza dover inviare una richiesta completa al modello:

Tokenizer online di OpenAI

In questo caso, una limitazione che lo stesso il team di Anthropic cita nella documentazione è che per un modello di generazione di testo, il prompt e l’output generato combinati devono essere al massimo pari alla lunghezza massima del contesto del modello. Ciò significa che se il modello ha una lunghezza massima del contesto di 4096 token, la somma dei token del prompt e dei token dell’output generato non può superare questo limite.

Questo non vale per i modelli di embedding o per i modelli di classificazione, che non generano testo ma restituiscono invece un vettore di embedding o una classificazione. In questi casi, il numero di token di input è l’unico fattore da considerare, e il numero di token di output è generalmente fisso (ad esempio, 768 token per un embedding).

Libreria tiktoken

Uno spunto di riflessione interessante è la libreria tiktoken, che è una libreria open source sviluppata da OpenAI per la tokenizzazione dei modelli di linguaggio. Questa libreria fornisce un modo efficiente per calcolare il numero di token in un testo, utilizzando lo stesso algoritmo di tokenizzazione dei modelli di OpenAI.

Si basa sul concetto di Byte Pair Encoding (BPE), che è un algoritmo di compressione dei dati che suddivide il testo in unità più piccole (token) in modo da ridurre la lunghezza complessiva del testo.

Quando leggiamo la parola “compressione”, la cosa che dovrebbe venirci in mente è che l’algoritmo cerca di rappresentare il testo in modo più compatto, riducendo il numero di token necessari per rappresentare la stessa informazione e quindi “perdendo” parte dell’informazione originale. In realtà, il BPE è progettato per essere reversibile e lossless, il che significa che non perde alcuna informazione durante la tokenizzazione.

Alcune caratteristiche chiave del Byte Pair Encoding (BPE) includono:

  • Lavora anche su testo che non è presente nel vocabolario di addestramento del modello, consentendo una maggiore flessibilità nella tokenizzazione.
  • Comprime il testo, quindi la sequenza di token è più corta rispetto ai byte corrispondenti al testo originale. In media, ogni token corrisponde a circa 4 byte.
  • Aiuta i modelli a vedere subword comuni, migliorando la generalizzazione e la comprensione della grammatica. Ad esempio, “ing” è un subword comune in inglese, quindi BPE potrebbe suddividere “encoding” in token come “encod” e “ing”, consentendo al modello di riconoscere “ing” in diversi contesti.

Come vengono calcolati i token di output

Il modello genera internamente una sequenza di token, uno dopo l’altro, fino al valore (se specificato) max_tokens, oppure a un token di stop, o a un’altra condizione di arresto. Questi token vengono poi convertiti in testo (stringa) che troverai nella risposta. Il numero di token di output è la lunghezza della sequenza generata.

In questo caso, vuol dire che se il modello ha generato 50 token, allora completion_tokens sarà 50, indipendentemente da come quei token si traducono in parole o frasi in linguaggio naturale. Anche qui il conteggio non è inferito dal numero di caratteri: deriva dalla sequenza di token che il modello ha effettivamente prodotto.

In alcune versioni recenti dell’API vedrai valori come cached_tokens, reasoning_tokens, e via dicendo: sono sotto categorie di token che il modello usa (per input riusato, passi di ragionamento interni, ecc.), ma il conteggio base è sempre “quanti token sono stati effettivamente processati/generati”.

Conclusioni

In sintesi, il conteggio dei token è un aspetto fondamentale per comprendere come funzionano i modelli di linguaggio e come vengono calcolati i costi associati al loro utilizzo. Non c’è una formula magica o una stima approssimativa: è sempre un conteggio diretto sulla tokenizzazione usata dal modello, che dipende dal testo, dalla lingua, dai simboli usati, e da come il tokenizer del modello suddivide il testo in token. Diffidate di chi vi dice “1 parola è circa 1 token” o “100 caratteri sono circa 20 token”: è sempre meglio fare un conteggio diretto con il tokenizer del modello che state usando, per avere una stima precisa dei token di input e output e quindi dei costi associati al loro utilizzo.

Risorse utili

Bio autore

Serena Sensini - Ciao! Mi chiamo Serena Sensini e sono la creatrice di @ TheRedCode.

TheRedCode.it - Il mondo #tech a piccoli #bit

Partners

Community, aziende e persone che supportano attivamente il blog

Logo di Welyk
Logo di GrUSP
Logo di Python Milano
Logo di Schrodinger Hat
Logo di Python Biella Group
Logo di Fuzzy Brains
Logo di Django Girls Italy
Logo di Improove
Logo de Il Libro Open Source
Logo di NgRome
Logo de La Locanda del Tech
Logo di Tomorrow Devs
Logo di DevDojo

Vuoi diventare tech content creator? 🖊️

Se ti va di raccontare la tua esperienza nel mondo tech, questo è il posto giusto.

Cerchiamo voci autentiche, esempi pratici e punti di vista utili per chi legge.

Scrivici a collaborazioni[at]theredcode.it con una proposta: idea, taglio del contenuto e una breve presentazione. Non vediamo l'ora di leggere la tua esperienza!

Invia la tua idea