PEFT 文件

LoRA

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

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` 進行推理預計會有開銷,尤其是在批次中不同介面卡的數量很高時。這是因為批次大小實際上減少為每個介面卡的樣本數。如果執行時效能是您的首要任務,請嘗試以下方法:
    • 增加批次大小。
    • 儘量避免在同一批次中有大量不同的介面卡,優先選擇同質批次。這可以透過緩衝具有相同介面卡的樣本,並僅對少數不同介面卡進行推理來實現。
    • 可以看看其他實現,例如 LoRAXpunicaS-LoRA,它們專門用於處理大量不同的介面卡。
< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.