PEFT 文件
故障排除
並獲得增強的文件體驗
開始使用
問題排查
如果您在使用 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 時,提供一個可重現問題的最小程式碼示例會非常有幫助。另外,請報告載入的模型效能是否與微調前相同,是否是隨機水平,或者只是比預期略差。這些資訊有助於我們更快地確定問題。
隨機偏差
如果您的模型輸出與之前的執行不完全相同,可能存在隨機因素的問題。例如:
- 請確保模型處於 `.eval()` 模式,這很重要,例如,如果模型使用了 dropout。
- 如果您在語言模型上使用 `generate`,可能會有隨機取樣,因此要獲得相同的結果需要設定隨機種子。
- 如果您使用了量化併合並了權重,由於四捨五入的誤差,預計會有微小的偏差。
模型載入不正確
請確保您正確載入了模型。一個常見的錯誤是嘗試使用 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 上更新