PEFT documentation

LoRA

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.21.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

LoRA

LoRA 是最受欢迎的 PEFT 方法之一,也是刚接触 PEFT 时的良好起点。它最初是为大型语言模型开发的,但由于其高效性和有效性,已成为扩散模型中极为流行的训练方法。

低秩适应(LoRA)是一种 PEFT 方法,它将一个大矩阵分解为两个较小的低秩矩阵。这大幅减少了需要微调的参数数量。

该论文的摘要如下:

自然语言处理的一个重要范式是在通用领域数据上进行大规模预训练,然后适配到特定任务或领域。随着我们预训练更大规模的模型,需要重新训练所有模型参数的全量微调变得越来越不可行。以 GPT-3 175B 为例——部署独立的微调模型实例,每个实例都包含 175B 参数,其成本高昂到难以承受。我们提出了低秩适配(Low-Rank Adaptation,简称 LoRA),该方法冻结预训练模型的权重,并在 Transformer 架构的每一层中注入可训练的秩分解矩阵,从而大幅减少下游任务的可训练参数数量。与使用 Adam 进行微调的 GPT-3 175B 相比,LoRA 可以将可训练参数数量减少 10,000 倍,并将 GPU 内存需求减少 3 倍。尽管 LoRA 的可训练参数更少、训练吞吐量更高,且与适配器(adapters)不同,它不会带来额外的推理延迟,但在 RoBERTa、DeBERTa、GPT-2 和 GPT-3 上,LoRA 的模型质量与全量微调相当甚至更优。我们还对语言模型适配中的秩亏现象进行了实证研究,这有助于阐明 LoRA 的有效性。我们发布了一个软件包,以便于将 LoRA 集成到 PyTorch 模型中,并在 this https URL 提供了我们在 RoBERTa、DeBERTa 和 GPT-2 上的实现代码和模型检查点。

LoRA 表示权重更新 ΔW\Delta W 通过两个较小的矩阵(称为 更新矩阵) )进行低秩分解。这些新矩阵可以被训练以适应新数据,同时保持整体参数量较低。原始权重矩阵保持冻结,不再接收任何更新。为了生成最终结果,原始权重和额外的适配权重会被组合在一起。你也可以将适配器权重与基础模型合并,以消除推理延迟。

这种方法具有许多优势:

  • LoRA 通过大幅减少可训练参数的数量,使微调更加高效。
  • 原始预训练权重保持冻结,这意味着您可以基于这些权重构建多个轻量级且可移植的 LoRA 模型,用于不同的下游任务。
  • LoRA 与其他参数高效方法正交,可与其中许多方法结合使用。
  • 使用 LoRA 微调的模型性能与完全微调的模型性能相当。

原则上,LoRA 可以应用于神经网络中任意一组权重矩阵,以减少可训练参数的数量。然而,为了简化并进一步提高参数效率,LoRA 通常仅应用于 Transformer 模型中的注意力模块——也可以考虑将其应用于其他层。LoRA 模型中可训练参数的数量取决于更新矩阵的大小,而该大小主要由秩 r 和原始权重矩阵的形状决定。

您可以针对不同的使用场景来初始化低秩矩阵——例如任务感知(CorDA、EVA、Astra)、更快的收敛(PiSSA)、缓解量化影响(LoftQ)——这里仅列举了部分场景。有关不同初始化方法的详细信息,请参阅下文。默认的初始化方式使 LoRA 成为一个无操作(no-op),以便在不显著干扰现有模型的情况下,逐步学习新的行为。

用法

低秩更新矩阵的大小由 rank 或 r 决定。秩越高,模型需要训练的参数就越多,但同时也意味着模型具有更强的学习能力。在下面的示例中,您将针对注意力块中的 query 和 value 矩阵。其他需要设置的重要参数包括 lora_alpha(缩放因子)、bias(是否训练 none、all 或仅 LoRA 偏置参数)以及 modules_to_save(除 LoRA 层外需要训练并保存的模块)。所有这些参数——以及更多参数——都可以在 LoraConfig 中找到。

from peft import LoraConfig, get_peft_model

config = LoraConfig(
    r=16,
    lora_alpha=16,
    target_modules=["query", "value"],
    lora_dropout=0.1,
    bias="none",
    modules_to_save=["classifier"],
)
model = get_peft_model(model, config)
model.print_healthcheck()

基准测试概览

初始化

LoRA 权重的初始化由 LoraConfig 中的参数 init_lora_weights 控制。默认情况下,PEFT 使用 Kaiming-uniform 初始化权重 A,并使用零初始化权重 B,从而实现恒等变换(与参考实现相同)。

对于 transformers.Conv1D 层(例如 GPT-2 中的投影层),请使用 fan_in_fan_out=True,因为其权重以 (in_features, out_features) 的形式存储,而非 nn.Linear 的布局。支持的基于权重的初始化器在分解时使用 Linear 布局,并在更新基础权重时恢复存储布局。LoRA-GA 的 "stable" 缩放也使用输出维度,无论存储布局如何。

也可以传入 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, ...)
NoRA
PiSSA
MiCA
CorDA
Astra
OLoRA
EVA
LoftQ
rsLoRA
LoRA-GA

归一化低秩适应(NoRA)对 LoRA 降维投影矩阵进行归一化。PEFT 仅在初始化阶段通过 LoraConfig 中的 init_lora_weights="nora" 应用此归一化:它会对随机初始化的 LoRA A 权重进行列归一化,并将 LoRA B 权重初始化为零。这两个矩阵均可训练,基础权重保持不变。

对于线性层,适配器权重更新为 $\Delta W = sBA$,其中 AA 的形状为 (r, in_features), BB 的形状为 (out_features, r), , ss 是 LoRA 缩放因子。NoRA 从 LoRA 线性层创建的随机 A 权重开始,并在秩维度上对每一列进行归一化: A:,j←A:,jmax⁡(∥A:,j∥2,10−6),B←0. A_{:,j} \leftarrow \frac{A_{:,j}}{\max(\lVert A_{:,j} \rVert_2, 10^{-6})}, \qquad B \leftarrow 0.

由于 B 为零,训练前适配器的权重更新为零。当 bias="none" 且 lora_bias=False 时,适配器最初会保持基础模型的输出不变。NoRA 不需要校准数据集或权重分解。

对于 NoRA,推荐的设置是 lora_alpha 等于 r(即 alpha/rank 比例为 1:1),例如 r=8, lora_alpha=8。在默认的 use_rslora=False 下,缩放因子保持为 1;启用 rank-stabilized LoRA 后,该值会变为 lora_alpha / sqrt(r)。

以下示例将 NoRA 应用于一个小型 PyTorch 模型中的两个线性层:

from torch import nn

from peft import LoraConfig, get_peft_model

model = nn.Sequential(
    nn.Linear(32, 32),
    nn.ReLU(),
    nn.Linear(32, 16),
)

config = LoraConfig(
    r=8,
    lora_alpha=8,
    target_modules=["0", "2"],
    init_lora_weights="nora",
    bias="none",
    lora_bias=False,
    use_rslora=False,
)
model = get_peft_model(model, config)
model.print_healthcheck()

对于 Transformers 模型,请将 target_modules 设置为您想要适配的线性层的名称,例如当模型中存在这些名称时,设置为 ["q_proj", "v_proj"]。

NoRA 使用现有的 LoRA 配置、训练、保存和加载 API,因此没有单独的 NoraConfig 或 NoraModel 类。有关共享的配置和模型 API,请参阅 LoraConfig 和 LoraModel。

NoRA 支持 torch.nn.Linear 目标模块,或直接通过 target_parameters 传入的 nn.Parameter。它不支持嵌入层、卷积层等。

训练

本节将展示如何处理更复杂的训练场景,而不仅仅是向模型应用低秩适配器并输入数据。

QLoRA 风格的训练

PEFT 中的默认 LoRA 设置会为每个注意力块的查询(query)和值(value)层添加可训练权重。但 QLoRA 会为 Transformer 模型的所有线性层添加可训练权重,其性能可与完全微调的模型相媲美。若要像 QLoRA 那样将 LoRA 应用于所有线性层,请设置 target_modules="all-linear"(这比按名称指定各个模块更简单,因为模块名称可能因架构而异)。

config = LoraConfig(target_modules="all-linear", ...)

有关如何将量化应用于 PEFT 适配器的更多信息,请参阅 量化指南。

使用 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 是一个使用该方法在 Mistral-7B 扩展至 10B 后训练得到的模型示例。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。如果不确定,不要尝试使用复杂的正则表达式——只需为每个具有不同 rank/alpha 的模块传入完整名称,并在前面加上^前缀即可。

自动检测可行的目标模块

peft.helpers.KappaTuneSelector 实现了源自 KappaTune 论文 的基于条件数的目标选择策略。它会扫描每个 nn.Linear 模块,对于将 MoE 专家权重存储为融合 3D nn.Parameter 张量的模型(例如 Llama-4、Qwen3-MoE),也会扫描这些参数,计算每个矩阵的条件数 κ = σ_max / σ_min,并选择各向同性最强的层(κ 值最低)。这些各向同性层是微调的理想候选,因为它们的高熵特性使其能够更轻松地吸收新信息,同时保持专门化的各向异性层不变,从而缓解持续学习过程中的灾难性遗忘。

使用 peft.helpers.find_kappa_target_modules() 作为单行代码,获取 LoraConfig 的最佳 target_modules:

from peft import LoraConfig, get_peft_model
from peft.helpers import find_kappa_target_modules

model = AutoModelForCausalLM.from_pretrained("mistralai/Mixtral-8x7B-Instruct-v0.1")

targets = find_kappa_target_modules(model, top_p=0.2)
config = LoraConfig(
    target_modules=targets["target_modules"],
    target_parameters=targets["target_parameters"] if stable_modules_dic["target_parameters"] else None,
    r=64,
    lora_alpha=32,
    task_type="CAUSAL_LM",
)
peft_model = get_peft_model(model, config)

完整示例请参见此处。

直接针对 nn.Parameter

通常,您应使用 target_modules 来指定目标模块(例如 nn.Linear)。然而,在某些情况下,这并不可行。例如,在 HF Transformers 的许多混合专家(MoE)层中,使用的不是 nn.Linear,而是 nn.Parameter。PEFT 通常会对 LoRA 重写 forward 方法,但对于 nn.Parameter,则不存在该方法。因此,要对该参数应用 LoRA,需要通过 target_parameters 将其作为目标。以 Llama4 为例,您可以传入:target_parameters=['feed_forward.experts.gate_up_proj', 'feed_forward.experts.down_proj]。

请注意,当针对专家参数时,PEFT 可能会带来显著的运行时开销。原因在于,即使只需要少量专家,PEFT 也始终会为每个专家实例化 LoRA 贡献。在训练期间,这一点不太重要,因为在序列处理过程中,通常会有很大一部分专家至少被激活一次。然而,在推理期间,通常会使用 KV 缓存,因此我们只需要计算最后一个 token,这意味着只有少量专家会被激活。因此,在 MoE 层上使用 LoRA 可能会导致推理时出现显著的性能下降。因此,建议合并权重(model.merge_adapter() 或 model = model.merge_and_unload())。这样可以消除 PEFT 的开销。

有关此问题的更详细调查,请参阅此MoE 优化的拉取请求。

注意事项

  • 目前,此参数可用于指定二维或三维的 nn.Parameter。对于三维参数,假定其第 0 维是专家维度。
  • 支持通过 model.add_adapter 或 model.load_adapter 添加的多个使用 target_parameters 的 LoRA 适配器,但它们必须针对同一组参数。

MoE 专家参数与 vLLM

Transformers 中的一些 MoE 模型将专家权重存储为 nn.Parameter 张量(通常为 3D),而不是 nn.Linear 模块。 要对这些专家应用 LoRA,请使用 target_parameters 并通过 rank_pattern 设置每层的秩:

num_experts = getattr(model.config, "num_local_experts", None) or model.config.num_experts
effective_r = max(1, r // num_experts)
config = LoraConfig(
    r=r,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    target_parameters=[
        # Mixtral / Qwen3-MoE / GPT-OSS
        "mlp.experts.gate_up_proj",
        "mlp.experts.down_proj",
        # Llama4
        # "feed_forward.experts.gate_up_proj",
        # "feed_forward.experts.down_proj",
    ],
    rank_pattern={
        "experts.gate_up_proj": effective_r,
        "experts.down_proj": effective_r,
    },
)

这使得 LoRA 的总参数预算与稠密层相似(参见 Schulman 等人撰写的 LoRA Without Regret)。非专家模块使用默认秩 r。

例如,可以使用 vLLM 对微调后的模型进行加速推理,该工具自 v0.11.2 版本起支持融合 MoE 专家层。

高效地训练令牌与 LoRA

PEFT LoRA 适配器支持通过 trainable_token_indices 参数添加新 token。这使得在微调特定层的同时,也能对其他 token 进行调优。只有指定的 token 会被训练,所有其他 token 保持不变。与训练整个嵌入矩阵相比,该方法更节省内存,且不会丢弃现有 token 嵌入中已学习到的上下文。在底层,此方法使用了 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
[...]

Token 权重作为适配器状态字典的一部分,与 LoRA 权重一起保存。如果进行全量微调并保存嵌入矩阵,则存储的文件会大得多。

为了大致说明可以节省多少 VRAM,我们对上述示例进行了一项粗略的比较:完全训练嵌入矩阵(modules_to_save=["embed_tokens"])、对嵌入矩阵使用 LoRA(target_modules=[..., "embed_tokens"],秩为 32)以及可训练 token(trainable_token_indices=[...],6 个 token):

可训练 TokenLoRA全量微调
VRAM15,562 MB15,581MB~16,500MB
影响6 个 token所有标记所有标记

权重绑定

许多因果语言模型使用权重绑定,即两个或多个权重共享相同的底层参数。最常见的情况是,输入嵌入权重(embed_tokens)和输出投影权重(lm_head)共享同一个张量。这样做可以减少参数量,同时通常能保持模型质量。

PEFT 在处理这些被绑定权重(tied weights)时,尤其是在它们被选为微调目标的情况下,其行为并不总是显而易见的。对于 LoRA,LoraConfig 中的 ensure_weight_tying 参数控制着 PEFT 是否应显式地保持这些层中适配器侧更新的绑定状态。在实践中,这可能会影响 modules_to_save、target_modules 以及 trainable_token_indices。请注意,该逻辑在层命名方面部分依赖于约定("embed_tokens"、"lm_head"),如果未遵循这些约定,则无法保证正常工作。

下表总结了预期行为。

modules_to_save

基础模型权重已绑定ensure_weight_tyingLoraConfig 形状行为
NoFalsemodules_to_save=["embed_tokens"] 或 ["lm_head"]仅在选定的层上添加 ModulesToSaveWrapper
NoTruemodules_to_save=["embed_tokens"] 或 ["lm_head"]先发出警告,然后仅在选定的层上添加 ModulesToSaveWrapper
是的Falsemodules_to_save=["embed_tokens"] 或 ["lm_head"]视为独立
是的Truemodules_to_save=["embed_tokens"] 或 ["lm_head"]包装绑定层并保持包装器绑定
NoFalsemodules_to_save=["embed_tokens", "lm_head"]视为独立
NoTruemodules_to_save=["embed_tokens", "lm_head"]先发出警告,然后视为独立
是的Falsemodules_to_save=["embed_tokens", "lm_head"]先发出警告,然后视为独立
是的Truemodules_to_save=["embed_tokens", "lm_head"]保持 ModulesToSaveWrapper 的绑定

target_modules

基础模型权重已绑定ensure_weight_tyingLoraConfig 形状行为
NoFalsetarget_modules=["embed_tokens"] 或 ["lm_head"]仅在选定的层上添加 LoRA
NoTruetarget_modules=["embed_tokens"] 或 ["lm_head"]发出警告,然后仅在选定的层上添加 LoRA
是的Falsetarget_modules=["embed_tokens"] 或 ["lm_head"]视为独立
是的Truetarget_modules=["embed_tokens"] 或 ["lm_head"]保持 LoRA 适配器绑定
NoFalsetarget_modules=["embed_tokens", "lm_head"]视为独立
NoTruetarget_modules=["embed_tokens", "lm_head"]先发出警告,然后视为独立
是的Falsetarget_modules=["embed_tokens", "lm_head"]先发出警告,然后视为独立
是的Truetarget_modules=["embed_tokens", "lm_head"]保持 LoRA 适配器绑定

trainable_token_indices

对于可训练 token,我们面临一个额外的复杂情况:即使 LM head 和 embeddings 是绑定的,作为用户,我可能希望对它们微调不同的 token。因此,在下方的示例表中,我们区分了微调相同 token 和微调不同 token 这两种情况。

基础模型权重已绑定ensure_weight_tyingLoraConfig 形状行为
NoFalsetrainable_token_indices=[1, 2, 3]仅嵌入层可训练
NoTruetrainable_token_indices=[1, 2, 3]警告,然后仅对嵌入中的可训练 token
是的Falsetrainable_token_indices=[1, 2, 3]绑定的可训练标记
是的Truetrainable_token_indices=[1, 2, 3]绑定的可训练标记
NoFalsetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [1, 2]}视为独立
NoTruetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [1, 2]}先发出警告,然后视为独立
是的Falsetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [1, 2]}绑定的可训练标记
是的Truetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [1, 2]}绑定的可训练标记
NoFalsetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [3, 4]}视为独立
NoTruetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [3, 4]}先发出警告,然后视为独立
是的Falsetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [3, 4]}视为独立
是的Truetrainable_token_indices={"lm_head": [1, 2], "embed_tokens": [3, 4]}错误

对于用户而言,这意味着:

  • 通常,如果您希望微调相互绑定的权重并保持其绑定状态,请传入 ensure_weight_tying=True。
  • 如果您的基础模型的权重未绑定,ensure_weight_tying=True 无法强制绑定,只会发出警告。
  • 对于 trainable_token_indices,当 ensure_weight_tying=True 时,绑定层必须使用相同的 token 索引。

优化器

LoRA 训练可选择性地包含专用优化器。目前,PEFT 支持 LoRA-FA、LoRA+ 以及黎曼预条件 LoRA。

LoRA-FA 优化器

如 LoRA-FA 中所述,使用 LoRA-FA 进行 LoRA 训练可以更加有效和高效。LoRA-FA 通过固定矩阵 A 并仅调整矩阵 B 来降低激活内存消耗。在训练过程中,B 的梯度被优化以近似全参数微调的梯度。此外,LoRA-FA 的内存消耗对秩不敏感(因为它擦除了 $A$ 的激活),因此可以通过增大 lora rank 来提升性能,而不会增加内存消耗。

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

LoRA 训练可以通过黎曼预条件器(Riemannian preconditioner)进行改进,如 Riemannian Preconditioned LoRA. 在每个优化器步骤中,LoRA 矩阵 AA 和 BB 的梯度会乘以一个 r×rr \times r 预条件器,该预条件器将欧几里得梯度重新缩放至低秩矩阵流形上的黎曼(缩放梯度)方向,从而更好地调节更新并可能改善收敛性。由于预条件器的尺寸为 $r \times r$,其在 LoRA 秩上的内存和运行时开销很小。 create_riemannian_optimizer 封装任意基础优化器类(例如 torch.optim.AdamW 或 torch.optim.SGD): 非 LoRA 参数由基础优化器按原样更新,只有 nn.Linear-形状的 LoRA 层(lora_A / lora_B) 会应用预条件处理。

from peft import LoraConfig, get_peft_model
from peft.optimizers import create_riemannian_optimizer
from transformers import Trainer, get_cosine_schedule_with_warmup
import torch

base_model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")

config = LoraConfig(...)
model = get_peft_model(base_model, config)

optimizer = create_riemannian_optimizer(
    model=model,
    optimizer_cls=torch.optim.AdamW,
    lr=5e-5,
    reg=1e-2,
)

scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=100,
    num_training_steps=1000,
)

trainer = Trainer(
    ...,
    optimizers=(optimizer, scheduler),
)

reg 是添加到 r×rr \times r 矩阵对角线上的阻尼项,用于在求逆之前稳定预条件子,特别是在 LoRA 因子(接近)秩亏时。

训练后

本节展示了针对已训练适配器的潜在后处理方法。

将 LoRA 权重合并到基础模型

While LoRA is significantly smaller and faster to train, you may encounter latency issues during inference due to separately loading the base model and the LoRA adapter. To eliminate latency, use the merge_and_unload() function to merge the adapter weights with the base model. This allows you to use the newly merged model as a standalone model. The merge_and_unload() function doesn’t keep the adapter weights in memory.

下图解释了 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 = model.merge_and_unload()

将返回的模型赋值给一个变量并使用它非常重要,因为 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()

在量化模型上,取消合并并非精确操作:合并过程会对基础权重进行重新量化,而反复执行合并与取消合并循环可能会累积舍入误差。请参阅 将适配器合并到量化权重中。

如果同时激活了多个适配器,但只合并了其中一部分,则未合并的适配器会被静默地忽略,不会生效。此行为将在 PEFT v1.0 中更改。请参阅 仅合并部分激活的适配器。

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", 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 时,不支持将 combination_type 设置为 “svd”。

现在,执行推理:

device = torch.accelerator.current_accelerator().type if hasattr(torch, "accelerator") else "cuda"

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(device) 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)

通过入侵者维度恢复基础模型性能

论文 LoRA vs Full Fine-tuning: An Illusion of Equivalence 指出,LoRA 训练会向权重中引入额外的维度,这些维度与已学习的权重几乎没有共同点,并会导致已学习信息的遗忘。PEFT 在 peft.tuners.lora.intruders.reduce_intruder_dimension() 中实现了所建议的缓解措施。

该缓解措施将针对已加载 LoRA 的 PEFT 模型,创建一个经过修改的新适配器,该适配器将与现有适配器一同加载,并成为当前激活的适配器。

用法示例:

from peft.tuners.lora.intruders import reduce_intruder_dimension

peft_model = AutoPeftModelForCausalLM.from_pretrained('hubnemo/llama-3.2b-metamathqa-lora64')

reduce_intruder_dimension(
    peft_model,
    mitigation_lambda=0.75,
)

peft_model.generate(...)

有几个超参数可用于调节缓解措施的有效性,但正如论文中的图 8 所示,适配器所学到的任务准确率与基础模型知识的遗忘之间始终存在权衡。该缓解措施会从适配器中移除信息,以降低对先前知识遗忘的影响,但这同时也意味着适配器所学习到的部分任务信息也会丢失。

虽然默认设置旨在在这两个因素之间取得良好的平衡,但并不能保证这些默认值适用于您的适配器、模型和数据,因此建议准备好基准测试以衡量其影响。

加载适配器

可以使用 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() 不是原地操作,请记住将返回的模型赋值给一个变量并使用它。

# unload adapter
model = model.unload()

# delete adapter
model.delete_adapter("dpo")

张量并行

LoRA 支持 Transformers 提供的 张量并行(TP)。当使用 tp_plan 加载基础模型时,PEFT 会自动检测每个目标模块的 TP 配置,并为 LoRA 适配器权重添加相应的钩子,使其能够正确参与张量并行计算。

LoRA 的 Tensor Parallelism 支持需要 transformers >= 5.4.0。 基于 DTensor 的 Tensor Parallelism API 需要 peft >= 0.21.1 和 transformers >= 5.17.0。

用法与标准的 LoRA 工作流完全相同——只需在通过 PEFT 包装之前,使用 tp_plan 加载基础模型即可:

from transformers import AutoModelForCausalLM
from peft import get_peft_model, LoraConfig

model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B", tp_plan="auto")
lora_config = LoraConfig(r=16, target_modules=["q_proj", "v_proj"])
model = get_peft_model(model, lora_config)

保存和加载操作与往常一样,通过 save_pretrained / from_pretrained 完成。PEFT 在保存前会将分片的适配器权重重新聚合为完整张量,因此检查点具有可移植性,且独立于训练期间使用的设备数量。

如果在未指定 TP 计划的情况下应用张量并行(例如使用 PyTorch 的 parallelize_module),当分片 nn.Linear 的权重设备网格中存在名为 "tp" 的维度时,PEFT 会将 LoRA 权重的大小设置为该本地分片的大小;否则(例如在使用 FSDP2 时),则设置为完整的层形状。PEFT 不会为这些 LoRA 权重添加 TP 钩子,也不会在保存时收集它们。如果您的 TP 网格维度名称不同,请在创建 PEFT 模型之前添加该维度:

from peft.utils.integrations import TpMeshDimRegistry

TpMeshDimRegistry.register("tp_dim")

推理

本节展示了在推理阶段使用 LoRA 时可以执行的操作,例如解耦适配器。

激活 LoRA(aLoRA)

激活 LoRA(aLoRA)是一种用于因果语言模型的低秩适配器架构,它复用现有基础模型的 KV 缓存以实现更高效的推理。这种方法最适合那些在大多数任务/生成中依赖基础模型,但使用 aLoRA 适配器在链中执行特定任务的推理流水线。例如,检查或纠正基础模型的生成输出。在这些场景中,推理速度可以提升一个数量级甚至更多。有关 aLoRA 的更多信息及许多示例用例,请参阅 aLoRA 论文。

该技术会扫描每个输入中调用序列(alora_invocation_tokens)的最后一次出现位置(该序列可以短至 1 个 token)。它会在从调用序列起始位置开始的 token 上激活适配器权重。调用序列之后的所有输入也会被适配,并且所有生成的 token 都将使用适配后的权重。先前 token 上的权重保持未适配状态,由于因果语言模型中的因果注意力掩码,这些 token 的缓存可以与基础模型的缓存互换使用。其用法与标准 LoRA 非常相似。关键区别在于,创建适配器时必须指定调用序列:

from peft import LoraConfig

config = LoraConfig(alora_invocation_tokens=alora_invocation_tokens, task_type="CAUSAL_LM", ...)

alora_invocation_tokens 是一个整数 token id 列表。给定所需的调用字符串,可以按以下方式获取:

invocation_string = "placeholder"
alora_invocation_tokens = tokenizer.encode(invocation_string, add_special_tokens=False).

分词器是基础模型的分词器。使用 add_special_tokens=False 以避免在搜索字符串中添加 SOS/EOS 标记(这很可能导致搜索失败)。

备注

  • 由于专注于缓存复用,aLoRA 仅支持 task_type=CAUSAL_LM 任务。
  • 由于权重是在更少的 token 上调整的,aLoRA 通常需要比 LoRA 更高的秩(r)(并非总是如此)。r=32 可以作为一个不错的起点。
  • 根据定义,aLoRA 权重无法合并到基础模型中,因为适配器权重仅选择性地应用于部分 token。尝试合并将会抛出错误。
  • 尚不支持 Beam search。
  • 通常不建议向分词器添加基础模型中不存在的新标记。这会使基础模型和适配器模型在重叠上下文上运行的目标用例变得复杂。你可以在训练适配器之前,先向基础模型添加可训练标记来规避此问题。

调用顺序的选择与 SFT 设计

您必须添加 alora_invocation_tokens 序列,因为它不会自动添加。我们建议在适配器特定的提示开始处尽早激活适配器权重,但要在任何长输入之后,以在不影响缓存复用的情况下最大化模型性能。与任何模型一样, 训练和测试之间的格式应保持一致。

考虑以下示例,其中基础模型具有聊天模板,目标是训练适配器以生成所需的输出。

  • 选项 1:如果不存在特定于任务的提示,即输入是带有 assistant 提示的聊天历史,那么聊天模板的 assistant 提示(例如 <|start_of_role|>assistant<|end_of_role|>)是调用字符串的自然选择。请查看模型的聊天模板以找到该模型的提示。
  • 选项 2:如果适配器有一个描述其正在学习的任务的特定任务提示,并且该提示作为 user 轮次直接放在生成之前,那么聊天模板的 user 提示(例如 <|start_of_role|>user<|end_of_role|>)是调用字符串的自然选择。

在确定调用字符串后,获取模型的 tokenizer,并将 alora_invocation_tokens 设置为

alora_invocation_tokens = tokenizer.encode(invocation_string, add_special_tokens=False).

一个推理配置示例位于alora finetuning。

如果为调用字符串使用自定义字符串,请确保字符串的开头和结尾是特殊标记,以避免在边界处出现标记化问题。

…

使用(并复用)生成缓存

aLoRA 的主要目的是在调用序列之前使基础模型与 aLoRA 适配器模型之间的 KV 缓存可以互换,因为基础模型和适配后的 KV 值并不兼容。具体而言,在一次模型生成过程中存储的键和值可用于后续生成,从而避免对上下文 token 执行昂贵的预填充操作。在基础模型与 aLoRA 适配器之间共享缓存时,主要有两种模式:

  1. 基础模型已生成内容,随后调用 aLoRA 适配器进行后续生成。例如,基础模型回答一个问题,而经过训练以检测幻觉的 aLoRA 会检查基础模型的响应。
  2. 某个 aLoRA 适配器已生成了一些内容,随后调用基础模型或另一个 aLoRA 适配器进行后续生成,且该后续生成与原始 aLoRA 的上下文存在部分重叠。例如,用户提供一个查询,aLoRA 将该查询改写为更自包含的形式,以改进 RAG 系统中的检索。然后,检索并加载文档到上下文中,aLoRA 检查这些文档是否与问题相关,最后由基础模型生成答案。

为了演示使用缓存时的上述行为,我们使用了来自 transformers 的 DynamicCache。请注意确保适配后的缓存值不与基础缓存值混合。特别是,当存在部分上下文重叠(模式 2)时,共享缓存需要额外的一步。

模式 1:基础模型后接 aLoRA 在这里,基础模型的整个输入和生成结果都会作为输入传递给 aLoRA 适配器,并附带调用序列:

from transformers import DynamicCache
...
cache = DynamicCache()
inputs_base = tokenizer(prompt_base, return_tensors="pt")
# Generate from base model and save cache
with model_alora.disable_adapter():
    output = model_alora.generate(inputs_base["input_ids"].to(device),attention_mask=inputs_base["attention_mask"].to(device),past_key_values = cache,return_dict_in_generate=True)
output_text_base = tokenizer.decode(output.sequences[0])
cache = output.past_key_values

# Generate with aLoRA adapter from cache
prompt_alora = output_text + INVOCATION_STRING
inputs_alora = tokenizer(prompt_alora, return_tensors="pt").to(device)
output = model_alora.generate(**inputs_alora, past_key_values=cache)
output_text_alora = tokenizer.decode(output[0])

# Note: cache is now tainted with adapter values and cannot be used in base model from here on!

模式 2:aLoRA 生成后接基础模型(或另一个 aLoRA),且部分上下文重叠 在此,我们使用基础模型对共享上下文进行预填充,然后进行生成。

from transformers import DynamicCache
import copy
...
cache = DynamicCache()
inputs_shared = tokenizer(prompt_shared, return_tensors="pt").to(device)

# Prefill from base model and save cache
with model_alora.disable_adapter():
    with torch.no_grad():
        model_alora(**inputs_shared, past_key_values=cache)
cache_copy = copy.deepcopy(cache)

# Generate from aLoRA using prefilled cache
prompt_alora = prompt_shared + INVOCATION_STRING
inputs_alora = tokenizer(prompt_alora, return_tensors="pt").to(device)
output = model_alora.generate(**inputs_alora, past_key_values=cache)
output_text_alora = tokenizer.decode(output[0])

# Generate from base model using saved cache not tainted by aLoRA KV values
prompt_base = prompt_shared
inputs_base = tokenizer(prompt_base, return_tensors="pt").to(device)
with model_alora.disable_adapter():
    output = model_alora.generate(**inputs_base, past_key_values=cache_copy)
output_text_base = tokenizer.decode(output[0])

在同一批次中使用不同的 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() 取消所有适配器的合并。请注意,在 adapter_names 之外,仅合并部分活动适配器也存在类似的注意事项。
  • 出于显而易见的原因,在调用 merge_and_unload() 之后无法使用此功能,因为在这种情况下,所有 LoRA 适配器都会被合并到基础权重中。
  • 此功能目前不支持 DoRA,因此如果您想使用它,请在 LoraConfig 中设置 use_dora=False。
  • modules_to_save 功能目前仅支持 Linear、Embedding、Conv2d 和 Conv1d 类型的层。
  • 使用 adapter_names, 进行推理时会产生预期的开销,尤其是当批次中不同适配器的数量较多时。这是因为批次大小实际上会缩减为每个适配器的样本数。如果运行时性能是您的首要考虑因素,请尝试以下方法:
    • 增加批处理大小。
    • 尽量避免在同一批次中包含大量不同的适配器,优先使用同构批次。可以通过缓冲使用相同适配器的样本,并仅使用少量不同适配器执行推理来实现这一点。
    • 请查看诸如 LoRAX、punica 或 S-LoRA 等替代实现,它们专门用于处理大量不同的适配器。

组合和复用 LoRA 适配器

Arrow

Arrow 是一种模块化路由算法,旨在组合多个预训练的任务特定 LoRA 适配器以解决给定任务,类似于 Polytropon,但无需进行微调。Arrow 并非简单地合并所有适配器,而是引入了一种无梯度、逐 token 的混合专家(MoE)路由机制。在推理阶段,它首先通过从每个 LoRA 的 SVD 分解中提取最大的右奇异向量,为每个 LoRA 计算一个原型。随后,通过余弦相似度将每个 token 表示与这些原型进行比较,以获得路由系数。Token 会被分配给最相关的 top-k 个 LoRA 适配器,其系数通过 softmax 进行归一化,并将它们的输出进行线性组合。这使得现有的 LoRA 模块能够被有效地复用于新任务,并带来更强的零样本泛化能力。

In PEFT, Arrow is enabled through [`ArrowConfig]andcreate_arrow_model. You can also configure parameters such astop_k(the number of LoRA adapters combined per token),router_temperature(the softmax temperature applied to the routing coefficients), andrng_seed` (for reproducibility).

from peft import create_arrow_model, ArrowConfig
from transformers import AutoModelForCausalLM

# Loading the model
base_model = AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct")

# Creating the Arrow config
arrow_config = ArrowConfig(
    top_k=3,
    router_temperature=1.0,
    rng_seed=42,
)

# The LoRA adapters below were trained on a clustered FLAN dataset.
# Task clustering was performed using the Model-Based Clustering (MBC) method,
# as described in the Arrow paper.
# While one could train a separate LoRA for each task and let Arrow route tokens among them,
# training LoRAs on clusters of tasks instead provides an indirect optimization for
# transfer across the multi-task dataset.
task_specific_adapter_paths = [
        f"TahaBa/phi3-mini-clustered-flan/ts_expert_{i}" for i in range(10)
    ]

# Creating the Arrow model
model = create_arrow_model(
        base_model=base_model,
        task_specific_adapter_paths=task_specific_adapter_paths,
        arrow_config=arrow_config,
    )

# Now the forward path could be called on this model, like a normal PeftModel.

此外,在调用 create_arrow_model 之后,你可以添加或移除适配器——例如,用于微调新的适配器或丢弃不必要的适配器。一旦适配器就位,你可以激活 "arrow_router" 以使用 Arrow 进行推理。请注意,如果在 create_arrow_model 之后添加了新的 LoRA 适配器并希望对其进行微调,你必须显式地将新适配器设置为活动状态,因为在 create_arrow_model 中默认激活的是 "arrow_router"。

from trl import SFTTrainer, SFTConfig

# Adding a new adapter and activating it
model.add_adapter(adapter_name='new_adapter')
model.set_adapter('new_adapter')

# Now the model could be trained along the `new_adapter`.
trainer = SFTTrainer(
        model=model,
        args=SFTConfig(...),
        ...
    )

# Once the training is done, you can activate `arrow_router` and use it in inference
model.set_adapter('arrow_router')    # Model is ready to be used at inference time now

GenKnowSub

GenKnowSub 通过在路由前对任务特定的 LoRA 适配器进行纯化来增强 Arrow。其核心思想是基于通过否定实现遗忘原则,减去 LoRA 空间中编码的通用知识,从而使任务适配器更加独立,并专注于与任务相关的信号。具体而言,GenKnowSub 从一组通用(非任务特定)LoRA 适配器中估计出一个低维的“通用”子空间,并在 Arrow 的逐 token 路由之前,从每个任务适配器的 LoRA 更新中移除该成分。这通常能提升组合性,并在组合多个任务适配器时减少干扰。

在 PEFT 中,通过在 ArrowConfig 中设置 use_gks=True,并在 create_arrow_model 中提供 general_adapter_paths 来启用 GenKnowSub:

from peft import create_arrow_model, ArrowConfig
from transformers import AutoModelForCausalLM

# Loading the model
base_model = AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct")

# Creating the Arrow config
arrow_config = ArrowConfig(
    top_k=3,
    router_temperature=1.0,
    use_gks=True,
    rng_seed=42,
)

# Path to task-specific, trained on flan clustered dataset (as we explained before.)
task_specific_adapter_paths = [
        f"TahaBa/phi3-mini-clustered-flan/ts_expert_{i}" for i in range(10)
    ]
# These general adapters are trained on English, German, and French Wikipedia dataset,
# with causal language modelling objective, each pair like: (507 token tsentence, 5 token completion), and the loss computed on the completion
general_adapter_paths = [
        "TahaBa/phi3-mini-general-adapters/cluster0_batch16_prop1.0_langen/checkpoint-17",
        "TahaBa/phi3-mini-general-adapters/cluster0_batch16_prop1.0_langfr/checkpoint-35",
        "TahaBa/phi3-mini-general-adapters/cluster0_batch16_prop1.0_langger/checkpoint-17"
    ]

# Creating the Arrow model
model = create_arrow_model(
        base_model=base_model,
        task_specific_adapter_paths=task_specific_adapter_paths,
        general_adapter_paths=general_adapter_paths,
        arrow_config=arrow_config,
    )

# Now the forward path could be called on this model, like a normal PeftModel.

为了编码通用知识,GenKnowSub 在路由开始之前,会从每个任务特定适配器中减去所提供的通用适配器的平均值,且仅执行一次。此外,在此情况下,仍然支持在调用 create_arrow_model 之后添加或移除适配器(如 Arrow 部分所述)。

使用 Arrow + GenKnowSub 时需要注意的事项:

  • 所有 LoRA 适配器(任务特定和通用)必须共享相同的 rank 和 target_modules。

  • 这些设置中的任何不一致都会在 create_arrow_model 中引发错误。

  • 支持在不同任务适配器中使用不同的缩放因子(lora_alpha)——Arrow 会自动处理它们。

  • 由于 "arrow_router" 具有动态路由行为,因此不支持对其进行合并。

  • 在 create_arrow_model 中,任务适配器以 task_i 的形式加载,通用适配器以 gks_j 的形式加载(其中 i 和 j 为索引)。该函数会确保 target_modules、rank 以及适配器是应用于 Linear 层还是 Linear4bit 层的一致性。随后,它会添加 "arrow_router" 模块并将其激活。若要自定义此过程,需要重写 create_arrow_model。

  • 此实现兼容 4 位量化(通过 bitsandbytes):

    from transformers import AutoModelForCausalLM, BitsAndBytesConfig
    import torch
    
    # Quantisation config
    bnb_config = BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_quant_type="nf4",
            bnb_4bit_compute_dtype=torch.bfloat16,
            bnb_4bit_use_double_quant=False,
        )
    
    # Loading the model
    base_model = AutoModelForCausalLM.from_pretrained(
        "microsoft/Phi-3-mini-4k-instruct",
        dtype=torch.bfloat16,
        device_map="auto",
        quantization_config=bnb_config,
    )
    
    # Now call create_arrow_model() as we explained before.

API

LoraConfig

类 peft.LoraConfig

< >

( task_type: Optional[Union[str, TaskType]] = Nonepeft_type: Optional[Union[str, PeftType]] = Noneauto_mapping: Optional[dict] = Nonepeft_version: Optional[str] = Nonebase_model_name_or_path: Optional[str] = Nonerevision: Optional[str] = Noneinference_mode: bool = Falser: int = 8target_modules: Optional[Union[list[str], str]] = Noneexclude_modules: Optional[Union[list[str], str]] = Nonelora_alpha: int = 8lora_dropout: float = 0.0fan_in_fan_out: bool = Falsebias: Literal['none', 'all', 'lora_only'] = 'none'use_rslora: bool = Falsemodules_to_save: Optional[list[str]] = Noneinit_lora_weights: bool | Literal['gaussian', 'eva', 'olora', 'pissa', 'pissa_niter_[number of iters]', 'corda', 'astra', 'loftq', 'orthogonal', 'mica', 'nora'] = Truelayers_to_transform: Optional[Union[list[int], int]] = Nonelayers_pattern: Optional[Union[list[str], str]] = Nonerank_pattern: Optional[dict] = <factory>alpha_pattern: Optional[dict] = <factory>megatron_config: Optional[dict] = Nonemegatron_core: Optional[str] = 'megatron.core'trainable_token_indices: Optional[Union[list[int], dict[str, list[int]]]] = Noneloftq_config: Union[LoftQConfig, dict] = <factory>eva_config: Optional[EvaConfig] = Nonecorda_config: Optional[CordaConfig] = Noneastra_config: Optional[AstraConfig] = Nonelora_ga_config: Optional[LoraGAConfig] = Noneuse_dora: bool = Falsevelora_config: Optional[Union[VeloraConfig, dict]] = Nonealora_invocation_tokens: Optional[list[int]] = Noneuse_qalora: bool = Falseqalora_group_size: int = 16monteclora_config: Optional[MontecloraConfig] = Nonelayer_replication: Optional[list[tuple[int, int]]] = Noneruntime_config: LoraRuntimeConfig = <factory>lora_bias: bool = Falsetarget_parameters: Optional[list[str]] = Noneuse_bdlora: Optional[BdLoraConfig] = Nonearrow_config: Optional[ArrowConfig] = Nonekasa_config: Optional[KasaConfig] = Noneensure_weight_tying: bool = False )

参数

  • r (int) — Lora 注意力维度(“秩”)。
  • target_modules (Optional[Union[List[str], str]]) — 要应用适配器的模块名称。如果指定了此参数,则只有名称匹配的模块会被替换。传入字符串时,将执行正则表达式匹配。传入字符串列表时,将执行精确匹配,或检查模块名称是否以列表中的任一字符串结尾。如果指定为 ‘all-linear’,则选择所有 linear/Conv1D 模块(如果模型是 PreTrainedModel,则排除输出层)。如果未指定,将根据模型架构选择模块。如果架构未知,则会引发错误——在这种情况下,应手动指定目标模块。为了避免针对任何模块(因为你想应用 target_parameters),请将 target_modules=[] 设置为空列表。
  • exclude_modules (Optional[Union[List[str], str]]) — 不应用适配器的模块名称。传入字符串时,将执行正则表达式匹配。传入字符串列表时,将执行精确匹配,或检查模块名称是否以传入的任一字符串结尾。
  • lora_alpha (int) — 用于 Lora 缩放的 alpha 参数。
  • lora_dropout (float) — Lora 层的 dropout 概率。
  • fan_in_fan_out (bool) — 如果待替换的层以 (fan_in, fan_out) 的形式存储权重,请将此项设置为 True。例如,gpt-2 使用的 Conv1D 以 (fan_in, fan_out) 的形式存储权重,因此应将其设置为 True。
  • bias (str) — LoRA 的偏置类型。可以是 ‘none’、‘all’ 或 ‘lora_only’。如果设置为 ‘all’ 或 ‘lora_only’,相应的偏置将在训练期间被更新。请注意,这意味着即使禁用了适配器,模型的输出也不会与未进行适配的基础模型相同。
  • use_rslora (bool) — 当设置为 True 时,使用 Rank-Stabilized LoRA,将适配器缩放因子设置为 lora_alpha/math.sqrt(r),因为已证明其效果更佳。否则,将使用 lora_alpha/r 的原始默认值。
  • modules_to_save (List[str]) — 除适配器层外,需要设置为可训练并在最终检查点中保存的模块列表。
  • init_lora_weights (bool | Literal["gaussian", "eva", "olora", "pissa", "pissa_niter_[number of iters]", "corda", "astra", "loftq", "orthogonal", "mica", "nora"]) — 如何初始化适配器层的权重。传入 True(默认值)将使用来自 Microsoft 参考实现的默认初始化方式,其中 LoRA B 权重被设置为 0。这意味着在未进行进一步训练的情况下,LoRA 适配器将不起作用(no-op)。将初始化设置为 False 会导致 LoRA A 和 B 进行随机初始化,这意味着在训练之前 LoRA 并非 no-op;此设置旨在用于调试目的。传入 'gaussian' 将执行按 LoRA 秩缩放的线性层和层的高斯初始化。传入 'loftq' 以使用 LoftQ 初始化。传入 'eva' 将执行 Explained Variance Adaptation. 的基于数据的初始化。EVA 基于层输入激活值的 SVD 来初始化 LoRA,并由于其能够适应微调数据而实现了 SOTA 性能。传入 'olora' 以使用 OLoRA 初始化。传入 'pissa' 将执行 https://huggingface.co/papers/2404.02948’ Principal Singular values and Singular vectors Adaptation (PiSSA) 的初始化,其收敛速度比 LoRA 更快,并最终实现更优越的性能。此外,与 QLoRA 相比,PiSSA 减少了量化误差,从而带来进一步的提升。传入 'pissa_niter_[number of iters]' 将启动基于 Fast-SVD 的 PiSSA 初始化,其中 [number of iters] 表示执行 FSVD 的子空间迭代次数,且必须为非负整数。当 [number of iters] 设置为 16 时,可以在几秒内完成 7B 模型的初始化,且训练效果与使用 SVD 大致相当。传入 'corda' 将执行 Context-Oriented Decomposition Adaptation 的初始化,在 Instruction-Previewed Mode 下其收敛速度比 PiSSA 更快,在 Knowledge-Preserved Mode 下比 LoRA 更好地保留了世界知识。传入 'astra' 将执行 Activation-Space Tail-Eigenvector Low-Rank Adaptation 的初始化,它将基础权重投影到从少量校准数据集中估计的输出激活值的特征空间上。传入 "orthogonal" 将导致 LoRA A 和 B 以正交方式初始化;在这方面它类似于 "olora",但基础权重保持不变(要求 r 为偶数,目前仅支持线性层)。传入 "mica" 将执行 Minor Component Adaptation (MiCA) 的初始化,它从基础权重中与最小奇异值相关的 r 个左奇异向量初始化 B,将 A 设置为零,并在训练期间冻结 B;仅更新 A。目前支持线性层和嵌入层。传入 "nora" 将执行 Normalized Low-Rank Adaptation (NoRA) 的初始化,它将随机初始化的 LoRA A 权重的每一列归一化为单位 L2 范数,并将 LoRA B 权重设置为零。归一化仅在初始化时应用;基础权重保持不变,且 A 和 B 均可训练。推荐的设置是 lora_alpha 等于 r 且 use_rslora=False,这保持了 1:1 的比例和 1 的缩放因子。目前支持线性层以及使用 target_parameters 指定的 nn.Parameters。

    Principal Singular values and Singular vectors Adaptation (PiSSA) 的收敛速度比 LoRA 更快,并最终取得更优的性能。此外,与 QLoRA 相比,PiSSA 降低了量化误差,从而带来进一步的提升。传入 'pissa_niter_[number of iters]' 可启动基于 Fast-SVD 的 PiSSA 初始化,其中 [number of iters] 表示执行 FSVD 的子空间迭代次数,且必须为非负整数。当 [number of iters] 设置为 16 时,可在数秒内完成 7B 模型的初始化,且训练效果与使用 SVD 大致相当。传入 'corda' 将初始化 Context-Oriented Decomposition Adaptation,在 Instruction-Previewed Mode 下其收敛速度比 PiSSA 更快,在 Knowledge-Preserved Mode 下比 LoRA 更好地保留了世界知识。传入 'astra' 将初始化 Activation-Space Tail-Eigenvector Low-Rank Adaptation,该方法将基础权重投影到由小型校准数据集估计的输出激活值的特征空间中。传入 "orthogonal" 将使 LoRA A 和 B 以正交方式初始化;在此方面,它与 "olora" 类似,但基础权重保持不变(要求 r 为偶数,目前仅支持线性层)。传入 "mica" 将初始化 Minor Component Adaptation (MiCA),该方法从基础权重中与最小奇异值相关的 r 个左奇异向量初始化 B,将 A 设为零,并在训练期间冻结 B;仅更新 A。目前支持线性层和嵌入层。传入 "nora" 将初始化 Normalized Low-Rank Adaptation (NoRA),该方法将随机初始化的 LoRA A 权重的每一列归一化为单位 L2 范数,并将 LoRA B 权重设为零。归一化仅在初始化时应用;基础权重保持不变,且 A 和 B 均可训练。推荐设置 lora_alpha 等于 r 且 use_rslora=False,以保持 1:1 的比例和 1 的缩放因子。目前支持线性层以及通过 target_parameters 指定的 nn.Parameter。

  • layers_to_transform (Union[List[int], int]) — 要转换的层索引。如果传入一个整数列表,则会将适配器应用于该列表中指定的层索引。如果传入单个整数,则会对该索引处的层应用转换。
  • layers_pattern (Optional[Union[List[str], str]]) — 层模式名称,仅在 layers_to_transform 不同于 None 时使用。它应指向模型的 nn.ModuleList,该列表通常命名为 'layers' 或 'h'。
  • rank_pattern (dict) — 从层名称或正则表达式到与 r 指定的默认 rank 不同的 rank 的映射。例如,{'^model.decoder.layers.0.encoder_attn.k_proj': 16}。
  • alpha_pattern (dict) — 从层名称或正则表达式到与lora_alpha指定的默认 alpha 不同的 alpha 值的映射。例如,{'^model.decoder.layers.0.encoder_attn.k_proj': 16}。
  • megatron_config (Optional[dict]) — Megatron 的 TransformerConfig 参数。它用于创建 LoRA 的并行线性层。您可以像这样获取它:core_transformer_config_from_args(get_args()),这两个函数均来自 Megatron。这些参数将用于初始化 Megatron 的 TransformerConfig。当您希望将 LoRA 应用于 megatron 的 ColumnParallelLinear 和 RowParallelLinear 层时,需要指定此参数。
  • megatron_core (Optional[str]) — 要使用的 Megatron 核心模块,默认为 "megatron.core"。
  • trainable_token_indices (Optional[Union[List[int], dict[str, List[int]]]]) — 允许您指定要选择性微调的 token 索引,而无需使用 peft.TrainableTokensModel 方法重新训练整个嵌入矩阵。您可以通过两种方式指定 token 索引。 您可以指定一个索引列表,该列表将针对模型的输入嵌入层(如果未找到,则为 embed_tokens)。或者,您可以指定一个字典,其中键是嵌入模块的名称,值是 token 索引列表,例如 {'embed_tokens': [0, 1, ...]}。请注意,使用 FSDP 进行训练时需要设置 use_orig_params=True,以避免非均匀 requires_grad 带来的问题。
  • loftq_config (Optional[LoftQConfig]) — LoftQ 的配置。如果此参数不为 None,则将使用 LoftQ 对骨干网络权重进行量化并初始化 Lora 层。同时请传入 init_lora_weights='loftq'。请注意,在这种情况下不应传入已量化的模型,因为 LoftQ 会自行对模型进行量化。LoftQ 仅应用于线性层,包括通过 target_parameters 指定的层。不支持嵌入层和卷积层,这些层将使用默认的 LoRA 初始化方式。nn.MultiheadAttention 层不受支持,传入时会引发错误。
  • eva_config (Optional[EvaConfig]) — EVA 的配置。至少需要设置 dataset 参数(使用与微调相同的 dataset)。
  • corda_config (Optional[CordaConfig]) — CorDA 的配置。如果此值不为 None,则将使用 CorDA 来构建适配器层。同时请传入 init_lora_weights='corda'。
  • astra_config (Optional[AstraConfig]) — Astra 的配置。如果此值不为 None,则将使用 Astra 来构建适配器层。同时,请传入 init_lora_weights='astra',并在此情况下在调用 get_peft_model 之前调用 preprocess_astra。
  • lora_ga_config (Optional[LoraGAConfig]) — LoRA-GA 的配置。如果传入此参数,则将使用 LoRA-GA 来初始化适配器层。在这种情况下,也请将 init_lora_weights='lora_ga' 设置为该值。
  • use_dora (bool) — 启用“权重分解低秩适应”(DoRA)。该技术将权重的更新分解为两部分:幅度和方向。方向由标准的 LoRA 处理,而幅度则由一个独立的可学习参数处理。这可以在低秩情况下提升 LoRA 的性能。目前,DoRA 仅支持 linear 和 Conv2D 层。与纯 LoRA 相比,DoRA 引入了更大的开销,因此建议在推理时合并权重。更多信息,请参阅 https://huggingface.co/papers/2402.09353。
  • velora_config (Optional[VeloraConfig]) — 通过提供 VeloraConfig 来启用 VeLoRA。VeLoRA 会为 LoRA A 投影替换一个自定义的反向传播过程,该过程存储压缩后的激活值,而不是完整的输入激活值。
  • kasa_config (Optional[KasaConfig]) — 通过提供 KasaConfig 来启用 KaSA(Knowledge-aware Singular-value Adaptation)。KaSA 通过一次性 SVD 截断冻结基础权重中 r 个最小的奇异分量,并在 LoRA A 和 B 因子之间插入一个可学习的奇异值对角线。目前仅支持线性层。
  • alora_invocation_tokens (List[int]) — 如果不为 None,则启用 ‘Activated LoRA’ (aLoRA),其中 alora_invocation_tokens 是适配器的分词调用字符串(必须存在于所有模型 输入字符串中)。该技术仅在 alora_invocation_tokens 期间及之后的 token 上选择性地激活适配器权重。当用于 CausalLM 时,这意味着调用之前的 KV 缓存可以与基础模型(以及以这种方式运行的其他 aLoRA 适配器)的 KV 缓存互换。因此,在涉及基础模型推理与适配器推理切换的推理流水线中(例如智能体流水线,示例见论文),通过节省预填充(prefill)操作,可以实现显著的性能提升(相对于 LoRA)。在 vLLM 上,根据共享上下文的长度,适配器推理的整体加速幅度可能达到一个数量级或更多。请注意,由于权重是选择性应用的,因此无法进行合并。
  • use_qalora (bool) — 目前仅在 GPTQ 中实现。启用 Quantization-Aware Low-Rank Adaptation (QALoRA)。该技术将量化感知训练与 LoRA 相结合,以提升量化模型的性能。这可以改善 LoRA 的性能,尤其是在低秩情况下。目前,QALoRA 仅支持线性层。
  • qalora_group_size (int) — QALoRA 池化的组大小参数,用于控制维度缩减因子。输入维度将被池化为该大小的组,从而降低计算成本。较高的值可提供更高的压缩率,但可能会降低模型质量。该参数决定了平均多少个原始特征以生成一个池化特征。仅在 use_qalora=True 时使用。
  • monteclora_config (Optional[MontecloraConfig]) — Monteclora(Monte Carlo Low-Rank Adaptation)的配置。如果传入该参数,将在 LoRA 适配器之上使用 Monteclora 添加变分蒙特卡洛采样。有关各个超参数的详细信息,请参阅 MontecloraConfig。
  • layer_replication (List[Tuple[int, int]]) — 根据指定的范围堆叠原始模型层,构建新的层堆栈。这允许在不复制基础模型权重的情况下扩展(或缩小)模型。新的层都将各自附加独立的 LoRA 适配器。
  • runtime_config (LoraRuntimeConfig) — 运行时配置(不会被保存或恢复)。
  • lora_bias (bool) — 默认为 False。是否启用 LoRA B 参数的偏置项。通常应禁用此项。其主要使用场景是当 LoRA 权重是从完全微调的参数中提取时,以便考虑这些参数的偏置。
  • target_parameters (List[str], 可选) — 要替换为 LoRA 的参数名称列表。此参数的行为与 target_modules 类似,区别在于此处应传入参数名称。通常,您应使用 target_modules 来指定模块(例如 nn.Linear)。然而,在某些情况下,这并不可行。例如,在 HF Transformers 的许多混合专家(MoE)层中,使用的不是 nn.Linear,而是 nn.Parameter。PEFT 通常会重写 LoRA 的 forward 方法,但对于 nn.Parameter, 并不存在该方法。因此,要对该参数应用 LoRA,需要通过 target_parameters 对其进行指定。以 Llama4 为例,您可以传入: target_parameters=['feed_forward.experts.gate_up_proj', 'feed_forward.experts.down_proj]。目前尚未实现通过传入字符串进行正则表达式匹配。请注意,当模型被编译且使用了 target_parameters 时,预计会发生重新编译和/或图中断。建议不要同时使用两者。
  • use_bdlora (Optional[BdLoraConfig]) — 通过提供 BdLoraConfig 来启用 BD-LoRA(Block-Diagonal LoRA)。该技术使用块对角矩阵作为 LoRA-A 或 LoRA-B 因子,通过消除分布式环境中的通信开销,从而实现更快的多 LoRA 服务。
  • arrow_config (Optional[ArrowConfig]) — 在模型上应用 arrow 路由所需的配置。
  • ensure_weight_tying (bool, 可选) — 是否在 peft 初始化后绑定权重。这将确保添加到已绑定层中的适配器也被绑定。此参数仅适用于通过 modules_to_save 和 target_modules 传入的层。

这是用于存储 LoraModel 配置的类。

to_dict

< >

( )

以字典形式返回适配器模型的配置。会移除运行时配置。

LoraModel

类 peft.LoraModel

< >

( modelpeft_config: Union[PeftConfig, dict[str, PeftConfig]]adapter_name: strlow_cpu_mem_usage: bool = Falsestate_dict: Optional[dict[str, torch.Tensor]] = None ) → torch.nn.Module

参数

  • model (torch.nn.Module) — 要适配的模型。
  • config (LoraConfig) — Lora 模型的配置。
  • adapter_name (str) — 适配器的名称,默认为 "default"。
  • low_cpu_mem_usage (bool, optional, defaults to False) — 在 meta 设备上创建空的适配器权重。有助于加快加载过程。

返回值

torch.nn.Module

Lora 模型。

从预训练的 transformers 模型创建低秩适配器(LoRA)模型。

该方法的详细描述见 https://huggingface.co/papers/2106.09685。

示例:

>>> from transformers import AutoModelForSeq2SeqLM
>>> from peft import LoraConfig, get_peft_model

>>> config = LoraConfig(
...     task_type="SEQ_2_SEQ_LM",
...     r=8,
...     lora_alpha=32,
...     target_modules=["q", "v"],
...     lora_dropout=0.01,
... )

>>> model = AutoModelForSeq2SeqLM.from_pretrained("t5-base")
>>> lora_model = get_peft_model(model, config)
>>> import torch
>>> import transformers
>>> from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

>>> rank = ...
>>> target_modules = ["q_proj", "k_proj", "v_proj", "out_proj", "fc_in", "fc_out", "wte"]
>>> config = LoraConfig(
...     r=4, lora_alpha=16, target_modules=target_modules, lora_dropout=0.1, bias="none", task_type="CAUSAL_LM"
... )
>>> quantization_config = transformers.BitsAndBytesConfig(load_in_8bit=True)

>>> tokenizer = transformers.AutoTokenizer.from_pretrained(
...     "kakaobrain/kogpt",
...     revision="KoGPT6B-ryan1.5b-float16",  # or float32 version: revision=KoGPT6B-ryan1.5b
...     bos_token="[BOS]",
...     eos_token="[EOS]",
...     unk_token="[UNK]",
...     pad_token="[PAD]",
...     mask_token="[MASK]",
... )
>>> model = transformers.GPTJForCausalLM.from_pretrained(
...     "kakaobrain/kogpt",
...     revision="KoGPT6B-ryan1.5b-float16",  # or float32 version: revision=KoGPT6B-ryan1.5b
...     pad_token_id=tokenizer.eos_token_id,
...     use_cache=False,
...     device_map={"": rank},
...     torch_dtype=torch.float16,
...     quantization_config=quantization_config,
... )
>>> model = prepare_model_for_kbit_training(model)
>>> lora_model = get_peft_model(model, config)

属性:

add_weighted_adapter

< >

( adapters: list[str]weights: list[float]adapter_name: strcombination_type: Literal['svd', 'linear', 'cat', 'ties', 'ties_svd', 'dare_ties', 'dare_linear', 'dare_ties_svd', 'dare_linear_svd', 'magnitude_prune', 'magnitude_prune_svd'] = 'svd'svd_rank: int | None = Nonesvd_clamp: int | None = Nonesvd_full_matrices: bool = Truesvd_driver: str | None = Nonedensity: float | None = Nonemajority_sign_method: Literal['total', 'frequency'] = 'total' )

参数

  • adapters (list) — 要合并的适配器名称列表。
  • weights (list) — 每个适配器的权重列表。权重可以为正或为负,从而允许对适配器效果进行加法和减法操作。
  • adapter_name (str) — 新适配器的名称。
  • combination_type (str) — 合并类型可以是 [svd, linear, cat, ties, ties_svd, dare_ties, dare_linear, dare_ties_svd, dare_linear_svd, magnitude_prune, magnitude_prune_svd] 之一。当使用 cat combination_type 时,生成的适配器秩等于所有适配器秩的总和(混合适配器可能过大,从而导致 OOM 错误)。请注意,cat 和 svd 是精确方法, 能为您提供良好的精度;linear 虽然高效,但是一种非常粗略的近似,如果条件允许应避免使用。
  • svd_rank (int,可选) — svd 输出适配器的秩。如果未提供,将使用合并适配器的最大秩。
  • svd_clamp (float, 可选) — 用于对 SVD 分解输出进行截断的分位数阈值。如果提供 None,则不执行截断。默认为 None。
  • svd_full_matrices (bool, 可选) — 控制计算完整 SVD 还是缩减 SVD,并由此决定返回张量 U 和 Vh 的形状。默认为 True。
  • svd_driver (str, 可选) — 要使用的 cuSOLVER 方法名称。此关键字参数仅在 CUDA 上合并时有效。可以是 [None, gesvd, gesvdj, gesvda] 之一。更多信息请参阅 torch.linalg.svd 文档。默认为 None。
  • density (float, 可选) — 0 到 1 之间的值。0 表示所有值都被剪枝,1 表示没有值被剪枝。应与 [ties, ties_svd, dare_ties, dare_linear, dare_ties_svd, dare_linear_svd, magnintude_prune, magnitude_prune_svd] 一起使用
  • majority_sign_method (str) — 用于获取符号值幅值的方法,应为 [“total”, “frequency”] 之一。 应与 [ties, ties_svd, dare_ties, dare_ties_svd] 配合使用

此方法通过按给定权重合并指定的适配器来添加一个新的适配器。

使用 cat 组合类型时,请注意,生成的适配器秩等于所有适配器秩的总和。因此,混合适配器可能会变得过大,从而导致 OOM 错误。

subtract_mutated_init

< >

( output_state_dict: dict[str, torch.Tensor]adapter_name: strkwargs = None )

此函数可通过比较 output_state_dict 中 PiSSA/CorDA/OLoRA 适配器的参数与 adapter_name 中 PiSSA/CorDA/OLoRA 的初始值,来计算 PiSSA/CorDA/OLoRA 的更新量,从而将 PiSSA/CorDA/OLoRA 转换为 LoRA。

像 PiSSA 这类方法会移除模型基础权重的一部分用于训练,因此不容易进行替换。但如果你同时知道初始预训练权重和训练后的权重,就可以计算 \Delta W 并将其用作 LoRA 适配器。

计算步骤:

  • W=Wres+A0B0W = W_{res} + A_0 B_0 (PiSSA 初始化
  • W+ΔW=Wres+ABW + \Delta W = W_{res} + A B (训练后的 PiSSA
  • ΔW=Wres+AB−W=Wres+AB−Wres−A0B0\Delta W = W_{res} + AB - W = W_{res} + AB - W_{res} - A_0 B_0 (推导 LoRA 的 dW
  • ΔW=AB−A0B0\Delta W = AB - A_0 B_0

实用工具

ArrowConfig

类 peft.ArrowConfig

< >

( top_k: int = 3router_temperature: float = 1.0use_gks: bool = Falserng_seed: Optional[int] = None )

这是用于存储 Arrow 和 GenKnowSub 算法配置的子配置类。Arrow 是一种路由算法,用于组合训练好的 LoRA 模块以解决新任务,该算法在 https://huggingface.co/papers/2405.11157 中提出。GenKnowSub 是在通过 Arrow 组合之前对训练好的模块进行优化,该方法在 https://aclanthology.org/2025.acl-short.54/ 中介绍。

LoftQ

replace_lora_weights_loftq

peft.replace_lora_weights_loftq

< >

( peft_modelmodel_path: Optional[str] = Noneadapter_name: str = 'default'callback: Optional[Callable[[torch.nn.Module, str], bool]] = None )

参数

  • peft_model (PeftModel) — 要替换权重的模型。必须是一个带有 LoRA 层的量化 PEFT 模型。
  • model_path (Optional[str]) — 模型 safetensors 文件的路径。如果模型是 Hugging Face 模型,此路径将从模型的 config 中推断得出。否则,必须提供该路径。
  • adapter_name (str) — 要替换权重的适配器名称。默认适配器名称为“default”。
  • callback (Optional[Callable[[PeftModel, str], bool]]) — 一个回调函数,将在每个模块被替换后调用。该回调函数应以模型和当前模块的名称作为输入,并返回一个布尔值,指示是否保留该替换。如果回调返回 False,则替换操作将被回滚。这在确认 LoftQ 初始化确实降低了模型的量化误差时非常有用。例如,该回调可以为给定输入生成 logits,并将其与原始未量化模型在相同输入下的 logits 进行比较,仅当存在改进时才返回 True。由于这是一种贪心优化,多次调用此函数可能会带来渐进式的改进。

使用 LoftQ 技术替换使用 bitsandbytes 量化的模型的 LoRA 权重。

替换过程是即时完成的:从本地存储的 safetensors 模型文件中加载未量化的权重,并初始化 LoRA 权重,以最小化原始权重与量化权重之间的量化误差。

由于 pickle 不支持延迟加载,因此无法支持常规的 PyTorch 检查点文件。

根据模型大小,调用此函数可能需要一些时间才能完成。

LoftQConfig

类 peft.LoftQConfig

< >

( loftq_bits: int = 4loftq_iter: int = 1 )

参数

  • loftq_bits (int) — LoftQ 的量化位数。
  • loftq_iter (int) — LoftQ 的交替迭代次数。

这是用于存储 LoraModel 配置的子配置类。

Eva

EvaConfig

类 peft.EvaConfig

< >

( rho: float = 2.0tau: float = 0.99use_label_mask: bool = Truelabel_mask_value: int = -100whiten: bool = Falseadjust_scaling_factors: bool = True )

参数

  • rho (float) — 用于 EVA 秩重分配的 Rho 值(>= 1.0)。某一层允许的最大秩为 lora_r * rho。默认值为 2.0, 意味着允许的最大秩为 2r。增大 rho 值将允许在各层之间进行更高程度的秩重分配。某些预训练模型可能对秩重分配更为敏感。因此,如果性能低于预期,尝试 rho=1.0(不进行重分配)可能会带来益处。
  • tau (float) — 用于提前停止的余弦相似度阈值。比较连续两个 SVD 步骤之间右奇异向量的余弦相似度。如果余弦相似度高于此阈值,则停止 SVD 迭代。默认值为 0.99。
  • use_label_mask (bool) — 在 EVA 初始化中使用标签掩码。这意味着在 SVD 计算中,标签为 label_mask_value 的位置将被忽略。在大多数情况下,建议将 use_label_mask 设置为 True,这对于多轮对话尤其有益。默认值为 True。根据标签掩码过滤掉某些项有时会导致批次大小变小,从而引起 SVD 计算的不稳定。如果批次中大部分项都会被过滤掉,请将 use_label_mask 设置为 False。
  • label_mask_value (int) — 如果 use_label_mask=True,则用于查找以屏蔽被忽略标记的值。默认值为 -100。
  • whiten (bool) — 对奇异向量应用白化。默认值为 False。 研究表明,白化在视觉领域对 EVA 有益。
  • adjust_scaling_factors (bool) — 在秩重新分配后调整 LoRA 缩放因子。将此设置为 True 表示会调整缩放因子,使得无论秩如何,所有 LoRA 梯度都具有相同的尺度。默认为 True。

这是用于存储通过 EVA 进行数据驱动初始化的配置的子配置类。EVA 在 Explained Variance Adaptation 中被提出。

initialize_lora_eva_weights

peft.initialize_lora_eva_weights

< >

( model: Moduledataloader: collections.abc.Iterable | None = Noneeva_state_dict: dict | None = Noneforward_fn: collections.abc.Callable | None = <function forward_fn_dict at 0x7fc5dc338880>prepare_model_inputs_fn: collections.abc.Callable | None = <function prepare_model_inputs_fn_language_modeling at 0x7fc5dc338670>prepare_layer_inputs_fn: collections.abc.Callable | dict[str, collections.abc.Callable] | None = <function prepare_layer_inputs_fn_language_modeling at 0x7fc5dc3387d0>adapter_name: str = 'default'gather_distributed_inputs: bool = Trueshow_progress_bar: bool = True ) → model (torch.nn.Module)

参数

  • model (PeftModel) — 用于计算 SVD 的 peft 模型。
  • dataloader (Optional[Iterable]) — 用于前向传播的数据加载器。如果为 None,则必须提供 eva_state_dict。
  • eva_state_dict (Optional[dict]) — 要加载到模型中的 state_dict。如果为 None,则需要提供 dataloader,并使用 get_eva_state_dict 计算 state_dict。
  • forward_fn (Callable) — 用于前向传播的前向函数。接受两个参数:model 和 inputs。默认行为为 return model(**inputs)
  • prepare_model_inputs_fn (Optional[Callable]) — 此函数接收模型输入和 peft_config,并将输出传递给 prepare_layer_inputs_fn。可用于根据原始模型输入修改 SVD 计算的输入。例如,在语言建模中,使用注意力掩码来确定哪些索引是填充标记,不应用于 SVD。在此处定义的任何函数都期望两个参数: model_input 和 peft_config。默认使用 peft.tuners.lora.eva.prepare_model_inputs_fn_language_modeling。
  • prepare_layer_inputs_fn (Union[Callable, Dict[str, Callable], None]) — 此函数接收层输入、模型输入(可能经过 prepare_model_inputs_fn 修改)以及层名称,并返回应用于该特定层 SVD 的输入。在此处定义的自定义函数期望接收三个参数:layer_input、model_input 和 layer_name,并应返回一个二维张量。默认逻辑可在 peft.tuners.lora.eva.prepare_layer_inputs_fn_language_modeling 中找到,适用于语言建模。在这种情况下,model_inputs 是用于确定哪些索引应被用于 SVD 的掩码(由 prepare_model_inputs_fn_language_modeling 创建)。
  • adapter_name (str) — 用于初始化权重的适配器名称。
  • gather_distributed_inputs (bool) — 是否从所有 rank 收集层输入。默认值为 True,表示在分布式设置中,将从所有 rank 收集层输入以进行 SVD 计算。在非分布式设置中,此参数将被忽略。如果在分布式设置中使用非分布式 dataloader,请将其设置为 False。
  • show_progress_bar (bool) — 是否显示进度条。默认为 True。

返回值

model (torch.nn.Module)

已初始化 LoRA 权重的模型。

使用 EVA 方法初始化 LoRA 层的权重。

此函数使用 EVA 方法初始化 LoRA 层的权重。它会为每个适配器层计算 SVD,并相应地更新权重。

get_eva_state_dict

peft.get_eva_state_dict

< >

( model: Moduledataloader: Iterablepeft_config: peft.tuners.lora.config.LoraConfig | None = Noneforward_fn: collections.abc.Callable | None = <function forward_fn_dict at 0x7fc5dc338880>prepare_model_inputs_fn: collections.abc.Callable | None = <function prepare_model_inputs_fn_language_modeling at 0x7fc5dc338670>prepare_layer_inputs_fn: collections.abc.Callable | dict[str, collections.abc.Callable] | None = <function prepare_layer_inputs_fn_language_modeling at 0x7fc5dc3387d0>adapter_name: str = 'default'gather_distributed_inputs: bool = Trueshow_progress_bar: bool = True ) → eva_state_dict (dict)

参数

  • model (torch.nn.Module) — 用于计算 SVD 的模型。无需是 PeftModel。
  • dataloader (Iterable) — 用于前向传播的数据加载器。
  • peft_config (Optional[LoraConfig]) — LoRA 层的配置。仅当 model 不是 PeftModel 时需要提供。
  • forward_fn (Callable) — 用于前向传播的前向函数。接受两个参数:model 和 inputs。默认行为为 return model(**inputs)
  • prepare_model_inputs_fn (Optional[Callable]) — 此函数接收模型输入和 peft_config,并将输出传递给 prepare_layer_inputs_fn。可用于根据原始模型输入修改 SVD 计算的输入。例如,在语言建模中,使用注意力掩码来确定哪些索引是填充标记,不应用于 SVD。在此处定义的任何函数都期望两个参数: model_input 和 peft_config。默认使用 peft.tuners.lora.eva.prepare_model_inputs_fn_language_modeling。
  • prepare_layer_inputs_fn (Union[Callable, Dict[str, Callable], None]) — 此函数接收层输入、模型输入(可能经过 prepare_model_inputs_fn 修改)以及层名称,并返回应用于该特定层 SVD 的输入。在此处定义的自定义函数期望接收三个参数:layer_input、model_input 和 layer_name,并应返回一个二维张量。默认逻辑可在 peft.tuners.lora.eva.prepare_layer_inputs_fn_language_modeling 中找到,适用于语言建模。在这种情况下,model_inputs 是用于确定哪些索引应被用于 SVD 的掩码(由 prepare_model_inputs_fn_language_modeling 创建)。
  • adapter_name (str) — 要计算 SVD 的适配器名称。
  • gather_distributed_inputs (bool) — 是否从所有 rank 收集层输入。默认值为 True,表示在分布式设置中,将从所有 rank 收集层输入以进行 SVD 计算。在非分布式设置中,此参数将被忽略。如果在分布式设置中使用非分布式 dataloader,请将其设置为 False。
  • show_progress_bar (bool) — 是否显示进度条。默认为 True。

返回值

eva_state_dict (dict)

包含每层 SVD 分量的状态字典。

计算模型中每一层的 SVD。

此函数计算模型中每一层的奇异值分解(SVD)。它使用增量 PCA 方法来计算 SVD 分量。该函数还使用余弦相似度来检查所计算分量的收敛性。每一层的秩分布是根据解释方差比确定的。

LoraGAConfig

类 peft.LoraGAConfig

< >

( direction: Literal['ArBr', 'A2rBr', 'ArB2r', 'random'] = 'ArB2r'scale: Literal['stable', 'weight_svd', 'gd_scale', 'unit'] = 'stable'stable_gamma: int = 16 )

参数

  • direction (Literal["ArBr", "A2rBr", "ArB2r", "random"]) — 将梯度 SVD 分量分配到 lora_A 和 lora_B 矩阵的策略。
    • “ArBr”:交替索引(A 取奇数,B 取偶数)
    • “A2rBr”:A 取索引 [r:2r],B 取索引 [:r]
    • “ArB2r”:A 取索引 [:r],B 取索引 [r:2r](推荐)
    • “random”:随机选择索引 默认值:“ArB2r”
  • scale (Literal["stable", "weight_svd", "gd_scale", "unit"]) — 适配器初始化的缩放策略。
    • “stable”:使用 gamma 参数进行稳定缩放
    • “weight_svd”:基于权重矩阵奇异值的缩放
    • “gd_scale”:基于梯度下降的缩放
    • “unit”:无额外缩放 默认值:“stable”
  • stable_gamma (int) — 稳定缩放方法的 Gamma 参数。默认值:16

这是用于存储 LoRA-GA 初始化配置的子配置类。

LoRA-GA(带梯度近似的低秩适配)在初始化过程中利用梯度信息,通过将初始适配器权重与全量微调梯度的方向对齐,实现更快的收敛(2-4 倍加速)。

参考:https://arxiv.org/abs/2407.05000

实用工具

peft.tuners.lora.loraga.estimate_gradients

< >

( model: Modulelora_config: LoraConfigtrain_step: Callable )

估计 LoRA-GA 初始化的梯度。

此函数仅针对目标模块的权重启用梯度计算,并运行 train_step 回调。这比全局启用梯度更加节省内存。

peft.preprocess_loraga

< >

( model: Modulelora_config: LoraConfigtrain_step: Callablecache_file: str | None = None )

参数

  • model (nn.Module) — 要预处理的模型。
  • lora_config (LoraConfig) — 模型的 Lora 配置。应设置 lora_config.lora_ga_config。
  • train_step (Callable[[], None]) — 用于执行梯度估计的回调。通常,您应在此回调中运行模型的前向和反向传播。梯度将在该回调内的所有调用中累积。
  • cache_file (Optional[str]) — 用于保存/加载梯度的缓存文件路径(可选)。如果提供了该路径且文件存在,则从缓存中加载梯度。否则,将估算梯度并将其保存到该路径。

通过估计梯度,为模型构建必要的 LoRA-GA 字段。

对于每个线性层,将通过运行提供的 train_step 回调来估计梯度。这些梯度随后会附加到模块上,并在初始化过程中使用。

完成后,每个目标模块都会设置以下字段: _peft_loraga_grad (torch.Tensor): 权重矩阵的累积梯度。

入侵者维度缩减

peft.tuners.lora.intruders.reduce_intruder_dimension

< >

( peft_modelold_adapter_name = 'default'new_adapter_name = 'intruder_reduced'top_k = 10threshold_epsilon = 0.5mitigation_lambda = 0.75logging_sink = <built-in function print> )

参数

  • peft_model — 加载了名为 old_adapter_name 的 LoRA 适配器的 PEFT 模型。目前不支持混合模型。
  • top_k(默认值 — 10) 在入侵者检测中考虑前 k 个维度。该值越大,用于入侵者检测分析的维度就越多(同时也可能产生更多的误报)。它基于基础权重与适配器权重之间的余弦相似度进行操作,这些相似度大致按维度的影响力排序(由奇异值分解确定),因此 top-k 为 10 时会查看 10 个“最重要”的维度。
  • threshold_epsilon(默认值 — 0.5) 用于判断基础权重与适配器权重之间的余弦相似度是否被视为入侵者的阈值。 根据论文,入侵者维度与预训练奇异向量的绝对余弦相似度接近于零。该值越低,被识别为潜在入侵者维度的数量越少。该值越高,越多潜在的误报会被视为入侵者。
  • mitigation_lambda(默认值 — 0.75) 从适配器的 delta 权重中减去的入侵者维度的相对比例。该值越高,减去的入侵者维度越多,但丢失的信息也越多。有关权衡分析,请参阅论文中的图 8。
  • logging_sink(默认值 — print) 用于打印缓解过程相关信息的函数。如果不需要任何输出,请将其设置为 None。

基于https://huggingface.co/papers/2410.21228(“LoRA vs Full Fine-tuning: An Illusion of Equivalence”)的入侵者维度缓解方法。

该方法可以通过对已训练的低秩适配器进行后处理来恢复先前的知识(即缓解遗忘)。这会以任务准确性为代价——可以通过调整migration_lambda的值来在这两个因素之间进行权衡。

完成缓解后,将生成一个名称为 new_adapter_name 的新适配器,并且该适配器会被设置为当前激活的适配器。因此,在缓解后的模型上进行推理时将使用修改后的适配器。若要切换回原始适配器,可以使用 peft_model.set_adapter(<old_adapter_name>)。

目前仅支持 LoRA,因为尚不清楚该方法是否能推广到其他增量权重方法。

Update on GitHub