Guide du fine-tuning des LLM : quand l'utiliser, LoRA et QLoRA

Traduction automatique

Cet article a été traduit automatiquement depuis la version originale en anglais.

Ce guide s’adresse aux ingénieurs Python qui doivent décider s’il faut faire le fine-tuning d’un grand modèle de langage, et comment s’y prendre. Vous verrez quand choisir le fine-tuning plutôt que le prompting ou la recherche documentaire, et comment choisir entre des méthodes comme LoRA et QLoRA. Le guide explique aussi comment préparer les données, évaluer le modèle entraîné et planifier son déploiement.

La partie pratique exécute un dépôt compagnon qui fait le fine-tuning d’un modèle de 4B pour le function calling.

Pour une comparaison plus courte entre fine-tuning, RAG et prompting, voir Fine-Tuning vs RAG vs Prompting.

Faut-il vraiment faire du fine-tuning ?

Avant de dépenser des heures de GPU, déterminez si le fine-tuning est le bon outil pour votre problème. La figure suit une requête et montre où agit chacune des quatre corrections, sur quel échec la tester en premier, et ce qu’elle ne peut pas corriger.

Une requête traverse le prompt, la fenêtre de contexte, les poids du modèle et le choix du jeton suivant ; le prompting, la recherche documentaire, le fine-tuning et le constrained decoding agissent chacun à l'un de ces points, et seul le fine-tuning modifie les poidsUne requête traverse le prompt, la fenêtre de contexte, les poids du modèle et le choix du jeton suivant ; le prompting, la recherche documentaire, le fine-tuning et le constrained decoding agissent chacun à l'un de ces points, et seul le fine-tuning modifie les poids

Le fine-tuning modifie un comportement encodé dans les poids. La recherche documentaire fournit des preuves au moment de l’inférence : c’est donc le meilleur point de départ pour des faits qui changent ou qui exigent des citations. Les nouveaux documents ne deviennent disponibles qu’après l’ingestion et l’indexation ; vérifiez cette fraîcheur par rapport à votre exigence.

Des prompts et des exemples clairs sont moins chers à itérer qu’un nouvel entraînement. Ils peuvent déjà résoudre un problème de style ou de respect des instructions. Le fine-tuning peut réduire les tokens de prompt répétés, mais la latence de service dépend du modèle, de la longueur de sortie, du matériel et de l’implémentation ; une mise à jour des poids n’est pas en soi une accélération.

Pour la syntaxe, des bibliothèques comme xgrammar et outlines masquent les tokens suivants invalides pour un schéma ou une grammaire pris en charge. Une génération contrainte menée à terme appartient à ce langage. Traitez séparément la troncature et les autres générations incomplètes, et validez le résultat avant de l’utiliser. La contrainte ne peut pas rendre un identifiant client exact ni un choix d’outil approprié. Mesurez sa latence sur votre backend au lieu de supposer un surcoût fixe.

Chacune des quatre options modifie une étape différente de la même requête. Seul le fine-tuning demande un entraînement et un nouvel artefact de modèle ; les trois autres modifient du texte, des documents ou un schéma autour des mêmes poids. Le tableau compare les quatre options côte à côte :

OptionCe qu’elle modifieComment la mettre à jourCoût de mise en placeCe qu’elle ne peut pas corriger
PromptingLes instructions et les exemplesModifier le texteFaible : écrire et tester des promptsUn comportement que le modèle ne peut pas suivre par instructions
RAGLes preuves dans la fenêtre de contexteIngérer et réindexer les documentsMoyen : index, retriever et évaluationUn modèle qui ignore ou comprend mal les preuves
Constrained decodingLes tokens que le décodeur peut émettreModifier le schéma ou la grammaireFaible à moyen : schéma et prise en charge du backendDes valeurs fausses dans une structure valide
Fine-tuningLes poids du modèleRéentraîner et évaluer un nouvel artefactÉlevé : curation des données, temps GPU, évaluationDes faits qui changent après l’entraînement

Référence rapide : associer les problèmes aux solutions

ProblèmePremier mécanisme à testerPourquoi ?
Connaissance manquanteRAGRécupère des preuves citées ; la fraîcheur dépend de l’ingestion et de l’indexation
Format ou ton incorrectPrompt engineeringLes modèles actuels suivent bien les consignes de style grâce aux exemples few-shot
Syntaxe de sortie invalideConstrained decodingImpose un schéma ou une grammaire pris en charge pendant la génération
Échec répété sur une tâcheFine-tuning (SFT)Apprend à partir d’exemples d’entrée/sortie sélectionnés
Écart de préférence par pairesOptimisation des préférencesUtilise des exemples choisi/rejeté une fois le comportement de la tâche mesurable
Latence et coût à grande échelleDistillation (SFT)Entraîne un modèle élève plus petit sur les sorties d’un modèle enseignant plus grand
Réduire la taille du modèleQuantification post-entraînementLa quantification post-entraînement réduit la mémoire des poids ; la vitesse dépend des kernels pris en charge et de la charge

Rendre le business case mesurable

Le fine-tuning peut réduire les tokens de prompt récurrents ou permettre à un modèle plus petit d’atteindre l’objectif, mais aucune de ces économies n’est automatique. Calculez le seuil de rentabilité avec votre propre trafic et vos propres tarifs :

break-even requests=training + evaluation + deployment costbaseline cost/request−tuned cost/request\text{break-even requests} = \frac{\text{training + evaluation + deployment cost}} {\text{baseline cost/request} - \text{tuned cost/request}}

Si le dénominateur est petit, négatif ou fondé sur une hypothèse de qualité non démontrée, le projet n’a pas encore de justification économique.

Un exemple chiffré, avec des nombres illustratifs : une base de référence avec prompt envoie environ 2 000 tokens d’instructions et d’exemples à chaque requête. Après le fine-tuning, la même tâche en demande environ 50. À un prix supposé de $0.50 par million de tokens d’entrée pour les deux modèles, l’économie est de 1 950 tokens, soit environ $0.001 par requête. Si l’entraînement, l’évaluation et le déploiement coûtent $3,000, le seuil de rentabilité est d’environ 3,1 millions de requêtes. À 100 000 requêtes par jour, cela fait environ un mois. À 5 000 requêtes par jour, plus d’un an. Le calcul n’est valable que si le modèle ajusté atteint les mêmes seuils de qualité que la base de référence.

La configuration hybride. Une architecture courante associe un modèle plus petit adapté à la tâche et une recherche documentaire pour les faits changeants. Prenez le modèle plus grand, utilisé avec un prompt, comme base de référence, et ne gardez le modèle plus petit que s’il atteint les mêmes seuils de qualité et de sécurité propres à la tâche.


Types de fine-tuning

Le fine-tuning peut prendre trois formes principales. Elles diffèrent par le type de données nécessaires et par ce qu’elles enseignent au modèle. Le schéma montre une ligne d’entraînement pour chaque type.

Le continued pre-training lit des notes cliniques brutes ; le SFT lit des paires entrée et sortie de text-to-SQL ; l'instruction tuning lit des paires instruction et réponse couvrant de nombreuses tâches ; le SFT et l'instruction tuning partagent la perte sur le jeton suivantLe continued pre-training lit des notes cliniques brutes ; le SFT lit des paires entrée et sortie de text-to-SQL ; l'instruction tuning lit des paires instruction et réponse couvrant de nombreuses tâches ; le SFT et l'instruction tuning partagent la perte sur le jeton suivant

1. Continued pre-training (auto-supervisé)

Vous entraînez le modèle de base sur davantage de texte brut, en prenant le token suivant de chaque séquence comme cible d’entraînement. C’est de l’apprentissage auto-supervisé : le texte fournit lui-même ses cibles, comme lors du pré-entraînement initial.

Quand l’utiliser :

  • Le domaine a un vocabulaire que le modèle de base n’a jamais vu (médical, juridique, bases de code internes).
  • Vous disposez de beaucoup de texte du domaine, mais d’aucune paire (entrée, sortie) étiquetée.
  • Le modèle de base se trompe sur la terminologie propre au domaine.

Exemple : entraîner sur des millions de notes cliniques pour que le modèle apprenne les abréviations médicales, les noms de médicaments et les flux de travail cliniques.

2. Supervised fine-tuning (SFT)

Le SFT s’entraîne sur des paires (entrée, sortie) étiquetées. Vous montrez au modèle la sortie exacte attendue pour chaque entrée.

Quand l’utiliser :

  • Vous avez une tâche précise avec un format d’entrée/sortie propre.
  • Vous avez des données étiquetées de qualité, même en petite quantité.
  • Vous avez besoin d’un comportement prévisible sur une forme d’entrée connue.

Exemple : entraîner sur des paires (description d’une requête SQL, code SQL) pour le text-to-SQL. Cet intervalle calendaire fixe suppose que signup_date est stockée en UTC :

{
    "input": "Get users who signed up in August 2026 (UTC)",
    "output": "SELECT * FROM users WHERE signup_date >= '2026-08-01' AND signup_date < '2026-09-01'"
}

3. Instruction tuning

L’instruction tuning est un cas particulier du SFT, conçu pour que les modèles suivent une grande variété d’instructions en langage naturel. Les données d’entraînement sont des paires (instruction, réponse) couvrant de nombreuses tâches différentes.

Quand l’utiliser :

  • Vous voulez un assistant généraliste (comme ChatGPT ou Claude).
  • Le modèle doit traiter des demandes variées et ouvertes.
  • Vous construisez une interface de chat.

Exemple : entraîner sur des milliers d’instructions variées comme « Résume cet article », « Écris un poème sur X », « Explique Y en termes simples ».

Comparaison

AspectContinued pre-trainingSFTInstruction tuning
DonnéesTexte brutPaires (entrée, sortie)Paires (instruction, réponse)
ÉtiquettesAucune (auto-supervisé)Propres à la tâcheTâches variées
ObjectifConnaissances du domaineComportement propre à une tâcheSuivre n’importe quelle instruction
Volume de donnéesEn général le plus grand corpusDéterminé par la couverture de la tâche et la diversité des erreursEn général plus large que le SFT propre à une tâche

Un objectif, deux datasets. Le SFT et l’instruction tuning utilisent le même objectif de prédiction du token suivant ; la différence tient à l’étendue et à la construction du dataset. Le continued pre-training est une expérience distincte : il doit être suivi de tests mesurant à la fois les gains dans le domaine et la régression des capacités générales.


Le pipeline de fine-tuning en 7 étapes

Le fine-tuning comporte sept étapes. Chaque étape a ses propres modes d’échec, et en sauter une se traduit généralement plus tard par un mauvais modèle. Le schéma montre les étapes dans l’ordre, ce que chaque étape ajoute au dossier de version, et le chemin de retour du monitoring vers la préparation des données.

Sept étapes, de la préparation des données au monitoring ; les étapes un à cinq ajoutent au dossier de version la version du dataset, la révision du modèle de base, le tokenizer, le chat template et la configuration d'entraînement, le hash de l'adapter et le rapport d'évaluation ; le monitoring recharge la version précédente sous le seuil de rollback et renvoie ses constats à l'étape 1Sept étapes, de la préparation des données au monitoring ; les étapes un à cinq ajoutent au dossier de version la version du dataset, la révision du modèle de base, le tokenizer, le chat template et la configuration d'entraînement, le hash de l'adapter et le rapport d'évaluation ; le monitoring recharge la version précédente sous le seuil de rollback et renvoie ses constats à l'étape 1

Chaque étape s’appuie sur la précédente :

  1. Préparation des données — Définir l’unité d’évaluation, séparer les données, puis les nettoyer et les formater
  2. Sélection du modèle — Choisir le bon modèle de base et charger les poids
  3. Configuration de l’entraînement — Vérifier la sérialisation et les cibles de la loss, puis choisir la méthode (LoRA, QLoRA, DoRA) et ses paramètres
  4. Fine-tuning — Lancer le SFT avec un framework ; ajouter DPO ou ORPO seulement si nécessaire
  5. Évaluation — Mesurer les performances et valider la qualité
  6. Déploiement — Exporter et servir votre modèle
  7. Monitoring — Suivre les performances, maintenir et itérer

Les données d’abord. L’entraînement reproduit les défauts systématiques des exemples. Inspectez les étiquettes, les fuites, la couverture et le respect des politiques avant de passer du temps sur des balayages d’hyperparamètres de l’optimiseur.


Étape 1 : préparation des données

Les problèmes présents dans les données se retrouvent dans le modèle entraîné : cette étape demande donc le plus de soin. Le schéma suit des exemples de tickets dans cet ordre : d’abord la séparation par source, puis cinq transformations à l’intérieur de chaque partition.

Les tickets et leurs quasi-doublons vont en bloc vers l'entraînement ou l'évaluation ; le filtrage et le traitement des données sensibles s'appliquent aux deux partitions ; la déduplication supprime les quasi-doublons ; l'augmentation ajoute des reformulations à l'entraînement uniquement, car une reformulation dans l'évaluation ferait fuiter son ticket sourceLes tickets et leurs quasi-doublons vont en bloc vers l'entraînement ou l'évaluation ; le filtrage et le traitement des données sensibles s'appliquent aux deux partitions ; la déduplication supprime les quasi-doublons ; l'augmentation ajoute des reformulations à l'entraînement uniquement, car une reformulation dans l'évaluation ferait fuiter son ticket source

Cinq transformations après la séparation des sources

Affectez les groupes de sources à l’entraînement ou à l’évaluation avant de transformer les lignes. Un ticket de support et chaque reformulation de ce ticket restent dans la même partition. Regroupez les quasi-doublons connus avant d’affecter les groupes, puis revérifiez entre les partitions après le nettoyage. Figez le jeu d’évaluation ; n’augmentez que les données d’entraînement.

Listez les erreurs que vos exemples doivent apprendre au modèle à corriger, puis définissez les champs et les étiquettes requis pour chaque ligne. Choisissez les outils de traitement pour ces étapes. DataTrove exécute le filtrage et la déduplication sous forme de jobs distribués pour les grands corpus ; Distilabel génère et étiquette des lignes synthétiques avec un modèle enseignant.

1. Ingestion et filtrage

  • Action : supprimer l’UTF-8 cassé et le contenu hors sujet. Étiqueter les refus selon la politique : conserver les refus corrects comme cibles d’entraînement et cas d’évaluation ; n’exclure que les refus invalides ou hors sujet.
  • Outils : Trafilatura quand la source est constituée de pages web ; inutile pour des données déjà en texte. Les modèles d’identification de la langue de fastText pour détecter la langue.

2. Politique relative aux données sensibles

  • Action : décider ce que le modèle a le droit d’apprendre, puis masquer, tokeniser ou exclure les champs personnels et confidentiels selon les besoins.
  • Outils : Microsoft Presidio quand vous avez besoin de recognizers configurables, de plusieurs langues ou d’une anonymisation réversible ; scrubadub pour un pipeline plus léger en Python pur qui supprime les données personnelles courantes du texte.
  • Pourquoi : un détecteur n’est qu’un contrôle parmi d’autres ; la provenance, le consentement, la rétention, l’accès et les exigences de suppression s’appliquent toujours.

3. Déduplication (MinHash LSH)

  • Action : supprimer les quasi-doublons pour réduire les répétitions et le risque de fuite ; cela n’empêche pas la mémorisation.
  • Outils : DataTrove quand le corpus demande un traitement distribué.

4. Augmentation synthétique, si nécessaire

  • Action : utiliser un modèle enseignant adapté pour reformuler des exemples d’entraînement en paires instruction-réponse, en conservant leurs identifiants de source. N’augmentez pas la partition mise de côté.
  • Outils : Distilabel.
  • Validation : échantillonner les sorties de l’enseignant, les vérifier selon la même grille que les étiquettes humaines et présenter séparément les résultats pour les exemples synthétiques et ceux écrits par des humains. Un tel sous-ensemble d’évaluation s’appelle une slice ; la langue et le type de tâche sont d’autres slices utiles.

5. Formatage

  • Action : convertir vers un format standard (Alpaca ou ShareGPT).

Exemples de formats de données

Format Alpaca (suivi d’instructions) :

{
    "instruction": "Summarize the following text.",
    "input": "The text to be summarized...",
    "output": "This is the summary."
}

Stockage de type ShareGPT (conversationnel) :

{
    "conversations": [
        { "from": "user", "value": "Hello, who are you?" },
        { "from": "assistant", "value": "I am a helpful AI assistant." }
    ]
}

Contrôles du dataset

  • La couverture avant le volume. Ajoutez des exemples qui représentent des modes d’échec distincts, pas des répétitions du cas majoritaire facile.
  • La propreté. Supprimez le texte hors sujet, normalisez les espaces, gardez un formatage cohérent.
  • L’équilibre. Préservez les cas rares importants et présentez les performances par slice.
  • La séparation. Séparez par source, utilisateur, document ou période quand un découpage aléatoire des lignes ferait fuiter des quasi-doublons.
  • La provenance. Consignez la source, la licence ou l’autorisation, l’historique des transformations et la procédure de suppression pour chaque version du dataset.

Étape 2 : sélection du modèle et du matériel

Le modèle de base et sa mémoire GPU minimale déterminent ce que vous pouvez entraîner.

Commencez par le plus petit modèle de base qui passe déjà les vérifications de référence non négociables. Confirmez :

  • la licence et les conditions de redistribution pour le produit visé ;
  • le comportement en langue, en domaine, en tool use et en sécurité avant l’adaptation ;
  • la compatibilité du tokenizer et du chat template avec le dataset ;
  • le contexte maximal et le comportement de troncature nécessaires pour les exemples réels ;
  • la prise en charge à la fois par le framework d’entraînement et par le moteur de serving cible.

Le fine-tuning est une étape d’adaptation, pas un correctif pour un modèle de base inadapté. Si le modèle échoue sur des capacités que le dataset ne couvre pas, choisissez un autre modèle de base avant de lancer d’autres epochs.

Estimer la mémoire de votre exécution

Il n’existe pas de table durable « taille du modèle → GPU ». La mémoire de pointe varie avec la précision des poids, l’optimiseur, le nombre de paramètres entraînables, la longueur de séquence, la taille du micro-batch, l’activation checkpointing, l’implémentation de l’attention et le surcoût du framework. Commencez par une estimation de la mémoire, puis lancez un court test à la longueur maximale dans votre environnement exact.

Composant mémoireFine-tuning completLoRAQLoRA
Poids de basePrécision d’entraînementGelés, en général en BF16/FP16Gelés, en général en NF4 sur 4 bits
GradientsTous les poids entraînablesPoids de l’adapterPoids de l’adapter
États de l’optimiseurTous les poids entraînablesPoids de l’adapterPoids de l’adapter
ActivationsDépendent de la taille du batch et de la longueur de séquence, quelle que soit la méthodeMême dépendanceMême dépendance

Le graphique applique ce tableau au modèle de base de la démo, Nemotron-Nano-4B avec 4,51 milliards de paramètres. Il répartit la mémoire approximative de chaque méthode entre les poids et l’état entraînable ; les activations s’y ajoutent.

Le fine-tuning complet demande environ 54 Go : 9 Go de poids, 9 Go de gradients et 36 Go d'états Adam ; LoRA demande environ 9,5 Go et QLoRA environ 4 Go, car leur état entraînable pèse environ 0,5 GoLe fine-tuning complet demande environ 54 Go : 9 Go de poids, 9 Go de gradients et 36 Go d'états Adam ; LoRA demande environ 9,5 Go et QLoRA environ 4 Go, car leur état entraînable pèse environ 0,5 Go

Le papier QLoRA d’origine a fait tenir un modèle LLaMA de 65B sur un seul GPU de 48 Go dans sa configuration précise. Ce résultat est une borne utile, pas une promesse que toute architecture 70B actuelle, toute longueur de contexte, tout kernel ou tout trainer tiendra sur le même appareil.

Calcul de la mémoire

Pour un modèle de PP paramètres, les poids seuls demandent environ 2P2P octets en BF16/FP16 ou 0.5P0.5P octets sur quatre bits, avant les métadonnées de quantification et les buffers d’exécution. Un entraînement complet de type Adam ajoute les gradients, les états de l’optimiseur et souvent des poids maîtres en précision supérieure. LoRA évite l’essentiel de la mémoire d’état entraînable ; QLoRA réduit en plus l’empreinte des poids de base gelés. Les activations peuvent encore dominer aux grandes longueurs de séquence.

Suivez ce déroulé :

  1. Choisissez la séquence la plus longue et le micro-batch que vous devez prendre en charge.
  2. Estimez les poids et l’état entraînable, en laissant de la marge pour les activations et les kernels.
  3. Exécutez une passe forward/backward à la longueur maximale.
  4. Relevez la mémoire de pointe allouée et réservée.
  5. Seulement ensuite, faites varier la taille de batch, le rang, la longueur de séquence ou le nombre de GPU.

Étape 3 : configuration de l’entraînement et méthodes

L’étape 3 comporte deux parties. Vérifiez d’abord que chaque ligne stockée devient bien le texte d’entraînement attendu. Choisissez ensuite la méthode d’entraînement et ses paramètres.

Des lignes stockées aux cibles d’entraînement

ShareGPT est une forme de dataset ; ChatML est une convention de sérialisation parmi d’autres. Convertissez from/value vers les rôles et les champs que le trainer accepte, puis utilisez le chat template du modèle choisi. Ne remplacez pas les tokens par ceux de ChatML simplement parce que les données sont conversationnelles.

Une ligne passe par cinq étapes :

  1. Ligne ShareGPT : paires from/value.
  2. messages et tools : les rôles que le trainer accepte.
  3. apply_chat_template avec le template du modèle choisi. L’inférence doit utiliser le même template.
  4. IDs de tokens et labels, où -100 signifie aucune loss.
  5. Entraînement.

Affichez le texte rendu à l’étape 3 avant l’entraînement.

Pour la ligne de salutation ci-dessus, les messages normalisés sont :

messages = [
    {"role": "user", "content": "Hello, who are you?"},
    {"role": "assistant", "content": "I am a helpful AI assistant."},
]
# Inspection step after loading the selected model's tokenizer:
rendered = tokenizer.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=False,
)
print(rendered)

Inspectez la ligne réellement sérialisée avant l’entraînement : les délimiteurs de rôle, les tokens de fin de tour et les tool schemas doivent correspondre à ceux de l’inférence. Pour une ligne de tool use, conservez les définitions d’outils, les tool calls de l’assistant et les tool results comme des champs distincts. Vérifiez que la troncature ne supprime pas la réponse supervisée et ne coupe pas un argument d’outil en deux.

Le masquage de la loss choisit les tokens que le modèle est récompensé de prédire. Dans un entraînement sur la séquence complète, les textes de l’utilisateur, du système, des outils et de l’assistant peuvent tous contribuer. Dans un entraînement limité à l’assistant, ces tokens de contexte restent visibles pour l’attention, mais leurs labels sont ignorés (couramment -100) ; seuls les segments de l’assistant contribuent. Pour cette salutation, la réponse doit être une cible et la question de l’utilisateur non, si le comportement voulu est celui de l’assistant seul.

Le schéma montre les deux réglages pour une même conversation de tool use.

En entraînement sur la séquence complète, chaque segment est une cible ; en entraînement limité à l'assistant, seuls le tool call et la réponse de l'assistant sont des ciblesEn entraînement sur la séquence complète, chaque segment est une cible ; en entraînement limité à l'assistant, seuls le tool call et la réponse de l'assistant sont des cibles

La documentation SFT de TRL prend en charge assistant_only_loss=True pour les datasets conversationnels dont les templates exposent des segments de génération. Un template sans ces segments ne peut pas fournir ce masque. Les datasets prompt/completion ont une option distincte, completion_only_loss. Avant d’activer l’une ou l’autre, inspectez un batch tokenisé et décodez les tokens dont les labels ne sont pas ignorés. Répétez ensuite avec une conversation d’outils et un exemple de longueur maximale. La démo plus loin dans ce guide utilise la loss sur la séquence complète, car le template de Nemotron n’a pas de segments de génération.

Choisissez une longueur de séquence à partir de la distribution des données et lancez le contrôle de mémoire à longueur maximale de l’étape 2. Le packing place plusieurs exemples plus courts dans une même séquence d’entraînement. Commencez sans lui, pour que les frontières entre lignes restent faciles à inspecter. Ne mesurez le packing qu’une fois les lignes sérialisées et les labels corrects.

Fine-tuning complet vs PEFT

Le fine-tuning complet (FFT) met à jour tous les poids : les gradients et l’état de l’optimiseur croissent donc avec le modèle entier. On ne peut pas déduire le pic du seul nombre de paramètres, mais il est très supérieur à la mémoire nécessaire pour charger les poids en inférence.

Le parameter-efficient fine-tuning (PEFT) n’entraîne qu’un petit sous-ensemble de paramètres et gèle le reste : les gradients et les états de l’optimiseur ne concernent donc que ce sous-ensemble.

LoRA : le point de départ

LoRA (Low-Rank Adaptation) gèle une matrice pré-entraînée et représente sa mise à jour apprise avec deux matrices plus petites. Le papier d’origine motive cela par l’hypothèse que les mises à jour d’adaptation utiles ont un rang intrinsèque faible.

Ici, dind_{in} est la largeur d’entrée de la couche et doutd_{out} sa largeur de sortie. Le rang rr est la plus petite dimension commune aux deux matrices entraînables : un rang plus petit utilise moins de paramètres entraînables. Le paramètre α\alpha règle l’échelle de la mise à jour par le rapport α/r\alpha/r.

Le schéma montre la matrice gelée, les deux matrices entraînables et la façon dont leur produit mis à l’échelle est ajouté, avec le nombre de paramètres d’une matrice de largeur 4 096 au rang 16.

Une entrée traverse la matrice gelée W0 de 4 096 par 4 096 et, en parallèle, les matrices entraînables A et B ; la sortie de B·A est mise à l'échelle par alpha sur r puis ajoutée ; au rang 16, A et B contiennent 131 072 paramètres contre 16 777 216 dans W0Une entrée traverse la matrice gelée W0 de 4 096 par 4 096 et, en parallèle, les matrices entraînables A et B ; la sortie de B·A est mise à l'échelle par alpha sur r puis ajoutée ; au rang 16, A et B contiennent 131 072 paramètres contre 16 777 216 dans W0

Pour une matrice gelée W0∈Rdout×dinW_0 \in \mathbb{R}^{d_{out} \times d_{in}}, LoRA apprend :

  • A∈Rr×dinA \in \mathbb{R}^{r \times d_{in}}
  • B∈Rdout×rB \in \mathbb{R}^{d_{out} \times r}

La couche adaptée est :

W′=W0+αrBAW' = W_0 + \frac{\alpha}{r}BA

L’adapter compte r(din+dout)r(d_{in}+d_{out}) paramètres entraînables au lieu de dindoutd_{in}d_{out} pour cette matrice. Pour une matrice carrée de largeur 4 096 avec un rang de 16, cela représente une réduction de 128× des paramètres entraînables pour cette matrice. Les économies sur le modèle entier dépendent des matrices qui reçoivent des adapters.

Comparaison des méthodes PEFT

MéthodeCe qui changeÀ choisir quand
LoRABase gelée plus mises à jour entraînables de rang faibleLe modèle de base tient confortablement et vous voulez de petits artefacts par tâche
QLoRALoRA avec la base gelée stockée sous forme de 4 bitsLa mémoire des poids de base est le facteur limitant
DoRASépare la magnitude du poids d’une direction mise à jour par LoRAUne base de référence LoRA mesurée laisse un écart de qualité qui justifie une complexité supplémentaire
Fine-tuning completTous les poids du modèlePEFT manque la cible et le gain de qualité justifie l’entraînement distribué et des checkpoints complets

DoRA : LoRA avec décomposition des poids

DoRA (Weight-Decomposed Low-Rank Adaptation) sépare la magnitude de chaque vecteur de poids de sa direction. Le papier DoRA applique une mise à jour LoRA à la composante directionnelle tout en apprenant la magnitude séparément. Le schéma dessine une colonne de poids sous forme de flèche : B·A fait tourner sa direction, et une magnitude entraînée m fixe sa longueur.

Une colonne de poids dessinée comme une flèche dont la longueur est la magnitude et l'angle la direction ; ajouter BA fait tourner la direction ; normaliser puis multiplier par la valeur entraînée m fixe la nouvelle longueurUne colonne de poids dessinée comme une flèche dont la longueur est la magnitude et l'angle la direction ; ajouter BA fait tourner la direction ; normaliser puis multiplier par la valeur entraînée m fixe la nouvelle longueur

Comment cela fonctionne :

Au lieu de traiter les poids comme une seule entité, DoRA décompose les poids pré-entraînés en deux composantes :

  1. Magnitude — une valeur entraînable par vecteur de poids.
  2. Direction — un vecteur normalisé, mis à jour par des matrices de rang faible.

Dans une notation compacte par colonnes :

W′=mV+BA∥V+BA∥cW' = m \frac{V + BA}{\lVert V + BA \rVert_c}

où :

  • m = magnitude (entraînable)
  • V=W0V = W_0 = la matrice initiale gelée ; on normalise après avoir ajouté BABA
  • BABA = la mise à jour directionnelle de rang faible apprise
  • ∥⋅∥c\lVert \cdot \rVert_c = normalisation par colonne

Effets de la décomposition :

  • Plus de degrés de liberté que LoRA standard, car la magnitude peut changer indépendamment.
  • De meilleurs résultats que LoRA dans plusieurs configurations rapportées par le papier qui la propose.
  • Des paramètres et des calculs supplémentaires : le gain doit donc être vérifié sur votre tâche et votre chemin de serving.

Fusion d’adapters pour l’apprentissage multitâche

Des adapters séparés permettent à une même base gelée de prendre en charge plusieurs tâches. Vous pouvez router les requêtes vers un adapter, servir plusieurs adapters depuis un même moteur quand c’est pris en charge, ou créer hors ligne un candidat fusionné. La fusion peut introduire des interférences : évaluez donc l’artefact fusionné au lieu de supposer que les adapters sources se composent sans problème.

La figure compare les deux options pour deux adapters sur une même base. Gardez le modèle fusionné seulement s’il réussit les deux suites de tests.

Servir garde une seule base en mémoire et la requête nomme l'adapter ; fusionner donne un seul modèle, gardé seulement si les deux suites de tests réussissentServir garde une seule base en mémoire et la requête nomme l'adapter ; fusionner donne un seul modèle, gardé seulement si les deux suites de tests réussissent

Méthodes de fusion courantes :

  1. Concaténation — combiner les paramètres des adapters et augmenter le rang effectif. Rapide et simple.
  2. Combinaison linéaire — somme pondérée des adapters. Les poids fixent la contribution de chaque tâche.
  3. SVD — décomposition matricielle pour la fusion. Plus flexible, mais plus lente.

add_weighted_adapter de PEFT les implémente sous les noms combination_type="cat", "linear" et "svd" ; le guide de fusion de modèles de PEFT les décrit, ainsi que des méthodes plus récentes.

Exemple : un adapter pour le résumé, un autre pour la traduction, fusionnés en un seul modèle multitâche.


Étape 4 : preference alignment (facultatif)

Le SFT est l’entraînement principal, et les sections sur les frameworks qui suivent cette étape l’exécutent. Cette étape présente ce que vous pouvez ajouter après le SFT, si les erreurs l’exigent.

Le SFT apprend des démonstrations. L’optimisation des préférences apprend au contraire à partir de comparaisons du type « la réponse A choisie est meilleure que la réponse B rejetée ». Ne l’utilisez que si la préférence par paires est le bon label pour l’erreur ; l’exactitude factuelle et le respect des politiques demandent souvent des évaluateurs plus solides qu’une préférence globale.

Le schéma montre les étapes de chaque méthode, les modèles qu’elle charge, et ce que fait une étape d’entraînement.

Le RLHF basé sur PPO comporte trois étapes et génère de nouvelles réponses qu'un reward model évalue ; DPO comporte deux étapes et s'entraîne sur des paires enregistrées face à une référence fixe ; ORPO s'entraîne sur des paires enregistrées en une seule étapeLe RLHF basé sur PPO comporte trois étapes et génère de nouvelles réponses qu'un reward model évalue ; DPO comporte deux étapes et s'entraîne sur des paires enregistrées face à une référence fixe ; ORPO s'entraîne sur des paires enregistrées en une seule étape

RLHF basé sur PPO

RLHF signifie reinforcement learning from human feedback. La policy est le modèle de langage qui génère les réponses ; un reward model note ces réponses à partir de préférences apprises sur des comparaisons humaines. Une recette basée sur PPO comporte trois étapes :

  1. SFT — apprendre la tâche.
  2. Reward model — entraîné sur des préférences humaines (choisi vs rejeté).
  3. PPO (Proximal Policy Optimization) — générer des réponses et mettre à jour le modèle de langage pour augmenter leurs scores de récompense.

Le coût opérationnel vient des différents composants :

  • Compliqué à implémenter et à maintenir.
  • Coûteux — vous entraînez plusieurs modèles.
  • L’échantillonnage à partir du modèle en cours d’entraînement exige de surveiller les mises à jour instables et les réponses qui exploitent le reward model sans satisfaire la tâche.

DPO

DPO (Direct Preference Optimization) supprime le reward model séparé et la boucle de reinforcement learning. Il favorise les réponses choisies tout en décourageant les écarts importants par rapport à un modèle de départ fixe, appelé modèle de référence. Le papier DPO dérive cet objectif en limitant l’écart que peuvent prendre les probabilités des réponses par rapport à cette référence ; la limite mathématique est une contrainte de divergence KL.

Une paire de préférence contient le même prompt et deux réponses candidates. Cette ligne abrégée montre ses champs ; de vrais labels demandent des réponses complètes et une raison de préférer l’une d’elles :

{
    "prompt": "Explain quantum computing",
    "chosen": "Quantum computing uses qubits...",   # Preferred response
    "rejected": "Well, it's complicated..."        # Non-preferred response
}

Ce qui change sur le plan opérationnel :

  • Un chemin de code plus simple (pas de reward model séparé, pas de boucle RL).
  • Un entraînement sur des paires de préférences enregistrées, plutôt que la génération de nouvelles réponses à noter à chaque mise à jour.
  • Un modèle de référence fixe, ou ses log probabilities enregistrées pour les réponses d’entraînement, dans la formulation standard.

DPO est plus facile à prototyper qu’un pipeline PPO complet, mais ce n’est pas une amélioration automatique de la qualité. Les résultats dépendent de la policy de départ, de la qualité des paires, des paramètres de la loss, des effets de longueur et du protocole d’évaluation. Comparez-le à un checkpoint SFT sur les mêmes suites de préférences et de tâches mises de côté.

ORPO

ORPO (Odds-Ratio Preference Optimization) combine la loss de log-vraisemblance négative du SFT avec une pénalité de rapport de cotes sur les réponses rejetées. Il supprime le modèle de référence séparé et peut combiner l’apprentissage de la tâche et l’optimisation des préférences en un seul entraînement.

Comment cela fonctionne : ORPO utilise une loss combinée qui fait deux choses à la fois :

  1. Maximiser la vraisemblance de la réponse choisie (apprendre la tâche).
  2. Pénaliser la réponse rejetée avec un terme de rapport de cotes (apprendre les préférences).

TRL fournit désormais ORPO sous le nom ORPOConfig dans trl.experimental.orpo (trainer ORPO de TRL). Deux paramètres méritent d’être connus :

  • learning_rate : le papier utilisait des taux bas comme 8e-6 ; ajustez selon votre modèle, votre batch et vos données plutôt que de copier une valeur comme une règle.
  • beta : règle le poids du terme de préférence par rapport au terme SFT.

Le compromis :

  • Une seule étape d’entraînement au lieu de deux.
  • Pas de reward model.
  • Pas de passe forward du modèle de référence.
  • Un entraînement couplé : si l’apprentissage de la tâche ou le comportement de préférence régresse, il n’existe aucun checkpoint SFT intermédiaire issu du même pipeline à inspecter.

Choisissez selon la conception des données et de l’évaluation :

  • Utilisez DPO quand vous avez déjà un checkpoint SFT satisfaisant et voulez une expérience de préférences hors ligne plus simple.
  • Testez ORPO quand un objectif sans référence et en une seule étape convient à vos données et à vos contraintes opérationnelles.
  • Utilisez le RLHF basé sur PPO quand l’échantillonnage en ligne face à une récompense apprise explicite fait partie de l’exigence et que vous pouvez surveiller l’exploitation de la récompense.

Aucun n’est un choix par défaut valable pour toutes les tâches. Gardez une base de référence SFT seul et présentez à la fois les métriques de tâche et les métriques de préférence.


Frameworks de fine-tuning

Les étapes 3 et 4 ont choisi la méthode. Les deux sections suivantes choisissent un framework et lancent le SFT avec lui.

Les frameworks se recoupent et évoluent vite. Choisissez selon le chemin d’exécution que vous devez prendre en charge, figez les versions et gardez une configuration d’entraînement assez portable pour être reproduite hors d’un notebook.

Unsloth — vitesse et efficacité mémoire

Unsloth s’intègre à trl et transformers de Hugging Face et fournit des kernels optimisés, du checkpointing et des chemins de fine-tuning quantifié pour les modèles pris en charge.

  • Des kernels GPU Triton sur mesure pour l’attention, RoPE et l’entropie croisée, qui évitent le surcoût de PyTorch.
  • Une rétropropagation économe en mémoire, qui recalcule les activations pendant la passe backward au lieu de les garder en mémoire.
  • Des opérations fusionnées qui regroupent plusieurs étapes (layer norm + couche linéaire, et similaires) en un seul appel GPU.
  • Une quantification 4 bits intégrée directement dans le chemin QLoRA, avec une déquantification optimisée.

L’ordre des imports compte. Suivez l’ordre des imports de l’exemple Unsloth pour la version que vous figez. Unsloth applique des correctifs à l’import : l’importer avant trl et transformers évite des optimisations manquantes ou des erreurs propres à une version.

# Correct order
from unsloth import FastLanguageModel  # Must be first!
from trl import SFTConfig, SFTTrainer

# Avoid this order with Unsloth's patched path
from trl import SFTTrainer
from unsloth import FastLanguageModel

Idéal pour : l’entraînement sur un seul GPU, le prototypage et les notebooks Colab.

Les chiffres de vitesse et de mémoire publiés varient selon le modèle, la longueur de séquence, le batch, la précision et le matériel. Mesurez les tokens par seconde et la mémoire de pointe sur votre propre exécution au lieu de traiter un ratio affiché comme une propriété du framework.

Axolotl — entraînement piloté par la configuration

Pour les configurations orientées config et distribuées, Axolotl garde toute l’exécution dans un seul fichier YAML. Ces clés sélectionnent une base 4 bits, un adapter QLoRA et un dataset au format Alpaca :

# axolotl_config.yaml (excerpt)
base_model: meta-llama/Meta-Llama-3-8B
load_in_4bit: true
adapter: qlora
lora_r: 32
lora_alpha: 16 # alpha/r = 0.5 here; the demo uses 2. Sweep it with the learning rate.
datasets:
    - path: data/training_data.jsonl
      type: alpaca

Les modules ciblés, la longueur de séquence, le packing, la taille de batch, le learning rate et les epochs vont dans le même fichier ; la référence de configuration liste chaque clé. Lancez l’entraînement :

axolotl train axolotl_config.yaml

Comparaison des frameworks

OutilUtile quandÀ vérifier avant de s’engager
UnslothVous voulez un chemin optimisé pour les modèles pris en charge, avec des exemples concisMatrice des modèles, GPU, quantifications et prise en charge du distribué
AxolotlVous voulez des configurations déclaratives et des recettes distribuées intégréesSchéma exact de la configuration et lanceur pour la version figée
TRLVous voulez un accès direct aux trainers SFT et de préférences de Hugging FaceFormat du dataset, chat template, masquage de la loss et intégration de PEFT
TorchtuneVous exécutez déjà des recettes torchtune ; il n’est plus activement maintenu (depuis 2025)Couverture des recettes de modèles et compatibilité d’export

Démonstration pratique : fine-tuning avec Unsloth

Cette démo apprend à un modèle à choisir une fonction et à fournir ses arguments. Mon unsloth-finetune-demo fait le fine-tuning de Llama-3.1-Nemotron-Nano-4B-v1.1 sur le dataset glaive-function-calling-v2. Le chemin d’entraînement CUDA de ce dépôt nécessite Linux ou WSL2 et un GPU NVIDIA. Unsloth propose aussi un chemin d’entraînement sur GPU Intel distinct, que cette démo n’implémente pas. Figez le commit que vous exécutez ; le fichier de verrouillage fige les dépendances, dont TRL 0.24.0.

Le schéma montre toute l’exécution avec ses valeurs réelles : comment une ligne Glaive devient un texte d’entraînement qui conserve les définitions d’outils, la séparation, les paramètres d’entraînement, les trois répertoires de sortie et la première vérification.

Les chaînes system et chat d'une ligne Glaive deviennent des outils et des messages, sont rendues en texte d'entraînement avec AVAILABLE_TOOLS et TOOLCALL, séparées 95 contre 5, entraînent un adapter LoRA de rang 16 sur une base 4 bits, puis exportent un adapter, un modèle fusionné et un fichier GGUFLes chaînes system et chat d'une ligne Glaive deviennent des outils et des messages, sont rendues en texte d'entraînement avec AVAILABLE_TOOLS et TOOLCALL, séparées 95 contre 5, entraînent un adapter LoRA de rang 16 sur une base 4 bits, puis exportent un adapter, un modèle fusionné et un fichier GGUF

Lancer la démo

git clone https://github.com/slavadubrov/unsloth-finetune-demo.git
cd unsloth-finetune-demo
uv sync

# Train on 1,000 random rows, then also save a merged model and a GGUF file
uv run finetune --max-samples 1000 --merge --gguf q4_k_m

Chaque exécution de finetune entraîne. --merge et --gguf ajoutent des exports après l’entraînement ; il n’existe pas de commande d’export seul. L’exécution écrit trois répertoires :

  • outputs/unsloth-nemotron-function-calling/ — l’adapter LoRA
  • outputs/unsloth-nemotron-function-calling-merged/ — le modèle fusionné en 16 bits (--merge)
  • outputs/unsloth-nemotron-function-calling-gguf/ — le fichier GGUF (--gguf q4_k_m)

Avant le début de l’entraînement, l’exécution affiche la première ligne d’entraînement telle que le modèle la voit. Lisez-la : les définitions d’outils apparaissent dans <AVAILABLE_TOOLS>, et chaque appel apparaît dans <TOOLCALL>.

Préparation des données

Chaque ligne glaive contient deux chaînes. La colonne system contient toutes les définitions de fonctions ; la colonne chat contient la conversation, avec les appels écrits sous la forme <functioncall> {...}. Entraîner sur chat seul apprend au modèle à appeler des outils dont il ne voit jamais les schémas. Le data.py de la démo convertit chaque ligne en messages et en une liste tools séparée, rend les deux avec le chat template propre au modèle, et met de côté 5 % des lignes comme jeu d’évaluation :

def to_text(example: dict, tokenizer) -> dict:
    """Render one glaive row with the model's chat template. Empty text marks a dropped row."""
    try:
        messages, tools = to_messages(example)
    except (ValueError, KeyError):  # bad JSON in a tool definition or call
        return {"text": ""}
    if not messages:
        return {"text": ""}
    text = tokenizer.apply_chat_template(messages, tools=tools or None, tokenize=False)
    # The trainer tokenizes this text with special tokens on, which adds BOS again.
    return {"text": text.removeprefix(tokenizer.bos_token or "")}

Sur le dataset complet, cela conserve 112 811 lignes sur 112 960 ; 149 lignes ont un JSON mal formé et sont écartées. Avec ce tokenizer, la ligne rendue la plus longue fait 4 076 tokens : une limite de 4 096 tokens n’en tronque donc aucune.

Le chat template de la model card a un défaut pour l’entraînement. Il rend un tool call passé sans l’accolade fermante après arguments : le texte dans <TOOLCALL> n’est donc pas du JSON valide. Un modèle entraîné sur ce texte apprend à l’émettre, et le parseur de serving ne peut pas lire l’appel. Le dépôt fournit le template avec l’accolade ajoutée et l’utilise pour l’entraînement et le serving. Son test sur CPU seul rend des conversations d’outils et vérifie que chaque <TOOLCALL> se parse.

Configuration de la démo

Le config.py de la démo fixe les valeurs de LoRA :

LORA_R = 16        # Adapter capacity; tune against held-out results
LORA_ALPHA = 32    # Update scale alpha/r = 2
LORA_DROPOUT = 0.05
MAX_SEQ_LENGTH = 4096

LORA_TARGET_MODULES liste les sept projections d’attention et de MLP de ce modèle de la famille Llama, de q_proj à down_proj.

Le rapport alpha sur rang. alpha/r règle l’échelle de la mise à jour LoRA classique. alpha = 2r est une heuristique de départ courante dans la documentation de certains outils, pas une garantie de stabilité. Ne balayez le rang, alpha, le learning rate et les modules ciblés qu’une fois les données et la base de référence figées.

Chargement du modèle

Le model.py de la démo charge la base en 4 bits, remplace le chat template par la version corrigée et ajoute les adapters LoRA :

from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name=MODEL_NAME,
    max_seq_length=MAX_SEQ_LENGTH,
    load_in_4bit=True,  # QLoRA: the frozen base is stored in 4-bit form
    trust_remote_code=True,
)
# Training rows, the saved tokenizer, and vLLM all use this one template.
tokenizer.chat_template = CHAT_TEMPLATE_PATH.read_text()

model = FastLanguageModel.get_peft_model(
    model,
    r=LORA_R,
    lora_alpha=LORA_ALPHA,
    lora_dropout=LORA_DROPOUT,
    target_modules=LORA_TARGET_MODULES,
    use_gradient_checkpointing="unsloth",  # Lower activation memory; extra compute
)

Configuration de l’entraînement

Le training.py de la démo transmet les lignes rendues au SFTTrainer de TRL. Il évalue la loss de tokens sur le jeu mis de côté une fois par epoch. Cette loss est un diagnostic, pas la précision des tool calls ; l’étape 5 exige toujours une évaluation distincte de la tâche.

trainer = SFTTrainer(
    model=model,  # 4-bit base with LoRA adapters from FastLanguageModel
    processing_class=tokenizer,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    args=SFTConfig(
        output_dir="outputs/unsloth-nemotron-function-calling",
        dataset_text_field="text",
        max_length=4096,
        packing=False,  # One row per sequence, so row boundaries are easy to inspect
        assistant_only_loss=False,  # The Nemotron template has no generation spans
        eval_strategy="epoch",
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        learning_rate=2e-4,
        num_train_epochs=3,
        bf16=True,  # The repository falls back to FP16 on GPUs without BF16
        optim="adamw_8bit",
    ),
)
trainer.train()

Premier contrôle

uv run infer --prompt "Book a flight to Tokyo"

infer charge l’adapter et envoie le prompt avec un outil, book_flight(destination), rendu avec le même template que les lignes d’entraînement. Un adapter entraîné doit répondre par <TOOLCALL>[{"name": "book_flight", "arguments": {"destination": "Tokyo"}}]</TOOLCALL>. S’il répond en texte brut, inspectez la ligne d’entraînement affichée avant d’entraîner plus longtemps.


Étape 5 : évaluation

Fixez les cas de test, les règles de notation et les seuils de réussite avant la première exécution. Au minimum, comparez le checkpoint ajusté au modèle de base exact non ajusté, avec le même prompt, les mêmes paramètres de décodage et le même environnement d’outils. Ne présentez la qualité agrégée qu’après avoir vérifié les slices d’échec que le projet devait améliorer.

Suivez quatre groupes :

  1. Tâche cible : correspondance exacte, réussite de l’exécution, grille humaine ou autre résultat lié au cas d’usage.
  2. Régression : capacités générales et slices de tâches déjà prises en charge que l’adaptation pourrait dégrader.
  3. Sécurité et politiques : refus, fuites de données, prompt injection ou contraintes propres au domaine.
  4. Exploitation : latence, débit, mémoire, taille de l’artefact et coût dans la configuration de serving visée.

Benchmarks automatisés

Utilisez lm-evaluation-harness pour vérifier des tâches standardisées pertinentes en plus de vos tests produit. Cette commande exige un checkpoint Hugging Face fusionné, que la démo écrit avec --merge :

lm_eval --model hf \
    --model_args pretrained=./outputs/unsloth-nemotron-function-calling-merged \
    --tasks hellaswag,arc_easy,mmlu \
    --batch_size 8

LLM-as-judge

Pour une qualité subjective, un modèle plus grand peut aider à noter, mais calibrez-le sur des exemples relus par des humains et gardez l’identité du candidat cachée. Donnez au juge la sortie du modèle, la réponse attendue et une grille qui définit chaque note pour chaque critère, comme la pertinence, l’exactitude et le formatage.

Évaluation propre au domaine

Mettez de côté des exemples réels par source, utilisateur, document ou période, pour que des quasi-doublons ne puissent pas fuiter d’un côté à l’autre de la séparation. Pour le function calling, validez la trajectoire complète : choix de l’outil, arguments, résultat d’exécution, reprise après erreur et réponse finale. Présentez des intervalles de confiance ou des décomptes appariés de victoires et de défaites quand l’échantillon est petit, et inspectez chaque régression dans une slice critique.

Contrôlez chaque trajectoire en quatre étapes :

  1. Le bon outil est choisi.
  2. Les arguments sont valides et corrects.
  3. Le résultat d’exécution est celui attendu.
  4. Le modèle reprend correctement après une erreur d’outil.

Comptez chaque échec par contrôle et par slice. Une réponse finale correcte peut cacher un mauvais argument ou une reprise ignorée.

Le code d’évaluation du Berkeley Function Calling Leaderboard (BFCL) note le choix de la fonction et les arguments par rapport à des appels de référence. Utilisez-le comme contrôle de régression à côté de vos propres conversations mises de côté.


Étape 6 : déploiement et formats de sortie

Choisissez l’artefact selon le moteur de serving et le plan de rollback, pas seulement selon la taille du fichier. Exportez chaque format à partir des mêmes objets model et tokenizer évalués, afin que tous les artefacts portent les mêmes poids entraînés. Le schéma compare les trois artefacts : ce que contient chaque fichier, ce qui le charge, et son compromis.

L'adapter est un petit fichier que vLLM charge à côté du modèle de base ; le modèle fusionné a environ la taille de la base en 16 bits ; le fichier GGUF contient des poids d'environ 4 bits pour Ollama et llama.cppL'adapter est un petit fichier que vLLM charge à côté du modèle de base ; le modèle fusionné a environ la taille de la base en 16 bits ; le fichier GGUF contient des poids d'environ 4 bits pour Ollama et llama.cpp

Dans la démo, uv run finetune enregistre toujours l’adapter, --merge ajoute le modèle fusionné et --gguf q4_k_m ajoute le fichier GGUF, le tout en une seule exécution d’entraînement. Les appels Python ci-dessous montrent ce que fait chaque option.

1. Adapter LoRA

model.save_pretrained("outputs/unsloth-nemotron-function-calling")
tokenizer.save_pretrained("outputs/unsloth-nemotron-function-calling")
  • Taille : proportionnelle aux modules ciblés, au rang, aux couches et au dtype ; souvent bien plus petite que la base.
  • Idéal pour : le développement, les adapters de tâche versionnés et les moteurs qui prennent LoRA en charge directement.
  • Bonus : vous pouvez changer d’adapter sans retélécharger le modèle de base.

2. Modèle fusionné

# What --merge runs: merge the adapter into the base weights and save a standalone model
model.save_pretrained_merged(
    "outputs/unsloth-nemotron-function-calling-merged", tokenizer, save_method="merged_16bit"
)
  • Taille : à peu près celle du checkpoint de base complet, à la précision de sortie choisie.
  • Idéal pour : les moteurs ou les circuits de distribution qui ne prennent pas l’adapter en charge séparément.
  • Compromis : artefact plus gros et déploiement plus lent ; chargement d’un modèle unique plus simple.

3. Format GGUF

# What --gguf q4_k_m runs; file size and name depend on the Unsloth version
model.save_pretrained_gguf(
    "outputs/unsloth-nemotron-function-calling-gguf", tokenizer, quantization_method="q4_k_m"
)
  • Taille : dépend du modèle ; environ des poids sur quatre bits plus des métadonnées pour les variantes Q4.
  • Idéal pour : l’inférence sur CPU, Ollama, llama.cpp, le déploiement en edge.
  • Options : q4_k_m (plus petit), q5_k_m (plus de fidélité des poids), q8_0 (plus gros et plus fidèle). Mesurez l’effet sur la tâche après la conversion.

Étape 7 : serving et monitoring

Avec vLLM

Cette commande sert l’adapter de la démo au-dessus du modèle de base. Servez avec le parseur et le chat template de la model card ; ils doivent correspondre au template utilisé pendant l’entraînement. La model card enregistre le parseur llama_nemotron_json via le fichier de plugin llama_nemotron_nano_toolcall_parser.py. Le dépôt de la démo fournit le template corrigé.

Utilisez un environnement Python 3.12 séparé avec vLLM 0.12.0 : le parseur figé importe l’API du parseur d’outils de cette version, qui a changé dans les versions suivantes. La commande fige le modèle de base et le parseur sur la même révision de la model card ; utilisez aussi cette révision de la base pour l’entraînement. La compatibilité du code source et un test du parseur sur CPU ne valident pas le serving sur GPU. Vérifiez le chargement du modèle et le tool call attendu sur votre GPU. Lancez la commande depuis la racine du dépôt :

uv venv --python 3.12 .venv-vllm
uv pip install --python .venv-vllm/bin/python "vllm==0.12.0" "huggingface-hub==0.36.0"
source .venv-vllm/bin/activate

hf download nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1 \
    llama_nemotron_nano_toolcall_parser.py \
    --revision d552708a9d575fa8d4a690b988fd870d65279f98 --local-dir serving

vllm serve nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1 \
    --revision d552708a9d575fa8d4a690b988fd870d65279f98 \
    --trust-remote-code \
    --enable-lora \
    --lora-modules function-calling=./outputs/unsloth-nemotron-function-calling \
    --enable-auto-tool-choice \
    --tool-parser-plugin ./serving/llama_nemotron_nano_toolcall_parser.py \
    --tool-call-parser llama_nemotron_json \
    --chat-template ./src/unsloth_demo/chat_template.jinja \
    --host 127.0.0.1 \
    --port 8000 \
    --max-model-len 4096

La commande uv run --with openai infer-vllm de la démo teste la génération d’arguments via l’API compatible OpenAI. Elle envoie un outil minimal, book_flight, avec une chaîne destination obligatoire, et demande une proposition d’appel ; elle n’exécute aucune réservation. La requête dans cli/inference.py est :

client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
response = client.chat.completions.create(
    model="function-calling",  # the --lora-modules name, not the base model
    messages=[{"role": "user", "content": "Book a flight to Tokyo"}],
    tools=[DEMO_TOOL],  # book_flight(destination), defined in config.py
    tool_choice="auto",
)
print(response.choices[0].message.tool_calls)

Pour cette entrée, le résultat attendu est book_flight avec {"destination": "Tokyo"}. Comme le choix de l’outil est automatique, vérifiez aussi que le modèle a bien renvoyé un appel. L’outil a la même forme que les définitions d’outils glaive sur lesquelles le modèle s’est entraîné. Le parseur de vLLM lit l’appel dans le texte généré : un appel mal formé revient donc comme simple contenu, sans tool_calls. vLLM peut aussi contraindre les arguments d’une fonction à son schéma quand la fonction est marquée strict: true ; le guide du tool calling décrit les paramètres du serveur et les parseurs qui le prennent en charge.

Avec Ollama (en local)

Listez le répertoire outputs/unsloth-nemotron-function-calling-gguf et remplacez MODEL_FILE.gguf ci-dessous par le nom de fichier produit ; les versions d’Unsloth peuvent nommer les fichiers autrement. Un fichier GGUF seul ne garantit pas un formatage correct du chat et des outils dans Ollama ou llama.cpp. Faites correspondre le template d’entraînement et testez l’artefact importé.

# Create Modelfile
echo 'FROM ./outputs/unsloth-nemotron-function-calling-gguf/MODEL_FILE.gguf' > Modelfile

# Import to Ollama
ollama create my-function-model -f Modelfile

# Run
ollama run my-function-model

Avec llama.cpp (CPU)

Avec le même export GGUF et la même substitution du nom de fichier, cette commande vérifie une réponse en texte brut. Elle ne teste pas un tool call complet :

llama-cli -m ./outputs/unsloth-nemotron-function-calling-gguf/MODEL_FILE.gguf \
    -p "What's the weather in Tokyo?" \
    --ctx-size 4096

Surveiller le modèle publié

Le cycle de vie ne s’arrête pas à une loss d’entraînement saine. Enregistrez la révision du modèle de base, le tokenizer et le chat template, le hash de l’adapter, la version du dataset, la configuration d’entraînement et le rapport d’évaluation comme une seule unité de release. En production, surveillez la réussite des tâches, les sorties invalides, les échecs de politique, la latence et la dérive des entrées par les mêmes slices que hors ligne. Gardez l’artefact précédent chargeable et définissez un seuil de rollback avant le lancement.

En résumé :

  1. Unité de release : révision du modèle de base, tokenizer et chat template, hash de l’adapter, version du dataset, configuration d’entraînement et rapport d’évaluation.
  2. Surveillez la réussite des tâches, les sorties invalides, les échecs de politique, la latence et la dérive des entrées par les mêmes slices que hors ligne.
  3. Si une métrique tombe sous le seuil de rollback défini avant le lancement, chargez l’artefact précédent.

Votre prochain test

Avant un entraînement complet, inspectez une conversation sérialisée et ses cibles de loss, puis effectuez une passe forward/backward à la longueur maximale. Enregistrez cette configuration avec la séparation du dataset et le rapport de la base de référence. Si un tool schema manque, conservez-le dans la ligne d’entraînement. Si une réponse est tronquée, ajustez la longueur de séquence et le formatage pour que la réponse complète survive. Résolvez ces problèmes avant d’ajouter des epochs.

Références

Papiers et recherche

Outils de traitement des données

Constrained decoding

  • xgrammar — Constrained decoding avec des automates à états finis
  • outlines — Génération structurée pour les LLMs

Frameworks d’entraînement

  • Unsloth — Framework de fine-tuning optimisé
  • Axolotl — Entraînement piloté par la configuration et lanceurs distribués
  • TRL — Bibliothèque Hugging Face pour le SFT et l’entraînement sur préférences
  • PEFT model merging — add_weighted_adapter et méthodes de fusion
  • Torchtune — Bibliothèque de fine-tuning native PyTorch, qui n’est plus activement maintenue

Inférence et déploiement

Évaluation

Guides et ressources