Les tokens : le texte vu par le modèle
Découper pour de vrai du chinois, de l'anglais, du chinois classique et du code avec les tokenizers de DeepSeek et d'OpenAI, voir en quoi le modèle transforme réellement le texte, pourquoi cela détermine le prix, et pourquoi le modèle n'arrive pas à compter les caractères.
- Environ 35 minutes
- Niveau : Débutant
- Testé : 2026-09-14 deepseek-flash, tokenizer DeepSeek V4, tiktoken o200k_base
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
Les grands modèles facturent au token, la fenêtre de contexte se compte en tokens, la limite de sortie aussi. À la leçon précédente, vous avez vu prompt_tokens et completion_tokens dans usage. Mais qu'est-ce qu'un token au juste ? Un caractère chinois est-il un token ? Et un mot anglais ?
Ce n'est pas qu'une question de comptabilité. Certains travers étranges des modèles, comme le fait de ne pas savoir compter combien de caractères compte une phrase, viennent aussi des tokens. Dans cette leçon, nous allons découper du texte pour de vrai.
Le modèle ne lit pas du texte, seulement des nombres
Un réseau de neurones ne sait traiter que des nombres. Avant d'entrer dans le modèle, le texte passe donc par un programme appelé tokenizer : il possède un vocabulaire fixe, découpe le texte en fragments présents dans ce vocabulaire, et chaque fragment correspond à un numéro. Le modèle voit une suite de numéros et produit aussi des numéros, que le tokenizer retraduit ensuite en texte.
Ces fragments sont les tokens. Ce peut être un caractère, un mot, une moitié de mot anglais, ou encore un signe de ponctuation ou quelques espaces. Le vocabulaire de DeepSeek compte 129 280 tokens ; celui d'OpenAI pour la génération GPT-4o, o200k_base, en compte 200 019.
Le vocabulaire est établi avant l'entraînement du modèle, par des statistiques sur une grande quantité de texte : les combinaisons de caractères qui apparaissent souvent ensemble sont fusionnées en un token. La leçon 1 du module 09 implémente cet algorithme de zéro. Pour l'instant, retenez la conclusion : les combinaisons courantes deviennent un seul token, les combinaisons rares sont fragmentées.
Découpons pour de vrai
DeepSeek a publié son tokenizer, qu'on peut télécharger et utiliser en local : lien de téléchargement ; une fois l'archive décompressée, on obtient un dossier deepseek_v4_tokenizer. Installez ensuite deux paquets :
uv add tokenizers tiktoken
tokenizers sert à charger les fichiers du tokenizer de DeepSeek ; tiktoken est le tokenizer d'OpenAI. Le script ci-dessous donne les mêmes textes aux deux tokenizers et affiche chaque fragment obtenu :
import tiktoken
from tokenizers import Tokenizer
deepseek = Tokenizer.from_file("deepseek_v4_tokenizer/tokenizer.json")
openai_enc = tiktoken.get_encoding("o200k_base") # GPT-4o 等 OpenAI 模型用的分词器
samples = [
"人工智能正在改变软件开发的方式,越来越多的程序员开始用大模型写代码。",
"Artificial intelligence is changing how software is built, and more programmers now write code with large language models.",
"学而时习之,不亦说乎?有朋自远方来,不亦乐乎?",
"def add(a, b):\n return a + b\n",
"3.1415926535897932384626",
"😀",
]
def deepseek_pieces(text):
ids = deepseek.encode(text, add_special_tokens=False).ids
return [deepseek.decode([i]) for i in ids]
def openai_pieces(text):
pieces = []
for i in openai_enc.encode(text):
raw = openai_enc.decode_single_token_bytes(i)
# 一个汉字或表情可能被拆成几个字节,单独拿出来不是合法的 UTF-8
pieces.append(raw.decode("utf-8", errors="replace"))
return pieces
for text in samples:
ds, oa = deepseek_pieces(text), openai_pieces(text)
print(f"原文({len(text)} 个字符):{text!r}")
print(f" DeepSeek {len(ds):3d} 个词元:{ds}")
print(f" OpenAI {len(oa):3d} 个词元:{oa}")
print()
Résultat :
原文(34 个字符):'人工智能正在改变软件开发的方式,越来越多的程序员开始用大模型写代码。'
DeepSeek 15 个词元:['人工智能', '正在', '改变', '软件开发', '的方式', ',', '越来越多的', '程序员', '开始', '用', '大', '模型', '写', '代码', '。']
OpenAI 21 个词元:['人工', '智能', '正在', '改变', '软件', '开发', '的', '方式', ',', '越来越', '多', '的', '程序', '员', '开始', '用', '大', '模型', '写', '代码', '。']
原文(122 个字符):'Artificial intelligence is changing how software is built, and more programmers now write code with large language models.'
DeepSeek 20 个词元:['Artificial', ' intelligence', ' is', ' changing', ' how', ' software', ' is', ' built', ',', ' and', ' more', ' programmers', ' now', ' write', ' code', ' with', ' large', ' language', ' models', '.']
OpenAI 20 个词元:['Artificial', ' intelligence', ' is', ' changing', ' how', ' software', ' is', ' built', ',', ' and', ' more', ' programmers', ' now', ' write', ' code', ' with', ' large', ' language', ' models', '.']
原文(23 个字符):'学而时习之,不亦说乎?有朋自远方来,不亦乐乎?'
DeepSeek 22 个词元:['学', '而', '时', '习', '之', ',', '不', '亦', '说', '乎', '?', '有', '朋', '自', '远方', '来', ',', '不', '亦', '乐', '乎', '?']
OpenAI 21 个词元:['学', '而', '时', '习', '之', ',不', '亦', '说', '乎', '?', '有', '朋', '自', '远', '方', '来', ',不', '亦', '乐', '乎', '?']
原文(32 个字符):'def add(a, b):\n return a + b\n'
DeepSeek 12 个词元:['def', ' add', '(a', ',', ' b', '):\n', ' ', ' return', ' a', ' +', ' b', '\n']
OpenAI 12 个词元:['def', ' add', '(a', ',', ' b', '):\n', ' ', ' return', ' a', ' +', ' b', '\n']
原文(24 个字符):'3.1415926535897932384626'
DeepSeek 10 个词元:['3', '.', '141', '592', '653', '589', '793', '238', '462', '6']
OpenAI 10 个词元:['3', '.', '141', '592', '653', '589', '793', '238', '462', '6']
原文(1 个字符):'😀'
DeepSeek 2 个词元:['�', '�']
OpenAI 1 个词元:['😀']
Cet écran de sortie contient plusieurs choses qui méritent qu'on s'y attarde.
Chinois moderne : DeepSeek découpe plus grossièrement. « 人工智能 » (intelligence artificielle), « 软件开发 » (développement logiciel), « 越来越多的 » (de plus en plus de) sont chacun un seul token dans le vocabulaire de DeepSeek ; la même phrase lui coûte 15 tokens, contre 21 chez OpenAI. Le vocabulaire est établi à partir du corpus d'entraînement, et celui de DeepSeek contient plus de chinois, donc davantage de groupes de mots chinois ont été fusionnés. Pour un même contenu chinois, moins de tokens signifie moins cher, et plus de contenu tient dans le contexte.
Anglais : les deux sont identiques. Les mots anglais courants font presque tous un seul token, et l'espace accompagne le mot qui le suit : ' intelligence' porte un espace devant. 122 caractères donnent 20 tokens, à peu près un par mot.
Chinois classique : presque un token par caractère. « 学而时习之 » (apprendre et le mettre en pratique en son temps) fait cinq caractères isolés dans les deux vocabulaires. En chinois moderne, les combinaisons courantes sont du type « 学习 » (apprendre) ou « 时间 » (temps) ; des combinaisons comme « 时习 » ou « 不亦 » sont trop rares pour avoir été fusionnées. À 23 caractères égaux, le chinois classique utilise deux fois plus de tokens que le chinois moderne.
Les espaces du code coûtent aussi. Dans une indentation de quatre espaces, trois espaces forment un token à part. Plus un code Python est indenté profondément, plus les espaces occupent de tokens.
Les nombres sont découpés par tranches de trois chiffres. 3.1415926... devient des blocs comme 141, 592, 653. Le modèle ne voit pas un nombre entier, mais plusieurs « fragments de trois chiffres ». C'est l'une des raisons pour lesquelles les modèles se trompent facilement en calcul sur de grands nombres : poser une opération exige d'aligner les chiffres un à un, alors que l'entrée n'est justement pas découpée chiffre par chiffre.
Les emojis peuvent être découpés en octets. DeepSeek coupe 😀 en deux tokens, qui décodés séparément donnent chacun un caractère illisible �, car chaque token n'est qu'une partie des octets de l'emoji ; ce n'est qu'ensemble qu'ils forment le caractère complet.
Estimer le nombre de tokens pour le coût
Les deux méthodes les plus exactes : compter en local avec le tokenizer officiel, ou regarder directement l'usage renvoyé par l'API.
Quand ce n'est pas pratique, on peut estimer grossièrement. La documentation officielle de DeepSeek donne la conversion suivante : 1 caractère anglais vaut environ 0,3 token, 1 caractère chinois environ 0,6 token. J'ai mesuré quelques textes avec l'API, et les résultats sont plus bas que les chiffres officiels :
| Texte | Caractères | Tokens | Tokens par caractère |
|---|---|---|---|
| Chinois moderne (la phrase ci-dessus) | 34 | 15 | 0,44 |
| Anglais (la phrase ci-dessus) | 122 | 20 | 0,16 |
| Chinois classique (la phrase ci-dessus) | 23 | 22 | 0,96 |
| Code Python | 32 | 12 | 0,38 |
| 5 emojis | 5 | 10 | 2,00 |
La mesure se fait ainsi : pour une même question, on fixe max_tokens=1 et on ne regarde que prompt_tokens. On envoie d'abord un message contenant seulement la lettre a et on note son nombre de tokens d'entrée comme référence (le surcoût des balises de format), puis on envoie le texte à mesurer ; la différence des deux, plus un, donne le nombre de tokens du texte lui-même.
def count_tokens(text):
r = client.chat.completions.create(
model="deepseek-flash",
messages=[{"role": "user", "content": text}],
max_tokens=1,
extra_body={"thinking": {"type": "disabled"}},
)
return r.usage.prompt_tokens
base = count_tokens("a") # 格式标记加上字母 a 本身,我测到的是 5
print(count_tokens("学而时习之,不亦说乎?有朋自远方来,不亦乐乎?") - base + 1)
Les chiffres officiels sont prudents, ce qui est parfait pour un budget : on ne le dépasse pas facilement. Mais retenez que le nombre de tokens dépend du contenu : chinois classique, caractères rares, emojis et langues peu courantes sont plus « chers » que le chinois de tous les jours.
Pourquoi le modèle ne sait pas compter les caractères
Un exemple célèbre : on demande au modèle « combien de r dans strawberry ? », beaucoup répondent 2, alors que la bonne réponse est 3.
Voyons comment le tokenizer traite ce mot : DeepSeek coupe strawberry seul en trois blocs, st, raw et berry, tandis que strawberry précédé d'un espace (c'est le cas au milieu d'une phrase) est un seul token. Le modèle reçoit un numéro (79430) et ne « voit » pas directement les lettres qu'il contient. Pour savoir combien de r contient ce mot, il ne peut compter que sur des discussions semblables vues pendant l'entraînement.
Mais cet exemple est trop célèbre ; la plupart des modèles actuels l'ont déjà vu. J'ai posé à deepseek-flash plusieurs variantes, trois fois chacune sans réflexion et trois fois avec :
| Question | Sans réflexion | Avec réflexion |
|---|---|---|
| En anglais : combien de r dans strawberry | 3, 3, 3 | 3, 3, 3 |
| En chinois : combien de lettres r dans strawberry | 2, 3, 3 | 3, 3, 3 |
| En chinois : combien de lettres r dans raspberry | 3, 2, 3 | 3, 3, 3 |
| Combien de caractères dans « 秋天的叶子一片片落下 » (les feuilles d'automne tombent une à une) | 10, 10, 11 | 10, 10, 10 |
La vieille question posée en anglais est désormais toujours juste ; posée en chinois, avec un autre mot ou pour compter des caractères chinois, la réponse est parfois fausse sans réflexion. Avec la réflexion, tout est juste, parce que le modèle écrit le mot lettre par lettre dans sa réflexion avant de compter : il découpe lui-même le token.
En développement, tout ce qui demande un comptage exact, comme compter les caractères, mesurer la longueur d'une chaîne ou tronquer un texte, ne doit pas être confié au modèle, mais au code. len("秋天的叶子一片片落下") vaudra toujours 10.
Problèmes courants
Peut-on comparer directement le nombre de tokens de modèles différents ? Non. Pour un même texte chinois, DeepSeek et OpenAI diffèrent de trente pour cent. Pour comparer les prix de deux modèles, prenez un même texte réel de votre activité, calculez son nombre de tokens pour chacun et multipliez par leur prix unitaire respectif ; ne comparez pas seulement les prix unitaires.
Puis-je compter les tokens de DeepSeek avec tiktoken dans mon code ? Pour l'anglais et le code, c'est à peu près pareil ; pour le chinois, cela surestime. Pour être exact, utilisez le tokenizer de DeepSeek.
Exercices
- Donnez votre nom, le nom de votre ville et une phrase en dialecte aux deux tokenizers de
tokens.py, et voyez comment chacun les découpe. - Mesurez avec
count_tokens: un même contenu écrit en chinois et en anglais, lequel utilise le plus de tokens ? Essayez avec un vrai texte de votre travail. - Prenez des données JSON (par exemple la réponse d'une API) et comptez leurs tokens. Compactez-les ensuite sur une seule ligne en retirant tous les espaces et comptez de nouveau : combien de tokens avez-vous économisés ? C'est utile quand il faut donner beaucoup de données au modèle.
Auto-test
1. Pour une même phrase de chinois moderne, DeepSeek utilise moins de tokens que le tokenizer d'OpenAI. Pourquoi ? Qu'est-ce que cela change pour vous ?
Le vocabulaire d'un tokenizer est établi à partir du corpus d'entraînement ; celui de DeepSeek contient une plus grande proportion de chinois, donc davantage de groupes de mots chinois ont été fusionnés en un seul token. Pour vous, un même contenu chinois coûte moins cher sur DeepSeek, et plus de contenu tient dans le contexte. Pour comparer des prix entre modèles, calculez le nombre de tokens sur un texte réel avant de comparer.
2. Pourquoi un modèle se trompe-t-il facilement en comptant combien de fois une lettre apparaît dans un mot ?
Le modèle voit des numéros de tokens, pas des lettres. Un mot courant est souvent un seul token ; le modèle ne peut pas « voir » directement les lettres qu'il contient et répond de mémoire, d'après l'entraînement. Avec la réflexion activée, le modèle écrit d'abord le mot lettre par lettre avant de compter, et la précision est bien meilleure. Pour un comptage exact, il faut utiliser du code plutôt que le modèle.
3. Pourquoi le chinois classique est-il plus « cher » que le chinois moderne à nombre de caractères égal ?
Le vocabulaire fusionne les combinaisons de caractères courantes dans le corpus d'entraînement. Les mots usuels du chinois moderne (« 人工智能 », « 程序员 », programmeur) apparaissent souvent et ont été fusionnés en un token ; les combinaisons du chinois classique (« 时习 », « 不亦 ») sont rares, n'ont pas été fusionnées et font un token par caractère. Plus de tokens, plus de frais.
Questions et discussion
Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.
Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.
Chargement de la discussion…