PEFT 文件

故障排除

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

問題排查

如果您在使用 PEFT 時遇到任何問題,請檢視以下常見問題及其解決方案列表。

示例無法執行

示例通常依賴於最新的軟體包版本,因此請確保它們是最新的。特別地,請檢查以下軟體包的版本:

  • peft
  • transformers
  • accelerate
  • torch

通常,您可以透過在 Python 環境中執行此命令來更新軟體包版本:

python -m pip install -U <package_name>

從原始碼安裝 PEFT 有助於跟上最新的開發進展。

python -m pip install git+https://github.com/huggingface/peft

Dtype 相關問題

ValueError: Attempting to unscale FP16 gradients

這個錯誤可能是因為模型以 `torch_dtype=torch.float16` 載入,然後在自動混合精度 (AMP) 上下文中使用,例如透過在 🤗 Transformers 的 `Trainer` 類中設定 `fp16=True`。原因是使用 AMP 時,可訓練權重不應使用 fp16。為了在不載入整個模型為 fp32 的情況下解決此問題,請在您的程式碼中新增以下內容:

peft_model = get_peft_model(...)

# add this:
for param in model.parameters():
    if param.requires_grad:
        param.data = param.data.float()

# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()

或者,您可以使用 cast_mixed_precision_params() 函式來正確地轉換權重:

from peft import cast_mixed_precision_params

peft_model = get_peft_model(...)
cast_mixed_precision_params(peft_model, dtype=torch.float16)

# proceed as usual
trainer = Trainer(model=peft_model, fp16=True, ...)
trainer.train()

從 PEFT v0.12.0 版本開始,PEFT 會在適當的時候自動將介面卡權重的 dtype 從 `torch.float16` 和 `torch.bfloat16` 提升到 `torch.float32`。要 *阻止* 這種行為,您可以將 `autocast_adapter_dtype=False` 傳遞給 ~get_peft_model()from_pretrained()load_adapter()

選擇介面卡的資料型別 (dtype)

大多數 PEFT 方法,如 LoRA,透過新增可訓練的介面卡權重來工作。預設情況下,這些權重以 float32 (fp32) 資料型別儲存,即以相對較高的精度儲存。因此,即使基礎模型以 float16 (fp16) 或 bfloat16 (bf16) 載入,介面卡權重也是 float32。在正向傳播計算介面卡結果時,輸入通常會是基礎模型的 dtype,因此如有必要,它將被上轉換為 float32,然後轉換回原始 dtype。

如果您希望介面卡權重具有基礎模型的較低精度,即 float16 或 bfloat16,您可以在建立模型 (~get_peft_model()) 或載入模型 (from_pretrained()) 時傳遞 `autocast_adapter_dtype=False`。這樣做有利有弊:

半精度介面卡的優點:

  • 計算速度略快
  • 記憶體佔用略少
  • 檢查點檔案更小(大小減半)

半精度介面卡的缺點:

  • 損失略高
  • 溢位或下溢的風險更高

請注意,對於大多數用例,總執行時間和記憶體成本將由基礎模型的大小和資料集決定,而 PEFT 介面卡的 dtype 影響較小。

從已載入的 PEFT 模型得到糟糕的結果

從載入的 PEFT 模型獲得較差結果的原因可能有多種,下面列出了一些。如果您仍然無法解決問題,請檢視 GitHub 上是否有其他人遇到類似的問題,如果找不到,請開一個新的 issue。

在開 issue 時,提供一個可重現問題的最小程式碼示例會非常有幫助。另外,請報告載入的模型效能是否與微調前相同,是否是隨機水平,或者只是比預期略差。這些資訊有助於我們更快地確定問題。

隨機偏差

如果您的模型輸出與之前的執行不完全相同,可能存在隨機因素的問題。例如:

  1. 請確保模型處於 `.eval()` 模式,這很重要,例如,如果模型使用了 dropout。
  2. 如果您在語言模型上使用 `generate`,可能會有隨機取樣,因此要獲得相同的結果需要設定隨機種子。
  3. 如果您使用了量化併合並了權重,由於四捨五入的誤差,預計會有微小的偏差。

模型載入不正確

請確保您正確載入了模型。一個常見的錯誤是嘗試使用 get_peft_model() 載入一個*已訓練*的模型,這是不正確的。相反,載入程式碼應該如下所示:

from peft import PeftModel, PeftConfig

base_model = ...  # to load the base model, use the same code as when you trained it
config = PeftConfig.from_pretrained(peft_model_id)
peft_model = PeftModel.from_pretrained(base_model, peft_model_id)

隨機初始化的層

對於某些任務,正確配置配置中的 `modules_to_save` 以處理隨機初始化的層非常重要。

舉個例子,如果您使用 LoRA 對一個語言模型進行序列分類微調,這是必要的,因為 🤗 Transformers 會在模型之上新增一個隨機初始化的分類頭。如果您不將這個層新增到 `modules_to_save`,分類頭將不會被儲存。下次載入模型時,您會得到一個*不同*的隨機初始化的分類頭,導致結果完全不同。

如果您在配置中提供了 `task_type` 引數,PEFT 會嘗試正確猜測 `modules_to_save`。這對於遵循標準命名約定的 transformers 模型應該有效。但最好還是仔細檢查一下,因為我們不能保證所有模型都遵循命名約定。

當您載入一個具有隨機初始化層的 transformers 模型時,您應該會看到類似以下的警告:

Some weights of <MODEL> were not initialized from the model checkpoint at <ID> and are newly initialized: [<LAYER_NAMES>].
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

提到的層應新增到配置中的 `modules_to_save` 中,以避免上述問題。

例如,當載入一個使用 DeBERTa 架構進行序列分類的模型時,您會看到一個警告,指出以下權重是新初始化的:`['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']`。由此可知,應將 `classifier` 和 `pooler` 層新增到:`modules_to_save=["classifier", "pooler"]`。

擴充套件詞彙表

對於許多語言微調任務,由於引入了新的詞元,擴充套件模型的詞彙表是必要的。這需要擴充套件嵌入層以適應新的詞元,並且根據微調方法的不同,在儲存介面卡時除了介面卡權重外,還需要儲存嵌入層。有幾種實現方式,按引數效率排序如下:

  • 可訓練詞元,僅訓練指定的詞元,可選地只儲存更新的值。
  • 在嵌入矩陣上訓練一個介面卡,可選地只儲存更新的值。
  • 對嵌入層進行全量微調。

使用可訓練詞元

讓我們從可訓練詞元開始,這裡是它的 LoRA 整合。如果您只對訓練新的嵌入而不訓練其他任何東西感興趣,請參考獨立文件

要啟用嵌入層的選擇性詞元訓練,您需要透過 `trainable_token_indices` 引數提供新新增詞元的 ID。如果存在多個嵌入層,您可以選擇性地指定目標層。對於一個 Mistral 模型,這可能看起來像這樣:

new_tokens = ['<think>', '</think>']
tokenizer.add_tokens(new_tokens)
base_model.resize_token_embeddings(len(tokenizer))

lora_config = LoraConfig(
    ...,
    trainable_token_indices={'embed_tokens': tokenizer.convert_tokens_to_ids(new_tokens)},
)

如果您的模型使用權重繫結(例如 `lm_head`),可訓練詞元會嘗試解析這些並保持它們的更新,因此在這種情況下不需要新增 `modules_to_save=["lm_head"]`。這僅在模型使用 Transformers 的權重繫結約定時有效。

使用 `model.save_pretrained` 儲存模型可能會儲存整個嵌入矩陣而不是僅僅儲存差異,這是因為嵌入矩陣被調整了大小。為了節省空間,您可以在呼叫 `save_pretrained` 時設定 `save_embedding_layers=False` 來停用此行為。只要您不同時透過其他方式修改嵌入矩陣,這樣做是安全的,因為這些更改不會被可訓練詞元跟蹤。

使用介面卡,例如 LoRA

透過將其新增到介面卡配置的 `target_modules` 來準備嵌入層。例如,Mistral 的配置可能如下所示:

config = LoraConfig(..., target_modules=["embed_tokens", "lm_head", "q_proj", "v_proj"])

一旦新增到 `target_modules`,如果模型具有 `get_input_embeddings` 和 `get_output_embeddings`,PEFT 會在儲存介面卡時自動儲存嵌入層。這通常是 Transformers 模型的情況。

如果模型的嵌入層不遵循 Transformer 的命名方案,但仍然實現了 `get_input_embeddings`,您仍然可以透過在儲存介面卡時手動傳遞 `save_embedding_layers=True` 來儲存它:

model = get_peft_model(...)
# train the model
model.save_pretrained("my_adapter", save_embedding_layers=True)

對於推理,首先載入基礎模型,並像訓練模型之前一樣調整其大小。調整基礎模型大小後,您可以載入 PEFT 檢查點。

有關完整示例,請檢視此 notebook

全量微調

全量微調在 VRAM 或儲存空間方面成本更高,但如果其他方法都失敗了,您可以回退到此方法,看看它是否適合您。透過將嵌入層的名稱新增到 `modules_to_save` 來實現。請注意,您還需要新增繫結的層,例如 `lm_head`。下面是一個使用 LoRA 的 Mistral 模型的例子:

config = LoraConfig(..., modules_to_save=["embed_tokens", "lm_head"], target_modules=["q_proj", "v_proj"])

收到關於“權重未從模型檢查點初始化”的警告

當您載入一個針對某個任務(例如,分類)訓練過的 PEFT 模型時,您可能會收到如下警告:

LlamaForSequenceClassification 的一些權重未從 meta-llama/Llama-3.2-1B 的模型檢查點初始化,而是新初始化的:['score.weight']。您可能應該在下游任務上訓練此模型,才能將其用於預測和推理。

雖然這看起來很嚇人,但很可能無需擔心。這個警告來自 Transformers,並不是 PEFT 特有的警告。它告訴您,一個隨機初始化的分類頭 (`score`) 被附加到基礎模型上,該頭必須經過訓練才能產生有意義的預測。

當您在訓練模型*之前*收到此警告時,如果您正確地將 `task_type` 引數傳遞給了 PEFT 配置,PEFT 會自動處理使分類頭可訓練。

from peft import LoraConfig, TaskType

lora_config = LoraConfig(..., task_type=TaskType.SEQ_CLS)

如果您的分類頭不遵循 Transformers 的常規命名約定(這種情況很少見),您必須在 `modules_to_save` 中明確告訴 PEFT 頭的名稱。

lora_config = LoraConfig(..., modules_to_save=["name-of-classification-head"])

要檢查分類頭的名稱,請列印模型,它應該是最後一個模組。

如果您在推理程式碼中,即*在*訓練模型之後載入 PEFT 模型時收到此警告,您總是需要先載入 Transformers 模型。由於 Transformers 不知道您之後會載入 PEFT 權重,它仍然會給出這個警告。

一如既往,最佳實踐是透過對模型進行一些驗證來確保其在推理中正常工作。

檢查層和模型狀態

有時,PEFT 模型可能會處於不良狀態,尤其是在處理多個介面卡時。可能會出現一些混淆,比如存在哪些介面卡、哪個是活動的、哪個被合併了等等。為了幫助調查此問題,請呼叫 get_layer_status()get_model_status() 方法。

get_layer_status() 方法為您提供每個目標層的活動、合併和可用介面卡的詳細概述。

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

>>> model_id = "google/flan-t5-small"
>>> model = AutoModel.from_pretrained(model_id)
>>> model = get_peft_model(model, LoraConfig())

>>> model.get_layer_status()
[TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.q',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['default'],
                  merged_adapters=[],
                  requires_grad={'default': True},
                  available_adapters=['default']),
 TunerLayerStatus(name='model.encoder.block.0.layer.0.SelfAttention.v',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['default'],
                  merged_adapters=[],
                  requires_grad={'default': True},
                  available_adapters=['default']),
...]

>>> model.get_model_status()
TunerModelStatus(
    base_model_type='T5Model',
    adapter_model_type='LoraModel',
    peft_types={'default': 'LORA'},
    trainable_params=344064,
    total_params=60855680,
    num_adapter_layers=48,
    enabled=True,
    active_adapters=['default'],
    merged_adapters=[],
    requires_grad={'default': True},
    available_adapters=['default'],
)

在模型狀態輸出中,您應該注意標有 `"irregular"` 的條目。這意味著 PEFT 在模型中檢測到不一致的狀態。例如,如果 `merged_adapters="irregular"`,則表示至少有一個介面卡在某些目標模組上被合併,但在其他模組上沒有。結果,推理結果很可能是不正確的。

解決此問題的最佳方法是重新載入整個模型和介面卡檢查點。確保您沒有對模型執行任何不正確的操作,例如手動在某些模組上合併介面卡而不是其他模組。

將層狀態轉換為 pandas `DataFrame` 以便於直觀檢查。

from dataclasses import asdict
import pandas as pd

df = pd.DataFrame(asdict(layer) for layer in model.get_layer_status())

對於非 PEFT 模型,如果它們在底層使用了 PEFT 層,也可以獲取此資訊,但在這種情況下,無法確定 `base_model_type` 或 `peft_types` 等資訊。例如,您可以像這樣在 diffusers 模型上呼叫它:

>>> import torch
>>> from diffusers import StableDiffusionPipeline
>>> from peft import get_model_status, get_layer_status

>>> path = "runwayml/stable-diffusion-v1-5"
>>> lora_id = "takuma104/lora-test-text-encoder-lora-target"
>>> pipe = StableDiffusionPipeline.from_pretrained(path, torch_dtype=torch.float16)
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-1")
>>> pipe.load_lora_weights(lora_id, adapter_name="adapter-2")
>>> pipe.set_lora_device(["adapter-2"], "cuda")
>>> get_layer_status(pipe.text_encoder)
[TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.k_proj',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['adapter-2'],
                  merged_adapters=[],
                  requires_grad={'adapter-1': False, 'adapter-2': True},
                  available_adapters=['adapter-1', 'adapter-2'],
                  devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
 TunerLayerStatus(name='text_model.encoder.layers.0.self_attn.v_proj',
                  module_type='lora.Linear',
                  enabled=True,
                  active_adapters=['adapter-2'],
                  merged_adapters=[],
                  requires_grad={'adapter-1': False, 'adapter-2': True},
                  devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']}),
...]

>>> get_model_status(pipe.unet)
TunerModelStatus(
    base_model_type='other',
    adapter_model_type='None',
    peft_types={},
    trainable_params=797184,
    total_params=861115332,
    num_adapter_layers=128,
    enabled=True,
    active_adapters=['adapter-2'],
    merged_adapters=[],
    requires_grad={'adapter-1': False, 'adapter-2': True},
    available_adapters=['adapter-1', 'adapter-2'],
    devices={'adapter-1': ['cpu'], 'adapter-2': ['cuda']},
)

速度

載入介面卡權重過慢

與載入基礎模型相比,載入 LoRA 等介面卡的權重通常應該很快。但是,在某些用例中,介面卡權重可能相當大,或者使用者需要載入大量介面卡,這時載入時間會累積起來。原因是介面卡權重首先被初始化,然後被載入的權重覆蓋,這是浪費的。為了加快載入時間,您可以將 `low_cpu_mem_usage=True` 引數傳遞給 from_pretrained()load_adapter()

如果此選項在不同用例中效果良好,未來可能會成為介面卡載入的預設設定。

可復現性

使用批歸一化 (batch norm) 的模型

當載入一個訓練好的 PEFT 模型,而基礎模型使用了批歸一化(例如 `torch.nn.BatchNorm1d` 或 `torch.nn.BatchNorm2d`)時,您可能會發現無法重現完全相同的輸出。這是因為批歸一化層在訓練期間會跟蹤執行統計資料,但這些統計資料不屬於 PEFT 檢查點。因此,當您載入 PEFT 模型時,將使用基礎模型的執行統計資料(即使用 PEFT 訓練前的統計資料)。

根據您的用例,這可能不是什麼大問題。但是,如果您需要輸出 100% 可重現,您可以透過將批歸一化層新增到 `modules_to_save` 來實現。下面是一個使用 resnet 和 LoRA 的示例。請注意,我們設定了 `modules_to_save=["classifier", "normalization"]`。我們需要 `"classifier"` 引數,因為我們的任務是影像分類,我們新增 `"normalization"` 引數以確保批歸一化層儲存在 PEFT 檢查點中。

from transformers import AutoModelForImageClassification
from peft import LoraConfig, get_peft_model

model_id = "microsoft/resnet-18"
base_model = AutoModelForImageClassification.from_pretrained(self.model_id)
config = LoraConfig(
    target_modules=["convolution"],
    modules_to_save=["classifier", "normalization"],
),

根據您使用的模型型別,批歸一化層的名稱可能不是 `"normalization"`,因此請確保名稱與您的模型架構匹配。

版本不匹配

載入配置時因意外關鍵字引數而出錯

當您遇到如下所示的錯誤時,意味著您嘗試載入的介面卡是使用比您系統上安裝的 PEFT 版本更新的版本訓練的。

TypeError: LoraConfig.__init__() got an unexpected keyword argument <argument-name>

解決此問題的最佳方法是安裝最新的 PEFT 版本:

python -m pip install -U PEFT

如果介面卡是從 PEFT 的原始碼安裝(未釋出的 PEFT 版本)訓練的,那麼您也需要從原始碼安裝 PEFT。

python -m pip install -U git+https://github.com/huggingface/peft.git

如果您無法升級 PEFT,可以嘗試一個變通方法。

假設錯誤訊息說未知的關鍵字引數名為 `foobar`。在此 PEFT 介面卡的 `adapter_config.json` 檔案中搜索 `foobar` 條目並將其從檔案中刪除。然後儲存檔案並嘗試再次載入模型。

此解決方案在大多數情況下有效。只要它是 `foobar` 的預設值,就可以忽略。但是,如果它設定為其他值,您將得到不正確的結果。升級 PEFT 是推薦的解決方案。

< > 在 GitHub 上更新

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