Réapprendre les LLM et le RL à partir des principes fondamentaux

English · 中文 · Français

Note de rédaction : les idées et le matériau du brouillon initial sont de moi. ChatGPT m’a aidé à structurer et réviser le texte, ainsi qu’à préparer les traductions.

J’avais déjà utilisé beaucoup des idées qui sous-tendent les modèles de langage et l’apprentissage par renforcement. J’en avais rencontré certaines pendant mon doctorat ; d’autres font désormais partie du vocabulaire courant de l’IA moderne : Softmax, Cross-Entropy, Attention, KL Divergence, Value Functions, Policy Gradients.

Je connaissais les formules. Je savais les utiliser.

Mais à un moment donné, surtout lorsque les grands modèles de langage sont devenus des outils que j’utilisais presque tous les jours, j’ai commencé à remarquer un écart entre reconnaître un objet mathématique et vraiment comprendre pourquoi il avait cette forme.

Reconnaître une formule n’est pas la même chose que comprendre pourquoi elle prend cette forme.

Prenons le Softmax :

\[p_i = \frac{e^{z_i}}{\sum_j e^{z_j}}.\]

Le cas à deux classes rend une partie de cette structure particulièrement visible. Si

\[\Delta = z_1-z_2,\]

alors la probabilité de la première classe peut s’écrire

\[p_1 = \frac{e^{\Delta}}{e^{\Delta}+1}.\]

Déplacez le curseur ci-dessous : on voit directement comment les probabilités changent lorsque la différence relative entre les logits varie.

Cette visualisation s’exécute directement dans le navigateur avec Marimo. Notebook source.

J’avais vu l’équation du Softmax un nombre incalculable de fois. Mais pourquoi une exponentielle ? Pourquoi utiliser précisément la base naturelle e ? Pourquoi ne pas simplement diviser les scores par leur somme ? Pourquoi ajouter la même constante à tous les logits ne change-t-il pas les probabilités ?

À partir du moment où j’ai commencé à poser ce genre de questions, d’autres formules pourtant familières sont redevenues étranges.

Lorsque nous écrivons

\[P(x_t \mid x_{<t})\]

d’où vient exactement cette probabilité ?

Le jeu de données nous donne des observations, pas une table de probabilités.

Lorsque nous maximisons la vraisemblance, pourquoi le simple fait qu’une chose ait été observée signifie-t-il que le modèle devrait lui attribuer une probabilité plus élevée ?

Et lorsque les méthodes de gradient de politique reposent sur des termes comme

\[\nabla_\theta \log \pi_\theta(a \mid s)\]

comment cela peut-il fonctionner alors que l’action elle-même a été échantillonnée de manière discrète ?

Ce sont des questions très élémentaires. D’une certaine manière, elles sont même beaucoup plus élémentaires que les problèmes de recherche sur lesquels j’avais travaillé. C’est précisément pour cela qu’elles me mettaient un peu mal à l’aise.

J’étais devenu suffisamment familier avec ces outils pour pouvoir passer devant ces équations sans m’y arrêter.

J’ai donc décidé de m’arrêter.

Ce que j’entends par « principes fondamentaux »

Le but de ce projet n’est pas de constituer une liste des équations que toute personne travaillant sur les LLM serait censée connaître.

Il ne s’agit pas non plus de prétendre que toute méthode efficace peut être déduite de manière unique à partir d’un petit ensemble d’axiomes mathématiques.

En apprentissage automatique, beaucoup de choses sont précisément cela : des choix.

Une identité probabiliste, une hypothèse de modélisation, une paramétrisation utile, une convention d’optimisation et une décision d’ingénierie n’ont pas le même statut.

Comprendre une formule, c’est aussi savoir de quel type d’objet il s’agit.

Pour chaque structure mathématique, je veux revenir à des questions comme :

  • Quel problème essayons-nous de résoudre ?
  • Quelles quantités observons-nous réellement ?
  • Quelles hypothèses sont nécessaires ?
  • Quelles étapes sont des identités mathématiques ?
  • Quelles étapes relèvent de choix de modélisation ?
  • Pourquoi ce choix particulier est-il utile ?
  • Qu’est-ce qui cesserait de fonctionner si nous le changions ?

Le test que j’ai en tête est simple :

Si j’oublie l’équation, puis-je reconstruire le raisonnement qui y mène ?

Le Softmax en est un bon exemple. Retenir

\[p_i = \frac{e^{z_i}}{\sum_j e^{z_j}}\]

est facile. La compréhension devient plus intéressante lorsqu’on réduit l’équation à

\[\frac{p_i}{p_j}=e^{z_i-z_j},\]

ou, de manière équivalente,

\[z_i-z_j=\log\frac{p_i}{p_j}.\]

Les logits ne sont alors plus simplement des nombres mystérieux qu’il faudrait normaliser. Leurs différences décrivent des logarithmes de rapports de probabilités.

C’est ce type de transition que je veux conserver dans ces notes : non seulement l’équation finale, mais aussi la question qui m’a donné une raison de vouloir la comprendre.

Apprendre en expliquant

Il y a une autre raison pour laquelle j’écris cette série.

Je veux mieux apprendre à expliquer des idées techniques.

Il est étonnamment facile de me convaincre que j’ai compris quelque chose tant que l’explication reste dans ma tête. L’écriture impose un autre niveau d’exigence. Si je ne peux pas expliquer pourquoi une étape découle de la précédente, ou si je remplace silencieusement un choix de modélisation par « c’est simplement comme ça que ça marche », le trou dans mon raisonnement devient visible.

Pour moi, l’explication n’arrive donc pas après l’apprentissage.

Elle fait partie du test qui permet de savoir si j’ai réellement appris.

Le processus vers lequel j’ai progressivement convergé ressemble à peu près à ceci :

\[\text{confusion} \rightarrow \text{dérivation} \rightarrow \text{intuition} \rightarrow \text{implémentation} \rightarrow \text{explication}.\]

L’ordre n’est pas toujours aussi propre. Parfois, une implémentation révèle une incompréhension. Parfois, un tout petit exemple numérique aide davantage qu’une dérivation complète. Parfois, une explication qui semblait convaincante s’effondre dès que j’essaie de l’écrire avec précision.

Ces échecs sont utiles. Je ne veux pas tous les effacer des articles finaux pour les remplacer par un chemin de manuel où chaque idée paraît évidente rétrospectivement.

La question « pourquoi le Softmax utilise-t-il une exponentielle ? » compte pour moi en partie parce que je l’ai réellement posée.

Apprendre les LLM avec un LLM

Ce projet possède un aspect récursif assez évident : une grande partie de cet apprentissage passe par des conversations avec ChatGPT.

Je l’utilise comme tuteur et comme partenaire de réflexion. Je pose des questions, je conteste des explications, je propose mes propres interprétations, je travaille des dérivations, puis je reviens souvent plusieurs fois sur la même idée jusqu’à pouvoir l’exprimer dans mes propres termes.

Cela ne supprime pas la nécessité de comprendre le contenu. Au contraire, cette distinction devient peut-être encore plus importante.

Un LLM peut produire très rapidement une explication qui semble plausible. Mais la plausibilité n’est pas le critère que je veux utiliser ici. Je dois toujours vérifier l’algèbre, examiner les hypothèses, tester l’intuition sur des exemples et décider si l’explication a réellement du sens pour moi.

Ces conversations font donc partie du matériau source de cette série. Je veux conserver les vraies questions et les changements de compréhension qui y sont apparus, plutôt que de reconstruire après coup une histoire d’apprentissage plus propre.

Il y a quelque chose d’assez circulaire dans le fait d’utiliser un modèle de langage pour examiner les mathématiques qui se trouvent sous les modèles de langage. Je suis curieux de voir quand cette circularité aide réellement à comprendre, et quand elle devient au contraire un raccourci qui masque la compréhension.

Des équations au code

En parallèle, je développe un dépôt appelé llm-from-first-principles.

Son rôle est complémentaire à celui du blog.

L’écriture pose la question :

Est-ce que je peux l’expliquer clairement ?

L’implémentation en pose une autre :

Est-ce que je peux le construire sans me cacher le mécanisme ?

Le dépôt est l’endroit où je travaille directement les différents composants. Le blog est l’endroit où j’essaie de reconstruire le raisonnement qui les sous-tend.

Le Softmax est aussi un petit exemple de la raison pour laquelle je ne veux pas m’arrêter à l’équation elle-même.

Sur le papier, nous écrivons

\[p_i = \frac{e^{z_i}}{\sum_j e^{z_j}}.\]

Mathématiquement, cette définition ne pose pas de problème. Mais une implémentation littérale peut être numériquement instable : si un logit positif est très grand, son exponentielle peut déborder.

Une implémentation stable commence par calculer

\[m = \max_j z_j,\]

puis décale tous les logits de la même quantité :

\[z_i' = z_i-m.\]

Elle calcule ensuite

\[p_i = \frac{e^{z_i-m}}{\sum_j e^{z_j-m}}.\]

On obtient exactement les mêmes probabilités, car le Softmax est invariant lorsqu’on ajoute ou soustrait la même constante à tous les logits :

\[\operatorname{softmax}(z) = \operatorname{softmax}(z-c).\]

Voici l’implémentation PyTorch que j’ai travaillée :

import torch

def softmax_stable(x: torch.Tensor) -> torch.Tensor:
    max_x = x.max(
        dim=-1, keepdim=True
    ).values  # .values => get values, here [0] the same.
    shifted_x = x - max_x

    exp_x = torch.exp(shifted_x)
    row_sums = exp_x.sum(dim=-1, keepdim=True)

    return exp_x / row_sums

Soustraire le maximum ne définit pas un autre Softmax. C’est une manière numériquement plus sûre de calculer la même fonction mathématique.

Comprendre une formule mathématique n’est pas encore la même chose que comprendre comment elle vit dans le code.

Il existe souvent une couche supplémentaire entre l’expression écrite sur le papier et une implémentation fiable : stabilité numérique, formes des tenseurs, masking, broadcasting, organisation mémoire, circulation des gradients, ou d’autres contraintes que la notation mathématique compacte ne montre pas.

Cet écart est l’une des raisons pour lesquelles j’ai commencé ce dépôt.

J’aimerais que les deux forment ensemble une boucle de ce type :

\[\text{question} \rightarrow \text{dériver} \rightarrow \text{implémenter} \rightarrow \text{expliquer} \rightarrow \text{questionner à nouveau}.\]

Je ne m’attends pas à ce que ce parcours débouche sur une théorie complète expliquant pourquoi les grands modèles de langage fonctionnent. Cette question dépasse largement n’importe quelle collection de formules, et beaucoup de ses aspects restent ouverts.

Mon objectif est plus modeste et, pour moi, plus utile :

Faire en sorte qu’un nombre croissant de pièces de la machine cessent de ressembler à de la magie.

Je veux commencer par la probabilité.

Nous entraînons les modèles de langage en attribuant des probabilités aux tokens et aux séquences, alors que les données dont nous partons ne sont que des observations.

D’où vient donc, exactement, cette probabilité ?

Ce sera la question du premier article.