PEFT documentation

トラブルシューティング

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

トラブルシューティング

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: FP16勾配のスケール解除を試みている

このエラーは、モデルが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の選択

LoRAのようなほとんどのPEFT手法は、学習可能なアダプター重みを追加することで機能します。デフォルトでは、これらの重数はfloat32 dtype (fp32)、つまり比較的高い精度で保存されます。したがって、ベースモデルがfloat16 (fp16) またはbfloat16 (bf16) で読み込まれていても、アダプター重数はfloat32になります。フォワードパス中にアダプターの結果が計算される際、入力は通常ベースモデルのdtypeであるため、必要に応じてfloat32にアップキャストされ、その後元のdtypeにキャストされます。

アダプター重みをベースモデルの低い精度、つまり float16 または bfloat16 で保持したい場合は、モデルの作成時(~get_peft_model())または読み込み時(from_pretrained())に autocast_adapter_dtype=False を渡すことができます。これには利点と欠点があります:

半精度アダプターの利点:

  • 計算がわずかに高速
  • メモリ使用量がわずかに少なくなります
  • チェックポイントのファイルサイズが小さい(半分)

半精度アダプターのデメリット:

  • わずかに悪いロス
  • オーバーフローやアンダーフローのリスクが高まる

ほとんどのユースケースにおいて、全体のランタイムとメモリコストはベースモデルのサイズとデータセットによって決定され、PEFTアダプターのdtypeはわずかな影響しか与えないことに注意してください。

トレーニングは実行されますが、モデルは改善されません

トレーニングが正常に進行しているように見える(損失が低下し、チェックポイントがエラーなく保存される)にもかかわらず、結果として得られたモデルがベースモデルより性能が向上していない場合は、トレーニング中に学習可能なパラメータが実際に変更されたかどうかを確認してください:

# before training
sample_param = next(p for p in model.parameters() if p.requires_grad)
before = sample_param.detach().clone()

# ... train for at least three optimizer steps ...

after = next(p for p in model.parameters() if p.requires_grad)
print("weights updated:", not torch.allclose(before, after.detach().cpu()))

forwardまたはbackwardフックが不足しています

重みが移動しなかった場合、考えられる原因として、パラメータがターゲットデバイス上でマテリアライズされる前に、パラメータへの参照がキャプチャされていたことが挙げられます。これは、ベースモデルのパラメータがまだターゲットデバイスに移動されていない状態で get_peft_model() によってラップされ、その後サードパーティ製ライブラリが forward/backward フック(例えば、差分プライバシーフレームワークからのサンプルごとの勾配フックなど)を登録したり、最初の forward 呼び出しの前にオプティマイザーが作成されたりした場合に発生し得ます。パラメータが後でマテリアライズされた際、これらのフックやオプティマイザーの参照は古いテンソルを指すことになり、すべての更新がサイレントな no-op となります。

これを回避するには、以下の順序を使用してください:

  1. ベースモデルを読み込み、ターゲットデバイスに移動します(例: model.to(device))。
  2. get_peft_model()でラップします。
  3. その後、フックを登録するか、オプティマイザーを作成してください。

ターゲットモジュールがモデルと一致しません

トレーニングがエラーなく実行されるものの、損失がほとんど変化しない、またはファインチューニング後にモデルの改善が見られない場合、設定されたtarget_modules(ユーザーが明示的に指定していない場合はデフォルトのtarget_modules)が、モデル内の実際のモジュール名と一致していない可能性があります。これは、標準的なTransformerモデルとは異なるレイヤー名を使用するMamba、Jamba、NemotronHなどのハイブリッドアーキテクチャでよく見られる現象です。

PEFTは、設定されたtarget_modulesがモデル内のどのモジュールとも一致しない場合のみ、エラーを発生させます。一部が一致し、一部が一致しない場合、一致しないエントリは警告なしに無言でスキップされます。モデルの大部分が、何の通知もなくフリーズしたままになる可能性があります。

最初の診断は、学習可能なパラメータの数をチェックすることです:

from peft import LoraConfig, get_peft_model

config = LoraConfig(target_modules=["q_proj", "v_proj", "gate_proj"])
model = get_peft_model(base_model, config)
model.print_healthcheck()

報告された数値が期待値より大幅に低い場合は、モデル内のアダプタ層を確認してください:

print(model.get_layer_status())

これは、各アダプタレイヤーの名前、モジュールタイプ、有効状態、およびアクティブなアダプタを示すTunerLayerStatusエントリのリストを返します。代わりに、モデルのreprを出力する(print(model))ことで、モジュール階層全体の一覧を確認でき、見慣れないアーキテクチャで正しいレイヤー名を特定するのに役立ちます。

モデル内のモジュールに対応しないtarget_modulesのエントリは、サイレントにスキップされます。target_modulesを実際のモジュール名に合わせて更新してください。

この問題は、target_modules を使用するすべての PEFT メソッド(LoRA、LoHa、IA³ など)に影響します。LoRA だけでなく、どのメソッドでも診断手順は同じです。特定のアーキテクチャが使用するモジュール名が不明な場合は、アダプタを設定する前に、モデルのドキュメントを確認するか、model.named_modules() を検査してください。

読み込んだPEFTモデルからの悪い結果

読み込んだPEFTモデルから悪い結果が得られる原因はいくつか考えられます。以下にその例を挙げます。それでも問題のトラブルシューティングができない場合は、GitHub上で他の人が同様の問題を抱えていないか確認してください。該当するものが見つからない場合は、新しい問題を報告してください。

Issueを開く際、問題を再現する最小限のコード例を提供すると非常に役立ちます。また、読み込まれたモデルがファインチューニング前のモデルと同程度の性能を示しているのか、ランダムなレベルの性能しか示していないのか、それとも期待値よりわずかに劣っているのかについても、必ず報告してください。この情報により、問題をより迅速に特定できます。

ランダムな逸脱

モデルの出力が以前のランと完全に一致しない場合、ランダム要素に問題がある可能性があります。例えば:

  1. モデルがdropoutを使用している場合など、重要となるため、.eval()モードになっていることを確認してください。
  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)

アダプターをバイパスする最適化された実行パス

一部の最適化された実行パスは、forward メソッドを呼び出す代わりに、モジュールの重みを直接読み取ります。これにより、アダプターがアクティブな状態であっても、警告なしにマージされていないアダプターによる変更がスキップされる可能性があります。この問題は、異なるモジュールや PEFT メソッドに影響を与える可能性があります。

例えば、PyTorchのネイティブなtorch.nn.TransformerEncoderは、torch.no_grad()またはtorch.inference_mode()を用いた評価時に、融合パスを使用できます。このパスでは、TransformerEncoderLayerサブモジュール内のアダプター、例えばlinear1とlinear2上のLoRAアダプターがスキップされる可能性があります。その結果、トレーニングは正しく動作しても、評価ではアダプターの変更が反映されない場合があります。再現手順については、issue #3976を参照してください。

この問題が疑われる場合は、まずget_layer_status()でアダプターが有効かつアクティブであることを確認してください。評価モードで、同じ入力に対してアダプターを有効にした場合と無効にした場合の出力を比較し、次に最適化を無効にした場合と比較してください。出力が変化するよう学習済みのアダプターを使用してください。新規初期化されたLoRAアダプターは通常、出力に影響を与えないためです。わずかな数値の差異は正常です。

eagerモードのネイティブエンコーダー上の未マージのアダプターの場合、同じテンソル入力xに対する出力を比較します:

import torch

model.eval()
previous = torch.backends.mha.get_fastpath_enabled()
try:
    with torch.inference_mode():
        torch.backends.mha.set_fastpath_enabled(True)
        fast_output = model(x)

        torch.backends.mha.set_fastpath_enabled(False)
        reference_output = model(x)
finally:
    torch.backends.mha.set_fastpath_enabled(previous)

print((fast_output - reference_output).abs().max().item())

バックエンドフラグが有効化されているだけでは、フュージョンパスが実行されることを意味しません。その使用は、モデル、入力、およびランタイムの条件にも依存します。外側のモジュールのforward実装を調べ、直接の重みアクセスやフュージョン演算子の呼び出しを確認してください。フォワードフックを用いたデバッグには注意が必要です。フックの登録自体が最適化を無効にし、調査中の動作を変えてしまう可能性があるためです。

Eager モードのネイティブエンコーダーでは、torch.backends.mha.set_fastpath_enabled(False) を設定することで、アダプターをマージせずに保つワークアラウンドとして機能します。この設定はプロセス全体に適用されるため、他のモデルやパフォーマンスに影響を与える可能性があり、並行リクエストによって独立して切り替えるべきではありません。その他の最適化パスについては、該当する最適化を無効にするか、アダプター付きモジュールを呼び出す実装を使用してください。PEFT メソッドと量子化設定が許可している場合、merge_and_unload() を使用してアダプターをマージし、返されたモデルを推論に使用することもできます。この場合、アダプターの変更はベースの重みに含まれます。マージするとアダプターのラッパーが削除されるため、アダプターを切り替えたりトレーニングを再開したりする必要がある場合は、元のチェックポイントを保持してください。

ランダム初期化されたレイヤー

一部のタスクでは、ランダムに初期化されたレイヤーを考慮するために、設定内で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に言及したレイヤーを追加する必要があります。

healthcheck() は、この誤りの最も一般的な形式に対して TASK_HEAD_NOT_SAVED の警告を報告します。これは、ベースモデルの読み込み時に新規に初期化されたシーケンス分類、トークン分類、または質問応答のヘッドが modules_to_save に含まれていない場合です。例えば、task_type が設定されていない場合などが該当します。ベースモデルが設定内でそのアーキテクチャを列挙していない場合、このチェックはスキップされます。

例えば、DeBERTaアーキテクチャを使用したシーケンス分類のモデルを読み込むと、以下の重みが新規に初期化されたという警告が表示されます: ['classifier.bias', 'classifier.weight', 'pooler.dense.bias', 'pooler.dense.weight']。これにより、classifierおよびpoolerレイヤーをmodules_to_save=["classifier", "pooler"]に追加すべきことがわかります。

語彙の拡張

多くの言語ファインチューニングタスクでは、新しいトークンが導入されるため、モデルの語彙を拡張する必要があります。これには、新しいトークンを考慮して埋め込み層を拡張する必要があり、ファインチューニング手法によっては、アダプターを保存する際にアダプター重みのほかに埋め込み層も保存する必要があります。パラメータの効率的な使用の観点から、これを達成する方法はいくつかあります。

  • trainable tokens、指定されたトークンのみを学習し、必要に応じて更新された値のみを保存します
  • 埋め込み行列に対してアダプタを学習し、必要に応じて更新された値のみを保存
  • embedding層のフルファインチューニング

学習可能なトークンを使用する

学習可能なトークンから始めましょう。ここでは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チェックポイントを読み込むことができます。

完全な例については、このノートブックをご覧ください。

完全ファインチューニング

フルファインチューニングはVRAMやストレージスペースの観点でコストが高くなりますが、他の方法がすべて失敗した場合の最終手段として、こちらに切り替えて動作するか確認できます。modules_to_saveに埋め込み層の名前を追加することで実現できます。なお、tied層も追加する必要があります。例えば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’]。予測や推論にこのモデルを使用できるようにするには、下流タスクでこのモデルを TRAIN する必要があるでしょう。

一見すると恐ろしいように見えますが、おそらく心配する必要はありません。この警告はTransformersから発行されるものであり、PEFT固有の警告ではありません。ベースモデルにランダムに初期化された分類ヘッド(score)が接続されていることを示しており、このヘッドは妥当な予測を生成するために訓練される必要があることを通知しています。

モデルの学習前にこの警告が表示された場合、PEFT config に task_type 引数を正しく渡している場合、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 の重みが読み込まれることを認識していないため、警告が表示されます。

常に、モデルが推論で正しく動作することを確認するために、いくつかの検証を実行するのがベストプラクティスです。

トレーニングのヘルスチェックを実行する

学習の前に、print_healthcheck() を使用して、PEFT モデルの状態に関する簡潔で人間が読みやすいサマリーを取得します。これはモデルおよびレイヤーのステータス API(次のセクションを参照)から導出され、アダプターの不整合、マージされたアダプター、または学習可能なパラメータ数の異常といった疑わしい状態をフラグ付けします。

peft_model.print_healthcheck()

レポート末尾に「Findings」が含まれているかどうかに特に注意してください。これは、不審な点が見つかった場合にのみ含まれます。

JSON シリアライズ可能な辞書形式の出力を希望する場合は、代わりに healthcheck() を使用してください:

healthcheck = peft_model.healthcheck()
print(healthcheck.get("findings", "all good")

このチェックは、モデルとそのアダプターのみを検査します。トレーニングループ、オプティマイザー、データセットの検証は行われません。

レイヤーとモデルのステータスを確認

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"の場合、少なくとも1つのアダプターについて、一部のターゲットモジュールにはマージされているが、他のモジュールにはマージされていないことを示します。その結果、推論の出力は正しくない可能性が高いです。

この問題を解決する最善の方法は、モデル全体とアダプターチェックポイントを再読み込みすることです。モデルに対して誤った操作を行わないようにしてください。例えば、一部のモジュールではアダプターを手動でマージするが、他のモジュールでは行わない、といった操作は避けてください。

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, 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()に渡すことができます。

このオプションが異なるユースケースで良好に動作する場合は、将来的にアダプターの読み込みのデフォルトとなる可能性があります。

再現性

バッチノルムを使用するモデル

ベースモデルがバッチノルム(例: torch.nn.BatchNorm1d または torch.nn.BatchNorm2d)を使用している学習済み PEFT モデルを読み込む際、完全に同一の出力を再現できない場合があります。これは、バッチノルム層が学習中にランニング統計を記録するものの、これらの統計が PEFT チェックポイントの一部ではないためです。したがって、PEFT モデルを読み込むと、ベースモデルのランニング統計(つまり、PEFT による学習前のもの)が使用されます。

ユースケースによっては、これが大きな問題にならない場合もあります。ただし、出力を100%再現可能にする必要がある場合は、modules_to_saveにバッチノルム層を追加することで実現できます。以下は、resnetとLoRAを用いたこの方法の例です。modules_to_save=["classifier", "normalization"]を設定している点にご注意ください。タスクが画像分類であるため"classifier"引数が必要であり、PEFTチェックポイントにバッチノルム層が保存されることを保証するために"normalization"引数を追加しています。

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"],
),

使用するモデルの種類によって、batch norm層の名前が"normalization"と異なる場合があります。モデルのアーキテクチャと名前が一致していることを確認してください。

バージョン不一致

予期しないキーワード引数のために設定の読み込み中にエラーが発生しました

以下のようなエラーに遭遇した場合、読み込もうとしているアダプターは、システムにインストールされているPEFTのバージョンよりも新しいバージョンの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のアップグレードです。

アダプター処理

複数のアダプタを同時に使用

PEFTでは、同じモデルに複数のアダプターを作成できます。これは多くの状況で有用です。例えば、推論時には、各ファインチューニング済みモデルごとにベースモデルを一度ずつ読み込むよりも、同じベースモデルから2つのファインチューニング済みモデルをサーブする方が、メモリコストを抑えられる場合があります。ただし、複数のアダプターを同時にアクティブにすることも可能です。これにより、モデルはこれらのアダプターすべての学習成果を同時に活用できます。例えば、拡散モデルを持っている場合、スタイルを変更するためのLoRAアダプターと、対象を変更するための別のLoRAアダプターを同時に使用したい場合があります。

PEFTのすべての手法(LoRA、LoHa、IA³など)において、複数のアダプタを同時に有効にすることは一般的に可能ですが、プロンプト学習手法(p-tuning、prefix tuningなど)は例外です。以下の例では、その実現方法を示しています。

from transformers import AutoModelForCausalLM
from peft import PeftModel

model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
model = PeftModel.from_pretrained(base_model, lora_path_0)  # default adapter_name is 'default'
model.load_adapter(lora_path_1, adapter_name="other")
# the 'other' adapter was loaded but it's not active yet, so to activate both adapters:
model.base_model.set_adapter(["default", "other"])

上記の例では、model.base_model.set_adapter(["default", "other"]) を呼び出す必要があることがわかります。model.set_adapter(["default", "other"]) を呼び出せないのはなぜでしょうか。残念ながら、前述したように、一部の PEFT メソッドでは一度に複数のアダプターを有効にできないため、これはできません。

同時に2つのアダプターをトレーニングすることも可能ですが、両方のアダプターの重みがオプティマイザーに認識されていることを確認してください。そうしないと、片方のアダプターのみが更新されてしまいます。

from transformers import AutoModelForCausalLM
from peft import LoraConfig, get_peft_model

model_id = ...
base_model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config_0 = LoraConfig(...)
lora_config_1 = LoraConfig(...)
model = get_peft_model(base_model, lora_config_0)
model.add_adapter(adapter_name="other", peft_config=lora_config_1)

ここで呼び出すとします:

from transformers import Trainer

trainer = Trainer(model=model,  ...)
trainer.train()

または

optimizer = torch.optim.AdamW([param for param in model.parameters() if param.requires_grad], ...)

その場合、2番目のLoRAアダプター("other")は学習されません。これは、この時点で非アクティブであり、そのパラメーターのrequires_grad属性がFalseに設定されているため、オプティマイザーが無視するからです。したがって、学習すべきすべてのアダプターをオプティマイザーの初期化前にアクティブにしてください。

# activate all adapters
model.base_model.set_adapter(["default", "other"])
trainer = Trainer(model=model,  ...)
trainer.train()

このセクションでは、同じモデルに同じタイプの複数のアダプタを使用する方法について説明します。例えば、複数のLoRAアダプタを同時に使用する場合です。同じモデルに異なるタイプのアダプタを使用する場合(例えば、LoRAアダプタとLoHaアダプタを1つずつ使用する場合)には適用されません。その場合は、PeftMixedModelを参照してください。

アクティブなアダプタの一部のみをマージする

アダプター重みがベース重みにマージされると、影響を受けるレイヤーはフォワードパス中にアダプターを適用しなくなり、マージされたベース重みのみを使用します。したがって、複数のアダプターがアクティブな状態で、そのうち一部だけがマージされている場合、まだアクティブだがマージされていないアダプターは、サイレントに適用されません:

model = get_peft_model(base_model, lora_config_0, adapter_name="default")
model.add_adapter("other", lora_config_1)
model.base_model.set_adapter(["default", "other"])
output_both = model(**inputs)

model.merge_adapter(adapter_names=["default"])
output_merged = model(**inputs)  # this is *not* the same as output_both

ここでは、"other"がまだアクティブとしてリストされているにもかかわらず、output_mergedは"default"アダプター単独の出力です。

>>> model.get_layer_status()[0].active_adapters
['default', 'other']
>>> model.get_layer_status()[0].merged_adapters
['default']

アクティブでない非アクティブなアダプターをマージした場合も同様です。マージ後、モデルはマージされたアダプターの出力を返し、アクティブなアダプターは無視されます。どちらの場合も、エラーや警告は発生しません。アダプター自体の重みには影響がないため、unmerge_adapter()を呼び出すことで、統合された出力が復元されます。

これを回避するには、すべてのアクティブなアダプターをマージするか(adapter_namesを指定せずにmodel.merge_adapter()を呼び出す)、またはアクティブなアダプターを変更する前にアンマージしてください。

この動作は、マージをサポートするすべてのPEFT手法(LoRA、LoHa、LoKr、IA³、OFTなど)で一貫しています。これらはすべて、forward passにおいて同じロジックを共有しているためです。

この動作は混乱を招くため、PEFT v1.0 のリリースで変更される予定です。

Update on GitHub