AvancéCas Pratiques
18 min de lecture12 vues

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éthodeVRAM requise (8B)Qualité
Full fine-tuning~80 GoMaximale
LoRA (16-bit)~16 GoTrès bonne
QLoRA (4-bit)~6 GoTrè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

pip install torch transformers peft bitsandbytes trl datasets accelerate

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étriqueCe qu''elle mesureObjectif
Train lossErreur sur le train setDoit diminuer
Eval lossErreur sur le eval setDoit diminuer puis se stabiliser
OverfittingÉcart train/eval lossDoit 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

ÉtapeOutilDurée
Préparation donnéesPython + JSON1-2 heures
Entraînement QLoRAtransformers + peft15 min - 2h
ÉvaluationTests manuels + métriques30 min
Export GGUFllama.cpp5 min
DéploiementOllama2 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.