PEFT documentation
OSF(直交部分空間ファインチューニング)
OSF(直交部分空間ファインチューニング)
直交部分空間ファインチューニング(OSF)は、継続的学習のために設計された PEFT の手法です。この手法は、パラメータの更新が以前に重要だった方向に対して直交するように制約をかけることで、追加のパラメータや過去の勾配の保存を必要とせずに、完全なファインチューニングを可能にし、かつカタストロフィック・フォーゲッティング(破滅的忘却)を防ぎます。
論文の抄録は以下の通りです:
大規模言語モデル(LLM)における継続学習は、新しいタスクに適応することで以前に学習したタスクの性能が著しく低下するカタストロフィック・フォーゲッティング(破滅的忘却)に陥りやすい。既存の手法は通常、モデルの表現能力を制限し、タスクごとに追加のパラメータを導入する低ランクのパラメータ効率の良い更新に依存しており、スケーラビリティの問題を引き起こす。これらの限界に対処するため、我々は適応型特異値分解(SVD)を活用した新しい継続フルファインチューニング手法を提案する。我々の手法は、タスク固有の低ランクパラメータ部分空間を動的に特定し、更新が以前のタスクに関連する重要な方向に対して直交するように制約することで、追加のパラメータオーバーヘッドや以前のタスクの勾配の保存なしに、干渉を効果的に最小化する。我々は、エンコーダ・デコーダ(T5-Large)モデルとデコーダのみ(LLaMA-2 7B)モデルの両方を用いて、分類、生成、推論など多様なタスクを含む標準的な継続学習ベンチマークで、我々の手法を広く評価した。実証的に、我々の手法はO-LoRAなどの最近のベースラインと比較して平均精度が最大7%高い最先端の結果を達成し、忘却をほぼ無視できるレベルまで低減することで、継続学習プロセス全体を通じてモデルの一般的な言語能力、指示追従の精度、安全性を維持する。我々の適応型SVDフレームワークは、モデルの可塑性と知識保持を効果的にバランスさせ、大規模言語モデルにおける継続学習シナリオに対して、実用的で理論的根拠があり、計算スケーラビリティのあるソリューションを提供する。
OSFの仕組み
OSFはSVDを用いて、各重み行列を高ランク(凍結)成分と低ランク(学習可能)成分に分解します:
W = U_high * S_high * V_high^T + U_low * S_low * V_low^Tここで:
U_high, S_high, V_high: 以前のタスクからの重要な方向を保持(凍結)U_low, S_low, V_low: 新タスクへの適応を許可(学習可能)
学習中、勾配は高ランク部分空間に対して直交するように射影され、更新が以前に学習された知識と干渉しないことが保証されます。
基本的な使用方法
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import OSFConfig, get_peft_model
# Load base model
model = AutoModelForCausalLM.from_pretrained("facebook/opt-125m")
# Configure OSF
config = OSFConfig(
target_modules=["q_proj", "v_proj"], # Target attention layers
effective_rank=8, # Default rank for decomposition
rank_pattern={"q_proj": 16} # Override rank for specific modules
)
# Apply OSF
model = get_peft_model(model, config)
# Train as usual
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)
tokenizer = AutoTokenizer.from_pretrained("facebook/opt-125m")
inputs = tokenizer("Hello world", return_tensors="pt", padding=True)
loss = model(**inputs, labels=inputs.input_ids).loss
loss.backward()
optimizer.step()
optimizer.zero_grad()設定オプション
ターゲットモジュール
ターゲットモジュールは、いくつかの方法で指定できます。
# Specific module names
config = OSFConfig(target_modules=["q_proj", "k_proj", "v_proj", "o_proj"])
# All linear layers
config = OSFConfig(target_modules="all-linear")
# Model-specific defaults (automatically detected)
config = OSFConfig() # Uses model-appropriate defaults有効ランクの設定
保持/学習可能な部分空間を制御します:
# Global preserved rank (applies to all target modules)
config = OSFConfig(effective_rank=16) # preserves top-16 singular directions; trains the rest
# Automatic preserved rank (50% of the smaller matrix dimension per target)
config = OSFConfig(effective_rank=None)
# Per-module preserved-rank overrides
config = OSFConfig(
effective_rank=8,
rank_pattern={
"q_proj": 16, # Higher rank for query projection
"gate_proj": 4 # Lower rank for gate projection
}
)
# Fractional preserved rank is supported (interpreted per-target as fraction * min_dim)
config = OSFConfig(effective_rank=0.8) # preserve 80% of min_dim; train remaining 20%
config = OSFConfig(rank_pattern={"q_proj": 0.5}) # preserve 50% on q_proj, others use global/default注: OSFのeffective_rankは、保持(凍結)されるランクであり、学習可能なランクではありません。学習可能なランクはmin(weight.shape) - effective_rankに等しくなります。これは、学習可能なランクを直接指定するLoRAのrとは異なります。
継続学習のためのトレーニングアドバイス
逐次タスク学習
OSFは、タスクを順番に学習するために特化して設計されています。タスクの間にSVDを再計算し、保持される部分空間が最新の重みを反映するようにします。簡単な方法の一つは、更新されたベースモデルをOSFで再度ラップすることです:
# Task 1: train on domain A with initial preserved subspace
r = 8 # initial effective rank to preserve
model = get_peft_model(base_model, OSFConfig(effective_rank=r))
train_task(model, task_1_data)
# Task 2: recompute SVD on updated weights and increase preserved subspace
base_model = model.unload() # unwrap base model without assuming internals
r += 4 # grow preserved subspace to include Task 1 knowledge
model = get_peft_model(base_model, OSFConfig(effective_rank=r))
train_task(model, task_2_data)
# Task 3: recompute again and expand preserved subspace further
base_model = model.unload()
r += 4
model = get_peft_model(base_model, OSFConfig(effective_rank=r))
train_task(model, task_3_data)タスクシーケンスへの予算配分
既知のnタスクのシーケンスで学習を行う場合、新しいタスクの学習と既存の知識の保持をバランスよく進めるために、モデルの容量を段階的に割り当てるのが効果的な戦略です。
- タスク 1:全容量を使用する(すべてを学習する)
- タスク 2: モデル容量の 1/n をフリーズし、残りの (n-1)/n の容量を学習
- タスク 3: モデル容量の 2/n をフリーズし、残りの (n-2)/n の容量を学習
- タスク n: モデル容量の (n-1)/n をフリーズし、最後のタスクに 1/n の容量を使用
このアプローチにより、各タスクに十分な学習容量を確保しつつ、以前のタスクからの知識を段階的により多く保持することが保証されます。
# Example: 4-task sequence with progressive budget allocation
n_tasks = 4
max_preserved_rank = 512 # Upper bound for preserved rank per target (heuristic)
for task_id in range(n_tasks):
# Freeze increases over time; trainable capacity shrinks
preserved_fraction = (task_id + 1) / n_tasks
preserved_rank = int(max_preserved_rank * preserved_fraction)
config = OSFConfig(
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
effective_rank=preserved_rank,
)
print(
f"Task {task_id + 1}: Preserving rank {preserved_rank} "
f"({preserved_fraction:.1%} of max_preserved_rank - {max_preserved_rank} frozen); trainable rank = min_dim - preserved_rank"
)
model = get_peft_model(base_model, config)
train_task(model, task_data[task_id])ベストプラクティス
- 効果的なランクの選択:
effective_rank=None(各ターゲットモジュールについて、小さい方の重みの次元の50%にランクが自動設定されます)から開始し、タスクの複雑さに基づいて調整します - 学習率: 標準的なファインチューニングと比較して、より小さい学習率(1e-5から1e-4)を使用します
- タスクの重要度:
rank_patternを使用して、重要なモジュールにより多くの容量を割り当てます - モデルアーキテクチャ: OSFは、アテンションとMLPの分離が明確なTransformerアーキテクチャで最も効果的に機能します
- キャパシティプランニング: 既知のタスクシーケンスに対しては、段階的な予算配分(1/n、2/n、…、(n-1)/n のフリーズ)を用いて、可塑性と安定性のバランスを取ります
メモリに関する考慮事項
OSF は重みをインプレースで変更し、パラメータを追加しないため、メモリ効率に優れています:
# Memory usage remains close to base model
print(f"Base model parameters: {base_model.num_parameters():,}")
print(f"OSF model parameters: {osf_model.num_parameters():,}") # Similar count高度な使い方
カスタムターゲットモジュール
非標準アーキテクチャを持つモデルの場合:
config = OSFConfig(
target_modules=["dense", "intermediate.dense"], # Custom layer names
effective_rank=12,
rank_pattern={"dense": 8, "intermediate.dense": 16}
)他の手法との統合
OSFは他の技術と組み合わせることができます:
# Use with gradient checkpointing for memory efficiency
# note: use `model.gradient_checkpointing_enable()` for older Transformers versions
model.activation_checkpointing_enable()
# Apply weight decay selectively (regularizes low-rank factors to limit drift/overfitting in continual updates; keep small)
optimizer = torch.optim.AdamW([
{"params": [p for n, p in model.named_parameters() if "U_low" in n], "weight_decay": 0.01},
{"params": [p for n, p in model.named_parameters() if "S_low" in n], "weight_decay": 0.001},
{"params": [p for n, p in model.named_parameters() if "V_low" in n], "weight_decay": 0.01},
], lr=1e-4)ベンチマーク概要
API
OSFConfig
クラス peft.OSFConfig
< source >( 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 = Falseeffective_rank: Optional[Union[int, float]] = Nonetarget_modules: Optional[Union[list[str], str]] = Nonerank_pattern: Optional[dict[str, Union[int, float]]] = Noneinit_weights: Optional[bool] = Truemodules_to_save: Optional[list[str]] = Nonetarget_svd_config: Optional[dict[str, int]] = None )
パラメータ
- effective_rank (int または float、任意) —
保持される SVD ランク(「高次元」部分空間)です。上位
effective_rank個の固有方向は凍結され、タスク間で保持されます。残りの次元が学習可能な低ランク部分空間を形成します。None の場合、ターゲットモジュールごとに、小さい方の重み次元の 50% がデフォルト値になります。注: これは LoRA の r(学習可能なランク)とは異なります。OSF では、学習可能なランクは min(weight.shape) - effective_rank です。 - target_modules (Union[list[str], str]、任意) — OSFを適用するモジュールの名前です。モジュール名のリストまたは“all-linear”を指定できます。
- rank_pattern (dict[str, int|float]、optional) — 特定のモジュールの effective_rank を上書きするための正規表現パターンの辞書です。値は絶対値の整数または (0, 1] の範囲の分数で、ターゲットごとの小さい方の行列の次元に対する分数として解釈されます。
直交部分空間ファインチューニング(OSF)の設定。
OSFModel
クラス peft.OSFModel
< source >( modelconfigadapter_namelow_cpu_mem_usage: bool = Falsestate_dict: dict[str, torch.Tensor] | None = None )
Orthogonal Subspace Fine-tuningを実装する最小限のチューナー。
ユーティリティ関数
重みの分解
weightのSVDを実行し、凍結部分と学習可能部分に分割します。
勾配射影
U_low と V_low の勾配を、高ランク空間に対して直交するように射影します。