【Bug已解决】Add BoRA to PEFT 解决方案
2026/7/26 5:15:22 网站建设 项目流程

【Bug已解决】Add BoRA to PEFT 解决方案

一、现象长什么样

社区提议把BoRA(Bottleneck Rank Adaptation,瓶颈秩适配)加进 PEFT。BoRA 是 LoRA 的一个变体:在标准 LoRA 的B·A·x之外,再引入一个共享瓶颈来进一步压缩可训练参数、提升跨层一致性。

但要把一个新方法接进 PEFT 时,使用者会遇到和之前“Add PSOFT”类似的工程问题:

  • 没有统一的BoRAConfig/BoRAModel/mapping注册,自己改写forward容易和 PEFT 的merge_and_unload/load_adapter/print_trainable_parameters冲突;
  • BoRA 的“瓶颈”结构(共享投影)若实现成额外 Module,保存时键名和 PEFT 约定不一致,跨模型load错配;
  • 想和 LoRA 组合(同一模型一部分层用 LoRA、一部分用 BoRA)时发现 PEFT 没有对应扩展点;
  • 复用save_pretrained时,瓶颈参数没有统一的adapter_config.json描述。

所以“Add BoRA to PEFT”本质是集成方法论:如何按 PEFT 扩展规范,把 BoRA 接成一等公民,且能与现有方法组合、可被get_peft_model管理。

二、背景

BoRA 的核心思想(以 Bottleneck Rank Adaptation 的通用形式):标准 LoRA 是Δ = B·A·x,其中A ∈ ℝ^{r×in}B ∈ ℝ^{out×r}。BoRA 在此基础上引入一个跨层共享的瓶颈投影P ∈ ℝ^{r×s}s < r),把每层的A写成A = P·a_layera_layer ∈ ℝ^{s×in}是每层独有的小矩阵),于是每层只需训a_layer(远小于完整A),瓶颈P在所有层间共享:

Δ = B · (P · a_layer) · x

好处:每层可训练参数从r·in + out·r降到s·in + out·r + r·s(共享P只算一次),且共享瓶颈起到跨层正则作用,缓解过拟合。

PEFT 扩展规范(回顾)仍是三件套:

  1. BoRAConfig(PeftConfig):超参rsalphatarget_modules
  2. BoRAModel(BaseTuner):持有基座 + 每层a_layer+ 共享P,重写forward
  3. mapping.py注册:PEFT_TYPE_TO_MODEL_MAPPING[PeftType.BORA] = BoRAModel

下面用可运行代码给出自包含的 BoRA 实现(结构对齐 PEFT 规范),以及一个把它接到 PEFT 风格的骨架。

三、根因

这里的“问题”是集成规范缺失导致的重复造轮子

  1. 三件套不全:每个用户自己改forward,无法复用 PEFT 生态。
  2. 瓶颈参数键名不统一:跨模型save/load错配。
  3. 无法与 LoRA 组合:缺扩展点。

修复方向:按 PEFT 规范实现BoRAConfig+BoRAModel+mapping注册,让 BoRA 成为可管理的一等公民,且能用add_adapter与 LoRA 并存。

四、最小可运行复现

下面给一个自包含的 BoRA 实现(共享瓶颈P+ 每层a_layer+B),离线可验证数学正确。

import torch import torch.nn as nn class BoRAConfig: def __init__(self, r=8, s=2, alpha=16, target_modules=("fc1",)): self.r, self.s, self.alpha = r, s, alpha self.target_modules = target_modules class BoRALayer(nn.Module): """单层 BoRA:Δ = B · (P · a) · x,P 共享、a/B 本层独有。""" def __init__(self, in_f, out_f, r, s): super().__init__() self.a = nn.Parameter(torch.randn(s, in_f) * 0.01) # 每层独有 [s, in] self.B = nn.Parameter(torch.zeros(out_f, r)) # 每层独有 [out, r] # 共享瓶颈 P 作为 buffer(所有 BoRALayer 共享同一份) self.register_buffer("P", torch.randn(r, s) * 0.01) def forward(self, x, P): # a: [s, in] -> P@a: [r, in] -> (P@a)@x.T: [r, T] -> B@: [out, T] A_eff = P @ self.a # [r, in] delta = (self.B @ (A_eff @ x.T)).T # [T, out] return delta class BoRANet(nn.Module): def __init__(self, in_f, out_f, r, s, n_layers): super().__init__() self.P = nn.Parameter(torch.randn(r, s) * 0.01) # 共享瓶颈 self.blocks = nn.ModuleList() self.base = nn.ModuleList([nn.Linear(in_f, out_f) for _ in range(n_layers)]) for _ in range(n_layers): self.blocks.append(BoRALayer(in_f, out_f, r, s)) def forward(self, x): out = x for i, blk in enumerate(self.blocks): out = self.base[i](out) + blk(out, self.P) # 基座 + BoRA 增量 return out torch.manual_seed(0) net = BoRANet(16, 16, r=8, s=2, n_layers=3) x = torch.randn(2, 16) out = net(x) # 训练参数:共享 P(r*s) + 每层 (a: s*in + B: out*r) per_layer = 2 * 16 + 16 * 8 total = 8 * 2 + 3 * per_layer tr = sum(p.numel() for p in net.parameters() if p.requires_grad) print("输出形状:", tuple(out.shape)) print("实际可训练:", tr, " 理论:", total, " 一致:", tr == total)

运行后:输出形状正确,可训练参数 = 共享P(8*2)+ 每层a(2*16)+B(16*8),且requires_grad=True仅限这些——验证了 BoRA 的核心结构与参数节省。

五、解决方案(第一层):最小直接修复

修复 1:共享瓶颈 P 用 buffer 或 Module 级参数

BoRALayer.register_buffer("P", ...)或放在BoRANet.P,确保所有层引用同一份,避免重复存、重复训。

修复 2:冻结基座,只训 a / B / P

for p in net.base.parameters(): p.requires_grad_(False) # a/B/P 保持 requires_grad

修复 3:键名统一,便于 save/load

# 每层 a/B 键名: boralayer.{i}.a.weight / boralayer.{i}.B.weight # 共享 P: boralayer.shared_P.weight

六、解决方案(第二层):结构性改进

改进 1:按 PEFT 规范写 BoRAConfig + BoRAModel

from peft import PeftConfig, PeftType class BoRAConfig(PeftConfig): def __init__(self, r=8, s=2, alpha=16, **kwargs): super().__init__(peft_type=PeftType.BORA, **kwargs) self.r, self.s, self.alpha = r, s, alpha from peft.tuners import BaseTuner class BoRAModel(BaseTuner): def __init__(self, model, config, adapter_name="default"): super().__init__(model, config, adapter_name) # 注入 BoRALayer、持有共享 P、重写 forward def forward(self, *a, **k): return self.model(*a, **k) # 内部各层调用 BoRALayer

改进 2:mapping 注册

from .bora import BoRAModel PEFT_TYPE_TO_MODEL_MAPPING[PeftType.BORA] = BoRAModel

改进 3:与 LoRA 组合

model = get_peft_model(base, LoraConfig(r=8, target_modules=["q_proj"])) model.add_adapter("bo1", BoRAConfig(r=8, s=2, target_modules=["v_proj"])) model.set_adapter(["default", "bo1"])

七、解决方案(第三层):断言 / CI 守护

import torch import torch.nn as nn import pytest class BoRALayer(nn.Module): def __init__(self, in_f, out_f, r, s): super().__init__() self.a = nn.Parameter(torch.randn(s, in_f) * 0.01) self.B = nn.Parameter(torch.zeros(out_f, r)) self.register_buffer("P", torch.randn(r, s) * 0.01) def forward(self, x, P): return (self.B @ ((P @ self.a) @ x.T)).T def test_bora_delta_shape(): torch.manual_seed(0) layer = BoRALayer(16, 16, 8, 2) P = torch.randn(8, 2) * 0.01 d = layer(torch.randn(2, 16), P) assert d.shape == (2, 16) def test_shared_bottleneck_used(): layer = BoRALayer(16, 16, 8, 2) assert hasattr(layer, "P") and layer.P.shape == (8, 2) def test_only_a_B_trainable(): torch.manual_seed(1) base = nn.Linear(16, 16) layer = BoRALayer(16, 16, 8, 2) for p in base.parameters(): p.requires_grad_(False) tr = sum(p.numel() for p in [base, layer] if p.requires_grad) assert tr == (2*16 + 16*8) # 仅 a + B def test_bora_adds_to_base(): torch.manual_seed(2) base = nn.Linear(16, 16) layer = BoRALayer(16, 16, 8, 2) x = torch.randn(2, 16) out = base(x) + layer(x, torch.randn(8, 2)*0.01) base_only = base(x) assert not torch.allclose(out, base_only)

这四个测试守护“BoRA 增量形状、共享瓶颈存在、仅 a/B 可训练、增量叠加到基座”。

八、排查清单

把 BoRA 接进 PEFT 时按序查:

  1. 三件套齐全BoRAConfig+BoRAModel+mapping注册。
  2. 共享瓶颈 P 唯一:用 buffer 或模型级参数,所有层引用同一份。
  3. 基座冻结:只训a/B/P
  4. 键名统一boralayer.{i}.a/B+shared_P,便于 save/load。
  5. 复用生态:接好即可print_trainable_parameters/save_pretrained/load_adapter
  6. 与 LoRA 组合:用add_adapter+set_adapter
  7. 参数计数核对total = r*s + n_layers*(s*in + out*r)
  8. 测试守护:增量形状、共享瓶颈、可训练参数、叠加基座。

九、小结

Add BoRA to PEFT的本质不是代码崩溃,而是集成规范缺失:用户各自改写forward、瓶颈参数键名不统一、无法与 LoRA 组合、不能复用 PEFT 生态。

最小修复是实现自包含 BoRA(每层a/B+ 共享瓶颈P、冻结基座、键名统一);结构性改进是按 PEFT 规范写BoRAConfig+BoRAModel+mapping注册,让 BoRA 成为一等公民并能与 LoRA 组合;最后用测试守护“增量形状正确、共享瓶颈存在、仅 a/B 可训练、增量叠加基座”。这样 BoRA 就能像其它 PEFT 方法一样被get_peft_model统一管理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询