Auditer un tokenizer inconnu
à consulter, pas à lire d'affilée. Un relevé de la littérature, pas une mesure d'ici.
Quinze tests, quinze minutes, dix lignes de Python — et il faut un modèle ouvert : un tokenizer fermé ne s'audite pas.
1. La checklist, dans l'ordre
A · Identité et intégrité
| le geste | ce qu'on cherche |
|---|---|
| model.type, byte_fallback | BPE au niveau octet partout : sur dix familles de pointe, Unigram 0 sur 10. Gemma, réputé unigram, déclare BPE. |
| len(tokenizer) == vocab_size | le test le plus rentable : des lignes d'embedding sans jeton derrière. |
| compter les réservés, par motif exact | Llama 3.1 : 248. Gemma 3 : 6 242 <unusedN>, Gemma 4 : 6 227. Un grep large sur « unused » gonfle le chiffre. |
| decode(encode(x)) == x | sur votre corpus, emoji compris. Un BPE octet ne perd rien — mais le modèle peut générer des octets non UTF-8. |
Les lignes fantômes
Qwen3 déclare 151 936 pour 151 669 identifiants : 267 lignes d'embedding sans aucun jeton. Qwen3.5 : 248 320 pour 248 070, soit 250.
fichiers publiés — Qwen3-8B/config.json
B · Comportement — six sondes de dix secondes
| le geste | ce qu'on cherche |
|---|---|
| encode("1234567") | paquets de trois (GPT-4/4o/5, Llama 3.1/4, DeepSeek, Kimi K3) ou chiffre par chiffre (Qwen3/3.5, Gemma 3/4, Mistral Nemo). |
| encode("bonjour") contre encode(" bonjour") | l'espace change tout : chez o200k (GPT-4o), un jeton contre deux. D'où les pathologies de niveau caractère. |
| lire la regex de pré-découpe | chiffres par paquets de trois (\p{N}{1,3}) ou un par un (\p{N}) ? \p{M} — les accents posés sur une lettre — dans la classe des lettres ? une clause pour le chinois et le thaï ? Gemma 3/4 n'en a quasiment pas. |
| du code ? encoder 4, 8, … 32 espaces | pas « quel maximum ? » mais « la grille colle-t-elle à mon pas d'indentation ? ». |
| encoder un texte contenant <|im_end|> | surface d'attaque, pas curiosité. |
| fréquence de chaque jeton sur le corpus réel | aucun jeton à zéro : un jeton jamais vu garde une ligne d'embedding tirée au hasard. |
Deux défauts opposés
tiktoken lève une erreur sur un jeton spécial rencontré dans du texte : sa docstring dit qu'ils « peuvent servir à tromper un modèle ». HuggingFace fait l'inverse : split_special_tokens vaut False, donc la balise tapée devient une vraie balise. Le gabarit fait partie du tokenizer, et il casse en silence.
C · Mesure — trois chiffres, jamais un seul
| le chiffre | comment, et sur quoi |
|---|---|
| fertilité | combien de jetons coûte le même texte, sur texte parallèle (FLORES) et sur vos langues — par phrase, pas par mot : en chinois, le mot n'a pas de sens. Face à l'anglais : français ×1,34 à ×1,68 ; khmer ×8,86 et birman ×11,25 avec Llama 3.1. |
| octets par jeton, bit par octet | repère : 4,57 à 4,77 caractères par jeton en anglais, tous tokenizers modernes confondus. |
| métriques intrinsèques | corrélation à la perplexité : Rényi −0,80, entropie trigramme −0,66, taux de compression — celui que tout le monde regarde — seulement −0,51. Sur 46 configurations : une indication. |
Les mêmes gestes faits ici, clavier de Qwen2.5 : 3,77 caractères par jeton en anglais, 3,32 en français, ×1,13 — loin sous ces repères, sur un autre corpus que FLORES. Ce que coûte un caractère.
Jamais la perplexité par jeton
Comprimer davantage rend chaque jeton plus dur à prédire : un gros vocabulaire dégrade la perplexité tout en améliorant le modèle. La monnaie commune est BPB = (perte par jeton / ln 2) × (jetons / octets).
TokEval — arxiv.org/2608.18062
D · Économie
| le calcul | le seuil |
|---|---|
| part des poids : V × d / total | au-dessus de ~20 %, trop gros. |
| tenseur de logits : B × T × V | section 2. |
2. Le mur de mémoire, derrière le vocabulaire
Ce qui fait sauter un entraînement sur petit GPU, ce n'est pas les paramètres, c'est le tenseur de logits. Cut Cross-Entropy ne le matérialise jamais : sur Gemma 2 2B, la perte passe de 24 Go à 1 Mo, alors qu'elle pèse jusqu'à 90 % de l'empreinte d'un entraînement moderne.
Sans elle, 262 k est inentraînable sur 16 Go ; avec elle, la question redevient une affaire de paramètres.
Cut Cross-Entropy — arxiv.org/2411.09009
3. Ce qui n'est pas tranché
Zéro produit, six familles
En septembre 2026, aucun modèle sans tokenizer n'est déployé en production : toutes les API frontières tournent en BPE octet. Même constat pour SuperBPE, les têtes de diffusion, la compression optique, la 3D par face, le décodage spéculatif.
affirmation négative : l'appui est l'absence d'annonce, pas une source.
Ce que BLT démontre recherche
Au niveau caractère, l'écart est franc : 54,1 contre 27,5 sur le banc CUTE, et 64,3 contre 56,9 en robustesse au bruit. C'est son vrai argument.
Sur le multilingue — son argument le plus vendu — BLT est le pire des quatre approches testées. Onze langues d'Asie du Sud-Est, vocabulaire 90 k. Parité face à l'anglais, puis Gini (0 = toutes au même prix) : BLT 2,87 et 0,212 ; BPE ordinaire 1,73 et 0,220 ; BPE équitable 1,24 et 0,028. Le découpage par entropie d'octets pénalise les écritures rares.
Les deux lois d'échelle se contredisent
L'une dit que le vocabulaire doit grandir avec le modèle : V ∝ N_nv^0,83 (les poids hors embedding), donc 218 k à 70B. L'autre, sur 988 modèles de 50 M à 7B, dit l'inverse : la compression optimale décroît avec le budget.
Ce que ni l'une ni l'autre ne dit : elles sont ajustées dans la même fenêtre, ≤ 7B — l'extrapolation à 70B est donc aussi fragile que la contradiction. Sous 10²¹ FLOPs, suivez la première ; au-delà, tranchez sur le matériel.
L'erreur n'est pas symétrique
La qualité varie de moins de 2 % en bits par octet de 8 k à 1 M : le choix est économique, pas qualitatif. Et irréversible — greffer un autre tokenizer ne garde que ~78,5 % des identifiants à leur place.
Se tromper vers le petit coûte de la longueur de séquence — récupérable en réentraînant le seul tokenizer. Vers le gros, ce sont des paramètres gelés et 2 GiO d'activations de logits par séquence : non récupérable.
deux préprints non répliqués — arxiv.org/2608.11361 · arxiv.org/2608.00582
4. La recette, sur deux sessions GPU
- BPE au niveau octet.
- V × d sous ~20 % des poids ; embeddings liés au-dessus de ~10 % de part, déliés sous ~3 %.
- Chiffres un par un si le modèle doit calculer ; \p{M} dans la classe des lettres si le corpus n'est pas latin.
- Tokenizer entraîné sur le corpus lui-même, len(tokenizer) == vocab_size vérifié avant le premier run.
- Cut Cross-Entropy dès le premier run.