PEFT 中 PSOFT 微调实战:主元子空间上的高效正交微调(PsoftConfig 详解与 LLaMA 训练示例)
【免费下载链接】peft🤗 PEFT: State-of-the-art Parameter-Efficient Fine-Tuning.项目地址: https://gitcode.com/gh_mirrors/pe/peft
本文围绕 PEFT 仓库中 PSOFT(Principal Subspace adaptation Orthogonal Fine-Tuning,主元子空间正交微调)的官方示例 examples/psoft_finetuning/README.md 展开,讲清楚 PSOFT 的核心思想(把 LoRA 的低秩结构与 OFT 的正交性约束结合)、PsoftConfig全部关键参数的语义与默认值,并给出从 125M 小模型快速上手到 LLaMA-3.2-3B 完整训练的复现路径。读完本文,你可以直接复制仓库中的训练脚本完成一次 PSOFT 微调,并理解底层 A/B 分解缓存、Cayley 参数化与 Cayley–Neumann 近似的实现细节。
1. PSOFT 是什么:介于 LoRA 与 OFT 之间的正交微调
PSOFT 的目标是在保持 OFT(Orthogonal Fine-Tuning)核心原则——保留预训练权重列向量之间的几何关系——的同时,在参数、计算与内存三个维度上取得均衡。与依赖稀疏化设计的 OFT 变体(如 OFTv2、BOFT、GOFT)不同,PSOFT 从"低秩主元子空间"的视角出发,把正交变换限制在预训练权重的 top-r 主元子空间内,从而在 LoRA 与 OFT 之间架起桥梁。官方文档页 PSOFT 参考文档 还给出了它在 GLUE、VTAB-1K、GSM8K、MATH 及常识推理等基准上的有效性说明。
具体做法是:对权重矩阵做 SVD,$W_{\text{pre}} = U S V^\top$,取 top-r 奇异分量构造主元子空间 $W_{\text{pri}} = U_r S_r V_r^\top = AB$,于是
$$W_{\text{pre}} = AB + W_{\text{res}}$$
训练中冻结 $A$、$B$ 与残差 $W_{\text{res}}$,只训练中间的 $R$(以及可选的幅值向量 $\alpha$、$\beta$)。文档页区分两种模式:
- PSOFT-SO(严格正交):$W_{\text{ps-tuned}} = A R B + W_{\text{res}}$;
- PSOFT-RO(放松正交):$W_{\text{ps-tuned}} = A, \mathrm{diag}(\alpha), R, \mathrm{diag}(\beta), B + W_{\text{res}}$,两个低维可调向量在训练中逐步放松正交约束以提升任务适应性。
为兼容 PEFT 框架"加法式权重更新"的约定,实现层面采用等价形式:
$$W_{\text{ps-tuned}} = W_{\text{pre}} + A (R - I_r) B$$
这一实现细节可以从源码直接验证:layer.py 中Linear.get_delta_weight计算的就是B @ (R - I) @ A * scaling。仓库对 PSOFT 的适用边界有两条明确限制:仅支持nn.Linear层、不支持量化层(见 model.py 的dispatch_default与 layer.py 中对权重的 dtype 检查)。
2. 快速上手:四行配置接入 PSOFT
以下是最小可运行流程(继承自 README 的 Quick Start 章节),使用 TRL 的SFTTrainer在 IMDB 数据上训练:
import torch from peft import PsoftConfig, get_peft_model from transformers import AutoTokenizer, AutoModelForCausalLM from trl import SFTConfig, SFTTrainer from datasets import load_dataset model_name = "facebook/opt-125m" model = AutoModelForCausalLM.from_pretrained(model_name) tokenizer = AutoTokenizer.from_pretrained(model_name) tokenizer.pad_token_id = tokenizer.eos_token_id psoft_config = PsoftConfig( r=32, psoft_alpha=32, ) peft_model = get_peft_model(model, psoft_config) peft_model.print_trainable_parameters() dataset = load_dataset("imdb", split="train[:1%]") training_args = SFTConfig(dataset_text_field="text", max_length=128) trainer = SFTTrainer( model=peft_model, args=training_args, train_dataset=dataset, processing_class=tokenizer, ) trainer.train() peft_model.save_pretrained("psoft-opt-125m")关键点说明:
PsoftConfig在 config.py 中定义,__post_init__会把peft_type固定为PeftType.PSOFT,并做一系列参数合法性校验(r > 0、初始化方式与 SVD 后端枚举、Cayley–Neumann 参数范围等);- 默认
r=32、psoft_alpha=32,二者相等使缩放因子scaling = psoft_alpha / r = 1,与文档"缩放因子通常设为 r"的最佳实践一致(scaling的计算见 layer.py 的update_layer); - 只传
r与psoft_alpha时,其余参数走默认值:ab_svd_init="psoft_init"、psoft_svd="full"、psoft_orth=True、psoft_mag_a/b=True,即默认开启"放松正交"(RO)模式。
完整参数与文档化配置示例
PSOFT 参考文档 给出了带完整注释的配置写法,覆盖了所有核心开关:
config = PsoftConfig( r=32, # 可训练矩阵 R 的维度 psoft_alpha=32, # 缩放因子(通常设为 r) target_modules=["q_proj", "v_proj"], # 目标注意力投影层 ab_svd_init="psoft_init", # 主元子空间初始化 psoft_svd="full", # SVD 方法 psoft_orth=True, # 启用正交 R(Cayley 参数化) psoft_mag_a=True, # 启用可调向量 alpha psoft_mag_b=True, # 启用可调向量 beta use_cayley_neumann=False, # 关闭 Cayley–Neumann 近似 num_cayley_neumann_terms=5, # Neumann 级数项数 cayley_neumann_eps=None, # 数值稳定性阈值 )结合 config.py 的字段定义,各参数语义与默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
r | 32 | 正交变换 R 的维度,决定适配器容量;建议 32–128(简单任务)或 64–256(复杂任务) |
psoft_alpha | 32 | 缩放因子,语义同 LoRA alpha;scaling = psoft_alpha / r |
psoft_dropout | 0.0 | 作用在 A 路径上的 Dropout |
ab_svd_init | "psoft_init" | "psoft_init":A 行正交的非对称 SVD 初始化(严格正交,PSOFT 默认);"pissa_init":A/B 对称分解(标准 PiSSA 风格) |
psoft_svd | "full" | "full"用torch.linalg.svd;"lowrank"用torch.svd_lowrank |
psoft_svd_lowrank_niter | 10 | 仅psoft_svd="lowrank"时生效的幂迭代次数 |
random_seed | 0 | 仅psoft_svd="lowrank"时用于固定随机投影,保证保存/加载后结果可复现 |
psoft_orth | True | True 时 R 经 Cayley 参数化保持正交;False 时 R 为自由矩阵 |
psoft_mag_a/psoft_mag_b | True / True | 在 R 的输入/输出侧各学一个对角缩放向量(RO 模式的 α、β) |
use_cayley_neumann | False | 用 Neumann 级数代替矩阵求逆,提升大 rank 下的计算效率 |
num_cayley_neumann_terms | 5 | 仅use_cayley_neumann=True时生效,项数越多正交近似误差越小 |
cayley_neumann_eps | None | 对生成矩阵 Q 的 Frobenius 范数上界;设为 (0,1) 内的值(如 0.9)会在范数超限时对 Q 重缩放 |
fan_in_fan_out | False | 被替换层以 (fan_in, fan_out) 存储权重时置 True |
layers_to_transform/layers_pattern | None | 限定作用层;与字符串形式的target_modules(正则)互斥,__post_init__中会直接抛错 |
这些校验并非纸面约束,tests/test_initialization.py 中有对应测试逐一验证:例如psoft_svd="full"却修改了psoft_svd_lowrank_niter会触发 UserWarning,use_cayley_neumann=True时num_cayley_neumann_terms<=0或cayley_neumann_eps不在 (0,1) 会直接抛出 ValueError。
3. 底层实现:正交层如何构造 R,A/B 如何初始化
3.1 OrthLayer:用 r(r-1)/2 个参数表示 r×r 正交矩阵
可训练的核心是 layer.py 中的OrthLayer。当psoft_orth=True时,它并不直接存储完整的 r×r 矩阵,而是只存一个上三角向量(r*(r-1)//2个参数),由_skew_symmetric还原为反对称矩阵 Q,再经Cayley 变换$R = (I+Q)(I-Q)^{-1}$ 得到正交矩阵——这就是"通过 Cayley 参数化保证 R 正交"的来源,权重置零即对应 $R=I$(初始时更新量为零,训练从预训练模型无损出发)。
两种求 R 的路径:
- 精确路径(
use_cayley_neumann=False):调用torch.linalg.solve(I-Q, I+Q, left=False),半精度下会自动升 fp32 求解; - Cayley–Neumann 近似路径(
use_cayley_neumann=True):用级数 $R \approx I + 2(Q + Q^2 + \cdots + Q^{t-1}) + Q^t$ 代替求逆,num_cayley_neumann_terms控制截断项数 t;cayley_neumann_eps非 None 时会先把 $|Q|_F$ 投影到该上界以内,改善数值稳定性(源码注释说明该思路借鉴自 OFT v2 的 Cayley/Neumann 参数化)。
随后get_matrix按开关把vector_b(按行缩放)与vector_a(按列缩放)乘到 R 上,即 RO 模式的 α/β。reset_parameters中:正交模式权重清零,非正交模式初始化为单位阵,两个幅值向量一律初始化为 1(初始恒等)。
3.2 A/B 缓存:主元子空间分解与两种初始化
update_layer会先检查r <= min(in_features, out_features),然后调用_build_psoft_ab_cache_buffers(见 layer.py)构建 A、B 缓存:
- 把基座权重转成 fp32 并取 (out, in) 布局(若基座权重不是 float32/float16/bfloat16 会抛 TypeError,这正是"不支持量化层"的落点);
- 按
psoft_svd选择后端:full用torch.linalg.svd取前 r 个奇异分量;lowrank用torch.svd_lowrank,并在torch.random.fork_rng内用random_seed播种,保证随机投影确定性(因为torch.svd_lowrank不接受 generator 参数,且必须覆盖 SVD 实际运行的设备); - 按
ab_svd_init组装:psoft_init:A = U_h[:r](行正交),B = V_r @ diag(Sr / scaling),把缩放吸进 B;pissa_init:取sqrt(Sr_scaled),A = diag(s)@U_h、B = V_r@diag(s),得到对称 A/B(无严格正交约束的 PiSSA 风格)。
A、B 存入BufferDict缓存并冻结,训练只更新psoft_R。前向传播(Linear.forward)的增量计算为xa = x @ A.T→xr = R(xa)→delta_y = (xr - xa) @ B.T * scaling,再与基座输出相加——这正是加法式形式 $W_{pre} + A(R-I)B$ 的逐元素实现。
3.3 合并与 LoRA 转换
Linear.merge/unmerge基于get_delta_weight把 $\Delta W$ 加减回基座权重,支持safe_merge(合并后检测 NaN)。此外supports_lora_conversion返回 True,即 PSOFT 适配器可转换为 LoRA 形式使用。
4. 完整训练:LLaMA-3.2-3B 上的复现命令
仓库提供了带完整参数解析的训练脚本 psoft_finetuning.py,其ScriptArguments继承SFTConfig,把上述 PSOFT 参数全部暴露为命令行参数,并额外处理了数据侧逻辑:数据集若没有text列,可用--dataset_field "[input, output]"自动拼成### USER: ...\n### ASSISTANT: ...的 SFT 文本。脚本开头还有一道防御性检查:只接受bf16/fp16/fp32三种精度,从源头杜绝误加载量化模型。
README 中给出的 LLaMA-3.2-3B 命令可直接复制运行:
python psoft_finetuning.py \ --base_model_name_or_path meta-llama/Llama-3.2-3B \ --output_dir ./outputs/psoft-llama3.2-3b-imdb \ --data_path imdb \ --dataset_split "train[:1%]" \ --max_length 128 \ --num_train_epochs 1 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 8 \ --learning_rate 5e-4 \ --bits bf16 \ --r 128 \ --psoft_alpha 128 \ --target_modules q_proj v_proj要点:r=128与psoft_alpha=128成对出现(scaling=1);目标模块限定为q_proj、v_proj(脚本默认值也是这两个);bits=bf16指定 bf16 精度加载基座,device_map="auto"自动分片。训练结束后,适配器与 tokenizer 会保存到output_dir/psoft_ft下。
脚本中各 PSOFT 相关参数的默认值与 config.py 保持一致:ab_svd_init="psoft_init"、psoft_svd="full"、psoft_orth=True、psoft_mag_a/b=True、use_cayley_neumann=False、num_cayley_neumann_terms=5;psoft_svd_lowrank_niter默认为 None,仅在用户显式设置(通常配合--psoft_svd lowrank)时才传入配置。
5. 官方最佳实践与适用建议
以下五条 Best Practices 完整继承自 README,并结合源码给出对应落点:
- 秩的选择(Rank Choice):较小秩(32–128)适合简单任务;较大秩(64–256)表达力更强,但参数与计算开销同步上升。
r直接决定 R 的规模(OFT 参数个数为 $r(r-1)/2$),也受r <= min(in, out)约束。 - 缩放因子(Scaling Factor):
psoft_alpha通常设为 r,使scaling = psoft_alpha / r = 1; - 学习率(Learning Rate):使用常规学习率区间(如
1e-4到5e-3)即可获得稳定训练(LLaMA 示例用的5e-4落在此区间); - SVD 初始化(SVD Initialization):
lowrank后端比full更省内存与算力,更适合大模型。注意lowrank结果依赖随机投影,仓库通过random_seed参数(默认 0)保证保存的适配器在重新加载后可复现输出; - Cayley–Neumann 近似:秩较大时开启
use_cayley_neumann=True可显著提升计算效率(免去 r×r 求逆),小秩时收益有限;Neumann 级数项数取 5(默认值)通常能在精度与效率间取得平衡。需要更强数值稳定性时可设置cayley_neumann_eps(如 0.9)。
6. 验证、测试与基准结果
仓库内有多处可交叉验证 PSOFT 行为的证据:
- 配置校验测试:tests/test_initialization.py 覆盖了非法参数(terms 非正、eps 越界、niter/eps 在不适用场景下的告警)以及配置构造;
- 模型级测试:tests/test_custom_models.py 中包含
{"target_modules": ["lin0"], "r": 4, "psoft_alpha": 4, "psoft_svd": "lowrank", "psoft_svd_lowrank_niter": 10}与use_cayley_neumann=True等多种 PSOFT 配置的组合测试; - 方法对比基准:仓库 method_comparison/MetaMathQA 目录下有 PSOFT 在 LLaMA-3.2-3B 上的实测记录,如 psoft--llama-3.2-3B-default.json 与 psoft--llama-3.2-3B-fast.json(后者对应"fast"变体,可对照本文的低秩/Cayley–Neumann 效率选项);
- 架构映射:从 constants.py 看,
TRANSFORMERS_MODELS_TO_PSOFT_TARGET_MODULES_MAPPING直接复用了 LoRA 的模型-目标模块映射,因此未显式指定target_modules时,PSOFT 会按已知架构自动选择目标层,未知架构则报错要求手动指定。
7. 引用信息
如在使用 PSOFT 时引用该方法,可参考 README 中的 BibTeX 信息:
@inproceedings{wu2026efficient, title={Efficient Orthogonal Fine-Tuning with Principal Subspace Adaptation}, author={Wu, Fei and Hu, Jia and Min, Geyong and Wang, Shiqiang}, booktitle={The Fourteenth International Conference on Learning Representations}, year={2026} }小结:PSOFT 用"SVD 主元子空间 + 冻结 A/B + 可训练小尺寸正交 R(可选 α/β 放松)"的组合,把 OFT 的正交语义保留与 LoRA 式的低参数量结合起来。实操上记住三件事即可:默认配置(r=32, psoft_alpha=32, psoft_orth=True, mag_a/b=True)适合快速实验;大模型把psoft_svd切到lowrank;大 rank 时打开use_cayley_neumann。注意其仅支持nn.Linear且不支持量化基座这两条硬边界。
【免费下载链接】peft🤗 PEFT: State-of-the-art Parameter-Efficient Fine-Tuning.项目地址: https://gitcode.com/gh_mirrors/pe/peft
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考