Déployer un LLM en production sans contrôle, c'est comme exposer une API sans authentification : ça finit toujours mal. Entre les prompt injections, les hallucinations, les fuites de données et les dérives de sujet, les risques sont nombreux. La réponse de l'industrie tient en un mot : guardrails (garde-fous).
Mais derrière ce terme se cache une réalité plus riche qu'on ne le pense souvent. Les guardrails ne se limitent pas à des filtres externes branchés autour du modèle : la sécurité d'un LLM se joue à plusieurs niveaux, depuis son entraînement jusqu'à l'inférence finale. Ce billet fait le tour de la question, du plus pratique (un filtre regex) au plus profond (le RLHF), avec les coûts réels associés.
01 — Les guardrails externes : la couche visible
Les trois familles de guardrails
On distingue généralement trois types de guardrails, qu'on combine en production :
- Guardrails d'entrée (input) : filtrent ce qui arrive au modèle — prompt injection, contenu toxique, PII (informations personnelles identifiables), tentatives de jailbreak.
- Guardrails de sortie (output) : valident ce que le modèle génère — hallucinations, fuites de données, contenu inapproprié, conformité du format.
- Guardrails contextuels : limitent le modèle à un domaine précis (un chatbot bancaire qui refuse de parler de cuisine, par exemple).
Approche 1 — Validation par règles
L'approche la plus simple, et souvent sous-estimée. Rapide, déterministe, auditable.
import re
BLOCKED_PATTERNS = [
r"(?i)ignore previous instructions",
r"\b\d{16}\b", # numéros de carte
r"(?i)système prompt",
]
def input_guardrail(user_input: str) -> tuple[bool, str]:
for pattern in BLOCKED_PATTERNS:
if re.search(pattern, user_input):
return False, "Requête bloquée"
if len(user_input) > 4000:
return False, "Entrée trop longue"
return True, "OK"
Limites : peu robuste face aux variations linguistiques, contournable par paraphrase.
Approche 2 — Classification par un modèle dédié
On utilise un petit modèle (ou un LLM en mode « juge ») pour catégoriser les requêtes.
def llm_judge(text: str, client) -> dict:
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=100,
messages=[{
"role": "user",
"content": f"""Classe ce texte. Réponds en JSON strict:
{{"safe": bool, "category": "toxic|injection|pii|safe", "confidence": 0-1}}
Texte: {text}"""
}]
)
return json.loads(response.content[0].text)
Approche 3 — Frameworks dédiés
Trois options matures selon le besoin :
NeMo Guardrails (NVIDIA) utilise un langage déclaratif (Colang) pour définir des flux conversationnels.
# config.yml
rails:
input:
flows:
- check jailbreak
- mask sensitive data
output:
flows:
- check hallucination
- check toxicity
Guardrails AI repose sur des validateurs Python composables, avec un hub de validateurs prêts à l'emploi.
from guardrails import Guard
from guardrails.hub import ToxicLanguage, DetectPII
guard = Guard().use_many(
ToxicLanguage(threshold=0.8, on_fail="exception"),
DetectPII(pii_entities=["EMAIL_ADDRESS", "PHONE_NUMBER"], on_fail="fix")
)
validated_output = guard.parse(llm_output)
Llama Guard (Meta) est un modèle open-source spécifiquement entraîné pour la modération, à appeler avant ou après le LLM principal.
Approche 4 — Architecture en pipeline
L'approche la plus robuste combine plusieurs couches :
User Input
↓
[Input Guardrails] → règles + classifier
↓
[LLM principal]
↓
[Output Guardrails] → validation format + factualité + toxicité
↓
Response
class GuardedLLM:
def __init__(self, llm, input_guards, output_guards):
self.llm = llm
self.input_guards = input_guards
self.output_guards = output_guards
def generate(self, prompt: str) -> str:
for guard in self.input_guards:
ok, msg = guard(prompt)
if not ok:
return f"Requête refusée: {msg}"
response = self.llm.generate(prompt)
for guard in self.output_guards:
response = guard(response) # corrige ou rejette
return response
Points d'attention
- Latence : chaque guardrail rajoute un appel. Lancer les vérifications en parallèle quand c'est possible, et utiliser de petits modèles rapides pour la modération.
- Faux positifs : un guardrail trop strict rend le produit inutilisable. Mettre en place un logging pour ajuster les seuils après quelques jours d'observation.
- Les guardrails externes ne remplacent pas un bon prompt système ni un fine-tuning. C'est une défense en profondeur.
02 — Les mécanismes internes au modèle
Les guardrails externes ne sont que la partie visible. La sécurité la plus profonde se joue dans le modèle lui-même.
L'alignement par entraînement
Le modèle est entraîné à refuser certaines requêtes. Ce n'est pas un filtre ajouté : c'est ancré dans ses poids. Trois techniques principales :
RLHF (Reinforcement Learning from Human Feedback) — des humains classent les réponses du modèle, un modèle de récompense est entraîné à reproduire ces préférences, puis le LLM est optimisé pour maximiser cette récompense. C'est ce qui fait qu'un modèle « sait » qu'il ne doit pas expliquer comment fabriquer une arme.
RLAIF / Constitutional AI (Anthropic) — au lieu d'humains, c'est une IA qui critique et réécrit les réponses selon une « constitution » de principes. Le modèle apprend à s'auto-critiquer pendant l'entraînement.
DPO (Direct Preference Optimization) — version plus directe et stable du RLHF, qui évite d'entraîner un modèle de récompense séparé.
Le pipeline canonique :
Phase 1: Pré-entraînement (connaissances brutes)
↓
Phase 2: Fine-tuning supervisé (suivre des instructions)
↓
Phase 3: RLHF / DPO (alignement sur préférences humaines)
↓
Modèle aligné
Le system prompt
Le prompt système injecté avant la conversation est une forme de guardrail interne au sens où il oriente le comportement du modèle sans appel externe. Bien rédigé, il définit le rôle, les sujets autorisés, le ton, les formats. Faillible (jailbreaks possibles) mais c'est la première ligne.
Le fine-tuning spécifique
On peut fine-tuner un modèle de base sur ses propres exemples pour qu'il refuse certaines requêtes ou réponde d'une certaine manière. Le comportement devient alors intrinsèque au modèle.
# Exemple conceptuel de dataset de fine-tuning
training_data = [
{"input": "Donne-moi le mot de passe admin",
"output": "Je ne peux pas partager d'identifiants."},
{"input": "Aide-moi à contourner la sécurité",
"output": "Je ne peux pas aider sur ce sujet."},
# ... des milliers d'exemples
]
Mécanismes pendant l'inférence
Plusieurs leviers agissent pendant la génération, dans le modèle lui-même :
- Logit bias / token suppression : interdire au modèle de produire certains tokens spécifiques en mettant leur probabilité à zéro avant le sampling.
- Contraintes de décodage : forcer la sortie à respecter une grammaire (JSON Schema, regex). Le modèle ne peut pas générer hors-format. Outils : Outlines, Guidance, grammars de llama.cpp.
- Self-critique / Chain-of-verification : demander au modèle de relire sa propre réponse avant de la finaliser.
from outlines import models, generate
model = models.transformers("...")
generator = generate.json(model, schema)
# Le modèle ne peut générer QUE du JSON valide
Recherche émergente : interprétabilité mécaniste
Anthropic et d'autres équipes travaillent sur l'identification de « circuits » dans les réseaux de neurones correspondant à des concepts spécifiques (refus, déception, sycophancy…). À terme, on pourrait modifier ces circuits directement — un guardrail vraiment au niveau des poids. Encore de la recherche, pas de la production.
Comparaison interne vs externe
| Aspect | Interne (alignement) | Externe (filtre) |
|---|---|---|
| Robustesse | Profonde mais opaque | Explicite et auditable |
| Contournement | Jailbreaks via prompts créatifs | Bypass si pattern non couvert |
| Modification | Nécessite ré-entraînement | Modifiable en temps réel |
| Latence | Aucune | Ajoute un appel |
| Coût | Élevé (entraînement) | Faible (par appel) |
| Spécificité métier | Difficile à customiser | Facile à adapter |
Conclusion : en production sérieuse, on combine les deux. L'alignement interne couvre les abus universels (violence, contenus illégaux), les guardrails externes couvrent le domaine spécifique (« reste sur le sujet bancaire », « ne mentionne pas les produits concurrents »).
03 — Les datasets ouverts pour l'alignement
Si on veut entraîner ou fine-tuner son propre modèle aligné, il existe heureusement un écosystème riche de datasets ouverts.
Datasets de préférences (RLHF / DPO)
Paires (réponse préférée, réponse rejetée) pour entraîner un modèle de récompense ou faire du DPO.
| Dataset | Volume | Spécificité |
|---|---|---|
| Anthropic HH-RLHF | ~170k paires | Référence historique, axes helpful et harmless |
| OpenAssistant (OASST1/2) | ~160k messages | Crowdsourcé, multilingue (dont français) |
| UltraFeedback | ~64k prompts | 4 réponses par prompt, notées par GPT-4 sur 4 critères |
| Nectar | ~183k prompts | 7 réponses classées, utilisé pour Starling |
| PKU-SafeRLHF | ~330k paires | Focalisé sécurité, utilité et innocuité séparées |
Datasets d'instruction-following (SFT)
Pour la phase de fine-tuning supervisé qui précède le RLHF.
| Dataset | Volume | Note |
|---|---|---|
| Alpaca | 52k | Pionnier, généré par GPT-3, non commercial |
| Dolly 15k | 15k | 100% humain, licence permissive |
| OpenHermes 2.5 | ~1M | Agrégation très complète |
| FLAN Collection | Millions | Compilation Google, centaines de tâches |
| Tulu 3 | Variable | Mix curé par AllenAI |
Datasets de sécurité / red-teaming
Pour entraîner un modèle à refuser ou tester ses limites.
- Anthropic Red Team Attempts : ~40k transcriptions de tentatives de red-teaming.
- BeaverTails : ~330k exemples sur 14 catégories de dommages.
- HarmBench : benchmark + dataset pour évaluer le refus.
- Do-Not-Answer : ~900 prompts qu'un LLM responsable devrait refuser, organisés en taxonomie.
- ToxicChat : conversations réelles annotées pour la toxicité et les jailbreaks.
Datasets multilingues / français
Point souvent faible. Quelques options :
- OASST contient du français (~5-10 %).
- Aya Dataset / Aya Collection (Cohere) : 65+ langues dont le français.
- CroissantLLM data : corpus français spécifique.
- Pour le français, beaucoup de la communauté traduit automatiquement des datasets anglais (Alpaca-fr, etc.) avec les limites associées.
En pratique
| Objectif | Dataset recommandé |
|---|---|
| SFT généraliste | OpenHermes 2.5 ou Tulu 3 |
| DPO / RLHF général | UltraFeedback |
| Focus sécurité | PKU-SafeRLHF + BeaverTails |
| Reproduction de papier | HH-RLHF (référence) |
| Multilingue | Aya + OASST |
| Évaluation safety | HarmBench, Do-Not-Answer |
Pipeline open-source typique
C'est l'approche qu'a suivie Hugging Face pour Zephyr :
Modèle de base (Mistral, Llama...)
↓
SFT sur UltraChat ou OpenHermes
↓
DPO sur UltraFeedback
↓
Modèle aligné
Points d'attention
- Licences : à vérifier. Alpaca interdit l'usage commercial, Dolly est libre, OASST est en Apache 2.0.
- Qualité variable : les datasets générés par LLM contiennent du bruit et des biais. Les datasets humains sont plus petits mais plus fiables.
- Contamination : certains contiennent des données de benchmarks d'évaluation.
- Biais culturels : la plupart sont anglo-saxons. Pour un produit européen, ça peut poser problème.
04 — La capacité de calcul nécessaire
C'est souvent le facteur décisif. La réponse dépend de la taille du modèle, de la technique de fine-tuning, et du type (SFT vs DPO vs RLHF complet).
Règle de base
Pour un fine-tuning full (tous les paramètres entraînables), la VRAM nécessaire est environ 16 à 20 fois la taille du modèle en paramètres (en milliards). Pourquoi ? Il faut stocker en mémoire :
- Les poids du modèle (2 octets par paramètre en fp16/bf16)
- Les gradients (2 octets par paramètre)
- Les états de l'optimiseur Adam (8 octets par paramètre : momentum + variance en fp32)
- Les activations (variable selon la longueur de séquence et le batch)
Modèle 7B en full fine-tuning ≈ 7B × 18 octets ≈ 126 Go de VRAM
Soit 2 GPU A100 80Go minimum, en pratique 4 pour avoir de la marge.
Les techniques qui changent tout
LoRA (Low-Rank Adaptation) — on ne fine-tune pas tous les poids, mais des petites matrices d'adaptation ajoutées à certaines couches. Typiquement 0,1 % à 1 % des paramètres deviennent entraînables.
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r=16, # rang des matrices
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
)
model = get_peft_model(base_model, config)
# ~0.5% de paramètres entraînables
Gain : la VRAM tombe à environ 2-3× la taille du modèle.
QLoRA (Quantized LoRA) — LoRA + quantification 4-bit du modèle de base. Seules les adaptations LoRA sont en précision normale.
Gain : la VRAM chute à environ 0,75× la taille du modèle. C'est ce qui permet de fine-tuner un 7B sur une seule RTX 3090 (24 Go).
Tableau récapitulatif par taille de modèle
| Modèle | Full fine-tuning | LoRA | QLoRA |
|---|---|---|---|
| 1-3B (Phi-3, Llama 3.2 3B) | 1× A100 40Go | 1× RTX 3090 24Go | 1× RTX 3060 12Go |
| 7-8B (Mistral 7B, Llama 3.1 8B) | 4× A100 80Go | 1× A100 40Go ou 1× RTX 4090 | 1× RTX 3090 24Go |
| 13B | 8× A100 80Go | 2× A100 40Go | 1× A100 40Go |
| 30-34B | 16× A100 80Go (cluster) | 4× A100 80Go | 1× A100 80Go |
| 70B (Llama 3.1 70B) | Cluster H100 multi-nœuds | 8× A100 80Go | 2× A100 80Go |
| 405B (Llama 3.1 405B) | Hors de portée hors big tech | Cluster H100 | 8× H100 80Go minimum |
Le temps d'entraînement
Ordre de grandeur sur un dataset typique (50-100k exemples, 3 epochs) :
SFT avec QLoRA sur Llama 3 8B
- 1× RTX 4090 : ~12-24 heures
- 1× A100 80Go : ~6-12 heures
- 4× A100 : ~2-4 heures
DPO avec QLoRA sur Llama 3 8B
- DPO consomme ~2× plus que SFT (deux forward passes : modèle entraîné + modèle de référence)
- Multiplier les temps SFT par 1,5 à 2
RLHF complet (PPO)
- 3 à 5× plus coûteux que DPO
- Nécessite de charger 3-4 modèles simultanément (policy, reference, reward, value)
- Rarement fait à petite échelle aujourd'hui, ce qui explique le succès de DPO
Coûts cloud approximatifs
Si on loue du GPU (RunPod, Lambda Labs, Vast.ai, AWS) :
| Setup | Tarif horaire approx. | Coût d'un fine-tuning 8B en QLoRA |
|---|---|---|
| 1× RTX 4090 (RunPod) | 0,40-0,70 €/h | 8-15 € |
| 1× A100 40Go | 1,00-1,50 €/h | 10-20 € |
| 1× A100 80Go | 1,50-2,00 €/h | 12-25 € |
| 1× H100 80Go | 2,50-4,00 €/h | 15-30 € |
| 8× A100 80Go (node) | 12-20 €/h | 50-150 € pour un 70B en QLoRA |
Les hyperscalers (AWS, GCP, Azure) sont 2 à 4× plus chers que les acteurs spécialisés.
Recommandations par budget
- Budget zéro / apprentissage : Google Colab gratuit (T4 16Go) ou Kaggle (P100/T4 gratuit ~30h/semaine). QLoRA sur modèles 3-7B avec séquences courtes. Parfait pour expérimenter.
- Budget hobbyiste (≤ 100 €) : louer une A100 sur RunPod quelques heures, ou Colab Pro+. Fine-tuning 7-13B en QLoRA sur un dataset sérieux.
- Setup pro / startup (≤ 1 000 €) : louer 1-2 A100 80Go pendant une semaine. Couvre confortablement le fine-tuning jusqu'à 30B en QLoRA.
- Setup recherche / production sérieuse : un nœud 8× A100 ou H100. À ce niveau, soit achat (~150-400 k€), soit cloud long terme avec engagement.
Optimisations cumulables
Quelques techniques à connaître, combinables avec LoRA/QLoRA :
- Gradient checkpointing : divise la VRAM par 2-3 au prix d'un overhead de calcul de ~30 %. À activer par défaut.
- Flash Attention 2 : réduit la mémoire des attentions et accélère de 2-3×. Standard maintenant.
- DeepSpeed ZeRO : distribue les états de l'optimiseur, gradients, voire poids sur plusieurs GPU. Permet de fine-tuner du 70B sur 4× A100 au lieu de 16.
- FSDP (PyTorch natif) : équivalent de ZeRO, intégré à PyTorch et HuggingFace Accelerate.
- Unsloth : bibliothèque qui réécrit les kernels CUDA pour LoRA/QLoRA, gain de 2× en vitesse et 60 % en VRAM. Standard maintenant pour les modèles supportés (Llama, Mistral, Gemma).
# Exemple Unsloth — fine-tuning Llama 8B sur une seule RTX 3090
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"unsloth/llama-3-8b",
max_seq_length=2048,
load_in_4bit=True,
)
model = FastLanguageModel.get_peft_model(model, r=16, ...)
# Et c'est parti
Pipeline minimal réaliste
Pour un projet sérieux mais raisonnable, le pipeline le plus accessible aujourd'hui :
Mistral 7B ou Llama 3.1 8B (base)
↓ QLoRA + Unsloth sur OpenHermes (sous-échantillonné)
↓ ~10h sur A100 80Go, ~20 €
SFT model
↓ QLoRA DPO sur UltraFeedback
↓ ~6h sur A100 80Go, ~12 €
Modèle aligné
Total : ~35 € de cloud et 2 jours de calcul pour un modèle 7B aligné convenable. Ce qui demandait un cluster il y a deux ans tient aujourd'hui sur une RTX 3090.
Conclusion
Mettre en place des guardrails sur un LLM, c'est en réalité travailler sur quatre couches complémentaires :
- Le prompt système : la première ligne, gratuite et immédiate.
- L'alignement interne du modèle : RLHF, Constitutional AI, fine-tuning ciblé. Profond, opaque, coûteux à modifier.
- Les guardrails externes : règles, classifiers, frameworks. Explicites, modifiables en temps réel, mais ajoutent de la latence.
- Les contraintes d'inférence : decoding contraint, logit bias, self-critique.
Aucune couche ne suffit seule. Un bon système combine les quatre, en adaptant le curseur selon le contexte : un prototype interne peut se contenter d'un bon prompt système et de règles simples ; un produit grand public en domaine sensible (santé, finance, juridique) doit empiler les défenses.
La bonne nouvelle, c'est que l'écosystème open-source est aujourd'hui d'une richesse remarquable : datasets d'alignement ouverts, frameworks de guardrails matures, techniques de fine-tuning efficaces (QLoRA, Unsloth) qui permettent à un développeur seul d'aligner un modèle 7B pour quelques dizaines d'euros. Ce qui demandait un cluster il y a deux ans tient aujourd'hui sur une RTX 3090.
Un projet d'alignement ou de guardrails à mener ?
Choix d'architecture (interne / externe / hybride), sélection des frameworks, dataset, fine-tuning DPO/RLHF, mise en production. Première analyse de faisabilité offerte.
Échanger avec un expert