PEFT documentation
Dépannage
Dépannage
Si vous rencontrez un problème lors de l'utilisation de PEFT, veuillez consulter la liste suivante des problèmes courants et de leurs solutions.
Les exemples ne fonctionnent pas
Les exemples s’appuient souvent sur les versions les plus récentes des paquets. Veuillez donc vous assurer qu’ils sont à jour. En particulier, vérifiez les versions des paquets suivants :
pefttransformersacceleratetorch
En général, vous pouvez mettre à jour la version du package en exécutant cette commande dans votre environnement Python :
python -m pip install -U <package_name>
L’installation de PEFT à partir du code source est utile pour suivre les dernières évolutions :
python -m pip install git+https://github.com/huggingface/peft
Problèmes liés au Dtype
ValueError: Tentative de désamplification des gradients FP16
Cette erreur est probablement survenue car le modèle a été chargé avec dtype=torch.float16 puis utilisé dans un contexte de précision mixte automatique (AMP), par exemple en définissant fp16=True dans la classe Trainer de 🤗 Transformers. La raison est que, lors de l'utilisation de l'AMP, les poids entraînables ne doivent jamais utiliser fp16. Pour que cela fonctionne sans charger le modèle entier en fp32, ajoutez le code suivant :
peft_model = get_peft_model(...)
# add this:
for param in model.parameters():
if param.requires_grad:
param.data = param.data.float()
# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()Sinon, vous pouvez utiliser la fonction cast_mixed_precision_params() pour convertir correctement les poids :
from peft import cast_mixed_precision_params
peft_model = get_peft_model(...)
cast_mixed_precision_params(peft_model, dtype=torch.float16)
# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()À partir de la version v0.12.0 de PEFT, PEFT promeut automatiquement le dtype des poids de l'adaptateur de
torch.float16ettorch.bfloat16verstorch.float32lorsque c'est approprié. Pour empêcher ce comportement, vous pouvez passerautocast_adapter_dtype=Falseà ~get_peft_model(), à from_pretrained() et à load_adapter().
Sélectionner le dtype de l'adaptateur
La plupart des méthodes PEFT, comme LoRA, fonctionnent en ajoutant des poids d’adaptateurs entraînables. Par défaut, ces poids sont stockés dans le type de données float32 (fp32), c’est-à-dire avec une précision relativement élevée. Par conséquent, même si le modèle de base est chargé en float16 (fp16) ou bfloat16 (bf16), les poids de l’adaptateur restent en float32. Lorsque les résultats de l’adaptateur sont calculés lors du passage avant, l’entrée est généralement dans le type de données du modèle de base ; elle est donc convertie vers float32 si nécessaire, puis reconvertie dans le type de données d’origine.
Si vous préférez que les poids de l'adaptateur soient dans la précision inférieure du modèle de base, c'est-à-dire en float16 ou bfloat16, vous pouvez passer autocast_adapter_dtype=False lors de la création du modèle (~get_peft_model()) ou du chargement du modèle (from_pretrained()). Cela présente certains avantages et inconvénients :
Avantages de l'adaptateur en demi-précision :
- calcul légèrement plus rapide
- légèrement moins de mémoire
- taille de fichier de point de contrôle réduite (moitié de la taille)
Inconvénients de l'adaptateur en demi-précision :
- perte légèrement plus élevée
- risque accru de dépassement ou de sous-dépassement
Notez que, pour la plupart des cas d’utilisation, le temps d’exécution global et le coût mémoire seront déterminés par la taille du modèle de base et par le jeu de données, tandis que le dtype de l’adaptateur PEFT n’aura qu’un impact mineur.
L'entraînement s'exécute mais le modèle ne s'améliore pas
Si l’entraînement semble sain (la perte diminue, les points de contrôle sont enregistrés sans erreur) mais que le modèle résultant ne performe pas mieux que le modèle de base, vérifiez si les paramètres entraînables ont réellement changé pendant l’entraînement :
# before training
sample_param = next(p for p in model.parameters() if p.requires_grad)
before = sample_param.detach().clone()
# ... train for at least three optimizer steps ...
after = next(p for p in model.parameters() if p.requires_grad)
print("weights updated:", not torch.allclose(before, after.detach().cpu()))Hooks forward ou backward manquants
Si les poids n’ont pas bougé, la cause probable est que les références aux paramètres ont été capturées avant que les paramètres ne soient matérialisés sur leur appareil cible. Cela peut se produire lorsque le modèle de base est enveloppé avec get_peft_model() alors que ses paramètres n’ont pas encore été déplacés vers l’appareil cible, et qu’une bibliothèque tierce enregistre ensuite des hooks forward/backward (par exemple, des hooks de gradient par échantillon issus d’un cadre de confidentialité différentielle) ou qu’un optimiseur est créé avant le premier appel forward. Lorsque les paramètres sont matérialisés plus tard, ces hooks et références d’optimiseur pointent vers des tenseurs obsolètes et chaque mise à jour devient une opération silencieuse sans effet.
Pour éviter cela, utilisez l'ordre suivant :
- Chargez le modèle de base et déplacez-le sur l'appareil cible (par exemple
model.to(device)). - L’entourer avec get_peft_model().
- Enregistrez les hooks ou créez l'optimiseur uniquement à ce moment-là.
Les modules cibles ne correspondent pas au modèle
Si l’entraînement s’exécute sans erreur mais que la perte évolue à peine — ou que le modèle ne montre aucune amélioration après l’ajustement fin —, les target_modules configurés (ou les target_modules par défaut, si l’utilisateur n’en a pas défini d’autres) ne correspondent peut-être pas aux noms réels des modules dans le modèle. C’est fréquent avec les architectures hybrides telles que Mamba, Jamba ou NemotronH, qui utilisent des noms de couches différents de ceux des modèles Transformer standards.
PEFT ne lève une erreur que lorsque aucun des target_modules configurés ne correspond à un module du modèle. Si certains correspondent et que certains ne correspondent pas, les entrées non correspondantes sont ignorées silencieusement — aucun avertissement n'est émis. Une grande partie du modèle peut rester figée sans aucune indication.
Le premier diagnostic consiste à vérifier le nombre de paramètres entraînables :
from peft import LoraConfig, get_peft_model
config = LoraConfig(target_modules=["q_proj", "v_proj", "gate_proj"])
model = get_peft_model(base_model, config)
model.print_healthcheck()Si le nombre signalé est bien inférieur à ce qui est attendu, inspectez les couches d'adaptateur dans le modèle :
print(model.get_layer_status())Cela renvoie une liste d’entrées TunerLayerStatus indiquant le nom, le type de module, l’état d’activation et les adaptateurs actifs de chaque couche d’adaptateur. Alternativement, l’affichage de la représentation du modèle (print(model)) fournit un aperçu de la hiérarchie complète des modules, ce qui est utile pour identifier les noms de couches corrects sur des architectures peu familières.
Toute entrée de target_modules qui ne correspond pas à un module du modèle est silencieusement ignorée. Mettez à jour target_modules pour qu'il corresponde aux noms de modules réels.
Ce problème concerne toutes les méthodes PEFT qui utilisent
target_modules(LoRA, LoHa, IA³, etc.), et pas seulement LoRA. Les étapes de diagnostic sont les mêmes quelle que soit la méthode. Si vous n'êtes pas sûr des noms de modules utilisés par une architecture donnée, consultez la documentation du modèle ou inspectezmodel.named_modules()avant de configurer l'adaptateur.
Résultats médiocres d'un modèle PEFT chargé
Plusieurs raisons peuvent expliquer un résultat médiocre d’un modèle PEFT chargé, et elles sont énumérées ci-dessous. Si vous n’arrivez toujours pas à résoudre le problème, vérifiez si d’autres personnes ont rencontré un problème similaire sur GitHub, et si vous n’en trouvez aucun, ouvrez un nouveau ticket.
Lors de l’ouverture d’un ticket, il est très utile de fournir un exemple de code minimal qui reproduit le problème. Veuillez également indiquer si le modèle chargé présente des performances au même niveau que le modèle avant l’ajustement fin, s’il fonctionne de manière aléatoire, ou s’il est simplement légèrement moins performant que prévu. Ces informations nous aident à identifier le problème plus rapidement.
Écarts aléatoires
Si les sorties de votre modèle ne sont pas exactement identiques à celles des exécutions précédentes, il peut y avoir un problème lié aux éléments aléatoires. Par exemple :
- veuillez vous assurer qu'il est en mode
.eval(), ce qui est important, par exemple, si le modèle utilise le dropout - si vous utilisez generate sur un modèle de langage, un échantillonnage aléatoire peut avoir lieu, de sorte qu’obtenir le même résultat nécessite de définir une graine aléatoire
- si vous avez utilisé la quantification et fusionné les poids, de petites déviations sont attendues en raison des erreurs d'arrondi
Modèle chargé de manière incorrecte
Veuillez vous assurer de charger le modèle correctement. Une erreur courante consiste à essayer de charger un modèle entraîné avec get_peft_model(), ce qui est incorrect. Le code de chargement doit plutôt ressembler à ceci :
from peft import PeftModel, PeftConfig
base_model = ... # to load the base model, use the same code as when you trained it
config = PeftConfig.from_pretrained(peft_model_id)
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)Chemins d'exécution optimisés contournant les adaptateurs
Certain chemins d’exécution optimisés lisent directement les poids des modules au lieu d’appeler leurs méthodes forward. Cela peut contourner les modifications apportées par des adaptateurs non fusionnés sans avertissement, même si l’adaptateur est actif. Le problème peut affecter différents modules et méthodes PEFT.
Par exemple, le torch.nn.TransformerEncoder natif de PyTorch peut utiliser un chemin fusionné lors de l’évaluation avec torch.no_grad() ou torch.inference_mode(). Ce chemin peut ignorer les adaptateurs dans les sous-modules TransformerEncoderLayer, tels que les adaptateurs LoRA sur linear1 et linear2. L’entraînement peut fonctionner correctement tandis que l’évaluation ignore les modifications apportées par les adaptateurs. Consultez issue #3976 pour une reproduction.
Si vous soupçonnez ce problème, vérifiez d’abord que l’adaptateur est activé et actif à l’aide de get_layer_status(). Comparez les sorties sur les mêmes entrées en mode évaluation, avec et sans l’adaptateur, puis avec l’optimisation désactivée. Utilisez un adaptateur entraîné qui modifie les sorties, car les adaptateurs LoRA nouvellement initialisés n’ont généralement aucun effet. De petites différences numériques sont normales.
Pour un adaptateur non fusionné sur un encodeur natif en mode eager, comparez les sorties pour la même entrée tensorielle x :
import torch
model.eval()
previous = torch.backends.mha.get_fastpath_enabled()
try:
with torch.inference_mode():
torch.backends.mha.set_fastpath_enabled(True)
fast_output = model(x)
torch.backends.mha.set_fastpath_enabled(False)
reference_output = model(x)
finally:
torch.backends.mha.set_fastpath_enabled(previous)
print((fast_output - reference_output).abs().max().item())Le simple fait qu’un drapeau de backend soit activé ne signifie pas que le chemin fusionné s’exécute ; son utilisation dépend également du modèle, des entrées et des conditions d’exécution. Inspectez l’implémentation de forward du module englobant pour détecter un accès direct aux poids ou des appels d’opérateurs fusionnés. Faites attention lors du débogage avec des hooks de forward : l’enregistrement d’un hook peut lui-même désactiver une optimisation et modifier le comportement que vous êtes en train d’étudier.
Pour les encodeurs natifs en mode eager, torch.backends.mha.set_fastpath_enabled(False) est une solution de contournement qui permet de conserver les adaptateurs non fusionnés. Ce paramètre s’applique à l’ensemble du processus, peut affecter d’autres modèles et les performances, et ne doit pas être modifié de manière indépendante par des requêtes concurrentes. Pour les autres chemins optimisés, désactivez l’optimisation concernée ou utilisez une implémentation qui appelle les modules adaptés. Si la méthode PEFT et la configuration de quantisation le permettent, vous pouvez également fusionner les adaptateurs avec merge_and_unload() et utiliser le modèle renvoyé pour l’inférence. Les modifications apportées par les adaptateurs sont alors incluses dans les poids de base. La fusion supprime les enveloppes d’adaptateurs, il est donc recommandé de conserver vos points de contrôle originaux si vous devez changer d’adaptateurs ou reprendre l’entraînement.
Couches initialisées aléatoirement
Pour certaines tâches, il est important de configurer correctement modules_to_save dans la configuration afin de prendre en compte les couches initialisées aléatoirement.
À titre d’exemple, c’est nécessaire si vous utilisez LoRA pour l’ajustement fin d’un modèle de langage pour la classification de séquences, car 🤗 Transformers ajoute une tête de classification initialisée aléatoirement au sommet du modèle. Si vous n’ajoutez pas cette couche à modules_to_save, la tête de classification ne sera pas sauvegardée. La prochaine fois que vous chargerez le modèle, vous obtiendrez une tête de classification initialisée aléatoirement différente, ce qui entraînera des résultats complètement différents.
PEFT tente de deviner correctement les modules_to_save si vous fournissez l'argument task_type dans la configuration. Cela devrait fonctionner pour les modèles transformers qui suivent le schéma de nommage standard. Il est toutefois toujours judicieux de vérifier à nouveau, car nous ne pouvons pas garantir que tous les modèles respectent ce schéma de nommage.
Lorsque vous chargez un modèle transformers dont les couches sont initialisées aléatoirement, vous devriez voir un avertissement du type :
Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.Les couches mentionnées doivent être ajoutées à modules_to_save dans la configuration pour éviter le problème décrit.
healthcheck() signale un avertissement TASK_HEAD_NOT_SAVED pour la forme la plus courante de cette erreur : une tête de classification de séquence, de classification de jetons ou de réponse à une question nouvellement initialisée lors du chargement du modèle de base, mais qui n’est pas incluse dans modules_to_save, par exemple parce que task_type n’a pas été défini. La vérification est ignorée si le modèle de base n’énumère pas ses architectures dans la configuration.
Par exemple, lors du chargement d’un modèle utilisant l’architecture DeBERTa pour la classification de séquences, un avertissement indique que les poids suivants sont nouvellement initialisés :
['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']. Il en découle que les couchesclassifieretpoolerdoivent être ajoutées à :modules_to_save=["classifier", "pooler"].
Étendre le vocabulaire
Pour de nombreuses tâches de fine-tuning de modèles de langage, il est nécessaire d’étendre le vocabulaire du modèle, car de nouveaux jetons sont introduits. Cela implique d’étendre la couche d’embedding pour prendre en compte ces nouveaux jetons et, selon la méthode de fine-tuning utilisée, de stocker la couche d’embedding en plus des poids de l’adaptateur lors de la sauvegarde de celui-ci. Voici plusieurs façons d’y parvenir, classées par efficacité paramétrique :
- jetons entraînables, n’entraîner que les jetons spécifiés, stocker éventuellement uniquement les valeurs mises à jour
- entraîner un adaptateur sur la matrice d'incorporation, en stockant éventuellement uniquement les valeurs mises à jour
- full-finetuning de la couche d'embedding
Utilisation de jetons entraînables
Commençons par les jetons entraînables, dans ce cas il s’agit de l’intégration LoRA. Si vous souhaitez uniquement entraîner les nouveaux embeddings et rien d’autre, reportez-vous à la documentation autonome.
Pour activer l’entraînement sélectif des jetons de la couche d’incorporation, vous devez fournir les identifiants de jetons de vos nouveaux jetons via le paramètre trainable_token_indices. Vous pouvez éventuellement spécifier la couche à cibler s’il existe plusieurs couches d’incorporation. Pour un modèle Mistral, cela pourrait ressembler à ceci :
new_tokens = ['<think>', '</think>']
tokenizer.add_tokens(new_tokens)
base_model.resize_token_embeddings(len(tokenizer))
lora_config = LoraConfig(
...,
trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
)Si votre modèle utilise des poids liés (comme lm_head), les jetons entraînables tenteront de les résoudre et de les mettre à jour également, il n’y a donc pas besoin d’ajouter modules_to_save=["lm_head"] dans ce cas. Cela ne fonctionne que si le modèle utilise la convention de Transformers pour le lien des poids.
Lors de l’enregistrement du modèle avec model.save_pretrained, la matrice d’embedding complète peut être enregistrée au lieu de n’enregistrer que la différence, par précaution, car la matrice d’embedding a été redimensionnée. Pour économiser de l’espace, vous pouvez désactiver ce comportement en définissant save_embedding_layers=False lors de l’appel de save_pretrained. Cette opération est sûre tant que vous ne modifiez pas la matrice d’embedding par d’autres moyens, car de telles modifications ne seraient pas suivies par les jetons entraînables.
Utiliser un adaptateur, par exemple LoRA
Préparez la couche d’embedding en l’ajoutant à target_modules de votre configuration d’adaptateur. Par exemple, la configuration Mistral pourrait ressembler à ceci :
config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])Une fois ajoutés à target_modules, PEFT stocke automatiquement la couche d’embedding lors de l’enregistrement de l’adaptateur si le modèle dispose de get_input_embeddings et get_output_embeddings. C’est généralement le cas pour les modèles Transformers.
Si la couche d’embedding du modèle ne suit pas le schéma de nommage du Transformer mais implémente néanmoins get_input_embeddings, vous pouvez tout de même l’enregistrer en transmettant manuellement save_embedding_layers=True lors de l’enregistrement de l’adaptateur :
model = get_peft_model(...)
# train the model
model.save_pretrained("my_adapter", save_embedding_layers=True)Pour l'inférence, chargez d'abord le modèle de base et redimensionnez-le de la même manière que vous l'avez fait avant d'entraîner le modèle. Une fois le modèle de base redimensionné, vous pouvez charger le point de contrôle PEFT.
Pour un exemple complet, veuillez consulter ce notebook.
Ajustement complet
Le fine-tuning complet est plus coûteux en termes de VRAM ou d'espace de stockage, mais si tout le reste échoue, vous pouvez revenir à cette méthode et vérifier si elle fonctionne pour vous. Pour y parvenir, ajoutez le nom de la couche d'embedding à modules_to_save. Notez que vous devez également ajouter les couches liées, par exemple lm_head. Exemple pour un modèle Mistral avec LoRA :
config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])Recevoir un avertissement indiquant que les « poids ne sont pas initialisés à partir du point de contrôle du modèle »
Lorsque vous chargez votre modèle PEFT qui a été entraîné sur une tâche (par exemple, la classification), vous pouvez recevoir un avertissement de ce type :
Certains poids de LlamaForSequenceClassification n’ont pas été initialisés à partir du point de contrôle du modèle situé dans meta-llama/Llama-3.2-1B et ont été nouvellement initialisés : [‘score.weight’]. Vous devriez probablement ENTRAÎNER ce modèle sur une tâche en aval pour pouvoir l’utiliser pour les prédictions et l’inférence.
Bien que cela puisse paraître inquiétant, il n’y a le plus souvent aucune raison de s’en préoccuper. Cet avertissement provient de Transformers et n’est pas spécifique à PEFT. Il vous informe qu’une tête de classification initialisée aléatoirement (score) est attachée au modèle de base, et que cette tête doit être entraînée pour produire des prédictions cohérentes.
Lorsque vous recevez cet avertissement avant l’entraînement du modèle, PEFT prend automatiquement en charge le rendu de la tête de classification entraînable si vous avez correctement transmis l’argument task_type à la configuration PEFT.
from peft import LoraConfig, TaskType
lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)Si votre tête de classification ne suit pas les conventions de nommage habituelles de Transformers (ce qui est rare), vous devez explicitement indiquer à PEFT le nom de la tête dans modules_to_save.
lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])Pour vérifier le nom de la tête de classification, affichez le modèle ; il s’agit du dernier module.
Si vous obtenez cet avertissement depuis votre code d’inférence, c’est-à-dire après l’entraînement du modèle, lors du chargement du modèle PEFT, vous devez toujours charger d’abord le modèle Transformers. Comme Transformers ne sait pas que vous chargerez les poids PEFT par la suite, il affiche toujours l’avertissement.
Comme toujours, il est recommandé de vérifier que le modèle fonctionne correctement pour l'inférence en exécutant quelques validations dessus.
Exécuter un contrôle d'intégrité de l'entraînement
Avant l’entraînement, utilisez print_healthcheck() pour obtenir un résumé compact et lisible par un humain de l’état du modèle PEFT. Il est dérivé des API d’état du modèle et des couches (voir la section suivante) et signale les états suspects tels que des adaptateurs incohérents, des adaptateurs fusionnés ou un nombre suspect de paramètres entraînables.
peft_model.print_healthcheck()
Accordez une attention particulière à la présence de la section « Findings » à la fin du rapport, qui est incluse si des irrégularités ont été détectées.
Si vous préférez une sortie sous forme de dictionnaire, sérialisable en JSON, utilisez healthcheck() à la place :
healthcheck = peft_model.healthcheck()
print(healthcheck.get("findings", "all good")Cette vérification n'inspecte que le modèle et ses adaptateurs. Elle ne valide pas votre boucle d'entraînement, votre optimiseur ou votre jeu de données.
Vérifier l'état des couches et du modèle
Un modèle PEFT peut parfois se retrouver dans un état instable, en particulier lors de la gestion de plusieurs adaptateurs. Il peut y avoir une certaine confusion quant aux adaptateurs existants, à celui qui est actif, à celui qui est fusionné, etc. Pour aider à investiguer ce problème, appelez les méthodes get_layer_status() et get_model_status().
La méthode get_layer_status() vous donne un aperçu détaillé des adaptateurs actifs, fusionnés et disponibles pour chaque couche ciblée.
>>> from transformers import AutoModel
>>> from peft import get_peft_model, LoraConfig
>>> model_id = "google/flan-t5-small"
>>> model = AutoModel.from_pretrained(model_id)
>>> model = get_peft_model(model, LoraConfig())
>>> model.get_layer_status()
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
module_type='lora.Linear',
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default']),
TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
module_type='lora.Linear',
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default']),
...]
>>> model.get_model_status()
TunerModelStatus(
base_model_type='T5Model',
adapter_model_type='LoraModel',
peft_types={'default': 'LORA'},
trainable_params=344064,
total_params=60855680,
num_adapter_layers=48,
enabled=True,
active_adapters=['default'],
merged_adapters=[],
requires_grad={'default': True},
available_adapters=['default'],
)Dans la sortie de l’état du modèle, vous devez surveiller les entrées indiquant "irregular". Cela signifie que PEFT a détecté un état incohérent dans le modèle. Par exemple, si merged_adapters="irregular", cela signifie que pour au moins un adaptateur, il a été fusionné sur certains modules cibles mais pas sur d’autres. Les résultats d’inférence seront très probablement incorrects en conséquence.
La meilleure façon de résoudre ce problème est de recharger l’ensemble du modèle et des points de contrôle de l’adaptateur. Assurez-vous de ne pas effectuer d’opérations incorrectes sur le modèle, par exemple fusionner manuellement les adaptateurs sur certains modules mais pas sur d’autres.
Convertir l'état des couches en DataFrame pandas pour une inspection visuelle plus aisée.
from dataclasses import asdict
import pandas as pd
df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())Il est possible d’obtenir ces informations pour des modèles non-PEFT s’ils utilisent des couches PEFT en interne, mais certaines informations comme base_model_type ou peft_types ne peuvent pas être déterminées dans ce cas. À titre d’exemple, vous pouvez l’appeler sur un modèle diffusers de la manière suivante :
>>> import torch
>>> from diffusers import StableDiffusionPipeline
>>> from peft import get_model_status, get_layer_status
>>> path = "runwayml/stable-diffusion-v1-5"
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
>>> pipe = StableDiffusionPipeline.from_pretrained(path, dtype=torch.float16)
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
>>> pipe.set_lora_device(["adapter-2"], "cuda")
>>> get_layer_status(pipe.text_encoder)
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
module_type='lora.Linear',
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
available_adapters=['adapter-1', 'adapter-2'],
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
module_type='lora.Linear',
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
...]
>>> get_model_status(pipe.unet)
TunerModelStatus(
base_model_type='other',
adapter_model_type='None',
peft_types={},
trainable_params=797184,
total_params=861115332,
num_adapter_layers=128,
enabled=True,
active_adapters=['adapter-2'],
merged_adapters=[],
requires_grad={'adapter-1': False, 'adapter-2': True},
available_adapters=['adapter-1', 'adapter-2'],
devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
)Vitesse
Le chargement des poids de l'adaptateur est lent
Le chargement d’adaptateurs tels que les poids LoRA devrait généralement être rapide par rapport au chargement du modèle de base. Toutefois, il peut exister des cas d’usage où les poids de l’adaptateur sont très volumineux ou où les utilisateurs doivent charger un grand nombre d’adaptateurs — le temps de chargement peut alors s’accumuler. La raison en est que les poids de l’adaptateur sont d’abord initialisés puis remplacés par les poids chargés, ce qui est gaspilleux. Pour accélérer le temps de chargement, vous pouvez passer l’argument low_cpu_mem_usage=True à from_pretrained() et load_adapter().
Si cette option fonctionne bien dans différents cas d’usage, elle pourrait devenir la valeur par défaut pour le chargement des adaptateurs à l’avenir.
Reproductibilité
Modèles utilisant la normalisation par lot
Lors du chargement d'un modèle PEFT entraîné dont le modèle de base utilise la normalisation par lot (par exemple torch.nn.BatchNorm1d ou torch.nn.BatchNorm2d), vous constaterez peut-être qu'il est impossible de reproduire exactement les mêmes sorties. Cela s'explique par le fait que les couches de normalisation par lot conservent les statistiques courantes pendant l'entraînement, mais que ces statistiques ne font pas partie du point de contrôle PEFT. Par conséquent, lors du chargement du modèle PEFT, les statistiques courantes du modèle de base sont utilisées (c'est-à-dire celles d'avant l'entraînement avec PEFT).
Selon votre cas d’usage, cela peut ne pas être un problème majeur. Si, en revanche, vous avez besoin que vos sorties soient 100 % reproductibles, vous pouvez y parvenir en ajoutant les couches de normalisation par lot à modules_to_save. Voici un exemple utilisant resnet et LoRA. Notez que nous définissons modules_to_save=["classifier", "normalization"]. Nous avons besoin de l’argument "classifier" car notre tâche est la classification d’images, et nous ajoutons l’argument "normalization" pour nous assurer que les couches de normalisation par lot sont enregistrées dans le point de contrôle PEFT.
from transformers import AutoModelForImageClassification
from peft import LoraConfig, get_peft_model
model_id = "microsoft/resnet-18"
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
config = LoraConfig(
target_modules=["convolution"],
modules_to_save=["classifier", "normalization"],
),Selon le type de modèle que vous utilisez, les couches de normalisation par lot peuvent avoir des noms différents de "normalization". Veuillez donc vous assurer que le nom correspond à l'architecture de votre modèle.
Incompatibilité de version
Erreur lors du chargement de la configuration en raison d'un argument de mot-clé inattendu
Lorsque vous rencontrez une erreur comme celle ci-dessous, cela signifie que l’adaptateur que vous essayez de charger a été entraîné avec une version de PEFT plus récente que celle installée sur votre système.
TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>La meilleure façon de résoudre ce problème est d'installer la dernière version de PEFT :
python -m pip install -U PEFT
Si l’adaptateur a été entraîné à partir d’une installation source de PEFT (une version non publiée de PEFT), vous devez également installer PEFT à partir du code source.
python -m pip install -U git+https://github.com/huggingface/peft.git
Si vous ne pouvez pas mettre à niveau PEFT, il existe un contournement que vous pouvez essayer.
Supposons que le message d'erreur indique que l'argument de mot-clé inconnu est nommé foobar. Recherchez l'entrée foobar dans le fichier adapter_config.json de cet adaptateur PEFT et supprimez-la du fichier. Enregistrez ensuite le fichier et essayez de charger le modèle à nouveau.
Cette solution fonctionne dans la plupart des cas. Tant qu’il s’agit de la valeur par défaut de foobar, elle peut être ignorée. Cependant, lorsqu’elle est définie sur une autre valeur, vous obtiendrez des résultats incorrects. La mise à niveau de PEFT est la solution recommandée.
Gestion des adaptateurs
Utiliser plusieurs adaptateurs simultanément
PEFT vous permet de créer plus d'un adaptateur sur le même modèle. Cela peut s'avérer utile dans de nombreuses situations. Par exemple, pour l'inférence, vous pouvez vouloir servir deux modèles affinés à partir du même modèle de base plutôt que de charger le modèle de base une fois pour chaque modèle affiné, ce qui coûterait plus de mémoire. Cependant, plusieurs adaptateurs peuvent être activés simultanément. De cette manière, le modèle peut tirer parti des apprentissages de tous ces adaptateurs en même temps. À titre d'exemple, si vous disposez d'un modèle de diffusion, vous pouvez vouloir utiliser un adaptateur LoRA pour modifier le style et un autre pour modifier le sujet.
L’activation de plusieurs adaptateurs simultanément est généralement possible avec toutes les méthodes PEFT (LoRA, LoHa, IA³, etc.), à l’exception des méthodes d’apprentissage par prompt (p-tuning, prefix tuning, etc.). L’exemple suivant illustre comment y parvenir :
from transformers import AutoModelForCausalLM
from peft import PeftModel
model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
model = PeftModel.from_pretrained(base_model, lora_path_0) # default adapter_name is 'default'
model.load_adapter(lora_path_1, adapter_name="other")
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
model.base_model.set_adapter(["default", "other"])Dans l’exemple ci-dessus, on constate qu’il est nécessaire d’appeler
model.base_model.set_adapter(["default", "other"]). Pourquoi ne peut-on pas appelermodel.set_adapter(["default", "other"])? Malheureusement, ce n’est pas possible car, comme expliqué précédemment, certaines méthodes PEFT ne prennent pas en charge l’activation de plus d’un adaptateur à la fois.
Il est également possible d’entraîner deux adaptateurs simultanément, mais il faut veiller à ce que les poids des deux adaptateurs soient connus de l’optimiseur. Sinon, seul un adaptateur recevra des mises à jour.
from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config_0 = LoraConfig(...)
lora_config_1 = LoraConfig(...)
model = get_peft_model(base_model, lora_config_0)
model.add_adapter(adapter_name="other", peft_config=lora_config_1)Si nous appelons maintenant :
from transformers import Trainer
trainer = Trainer(model=model, ...)
trainer.train()ou
optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)le second adaptateur LoRA ("other") ne serait pas entraîné. Cela est dû au fait qu’il est inactif à ce moment-là, ce qui signifie que l’attribut requires_grad de ses paramètres est défini sur False et que l’optimiseur l’ignorerait. Assurez-vous donc d’activer tous les adaptateurs devant être entraînés avant d’initialiser l’optimiseur :
# activate all adapters
model.base_model.set_adapter(["default", "other"])
trainer = Trainer(model=model, ...)
trainer.train()Cette section traite de l'utilisation de plusieurs adaptateurs de même type sur un même modèle, par exemple, l'utilisation simultanée de plusieurs adaptateurs LoRA. Elle ne s'applique pas à l'utilisation de types différents d'adaptateurs sur un même modèle, par exemple un adaptateur LoRA et un adaptateur LoHa. Pour cela, veuillez consulter
PeftMixedModel.
Fusionner uniquement certains des adaptateurs actifs
Lorsque les poids des adaptateurs sont fusionnés dans les poids de base, les couches concernées n’appliquent plus aucun adaptateur lors du passage avant ; elles n’utilisent que les poids de base fusionnés. Par conséquent, si plusieurs adaptateurs sont actifs mais que seuls certains sont fusionnés, les adaptateurs qui restent actifs mais non fusionnés ne sont pas appliqués silencieusement :
model = get_peft_model(base_model, lora_config_0, adapter_name="default")
model.add_adapter("other", lora_config_1)
model.base_model.set_adapter(["default", "other"])
output_both = model(**inputs)
model.merge_adapter(adapter_names=["default"])
output_merged = model(**inputs) # this is *not* the same as output_bothIci, output_merged est la sortie de l'adaptateur "default" seul, même si "other" est toujours répertorié comme actif :
>>> model.get_layer_status()[0].active_adapters
['default', 'other']
>>> model.get_layer_status()[0].merged_adapters
['default']La même règle s’applique lorsqu’un adaptateur qui n’est pas actif est fusionné : à partir de ce moment, le modèle renvoie la sortie de l’adaptateur fusionné et celui qui est actif est ignoré. Aucune erreur ni aucun avertissement n’est émis dans les deux cas. Les poids de l’adaptateur lui-même ne sont pas modifiés, de sorte que l’appel de unmerge_adapter() restaure la sortie combinée.
Pour éviter cela, fusionnez tous les adaptateurs actifs, c’est-à-dire appelez model.merge_adapter() sans adapter_names, ou défusionnez avant de modifier les adaptateurs actifs.
Ce comportement est cohérent pour toutes les méthodes PEFT qui prennent en charge la fusion (LoRA, LoHa, LoKr, IA³, OFT, etc.), car elles partagent toutes la même logique dans le passage avant.
Update on GitHubCe comportement est source de confusion et est prévu pour changer dans la version PEFT v1.0.