Fine-tuning et RLHF
Adapter et aligner les modèles de langage
Le fine-tuning permet d'adapter un modèle pré-entraîné à une tâche ou un domaine spécifique. Le RLHF (Reinforcement Learning from Human Feedback) aligne le modèle avec les préférences humaines.
Quand fine-tuner ?
Fine-tuning vs alternatives
| Besoin | Solution |
|---|---|
| Nouvelles connaissances | RAG |
| Style/format spécifique | Fine-tuning |
| Tâche très spécialisée | Fine-tuning |
| Comportement personnalisé | Fine-tuning + RLHF |
| Données confidentielles | Fine-tuning local |
Indicateurs pour le fine-tuning
✓ Fine-tuner si :
- Le prompt engineering ne suffit pas
- Besoin de réponses dans un format très spécifique
- Domaine technique avec jargon particulier
- Volume important de requêtes similaires
- Latence critique (modèle plus petit)
✗ Éviter si :
- Peu de données d'entraînement (<100 exemples)
- Les connaissances changent souvent (→ RAG)
- Budget limité
- Besoin de flexibilitéTypes de fine-tuning
Full Fine-tuning
Entraîner tous les paramètres du modèle.
Modèle original (7B params)
↓
Entraînement sur nouvelles données
↓
Nouveau modèle (7B params modifiés)Avantages : Performance optimale Inconvénients : Coûteux, risque de catastrophic forgetting
Parameter-Efficient Fine-Tuning (PEFT)
Entraîner seulement une petite partie des paramètres.
LoRA (Low-Rank Adaptation)
Ajoute des matrices de faible rang aux couches existantes.
┌─────────┐
x ────→│ Weights │────→ y (original)
│ W │
└─────────┘
+
┌───┐ ┌───┐
x ────→│ A │→│ B │────→ Δy (adaptation)
└───┘ └───┘
r×d d×r
W' = W + BA où rank(BA) = r << dfrom peft import LoraConfig, get_peft_model
config = LoraConfig(
r=16, # Rang (8-64 typique)
lora_alpha=32, # Scaling
target_modules=["q_proj", "v_proj"], # Couches ciblées
lora_dropout=0.1,
bias="none"
)
model = get_peft_model(base_model, config)
# Paramètres entraînables : ~0.1% du totalQLoRA
LoRA avec quantification 4-bit du modèle de base.
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto"
)Avantage : Fine-tuner un modèle 7B sur un GPU 24GB.
Comparaison des méthodes
| Méthode | Params entraînés | Mémoire | Performance |
|---|---|---|---|
| Full FT | 100% | Très haute | Meilleure |
| LoRA | 0.1-1% | Moyenne | Excellente |
| QLoRA | 0.1-1% | Basse | Très bonne |
| Prefix Tuning | ~0.1% | Basse | Bonne |
| Adapters | 1-5% | Moyenne | Très bonne |
Préparation des données
Format des données
{"messages": [
{"role": "system", "content": "Tu es un assistant médical."},
{"role": "user", "content": "Quels sont les symptômes de la grippe ?"},
{"role": "assistant", "content": "Les symptômes de la grippe incluent..."}
]}Qualité des données
✓ Données de qualité :
- Réponses bien écrites
- Factuellement correctes
- Représentatives de l'usage cible
- Diversifiées
✗ À éviter :
- Réponses trop courtes/longues
- Erreurs factuelles
- Biais indésirables
- Données sensibles/PIIVolume recommandé
| Tâche | Minimum | Idéal |
|---|---|---|
| Classification | 100 | 1000+ |
| Génération simple | 500 | 5000+ |
| Chat/Assistant | 1000 | 10000+ |
| Domaine technique | 500 | 2000+ |
Entraînement avec Hugging Face
Configuration complète
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer
)
from datasets import load_dataset
from peft import LoraConfig, get_peft_model
# Charger le modèle
model_name = "mistralai/Mistral-7B-v0.1"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# Configuration LoRA
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# Charger les données
dataset = load_dataset("json", data_files="training_data.jsonl")
# Configuration d'entraînement
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
warmup_steps=100,
logging_steps=10,
save_steps=500,
fp16=True,
)
# Entraînement
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
tokenizer=tokenizer,
)
trainer.train()Fine-tuning via API (OpenAI)
from openai import OpenAI
client = OpenAI()
# Upload du fichier
file = client.files.create(
file=open("training_data.jsonl", "rb"),
purpose="fine-tune"
)
# Lancer le fine-tuning
job = client.fine_tuning.jobs.create(
training_file=file.id,
model="gpt-3.5-turbo",
hyperparameters={
"n_epochs": 3
}
)
# Suivre le statut
status = client.fine_tuning.jobs.retrieve(job.id)
print(status.status)
# Utiliser le modèle fine-tuné
response = client.chat.completions.create(
model=job.fine_tuned_model,
messages=[{"role": "user", "content": "..."}]
)RLHF : Reinforcement Learning from Human Feedback
Le RLHF aligne les modèles avec les préférences humaines.
Pipeline RLHF
┌─────────────────────────────────────────────────────────────┐
│ PIPELINE RLHF │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. SUPERVISED FINE-TUNING (SFT) │
│ Modèle base → Entraînement sur réponses de qualité │
│ ↓ │
│ 2. REWARD MODEL TRAINING │
│ Collecte préférences humaines (A > B) │
│ Entraîner un modèle de récompense │
│ ↓ │
│ 3. PPO OPTIMIZATION │
│ Optimiser la politique avec le reward model │
│ Équilibrer récompense et divergence KL │
│ ↓ │
│ Modèle aligné │
│ │
└─────────────────────────────────────────────────────────────┘Étape 1 : Supervised Fine-Tuning
Entraîner sur des exemples de haute qualité.
# Données : prompts + réponses idéales écrites par des humains
sft_dataset = [
{"prompt": "Explique la gravité", "response": "La gravité est..."},
...
]Étape 2 : Reward Model
Entraîner un modèle à prédire les préférences humaines.
# Données : paires de réponses avec préférence
preference_data = [
{
"prompt": "Question...",
"chosen": "Bonne réponse...",
"rejected": "Mauvaise réponse..."
},
...
]
# Le reward model apprend à donner un score plus élevé à "chosen"Étape 3 : PPO
Optimiser le modèle avec le reward model.
from trl import PPOTrainer, PPOConfig
ppo_config = PPOConfig(
learning_rate=1e-5,
batch_size=256,
mini_batch_size=64,
ppo_epochs=4,
)
ppo_trainer = PPOTrainer(
config=ppo_config,
model=model,
ref_model=ref_model, # Modèle de référence pour KL penalty
tokenizer=tokenizer,
reward_model=reward_model,
)
# Boucle d'entraînement
for batch in dataloader:
query_tensors = tokenizer(batch["prompts"])
response_tensors = model.generate(query_tensors)
rewards = reward_model(query_tensors, response_tensors)
ppo_trainer.step(query_tensors, response_tensors, rewards)DPO : Direct Preference Optimization
Alternative simplifiée au RLHF, sans reward model explicite.
RLHF : SFT → Reward Model → PPO
DPO : SFT → Optimization directe sur préférencesImplémentation DPO
from trl import DPOTrainer, DPOConfig
dpo_config = DPOConfig(
beta=0.1, # Température
learning_rate=5e-7,
)
trainer = DPOTrainer(
model=model,
ref_model=ref_model,
args=dpo_config,
train_dataset=preference_dataset,
tokenizer=tokenizer,
)
trainer.train()DPO vs RLHF
| Aspect | RLHF | DPO |
|---|---|---|
| Complexité | Haute | Moyenne |
| Reward model | Nécessaire | Non |
| Stabilité | Moins stable | Plus stable |
| Performance | Référence | Comparable |
| Implémentation | Difficile | Simple |
Constitutional AI (CAI)
Approche d'Anthropic pour l'alignement sans feedback humain massif.
1. Red teaming : Générer des prompts problématiques
2. Self-critique : Le modèle critique ses propres réponses
3. Révision : Le modèle améliore selon des principes (constitution)
4. RL : Optimiser avec les réponses révisées comme cibleBonnes pratiques
Éviter le catastrophic forgetting
# Mélanger données nouvelles et données générales
combined_dataset = concatenate([
new_task_data,
sample(general_data, n=len(new_task_data))
])Hyperparamètres recommandés
# LoRA
r = 8-64 # Plus grand = plus expressif
lora_alpha = 16-64 # Généralement 2× r
dropout = 0.05-0.1
# Entraînement
learning_rate = 1e-4 à 2e-4 # LoRA
learning_rate = 1e-5 à 5e-5 # Full fine-tuning
batch_size = 4-32
epochs = 1-5
warmup_ratio = 0.03-0.1Validation
# Toujours garder un jeu de validation
train_data, val_data = dataset.train_test_split(test_size=0.1)
# Surveiller la loss de validation
# Arrêter si elle augmente (overfitting)Déploiement
Fusionner les poids LoRA
# Fusionner les adapters avec le modèle de base
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./merged_model")Quantification pour le déploiement
from transformers import AutoModelForCausalLM
import torch
model = AutoModelForCausalLM.from_pretrained(
"./merged_model",
torch_dtype=torch.float16,
device_map="auto"
)
# Ou quantification 4-bit pour inference
model = AutoModelForCausalLM.from_pretrained(
"./merged_model",
load_in_4bit=True,
)Résumé
FINE-TUNING = Adapter un modèle pré-entraîné
Types :
├── Full Fine-tuning : Tous les paramètres
├── LoRA : Matrices low-rank (~0.1% params)
├── QLoRA : LoRA + Quantification 4-bit
└── Prefix Tuning : Tokens apprenables
RLHF = Aligner avec les préférences humaines
├── SFT : Supervised Fine-Tuning initial
├── Reward Model : Prédire les préférences
└── PPO : Optimiser avec récompenses
Alternatives :
├── DPO : Optimisation directe (plus simple)
└── CAI : Auto-critique selon constitution
Quand fine-tuner :
✓ Style/format très spécifique
✓ Domaine technique pointu
✓ Volume de requêtes similaires
✗ Connaissances changeantes (→ RAG)