TP : Fine-tuner un LLM sur vos Données
Apprenez à fine-tuner un LLM (Llama, Mistral) avec LoRA/QLoRA sur vos données métier : préparation du dataset, entraînement, évaluation et déploiement avec Ollama.
Quand Fine-tuner un LLM ?
Le fine-tuning n''est pas toujours nécessaire. Voici un arbre de décision :
Cas où le fine-tuning est pertinent
- Votre modèle doit adopter un ton/style très spécifique (juridique, médical)
- Vous avez besoin d''un format de sortie strict que le prompt engineering ne peut pas garantir
- Votre domaine utilise un jargon métier très spécialisé
- Vous voulez réduire les coûts en utilisant un petit modèle fine-tuné plutôt qu''un gros modèle généraliste
Le fine-tuning ne donne PAS de nouvelles connaissances au modèle. Il ajuste son style et son comportement. Pour ajouter des connaissances, utilisez le RAG.
Ce que Nous Allons Construire
Fine-tuner Llama 3.1 8B avec QLoRA pour créer un assistant spécialisé dans un domaine.
Stack technique
- Python 3.11+
- transformers + peft (Hugging Face)
- bitsandbytes (quantification 4-bit)
- trl (Transformer Reinforcement Learning)
- GPU : 1x RTX 3090/4090 (24 Go VRAM) ou Google Colab
Étape 1 : Préparer le Dataset
La qualité de vos données détermine la qualité du modèle. Format attendu : des paires instruction/réponse.
Format du dataset
[
{
"instruction": "Rédige une clause de confidentialité pour un contrat freelance",
"input": "",
"output": "Article X - Confidentialité\n\nLe Prestataire s''engage à maintenir la plus stricte confidentialité sur l''ensemble des informations..."
},
{
"instruction": "Simplifie cette clause juridique pour un non-initié",
"input": "Le locataire est tenu de jouir paisiblement des lieux loués...",
"output": "Le locataire doit utiliser le logement de manière calme et respectueuse, sans causer de nuisances aux voisins..."
}
]
Script de préparation
import json
def prepare_dataset(raw_data: list[dict]) -> list[dict]:
"""Convertit les données brutes au format d''entraînement."""
formatted = []
for item in raw_data:
# Format ChatML (compatible Llama, Mistral)
conversation = {
"messages": [
{
"role": "system",
"content": "Tu es un assistant juridique spécialisé en droit français."
},
{
"role": "user",
"content": item["instruction"]
+ (f"\n\nContexte : {item[''input'']}" if item.get("input") else "")
},
{
"role": "assistant",
"content": item["output"]
}
]
}
formatted.append(conversation)
return formatted
# Charger et formater
with open("raw_data.json") as f:
raw = json.load(f)
dataset = prepare_dataset(raw)
# Séparer train/eval (90/10)
split = int(len(dataset) * 0.9)
train_data = dataset[:split]
eval_data = dataset[split:]
with open("train.jsonl", "w") as f:
for item in train_data:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
with open("eval.jsonl", "w") as f:
for item in eval_data:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
print(f"Train : {len(train_data)} exemples")
print(f"Eval : {len(eval_data)} exemples")
Combien d''exemples faut-il ? En pratique, 100-500 exemples de haute qualité suffisent pour un fine-tuning LoRA efficace. La qualité prime sur la quantité.
Étape 2 : Comprendre LoRA et QLoRA
Le problème du fine-tuning classique
Fine-tuner tous les paramètres d''un modèle 8B = modifier 8 milliards de paramètres. C''est coûteux en mémoire et en calcul.
LoRA : Low-Rank Adaptation
LoRA gèle les poids originaux et ajoute de petites matrices d''adaptation :
QLoRA : LoRA + Quantification 4-bit
QLoRA va encore plus loin : le modèle de base est compressé en 4 bits, réduisant la mémoire nécessaire de 4x :
| Méthode | VRAM requise (8B) | Qualité |
|---|---|---|
| Full fine-tuning | ~80 Go | Maximale |
| LoRA (16-bit) | ~16 Go | Très bonne |
| QLoRA (4-bit) | ~6 Go | Très bonne |
QLoRA permet de fine-tuner un modèle 8B sur un GPU de 8 Go (RTX 3060/4060). C''est une révolution pour l''accessibilité du fine-tuning.
Étape 3 : Entraînement avec QLoRA
Installation
Script d''entraînement
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
from datasets import load_dataset
# --- 1. Configuration de la quantification ---
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
# --- 2. Charger le modèle de base ---
model_name = "meta-llama/Llama-3.1-8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
torch_dtype=torch.bfloat16,
)
# --- 3. Configuration LoRA ---
lora_config = LoraConfig(
r=16, # Rang des matrices LoRA
lora_alpha=32, # Facteur de scaling
lora_dropout=0.05, # Dropout pour régularisation
target_modules=[ # Couches à adapter
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
task_type="CAUSAL_LM",
)
model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, lora_config)
# Afficher le nombre de paramètres entraînables
model.print_trainable_parameters()
# → trainable params: 13.6M / 8.03B total (0.17%)
# --- 4. Charger le dataset ---
dataset = load_dataset("json", data_files={
"train": "train.jsonl",
"eval": "eval.jsonl",
})
# --- 5. Configuration de l''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=10,
logging_steps=10,
eval_strategy="steps",
eval_steps=50,
save_steps=100,
bf16=True,
optim="paged_adamw_8bit",
)
# --- 6. Lancer l''entraînement ---
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset["train"],
eval_dataset=dataset["eval"],
args=training_args,
max_seq_length=2048,
)
trainer.train()
# --- 7. Sauvegarder ---
trainer.save_model("./mon-modele-finetune")
tokenizer.save_pretrained("./mon-modele-finetune")
print("Modèle sauvegardé !")
Durée d''entraînement : avec 300 exemples et 3 epochs sur un RTX 4090, comptez environ 15-30 minutes. Sur Google Colab (T4), environ 1-2 heures.
Étape 4 : Évaluation
Test qualitatif
from peft import PeftModel
# Charger le modèle fine-tuné
base_model = AutoModelForCausalLM.from_pretrained(
model_name, quantization_config=bnb_config, device_map="auto"
)
model = PeftModel.from_pretrained(base_model, "./mon-modele-finetune")
def generate(prompt: str) -> str:
messages = [
{"role": "system", "content": "Tu es un assistant juridique."},
{"role": "user", "content": prompt},
]
inputs = tokenizer.apply_chat_template(
messages, return_tensors="pt", add_generation_prompt=True
).to("cuda")
outputs = model.generate(inputs, max_new_tokens=500, temperature=0.7)
return tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True)
# Tester
print(generate("Rédige une clause de non-concurrence"))
Métriques à surveiller
| Métrique | Ce qu''elle mesure | Objectif |
|---|---|---|
| Train loss | Erreur sur le train set | Doit diminuer |
| Eval loss | Erreur sur le eval set | Doit diminuer puis se stabiliser |
| Overfitting | Écart train/eval loss | Doit rester faible |
Étape 5 : Déploiement avec Ollama
Ollama permet de servir votre modèle fine-tuné localement.
Exporter au format GGUF
# Merger les poids LoRA avec le modèle de base
from peft import PeftModel
from transformers import AutoModelForCausalLM
base = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)
merged = PeftModel.from_pretrained(base, "./mon-modele-finetune")
merged = merged.merge_and_unload()
merged.save_pretrained("./merged-model")
# Convertir en GGUF (nécessite llama.cpp)
python llama.cpp/convert_hf_to_gguf.py ./merged-model --outfile model.gguf --outtype q4_K_M
Créer un Modelfile Ollama
FROM ./model.gguf
SYSTEM "Tu es un assistant juridique expert en droit français des contrats."
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
Utiliser avec Ollama
# Créer le modèle dans Ollama
ollama create mon-assistant-juridique -f Modelfile
# Tester
ollama run mon-assistant-juridique "Rédige une clause de confidentialité"
# Utiliser via API
curl http://localhost:11434/api/chat -d '{
"model": "mon-assistant-juridique",
"messages": [{"role": "user", "content": "Rédige une clause de confidentialité"}]
}'
Votre modèle tourne maintenant en local : aucune donnée ne sort de votre machine, pas de coût API, et des réponses spécialisées dans votre domaine.
Résumé du Pipeline
| Étape | Outil | Durée |
|---|---|---|
| Préparation données | Python + JSON | 1-2 heures |
| Entraînement QLoRA | transformers + peft | 15 min - 2h |
| Évaluation | Tests manuels + métriques | 30 min |
| Export GGUF | llama.cpp | 5 min |
| Déploiement | Ollama | 2 min |
Points clés :
- QLoRA permet de fine-tuner un modèle 8B avec seulement 6 Go de VRAM
- 100-500 exemples de qualité suffisent
- Le fine-tuning ajuste le style/comportement, pas les connaissances (utilisez RAG pour ça)
- Ollama permet un déploiement local simple et gratuit
Specialiste IA — Master Intelligence Artificielle
Diplome d'un Master en Intelligence Artificielle, je travaille au quotidien sur des projets IA en entreprise. J'ai cree IwanttolearnAI pour rendre l'apprentissage de l'IA accessible a tous, gratuitement.
Continuer a apprendre
TP : Créer un Chatbot RAG de A à Z
Construisez un chatbot intelligent qui répond en se basant sur vos documents : chunking, embeddings, vector store, prompt engineering et interface web.
Token Trainer
Prédisez comment un tokenizer découpe les phrases en tokens ! Cliquez entre les caractères pour placer vos séparations et découvrez les règles de tokenization.
Embedding Explorer
Classez des phrases par similarité sémantique ! Découvrez comment les embeddings capturent le sens des mots grâce à une visualisation 2D interactive.