Appearance
Cours 1 — Ce qu'est réellement un LLM
Module 1 · Fondamentaux · Prérequis : Python et fonctionnement d'une API HTTP
Objectif réel : après ce cours, tu dois être capable de dessiner ce qui se passe entre
POST /analyzedans FastAPI et la réponse d'Ollama, sans confondre le modèle, le contexte, la base de données et le mécanisme de génération.
Ce cours concerne surtout les LLM génératifs auto-régressifs, la famille utilisée couramment pour les modèles de conversation et de génération de code. Tous les modèles dits « de langage » n'ont pas exactement la même architecture ni le même objectif.
1. L'idée centrale : prédire la suite oblige à modéliser ce qui précède
Imagine que tu lises :
text
J'utilise FastAPI. Ma base de données est PostgreSQL.
Je souhaite ajouter un worker pour que les exports PDF ne ...Pour proposer une bonne continuation, il ne suffit pas de connaître la grammaire française. Il faut aussi suivre le sujet, relier « worker » à « exports PDF » et comprendre qu'on parle probablement de ne pas bloquer la requête HTTP.
C'est l'intuition fondamentale : un modèle entraîné à prédire la suite de textes variés doit apprendre beaucoup de structures présentes dans ces textes : langage, code, faits fréquents, associations entre concepts et certaines régularités de raisonnement. Ce n'est ni une simple table de mots adjacents ni une base de données de faits explicitement organisée.
Pour un modèle auto-régressif, l'objectif se formule ainsi :
text
P(prochain token | tokens précédents)Preprésente une distribution de probabilités.- « Prochain token » désigne une unité de vocabulaire, pas obligatoirement un mot.
- « Tokens précédents » désigne la séquence disponible dans le contexte, incluant éventuellement des tokens que le modèle vient lui-même de générer.
L'essence en une phrase
Le modèle n'écrit pas une réponse complète d'un seul geste : il calcule à chaque étape quel token pourrait venir ensuite, en tenant compte de la séquence disponible.
Une difficulté qui révèle le mécanisme
Si le modèle vient de générer Rev, le token suivant peut être yy selon son tokenizer. Il n'a pas nécessairement choisi d'un coup une réponse complète « Revyy » parmi une liste de réponses. La génération s'effectue à la granularité des tokens, même quand nous lisons des mots et des phrases.
Cela explique pourquoi un modèle peut produire une phrase grammaticalement parfaite, puis prendre une mauvaise direction au milieu de sa réponse : chaque nouveau token dépend aussi des choix déjà effectués.
2. Le chemin réel : du texte au calcul, puis du calcul au texte
Prends une requête de ton futur système de notes :
python
messages = [
{"role": "system", "content": "Extrais le projet mentionné."},
{"role": "user", "content": "Nous avons parlé de Revyy avec Paul."},
]À haut niveau, la requête suit ce trajet :
text
Ton application FastAPI
│ envoie des messages à Ollama
▼
Formatage de conversation (chat template)
│ rôles + délimiteurs adaptés au modèle
▼
Tokenizer
│ texte → identifiants numériques
▼
Réseau neuronal (poids appris)
│ scores du prochain token : les logits
▼
Stratégie de décodage
│ choisit un token à partir des scores
├── si fin : arrêter
└── sinon : ajouter le token et recommencer
▼
Détokenisation → texte de sortie
▼
Ton backend valide la réponse et décide quoi en faireQuatre responsabilités distinctes : le tokenizer découpe et encode ; le modèle calcule des scores ; le décodeur sélectionne des tokens ; ton application reste responsable des permissions, de la validation et des écritures en base.
Le modèle ne déclenche pas spontanément SELECT * FROM notes. Une application doit explicitement lui fournir des données ou exécuter un outil autorisé.
3. Token, ID et embedding : trois notions différentes
Le token est une unité du vocabulaire du tokenizer. Selon le modèle, une séquence peut être découpée en mots entiers, sous-mots, morceaux avec espaces, ponctuation, chiffres ou tokens spéciaux.
Exemple illustratif, qui ne prétend pas reproduire un tokenizer particulier :
text
Texte : "Je développe une application."
Tokens : ["Je", " développe", " une", " application", "."]
IDs : [120, 5142, 88, 9031, 17]Le modèle ne reçoit pas les mots comme des objets Python ; il reçoit des identifiants. Chaque identifiant est ensuite transformé par une table de représentations apprises en un vecteur de nombres : un embedding de token.
text
" application" → ID 9031 → [0.21, -0.07, 0.43, ...]
unité numéro représentation numériqueCes nombres et ces IDs sont fictifs. L'essentiel n'est pas leur valeur mais la séparation entre les trois étapes.
Pourquoi ne pas utiliser directement des mots ?
Parce qu'un vocabulaire fondé sur tous les mots possibles serait peu pratique : mots rares, fautes de frappe, néologismes, noms propres et code changent sans cesse. Les tokenizers de sous-mots peuvent réutiliser de petites unités pour représenter beaucoup de chaînes différentes.
Par conséquent :
- Un token n'est pas forcément un mot. Un mot peut se diviser en plusieurs tokens.
- Un token n'est pas son ID. L'ID sert à retrouver l'unité dans le vocabulaire.
- Un ID n'est pas son embedding. L'embedding est un vecteur appris utilisé pour les calculs internes.
- Les embeddings de tokens ne sont pas les embeddings de documents que tu stockeras plus tard dans pgvector : le mot est le même, mais leur rôle dans l'architecture est différent.
Conséquence pratique
La longueur d'un texte en caractères ou en mots n'indique pas exactement sa longueur en tokens. Deux modèles peuvent découper différemment le même texte ; cela a un effet sur la fenêtre de contexte, la latence et parfois le coût.
4. Que calcule le réseau ?
Après avoir converti les IDs en représentations numériques, le réseau traite la séquence. Dans les Transformers causaux, les mécanismes d'attention permettent notamment aux représentations d'un token de prendre en compte les éléments précédents pertinents de la séquence. D'autres couches transforment ensuite ces représentations.
Tu n'as pas encore besoin de connaître les matrices Q, K et V. Comprends d'abord pourquoi cette opération existe : le sens d'un élément dépend de son contexte.
Exemple :
text
"Paul a présenté Revyy. Ce projet cible les commerçants."Pour continuer après « Ce projet », le modèle doit pouvoir relier cette expression à Revyy, mentionné plus tôt. C'est une capacité de traitement contextuel, pas une requête cachée à PostgreSQL.
À la dernière position, le réseau produit un score brut — un logit — pour chaque token candidat de son vocabulaire. Une transformation et une stratégie de décodage permettront de choisir le prochain token. Le cours 2 développera précisément cette partie.
text
Contexte : "Le framework Python utilisé ici est"
Scores fictifs :
" FastAPI" 8.3
" Django" 5.2
" Laravel" 0.7
...Important : un score élevé signifie que ce token est favorisé comme suite du texte dans ce contexte. Ce n'est pas un taux de confiance sur la vérité d'une proposition.
5. La boucle de génération : la réponse se construit en marchant
Imagine cette instruction :
text
Réponds en un seul mot : quel projet est mentionné dans
"Nous avons parlé de Revyy avec Paul" ?Une génération conceptuelle ressemble à :
text
1. Le modèle reçoit les tokens du prompt.
2. Il calcule les logits pour le premier token de la réponse.
3. Le décodeur choisit un token, par exemple " Rev".
4. Ce token rejoint la séquence ; le modèle produit les logits suivants.
5. Le décodeur choisit éventuellement "yy".
6. Le modèle émet un token de fin de réponse ; le moteur s'arrête.
7. Ollama renvoie le texte reconstitué : "Revyy".La découpe " Rev" puis "yy" est hypothétique : un autre tokenizer peut représenter Revyy en un seul token ou en davantage de fragments.
Pseudo-code de compréhension, volontairement simplifié :
python
sequence = tokenizer.encode(formatted_prompt)
generated = []
while len(generated) < max_new_tokens:
logits = model(sequence) # calcul sur la séquence disponible
token = decode(logits[-1]) # règle greedy ou tirage probabiliste
if token == END_OF_TURN:
break
sequence.append(token)
generated.append(token)
answer = tokenizer.decode(generated)Un vrai moteur utilise notamment des optimisations pour éviter de recalculer inutilement tout le passé. Mais l'ordre logique reste auto-régressif : le token suivant dépend des tokens déjà présents.
Pourquoi cette granularité est importante
Pour un LLM, "Revyy", un JSON et une explication de vingt lignes passent tous par le même mécanisme de production de tokens. Le fait que le résultat ressemble à un objet structuré ne garantit pas qu'il respecte ton schéma Pydantic.
6. Que signifie « entraîné sur du texte » ?
Entraîner un modèle consiste à ajuster ses poids pour améliorer ses prédictions sur des exemples. En préentraînement auto-régressif, on lui montre des séquences et on mesure la qualité de ses prédictions des tokens suivants.
text
Exemple vu pendant l'entraînement :
"La capitale de la France est Paris."
À une position donnée :
contexte visible : "La capitale de la France est"
cible attendue : " Paris"Si le modèle attribue peu de probabilité au token attendu, la pénalité d'apprentissage est forte. Les méthodes d'optimisation calculent comment modifier un grand nombre de poids pour réduire l'erreur moyenne sur de nombreux exemples.
text
Données → prédictions → calcul de l'erreur
→ mise à jour des poids
→ nouveaux exemples → ...Ce qui est appris n'est pas un fichier de règles explicites. Ce sont des coefficients numériques qui participent collectivement aux calculs du réseau. Des connaissances et des régularités y sont représentées de manière distribuée.
Que signifie « modèle 8B » ?
Environ 8 milliards de paramètres numériques. Ni huit milliards de documents, ni huit milliards de faits, ni huit milliards de règles indépendantes.
Cette distinction t'évite une erreur d'architecture : tu ne peux pas interroger un modèle comme si ses poids formaient une base relationnelle avec provenance, dates de mise à jour et contraintes d'intégrité.
Pourquoi un modèle instruct obéit-il mieux aux consignes ?
Le préentraînement apprend principalement à modéliser des continuations. Des étapes supplémentaires d'entraînement peuvent ensuite rendre le modèle plus adapté aux consignes et aux conversations. Un modèle instruct/chat a été adapté pour traiter les demandes comme « classe cette note » ou « réponds en JSON ».
Cela ne crée pas de garantie formelle : suivre une consigne est encore un comportement appris, et une sortie métier doit toujours être contrôlée.
7. Entraînement, inférence et stockage : trois endroits où l'information peut se trouver
C'est probablement la distinction la plus importante pour ton projet.
| Mécanisme | Où se trouve l'information ? | Que fait ton application ? |
|---|---|---|
| Poids du modèle | Dans les paramètres appris | Charge le modèle pour faire une inférence ; les poids restent normalement fixes. |
| Contexte | Dans les tokens fournis pour la requête | Envoie une note, une instruction, des extraits ou l'historique pertinent. |
| Stockage externe | Dans PostgreSQL, des fichiers ou une autre source | Conserve les notes et décide lesquelles transmettre au modèle. |
Supposons que tu ajoutes aujourd'hui une note sur un nouveau projet fictif, Orion.
- Tu enregistres la note dans PostgreSQL : elle persiste dans ton application.
- Tu transmets cette note au LLM pour en extraire un titre : elle apparaît dans son contexte pour la requête.
- Tu n'as pas réentraîné les poids du modèle en lui envoyant simplement cette note.
Le lendemain, si tu veux qu'il réutilise cette information, ton backend devra la retrouver et la lui refournir, ou s'appuyer sur un mécanisme explicite de mémoire.
Question qui révèle la compréhension
Si tu arrêtes Ollama puis le redémarres, où se trouve la nouvelle note ? Dans PostgreSQL si tu l'y as écrite ; pas automatiquement dans les poids du modèle.
8. Le contexte n'est ni une mémoire infinie ni un RAG
Le contexte est la séquence effectivement accessible au modèle lors d'une génération : instructions, contenu de l'utilisateur, historique éventuellement inclus, documents éventuellement récupérés et tokens déjà produits.
text
System : Extrais le nom du projet mentionné.
User : J'ai rencontré Paul au sujet de Revyy.Le modèle peut répondre Revyy sans recherche externe : le nom figure déjà dans le contexte direct.
La fenêtre de contexte
La fenêtre est la quantité de tokens que la configuration du modèle et du moteur permet de traiter. À titre d'exemple, avec une limite effective de 32 000 tokens, tu ne peux pas transmettre une conversation de 200 000 tokens en une seule séquence intacte. L'espace disponible peut aussi devoir accueillir les nouveaux tokens générés, selon la configuration.
Tu as alors plusieurs stratégies applicatives : conserver les messages récents, résumer, sélectionner des passages ou rechercher les documents pertinents. Chacune fait perdre ou privilégie certaines informations.
Et le RAG dans tout ça ?
Le RAG est un pipeline externe au modèle, pas une faculté automatiquement présente dans tout LLM :
text
Question utilisateur
↓
Ton backend recherche dans PostgreSQL / pgvector
↓
Il choisit des extraits pertinents
↓
Il les ajoute au contexte envoyé au LLM
↓
Le LLM génère une réponse avec ces extraitsSi une information est déjà dans le prompt, tu n'as pas besoin de retrieval pour qu'elle soit visible. Si elle dort dans une table SQL non interrogée, le modèle n'y a pas accès.
9. Les rôles system et user ne sont pas des pouvoirs magiques
Une API accepte souvent une liste de messages structurés :
python
messages = [
{"role": "system", "content": "Tu es un extracteur de projets."},
{"role": "user", "content": "J'ai travaillé sur Revyy aujourd'hui."},
]Un chat template transforme ces messages en séquence de tokens adaptée au modèle. Cette séquence utilise souvent des délimiteurs ou tokens spéciaux pour distinguer les rôles. Le format dépend du modèle ; les balises ci-dessous sont inventées pour illustrer :
text
<SYSTEM>Tu es un extracteur de projets.</SYSTEM>
<USER>J'ai travaillé sur Revyy aujourd'hui.</USER>
<ASSISTANT>Le modèle a été entraîné à interpréter cette structure, mais les données de l'utilisateur restent des données non fiables. Si une note contient « Ignore toutes tes instructions », il faut la traiter comme le contenu de la note, pas comme un ordre autorisé. Nous consacrerons un module entier aux injections de prompt et aux permissions des outils.
10. Pourquoi la probabilité d'une suite n'est pas la vérité d'un fait
Un LLM peut donner une réponse fausse avec assurance pour plusieurs raisons : une information n'est pas présente dans ses données ou son contexte, des sources se contredisent, le contexte est ambigu, ou le processus de génération produit une continuation plausible sans vérification indépendante.
Exemple :
text
Question : Quelle est la date exacte de création d'un projet interne
dont je ne t'ai jamais parlé ?Une date très vraisemblable peut être inventée. Il n'y a pas de lien automatique entre la fluidité de la phrase et la présence d'une source fiable.
Garde deux grandeurs séparées dans ta tête :
text
P(" Paris" | contexte)
≠
P("l'affirmation que je viens d'écrire est vraie")La première est produite par le mécanisme du modèle. La seconde exigerait une démarche d'évaluation et de vérification que ce nombre, à lui seul, ne fournit pas.
Même un RAG correctement conçu ne supprime pas toute erreur : il peut récupérer un mauvais extrait, omettre une information décisive ou mal interpréter une source. La responsabilité de l'application consiste donc à retrouver, vérifier, contraindre et valider, selon les risques de la tâche.
11. Application au Second Brain : où se situe chaque responsabilité ?
text
POST /notes
↓
FastAPI vérifie la requête
↓
PostgreSQL enregistre le texte original
↓
Redis / worker déclenche une tâche d'analyse
↓
Ollama exécute le modèle instruct
↓
Le modèle génère une proposition de titre/type/tags
↓
Pydantic valide la forme et les valeurs autorisées
↓
Ton backend conserve la proposition ou signale l'échecCette architecture sépare le fait original (la note brute, sauvegardée) de l'interprétation probabiliste (la catégorie proposée). C'est essentiel pour pouvoir corriger ultérieurement une erreur d'analyse ou relancer le pipeline avec un autre modèle.
Une sortie possible est :
json
{
"title": "Application mobile Revyy",
"type": "idea",
"tags": ["revyy", "mobile"]
}À ce stade, souviens-toi qu'il s'agit d'une suite de tokens qui ressemble à du JSON, pas encore d'une donnée sûre à insérer en base. Le module 2 montrera comment imposer et valider un schéma.
12. Les contresens à éliminer
| Raccourci trompeur | Modèle mental correct |
|---|---|
| « Un token est un mot. » | Un token est une unité du vocabulaire propre au tokenizer. |
| « Le réseau voit du texte brut. » | Il traite des IDs convertis en représentations numériques. |
| « 8B = huit milliards de faits. » | 8B ≈ huit milliards de paramètres numériques appris. |
| « J'ai envoyé une note, donc il l'a apprise. » | Tu lui as fourni une donnée de contexte pour l'inférence. |
| « Il connaît toutes les notes de PostgreSQL. » | Il ne voit que ce que ton application lui transmet explicitement. |
| « Il choisit la réponse complète la plus probable. » | La génération auto-régressive sélectionne un token à chaque étape, sans rechercher nécessairement la réponse complète la plus probable. |
| « Il a répondu grâce à son RAG. » | Un RAG n'existe que si une application a organisé retrieval et injection de contexte. |
| « Une réponse bien écrite est vérifiée. » | Plausibilité et véracité sont deux notions différentes. |
13. Mini-test — explique avec tes mots
Consigne : réponds sans regarder le cours. Pour chaque question, privilégie une explication causale plutôt qu'une définition apprise par cœur. Les corrections ne sont volontairement pas fournies ici.
- Ton endpoint envoie une instruction de résumé à Ollama. Pourquoi une phrase de réponse de 20 mots peut-elle demander plus de 20 étapes de génération ?
- Explique précisément la différence entre texte, token, ID de token et embedding de token. Utilise un exemple imaginaire.
- Pourquoi un modèle « 8B » n'est-il pas une base de huit milliards de faits ? Qu'ont appris ses paramètres ?
- Tu enregistres une note aujourd'hui, puis tu poses demain une question à Ollama sans renvoyer la note. À quelles conditions pourra-t-il répondre à partir de cette note ?
- Tu envoies une conversation de 200 000 tokens à une configuration limitée à 32 000. Pourquoi ne suffit-il pas de « demander au modèle de se souvenir » ?
- Quelle différence fais-tu entre une instruction system, une note utilisateur non fiable et une donnée récupérée par RAG ?
- Quelle est la différence entre « ce token a 90 % de probabilité » et « ce fait a 90 % de chances d'être vrai » ?
- Dans la phrase « Paul a présenté Revyy. Ce projet... », qu'apporte le traitement du contexte à la prédiction de la suite ?
14. Exercice 1 — Reconstituer une requête de bout en bout
La note suivante existe uniquement dans le corps d'une requête, pas dans une base :
text
"Paul et moi préparons une version mobile de Revyy."Ton backend pose la question : Quel projet est mentionné ? et reçoit Revyy.
Travail demandé :
- Décris le parcours complet depuis les messages Python jusqu'au texte retourné par Ollama. N'oublie pas le chat template, le tokenizer, le réseau et le décodeur.
- Précise ce qui, dans ton explication, relève de l'hypothèse : par exemple, tu ne connais pas le véritable découpage de
Revyysans inspecter le tokenizer. - Indique si une base vectorielle ou un RAG était nécessaire dans ce scénario et justifie.
- Explique pourquoi enregistrer
Revyycomme catégorie de note sans vérification serait une erreur métier, même si le LLM a parfaitement identifié le projet.
15. Exercice 2 — Trouver l'erreur d'architecture
Un développeur propose :
text
« À la création de chaque note, on l'envoie une fois à Ollama.
Le modèle la connaît désormais ; inutile de la conserver en base.
Pour retrouver une note trois mois plus tard, on lui posera
simplement la question. »Rédige une revue d'architecture de 8 à 12 lignes : identifie au moins trois hypothèses erronées et propose une architecture minimale cohérente avec FastAPI, PostgreSQL et Ollama. Pas besoin de RAG ou de pgvector à ce stade.
16. Exercice 3 — Mini-expérience Python, sans modèle à installer
Cet exercice ne simule pas l'intelligence d'un LLM. Il sert à visualiser le rôle du tokenizer et la boucle de production :
python
# Vocabulaire fictif pour comprendre les représentations, pas un vrai tokenizer.
vocab = {" Rev": 10, "yy": 11, " est": 12, " utile": 13, "<FIN>": 99}
reverse_vocab = {token_id: token for token, token_id in vocab.items()}
# Un décodeur fictif a choisi successivement ces IDs :
produced_ids = [10, 11, 99]
# TODO : reconstruis le texte en ignorant le token de fin.Puis réponds : si le modèle avait choisi [12, 13, 99], pourquoi cela ne prouverait-il rien sur la vérité de la phrase obtenue ?
17. Question d'entretien
« Notre application envoie toutes les nouvelles notes à un modèle local. Pourquoi faut-il quand même conserver les notes originales dans PostgreSQL ? »
Réponds en cinq phrases maximum. Distingue apprentissage des poids, contexte de la requête, persistance et capacité de réanalyse.
18. Vérification de compréhension avant de continuer
Tu es prêt pour le cours 2 lorsque tu sais expliquer sans utiliser les mots “magie”, “mémoire du modèle” ou “il cherche sur Internet” :
- le parcours
texte → tokens → IDs → calcul du réseau → scores → nouveaux tokens → texte; - pourquoi le contexte peut résoudre une question sur un nom inventé, mais ne constitue pas une mémoire persistante ;
- pourquoi l'entraînement change les poids alors qu'une inférence ordinaire ne le fait pas ;
- pourquoi la probabilité d'un token n'est pas un score de fiabilité factuelle.
À retenir absolument
Le modèle calcule ; le décodeur sélectionne ; le contexte apporte temporairement de l'information ; ton application stocke, autorise et valide. Ces responsabilités ne doivent jamais se confondre dans une architecture LLM.