Tokens et inférence : comment un LLM génère du texte avec Python (sans LangChain ni API OpenAI pour commencer) — IA expliquée
Tokens et inférence : comment un LLM génère du texte (avec Python)
Vous tapez une question dans ChatGPT, Copilot ou Claude, et une réponse s'écrit sous vos yeux, mot après mot. Tout le monde a vu ça. Mais très peu de gens savent ce qui se passe réellement entre le moment où vous appuyez sur Entrée et le moment où le premier mot apparaît.
Avant d'utiliser LangChain, de construire un RAG, un chatbot ou un agent IA en Python, il faut comprendre deux notions que tout le reste suppose acquises : le token et l'inférence. Sans elles, vous utiliserez une API LLM comme une boîte noire, et vous ne comprendrez ni pourquoi votre facture grimpe, ni pourquoi le modèle « oublie » le début d'un long document, ni pourquoi deux exécutions de la même question donnent deux réponses différentes.
Nous allons donc faire le chemin inverse de la plupart des tutoriels : pas de framework, pas de chaîne, pas de chatbot. Juste un script Python de quelques lignes pour découper une phrase en tokens, puis un deuxième pour regarder le modèle prédire le token suivant. Une fois que vous aurez vu ça tourner, tout le reste de la série deviendra beaucoup plus concret.
Un LLM, c'est quoi ? Un modèle de langage qui prédit la suite
Un LLM (Large Language Model, ou grand modèle de langage en français) est un modèle de langage entraîné sur de très grands ensembles de données textuelles (on parle aussi de « modèle LLM » ou de grands modèles de langage) : pages web, livres, code source, documentation. Pendant cet entraînement, le modèle apprend les relations entre les mots, ou plus exactement entre les tokens, nous y venons.
Ce qu'il faut retenir, et c'est presque décevant tellement c'est simple : un LLM ne fait qu'une seule chose, prédire le token suivant. Vous lui donnez une séquence de texte, il vous rend une distribution de probabilités sur ce qui devrait venir juste après. C'est tout. Les modèles d'IA générative que vous connaissez, GPT, Claude, Gemini, Mistral, Llama, reposent tous sur ce mécanisme, quelle que soit la fonctionnalité mise en avant par leur éditeur.
Tout ce que ces modèles d'IA semblent savoir faire, répondre à une question, résumer un PDF, générer du code, traduire, tenir une conversation, est une conséquence de cette prédiction répétée des milliers de fois. On parle de « génération de texte » parce que le modèle enchaîne les prédictions jusqu'à produire une réponse complète.
Il n'y a donc pas de « compréhension » au sens où un humain l'entend, pas de base de connaissances consultée en temps réel (sauf si vous la branchez vous-même, c'est précisément le rôle du RAG), et pas de garantie d'exactitude. Le modèle génère la suite la plus plausible au vu de ce qu'il a appris. Quand cette suite plausible est fausse, on appelle ça une hallucination.
Le token : l'unité que manipule réellement le modèle
Un modèle de langage ne lit pas des mots. Il ne lit pas non plus des lettres. Il lit des tokens, des morceaux de texte issus d'un découpage défini une fois pour toutes par un composant appelé le tokenizer.
Un token peut être :
un mot entier et fréquent (" Paris", " données"),
un morceau de mot (" gén" + "èrent"),
un signe de ponctuation (".", " :"),
un espace, un retour à la ligne, un fragment de code.
Chaque token correspond à un numéro dans un vocabulaire fixe (de l'ordre de 100 000 à 200 000 entrées pour les modèles récents). Le texte brut que vous envoyez est converti en une liste de numéros, et c'est cette liste de numéros que le modèle traite. À la sortie, le modèle produit un numéro, que le tokenizer reconvertit en texte.
Pourquoi cette notion compte-t-elle pour vous, développeur ou analyste qui veut utiliser ces modèles ?
La facturation des API se fait au token, pas au mot ni au caractère. Un texte en français coûte généralement 20 à 40 % de tokens de plus que le même texte en anglais, parce que les tokenizers ont été optimisés sur des corpus majoritairement anglophones.
La fenêtre de contexte se mesure en tokens. Quand on vous dit qu'un modèle accepte 128 000 tokens, c'est la somme de votre requête, du contexte que vous y ajoutez (un document, un historique de conversation) et de la réponse générée.
Le modèle ne « voit » pas les lettres. C'est pour ça que le modèle peut se tromper en comptant les lettres d'un mot ou en inversant une chaîne de caractères : il raisonne sur des tokens, pas sur des caractères.
Découper une phrase en tokens avec Python
Passons au code. La bibliothèque Python la plus simple pour tokeniser comme le fait OpenAI s'appelle tiktoken. Elle fonctionne entièrement en local, sans clé d'API, sans compte, sans connexion internet après l'installation.
Installer tiktoken
Dans un environnement Python propre (un venv ou un environnement conda, pour gérer les dépendances proprement), installer la bibliothèque tient en une ligne :
bash
pip install tiktokenExécuter le script de découpage
Voici un script complet qui découpe une phrase et affiche chaque token :
python
import tiktoken
# Le tokenizer utilisé par les modèles GPT-4o d'OpenAI
encodeur = tiktoken.get_encoding("o200k_base")
phrase = "Les LLM génèrent du texte token par token."
tokens = encodeur.encode(phrase)
print(f"{len(tokens)} tokens")
print(tokens)
for numero in tokens:
print(f"{numero:>7} -> {encodeur.decode([numero])!r}")Exemple de sortie (le découpage exact dépend du tokenizer et de sa version, mais la logique reste la même) :
10 tokens
[8303, 100098, 3306, 19283, 1217, 23637, 6602, 1123, 6602, 13]
8303 -> 'Les'
100098 -> ' LLM'
3306 -> ' gén'
19283 -> 'èrent'
1217 -> ' du'
23637 -> ' texte'
6602 -> ' token'
1123 -> ' par'
6602 -> ' token'
13 -> '.'Lire le résultat
Trois choses sautent aux yeux :
L'espace fait partie du token. ' token' (avec un espace devant) et 'token' (sans espace) sont deux tokens différents. C'est pour cela que le mot token apparaît deux fois avec le même numéro 6602 : à chaque fois, il est précédé d'un espace.
Le verbe conjugué est coupé en deux. génèrent n'est pas assez fréquent pour avoir son propre token, il est donc reconstitué à partir de ' gén' et 'èrent'. C'est typique du français : accents, conjugaisons et accords multiplient les formes, donc les tokens.
La ponctuation est un token à part entière. Le point final compte comme n'importe quel autre élément.
Compter les tokens d'un prompt avant de l'envoyer
Dans un projet réel, vous utiliserez tiktoken pour une chose très concrète : estimer le coût d'un appel ou vérifier que votre texte tient dans la fenêtre de contexte. La fonction encoding_for_model choisit automatiquement le bon tokenizer pour un modèle donné :
python
import tiktoken
encodeur = tiktoken.encoding_for_model("gpt-4o-mini")
def compter_tokens(texte: str) -> int:
return len(encodeur.encode(texte))
prompt_fr = "Résume ce rapport en trois points, en restant factuel."
prompt_en = "Summarize this report in three bullet points, staying factual."
print("Français :", compter_tokens(prompt_fr), "tokens")
print("Anglais :", compter_tokens(prompt_en), "tokens")Vous constaterez que la version française coûte plus de tokens que son équivalent anglais, à sens égal. Faites le test avec une page entière de texte, l'écart devient significatif. Ce n'est pas une raison pour écrire en anglais, mais c'est une donnée à connaître quand on dimensionne un projet.
Pour les modèles ouverts (Llama, Mistral, Qwen), le tokenizer est différent et se charge avec la bibliothèque transformers de Hugging Face. Le principe est rigoureusement identique, nous l'utiliserons dans la section suivante.
L'inférence : comment un LLM génère du texte, token après token
Entraînement et inférence : deux phases, deux mondes
Un modèle de langage vit deux vies.
<table border="1" cellpadding="8" cellspacing="0"> <thead> <tr> <th></th> <th>Entraînement</th> <th>Inférence</th> </tr> </thead> <tbody> <tr> <td><strong>Quand</strong></td> <td>Une fois, chez l'éditeur du modèle (Anthropic, Mistral, Meta…)</td> <td>À chaque requête d'un utilisateur</td> </tr> <tr> <td><strong>Ce qui se passe</strong></td> <td>Le modèle ajuste des milliards de paramètres pour mieux prédire le token suivant sur un corpus gigantesque</td> <td>Les paramètres sont figés ; le modèle calcule la probabilité du token suivant à partir de votre requête</td> </tr> <tr> <td><strong>Coût</strong></td> <td>Des millions d'euros de calcul, des semaines sur des milliers de GPU</td> <td>Quelques millisecondes à quelques secondes par token</td> </tr> <tr> <td><strong>Ce que vous contrôlez</strong></td> <td>Rien (sauf fine-tuning)</td> <td>Le prompt, le contexte, la température, la longueur maximale</td> </tr> </tbody> </table>
Quand vous utilisez un modèle via une API ou une interface comme ChatGPT, vous ne touchez jamais à l'entraînement. Tout ce que vous faites, c'est de l'inférence. Le modèle que vous interrogez a cessé d'apprendre le jour où son entraînement s'est terminé. S'il semble « connaître » votre document, c'est parce que vous l'avez mis dans la requête, pas parce qu'il l'a mémorisé.
La boucle de génération
L'inférence se déroule en boucle, et cette boucle est le cœur de tout ce qui suit dans la série :
Le tokenizer convertit le texte d'entrée en liste de tokens.
Le modèle lit toute la séquence et calcule, pour chacun des 100 000 à 200 000 tokens de son vocabulaire, un score (appelé logit).
Ces scores sont transformés en probabilités (via une fonction softmax).
Un token est choisi parmi les plus probables.
Ce token est ajouté à la fin de la séquence.
On retourne à l'étape 2 avec la séquence allongée d'un token.
La boucle s'arrête quand le modèle produit un token spécial de fin, ou quand la longueur maximale est atteinte. C'est ce qu'on appelle la génération autorégressive : chaque nouveau token dépend de tous les précédents, y compris ceux que le modèle vient lui-même de générer.
C'est aussi pour cette raison que la réponse s'affiche progressivement dans ChatGPT ou dans n'importe quelle interface de chatbot : le modèle ne « sait » pas la fin de sa phrase quand il en écrit le début. Il la découvre token après token, exactement comme vous allez le voir dans le code.
Température : pourquoi la même question ne donne pas toujours la même réponse
À l'étape 4, « choisir un token parmi les plus probables » ne veut pas forcément dire « prendre le plus probable ». Le paramètre de température règle ce comportement :
Température 0 : on prend systématiquement le token le plus probable (décodage greedy). Le résultat est déterministe, ou presque. C'est le réglage à privilégier pour générer du code, extraire des données ou classer des textes.
Température 0,7 à 1 : on tire au sort en respectant les probabilités. Un token à 30 % sortira environ trois fois sur dix. Les réponses des modèles deviennent plus variées, plus « créatives », et moins prévisibles.
Au-delà de 1 : les probabilités s'aplatissent, le modèle commence à choisir des tokens improbables et le texte part en vrille.
Dans la plupart des projets professionnels (extraction, classification, génération de code, assistance sur une base de connaissances), vous voudrez une température basse. Le mode « créatif » est rarement ce qu'on cherche quand on automatise un traitement.
Voir la prédiction du token suivant en Python avec un LLM local
Passons maintenant à l'inférence proprement dite. Pour commencer sans dépendre d'aucune API et voir vraiment les probabilités, nous allons exécuter un LLM local, petit mais fonctionnel : Qwen2.5 en version 0,5 milliard de paramètres. Il tient en mémoire sur un ordinateur portable classique, sans GPU, et comprend correctement le français.
Installer les dépendances
bash
pip install torch transformersLe premier téléchargement du modèle (environ 1 Go) se fait automatiquement depuis Hugging Face, puis il est mis en cache.
Afficher les cinq tokens les plus probables
python
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
nom_modele = "Qwen/Qwen2.5-0.5B"
tokenizer = AutoTokenizer.from_pretrained(nom_modele)
modele = AutoModelForCausalLM.from_pretrained(nom_modele)
modele.eval()
prompt = "La capitale de la France est"
entrees = tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
sortie = modele(**entrees)
# Scores (logits) prédits pour le token qui suit le dernier token du prompt
logits = sortie.logits[0, -1]
# Conversion des scores en probabilités
probas = torch.softmax(logits, dim=-1)
# Les 5 tokens les plus probables
top = torch.topk(probas, k=5)
print(f"Prompt : {prompt!r}\n")
for proba, indice in zip(top.values, top.indices):
print(f"{tokenizer.decode(indice)!r:>12} {proba.item():6.1%}")Exemple de sortie (les valeurs exactes varient selon le modèle et sa version) :
Prompt : 'La capitale de la France est'
' Paris' 71.4%
' la' 6.2%
' une' 3.9%
' située' 2.1%
' :' 1.8%Voilà, concrètement, ce qu'est l'inférence : une distribution de probabilités sur le vocabulaire, calculée à partir de la séquence d'entrée. À température 0, le modèle écrira Paris. À température 1, il écrira Paris sept fois sur dix, et autre chose le reste du temps.
Amusez-vous à changer la phrase de départ. Remplacez-la par "La capitale de la France est une ville" : la distribution change complètement, parce que la séquence précédente a changé. C'est la seule chose que le modèle « sait » : ce qu'il y a avant.
Écrire sa propre boucle de génération
Pour bien ancrer la notion de génération autorégressive, écrivons la boucle à la main plutôt que d'appeler modele.generate() :
python
ids = entrees["input_ids"]
for _ in range(12):
with torch.no_grad():
logits = modele(ids).logits[0, -1]
# Décodage glouton : on garde le token le plus probable
suivant = torch.argmax(logits).reshape(1, 1)
ids = torch.cat([ids, suivant], dim=1)
print(tokenizer.decode(ids[0]))À chaque tour de boucle, vous voyez la phrase s'allonger d'un token :
La capitale de la France est Paris
La capitale de la France est Paris.
La capitale de la France est Paris. La
La capitale de la France est Paris. La capitale
La capitale de la France est Paris. La capitale de
...Douze lignes de code, et vous venez de reproduire ce que fait ChatGPT quand il « écrit » une réponse. La fonction modele.generate() de transformers fait exactement cela, avec en plus la gestion de la température, des tokens de fin et de quelques optimisations.
Faire la même expérience via l'API OpenAI
Si vous préférez interagir avec un modèle hébergé dans le cloud plutôt qu'avec un modèle local, l'API OpenAI expose les mêmes probabilités grâce à l'option logprobs. Il faut au préalable créer un compte sur la plateforme OpenAI, générer une clé d'API et la stocker dans une variable d'environnement OPENAI_API_KEY (jamais en dur dans le script).
bash
pip install openai
python
import math
from openai import OpenAI
client = OpenAI() # la clé est lue dans la variable d'environnement OPENAI_API_KEY
reponse = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Complète cette phrase : La capitale de la France est"}],
max_tokens=1,
logprobs=True,
top_logprobs=5,
)
for alternative in reponse.choices[0].logprobs.content[0].top_logprobs:
print(f"{alternative.token!r:>12} {math.exp(alternative.logprob):6.1%}")L'API renvoie des log-probabilités (le logarithme des probabilités), d'où le math.exp pour retrouver un pourcentage lisible. Vous retrouverez une distribution du même type que celle du modèle local, avec un vocabulaire et des valeurs différents. L'ancienne syntaxe import openai fonctionne encore mais n'est plus recommandée.
Cette expérience vaut pour tous les fournisseurs : Anthropic, Mistral, Google ou un modèle local servi par Ollama. Les API LLM diffèrent par leur syntaxe, mais derrière, le mécanisme est toujours le même : tokeniser, prédire, ajouter, recommencer.
Ce que ça change quand on utilise ces modèles au quotidien
Maintenant que vous avez vu les tokens et l'inférence tourner, plusieurs comportements des modèles deviennent limpides :
Le prompt est tout. Le modèle ne dispose de rien d'autre que la séquence qu'on lui donne. Une consigne vague produit une distribution étalée, donc une réponse vague. Une consigne précise, avec des exemples et du contexte, concentre les probabilités sur ce que vous voulez. C'est la base de l'ingénierie de prompt.
Les hallucinations sont normales. Si la réponse correcte n'est pas dans le contexte et que le modèle n'a pas appris de token « je ne sais pas » suffisamment probable, il générera la suite la plus plausible, c'est-à-dire parfois une invention très bien écrite. Pour réduire les hallucinations, il faut donner au modèle la bonne information dans le contexte : c'est tout l'objet du RAG.
Le coût et la latence sont proportionnels aux tokens. Un document de 50 pages en contexte, c'est 30 000 à 40 000 tokens relus à chaque requête. D'où l'intérêt de ne transmettre que les passages pertinents.
Le modèle n'a pas de mémoire entre deux appels. Un chatbot « se souvient » de la conversation uniquement parce que votre code renvoie l'historique complet à chaque requête. Ce sont des tokens, encore.
Le modèle n'exécute rien. Il génère du texte. Si ce texte ressemble à un appel de fonction, c'est à votre code Python de l'exécuter réellement. C'est la base des agents.
Et après ? Passer à la pratique
Vous avez maintenant les deux briques que tout le reste suppose acquises. Pour les mettre en pratique dès maintenant, suivez notre tutoriel Comment créer un chatbot IA avec Streamlit et Python : vous y retrouverez les tokens (dans l'historique de conversation renvoyé à chaque requête) et l'inférence (dans chaque appel au modèle).
En résumé
Un grand modèle de langage prédit le token suivant, rien de plus, rien de moins.
Un token est un fragment de texte issu d'un découpage fixe ; le français coûte plus de tokens que l'anglais ; les API facturent et limitent au token.
L'inférence est la phase où le modèle, figé, calcule une distribution de probabilités sur le vocabulaire à partir de votre requête, puis génère le texte en boucle, token après token.
Avec tiktoken, vous comptez les tokens en local ; avec transformers, vous observez les probabilités d'un modèle local ; avec l'API OpenAI et logprobs, vous faites de même dans le cloud.
Le prompt, le contexte et la température sont les seuls leviers dont vous disposez au moment de l'inférence : tout le développement Python autour des modèles de langage (LangChain, RAG, agents, chatbots) consiste à bien les utiliser.
Vous souhaitez aller plus loin et apprendre à créer des applications IA fiables en Python, du premier appel d'API jusqu'au déploiement d'un agent ?


