Réglage fin de modèles
Lorsque vous avez besoin d'un modèle spécifiquement pour générer des descriptions de produits e-commerce, il existe trois options principales.
La première est le pré-entraînement à partir de zéro. Cela nécessite de collecter d'énormes quantités de données textuelles (potentiellement des billions de tokens), de louer des dizaines de GPU A100 pour entraîner pendant des mois, et de dépenser des millions de dollars. C'est ce que font des géants comme OpenAI et Google, et c'est irréaliste pour la grande majorité des gens.
-
La deuxième est l'ingénierie de prompt. Donnez à un grand modèle généraliste un prompt soigneusement conçu, tel que « Vous êtes un rédacteur e-commerce professionnel. Veuillez générer une description attrayante à partir des informations produit suivantes... » Cette approche a un coût nul et est immédiatement utilisable, mais les problèmes sont les suivants : vous devez écrire de longs prompts à chaque fois, les résultats sont instables, ils sont facilement perturbés par des entrées non pertinentes, et la consommation de tokens est élevée.
-
La troisième est le fine-tuning. Prenez un modèle généraliste déjà entraîné, continuez à l'entraîner pendant un court moment avec vos données propriétaires, et laissez-le « apprendre » votre tâche spécifique. Le modèle résultant conserve ses capacités générales tout en étant excellent sur votre tâche, et le coût est maîtrisable.
Comparaison des trois approches :
| Approche | Coût | Efficacité | Scénarios applicables |
|---|---|---|---|
| Pré-entraînement à partir de zéro | Extrêmement élevé (millions de dollars) | Entièrement personnalisé | Création d'un nouveau modèle de fondation (uniquement pour les géants) |
| Ingénierie de prompt | Presque zéro | Instable, dépend des prompts | Tâches simples, validation rapide |
| Fine-tuning | Moyen (des centaines à des milliers de dollars) | Stable et professionnel | Tâches spécifiques à un domaine, productisation |
Utilisez une analogie simple :
- Le pré-entraînement à partir de zéro, c'est comme former soi-même un médecin à partir de zéro, ce qui prend plus d'une décennie.
- L'ingénierie des prompts, c'est comme donner à un médecin ordinaire un manuel d'utilisation détaillé.
- Le fine-tuning, c'est comme permettre à un médecin déjà autorisé à exercer de suivre une formation de spécialiste pendant quelques mois et de devenir un expert spécialiste.
Pourquoi le fine-tuning est nécessaire
Toutes les tâches n'ont pas besoin de fine-tuning ; commencez par clarifier quand l'utiliser et quand ne pas l'utiliser.
Limites de l'ingénierie de prompt
L'ingénierie de prompt est puissante, mais elle a plusieurs plafonds difficiles à franchir.
La première limitation est la limite de la fenêtre de contexte.Vos prompts et exemples doivent tenir dans la fenêtre de contexte du modèle (par exemple, 4K, 8K, 128K, 1024K tokens). Si votre tâche nécessite des centaines d'exemples pour être décrite clairement, le prompt ne peut pas les contenir.
-
La deuxième limitation est l'instabilité des résultats.Le même prompt peut produire des résultats très différents lorsqu'il est formulé différemment. Lorsque le modèle est de "bonne humeur" (caractère aléatoire de l'échantillonnage), la sortie est bonne ; sinon, il peut complètement sortir du sujet.
-
La troisième limitation est le coût d'inférence élevé.Chaque inférence nécessite l'envoi de l'intégralité du long prompt, ce qui consomme de nombreux tokens et ralentit le temps de réponse. Dans les scénarios à forte concurrence, les coûts peuvent exploser.
-
La quatrième limitation est le problème de l'oubli.Bien que le modèle ait lu votre prompt, la masse de données de pré-entraînement qu'il a vues auparavant peut encore tirer les choses vers le bas. Par exemple, si vous lui demandez de produire du JSON, il peut encore ajouter beaucoup d'explications en langage naturel.
Scénarios applicables pour le fine-tuning
Le fine-tuning vaut la peine d'être envisagé lorsque votre tâche correspond aux caractéristiques suivantes.
Caractéristique 1 : Définition claire de la tâche et format de sortie fixe. Par exemple, "traduire une documentation technique en un blog facile à comprendre," "générer des résumés de tickets à partir de dossiers de service client," "convertir le langage naturel en requêtes SQL."
-
Caractéristique 2 : Vous disposez de quelques centaines à quelques milliers d'échantillons de données étiquetés de haute qualité. Trop peu de données ne produira pas de résultats avec le fine-tuning, et trop de données sont inutiles—généralement, 500-5,000 échantillons constituent la plage idéale.
-
Caractéristique 3 : Vous avez besoin de performances professionnelles dans un domaine spécifique. Par exemple, l'interprétation de rapports médicaux, le résumé de documents juridiques, l'analyse de l'actualité financière. Les modèles polyvalents peuvent ne pas comprendre la terminologie et les conventions du secteur ; le fine-tuning peut les aider à "entrer dans le secteur".
-
Caractéristique 4 : Vous êtes sensible à la vitesse de réponse et au coût. Un petit modèle fine-tuné peut surpasser un grand modèle généraliste, avec une vitesse d'inférence 10x plus rapide et un coût 100x plus faible.
-
Caractéristique 5 : Vous avez besoin d'un respect strict du format de sortie. Par exemple, la sortie doit être au format JSON, doit utiliser un ton spécifique, doit inclure certains champs. Le fine-tuning permet au modèle de "mémoriser" ces exigences sans avoir à les répéter dans le prompt à chaque fois.
Analyse coût-bénéfice du fine-tuning
Avant de faire du fine-tuning, faites d'abord le calcul.
Le coût provient principalement de trois aspects : la préparation des données, les ressources de calcul et le débogage manuel. La préparation des données demande généralement le plus de travail : vous devez collecter, nettoyer et étiqueter les données. Les ressources de calcul sont désormais très bon marché ; avec LoRA + QLoRA, un GPU grand public (RTX 3090/4090) peut fine-tuner des modèles 7B/13B.
Les bénéfices se reflètent dans plusieurs aspects : de meilleurs résultats, des coûts d'inférence réduits, des temps de réponse plus rapides et une sortie plus stable. Si votre modèle doit fournir des services en externe, ces bénéfices continueront de s'accumuler.
Quand ne faut-il pas faire de fine-tuning ? Si les tâches changent rapidement et que les formats de données changent chaque semaine, le prompt engineering est plus flexible. Si vous ne faites qu'une exploration ponctuelle, ou si vous n'avez que quelques dizaines d'échantillons, cela ne vaut pas non plus la peine de faire du fine-tuning.
| Facteur | Mieux adapté au prompt | Mieux adapté au fine-tuning |
|---|---|---|
| Volume de données | < 100 échantillons | > 500 échantillons |
| Stabilité de la tâche | Change fréquemment | Relativement stable |
| Fréquence d'utilisation | Utilisation occasionnelle | Utilisation à haute fréquence |
| Sensibilité au coût | Faible | Élevée |
| Exigences de qualité | Suffisamment bon suffit | Rechercher le meilleur |
Fine-tuning complet
Comprenez d'abord la méthode de fine-tuning la plus traditionnelle, puis voyez pourquoi nous l'utilisons rarement aujourd'hui.
Principes et processus
L'idée du fine-tuning complet est simple : prendre tous les paramètres du modèle pré-entraîné et continuer l'entraînement sur votre jeu de données, en mettant à jour tous les poids.
Le processus se déroule à peu près comme suit :
Première étape : préparez les données. Organisez les données de votre tâche en paires « entrée-sortie ».
-
Deuxième étape, chargez le modèle pré-entraîné. Par exemple, téléchargez des modèles open source tels que LLaMA-2-7B et Mistral-7B.
-
Troisième étape, définissez les paramètres d'entraînement. Le taux d'apprentissage doit être défini très petit (par ex., 1e-5 à 5e-5), car nous ne voulons pas écraser complètement les connaissances apprises lors du pré-entraînement.
-
Étape 4 : commencez l'entraînement. Laissez le modèle effectuer quelques époques sur vos données (plusieurs passages complets dans le jeu de données).
-
Cinquième étape, enregistrez le modèle. Après l'entraînement, vous obtenez un tout nouveau fichier de poids, de la même taille que le modèle d'origine.
Exemple
# Démonstration du concept de fine-tuning complet (pseudo-code, indépendant du framework)
# Montrer l'idée centrale ; le code réel utilisera HuggingFace.
# ============================================
def full_finetuning_concept():
Démonstration des concepts fondamentaux du fine-tuning complet
# 1. Charger le modèle pré-entraîné (tous les paramètres sont entraînables)
model = load_pretrained_model("llama-2-7b")
# Le modèle a 7 milliards de paramètres, et chaque paramètre est mis à jour.
print(fNombre total de paramètres du modèle : {count_parameters(model)})
# Sortie : Nombre total de paramètres du modèle : 7000000000
2. Préparez vos données spécifiques à la tâche.
train_data = [
{"input": Produit : Gobelet isotherme\nGénérer une description e-commerce.,
"output": Cette tasse thermos de haute qualité dispose d'une paroi intérieure en acier inoxydable 304 et d'une technologie d'isolation sous vide, gardant les boissons chaudes jusqu'à 12 heures.},
{"input": Produit : Écouteurs sans fil\nGénérer une description e-commerce.,
"output": Véritables écouteurs Bluetooth sans fil, connexion Bluetooth 5.3 stable, réduction active du bruit, autonomie de 24 heures, confortables à porter.},
# ... des centaines ou des milliers d'entrées de données similaires
]
# 3. Définir l'optimiseur et un taux d'apprentissage très faible
optimizer = create_optimizer(model, learning_rate=2e-5)
# 4. Boucle d'entraînement
for epoch in range(3): # Généralement, entraîner pendant 2-5 époques
for batch in create_batches(train_data):
Propagation avant
loss = model.compute_loss(batch)
Rétropropagation, mettre à jour tous les paramètres!
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(fÉpoque {epoch} terminée)
# 5. Enregistrer le modèle complet (7 milliards de paramètres, environ 13GB)
model.save("llama-2-7b-ecommerce-finetuned")
print(Fine-tuning complet terminé, nouveau modèle enregistré.)
Ceci n'est qu'une démo de concept ; l'entraînement réel nécessite une grande quantité de VRAM.
Problème d'oubli catastrophique
Le fine-tuning complet présente un grave problème : l'oubli catastrophique.
Qu'est-ce que cela signifie ? Lorsque vous entraînez le modèle sur vos données, il « oublie » progressivement les connaissances générales apprises lors du préentraînement. C'est comme une personne qui fait frénétiquement des exercices de mathématiques pour préparer un examen de mathématiques et finit par oublier le chinois et l'anglais qu'elle avait appris auparavant.
Pourquoi cela se produit-il ? Parce que le fine-tuning complet met à jour tous les paramètres. Les connaissances apprises lors du préentraînement sont encodées dans ces paramètres, et des modifications radicales peuvent détruire les connaissances d'origine.
Le résultat est le suivant : le modèle issu du fine-tuning est performant sur votre tâche, mais ses performances sur d'autres tâches générales diminuent. Pire encore, il peut perdre certaines capacités de base, comme suivre des instructions et comprendre des problèmes complexes.
Comment atténuer cela ? Vous pouvez mélanger des données de préentraînement pour un entraînement conjoint, mais cela nous ramène au vieux problème du « besoin de quantités massives de données ». Sinon, vous pouvez utiliser la régularisation pour empêcher les paramètres de trop changer, mais l'effet est limité.
Calcul des besoins en VRAM
Un autre problème du fine-tuning complet est que les besoins en VRAM sont trop importants. Calculons :
VRAM nécessaire pour entraîner un modèle = paramètres du modèle + gradients + états de l'optimiseur + activations.
Pour un modèle LLaMA-7B, en pleine précision (FP32), 7 milliards de paramètres × 4 octets = 28 GB. Les gradients occupent aussi 28 GB, et les états de l'optimiseur (optimiseur Adam) nécessitent 56 GB (deux fois les gradients). Les activations varient selon la taille de lot et la longueur de séquence, et nécessitent aussi au moins quelques GB.
En additionnant le tout, le fine-tuning complet d'un modèle 7B nécessite environ 100+ GB de VRAM — cela signifie qu'il faut au moins 8 GPU A100 (40GB) ou 2 GPU A100 (80GB).
Si la demi-précision (FP16/BF16) est utilisée, la mémoire requise peut être réduite de moitié, mais elle nécessite toujours environ 50-60 GB de VRAM. Cela reste encore hors de portée d'une personne ordinaire.
| Taille du modèle | VRAM pour le fine-tuning complet (FP16) | GPU nécessaires |
|---|---|---|
| 7B | 50-60 GB | 2×A100 (40GB) ou 1×A100 (80GB) |
| 13B | 90-100 GB | 3×A100 (40GB) ou 2×A100 (80GB) |
| 70B | 400+ GB | 12×A100 (40GB) ou 6×A100 (80GB) |
Le fine-tuning complet n'est pas inutilisable, mais il est coûteux et risqué. À moins d'avoir une raison impérieuse (par exemple, une très grande quantité de données et la nécessité de modifier le comportement fondamental du modèle), vous devriez donner la priorité aux méthodes de fine-tuning efficace en paramètres (PEFT).
Fine-tuning efficace en paramètres (PEFT)
PEFT (Parameter-Efficient Fine-Tuning) est un terme générique pour une classe de méthodes qui n'entraînent qu'un petit nombre de paramètres. Il conserve l'efficacité du fine-tuning tout en réduisant considérablement le coût.
Idée centrale de PEFT
L'idée centrale de PEFT est :Le modèle pré-entraîné contient déjà suffisamment de connaissances. Nous n'avons pas besoin de modifier tous les paramètres ; nous avons seulement besoin d'« ajuster » une petite partie des paramètres pour s'adapter à la nouvelle tâche.。
C'est comme un piano déjà construit. Vous n'avez pas besoin de reconstruire tout le piano ; vous avez juste besoin d'accorder quelques cordes pour produire le son que vous voulez.
Les avantages de PEFT sont évidents :
Premièrement, les besoins en mémoire sont considérablement réduits. Avant, il fallait 60 GB, maintenant il pourrait ne falloir que 6 GB.
-
Deuxièmement, pas d'oubli catastrophique. Les paramètres du modèle original restent inchangés ; nous ajoutons juste un petit quelque chose de nouveau, donc les connaissances existantes ne sont pas détruites.
-
Troisièmement, un faible coût de stockage. PEFT ne sauvegarde généralement qu'un petit nombre de paramètres nouvellement ajoutés (de quelques MB à quelques centaines de MB), sans avoir à sauvegarder le modèle entier.
-
Quatrièmement, cela permet la « composition de tâches ». Entraînez plusieurs petits adaptateurs, chargez-les à la demande lors de l'inférence, et un seul modèle peut servir de plusieurs modèles.
Aperçu des principales méthodes PEFT
PEFT n'est pas une méthode unique, mais un terme collectif pour une classe de méthodes. Examinons plusieurs méthodes courantes :
La première est Adapter. Insérez de petits modules de réseau de neurones au milieu de chaque couche Transformer. Pendant l'entraînement, seuls ces adaptateurs sont entraînés, et les paramètres du modèle original sont gelés. C'est une méthode PEFT ancienne et elle est maintenant moins utilisée.
-
La deuxième est Prefix Tuning. Ajoutez des vecteurs "préfixe" apprenables avant chaque couche du modèle. Ces vecteurs de préfixe n'occupent qu'une petite partie, mais ils peuvent guider la sortie du modèle.
-
La troisième est Prompt Tuning. Ajoutez des prompts doux apprenables uniquement à la couche d'entrée. C'est la méthode la plus simple, mais son effet est relativement limité.
-
La quatrième est LoRA (Low-Rank Adaptation). Ajoutez des matrices de faible rang à côté des couches d'attention du modèle. C'est actuellement la méthode PEFT la plus populaire et la plus efficace.
-
La cinquième est QLoRA (LoRA quantifié). En plus de LoRA, elle ajoute une quantification du modèle pour réduire davantage les besoins en mémoire. C'est le choix dominant aujourd'hui.
| Méthode | Principe | Nombre de paramètres entraînables | Effet |
|---|---|---|---|
| Fine-tuning complet | Mettre à jour tous les paramètres | 100% | Bon, mais peut oublier |
| Adapter | Insérer un petit réseau | 0.1%-1% | Moyenne |
| Prefix Tuning | Ajouter des vecteurs de préfixe | 0.1%-1% | Moyenne |
| Prompt Tuning | Ajouter des prompts doux | < 0.1% | Moyenne |
| LoRA | Adaptation matricielle de rang faible | 0.1%-1% | Proche du fine-tuning complet |
| QLoRA | Quantification + LoRA | 0.1%-1% | Proche du fine-tuning complet |
La bonne pratique actuelle est claire :Privilégiez LoRA, et utilisez QLoRA si la mémoire est insuffisante. Ces deux méthodes sont efficaces, peu coûteuses et disposent d’un écosystème mature, ce qui en fait le choix optimal dans la plupart des cas.
LoRA(Adaptation de rang faible)
LoRA est actuellement la méthode de fine-tuning la plus répandue, et il est important d’en comprendre le principe.
Principe de la décomposition de rang faible (compréhension intuitive)
Tout d'abord, comprenez ce qu'est le « rang » et ce que signifie « de faible rang ».
Supposons que vous ayez une matrice 100 × 100, et que les données qu'elle contient ne soient pas complètement aléatoires. Si ces données peuvent être composées de quelques « motifs » seulement, on dit que la matrice est de rang faible.
Par exemple : si chaque ligne est un multiple de la première ligne, alors le rang de cette matrice est 1. Si chaque ligne est une combinaison linéaire des deux premières lignes, alors le rang est 2. Et ainsi de suite.
L'idée clé de LoRA est :Lorsqu'un grand modèle s'adapte à une nouvelle tâche, le changement des poids est généralement « de faible rang » — c'est-à-dire que ce changement peut être représenté par très peu de paramètres.。
C'est comme si, bien qu'il y ait 1000 descriptions de produits e-commerce différentes, leurs styles d'écriture pouvaient être déterminés par seulement quelques facteurs essentiels : le ton, la longueur, les points d'insistance. Vous n'avez pas besoin de 1000 directions différentes pour décrire la variation ; 8, 16 ou 32 suffisent.
Donc l'approche de LoRA est : geler tous les paramètres du modèle d'origine, et ajouter deux petites matrices A et B à côté des couches d'attention. A est d × r, B est r × d, où r est le rang (Rank), généralement un petit nombre comme 8, 16 ou 32.
Sortie du modèle d'origine = passe avant du modèle d'origine.
Sortie LoRA = passe avant du modèle d'origine + (entrée × A × B) × facteur d'échelle.
Pendant l'entraînement, seules A et B sont entraînées ; les paramètres du modèle d'origine restent complètement inchangés.
Dérivation mathématique de LoRA
N'ayez pas peur, la dérivation mathématique est simple, seulement quelques lignes.
Supposons qu'une certaine couche du modèle d'origine soit une transformation linéaire : h = W₀x, où W₀ est la matrice de poids pré-entraînée de forme d × d.
Pendant le fine-tuning, nous voulons mettre à jour W₀, mais pas trop. L'approche de LoRA consiste à représenter la modification de W₀ comme le produit de deux petites matrices : ΔW = BA.
Ainsi, la sortie après fine-tuning est : h = W₀x + (BA)x.
Ici, B a pour dimensions d × r, A a pour dimensions r × d, et r est le rang (Rank).
Plus précisément, la formule dans l'article LoRA est :
h = W₀x + α * (BA)x
où α est un facteur d'échelle, généralement défini comme un multiple de r, de sorte que lorsque r est modifié, il n'est pas nécessaire de réajuster les hyperparamètres.
Pendant l'entraînement, W₀ est complètement gelé ; seuls A et B sont entraînables.
À l'initialisation, A est initialisé avec une distribution gaussienne aléatoire, et B est initialisé à zéro, de sorte qu'au début de l'entraînement, la partie LoRA produit zéro et n'affecte pas le comportement du modèle d'origine.
Exemple
# Implémentation minimale du principe fondamental de LoRA
# Aucune dépendance à un framework, démo pure NumPy
# ============================================
import numpy as np
class LoRALayer:
"""Implémentation minimale de la couche LoRA"""
def __init__(self, d: int, r: int, alpha: float = 1.0):
"""
d : dimension d'entrée/sortie (par ex., 4096)
r : rang (par ex., 8, 16, 32)
alpha : coefficient de mise à l'échelle
"""
self.d = d
self.r = r
self.alpha = alpha
# Poids originaux gelés (simulation d'un modèle pré-entraîné)
self.W_0 = np.random.randn(d, d) * 0.01
self.W_0.flags.writeable = False # Marqué comme non entraînable
# Matrices A et B de LoRA, entraînables
# A : r × d, initialisée avec une gaussienne aléatoire
self.A = np.random.randn(r, d) * 0.01
# B : d × r, initialisée à zéro (n'affecte pas le modèle original au début de l'entraînement)
self.B = np.zeros((d, r))
def forward(self, x: np.ndarray) -> np.ndarray:
"""Passe avant : sortie du modèle original + sortie LoRA"""
# Sortie du modèle original
original_output = x @ self.W_0.T
# Sortie LoRA : (x @ A.T) @ B.T = x @ (B @ A).T
lora_output = (x @ self.A.T) @ self.B.T
# Sortie fusionnée
return original_output + self.alpha * lora_output
def get_trainable_params(self) -> dict:
"""Obtenir les paramètres entraînables (uniquement A et B)"""
return {"A": self.A, "B": self.B}
def count_params(self) -> dict:
"""Compter le nombre de paramètres"""
total_original = self.d * self.d
total_lora = self.r * self.d + self.d * self.r
return {
"original_params": total_original,
"lora_params": total_lora,
"ratio": total_lora / total_original * 100,
}
# ============================================
# Démontrer les économies de paramètres de LoRA
# ============================================
# On suppose d = 4096 (similaire à la dimension de la couche cachée de LLaMA-7B)
# r = 8 (taille de rang couramment utilisée)
d = 4096
r = 8
lora = LoRALayer(d=d, r=r, alpha=r)
params_info = lora.count_params()
print(fNombre de paramètres d'origine : {params_info['original_params']:,})
print(fNombre de paramètres LoRA : {params_info['lora_params']:,})
print(fRatio de paramètres : {params_info['ratio']:.4f}%)
# Exemple de sortie :
# Nombre de paramètres d'origine : 16,777,216
# Nombre de paramètres LoRA : 65,536
# Ratio de paramètres : 0.3906%
Voyez, LoRA n'a besoin d'entraîner que 0.4% des paramètres, et pourtant il peut obtenir des résultats proches d'un fine-tuning complet. C'est là toute sa magie.
Choix des paramètres r (rang) et α
LoRA a deux hyperparamètres clés : le rang r et le coefficient d'échelle α.
r détermine la capacité de LoRA. Plus r est grand, plus il peut représenter des changements complexes, mais le nombre de paramètres augmente aussi.
Valeurs courantes de r : 8, 16, 32, 64. Pour la plupart des tâches, r = 8 ou 16 suffit. Aller au-delà de r = 64 donne généralement des rendements décroissants et augmente le coût d'entraînement.
Comment choisir r ? Une suggestion pratique : essayez d'abord r = 8, et si les résultats sont mauvais, augmentez-le à 16 ou 32. Ne commencez pas avec un r très grand.
α est le coefficient d'échelle. Un réglage courant est α = r, afin que l'échelle de ΔW = BA reste à peu près stable. Sinon, définissez α = 1 et laissez l'utilisateur l'ajuster lui-même.
Autres paramètres à prendre en compte :
Modules cibles : à quelles couches LoRA doit-il être appliqué ? Généralement, il est efficace de n'ajouter que les projections query et value (q_proj, v_proj) des couches d'attention. Il peut aussi être ajouté à davantage de couches, mais cela augmente le nombre de paramètres.
Dropout : LoRA peut ajouter du dropout pour éviter le surapprentissage ; les valeurs courantes sont 0.05 ou 0.1.
| Paramètre | Valeur courante | Description |
|---|---|---|
| r (rang) | 8, 16, 32 | Plus grand signifie une capacité supérieure, mais aussi plus de paramètres |
| alpha | r ou 1 | Coefficient d'échelle, généralement défini à r |
| target_modules | ["q_proj", "v_proj"] | Couches auxquelles appliquer LoRA |
| lora_dropout | 0.0, 0.05, 0.1 | Éviter le surapprentissage |
Fusion des poids LoRA
Après l'entraînement de LoRA, il y a deux options au moment de l'inférence.
-
La première consiste à garder LoRA séparé. À l'inférence, charger le modèle original, puis charger les poids LoRA, et calculer dynamiquement la sortie fusionnée. L'avantage est que plusieurs LoRA peuvent être commutés de manière flexible ; l'inconvénient est que l'inférence est légèrement plus lente (généralement négligeable).
-
La seconde consiste à fusionner les poids LoRA. Ajouter ΔW = BA de LoRA au W₀ du modèle original, ce qui donne W = W₀ + α×BA. Cela produit un seul fichier de modèle, et l'inférence fonctionne comme pour un modèle normal.
L'avantage de la fusion est une inférence rapide et aucune prise en charge spéciale de LoRA n'est nécessaire. L'inconvénient est qu'une fois fusionné, cela ne peut pas être annulé, et s'il y a plusieurs LoRA, chacun doit être fusionné dans un fichier de modèle distinct.
Exemple
# Démonstration de fusion des poids LoRA
# ============================================
import numpy as np
def merge_lora_weights(W_0: np.ndarray, A: np.ndarray, B: np.ndarray,
alpha: float = 1.0) -> np.ndarray:
"""
Fusionner les poids LoRA dans les poids originaux
W = W_0 + alpha * (B @ A)
Paramètres :
W_0: poids originaux (d, d)
A: matrice A de LoRA (r, d)
B: matrice B LoRA (d, r)
alpha : coefficient de mise à l'échelle
Retourne :
Poids fusionnés
"""
# calculer delta_W = alpha * (B @ A)
delta_W = alpha * (B @ A)
# fusionner dans les poids d'origine
W_merged = W_0 + delta_W
return W_merged
# ============================================
# démontrer le processus de fusion
# ============================================
d = 4096 # dimension de la couche cachée
r = 8 # rang LoRA
# simuler les poids d'origine et les poids LoRA
W_0 = np.random.randn(d, d) * 0.01
A = np.random.randn(r, d) * 0.01
B = np.zeros((d, r)) # en pratique, B est le résultat après entraînement
# poids fusionnés
W_merged = merge_lora_weights(W_0, A, B, alpha=r)
print(f"forme du poids original : {W_0.shape}")
print(f"forme du poids fusionné : {W_merged.shape}")
print("fusion des poids terminée, utilisable comme un modèle normal")
QLoRA (LoRA quantifié)
QLoRA pousse le faible coût de LoRA à l'extrême—le fine-tuning de modèles 70B est possible sur des GPU grand public.
Quantification 4 bits + LoRA
L'idée de QLoRA est simple : quantifier le modèle en 4 bits, puis effectuer un fine-tuning LoRA par-dessus.
Qu'est-ce que la quantification ? Elle convertit les nombres à virgule flottante (FP16/BF16, chacun prenant 2 octets) en représentations de précision inférieure. La quantification 4 bits utilise 4 bits (0,5 octet) pour représenter un poids.
Cela réduit la taille du modèle de 75 % : un modèle 7B passe de 13 GB à environ 3,5 GB, un modèle 13B de 26 GB à environ 6,5 GB, et un modèle 70B de 140 GB à environ 35 GB.
Mais la quantification directe perd en précision. L'innovation de QLoRA est un type de données appelé NF4 (Normalized Float 4), qui préserve mieux les informations dans les poids des réseaux de neurones.
Approche de QLoRA :
-
1. Quantifier le modèle pré-entraîné en 4 bits (format NF4).
-
2. Ajouter des adaptateurs LoRA par-dessus.
-
3. Pendant l'entraînement, le modèle de base quantifié en 4 bits est complètement gelé ; seuls les poids LoRA FP16 sont entraînés.
Le résultat : les besoins en VRAM diminuent encore de 75 %, avec presque aucune perte de performance.
Bibliothèque bitsandbytes
bitsandbytes est la bibliothèque principale pour la quantification, développée par Tim Dettmers.
Elle fournit :
-
1. Quantification en 4 bits (NF4).
-
2. Double Quantization — quantifie également les constantes de quantification, ce qui permet d'économiser un peu plus de VRAM.
-
3. Paged Optimizer — déplace certaines données vers la mémoire CPU lorsque la VRAM est insuffisante.
Les utilisateurs ordinaires n'ont pas besoin d'interagir directement avec bitsandbytes ; les bibliothèques peft et transformers l'enveloppent déjà. Mais il est utile de savoir qu'elle existe.
Solutions de fine-tuning GPU grand public
Avec QLoRA, les gens ordinaires peuvent eux aussi se permettre de faire du fine-tuning.
La RTX 3090/4090 (24GB VRAM) peut facilement faire du fine-tuning sur des modèles 7B/13B, et peut même tenter des modèles 70B (ce qui nécessite des longueurs de séquence plus longues ou des tailles de lot plus petites).
Estimation des besoins en VRAM :
| Taille du modèle | Besoins en VRAM pour QLoRA | GPU adapté |
|---|---|---|
| 7B | 5-8 GB | RTX 3060/4060 et au-delà |
| 13B | 8-12 GB | RTX 3070/4070 et au-delà |
| 34B | 15-20 GB | RTX 3090/4090 |
| 70B | 30-40 GB | RTX 3090/4090 × 2 ou A100 |
QLoRA change la donne. Il a transformé le fine-tuning de grands modèles de « seules les grandes entreprises peuvent le faire » en « des personnes ordinaires peuvent le faire sur leurs propres ordinateurs portables de jeu ». C'est l'une des avancées les plus importantes de l'IA open source.
Préparation des données d'entraînement
La préparation des données est la partie la plus chronophage et la plus critique du processus de fine-tuning. Des données de mauvaise qualité en entrée, des résultats de mauvaise qualité en sortie — la qualité des données détermine la limite supérieure du modèle.
Formats de données (formats Alpaca, ShareGPT)
Il existe plusieurs formats de données courants ; choisissez-en un et gardez-le cohérent.
Le premier est le format Alpaca. C'est l'un des formats les plus populaires, popularisé par le projet Stanford Alpaca.
Le format est : instruction (instruction), entrée (input, facultatif), sortie (output).
Exemple
# Exemple de format de données Alpaca
# ============================================
alpaca_data = [
{
"instruction": Générer une description de produit e-commerce.,
"input": Nom du produit : Montre de sport intelligente\nFonctions : suivi de la fréquence cardiaque, suivi du sommeil, positionnement GPS, résistance à l'eau jusqu'à 50 mètres.,
"output": Cette montre de sport intelligente est un compagnon idéal pour les passionnés de fitness. Elle dispose d'un suivi précis de la fréquence cardiaque et d'un suivi scientifique du sommeil pour vous aider à bien comprendre votre état de santé. Le GPS intégré vous permet d'enregistrer vos parcours d'exercice sans porter votre téléphone. Avec une résistance à l'eau de 50 mètres, vous pouvez la porter en toute confiance en nageant.
},
{
"instruction": Réécrivez le texte suivant dans une expression plus familière.,
"input": Ce produit adopte une technologie avancée et présente des performances élevées et une faible consommation d'énergie.,
"output": Ce produit utilise une technologie très avancée, avec de fortes performances et une grande efficacité énergétique !
},
Plus de données
]
print(Le format Alpaca contient trois champs :)
print(- instruction : Instruction de tâche (obligatoire))
print(- input : entrée supplémentaire (facultatif, peut être une chaîne vide si aucune))
print(- output : sortie attendue (obligatoire))
Le second est le format ShareGPT. Ce format est utilisé pour les données de dialogue et prend en charge les conversations multi-tours.
Le format est : conversations (liste de dialogue), chaque tour a from (rôle) et value (contenu).
Exemple
Exemple de format de données ShareGPT
# ============================================
sharegpt_data = [
{
"conversations": [
{
"from": "human",
"value": Bonjour, j'aimerais en savoir plus sur vos montres connectées.
},
{
"from": "gpt",
"value": "Bonjour ! Notre montre connectée dispose de fonctions telles que la surveillance de la fréquence cardiaque, le suivi du sommeil et la localisation GPS, ainsi qu'une résistance à l'eau jusqu'à 50 mètres. Y a-t-il quelque chose en particulier que vous souhaitez savoir ?"
},
{
"from": "human",
"value": Quelle est l'autonomie de la batterie ?
},
{
"from": "gpt",
"value": En utilisation normale, la batterie dure 7 jours ; avec le mode sport GPS activé, elle dure 12 heures.
}
]
},
# ... Plus de conversations
]
print(Le format ShareGPT est utilisé pour les données de conversation à plusieurs tours :)
print(- conversations: liste des conversations)
print(- from: rôle (généralement human et gpt))
print(- value: contenu de la conversation)
Il existe également un format simple de paire entrée-sortie, adapté aux tâches simples.
Exemple
# Format simple de paire entrée-sortie
# ============================================
simple_data = [
{
"text": "Utilisateur : Génère une description de produit\nProduit : Gobelet thermos\n\nAssistant : Ce gobelet thermos de haute qualité utilise..."
},
{
"text": "Utilisateur : Génère une description de produit\nProduit : Écouteurs sans fil\n\nAssistant : Écouteurs Bluetooth entièrement sans fil, Bluetooth 5.3..."
},
# ... plus de données
]
print("Ce format concatène directement tout le contenu dans un seul champ texte, ce qui est le plus flexible mais aussi le moins standardisé.")
Quel format est le meilleur ? Le format Alpaca est le plus courant ; la plupart des frameworks d'entraînement le prennent en charge. Pour les tâches de conversation, ShareGPT est plus adapté.
Points clés pour le nettoyage des données
Le nettoyage des données est bien plus important que vous ne le pensez. 80% de la performance de votre modèle dépend de la qualité des données.
-
Premièrement, la déduplication. Les données en double amèneront le modèle à surapprendre ces motifs répétitifs. Vérifiez et supprimez les données complètement identiques ou très similaires.
-
Deuxièmement, filtrez les données de faible qualité. Supprimez les données trop courtes, trop longues, mal formatées ou comportant des erreurs évidentes. Par exemple, des sorties avec seulement quelques caractères, ou avec du texte corrompu.
-
Troisièmement, unifiez les formats. Par exemple, certaines sorties utilisent des guillemets chinois, d'autres utilisent des guillemets anglais ; certaines se terminent par un point, d'autres non. Essayez de les unifier pour que le modèle apprenne plus vite.
-
Quatrièmement, équilibrez la distribution. Si certaines catégories de données sont particulièrement abondantes et que d'autres sont rares, le modèle sera biaisé en faveur de la catégorie majoritaire. Envisagez un suréchantillonnage de la classe minoritaire, ou un sous-échantillonnage de la classe majoritaire.
-
Cinquièmement, vérifiez les fuites de données. Assurez-vous que l'ensemble de test et l'ensemble de validation n'apparaissent pas dans l'ensemble d'entraînement.
Exemple
# Exemple simple de nettoyage de données
# ============================================
import json
from typing import List, Dict
def clean_alpaca_data(data: List[Dict]) -> List[Dict]:
"""Fonction simple de nettoyage de données"""
cleaned = []
seen_outputs = set()
for item in data:
# 1. Vérifier si les champs requis existent
if "instruction" not in item or "output" not in item:
continue
instruction = item["instruction"].strip()
output = item["output"].strip()
# 2. Filtrer les sorties trop courtes
if len(output) < 10:
continue
# 3. Filtrer les sorties trop longues (ajuster selon la tâche)
if len(output) > 2000:
continue
# 4. Dédupliquer (déduplication approximative à l'aide de la sortie)
if output in seen_outputs:
continue
seen_outputs.add(output)
# 5. Unification simple du format
# Par exemple, remplacer les guillemets chinois par des guillemets anglais
output = output.replace(""", "\"").replace(""", "\"")
# Supprimer les espaces de début et de fin
output = output.strip()
cleaned.append({
"instruction": instruction,
"input": item.get("input", "").strip(),
"output": output
})
return cleaned
# Charger les données
with open("data/training_data_raw.json", "r", encoding="utf-8") as f:
raw_data = json.load(f)
# Nettoyer les données
cleaned_data = clean_alpaca_data(raw_data)
print(f"Nombre de données brutes : {len(raw_data)}")
print(f"Nombre de données nettoyées : {len(cleaned_data)}")
print(f"Filtrage de {len(raw_data) - len(cleaned_data)} entrées de données de faible qualité")
# Enregistrer les données nettoyées
with open("data/training_data_cleaned.json", "w", encoding="utf-8") as f:
json.dump(cleaned_data, f, ensure_ascii=False, indent=2)
print("Données nettoyées enregistrées dans data/training_data_cleaned.json")
Règles heuristiques pour le volume de données
De combien de données avez-vous besoin ? C'est l'une des questions les plus fréquentes.
La réponse est : cela dépend de la difficulté de la tâche.
-
Tâches simples (par ex., rendre un texte plus conversationnel) : 100-300 entrées peuvent suffire.
-
Tâches moyennes (par ex., générer des résumés dans un format spécifique) : 300-1000 entrées conviennent.
-
Tâches complexes (par ex., questions-réponses dans des domaines professionnels) : 1000-5000 entrées sont idéales.
Bien sûr, davantage de données est généralement préférable, mais avec des rendements marginaux décroissants. 1000 entrées valent bien mieux que 500, mais 10000 ne valent peut-être pas beaucoup mieux que 5000.
Suggestion pratique : préparez d'abord 500 entrées de données de haute qualité et lancez un essai. Si les résultats sont mauvais, analysez si cela est dû à des données insuffisantes, à une mauvaise qualité des données ou à des problèmes d'hyperparamètres, puis apportez des améliorations ciblées.
Qualité des données >> quantité de données. 1000 entrées soigneusement élaborées fonctionnent bien mieux que 10000 entrées assemblées à la hâte.
Passer plus de temps dans la phase de préparation des données en vaut la peine. L'entraînement du modèle n'est qu'une question d'« appuyer sur un bouton », mais la qualité des données détermine si le fait d'appuyer sur ce bouton est utile.
Fine-tuning supervisé (SFT) en pratique
Enfin, nous sommes arrivés à la section pratique. Nous utiliserons Unsloth ou LLaMA-Factory pour implémenter le flux de travail complet de fine-tuning.
Configuration de l'environnement
Étape 1 : Installez les bibliothèques nécessaires.
Configuration de l'environnement
# Installez les dépendances (il est recommandé de créer un nouvel environnement conda)
# ============================================
# Créer un nouvel environnement conda
conda create -n finetuning python=3.10 -y
conda activate finetuning
# Installer PyTorch (choisissez la commande appropriée en fonction de votre version de CUDA)
# Visitez https://pytorch.org/get-started/locally/ pour les dernières commandes
conda install pytorch pytorch-cuda=12.1 -c pytorch -c nvidia
# Installer les bibliothèques de l'écosystème HuggingFace
pip install transformers datasets accelerate peft bitsandbytes
# Installer Unsloth (facultatif, pour un entraînement plus rapide)
pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps trl xformers
# Ou utilisez LLaMA-Factory (framework d'entraînement tout-en-un)
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e ".[torch,metrics]"
echo "Configuration de l'environnement terminée !"
Utilisation d'Unsloth/LLaMA-Factory
Unsloth est une bibliothèque d'entraînement optimisée, rapide et économe en mémoire. LLaMA-Factory est un framework d'entraînement tout-en-un qui prend en charge plusieurs modèles et méthodes.
Regardons d'abord le code pour l'entraînement avec Unsloth + peft :
Exemple
# Utilisation d'Unsloth pour le fine-tuning LoRA (version complète)
# Fonctionne sur RTX 3090/4090
# ============================================
import torch
from datasets import load_dataset, Dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
BitsAndBytesConfig,
)
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
import json
# ============================================
# 1. Paramètres de configuration
# ============================================
MODEL_NAME = "unsloth/mistral-7b-v0.2-bnb-4bit" # ou d'autres modèles
DATA_PATH = "data/training_data_cleaned.json" # vos données
OUTPUT_DIR = "output/example-ecommerce-model" # répertoire de sortie
# Configuration de quantification 4-bit
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
# Configuration LoRA
lora_config = LoraConfig(
r=16, # Rang, 8-64 sont tous corrects, 16 est un bon point de départ
lora_alpha=16, # Facteur d'échelle, généralement égal à r
target_modules=[ # Couches sur lesquelles appliquer LoRA, ce sont des choix courants
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"gate_proj",
"up_proj",
"down_proj",
],
lora_dropout=0.05, # Dropout pour éviter le surapprentissage
bias="none",
task_type="CAUSAL_LM" # Modèle de langage causal
)
# Paramètres d'entraînement
training_args = TrainingArguments(
output_dir=OUTPUT_DIR,
per_device_train_batch_size=4, # Taille de lot par GPU
per_device_eval_batch_size=4,
gradient_accumulation_steps=4, # Accumulation de gradient, équivalente à un lot plus grand
learning_rate=2e-5, # Taux d'apprentissage, 2e-5 à 5e-5 est une plage courante
num_train_epochs=3, # Nombre d'époques, 2-5 époques est approprié
logging_steps=10, # Journaliser toutes les 10 étapes
evaluation_strategy="epoch", # Évaluer une fois par époque
save_strategy="epoch", # Enregistrer une fois par epoch
fp16=True, # Utiliser l'entraînement en précision mixte
bf16=False, # Pour les GPU Ampere et plus récents (séries 30/40), vous pouvez définir bf16=True
optim="paged_adamw_32bit", # Optimiseur paginé pour éviter l'OOM
report_to="none", # Ne pas envoyer de rapports à des plateformes comme wandb
save_total_limit=3, # Enregistrer au maximum 3 checkpoints
load_best_model_at_end=True, # Enfin charger le meilleur modèle
metric_for_best_model="eval_loss", # Sélectionner le meilleur modèle à l'aide de la perte d'évaluation
)
# ============================================
# 2. Charger les données et formater
# ============================================
def format_example(example):
"""Formater les données au format Alpaca en entrée du modèle"""
instruction = example["instruction"]
input_text = exemple.get("input", "")
output = exemple["output"]
if input_text.strip() == "":
# Cas sans entrée supplémentaire
prompt = f"""Ci-dessous se trouve une instruction qui décrit une tâche. Écrivez une réponse qui répond correctement à la requête.
### Instruction :
{instruction}
### Réponse :
"""
else:
# Cas avec entrée supplémentaire
prompt = f"""Ci-dessous se trouve une instruction qui décrit une tâche, accompagnée d'une entrée qui fournit davantage de contexte. Écrivez une réponse qui répond correctement à la requête.
### Instruction :
{instruction}
### Entrée :
{input_text}
### Réponse :
"""
# Le texte complet est invite + sortie
return {
"prompt": invite,
"completion": sortie,
"text": invite + sortie
}
# Charger les données
dataset = load_dataset("json", data_files=DATA_PATH, split="train")
# Séparer les ensembles d'entraînement et de validation (90% entraînement, 10% validation)
dataset = dataset.train_test_split(test_size=0.1, seed=42)
train_dataset = dataset["train"]
eval_dataset = dataset["test"]
# Formater les données
train_dataset = train_dataset.map(format_example)
eval_dataset = eval_dataset.map(format_example)
print(f"Taille de l'ensemble d'entraînement : {len(train_dataset)}")
print(f"Taille de l'ensemble de validation : {len(eval_dataset)}")
# ============================================
# 3. Charger le modèle et le tokenizer
# ============================================
print("Chargement du modèle...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token # Définir le token de padding
tokenizer.padding_side = "right" # Remplir depuis la droite
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
use_auth_token=True, # Si la connexion HuggingFace est requise
)
# Encapsuler LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # Afficher la proportion de paramètres entraînables
# ============================================
# 4. Créer le trainer et démarrer l'entraînement
# ============================================
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
args=training_args,
tokenizer=tokenizer,
peft_config=lora_config,
dataset_text_field="text", # Quel champ contient le texte complet
max_seq_length=2048, # Longueur maximale de séquence
packing=False, # Ne pas empaqueter les échantillons
)
print("Début de l'entraînement...")
trainer.train()
print("Entraînement terminé !")
# ============================================
# 5. Enregistrer le modèle
# ============================================
# Enregistrer l'adaptateur LoRA (petite taille, de quelques MB à quelques dizaines de MB)
model.save_pretrained(f"{OUTPUT_DIR}/lora_adapter")
tokenizer.save_pretrained(f"{OUTPUT_DIR}/lora_adapter")
print(f"Adaptateur LoRA enregistré dans {OUTPUT_DIR}/lora_adapter")
# Si vous souhaitez fusionner les poids LoRA (facultatif)
# merged_model = model.merge_and_unload()
# merged_model.save_pretrained(f"{OUTPUT_DIR}/merged_model")
# tokenizer.save_pretrained(f"{OUTPUT_DIR}/merged_model")
# print(f"Modèle fusionné enregistré dans {OUTPUT_DIR}/merged_model")
Si vous souhaitez une approche plus simple, vous pouvez utiliser LLaMA-Factory, qui fournit à la fois une interface Web et une interface en ligne de commande :
Exemple
# Utiliser LLaMA-Factory pour l'entraînement
# ============================================
# Méthode 1 : démarrer l'interface Web (la plus intuitive)
cd LLaMA-Factory
python src/train_web.py
# Méthode 2 : entraînement en ligne de commande (adapté à l'automatisation)
python src/train.py \
--model_name_or_path mistralai/Mistral-7B-v0.2 \
--dataset example_ecommerce \
--dataset_dir ./data \
--output_dir ./output/example-model \
--finetuning_type lora \
--quantization_bit 4 \
--lora_rank 16 \
--lora_alpha 16 \
--lora_dropout 0.05 \
--lora_target q_proj,v_proj,k_proj,o_proj,gate_proj,up_proj,down_proj \
--per_device_train_batch_size 4 \
--gradient_accumulation_steps 4 \
--lr_scheduler_type cosine \
--logging_steps 10 \
--save_strategy epoch \
--learning_rate 2e-5 \
--num_train_epochs 3 \
--fp16 true \
--plot_loss true
Réglages des hyperparamètres d'entraînement
Choisir les hyperparamètres est un art, mais il existe quelques règles empiriques.
Taux d'apprentissage : C'est l'hyperparamètre le plus important. Pour LoRA, 2e-5 à 5e-5 est un point de départ sûr. Si l'entraînement est instable (la perte oscille), diminuez-le un peu. Si l'entraînement est trop lent (la perte diminue très lentement), augmentez-le un peu.
Taille de lot : Plus c'est grand, mieux c'est, mais c'est limité par la VRAM. per_device_train_batch_size peut être défini sur 2, 4 ou 8. Si la VRAM est insuffisante, utilisez gradient_accumulation_steps pour accumuler les gradients. Par exemple, batch_size=2, accumulation_steps=4 équivaut à batch_size=8.
Époques : 2 à 5 époques, c'est approprié. Trop peu et le modèle n'apprendra pas assez ; trop, et cela peut causer du surapprentissage. Vous pouvez surveiller la perte d'évaluation, et arrêter lorsqu'elle cesse de diminuer.
Rang LoRA (r) : 8 à 64 conviennent tous. 16 est un bon point de départ. Utilisez 8 pour les petites tâches, et 32 ou 64 pour les tâches complexes.
Longueur maximale de séquence : Décidez en fonction de vos données. 512, 1024, 2048, 4096 sont tous courants. Plus elle est longue, plus il faut de VRAM.
| Hyperparamètre | Valeur de départ recommandée | Direction d'ajustement |
|---|---|---|
| learning_rate | 2e-5 | Si la perte oscille, diminuez ; si elle diminue trop lentement, augmentez |
| per_device_train_batch_size | 4 | Si la VRAM est suffisante, augmentez ; si elle est insuffisante, diminuez |
| gradient_accumulation_steps | 4 | Batch équivalent = batch_size × accumulation |
| num_train_epochs | 3 | En cas de surapprentissage, réduire ; en cas de sous-apprentissage, augmenter |
| lora_rank (r) | 16 | Tâches simples 8, tâches complexes 32-64 |
| max_seq_length | 1024 ou 2048 | Ajustez selon la longueur de vos données |
Suivi de l’entraînement et interprétation de la perte
Pendant l’entraînement, faites attention à quelques métriques.
Perte d’entraînement : la perte sur l’ensemble d’entraînement doit diminuer régulièrement. Si elle diminue rapidement, cela signifie que le taux d’apprentissage est peut-être trop élevé ou qu’il y a trop peu de données. Si elle diminue lentement, cela signifie que le taux d’apprentissage est trop faible ou que la capacité du modèle est insuffisante.
-
Perte d’évaluation : la perte sur l’ensemble de validation, c’est la métrique la plus importante. Elle doit diminuer en même temps que la perte d’entraînement. Si la perte d’évaluation commence à augmenter alors que la perte d’entraînement continue de diminuer, cela signifie un surapprentissage, et l’entraînement doit être arrêté.
Signes de surapprentissage : la perte d'évaluation augmente après avoir d'abord diminué, tandis que la perte d'entraînement continue de diminuer. Solutions : réduire les époques, augmenter le dropout, ajouter des données, diminuer le rang LoRA.
Signes de sous-apprentissage : la perte d'entraînement et la perte d'évaluation sont toutes deux élevées, et elles diminuent lentement. Solutions : augmenter les époques, augmenter le rang LoRA, ajouter des données, augmenter légèrement le taux d'apprentissage.
Évaluation des effets du fine-tuning
Après l'entraînement, comment savoir si le modèle est bon ?
Sélection des métriques d'évaluation
La méthode d'évaluation dépend du type de votre tâche.
La première est l'évaluation manuelle. Demandez à des personnes de lire la sortie du modèle et d'évaluer sa qualité. C'est la méthode la plus fiable, mais aussi la plus chronophage.
Vous pouvez créer un tableau de notation simple :
Exemple
# Modèle simple d'évaluation manuelle
# ============================================
evaluation_prompts = [
{
"instruction": "Générer une description de produit e-commerce",
"input": "Nom du produit : machine à café portable\nFonction : extraction en un clic, design portable, chargement USB",
"reference": "Cette machine à café portable vous permet de déguster du café fraîchement moulu à tout moment, où que vous soyez. L'extraction en un seul geste est simple à utiliser, et son design compact et portable convient au bureau, aux voyages et aux activités en plein air. Grâce à son design à recharge USB, vous n'avez pas à vous soucier des problèmes d'alimentation."
},
{
"instruction": "Générer une description de produit e-commerce",
"input": "Nom du produit : clavier mécanique\nCaractéristiques : commutateur bleu, rétroéclairage RGB, 87 touches, capuchons de touches PBT",
"reference": "Ce clavier mécanique de 87 touches utilise des commutateurs bleus précis pour une excellente sensation de frappe. Le rétroéclairage RGB sur toutes les touches le garde bien visible la nuit. Les capuchons de touches en PBT sont durables et ne développent pas de revêtement brillant, restant comme neufs même après une utilisation prolongée."
},
# ... 10-20 cas de test
]
def evaluate_model(model, tokenizer, prompts):
"""Évaluer le modèle avec un ensemble d'invites"""
results = []
for item in prompts:
# Générer la sortie
inputs = format_example_for_inference(item)
output = generate_with_model(model, tokenizer, inputs)
results.append({
"instruction": item["instruction"],
"input": item["input"],
"model_output": output,
"reference": item["reference"],
# Laisser vide pour une notation manuelle
"score_relevance": None, # 1-5, pertinence
"score_quality": None, # 1-5, qualité
"score_format": None, # 1-5, exactitude du format
})
return results
# Lancer l'évaluation
results = evaluate_model(model, tokenizer, evaluation_prompts)
# Enregistrer les résultats pour une notation manuelle ultérieure
with open("evaluation_results.json", "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(Les résultats de l'évaluation ont été enregistrés. Veuillez les noter manuellement avant l'analyse.)
La seconde est l'évaluation automatique. Utilisez des métriques telles que BLEU, ROUGE et BLEURT. Ces métriques ne sont pas parfaites, mais elles permettent une itération rapide.
La troisième est le test A/B. Mélangez les sorties de l'ancien modèle et du nouveau modèle pour une évaluation à l'aveugle afin de voir si le nouveau modèle est vraiment meilleur.
Prévention du surapprentissage
Le surapprentissage est le problème le plus courant lors du fine-tuning. Il présente de bonnes performances sur l'ensemble d'entraînement, mais de mauvaises performances sur de nouvelles données.
Méthodes pour prévenir le surapprentissage :
1. Arrêt précoce : surveillez la perte d'évaluation et arrêtez l'entraînement lorsqu'elle cesse de diminuer.
2. Ajoutez plus de données : des données plus nombreuses et plus diversifiées constituent le meilleur moyen de prévenir le surapprentissage.
3. Augmentation des données : réécrivez les données existantes pour créer de la variation.
4. Réduisez la capacité du modèle : diminuez le rang LoRA et réduisez le nombre de couches LoRA.
5. Augmentez le dropout : définissez lora_dropout dans la configuration LoRA à 0.05 ou 0.1.
6. Décroissance des poids : ajoutez weight_decay=0.01 aux paramètres d'entraînement.
Déploiement de modèles affinés
Une fois le modèle entraîné, comment l'utilisez-vous ?
Chargement de LoRA pour l'inférence
La première méthode consiste à charger le modèle de base, puis à charger l'adaptateur LoRA.
Exemple
# Charger LoRA et effectuer l'inférence
# ============================================
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
def load_finetuned_model(base_model_name, lora_adapter_path):
"""Charger le modèle de base et l'adaptateur LoRA"""
# Charger le tokenizer
tokenizer = AutoTokenizer.from_pretrained(base_model_name)
tokenizer.pad_token = tokenizer.eos_token
# Charger le modèle de base
model = AutoModelForCausalLM.from_pretrained(
base_model_name,
device_map="auto",
torch_dtype=torch.float16,
trust_remote_code=True,
)
# Charger l'adaptateur LoRA
model = PeftModel.from_pretrained(model, lora_adapter_path)
# Fusionner les poids (facultatif, rend l'inférence plus rapide)
model = model.merge_and_unload()
return model, tokenizer
def generate_product_description(model, tokenizer, product_name, features):
"""Générer une description de produit e-commerce"""
# Construire le prompt (comme pendant l'entraînement !)
prompt = f"""Ci-dessous se trouve une instruction qui décrit une tâche. Écrivez une réponse qui répond correctement à la requête.
### Instruction :
Générer une description de produit e-commerce
### Entrée :
Nom du produit : {product_name}
Caractéristiques : {features}
### Réponse :
"""
# Générer
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=200,
temperature=0.7, # Contrôler l'aléatoire, 0-1, plus grand signifie plus aléatoire
top_p=0.9,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
)
# Décoder la sortie
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
# Ne prendre que la partie Réponse
response = generated_text.split("### Response:")[-1].strip()
return response
# ============================================
# Exemple d'utilisation
# ============================================
BASE_MODEL = "mistralai/Mistral-7B-v0.2"
LORA_PATH = "output/example-ecommerce-model/lora_adapter"
print("Chargement du modèle...")
model, tokenizer = load_finetuned_model(BASE_MODEL, LORA_PATH)
print("Modèle chargé avec succès !")
# Test de génération
product_name = "Aspirateur robot intelligent"
features = "Navigation laser, recharge automatique, contrôle via l'application, forte aspiration de 2000Pa"
description = generate_product_description(
model, tokenizer, product_name, features
)
print(f"\nNom du produit : {product_name}")
print(fDescription générée :\n{description}")
Méthodes de déploiement
Il existe plusieurs options de déploiement courantes.
-
La première consiste à utiliser vLLM. Il s'agit d'un moteur d'inférence haute performance qui prend en charge LoRA, le continuous batching et d'autres fonctionnalités. Recommandé pour les environnements de production.
-
La deuxième consiste à utiliser Text Generation Inference. Le service d'inférence officiel de HuggingFace, riche en fonctionnalités.
-
La troisième consiste à l'encapsuler vous-même avec FastAPI. Simple et direct, adapté aux déploiements à petite échelle.
-
La quatrième consiste à utiliser Ollama. Convertissez le modèle au format GGUF et exécutez-le avec Ollama, adapté au déploiement local.