Introduction

Les grands modĂšles de langage (LLM) comme GPT-4, Claude ou Gemini sont impressionnants, mais leur taille et leur coĂ»t les rendent difficiles Ă  utiliser dans des contextes spĂ©cialisĂ©s. Heureusement, il existe une alternative puissante et Ă©conomique : le fine-tuning d’un petit LLM open-source avec LoRA (Low-Rank Adaptation).

Dans ce tutoriel, nous allons voir comment adapter un modĂšle comme Mistral 7B ou Llama 3.2 (3B) Ă  vos propres donnĂ©es, en utilisant Hugging Face et la bibliothĂšque PEFT. L’objectif : obtenir un modĂšle spĂ©cialisĂ© qui comprend votre domaine, sans nĂ©cessiter de cluster GPU coĂ»teux.

Prérequis

  • Python 3.10+ installĂ©
  • Un environnement virtuel (venv ou conda)
  • Un GPU avec au moins 8 Go de VRAM (ou Google Colab)
  • pip installĂ© Ă  jour

1. Mise en place de l’environnement

Commençons par installer les dépendances nécessaires :

pip install torch transformers datasets accelerate peft trl bitsandbytes

Connectez-vous à Hugging Face pour télécharger les modÚles :

huggingface-cli login
# Entrez votre token d'accĂšs

2. Choix du modĂšle de base

Pour un fine-tuning efficace avec des ressources limitées, privilégiez des modÚles entre 1B et 8B paramÚtres. Voici les meilleurs choix actuels :

  • Mistral 7B — excellent rapport qualitĂ©/performance
  • Llama 3.2 3B — lĂ©ger et performant
  • Phi-3 Mini (3.8B) — idĂ©al pour les tĂąches de raisonnement
  • Gemma 2 2B/9B — bonne alternative Google

Dans cet article, nous utiliserons Mistral 7B via le checkpoint mistralai/Mistral-7B-v0.1.

3. Préparation du jeu de données

Nous allons créer un jeu de données au format instruct. Chaque entrée contient un prompt et une réponse attendue :

from datasets import Dataset

data = [
    {
        "instruction": "Qu'est-ce que le fine-tuning LoRA ?",
        "output": "Le fine-tuning LoRA (Low-Rank Adaptation) est une technique qui consiste à adapter un modÚle pré-entraßné en entraßnant uniquement des matrices de faible rang, réduisant drastiquement le nombre de paramÚtres à ajuster."
    },
    {
        "instruction": "Explique le concept d'attention en NLP",
        "output": "Le mécanisme d'attention permet à un modÚle de pondérer l'importance relative de chaque mot d'une séquence, capturant ainsi les dépendances à longue portée."
    }
]

dataset = Dataset.from_list(data)

Pour un usage réel, vous pouvez charger un fichier JSON ou CSV :

from datasets import load_dataset
dataset = load_dataset("json", data_files="mes_donnees.json")["train"]

Structure attendue du JSON :

[
  {"instruction": "...", "output": "..."},
  {"instruction": "...", "output": "..."}
]

4. Tokenization et formatage

Nous devons formater les données dans le style attendu par le modÚle :

def format_instruction(example):
    return {
        "text": f"### Instruction:\n{example['instruction']}\n\n### Réponse:\n{example['output']}"
    }

dataset = dataset.map(format_instruction)

def tokenize_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        padding="max_length",
        max_length=512
    )

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1")
tokenizer.pad_token = tokenizer.eos_token

tokenized_dataset = dataset.map(tokenize_function, batched=True)

5. Configuration de LoRA avec PEFT

Voici la configuration LoRA que nous allons utiliser :

from peft import LoraConfig, get_peft_model

lora_config = LoraConfig(
    r=8,                # Rang de la matrice
    lora_alpha=32,      # Facteur d'échelle
    target_modules=["q_proj", "v_proj"],  # Couches Ă  adapter
    lora_dropout=0.05,  # Dropout pour régularisation
    bias="none",
    task_type="CAUSAL_LM"
)

Explication des paramĂštres :

  • r (rang) : plus il est Ă©levĂ©, plus le modĂšle peut apprendre, mais plus il consomme de mĂ©moire. 8-16 est un bon compromis.
  • lora_alpha : facteur d’Ă©chelle. Une valeur 2x supĂ©rieure Ă  r est courante.
  • target_modules : les couches d’attention Ă  adapter. Pour les modĂšles Llama/Mistral, q_proj et v_proj suffisent souvent.

6. EntraĂźnement avec SFTTrainer

Utilisons le SFTTrainer de la bibliothÚque TRL pour un fine-tuning supervisé optimal :

from trl import SFTTrainer
from transformers import AutoModelForCausalLM, TrainingArguments
import torch

model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-v0.1",
    load_in_4bit=True,          # Quantification 4-bit pour économiser la mémoire
    torch_dtype=torch.bfloat16,
    device_map="auto"
)

training_args = TrainingArguments(
    output_dir="./mistral-lora-finetuned",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    num_train_epochs=3,
    learning_rate=2e-4,
    fp16=False,
    bf16=True,
    logging_steps=10,
    save_strategy="epoch",
    report_to="none"
)

trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    peft_config=lora_config,
    tokenizer=tokenizer,
    max_seq_length=512
)

trainer.train()

Conseil mémoire : avec load_in_4bit=True et LoRA (rang 8), ce fine-tuning consomme environ 6-8 Go de VRAM pour un modÚle 7B, ce qui tient sur une RTX 3080 ou un T4 (Google Colab).

7. Sauvegarde et chargement du modÚle adapté

# Sauvegarder les poids LoRA
trainer.model.save_pretrained("./mon-modele-lora")
tokenizer.save_pretrained("./mon-modele-lora")

# Charger le modÚle adapté
from peft import PeftModel

base_model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-v0.1",
    load_in_4bit=True,
    device_map="auto"
)

model = PeftModel.from_pretrained(base_model, "./mon-modele-lora")

8. Inférence : tester le modÚle fine-tuné

def generate_response(prompt):
    inputs = tokenizer(
        f"### Instruction:\n{prompt}\n\n### Réponse:\n",
        return_tensors="pt"
    ).to("cuda")

    outputs = model.generate(
        **inputs,
        max_new_tokens=256,
        temperature=0.7,
        do_sample=True
    )

    return tokenizer.decode(outputs[0], skip_special_tokens=True)

# Test
print(generate_response("Comment déployer un modÚle fine-tuné en production ?"))

9. Fusion des poids LoRA (optionnel)

Pour déployer sans dépendance PEFT, vous pouvez fusionner les poids LoRA dans le modÚle de base :

merged_model = model.merge_and_unload()
merged_model.save_pretrained("./mon-modele-merged")
tokenizer.save_pretrained("./mon-modele-merged")

Le modĂšle fusionnĂ© peut ĂȘtre chargĂ© comme un modĂšle standard Hugging Face, sans avoir besoin d’importer PEFT :

model = AutoModelForCausalLM.from_pretrained("./mon-modele-merged")

10. Bonnes pratiques et conseils

  • QualitĂ© des donnĂ©es : 500 Ă  2000 exemples bien rĂ©digĂ©s valent mieux que 10 000 exemples bruitĂ©s.
  • Sur-apprentissage : Si la loss d’entraĂźnement continue de baisser mais que les performances en validation stagnent, rĂ©duisez le nombre d’Ă©poques (1-3 suffisent souvent).
  • Template de prompt : Utilisez le mĂȘme format de prompt lors de l’infĂ©rence que celui utilisĂ© pour l’entraĂźnement.
  • Mixed precision : Activez bf16 (si supportĂ©) ou fp16 pour rĂ©duire la mĂ©moire GPU.
  • Évaluation : Gardez 10-20% de vos donnĂ©es pour la validation croisĂ©e.

Conclusion

Le fine-tuning d’un petit LLM avec LoRA et Hugging Face est accessible Ă  tout dĂ©veloppeur possĂ©dant un GPU grand public. Cette approche vous permet de crĂ©er des modĂšles spĂ©cialisĂ©s pour vos cas d’usage — support client, documentation technique, gĂ©nĂ©ration de code interne — sans dĂ©pendre d’API externes coĂ»teuses.

Les techniques prĂ©sentĂ©es ici (quantification 4-bit, LoRA, SFTTrainer) forment la base de nombreux pipelines de production. Avec un budget infĂ©rieur Ă  10€ de crĂ©dits Colab, vous pouvez obtenir un modĂšle adaptĂ© Ă  vos donnĂ©es spĂ©cifiques.

Prochaine étape : explorez DPO (Direct Preference Optimization) pour aligner votre modÚle sur des préférences humaines, ou utilisez Ollama pour déployer votre modÚle fine-tuné en local.