Aller au contenu principal

Chapitre 1 : Approche d'Ajustement Fin Traditionnel

Mise à Jour de Tous les Paramètres du Modèle​

L'ajustement fin traditionnel implique la mise à jour de chaque paramètre entraînable du modèle pendant l'entraînement. Cela offre la flexibilité maximale pour adapter les modèles à de nouvelles tâches.


Comment Fonctionne l'Ajustement Fin Complet​

La Boucle d'Entraînement​

  1. Passage Avant : Les données traversent l'ensemble du modèle
  2. Calcul de la Perte : Comparer la prédiction à la cible
  3. Passage Arrière : Calculer les gradients pour tous les paramètres
  4. Mise à Jour des Poids : Appliquer les gradients pour mettre à jour tous les poids
  5. Répéter : Continuer jusqu'à convergence

Exemple Complet d'Entraînement​

import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling,
)
from datasets import load_dataset

# Charger le modèle et le tokenizer
model_name = "meta-llama/Llama-3.1-8B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

# Charger le modèle - tous les paramètres sont entraînables par défaut
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16, # Utiliser bf16 pour l'efficacité mémoire
device_map="auto",
)

# Vérifier que tous les paramètres sont entraînables
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f"Entraînable : {trainable_params:,} / {total_params:,} = 100%")

# Préparer le jeu de données
dataset = load_dataset("databricks/dolly-15k", split="train[:1000]")

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

dataset = dataset.map(format_instruction)

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

tokenized_dataset = dataset.map(tokenize, remove_columns=dataset.column_names)

# Configuration de l'entraînement
training_args = TrainingArguments(
output_dir="./llama-full-ft",
num_train_epochs=3,
per_device_train_batch_size=1, # Limité par la mémoire
gradient_accumulation_steps=16, # Simuler une taille de lot de 16
learning_rate=2e-5,
weight_decay=0.01,
warmup_ratio=0.03,
logging_steps=10,
save_strategy="epoch",
bf16=True, # Utiliser bfloat16
gradient_checkpointing=True, # Économiser de la mémoire
optim="adamw_torch_fused", # Optimiseur efficace
)

# Créer le formateur
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
)

# Entraîner
trainer.train()

# Sauvegarder
model.save_pretrained("./llama-full-ft-final")
tokenizer.save_pretrained("./llama-full-ft-final")

Composants Clés Expliqués​

Taux d'Apprentissage​

learning_rate = 2e-5 # Plage courante : 1e-5 à 5e-5

Pour l'ajustement fin complet, utilisez des taux d'apprentissage plus bas que PEFT :

  • Trop élevé : Le modèle diverge ou oublie l'entraînement préalable
  • Trop bas : L'entraînement est lent, peut ne pas s'adapter suffisamment

Décroissance des Poids​

weight_decay = 0.01 # Régularisation pour éviter le surapprentissage

La décroissance des poids empêche le modèle de surapprendre en pénalisant les poids élevés.

Échauffement​

warmup_ratio = 0.03 # 3% des étapes d'entraînement

Augmentez progressivement le taux d'apprentissage au début pour un entraînement stable.

Point de Contrôle des Gradients​

gradient_checkpointing = True # Échanger le calcul contre la mémoire

Recalculer les activations pendant le passage arrière au lieu de les stocker—économise ~30% de mémoire.


Choix de l'Optimiseur​

OptimiseurMémoireVitesseQualité
AdamWÉlevéeRapideMeilleure
Adam 8-bitMoyenneMoyenneBonne
SGDFaibleLentNécessite un ajustement
AdafactorFaibleMoyenneBonne
# AdamW standard
optim = "adamw_torch"

# AdamW fusionné (plus rapide)
optim = "adamw_torch_fused"

# Adam 8-bit (moins de mémoire)
optim = "adamw_8bit"

# Adafactor (pas d'états de momentum)
optim = "adafactor"

Dynamiques d'Entraînement​

Courbe de Perte​

Époque 1 : Perte d'entraînement diminuant rapidement
(Le modèle s'adapte aux nouvelles données)

Époque 2 : Perte d'entraînement se stabilisant
(Surveiller la perte de validation pour le surapprentissage)

Époque 3 : Surapprentissage possible
(La perte d'entraînement continue de baisser, la validation peut augmenter)

Surveillance de l'Entraînement​

# Utilisation de Weights & Biases
training_args = TrainingArguments(
# ... autres arguments ...
report_to="wandb",
run_name="llama-full-ft-experiment-1",
)

Principales métriques à surveiller :

  • Perte d'Entraînement : Devrait diminuer
  • Perte de Validation : Devrait diminuer mais surveiller la divergence par rapport à la perte d'entraînement
  • Norme des Gradients : Les pics peuvent indiquer une instabilité
  • Taux d'Apprentissage : Vérifier le fonctionnement de l'échauffement et de la décroissance

Entraînement en Précision Mixte​

Pourquoi Utiliser la Précision Mixte ?​

# Moitié de la mémoire, souvent un entraînement plus rapide
bf16 = True # Préféré pour les GPU plus récents (Ampere+)
# OU
fp16 = True # Pour les GPU plus anciens
PrécisionMémoirePlageStabilité
float324 octetsComplèteLa plus élevée
bfloat162 octetsComplèteÉlevée
float162 octetsLimitéeMoyenne
Stabilité du Float16

Le Float16 a une plage plus étroite et peut causer des débordements/sous-débordements. Le BFloat16 maintient la plage du float32 avec une précision réduite, le rendant plus stable.


Sauvegarde et Chargement​

Pendant l'Entraînement (Points de Contrôle)​

training_args = TrainingArguments(
save_strategy="steps",
save_steps=500,
save_total_limit=3, # Conserver seulement les 3 derniers points de contrôle
)

Après l'Entraînement​

# Sauvegarder le modèle complet
model.save_pretrained("./final-model")
tokenizer.save_pretrained("./final-model")

# Plus tard, le charger
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("./final-model")

Avantages de l'Ajustement Fin Complet​

Personnalisation Maximale​

Tous les paramètres peuvent être ajustés à votre tâche.

Potentiellement la Meilleure Qualité​

Aucune contrainte des poids gelés.

Simplicité​

Pas besoin de configuration d'adaptateur.

Sortie Fusionnée​

Fichier de modèle unique, pas de surcharge d'adaptateur à l'inférence.


Inconvénients​

Exigences Élevées en Mémoire​

Besoin de stocker le modèle, les gradients et les états de l'optimiseur.

Risque d'Oubli Catastrophique​

Peut perdre les connaissances pré-entraînées.

Coûteux​

Nécessite des ressources GPU importantes.

Entraînement Lent​

Chaque paramètre doit être mis à jour.


Quand Utiliser l'Ajustement Fin Complet​

Cas d'UtilisationRecommandation
Qualité maximale nécessaire✅ Ajustement Fin Complet
Ressources limitées❌ Utiliser PEFT
Expérimentation rapide❌ Utiliser PEFT
Production à grande échelle⚠️ Considérer les compromis
Recherche/benchmarking✅ Ajustement Fin Complet

Points Clés à Retenir​

✅ L'ajustement fin complet met à jour tous les paramètres du modèle

✅ Nécessite une mémoire GPU significative (modèle + gradients + optimiseur)

✅ Utiliser la précision mixte et le point de contrôle des gradients pour réduire la mémoire

✅ Utiliser des taux d'apprentissage plus bas que PEFT (1e-5 à 5e-5)

✅ Surveiller le surapprentissage avec la perte de validation

✅ Meilleur pour une personnalisation maximale lorsque les ressources le permettent


Prochaines Étapes​

Continuez vers Chapitre 2 : Exigences Computationnelles pour comprendre les besoins exacts en mémoire et en calcul.


Vérification Rapide​

Avant de continuer, assurez-vous de pouvoir répondre :

  1. Quels sont les quatre principaux composants stockés en mémoire GPU pendant l'entraînement ?
  2. Pourquoi le bfloat16 est-il souvent préféré au float16 ?
  3. Quelle plage de taux d'apprentissage est typique pour l'ajustement fin complet ?