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], 可选) — 一个正则表达式模式字典,用于覆盖特定模块的 effective_rank。值可以是绝对整数,也可以是 (0, 1] 区间内的分数,后者被解释为每个目标较小矩阵维度的比例。
正交子空间微调(OSF)的配置。
OSFModel
类 peft.OSFModel
< source >( modelconfigadapter_namelow_cpu_mem_usage: bool = Falsestate_dict: dict[str, torch.Tensor] | None = None )
一个实现了正交子空间微调的最小化调优器。
实用函数
权重分解
对 weight 执行 SVD,并将其拆分为冻结部分和可训练部分。
梯度投影
将U_low和V_low的梯度投影到与高秩空间正交的方向上。