【Bug已解决】Proposal: add MiCA (Minor Component Adaptation) as a PEFT method 解决方案

📅 2026/7/24 22:22:14
【Bug已解决】Proposal: add MiCA (Minor Component Adaptation) as a PEFT method 解决方案
【Bug已解决】Proposal: add MiCA (Minor Component Adaptation) as a PEFT method 解决方案一、现象长什么样你想在 PEFT 里加一个叫MiCAMinor Component Adaptation次要成分适配的新方法。它和 PiSSAPrincipal Singular Values/Singular Vectors Adaptation主成分适配正好互补PiSSA 用权重矩阵 SVD 的主成分最大奇异值方向初始化 LoRA而 MiCA 用次要成分最小奇异值方向——即那些对原任务贡献小、但留给新任务调整空间大的方向——来初始化/约束适配。但接入时发现MiCA 和 PiSSA 都基于 SVD 初始化只是取哪一端奇异值不同实现容易和 PiSSA 混淆次要成分的数值很小奇异值接近 0直接拿来做初始化可能导致梯度/数值不稳定需要决定“训练时是否固定主成分、只训次要成分方向”保存/加载的 state_dict 键要和 PiSSA 区分。本文给出 MiCA 作为 PEFT 方法的完整设计与落地。二、背景权重矩阵W ≈ U Σ VᵀSVD。PiSSA 的思路取前r个最大奇异值对应的方向作为 LoRA 的初始化A、B从这个主成分里来因为这些方向承载原任务主要信息微调时从“已学好的主成分”出发收敛快。MiCA 反过来取最小的r个奇异值方向次要成分。直觉是——原任务主要信息在主成分次要成分对原任务几乎无贡献意味着它们是“冗余/可塑”的方向在次要成分方向上做适配对原任务破坏最小知识遗忘少又能给新任务提供“_free”的表达空间某些场景下原任务已很饱和、只需小幅调整MiCA 比 PiSSA 更稳。实现上MiCA 层冻结主成分部分用B·A初始化为次要成分方向A←V_minᵀ相关B←U_min · Σ_min训练时只更新这组 LoRA。难点在“次要成分奇异值小→初始化数值小→需合理缩放避免梯度消失”。三、根因为什么接入容易错根因 A把 MiCA 当 PiSSA 用取反方向即可但易取错端最直接。SVD 排序后取最后r个而非前r个索引写反就变成 PiSSA。根因 B次要成分奇异值小初始化数值不稳定Σ_min接近 0B·A初始化值极小前向输出接近 0梯度也小训练可能不更新。需要缩放处理。根因 C训练目标不明确训次要成分还是全训若冻结主成分只训次要成分 LoRA需明确requires_grad设置否则退化。根因 Dstate_dict 键与 PiSSA 混淆MiCA/PiSSA 都用lora_A/lora_B存需加标识区分方法避免加载错位。根因小结MiCA 取 SVD 的次要最小奇异值方向初始化 LoRA与 PiSSA 取主成分相反难点次要成分数值小→初始化需缩放、训练目标明确、requires_grad正确修复正确取 SVD 末端 缩放初始化 冻结主成分只训次要成分 键区分。四、最小可运行复现下面脚本演示 MiCA 的 SVD 取次要成分初始化 缩放对比 PiSSAimport torch import torch.nn as nn def svd_init(W2d, r, modemica): 对 2D 权重做 SVD按 mode 取主/次成分初始化 A,B U, S, Vh torch.svd(W2d) if mode pissa: idx list(range(r)) # 前 r 个主成分 else: # mica idx list(range(len(S) - r, len(S))) # 后 r 个次要成分 U_r U[:, idx] # [out, r] S_r S[idx] # [r] V_r Vh[idx, :] # [r, in] # 把 W_r U_r diag(S_r) V_r 拆成 B·A # A V_rᵀ * sqrt(S_r), B U_r * sqrt(S_r) sqrtS torch.sqrt(S_r.clamp(min1e-8)) A (V_r.t() * sqrtS) # [in, r] B (U_r * sqrtS) # [out, r] return A, B, S_r def demo(): torch.manual_seed(0) W torch.randn(8, 16) # 2D 权重 Ap, Bp, Sp svd_init(W, r2, modepissa) Am, Bm, Sm svd_init(W, r2, modemica) print(PiSSA 奇异值:, Sp.round(3).tolist()) print(MiCA 奇异值:, Sm.round(3).tolist()) # 应明显更小次要成分 # 缩放MiCA 奇异值小加 scaling 防止梯度消失 mica_scaling (Sp.mean() / (Sm.mean() 1e-8)).item() print(MiCA 相对缩放系数:, round(mica_scaling, 2)) # 重建验证 W_p Bp Ap W_m Bm Am print(PiSSA 重建与 W 主成分一致性差:, (W_p - (U_recon : W)).abs().sum().item() if False else 略) if __name__ __main__: demo()运行后MiCA 取到的奇异值明显小于 PiSSA次要成分并给出相对缩放系数证明“取末端 缩放”可行。五、解决方案第一层最小直接修复把 MiCA 接成 PEFT tuner基于 SVD 取次要成分import torch import torch.nn as nn class MiCALayer(nn.Module): def __init__(self, base, r, scaling1.0): super().__init__() self.base_layer base base.weight.requires_grad False W2d base.weight.detach().reshape(base.weight.shape[0], -1) U, S, Vh torch.svd(W2d) idx list(range(len(S) - r, len(S))) # 次要成分末端 sqrtS torch.sqrt(S[idx].clamp(min1e-8)) self.A nn.Parameter((Vh[idx, :].t() * sqrtS)) # [in, r] self.B nn.Parameter((U[:, idx] * sqrtS)) # [out, r] self.scaling scaling def forward(self, x): return self.base_layer(x) (x self.A.t()) self.B.t() * self.scalingConfig 区分方法避免与 PiSSA 混淆class MiCAConfig(PeftConfig): def __init__(self, r4, target_modulesNone, scaling1.0, **kw): super().__init__(**kw) self.peft_type PeftType.MICA self.r r self.target_modules target_modules or [lin] self.scaling scaling # MiCA 通常需更大 scaling 抵消小奇异值六、解决方案第二层结构性改进6.1 用缩放抵消小奇异值# MiCA 次要成分奇异值小scaling 设大些保证梯度 scaling max(1.0, (principal_mean / minor_mean).item())6.2 与 PiSSA 共用 SVD 基础设施def svd_init(W2d, r, mode): # modepissa 取前 rmodemica 取后 r共用一套 ...6.3 键区分与合并def get_mica_state_dict(model): return {k: v for k, v in model.state_dict().items() if lora in k}七、解决方案第三层断言 / CI 守护import torch import pytest def test_mica_uses_minor_components(base_factory): layer MiCALayer(base_factory(8, 16), r2) # 验证 A/B 来自 SVD 末端重建应接近次要成分方向 W2d layer.base_layer.weight.detach().reshape(8, -1) U, S, Vh torch.svd(W2d) # MiCA 的奇异值应 主成分奇异值 minor S[-2:] assert minor.max() S[:2].max() def test_mica_scaling_non_trivial(layer_factory): layer layer_factory(8, 16, r2, scaling4.0) assert layer.scaling 1.0, MiCA 需要缩放抵消小奇异值 def test_mica_initialization_finite(layer_factory): layer layer_factory(8, 16, r2) assert torch.isfinite(layer.A).all() and torch.isfinite(layer.B).all() def test_mica_distinct_from_pissa(base_factory): mica MiCALayer(base_factory(8, 16), r2) pissa PiSSALayer(base_factory(8, 16), r2) assert not torch.allclose(mica.A, pissa.A), MiCA 与 PiSSA 初始化应不同CI 跑这四条MiCA 的“取次要成分 缩放 与 PiSSA 区分”被守住。八、排查清单实现 MiCA 接入 PEFT 时查取的是 SVD 末端次要成分吗别和 PiSSA前端主成分混淆。次要成分奇异值小缩放了吗设合适 scaling 防梯度消失。只训次要成分 LoRA 吗base 冻结requires_grad正确。与 PiSSA 键/Config 区分了吗避免加载错位。初始化数值有限吗sqrt(S.clamp(min1e-8))防 0。重建接近次要成分方向吗断言验证。注册到 mapping 了吗get_peft_model可用。九、小结“Proposal: add MiCA (Minor Component Adaptation) as a PEFT method” 是把次要成分适配取 SVD 最小奇异值方向初始化 LoRA作为新 PEFT 方法的工程实现MiCA 与 PiSSA 互补PiSSA 取 SVD 主成分最大奇异值MiCA 取次要成分最小奇异值对原任务破坏小、留给新任务可塑空间难点次要成分奇异值小→需缩放防梯度消失、明确只训次要成分 LoRA接入SVD 取末端r个方向初始化 A/BConfig 区分方法合并逻辑同 LoRA用“取末端 缩放有限 与 PiSSA 区分”的断言守护。一句话MiCA 用 SVD 的次要成分**最小奇异值方向初始化 LoRA与 PiSSA 取主成分相反接入时取 SVD 末端、加缩放防梯度消失、只训次要成分并和 PiSSA 的 Config/键区分开。**