Pourquoi ce module ?
Avant de construire un chatbot, un agent vocal, ou un pipeline RAG, il faut comprendre la machine qui est au coeur de tout : le Large Language Model.
Un LLM n'est pas magique. C'est un programme mathematique qui prend du texte en entree, calcule des probabilites, et produit du texte en sortie. Tout le reste — agents, RAG, tool calling — n'est que de la plomberie autour de ce mecanisme.
A la fin de ce module, tu sauras expliquer a n'importe qui comment un LLM fonctionne, ce qu'est un token, ce que fait l'attention, et pourquoi la temperature change le comportement du modele. C'est le socle.
Les tokens — l'alphabet des LLMs
Un LLM ne lit pas des lettres ni des mots entiers. Il lit des . C'est la premiere chose a comprendre.
Prenons un exemple concret. La phrase :
"Bonjour, je cherche un contrat d'electricite"Pourrait etre decoupee en tokens comme ca (ca depend du tokenizer) :
["Bon", "jour", ",", " je", " cherche", " un", " contrat", " d", "'", "elect", "ric", "ite"]Remarque : certains mots sont coupes en morceaux ("electricite" → 3 tokens). C'est parce que le tokenizer a appris que "elect", "ric" et "ite" sont des sous-unites frequentes.
import tiktoken # tokenizer d'OpenAI (pip install tiktoken)
enc = tiktoken.encoding_for_model("gpt-4o")
tokens = enc.encode("Bonjour, je cherche un contrat d'electricite")
print(f"{len(tokens)} tokens") # → 12 tokens
print(enc.decode_single_token_bytes(t) for t in tokens])
# Avec Hugging Face Transformers (PyTorch)
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1")
result = tokenizer("Bonjour, je cherche un contrat d'electricite")
print(result["input_ids"]) # → [1, 9038, 28725, ...]
print(len(result["input_ids"])) # → 13 tokens (different ! chaque modele a son tokenizer)| Concept | Exemple | Impact pratique |
|---|---|---|
| 1 token | "hello" | Les mots courants = 1 token |
| Multi-token | "electricite" → 3 tokens | Les mots longs ou rares coutent plus |
| Token special | "<|endoftext|>" | Signale la fin d'une sequence au modele |
| ~100 tokens | ~75 mots en anglais | Regle rapide pour estimer les couts |
Les embeddings — donner du sens aux mots
OK, le modele a decoupes le texte en tokens. Mais un ordi ne comprend pas les mots — il comprend les nombres. Comment on passe de "chat" a quelque chose qu'un ordinateur peut manipuler ?
Voici a quoi ca ressemble en simplifie :
import torch
from transformers import AutoTokenizer, AutoModel
# Charger un modele d'embeddings (sentence-transformers)
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
model = AutoModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
# Embedder des mots
phrases = ["roi", "reine", "banane"]
inputs = tokenizer(phrases, padding=True, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
# Mean pooling sur les tokens → un vecteur par phrase
embeddings = outputs.last_hidden_state.mean(dim=1)
print(embeddings.shape) # → torch.Size([3, 384]) (3 phrases, 384 dimensions)
# Similarite cosinus
from torch.nn.functional import cosine_similarity
print(cosine_similarity(embeddings[0], embeddings[1], dim=0)) # roi↔reine → 0.72 (proche !)
print(cosine_similarity(embeddings[0], embeddings[2], dim=0)) # roi↔banane → 0.11 (loin)La propriete magique des embeddings, c'est qu'on peut faire de l'arithmetique dessus :
roi - homme + femme ≈ reineCe n'est pas une metaphore — ca marche vraiment avec les vecteurs. Le modele a appris ces relations en lisant des milliards de textes.
L'attention — le coeur du Transformer
C'est LE concept qui a revolutionne le NLP en 2017 avec le papier "Attention Is All You Need". Avant l'attention, les modeles lisaient le texte mot par mot, de gauche a droite (RNN). Le probleme : pour des phrases longues, le modele "oubliait" le debut.
Prenons une phrase :
"Le chat dort sur le canape car il est fatigue"Quand le modele traite le mot "il", l'attention lui permet de "regarder en arriere" et de comprendre que "il" se refere a "chat", pas a "canape". Il attribue un poids eleve a "chat" et un poids faible a "canape".
Mecanisme d'attention simplifie
"il" donne un poids fort a "chat" (0.72) et faible a "canape" (0.03)
En pratique, le Transformer utilise du multi-head attention: plusieurs "tetes" d'attention en parallele, chacune specialisee dans un type de relation (syntaxe, semantique, coreference...).
Le calcul mathematique (simplifie) :
Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V
Q = Query → "Qu'est-ce que je cherche ?" (le token actuel)
K = Key → "Qu'est-ce que je contiens ?" (chaque autre token)
V = Value → "Quelle information je donne ?" (le contenu a transmettre)
Le softmax normalise les poids pour qu'ils somment a 1.
sqrt(d_k) empeche les valeurs de devenir trop grandes.import numpy as np
def attention(Q, K, V):
"""Self-attention en 4 lignes de NumPy."""
d_k = Q.shape[-1]
scores = Q @ K.T / np.sqrt(d_k) # similarite entre Q et K
weights = np.exp(scores) / np.exp(scores).sum(axis=-1, keepdims=True) # softmax
return weights @ V # moyenne ponderee des valeurs
# Exemple : 4 tokens, dimension 8
np.random.seed(42)
Q = np.random.randn(4, 8) # 4 tokens, chacun un vecteur de dim 8
K = np.random.randn(4, 8)
V = np.random.randn(4, 8)
output = attention(Q, K, V)
print(output.shape) # → (4, 8) — chaque token recoit un vecteur "enrichi"
# En PyTorch, c'est une seule ligne :
import torch
import torch.nn.functional as F
attn_output = F.scaled_dot_product_attention(
torch.randn(4, 8), torch.randn(4, 8), torch.randn(4, 8)
) # PyTorch gere le scaling et le softmax pour toiArchitecture complete d'un Transformer
Maintenant qu'on a les briques (tokens, embeddings, attention), assemblons le puzzle complet.
Tokenization
Le texte brut est decoupe en tokens numeriques.
Embedding + Positional Encoding
Chaque token recoit un vecteur (embedding) + une information de position. Sans la position, le modele ne saurait pas l'ordre des mots.
N couches de Transformer
Chaque couche contient : multi-head attention → normalisation → feed-forward network → normalisation. GPT-4 a ~120 couches. Claude ~96. Chaque couche raffine la comprehension.
Couche de sortie (logits)
Le dernier vecteur est projete sur le vocabulaire entier (~100k tokens). Chaque token recoit un score (logit). Plus le score est haut, plus le modele pense que ce token est la bonne suite.
Sampling
Les logits sont convertis en probabilites (softmax), puis un token est choisi selon une strategie (greedy, top-k, top-p...). Ce token est ajoute a la sequence, et on recommence a l'etape 1.
Pipeline simplifie d'un LLM
"Bonjour, je"
|
[ Tokenizer ]
|
[82, 4521, 29]
|
[ Embedding + Position ]
|
[ 0.21, -0.3, ... ] x 3 tokens
|
┌──────────────────────┐
│ Transformer Layer 1 │ ← attention + feed-forward
│ Transformer Layer 2 │
│ ... │
│ Transformer Layer N │
└──────────────────────┘
|
[ Projection → vocabulaire ]
|
"cherche" (probabilite: 0.12)
"veux" (probabilite: 0.08)
"suis" (probabilite: 0.07)
|
[ Sampling ] → "cherche"
|
Output: "Bonjour, je cherche"
→ on recommence avec cette nouvelle sequence
Comment le modele genere du texte
Quand le modele a calcule les probabilites pour le prochain token, comment choisit-il ? Il y a plusieurs strategies :
| Strategie | Principe | Usage |
|---|---|---|
| Greedy | Toujours choisir le token le plus probable | Rapide mais repetitif et ennuyeux |
| Top-k | Choisir parmi les k tokens les plus probables | Plus de variete, mais k est arbitraire |
| Top-p (nucleus) | Choisir parmi les tokens dont la proba cumulee atteint p | Le plus utilise — s'adapte au contexte |
| Temperature | Aplatit ou accentue les probabilites avant sampling | Controle la 'creativite' globale |
En pratique, on combine temperature + top-p. C'est ce que tu controles quand tu appelles l'API.
Temperature, top-p, top-k
Ces trois parametres sont les boutons que tu tournes le plus souvent quand tu utilises un LLM. Comprends-les bien.
Temperature = 0.0 → "Le chat est sur le tapis."
Toujours la meme reponse. Factuel.
Temperature = 0.7 → "Le chat somnole paisiblement sur le tapis."
Un peu de variete. Bon equilibre.
Temperature = 1.5 → "Le chat cosmique danse sur un tapis de nebuleuses."
Creatif mais peut delirer.import torch
import torch.nn.functional as F
# Logits bruts du modele (scores avant softmax)
logits = torch.tensor([2.0, 1.5, 0.8, 0.3, -0.5]) # 5 tokens candidats
labels = ["chat", "chien", "oiseau", "dragon", "dinosaure"]
# Temperature = 1.0 (distribution naturelle)
probs_t1 = F.softmax(logits / 1.0, dim=0)
print(dict(zip(labels, probs_t1.tolist())))
# → {'chat': 0.42, 'chien': 0.25, 'oiseau': 0.13, 'dragon': 0.08, 'dinosaure': 0.03}
# Temperature = 0.3 (quasi-deterministe)
probs_t03 = F.softmax(logits / 0.3, dim=0)
# → {'chat': 0.89, 'chien': 0.09, ...} ← "chat" domine
# Temperature = 2.0 (tres aleatoire)
probs_t2 = F.softmax(logits / 2.0, dim=0)
# → {'chat': 0.28, 'chien': 0.23, ...} ← distribution aplatie
# Top-p sampling (nucleus)
def top_p_sample(logits, p=0.9):
probs = F.softmax(logits, dim=0)
sorted_probs, sorted_idx = torch.sort(probs, descending=True)
cumsum = torch.cumsum(sorted_probs, dim=0)
mask = cumsum - sorted_probs < p # garder jusqu'a cumsum >= p
filtered = sorted_probs * mask
return torch.multinomial(filtered / filtered.sum(), 1) # echantillonner| Param | Valeur basse | Valeur haute | Recommandation |
|---|---|---|---|
| temperature | 0 = deterministe | 2 = tres aleatoire | 0.7 pour du general, 0 pour de l'extraction |
| top_p | 0.1 = tres restrictif | 1.0 = tout garder | 0.9 pour du general |
| top_k | 1 = greedy | 100+ = large choix | Souvent ignore, top-p suffit |
Le context window — la memoire de travail
Le est la quantite de texte que le modele peut "voir" en meme temps. C'est sa memoire de travail.
| Modele | Context window | En mots (~) | Equivalent |
|---|---|---|---|
| GPT-3.5 | 4k tokens | ~3 000 mots | ~6 pages A4 |
| GPT-4o | 128k tokens | ~96 000 mots | Un roman entier |
| Claude 3.5 Sonnet | 200k tokens | ~150 000 mots | 2 romans |
| Claude Opus 4 | 1M tokens | ~750 000 mots | 10 romans |
| Gemini 2.5 | 1M tokens | ~750 000 mots | 10 romans |
Panorama des modeles (2024-2026)
Le paysage evolue tres vite. Voici les acteurs majeurs :
| Famille | Modeles | Forces | Faiblesses |
|---|---|---|---|
| OpenAI (GPT) | GPT-4o, GPT-5, o1, o3 | Polyvalent, bon en code, large ecosysteme | Closed-source, cher sur les gros modeles |
| Anthropic (Claude) | Claude 3.5 Sonnet, Claude Opus 4 | Excellent en analyse longue, suivi d'instructions, ethique | Closed-source, pas de fine-tuning public |
| Google (Gemini) | Gemini 2.5 Flash/Pro | Multimodal natif, context 1M, bon rapport qualite/prix | Moins bon en code complexe |
| Meta (Llama) | Llama 3.3, Llama 4 | Open-source, deployable en local, fine-tunable | Moins bon que les closed-source sur le raisonnement |
| Mistral | Mixtral, Mistral Large | Francais ! Open-source, bon en multilingue | Ecosysteme plus petit |
Pour ton stage chez Selectra, tu utiliseras probablement un modele closed-source (GPT ou Claude) via API pour l'agent vocal. La latence et la qualite du raisonnement comptent plus que le cout pour une demo.
Utiliser un LLM via API — premier appel
Assez de theorie. Voici comment on parle a un LLM en vrai. On utilise le Vercel AI SDK— c'est la librairie standard pour faire de l'IA en TypeScript.
Installer les dependances
npm install ai @ai-sdk/reactCreer la route API (serveur)
import { streamText, convertToModelMessages } from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: "anthropic/claude-sonnet-4.6", // via AI Gateway
system: "Tu es un assistant Selectra. Tu aides les clients a trouver le meilleur contrat d'energie.",
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}Creer le composant client (UI)
"use client";
import { useChat, DefaultChatTransport } from "@ai-sdk/react";
const transport = new DefaultChatTransport({ api: "/api/chat" });
export default function Chat() {
const { messages, status, sendMessage } = useChat({ transport });
const [input, setInput] = useState("");
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong>
{m.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span> : null
)}
</div>
))}
<form onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput("");
}}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</form>
</div>
);
}Quiz final
Verifions que les fondations sont solides avant de passer au Module 2.
Module 01 termine
Tu connais maintenant les fondations : tokens, embeddings, attention, architecture Transformer, parametres de generation, et comment faire ton premier appel API. Prochain module : Prompt Engineering — l'art de parler aux LLMs.