Skip to content

Distillation — comment économiser 10x sans perdre de qualité

TL;DR La distillation entraîne un petit modèle "student" (Haiku, Mistral Small, Llama 7B FT) à imiter un gros modèle "teacher" (Opus, GPT-4o, Mistral Large) sur une tâche précise. Trois approches : (1) response distillation — le teacher génère des outputs, on SFT le student dessus ; (2) feature distillation — on aligne les hidden states (rare, complexe) ; (3) API-native distillation (OpenAI, Anthropic) — la plateforme log les paires input/output et orchestre le training. En 2026, c'est la technique reine pour transformer un POC à 0.40€/req en prod à 0.04€/req. Workflow type : 5-20K exemples teacher → curation/filter → SFT student → eval contre teacher. Gain typique : qualité 90-95% du teacher pour 10-20% du coût.


🧠 Mental model

                Teacher (Opus, GPT-4o)
                  • 4.7/5 qualité
                  • 0.40€/requête
                  • 4-9s latence

                       │ génère N exemples
                       │ (input → output)

                ┌──────────────────┐
                │ Dataset distill  │
                │ 5K-50K paires    │
                │ + curation       │  ← filtrer hallu, dédupliquer
                └────────┬─────────┘
                         │ SFT (LoRA ou full)

                Student (Haiku, Mistral 7B)
                  • 4.3-4.5/5 qualité
                  • 0.04€/requête
                  • 0.6-1.5s latence

                   "miniaturisation par mimétisme"

Analogie senior : tu as un consultant senior (Opus 4.8) à 1500€/j et tu veux former un junior (Mistral 7B). Tu ne lui donnes pas un livre théorique — tu lui montres 5000 cas pratiques résolus par le senior, et tu lui demandes de reproduire. Au bout d'un certain temps, il fait 95% du job du senior, pour 10% du coût, sur cette tâche-là. Il ne devient pas généraliste, il devient spécialiste-junior.

Distillation ≠ fine-tuning classique : en FT classique tu pars d'un dataset humain. En distillation, le dataset vient d'un LLM, ce qui change deux choses :

  • Tu peux générer autant de data que tu veux (limite = budget API teacher).
  • Tu hérites des biais et erreurs du teacher (curation cruciale).

Comment un staff engineer raisonne avant de distiller

Distiller, c'est figer une politique de décision dans un modèle plus petit. Avant d'écrire une ligne de code, le staff engineer se pose 4 questions, dans cet ordre :

  1. Le teacher est-il assez bon ? Le plafond de qualité du student, c'est le teacher (moins la perte de distillation). Distiller un teacher à 4.2/5 te donne un student à ~3.8/5 au mieux. Si le teacher n'est pas en prod et validé, on ne distille pas — on améliore le teacher d'abord (fichier 01).
  2. Le ratio coût/volume justifie-t-il l'effort ? Rentabilité = (coût_teacher − coût_student) × volume_mensuel − coût_projet. En dessous de ~100K req/mois, le payback dépasse souvent 18 mois → on garde le teacher en cascade.
  3. La tâche est-elle stable ? Un student est un snapshot. Si la distribution d'inputs ou la définition du "bon output" change tous les mois, chaque dérive force une re-distillation. La distillation transforme un coût marginal (tokens) en coût fixe récurrent (MLOps). C'est un bon deal seulement si la tâche est stable.
  4. Quel axe j'optimise vraiment ? Coût ou latence ou souveraineté. Ces trois objectifs ne mènent pas au même student : optimiser la latence pousse vers un classifieur edge (DistilBERT), optimiser le coût vers un Haiku FT managé, optimiser la souveraineté vers un open-source self-host. Décider AVANT de choisir l'architecture du student.

Mental model de la perte de qualité. Le student n'apprend pas la tâche — il apprend à imiter la fonction input → output du teacher sur la distribution du dataset. Trois sources de perte cumulatives :

  • Perte de capacité : un 7B ne peut pas encoder tout ce qu'un Opus encode → il généralise moins bien hors-distribution.
  • Perte de curation : tout exemple bruité/halluciné dans le dataset est appris comme vérité.
  • Perte de couverture : ce que le dataset ne montre pas, le student ne l'apprend pas. Les edge cases rares en prod sont sous-représentés → c'est exactement là que le student s'effondre. D'où l'iterative distillation : générer du teacher ciblé sur les failures du student.

🛠️ Code minimal — response distillation

python
# 1. Génération du dataset avec le teacher — version production (async + résilience)
import anthropic, asyncio, json
from anthropic import AsyncAnthropic

# AsyncAnthropic pour paralléliser ; max_retries gère 429/529 avec backoff exponentiel.
client = AsyncAnthropic(max_retries=4, timeout=60.0)
inputs = load_inputs("data/prod_inputs_sampled.jsonl")  # 10K inputs réels

SEM = asyncio.Semaphore(20)  # borne la concurrence pour respecter ton TPM/RPM

async def gen_one(inp):
    async with SEM:
        try:
            resp = await client.messages.create(
                model="claude-opus-4-8",                 # teacher flagship 2026
                max_tokens=2048,
                # adaptive thinking : le teacher décide quand raisonner.
                # On distille la STRUCTURE du raisonnement, pas juste la réponse.
                thinking={"type": "adaptive", "display": "summarized"},
                output_config={"effort": "high"},
                # cache_control sur le préfixe stable (system) → ~10x moins cher
                # sur les 10K générations qui partagent le même system prompt.
                system=[{"type": "text", "text": SYSTEM_PROMPT,
                         "cache_control": {"type": "ephemeral"}}],
                messages=[{"role": "user", "content": inp["prompt"]}],
            )
        except anthropic.APIStatusError as e:
            return {"input": inp["prompt"], "error": e.type, "status": e.status}
        text = "".join(b.text for b in resp.content if b.type == "text")
        return {
            "input": inp["prompt"],
            "output": text,
            "teacher": "claude-opus-4-8",
            # log usage pour suivre le coût RÉEL de génération du dataset
            "usage": {"in": resp.usage.input_tokens,
                      "out": resp.usage.output_tokens,
                      "cache_read": resp.usage.cache_read_input_tokens},
        }

async def main():
    results = await asyncio.gather(*(gen_one(i) for i in inputs))
    with open("data/teacher_outputs.jsonl", "w") as out:
        for r in results:
            out.write(json.dumps(r) + "\n")

asyncio.run(main())

# 2. Curation — filtrer hallucinations, refus, outputs trop courts/longs
kept = []
for line in open("data/teacher_outputs.jsonl"):
    d = json.loads(line)
    if "error" in d:                                  continue  # appels échoués
    out = d["output"]
    if len(out) < 50 or len(out) > 4000:              continue
    if "I cannot" in out or "Je ne peux pas" in out:  continue  # refus du teacher
    if not passes_format_check(out):                  continue
    kept.append(d)
print(f"Kept {len(kept)}/{len(inputs)}")  # vise 70-90%

# 3. SFT du student (LoRA, cf. fichier 02)
#    - base: claude-haiku-4-5 via Bedrock FT, ou Mistral Small via Mistral platform,
#      ou Llama 7B self-host avec Axolotl

# 4. Eval student vs teacher
#    - 200 exemples hold-out (jamais vus par teacher ni student)
#    - LLM-as-judge (Opus 4.8 juge) avec rubric stricte

Note senior — pourquoi AsyncAnthropic et pas une boucle bête. Générer 10K exemples en synchrone séquentiel à ~5s/appel = 14h de wall-clock et un seul 529 fait planter le run. La version async + Semaphore + max_retries transforme ça en ~20-40 min, survit aux overloads, et le cache_control sur le system prompt fait que les 9 999 appels suivants paient le system à ~0.1x. Sur un dataset de distillation, le system prompt est par définition identique partout — ne pas cacher, c'est jeter 90% du coût d'input.


🎬 Cas d'usage concrets

1. Distillation agent customer support Claude → Haiku-FT

Marketplace française B2B (1.2M tickets/an). Agent built sur Sonnet + tools (commande, livraison, retour, facture) répond à 78% des tickets sans humain. Coût : 0.08€/ticket × 1.2M = 96 000€/an. Distillation : Sonnet log 30K conversations complètes (input + tool calls + outputs), curation par 2 agents support seniors, SFT Haiku 4.5 via Bedrock. Résultat : Haiku-FT résout 74% des tickets (vs 78%), coût 0.008€/ticket × 1.2M = 9 600€/an. Économie nette 86 400€/an, payback investissement (45k€) en 6 mois. Les 4% perdus = escalade humain, acceptable.

2. Distillation classifier complexe vers BERT small

ETI assurance veut classifier 200 000 sinistres/an en 35 catégories pour routing automatique. Claude Sonnet = 96% accuracy mais 0.005€/sinistre = 1 000€/an + latence 3s. Distillation vers DistilBERT (66M params) : Sonnet annote 50K sinistres, SFT DistilBERT en classification head, déploiement sur CPU (pas même de GPU !). Résultat : 94% accuracy, 30ms latence, coût marginal (~ 0). ROI : économie 1k€/an mais surtout latence ×100 et déploiement edge possible (filiales sans cloud).

3. Distillation voice agent pour assistant médical

Plateforme téléconsultation FR utilise GPT-4o-realtime pour des call agents (prise de rdv, triage). Coût 0.45€/min. Distillation vers un stack maison : Whisper Large (FR) → Mistral Small FT (logic) → ElevenLabs Flash (TTS). Le Mistral Small est FT sur 20K conversations GPT-4o, curated par médecins. Latence end-to-end 600ms (vs 800ms), coût 0.05€/min. À 200K minutes/mois, économie 960k€/an. Bonus souveraineté : data jamais chez OpenAI (HDS compliance).


🛠️ Exemple end-to-end — distillation assistant compta via OpenAI Distillation API

Contexte client : éditeur SaaS compta français (50K PME utilisatrices). Assistant IA répond aux questions des utilisateurs sur leur compta ("comment passer une écriture de provision ?", "TVA déductible sur cadeaux client ?"). Aujourd'hui : GPT-4o à 8K requêtes/jour = 240K/mois × 0.12€ = 28 800€/mois. Objectif : passer à GPT-4o-mini distillé, viser 90%+ qualité, coût /10.

Étape 1 — Setup logging Anthropic-style ou OpenAI distillation

OpenAI propose nativement le store=true + metadata pour log automatique vers leur distillation API. Anthropic supporte via Bedrock + custom logging S3.

python
# app.py — production, on log les paires
from openai import OpenAI
client = OpenAI()

def answer_compta(question: str) -> str:
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": SYSTEM_COMPTA_FR},
            {"role": "user", "content": question},
        ],
        store=True,                                  # store pour distillation
        metadata={"task": "compta_qa", "version": "v3"},
    )
    return resp.choices[0].message.content

Après 2 semaines, ~110K paires loggées avec metadata task=compta_qa.

Étape 2 — Curation

python
# curate.py
import json
from openai import OpenAI
client = OpenAI()

paires = client.chat.completions.list(
    metadata={"task": "compta_qa"},
    limit=100_000,
)

# Filtres
def keep(p):
    out = p.choices[0].message.content
    if len(out) < 80 or len(out) > 3000:               return False
    if "je ne peux pas" in out.lower():                return False
    if not re.search(r"(compte|écriture|TVA|bilan)", out, re.I): return False
    return True

filtered = [p for p in paires if keep(p)]
print(f"{len(filtered)} paires retenues sur {len(paires)}")
# ~ 78 000 retenues

# Dédup via embeddings (questions trop similaires)
# Stratification par catégorie compta (provisions, TVA, immobilisations...)
# Cible : 25 000 paires variées de haute qualité

Étape 3 — Lancer la distillation

python
# distill.py — via OpenAI Distillation
job = client.fine_tuning.jobs.create(
    training_file=upload_id,                # paires curées au format JSONL
    model="gpt-4o-mini-2024-07-18",
    suffix="compta-fr-v1",
    hyperparameters={"n_epochs": 3, "learning_rate_multiplier": 0.5},
)
# coût : ~ 25K paires × 800 tokens avg × $25/1M = ~500$
# durée : 2-4h

Étape 4 — Eval avant prod

python
# eval.py — 200 questions hold-out, jugées par GPT-4o avec rubric
EVAL_PROMPT = """Note la réponse suivante sur 5 :
- Exactitude comptable (poids 3)
- Clarté (poids 1)
- Mention article PCG / CGI si pertinent (poids 1)
Réponds en JSON: {"score": float, "raison": str}"""

scores_teacher = []  # GPT-4o
scores_student = []  # GPT-4o-mini distillé

for q in hold_out:
    t = client.chat.completions.create(model="gpt-4o", messages=[...])
    s = client.chat.completions.create(model="ft:gpt-4o-mini-compta-fr-v1", messages=[...])
    judge = client.chat.completions.create(model="gpt-4o", messages=[...EVAL_PROMPT...])
    scores_teacher.append(...)
    scores_student.append(...)

print("Teacher avg:", mean(scores_teacher))   # 4.42 / 5
print("Student avg:", mean(scores_student))   # 4.18 / 5  (94% du teacher)

Étape 5 — Rollout progressif

python
# router.py — A/B 20% trafic student les 2 premières semaines
import random
def route(question):
    if random.random() < 0.20:
        return "ft:gpt-4o-mini-compta-fr-v1"
    return "gpt-4o"

# Monitoring : satisfaction utilisateur (👍/👎 dans UI), taux d'escalade humain
# Si stable 2 semaines → 50%, puis 80%, puis 95% (5% Sonnet gardé en safety net)

Étape 6 — ROI

Avant : 240K req/mois × 0.12€ = 28 800€/mois
Après : 240K req/mois × 0.012€ (mini) × 0.95 + 240K × 0.12€ × 0.05 = 5 130€/mois

Économie : 23 670€/mois = 284 000€/an
Investissement initial :
  - Setup logging + curation (15j) : 18 000€
  - Distillation job + eval (5j) : 6 500€
  - Rollout + monitoring (10j) : 12 000€
  - Total : 36 500€
Payback : 1.5 mois

🎯 Patterns courants

  • Log d'abord, distill après : si tu n'as pas instrumenté le logging des paires teacher en prod, tu repars de zéro. Toujours store=true sur OpenAI ou logging S3 chez Anthropic dès J1.
  • Curation = 60% du résultat : filtrer hallu, refus, outputs trop courts/longs, dédupliquer par embedding similarité.
  • Stratification : répartir le dataset par catégorie/intent pour éviter qu'il soit dominé par 1 cas fréquent.
  • Iterative distillation : v1 student sur 5K paires → identifier les failures → générer 2K paires teacher ciblées sur ces failures → v2 student.
  • Cascade : router 80% trafic vers student, 20% (les durs) vers teacher. Le routing peut être un classifier ou un score de confidence.
  • Multi-task distillation : 1 student fine-tuné sur N tâches du teacher, économie d'échelle.
  • Distill un agent, pas juste les outputs : capturer aussi tool calls, plans, raisonnement. Le student doit reproduire la structure de décision.

🧮 Choisir l'architecture du student — tradeoff table

Le choix du student découle de l'axe optimisé (cf. mental model). Tableau de décision senior :

StudentQuand le choisirQualité rétenueLatenceCoût marginalDéploiementPiège
Haiku 4.5 FT (Bedrock)Tâche générative, garder la "fluidité" LLM, pas d'infra ML90-95%0.4-1s~5$/1M outManagé, zéro opsCoût FT + lock-in plateforme
Mistral Small / Llama 8B FT (self-host)Souveraineté (HDS, RGPD), volume très élevé, contrôle total88-94%0.3-0.8scompute GPUvLLM/TGI (fichier 04)Tu portes l'ops + le scaling GPU
DistilBERT / classifieurTâche = classification/extraction structurée bornée92-96% sur la tâche10-30ms CPU~0 (CPU)Edge possible, pas de GPUInutilisable hors classification
Cascade student+teacherDistribution longue-traîne, certains cas trop durs~teachervariablehybriderouter + 2 modèlesComplexité du routing

Heuristique de décision : si l'output est une classe ou une structure bornée → classifieur (gain latence ×100, coût ~0). Si l'output est du texte libre → LLM FT (Haiku managé si tu veux zéro ops, open-source si souveraineté/volume). Si la distribution a une longue traîne dure → cascade (student rapide + escalade teacher sur low-confidence).


🏭 Concerns production — ce qu'un junior oublie

1. Observabilité du student en prod. Un student dérive silencieusement : la distribution d'inputs évolue, et le student — figé — ne suit pas. Instrumenter :

  • Drift d'inputs : sampler 1% des inputs, comparer leur distribution (embeddings) à celle du dataset de distillation. Alerte si KL-divergence dépasse un seuil.
  • Shadow eval async : sampler 1% des outputs student, les faire juger par le teacher (Opus 4.8) en async/batch. Si la rétention qualité chute sous 85%, déclencher une re-distillation.
  • Logger usage partout : sur le teacher pendant la génération (coût du dataset) ET sur le student en prod (coût marginal réel vs estimé).

2. Sécurité & data. Le dataset de distillation contient des inputs prod réels → souvent du PII. Avant de l'envoyer à une plateforme FT : anonymiser/pseudonymiser. En contexte HDS/RGPD, c'est exactement l'argument pour le student self-host : le teacher ne voit la data qu'au moment de générer le dataset (anonymisé), le student tourne on-prem.

3. Versioning & rollback. Un student est un artefact versionné comme du code. compta-fr-v2 ne doit JAMAIS écraser v1 sans A/B. Garder le teacher branché en safety net (5% du trafic) permet un rollback instantané si v2 régresse. Pinner chaque version, garder l'eval set hold-out immuable entre versions pour comparer pommes à pommes.

4. Coût caché de la génération. Le coût dominant d'un projet distillation n'est souvent PAS le training — c'est la génération du dataset teacher. 30K exemples × 1500 tokens out × Opus 4.8 (25$/1M) = ~1125$ d'output seul. Le prompt caching sur le system partagé et l'usage de Haiku 4.5 comme teacher quand c'est suffisant sont les deux leviers majeurs.


🔄 Versions & écosystème 2026

  • OpenAI Distillation API : workflow natif store=true + UI distillation dans dashboard. Gold standard UX.
  • Anthropic : pas d'API distillation native, mais logging via Bedrock + Claude FT (Haiku 4.5) marche bien. Anthropic Console expose les requêtes pour export.
  • Mistral : plateforme FT, on apporte son dataset (que l'on peut générer avec Mistral Large).
  • DistillKit (Arcee AI) : framework OSS, supporte response + feature distillation.
  • TextBrewer : framework distillation BERT-style, vieux mais robuste pour classifiers.
  • Predibase Adapters Hub : adapters distillés pré-entraînés sur tâches communes.
  • Together AI / Anyscale : managed distillation pour open-source models.
  • Synthesized data revolution : 2026 = explosion des plateformes (Synthesized.io, Mostly AI, Gretel) qui combinent distillation + génération synthétique pour data privacy.

⚠️ Pitfalls

  1. Distill un teacher buggy : si Opus hallucine 3% des outputs, ton student apprend à halluciner. Curation OBLIGATOIRE.
  2. Pas d'eval blind : juger student avec teacher = circulaire. Toujours hold-out humain ou tiers modèle.
  3. Dataset déséquilibré : 80% du trafic = 1 type de question → student excellent là, nul ailleurs. Stratifier.
  4. Refus du teacher loggés : "Je ne peux pas répondre" devient une réponse fréquente apprise par le student. Filtrer.
  5. Légalité OpenAI ToS : tu ne peux pas distill GPT pour entraîner un modèle qui te concurrence (interdit), mais distill via Distillation API pour ton usage propre = OK.
  6. Forgetting général : le student devient pointu sur la tâche mais perd sa "polyvalence". Mesurer aussi sur tâches générales si l'app a besoin de fallback.
  7. Mismatch system prompt : student FT avec un system prompt précis ne marche pas si on change le system prompt en prod.
  8. Inflation du training set : "plus c'est gros mieux c'est" est faux. 5K curated > 50K bruts.
  9. Pas de versioning : gpt-4o-mini-compta-fr deuxième version écrase la première sans A/B = catastrophe en cas de régression.
  10. Coût de génération teacher sous-estimé : 30K exemples × 1500 tokens × 0.15€/1K = 6750€ rien que pour générer le dataset.

💰 Pricing / ROI client

Coût de génération teacher (générer le dataset) :

  • 10K exemples × 1500 tokens output × Opus 4.8 (25$/1M output) ≈ 375$ (~350€) en output ; ajoute l'input (system + prompt). Avec prompt caching sur le system partagé, l'input retombe à ~0.1x → le coût dominant devient l'output.
  • 10K exemples × 1500 tokens output × Haiku 4.5 (5$/1M output) ≈ 75$ (~70€) — Haiku 4.5 comme teacher "économique" quand la tâche est assez simple.
  • 10K exemples via GPT-4o (10$/1M output) ≈ 150$ (~140€)

Tarifs canoniques 2026 (input / output par 1M tokens). Flagship Opus 4.8 = claude-opus-4-8, 5$ / 25$ (contexte 1M). Milieu de gamme Sonnet 4.6 = claude-sonnet-4-6, 3$ / 15$. Économique Haiku 4.5 = claude-haiku-4-5, 1$ / 5$. Le student distillé vise à reproduire le teacher (Opus 4.8) pour le coût d'un Haiku FT ou d'un open-source self-host.

Coût de distillation (le training lui-même) :

  • OpenAI GPT-4o-mini FT : 25$/1M training tokens, soit 600-1500$ pour un projet typique
  • Anthropic Haiku FT via Bedrock : variable, ~ 1000-3000€
  • Open-source LoRA self-host : 20-100€ de compute (cf. fichier 02)

Tarification mission distillation freelance :

  • Audit + design (5j) : 6 000€
  • Pipeline logging + curation (10-15j) : 12-18 000€
  • Distillation + eval (5-8j) : 6-10 000€
  • Rollout + monitoring (5j) : 6 000€
  • Total : 30-45k€, ROI typique 6-18 mois selon volume client

Heuristique senior : la distillation est rentable dès que volume mensuel > 100K requêtes ou que latence devient un blocker.


🧪 Testing / Eval

  • Pyramid eval identique à FT (cf. 01-when-to-fine-tune.md).
  • Comparaison directe teacher/student : 200-500 questions hold-out, LLM-as-judge + sample human review.
  • Metric "rétention qualité" : score_student / score_teacher × 100. Cible 90%+, acceptable 85%+.
  • Stress test catégoriel : 20 exemples par catégorie métier, vérifier aucune ne s'effondre.
  • Drift monitoring : en prod, sampler 1% des outputs student, faire juger en async par Opus pour détecter dégradation.
  • Adversarial probes : 30 prompts hostiles ou edge cases (négations, ambiguïtés) à passer avant rollout.

🔁 Quand utiliser / éviter

Utilise distillation quand :

  • Volume élevé (>100K req/mois) où le coût teacher est douloureux.
  • Latence est un problème UX (chat temps réel, voice).
  • Tâche stable, bien définie, mesurable.
  • Tu as déjà du trafic prod (paires teacher gratuites par logging).
  • Souveraineté / HDS : student tourne on-prem, teacher juste pour générer dataset.

Évite distillation quand :

  • Tâche évolue chaque mois (re-distill coûteux).
  • Volume faible (< 20K req/mois) : pas rentable.
  • Tu n'as pas encore validé le teacher fonctionne (distill un POC bancal = catastrophe).
  • L'app a besoin de polyvalence (student perd les capacités hors-domaine).
  • Pas d'eval set construit.

🏋️ Exercices

Progressifs, durs, orientés "défends ta décision en prod". Chaque exercice escalade : implémenter → mesurer → casser → défendre le chiffre.

Exercice 1 — Pipeline de génération résilient

Objectif : générer un dataset de distillation de 5K exemples avec AsyncAnthropic, qui survit aux 429/529, log le coût réel via usage, et exploite le prompt caching sur le system partagé.

Indice/Solution : AsyncAnthropic(max_retries=4), asyncio.Semaphore(20) pour borner la concurrence sous ton TPM, asyncio.gather. Mettre cache_control: {"type": "ephemeral"} sur le bloc system. Vérifier que resp.usage.cache_read_input_tokens > 0 à partir du 2ᵉ appel — sinon un invalidateur silencieux (timestamp, JSON non trié) casse le cache. Sommer les usage pour produire le coût total réel et comparer à ton estimation a priori (tu vas voir que tu sous-estimais l'input).

Exercice 2 — Curation qui change le résultat

Objectif : prendre un dataset brut de 5K paires teacher, construire un pipeline de curation (filtres longueur/refus/format + dédup par embeddings + stratification par catégorie) et mesurer l'impact : entraîner deux students, un sur 5K brut, un sur 2.5K curé, et montrer lequel gagne.

Indice/Solution : dédup = clusteriser les embeddings des inputs (cosine > 0.95 = doublon), garder 1 par cluster. Stratifier = répartir uniformément par intent/catégorie pour éviter que 80% du trafic d'un seul type domine. Le résultat attendu : 2.5K curé bat 5K brut sur l'eval hold-out — c'est le "5K curated > 50K bruts" rendu mesurable. Si ce n'est pas le cas, ton eval set est mal stratifié.

Exercice 3 — Eval blind anti-circulaire

Objectif : construire une eval qui ne triche pas. Beaucoup d'équipes jugent le student avec le teacher sur les mêmes exemples que ceux distillés → fuite. Construire un hold-out jamais vu, un LLM-as-judge avec rubric stricte, et calculer la métrique "rétention qualité" score_student / score_teacher.

Indice/Solution : 200-500 exemples hold-out tirés AVANT la génération du dataset (sinon ils fuitent). Juge = Opus 4.8 avec output_config.format (sortie structurée {score, raison}) plutôt qu'un parsing fragile. Ajouter un échantillon de revue humaine pour calibrer le juge. Piège classique : juger student et teacher dans le même prompt biaise le juge (effet d'ordre) — juger séparément, randomiser l'ordre.

Exercice 4 — Casse ton student, puis répare-le (iterative distillation)

Objectif : déployer un student v1, identifier ses failure modes en prod (stress test catégoriel + adversarial probes), générer du teacher ciblé sur ces failures, et produire un v2 qui ferme le gap — en prouvant qu'il ne régresse nulle part ailleurs.

Indice/Solution : 20 ex/catégorie pour trouver la catégorie qui s'effondre ; 30 prompts hostiles (négations, ambiguïtés). Une fois les failures identifiés, générer 2K nouveaux exemples teacher biaisés vers ces cas durs, ré-entraîner. Le piège : v2 peut gagner sur les failures ET régresser ailleurs (catastrophic forgetting partiel) → toujours re-passer l'eval set complet v1, pas juste les nouveaux cas. C'est l'eval set hold-out immuable qui rend la comparaison honnête.

Exercice 5 — Défends le ROI devant le CFO

Objectif : on te donne un client à 240K req/mois sur Opus 4.8. Produire un business case complet : coût teacher actuel, coût de génération du dataset, coût FT, coût student, économie nette annuelle, payback. Puis défendre chaque chiffre quand on te challenge ("et si le volume baisse de 40% ?", "et si la tâche change dans 3 mois ?").

Indice/Solution : décomposer coût_actuel = volume × prix_opus_4.8 ; coût_cible = 95% × volume × prix_student + 5% × volume × prix_opus (safety net) ; invest = génération_dataset + FT + curation + rollout. Défense des sensibilités : à −40% volume le payback s'allonge → montrer le seuil de rentabilité (volume mensuel minimum). Si la tâche change tous les 3 mois, intégrer le coût récurrent de re-distillation dans le TCO — c'est souvent ce qui tue le projet, pas le coût initial.

Exercice 6 (bonus, hard) — Distille un agent, pas juste des outputs

Objectif : distiller un agent (avec tool calls, plan, raisonnement) et non un simple input → texte. Le student doit reproduire la structure de décision : quels tools, dans quel ordre, avec quels arguments.

Indice/Solution : logger les conversations complètes du teacher incluant tool_use/tool_result et les blocs de raisonnement (adaptive thinking, display: "summarized"). Le dataset n'est plus (prompt, texte) mais (prompt, trace_de_décision). Eval = comparer non seulement l'output final mais la trajectoire (les bons tools appelés). Piège : un student peut donner la bonne réponse finale via une mauvaise trajectoire (tool inutile, ordre faux) → en prod ça explose le coût et la latence. Mesurer la fidélité de trajectoire, pas juste la réponse.


🎤 En entretien

Q : Quelle est la différence entre fine-tuning et distillation, et quand choisir l'un plutôt que l'autre ? R : FT part d'un dataset humain ; distillation génère le dataset depuis un teacher LLM. On distille quand on a déjà un gros modèle qui marche en prod et qu'on veut sa qualité au coût d'un petit modèle, sur une tâche stable et à fort volume — l'enjeu n'est pas la qualité absolue mais le ratio coût/latence à qualité quasi-constante.

Q : Ton student distillé perd 8% de qualité par rapport au teacher. Comment décides-tu si c'est acceptable ? R : Ça dépend du coût d'erreur métier, pas d'un seuil absolu : sur du routing de tickets, les 8% partent en escalade humaine (acceptable) ; sur du médical/légal, non. La bonne réponse senior, c'est la cascade — student sur 95% du trafic, escalade teacher sur les low-confidence, ce qui récupère la qualité là où ça compte sans perdre l'économie.

Q : Comment éviter qu'un student distillé hérite des hallucinations du teacher ? R : Curation obligatoire (filtrer refus/hallu/format) ET surtout eval blind sur un hold-out jamais vu, jugé par un tiers — jamais juger le student avec le teacher sur les exemples distillés, c'est circulaire. En prod, shadow eval async sur 1% des outputs pour détecter la dérive.

Q : Le coût dominant d'un projet de distillation, c'est le training, vrai ou faux ? R : Faux — c'est souvent la génération du dataset teacher. 30K exemples × 1500 tokens out × Opus 4.8 à 25$/1M ≈ 1125$ rien qu'en output, avant le training. Les leviers : prompt caching sur le system partagé (input à ~0.1x), Haiku 4.5 comme teacher quand la tâche le permet, et async pour ne pas payer 14h de wall-clock séquentiel.


🔗 Liens

  • OpenAI Distillation docs (UI dashboard, store=true workflow)
  • Anthropic Bedrock FT — Haiku fine-tuning
  • Paper "Distilling Step-by-Step" (Google 2023) — distill avec raisonnement
  • Paper "Orca" (Microsoft) — distill avec explanation traces
  • DistillKit (Arcee AI) — framework OSS
  • Hamel Husain — "Fine-Tuning is for Form, Not Facts" — applicable distillation
  • Fichier voisin : 01-when-to-fine-tune.md (cadre décisionnel)
  • Fichier voisin : 02-lora-peft.md (technique FT pour open-source student)
  • Fichier voisin : 04-vllm-tgi-llamacpp.md (servir le student)

Bibliothèque tech perso — Achref