De la prédiction du token suivant au code fonctionnel : comment les LLM génèrent du Python
Les tokens, le softmax, la température, les couches d’auto-attention et les couches feed-forward sont expliqués dans l’ordre, avec un exemple de tri étape par étape ainsi que les limites inhérentes aux prédictions.
Sources: How LLMs Actually Work: From Predicting the Next Word to Writing Code.
Des outils tels que ChatGPT et Copilot écrivent des fonctions, corrigent des bugs et expliquent des algorithmes, et il peut sembler qu’ils fassent plus que simplement compléter du texte. Le modèle mental présenté ci-dessous part de la base : la prédiction de tokens, l’attention, ainsi que le fait que le mécanisme permettant de terminer une phrase génère également du Python valide.
Une question posée à plusieurs reprises
Que le résultat soit un haïku, un e-mail marketing ou un tri fusion, un modèle de langage répond sans cesse à une seule question : étant donné tout ce qui a été vu jusqu’à présent, quel est le token suivant le plus probable ?
Il n’y a pas de compréhension sémantique au sens traditionnel, et le modèle ne passe par aucune étape où il déciderait d’écrire une fonction avant de produire le mot def. Il prédit token par token. Avec des milliards de paramètres et un texte d’entraînement comptant des centaines de milliards de tokens, ces prédictions ressemblent beaucoup à du raisonnement.
Qu’est-ce qu’un token ?
La précision est utile avant même d’aborder la question de la prédiction. Les modèles ne fonctionnent pas sur des caractères ou des mots, mais sur des tokens, des unités sous-mot produites par un algorithme tel que le Byte Pair Encoding. En gros :
- Les mots courants deviennent généralement des tokens uniques, comme
"the","def"et"return". - Les mots rares sont divisés en plusieurs parties, de sorte que
"unhappiness"devient["un", "happiness"].
"(", ":", "==" et "->".Pour GPT-4, un token correspond à environ 0,75 mot en anglais, tandis que dans le code il représente plutôt 3 ou 4 caractères, car le code est rempli de tokens d’un seul caractère comme : et (.
Le modèle dispose également d’un vocabulaire, une liste fixe de tokens connus comptant généralement de 50 000 à 100 000 éléments. À chaque étape, il attribue une probabilité à chaque élément et choisit soit le plus probable, soit un élément tiré aléatoirement selon la distribution, le choix étant déterminé par la température.
Le cycle de prédiction
Pour la séquence « The cat sat on the », la quantité à calculer est une probabilité conditionnelle :
P(next_token | "The", "cat", "sat", "on", "the")
C’est une distribution sur l’ensemble du vocabulaire. Une passe avant à travers le réseau produit un vecteur de la même taille que le vocabulaire, que la fonction softmax transforme en une distribution de probabilités valide :
P(token_i) = exp(logit_i) / Σⱼ exp(logit_j)
Dans le décodage gourmand, le token ayant la plus grande probabilité est choisi. Sinon, un token est sélectionné en fonction de la distribution, avec temperature qui contrôle son éventail :
temperature = 0choisit toujours le token le plus probable, ce qui rend la sortie déterministe.temperature = 1sélectionne aléatoirement dans la distribution brute, ce qui donne des résultats variés et créatifs.- Lorsque cette valeur est supérieure à 1, la distribution s’aplatit et la sortie devient plus aléatoire.
- Lorsqu’elle est inférieure à 1, la distribution s’aiguise et la sortie devient plus conservatrice.
C’est pourquoi un même prompt peut produire des réponses différentes lors de différentes exécutions.
Le Transformer
La fonction qui transforme une séquence de tokens en une distribution de tokens suivants est un Transformer, plus précisément un modèle de décodage uniquement, ce qui constitue le principe de conception de GPT, Llama, Mistral et Claude. Le flux général est le suivant :
Token IDs → Embedding layer → [Attention + FFN] × N layers → Linear head → Softmax → P(next token)
Les composants, dans l’ordre :
- Couche d’incorporation. Chaque identifiant de token entier devient un vecteur dense de taille
d_model, par exemple 4096 dans Llama-3 8B. Les incrustations de position, ou RoPE dans les modèles modernes, ajoutent des informations sur l’ordre. - Couches Transformer, répétées N fois. Chacune contient une attention auto-multidirectionnelle, un réseau feed-forward, une normalisation de couche appliquée avant chaque sous-couche selon le style « pre-norm », ainsi que des connexions résiduelles.
- Head linéaire et softmax. L’état caché final est mappé vers des logits de forme
[|V|], et le softmax produit la distribution.
Attention auto
La mécanique d’attention autonome permet à un token d’examiner les autres tokens de la séquence afin de se forger une image plus complète de lui-même. C’est ce qui donne aux Transformers un avantage sur les RNN : chaque token peut atteindre n’importe quel autre token en une seule étape, sans déclin des gradients en fonction de la distance.
Avec n tokens, chacun d’eux est projeté de trois manières différentes :
Q = XWᴬ_Q (Query: what am I looking for?)
K = XWᴬ_K (Key: what do I contain?)
V = XWᴬ_V (Value: what do I contribute?)
Ici, X est la matrice d’entrée de forme [n, d_model] et les trois matrices W représentent des poids appris. Le score entre le token i, agissant comme requête, et le token j, agissant comme clé, est :
score(i, j) = qᵢ · kⱼᵀ / √dₖ
En divisant par la racine carrée de la dimension clé, on empêche les produits scalaires de devenir trop importants lorsque dₖ est élevé, ce qui pourrait faire que le softmax se retrouve dans des régions à des gradients très faibles. Le calcul complet est le suivant :
Attention(Q, K, V) = softmax(QKᵀ / √dₖ) · V
Dans un modèle dédié uniquement à la décodage comme GPT, l’attention est causale, ou masquée : le token i ne peut prêter attention qu’aux positions jusqu’à i. Les positions futures reçoivent une note de néant négatif avant l’application de softmax, de sorte que leurs poids sont exactement nuls.
Attention multi-têtes exécute h fonctions d’attention indépendantes en parallèle, chacune ayant une dimension réduite de d_model / h, puis concatène les résultats :
MultiHead(Q, K, V) = Concat(head₁, ..., headₕ) · W_O
headᵢ = Attention(QWᵢ_Q, KWᵢ_K, VWᵢ_V)
Diverses parties du système s’occupent de relations différentes. Certaines peuvent suivre les dépendances syntaxiques, d’autres la co-référence, et d’autres encore la proximité positionnelle.
Le réseau feed-forward
Après l’attention, la représentation de chaque token passe par un réseau FFN par position :
FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
Les architectures modernes telles que Llama et Mistral utilisent plutôt SwiGLU :
FFN(x) = (SiLU(xW_gate) ⊙ xW_up) · W_down
La majeure partie des connaissances mémorisées par un modèle se trouve dans le FFN. Il fonctionne comme un stockage clé-valeur basé sur des associations factuelles. L’attention transmet les informations entre les tokens, tandis que le FFN recherche les faits et les schémas stockés. Dans les grands modèles, la largeur du FFN est d’environ quatre fois celle de d_model. Llama-3 70B a d_model = 8192 et une taille intermédiaire de 28672, c’est là que se trouvent la plupart des paramètres.
Pourquoi les modèles peuvent écrire du code
Le code est une séquence de tokens, tout comme le langage naturel. Pour un modèle, une fonction Python n’a rien de spécial : il s’agit simplement d’une série de tokens tels que def, sort, _list, (, arr, ), : et ainsi de suite.
Un résultat syntaxiquement valide est purement le fruit de la distribution d’entraînement. Considérez ces probabilités conditionnelles :
P("(" | ..., "def", " quicksort") ≈ 1.0
P(":" | ..., "def", " quicksort", "(", "arr", ")") ≈ 1.0
P("return" | context inside function body) >> P("import" | same context)
Elles présentent des pics aigus car le modèle a été exposé à des millions de fichiers Python respectant tous les mêmes règles. La grammaire de Python n’est pas codée nulle part dans le réseau ; elle émerge des statistiques du corpus.
Cela explique également pourquoi les modèles écrivent parfois du code parfaitement structuré mais logiquement incorrect. Ils maximisent la probabilité du token suivant compte tenu de la distribution d’entraînement. Ils ne lancent pas le code pour le vérifier.
Une fonction de tri, étape par étape
Supposons que la demande soit « Écrivez une fonction Python pour trier une liste ». Le modèle la traite comme une séquence de tokens et ajoute la sortie un token à la fois. En gros :
- Étape 1, après la demande :
defavec une probabilité d’environ 0,91. - Étape 2, après
def:sortavec une probabilité d’environ 0,78. - Étape 3 :
_listou(avec une probabilité d’environ 0,65. - Étape 4, après
sort(:arroulstvers 0,71. - Étapes 5 à 7 :
), puis:, puis un saut de ligne, chacun vers 0,99. - Étape 8 : l’indentation de quatre espaces vers 0,97.
Dans les étapes 5 à 8, le modèle atteint une certaine certitude concernant les tokens syntaxiques. Ce n’est pas une compréhension du Python. Il s’agit simplement de complétion de motifs, apprise à partir d’un corpus où chaque définition de fonction a le même aspect.
Les véritables choix interviennent plus tôt, aux étapes 1 à 4, où le nom de la fonction, le nom des paramètres et la structure globale sont choisis. Ces choix sont influencés par le contenu de l’instruction grâce à l’attention. Le token sort dans l’instruction possède un poids d’attention élevé lors de la génération du nom de la fonction.
Température dans la génération de code
Copilot et des outils similaires fonctionnent généralement à basse température, autour de 0,1 à 0,3, pour le code :
- Les tokens syntaxiques doivent être presque déterministes.
- Il existe souvent une seule implémentation correcte.
- Les API inventées deviennent un mode de défaillance majeur à haute température.
Pour les éléments créatifs tels que les noms de variables, les docstrings et les commentaires, une température légèrement plus élevée est acceptable. Certains outils utilisent également la recherche par faisceau, qui conserve en même temps les séquences partielles les plus pertinentes (top-k) et choisit à la fin celle ayant la meilleure probabilité conjointe.
Pourquoi les modèles plus grands se comportent différemment
Un modèle de 7 milliards de paramètres et un modèle de 70 milliards de paramètres partagent la même architecture. La différence réside dans l’échelle : plus de couches, un d_model plus grand et davantage de têtes d’attention. Pourtant, le modèle plus grand présente des améliorations qualitatives, notamment en termes de raisonnement multi-étapes, de code plus correct et d’apprentissage émergent dans le contexte.
À grande échelle, le modèle apprend bien plus que des statistiques superficielles des tokens. Il crée des représentations compressées de schémas plus profonds tels que la structure algorithmique, les dépendances logiques et les contraintes de type. Un modèle de 70 milliards de paramètres ayant vu suffisamment de code défectueux accompagné d’échecs de test a implicitement associé certains schémas à la correction. C’est la base empirique de l’hypothèse de l’échelle : la capacité suit une courbe progressive en termes de puissance de calcul, de données et de paramètres, et la qualité du code la suit avec autant de fiabilité que n’importe quelle autre compétence.
Limites découlant de la conception
Connaître le mécanisme révèle certaines limites intrinsèques :
- Exécution. Il n’y a pas d’environnement d’exécution à l’intérieur du modèle. Ce qu’il produit ressemble à la sortie d’une exécution correcte, mais rien n’a réellement été exécuté.
- Exactitude garantie. La prédiction maximise la probabilité par rapport à la distribution d’entraînement, et non la validité logique.
- Véritable originalité. Un algorithme sans précédent statistique dans les données d’entraînement ne peut être inventé. Le modèle peut combiner et s’adapter, mais il ne peut pas créer de nouveautés.
- Récupération exacte. Il n’y a pas de table de recherche. Toute la mémoire est répartie dans les poids, ce qui fait que des API, des signatures de fonctions et des versions de bibliothèques peuvent parfois être erronées.
Aucun de ces phénomènes n’est un bug attendant une correction. Ils découlent directement de l’architecture, et en en connaissant les raisons, on devient un utilisateur bien meilleur des assistants de codage.
Un aperçu général
- Un token est une unité sous-mot, équivalente à environ 0,75 mot en anglais.
- La prédiction se fait via une fonction softmax sur le vocabulaire à chaque étape.
- La self-attention permet à chaque token de prêter attention à tous les tokens précédents grâce à des produits query-clé échelonnés.
- La multi-head attention exécute plusieurs de ces mécanismes en parallèle pour capturer différentes relations.
- Le FFN agit comme une mémoire clé-valeur contenant des associations factuelles.
- La génération de code fonctionne de la même manière que la génération de texte, la syntaxe émergeant des statistiques d’entraînement.
- La température détermine le degré de netteté de la distribution de sampling.
- Les limites fondamentales sont l’absence d’exécution fiable, l’absence de correction garantie et l’absence de véritable originalité.
Tout dans ce mécanisme, de l’attention et du FFN aux résidus et à la norme de couche, sert un seul but : rendre la probabilité conditionnelle du token suivant, étant donné tous les tokens précédents, aussi précise que possible. Le reste est un effet émergent.
Lectures complémentaires
- À l’intérieur d’un LLM : inférence en Python pur pour Qwen3-0.6B sur MLX — Construction étape par étape de l’inférence d’un LLM en environ 200 lignes de code Python avec MLX : tokeniseur, RMSNorm, RoPE, attention groupée aux requêtes, SwiGLU, cache KV et échantillonnage.
- De la prédiction du prochain token à l’exécution d’instructions : affinage d’un modèle GPT — Comment un modèle GPT préentraîné apprend à suivre des instructions : formatage des enregistrements, décalage des étiquettes, masques de padding, boucle d’entraînement, limites d’évaluation et progression vers les préférences DPO.