PEFT documentation
Injection d'adaptateurs
Injection d'adaptateurs
Avec PEFT, vous pouvez injecter des adaptateurs entraînables dans n'importe quel module torch, ce qui vous permet d'utiliser les méthodes d'adaptateurs sans dépendre des classes de modélisation de PEFT. Cela fonctionne pour tous les adaptateurs, à l'exception de ceux basés sur l'apprentissage par prompt (par exemple, le prefix tuning ou le p-tuning) et des adaptateurs qui conservent un état partagé entre plusieurs couches cibles.
Consultez le tableau ci-dessous pour savoir quand vous devez injecter des adaptateurs.
| Avantages | Cons |
|---|---|
| le modèle est modifié en place, en conservant tous les attributs et méthodes d'origine | écrire manuellement les fonctions utilitaires from_pretrained et save_pretrained de Hugging Face pour enregistrer et charger les adaptateurs |
fonctionne avec tout module et toute modalité torch | ne fonctionne avec aucune des méthodes utilitaires fournies par PeftModel, telles que la désactivation et la fusion des adaptateurs |
inject_adapter_in_modelne prend pas en charge les méthodes PEFT qui conservent l'état de l'adaptateur partagé entre plusieurs couches cibles. Cela inclut actuellement TinyLoRA, UniLoRA, VeRA, PVeRA, VBLoRA et FRoD. Utilisez plutôt get_peft_model() pour ces méthodes.
Créer un nouveau modèle PEFT
Pour effectuer l'injection de l'adaptateur, utilisez la méthode inject_adapter_in_model(). Cette méthode prend 3 arguments : la configuration PEFT, le modèle et un nom d'adaptateur facultatif. Vous pouvez également attacher plusieurs adaptateurs au modèle en appelant inject_adapter_in_model() plusieurs fois avec des noms d'adaptateurs différents.
Par exemple, pour injecter des adaptateurs LoRA dans le sous-module linear du module DummyModel :
import torch
from peft import inject_adapter_in_model, LoraConfig
class DummyModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.embedding = torch.nn.Embedding(10, 10)
self.linear = torch.nn.Linear(10, 10)
self.lm_head = torch.nn.Linear(10, 10)
def forward(self, input_ids):
x = self.embedding(input_ids)
x = self.linear(x)
x = self.lm_head(x)
return x
lora_config = LoraConfig(
lora_alpha=16,
lora_dropout=0.1,
r=64,
bias="none",
target_modules=["linear"],
)
model = DummyModel()
model = inject_adapter_in_model(lora_config, model)
dummy_inputs = torch.LongTensor([[0, 1, 2, 3, 4, 5, 6, 7]])
dummy_outputs = model(dummy_inputs)Affichez le modèle pour vérifier que les adaptateurs ont été correctement injectés.
DummyModel(
(embedding): Embedding(10, 10)
(linear): Linear(
in_features=10, out_features=10, bias=True
(lora_dropout): ModuleDict(
(default): Dropout(p=0.1, inplace=False)
)
(lora_A): ModuleDict(
(default): Linear(in_features=10, out_features=64, bias=False)
)
(lora_B): ModuleDict(
(default): Linear(in_features=64, out_features=10, bias=False)
)
(lora_embedding_A): ParameterDict()
(lora_embedding_B): ParameterDict()
)
(lm_head): Linear(in_features=10, out_features=10, bias=True)
)Injection basée sur un state_dict
Il peut arriver qu’un point de contrôle d’adaptateur PEFT existe, mais que la configuration PEFT correspondante ne soit pas connue pour une raison quelconque. Pour injecter les couches PEFT de ce point de contrôle, il faudrait normalement rétro-ingénier la configuration PEFT correspondante, en particulier l’argument target_modules, à partir du state_dict du point de contrôle. Cette opération peut être fastidieuse et sujette aux erreurs. Pour l’éviter, il est également possible d’appeler inject_adapter_in_model() et de passer le state_dict chargé en argument :
from safetensors.torch import load_file
model = ...
state_dict = load_file(<path-to-safetensors-file>)
lora_config = LoraConfig(...)
model = inject_adapter_in_model(lora_config, model, state_dict=state_dict)Dans ce cas, PEFT utilisera le state_dict comme référence pour les couches à cibler, au lieu d’utiliser la configuration PEFT. En tant qu’utilisateur, vous n’avez pas besoin de définir les target_modules exacts de la configuration PEFT pour que cela fonctionne. Cependant, vous devez toujours passer une configuration PEFT du bon type, par exemple LoraConfig dans cet exemple, et vous pouvez laisser target_modules à None.
Notez que cela ne crée toujours que les couches PEFT non initialisées ; les valeurs de state_dict ne sont pas utilisées pour alimenter les poids du modèle. Pour alimenter les poids, poursuivez en appelant set_peft_model_state_dict() comme décrit ci-dessous.
⚠️ Notez que s'il y a une incohérence entre ce qui est configuré dans la configuration PEFT et ce qui est trouvé dans le state_dict, PEFT vous en avertira. Vous pouvez ignorer cet avertissement si vous savez que la configuration PEFT n'est pas correctement spécifiée.
Si les adaptateurs PEFT d’origine utilisaient
target_parametersau lieu detarget_modules, l’injection à partir d’unstate_dictne fonctionnera pas correctement. Dans ce cas, il est obligatoire d’utiliser la configuration PEFT correcte pour l’injection.
Enregistrer le modèle
Pour ne sauvegarder que l'adaptateur, utilisez la fonction get_peft_model_state_dict() :
from peft import get_peft_model_state_dict
peft_state_dict = get_peft_model_state_dict(model)
print(peft_state_dict)Sinon, model.state_dict() renvoie le state dict complet du modèle.
Chargement du modèle
Après avoir chargé le state_dict enregistré, il peut être appliqué à l’aide de la fonction set_peft_model_state_dict() :
from peft import set_peft_model_state_dict
model = DummyModel()
model = inject_adapter_in_model(lora_config, model)
outcome = set_peft_model_state_dict(model, peft_state_dict)
# check that there were no wrong keys
print(outcome.unexpected_keys)Si l’injection de l’adaptateur est lente ou si vous devez charger un grand nombre d’adaptateurs, vous pouvez utiliser une optimisation qui permet de créer un adaptateur « vide » sur le méta-appareil et de n’y insérer les poids réels que lorsque set_peft_model_state_dict() est appelé. Pour ce faire, passez low_cpu_mem_usage=True à la fois à inject_adapter_in_model() et à set_peft_model_state_dict().
model = DummyModel()
model = inject_adapter_in_model(lora_config, model, low_cpu_mem_usage=True)
print(model.linear.lora_A["default"].weight.device.type == "meta") # should be True
set_peft_model_state_dict(model, peft_state_dict, low_cpu_mem_usage=True)
print(model.linear.lora_A["default"].weight.device.type == "cpu") # should be TruePour charger les poids depuis le hub, il existe la fonction de bas niveau load_peft_weights() :
state_dict = load_peft_weights("my-account/my-adapter-repo")Définir et charger les poids de base
Les fonctions ci-dessus traitent le state dict de l'adaptateur. Il existe également des situations où les poids du modèle de base doivent être lus ou écrits à travers l'enveloppe PEFT. Ce n'est pas entièrement trivial car PEFT renomme les paramètres des modules ciblés (par exemple q_proj.weight devient q_proj.base_layer.weight) et ajoute des paramètres d'adaptateur qui n'existent pas dans le modèle de base. Utilisez get_base_model_state_dict() et set_base_model_state_dict() pour traduire entre les deux nomenclatures :
from peft import LoraConfig, get_peft_model, get_base_model_state_dict, set_base_model_state_dict
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("facebook/opt-125m")
model = get_peft_model(model, LoraConfig(target_modules="all-linear"))
# state dict with the original (pre-PEFT) key names, adapter parameters excluded
base_state_dict = get_base_model_state_dict(model)
# load a state dict with original key names into the PEFT-wrapped model
outcome = set_base_model_state_dict(model, base_state_dict, strict=False)
# check that there were no wrong keys
print(outcome.missing_keys, outcome.unexpected_keys)Le cas d’usage principal de cette fonctionnalité est le chargement des poids de base après que le modèle a déjà été encapsulé par PEFT. Par exemple, les configurations d’entraînement FSDP telles que TorchTitan initialisent le modèle sur le périphérique meta, appliquent PEFT, puis découpent le résultat. La mémoire réelle n’existe qu’après la découpe, de sorte que le point de contrôle, dont les clés utilisent les noms d’origine, doit être chargé dans le modèle déjà encapsulé. Notez que get_base_model_state_dict() renvoie les tenseurs actifs du modèle, et non des copies.