PEFT 文档
LoRA
并获得增强的文档体验
开始使用
LoRA
LoRA 是一种低秩分解方法,用于减少可训练参数的数量,从而加快大模型的微调速度并减少内存使用。在 PEFT 中,使用 LoRA 非常简单,只需设置一个 LoraConfig,并用 get_peft_model() 将其包装起来,即可创建一个可训练的 PeftModel。
本指南将更详细地探讨使用 LoRA 的其他选项和功能。
初始化
LoRA 权重的初始化由 LoraConfig 中的 `init_lora_weights` 参数控制。默认情况下,PEFT 使用 Kaiming 均匀分布初始化 LoRA 权重 A,并用零初始化权重 B,从而实现恒等变换(与参考 实现 相同)。
也可以传递 `init_lora_weights="gaussian"`。顾名思义,这将使用高斯分布初始化权重 A,并用零初始化权重 B(这是 Diffusers 初始化 LoRA 权重的方式)。
from peft import LoraConfig
config = LoraConfig(init_lora_weights="gaussian", ...)
还有一个选项可以设置 `init_lora_weights=False`,这对于调试和测试非常有用。这应该是您唯一使用此选项的情况。选择此选项时,LoRA 权重的初始化将不会导致恒等变换。
from peft import LoraConfig
config = LoraConfig(init_lora_weights=False, ...)
PiSSA
PiSSA 使用主奇异值和奇异向量来初始化 LoRA 适配器。这种简单的修改使得 PiSSA 比 LoRA 收敛得更快,并最终获得更优越的性能。此外,PiSSA 减少了与 QLoRA 相比的量化误差,从而带来了进一步的提升。
将初始化方法配置为“pissa”,这可能需要几分钟时间来对预训练模型执行 SVD
from peft import LoraConfig
config = LoraConfig(init_lora_weights="pissa", ...)
或者,执行快速 SVD,这只需要几秒钟。迭代次数决定了误差和计算时间之间的权衡
lora_config = LoraConfig(init_lora_weights="pissa_niter_[number of iters]", ...)
有关使用 PiSSA 的详细说明,请遵循这些说明。
CorDA
CorDA 从权重分解中构建任务感知的 LoRA 适配器,其分解方向由下游任务上下文(指令预览模式,IPM)或需要维护的世界知识(知识保留模式,KPM)决定。KPM 不仅在微调任务上比 LoRA 取得了更好的性能,还减轻了对预训练世界知识的灾难性遗忘。当保留预训练知识不是问题时,IPM 更受青睐,因为它可以进一步加速收敛并提高微调性能。
您需要将初始化方法配置为 “corda”,并指定 IPM 或 KPM 模式以及用于收集协方差矩阵的数据集。
@torch.no_grad()
def run_model():
# Assume `model` and `dataset` is in context...
model.eval()
for batch in dataset:
model(**batch)
corda_config = CordaConfig(
corda_method="kpm",
)
lora_config = LoraConfig(
init_lora_weights="corda",
corda_config=corda_config,
)
preprocess_corda(model, lora_config, run_model=run_model)
peft_model = get_peft_model(model, lora_config)
有关使用 CorDA 的详细说明,请遵循这些说明。
OLoRA
OLoRA 利用 QR 分解来初始化 LoRA 适配器。OLoRA 通过其 QR 分解的一个因子来平移模型的基础权重,即在进行任何训练之前就对权重进行变异。这种方法显著提高了稳定性,加速了收敛速度,并最终实现了更优越的性能。
您只需传递一个额外的选项即可使用 OLoRA
from peft import LoraConfig
config = LoraConfig(init_lora_weights="olora", ...)
有关更高级的用法,请参阅我们的文档。
EVA
EVA 对每层的输入激活进行奇异值分解(SVD),并使用右奇异向量来初始化 LoRA 权重。因此,它是一种数据驱动的初始化方案。此外,EVA 根据“解释方差比”——一个源自 SVD 分析的指标——在各层之间自适应地分配秩。
您可以通过设置 `init_lora_weights="eva"` 并在 LoraConfig 中定义 EvaConfig 来使用 EVA
from peft import LoraConfig, EvaConfig
peft_config = LoraConfig(
init_lora_weights = "eva",
eva_config = EvaConfig(rho = 2.0),
...
)
参数 `rho` (≥ 1.0) 决定了允许多少重分配。当 `rho=1.0` 且 `r=16` 时,LoRA 适配器的秩严格限制为 16,不允许任何重分配发生。EVA 推荐的重分配值为 2.0,这意味着允许一层的最大秩为 2r。
建议在 GPU 上执行 EVA 初始化,因为它速度快得多。为了优化 EVA 的可用内存量,您可以在 get_peft_model() 中使用 `low_cpu_mem_usage` 标志
peft_model = get_peft_model(model, peft_config, low_cpu_mem_usage=True)
然后,调用 initialize_lora_eva_weights() 来初始化 EVA 权重(在大多数情况下,用于 EVA 初始化的数据加载器可以与用于微调的数据加载器相同)
initialize_lora_eva_weights(peft_model, dataloader)
EVA 可以直接与 bitsandbytes 一起使用。只需使用 `quantization_config` 初始化模型,然后像往常一样调用 initialize_lora_eva_weights() 即可。
有关使用 EVA 的进一步说明,请参阅我们的文档。
LoftQ
标准方法
在为 QLoRA 训练量化基础模型时,请考虑使用 LoftQ 初始化,它已被证明在训练量化模型时能提高性能。其思想是初始化 LoRA 权重,以最小化量化误差。要使用 LoftQ,请遵循这些说明。
通常,为了使 LoftQ 发挥最佳效果,建议尽可能多地将 LoRA 应用于各层,因为未被应用 LoRA 的层无法应用 LoftQ。这意味着传递 `LoraConfig(..., target_modules="all-linear")` 很可能会得到最好的结果。此外,在使用 4 位量化时,您应在量化配置中使用 `nf4` 作为量化类型,即 `BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4")`。
一种更便捷的方法
应用 LoftQ 初始化的一个更简单但功能更有限的方法是使用便捷函数 `replace_lora_weights_loftq`。它将量化后的 PEFT 模型作为输入,并将其 LoRA 权重原地替换为 LoftQ 初始化的对应权重。
from peft import replace_lora_weights_loftq
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(load_in_4bit=True, ...)
base_model = AutoModelForCausalLM.from_pretrained(..., quantization_config=bnb_config)
# note: don't pass init_lora_weights="loftq" or loftq_config!
lora_config = LoraConfig(task_type="CAUSAL_LM")
peft_model = get_peft_model(base_model, lora_config)
replace_lora_weights_loftq(peft_model)
`replace_lora_weights_loftq` 还允许您传递一个 `callback` 参数,让您更好地控制哪些层应该被修改或不被修改,这在经验上可以大大提高结果。要查看更详细的示例,请查看这个笔记本。
`replace_lora_weights_loftq` 只实现了 LoftQ 的一个迭代步骤。这意味着只更新 LoRA 权重,而不是迭代地更新 LoRA 权重和量化基础模型权重。这可能导致性能下降,但优点是我们可以使用从基础模型派生的原始量化权重,而不必保留修改后的量化权重的额外副本。这种权衡是否值得取决于具体用例。
目前,`replace_lora_weights_loftq` 还有以下额外限制
- 模型文件必须存储为 `safetensors` 文件。
- 仅支持 bitsandbytes 4位量化。
在量化指南中了解更多关于 PEFT 如何与量化协同工作的信息。
秩稳定 LoRA
另一种初始化 LoraConfig 的方法是使用秩稳定 LoRA (rsLoRA) 方法。LoRA 架构在每次前向传播时,会用一个在初始化时设置的、取决于秩 `r` 的固定标量来缩放每个适配器。在原始实现中,该标量为 `lora_alpha/r`,但 rsLoRA 使用 `lora_alpha/math.sqrt(r)`,这可以稳定适配器,并提高使用更高 `r` 值的性能潜力。
from peft import LoraConfig
config = LoraConfig(use_rslora=True, ...)
权重分解低秩自适应 (DoRA)
该技术将权重的更新分解为两个部分:幅度和方向。方向由常规的 LoRA 处理,而幅度由一个单独的可学习参数处理。这可以提高 LoRA 的性能,尤其是在低秩情况下。有关 DoRA 的更多信息,请参见 https://huggingface.co/papers/2402.09353。
from peft import LoraConfig
config = LoraConfig(use_dora=True, ...)
如果模型的部分或 DoRA 适配器被卸载到 CPU,您可以通过在 `config.runtime_config` 中使用 `ephemeral_gpu_offload=True`,以一些临时的(短暂的)VRAM 开销为代价,获得显著的速度提升。
from peft import LoraConfig, LoraRuntimeConfig
config = LoraConfig(use_dora=True, runtime_config=LoraRuntimeConfig(ephemeral_gpu_offload=True), ...)
带有 DoRA 适配器的 `PeftModel` 也可以使用 `from_pretrained` 方法和 `load_adapter` 方法,通过 `ephemeral_gpu_offload=True` 标志加载。
from peft import PeftModel
model = PeftModel.from_pretrained(base_model, peft_model_id, ephemeral_gpu_offload=True)
DoRA 针对评估模式下的模型或当 dropout 设置为 0 时进行了优化(计算速度更快,占用内存更少)。在这些情况下,我们会重用基础结果以获得加速。在 4090 上运行DoRA 微调,设置 `CUDA_VISIBLE_DEVICES=0 time python examples/dora_finetuning/dora_finetuning.py --quantize --lora_dropout 0 --batch_size 16 --eval_step 2 --use_dora`,梯度累积设置为 2,最大步数设置为 20,观察结果如下:
无优化 | 有优化 | |
---|---|---|
train_runtime | 359.7298 | 279.2676 |
train_samples_per_second | 1.779 | 2.292 |
train_steps_per_second | 0.056 | 0.072 |
注意事项
- DoRA 目前仅支持 Embedding、Linear 和 Conv2d 层。
- DoRA 引入的开销比纯 LoRA 更大,因此建议在推理时合并权重,请参见 LoraModel.merge_and_unload()。
- DoRA 应该可以与使用 bitsandbytes 量化的权重一起工作(“QDoRA”)。然而,在使用 QDoRA 和 DeepSpeed Zero2 时报告了一些问题。
QLoRA 风格的训练
PEFT 中默认的 LoRA 设置会为每个注意力块的查询层和值层添加可训练的权重。但是,QLoRA 为 Transformer 模型的所有线性层添加可训练的权重,可以提供与完全微调模型相当的性能。要像 QLoRA 一样将 LoRA 应用于所有线性层,请设置 `target_modules="all-linear"`(这比按名称指定各个模块更容易,因为模块名称可能因架构而异)。
config = LoraConfig(target_modules="all-linear", ...)
使用 LoRA 实现内存高效的层复制
一种提高模型性能的方法是通过复制模型中的层来扩展模型,从而从给定大小的预训练模型构建一个更大的模型。例如,如 SOLAR 论文中所述,将一个 7B 模型增加到一个 10B 模型。PEFT LoRA 以一种内存高效的方式支持这种扩展,支持在层复制后使用附加到层上的 LoRA 适配器进行进一步的微调。复制的层不会占用额外的内存,因为它们共享底层权重,所以唯一需要的额外内存是适配器权重的内存。要使用此功能,您需要创建一个带有 `layer_replication` 参数的配置。
config = LoraConfig(layer_replication=[[0,4], [2,5]], ...)
假设原始模型有 5 层 `[0, 1, 2, 3, 4]`,这将创建一个有 7 层的模型,排列为 `[0, 1, 2, 3, 2, 3, 4]`。这遵循 mergekit 的直通合并约定,其中指定为起始包含和结束不包含元组的层序列被堆叠以构建最终模型。最终模型中的每一层都会获得其自己独特的 LoRA 适配器集。
Fewshot-Metamath-OrcaVicuna-Mistral-10B 是一个使用此方法在扩展到 10B 的 Mistral-7B 上训练的模型的示例。adapter_config.json 展示了一个应用此方法进行微调的示例 LoRA 适配器配置。
对秩和 alpha(缩放)进行精细控制
默认情况下,所有应用 LoRA 的层都将具有相同的秩 `r` 和相同的 `lora_alpha`(决定 LoRA 缩放),这取决于在 LoraConfig 中的指定。然而,在某些情况下,您可能希望为不同的层指定不同的值。这可以通过向 LoraConfig 传递 `rank_pattern` 和 `alpha_pattern` 参数来实现。这些参数应该是字典,键是层名,值是秩/alpha 值。键可以是正则表达式 (regex)。所有未在 `rank_pattern` 和 `alpha_pattern` 中明确提及的 LoRA 层将采用默认的 `r` 和 `lora_alpha` 值。
举个例子,假设我们有一个具有以下结构的模型
>>> print(model)
Outer(
(foo): Linear(...)
(module): Middle(
(foo): Linear(...)
(foobar): Linear(...)
(module): Inner(
(foo): Linear(...)
(barfoo): Linear(...)
)
)
)
- `rank_pattern={"foo": 42}` 将匹配所有 3 个 `foo` 层。`foobar` 和 `barfoo` 均不匹配。
- `rank_pattern={"^foo": 42}` 将只匹配模型的 `foo` 层,但不会匹配 `module.foo` 或 `module.module.foo`。这是因为在使用正则表达式时,`^` 表示“字符串的开始”,只有 `foo` 以 `"foo"` 开头,其他层名都有前缀。
- `rank_pattern={"^module.foo": 42}` 仅匹配 `module.foo`,而不匹配 `module.module.foo`,原因相同。
- `rank_pattern={"module.foo": 42}` 匹配 `module.foo` 和 `module.module.foo`,但不匹配 `foo`。
- `rank_pattern={"^foo": 42, "^module.module.foo": 55}` 分别匹配 `foo` 和 `module.module.foo`,但不匹配 `module.foo`。
- 无需指示 `$` 来标记匹配的结束,因为 PEFT 会自动添加它。
同样的逻辑也适用于 `alpha_pattern`。如果您不确定,不要试图使用复杂的正则表达式——只需为每个具有不同秩/alpha 的模块传递完整的名称,前面加上 `^` 前缀,您就应该没问题了。
直接定位 nn.Parameter
此功能是实验性的,可能会发生变化。
通常,您应该使用 `target_modules` 来定位模块(例如 `nn.Linear`)。然而,在某些情况下,这是不可能的。例如,在 HF Transformers 的许多混合专家(MoE)层中,使用的是 `nn.Parameter` 而不是 `nn.Linear`。PEFT 通常会为 LoRA 覆盖 `forward` 方法,但对于 `nn.Parameter`,没有 `forward` 方法。因此,要将 LoRA 应用于该参数,需要使用 `target_parameters` 来定位它。例如,对于Llama4,您可以传递:`target_parameters=['feed_forward.experts.gate_up_proj', 'feed_forward.experts.down_proj]`。
目前,该参数允许定位 2 维或 3 维的 `nn.Parameter`。假设在 3 维参数的情况下,第 0 维是专家维度。
优化器
LoRA 训练可以选择性地包括专用优化器。目前 PEFT 支持 LoRA-FA 和 LoRA+。
LoRA-FA 优化器
使用 LoRA-FA 可以使 LoRA 训练更有效、更高效,如 LoRA-FA 中所述。LoRA-FA 通过固定矩阵 A 并仅调整矩阵 B 来减少激活内存消耗。在训练期间,优化 B 的梯度以逼近全参数微调梯度。此外,LoRA-FA 的内存消耗对秩不敏感(因为它删除了 A 的激活),因此可以通过增大 LoRA 秩来提高性能,而无需增加内存消耗。
from peft import LoraConfig, get_peft_model
from peft.optimizers import create_lorafa_optimizer
from transformers import Trainer, get_cosine_schedule_with_warmup
base_model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
config = LoraConfig(...)
model = get_peft_model(base_model, config)
optimizer = create_lorafa_optimizer(
model=model,
r=128,
lora_alpha=32,
lr=7e-5,
)
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=100,
num_training_steps=1000,
)
trainer = Trainer(
...,
optimizers=(optimizer, scheduler),
)
LoRA+ 优化 LoRA
可以使用 LoRA+ 来优化 LoRA 训练,它对适配器矩阵 A 和 B 使用不同的学习率,已被证明可将微调速度提高多达 2 倍,性能提高 1-2%。
from peft import LoraConfig, get_peft_model
from peft.optimizers import create_loraplus_optimizer
from transformers import Trainer
import bitsandbytes as bnb
base_model = ...
config = LoraConfig(...)
model = get_peft_model(base_model, config)
optimizer = create_loraplus_optimizer(
model=model,
optimizer_cls=bnb.optim.Adam8bit,
lr=5e-5,
loraplus_lr_ratio=16,
)
scheduler = None
...
trainer = Trainer(
...,
optimizers=(optimizer, scheduler),
)
与 LoRA 一同高效训练词元
有时,不仅需要改变某些层的权重,还需要添加新的词元。对于较大的模型,这可能是一项耗费内存的工作。PEFT LoRA 适配器支持 `trainable_token_indices` 参数,允许在微调特定层的同时调整其他词元。此方法仅训练您指定的词元,而所有其他词元保持不变。这节省了内存,并且与训练整个嵌入矩阵相比,不会丢弃现有词元嵌入中已学习的上下文。在底层,此方法使用 TrainableTokensModel 的层。
# for layer 'embed_tokens'
config = LoraConfig(trainable_token_indices=[idx_1, idx_2, ...], ...)
# specific embedding layer
config = LoraConfig(trainable_token_indices={'emb_tokens': [idx_1, idx_2, ...]}, ...)
在下面的代码片段中,我们展示了如何向模型添加新词元,以及如何将其与模型中的其他层一起训练。
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import get_peft_model, LoraConfig
base_model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1")
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1")
# we define our new tokens and add them to the tokenizer as special tokens
special_tokens = ['<|start_think|>', '<|stop_think|>']
tokenizer.add_special_tokens({'additional_special_tokens': special_tokens})
# make room for new tokens in the embedding matrix if it isn't big enough already
base_model.resize_token_embeddings(max(len(tokenizer), base_model.model.embed_tokens.num_embeddings))
# typical LoRA config with `trainable_token_indices` targeting embedding layer `embed_tokens`
# and specifically our new tokens we just added
lora_config = LoraConfig(
target_modules='all-linear',
trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(special_tokens)},
)
peft_model = get_peft_model(base_model, lora_config)
# proceed to train the model like normal
[...]
词元权重是适配器状态字典的一部分,并与 LoRA 权重一起保存。如果我们使用 `modules_to_save=['embed_tokens']` 进行全微调,我们会在检查点中存储完整的嵌入矩阵,导致文件大得多。
为了大致了解可以节省多少 VRAM,我们对上述示例进行了初步比较,比较了完全训练嵌入矩阵(`modules_to_save=["embed_tokens"]`)、为嵌入矩阵使用 LoRA(`target_modules=[..., "embed_tokens"]`,秩为 32)和可训练词元(`trainable_token_indices=[...]`,6 个词元)。可训练词元使用的 VRAM 与 LoRA 大致相同(15,562MB vs. 15,581MB),同时针对特定词元,并且比完全训练嵌入矩阵节省了约 1GB 的 VRAM。
将 LoRA 权重合并到基础模型中
虽然 LoRA 的训练规模更小、速度更快,但在推理过程中可能会因分别加载基础模型和 LoRA 适配器而遇到延迟问题。为了消除延迟,请使用 merge_and_unload() 函数将适配器权重与基础模型合并。这使您可以将新合并的模型用作独立模型。merge_and_unload() 函数不会将适配器权重保留在内存中。
下图解释了 LoRA 适配器合并的直观原理

我们在下面的代码片段中展示了如何使用 PEFT 运行该操作。
from transformers import AutoModelForCausalLM
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1")
peft_model_id = "alignment-handbook/zephyr-7b-sft-lora"
model = PeftModel.from_pretrained(base_model, peft_model_id)
model.merge_and_unload()
如果您需要保留权重的副本,以便以后可以取消合并适配器或删除并加载不同的适配器,您应该改用 merge_adapter() 函数。现在您可以选择使用 unmerge_adapter() 来返回基础模型。
from transformers import AutoModelForCausalLM
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1")
peft_model_id = "alignment-handbook/zephyr-7b-sft-lora"
model = PeftModel.from_pretrained(base_model, peft_model_id)
model.merge_adapter()
# unmerge the LoRA layers from the base model
model.unmerge_adapter()
add_weighted_adapter() 函数可用于根据用户在 `weights` 参数中提供的加权方案将多个 LoRA 合并到一个新适配器中。下面是一个端到端的示例。
首先加载基础模型
from transformers import AutoModelForCausalLM
from peft import PeftModel
import torch
base_model = AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-v0.1", torch_dtype=torch.float16, device_map="auto"
)
然后我们加载第一个适配器
peft_model_id = "alignment-handbook/zephyr-7b-sft-lora"
model = PeftModel.from_pretrained(base_model, peft_model_id, adapter_name="sft")
然后加载一个不同的适配器并将其与第一个合并
weighted_adapter_name = "sft-dpo"
model.load_adapter("alignment-handbook/zephyr-7b-dpo-lora", adapter_name="dpo")
model.add_weighted_adapter(
adapters=["sft", "dpo"],
weights=[0.7, 0.3],
adapter_name=weighted_adapter_name,
combination_type="linear"
)
model.set_adapter(weighted_adapter_name)
对于 `combination_type`,有几种受支持的方法。请参阅文档了解更多详情。请注意,当使用 `torch.float16` 或 `torch.bfloat16` 作为数据类型时,不支持使用“svd”作为 `combination_type`。
现在,执行推理
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1")
prompt = "Hey, are you conscious? Can you talk to me?"
inputs = tokenizer(prompt, return_tensors="pt")
inputs = {k: v.to("cuda") for k, v in inputs.items()}
with torch.no_grad():
generate_ids = model.generate(**inputs, max_length=30)
outputs = tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
print(outputs)
加载适配器
可以使用 load_adapter() 将适配器加载到预训练模型上,这对于尝试不同的适配器(其权重未合并)非常有用。使用 set_adapter() 函数设置活动适配器权重。
from transformers import AutoModelForCausalLM
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1")
peft_model_id = "alignment-handbook/zephyr-7b-sft-lora"
model = PeftModel.from_pretrained(base_model, peft_model_id)
# load different adapter
model.load_adapter("alignment-handbook/zephyr-7b-dpo-lora", adapter_name="dpo")
# set adapter as active
model.set_adapter("dpo")
要返回基础模型,您可以使用 unload() 来卸载所有 LoRA 模块,或使用 delete_adapter() 来完全删除适配器。
# unload adapter
model.unload()
# delete adapter
model.delete_adapter("dpo")
在同一批次中使用不同的 LoRA 适配器进行推理
通常,PEFT 中的每个推理批次都必须使用相同的适配器。这有时会很烦人,因为我们可能会有包含旨在与不同 LoRA 适配器一起使用的样本的批次。例如,我们可能有一个在英语上表现良好的基础模型,以及另外两个 LoRA 适配器,一个用于法语,一个用于德语。通常,我们必须分割我们的批次,以便每个批次只包含一种语言的样本,我们不能在同一批次中组合不同的语言。
幸运的是,可以使用 `adapter_name` 参数在同一批次中混合不同的 LoRA 适配器。下面,我们展示一个实际操作的例子。首先,让我们像这样加载基础模型(英语)和两个适配器(法语和德语):
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
model_id = ...
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
# load the LoRA adapter for French
peft_model = PeftModel.from_pretrained(model, <path>, adapter_name="adapter_fr")
# next, load the LoRA adapter for German
peft_model.load_adapter(<path>, adapter_name="adapter_de")
现在,我们想在一个包含所有三种语言的样本上生成文本:前三个样本是英语,接下来的三个是法语,最后三个是德语。我们可以使用 `adapter_names` 参数来指定每个样本使用哪个适配器。由于我们的基础模型用于英语,我们为这些样本使用特殊字符串 `"__base__"`。对于接下来的三个样本,我们指定法语 LoRA 微调的适配器名称,在本例中是 `"adapter_fr"`。对于最后三个样本,我们指定德语 LoRA 微调的适配器名称,在本例中是 `"adapter_de"`。这样,我们就可以在一个批次中使用基础模型和两个适配器。
inputs = tokenizer(
[
"Hello, my dog is cute",
"Hello, my cat is awesome",
"Hello, my fish is great",
"Salut, mon chien est mignon",
"Salut, mon chat est génial",
"Salut, mon poisson est super",
"Hallo, mein Hund ist süß",
"Hallo, meine Katze ist toll",
"Hallo, mein Fisch ist großartig",
],
return_tensors="pt",
padding=True,
)
adapter_names = [
"__base__", "__base__", "__base__",
"adapter_fr", "adapter_fr", "adapter_fr",
"adapter_de", "adapter_de", "adapter_de",
]
output = peft_model.generate(**inputs, adapter_names=adapter_names, max_new_tokens=20)
请注意,这里的顺序无关紧要,即批次中的样本不需要像上面的例子那样按适配器分组。我们只需要确保 `adapter_names` 参数与样本正确对齐即可。
此外,同样的方法也适用于 `modules_to_save` 功能,它允许保存和重用特定的神经网络层,例如用于分类任务的自定义头,跨不同的 LoRA 适配器。
注意事项
使用此功能有一些缺点,即:
- 它只适用于推理,不适用于训练。
- 使用 `with model.disable_adapter()` 上下文禁用适配器优先于 `adapter_names`。
- 当某些适配器权重已使用 `merge_adapter` 方法与基础权重合并时,不能传递 `adapter_names`。请先调用 `model.unmerge_adapter()` 取消所有适配器的合并。
- 出于显而易见的原因,这不能在调用 `merge_and_unload()` 之后使用,因为在这种情况下,所有 LoRA 适配器都将合并到基础权重中。
- 此功能目前不适用于 DoRA,因此如果您想使用它,请在 `LoraConfig` 中设置 `use_dora=False`。
- `modules_to_save` 功能目前仅支持类型为 `Linear`、`Embedding`、`Conv2d` 和 `Conv1d` 的层。
- 使用 `adapter_names` 进行推理预计会有开销,尤其是在批次中不同适配器的数量很高时。这是因为批次大小实际上减少为每个适配器的样本数。如果运行时性能是您的首要任务,请尝试以下方法: