PEFT 中的 VeRA(Vector-based Random Matrix Adaptation):共享随机投影的低参数微调原理、配置与实战
2026/9/20 5:01:36 网站建设 项目流程

PEFT 中的 VeRA(Vector-based Random Matrix Adaptation):共享随机投影的低参数微调原理、配置与实战

【免费下载链接】peft🤗 PEFT: State-of-the-art Parameter-Efficient Fine-Tuning.项目地址: https://gitcode.com/gh_mirrors/pe/peft

VeRA(Vector-based Random Matrix Adaptation)是 🤗 PEFT 提供的一种基于向量随机矩阵的低参数微调方法:它与 LoRA 同属低秩适配,但通过跨层共享一对冻结的随机低秩矩阵、只训练每层两个缩放向量,用远少于 LoRA 的可训练参数取得相近甚至更优的效果,特别适合参数预算紧张的大模型微调与多任务适配器批量存储场景。本文以仓库文档 vera.md 为主体,结合 config.py、layer.py、model.py 等源码与 test_vera.py 测试用例,完整讲解 VeRA 的工作原理、VeraConfig全参数语义、保存/加载细节、多适配器行为及适用边界,读完即可在 PEFT 中正确配置并训练、部署 VeRA 适配器。

VeRA 工作原理:从 LoRA 的逐层矩阵到共享投影加缩放向量

LoRA 为每个被适配层学习两个小的低秩矩阵(AB),r是这两个矩阵的内维(秩),它决定适配器的容量。由于每个被适配层都要学习自己的一对低秩矩阵,LoRA 的可训练参数会随r和被适配层数量快速膨胀。

VeRA 保留了"低秩更新"的核心思想,但改变了哪些参数被训练

  • 使用一对冻结的、随机初始化的低秩矩阵AB,在所有被适配层之间共享
  • 每个被适配层只学习两个缩放向量vera_lambda_dvera_lambda_b,用它们缩放共享矩阵,产生该层特有的更新;
  • vera_lambda_d的尺寸为r,因此 VeRA 的可训练参数仍然随r和被适配层数增长,但增长远比 LoRA 缓慢——因为大矩阵AB是共享且冻结的,不再逐层学习。

论文中的简化参数计数对比如下:

方法可训练参数规模说明
LoRA2 * L_tuned * d_model * rL_tuned为被适配层数,d_model为模型维度,r为秩
VeRAL_tuned * (d_model + r)大低秩矩阵共享冻结,只训练每层的小向量

即 LoRA 的参数量随L_tunedd_model * r的乘积增长,而 VeRA 只随L_tuned * (d_model + r)线性增长,这正是 VeRA 能用远少于 LoRA 的可训练参数的原因。

从源码看,前向计算路径与上述描述完全一致。在 layer.py 的Linear.forward中,每层先从共享矩阵中切出子矩阵,再按如下公式累加适配结果:

sliced_A = vera_A[:, : self.in_features] # 按输入维度切子矩阵 sliced_B = vera_B[: self.out_features, :] # 按输出维度切子矩阵 result = result + lambda_b * F.linear(lambda_d * F.linear(dropout(x), sliced_A), sliced_B)

而 layer.py 的get_delta_weight则在合并(merge)场景下计算等效权重增量:ΔW = λ_b ⊙ (B_sliced @ (λ_d ⊙ A_sliced))(含fan_in_fan_out转置处理),并针对 CPU 上的 fp16/bf16 做了先提升到 fp32 计算再转回的优化。

共享矩阵的尺寸确定与子矩阵切分

不同被适配层的输入输出维度往往不同。由于 VeRA 只有一对共享投影矩阵,PEFT 在初始化时按各维度所需的最大尺寸创建AB,前向时再切出对应子矩阵。文档给出的例子:同时适配形状为(100, 20)(80, 50)的两个线性层,会创建形状分别为(rank, 50)(100, rank)AB矩阵;适配形状为(100, 20)的层时,则切出形状为(rank, 20)(100, rank)的子矩阵。

这一行为在源码中有明确实现:

  • model.py 的VeraModel._find_dim遍历所有目标模块,逐对取max计算全局最大的(out_features, in_features),找不到兼容层时会抛出ValueError提示检查target_modules
  • model.py 的_init_vera_A_vera_B据此创建vera_A: (r, linear_in_dim)vera_B: (linear_out_dim, r)两个共享 Buffer,并使用基于projection_prng_keytorch.Generator以 Kaiming Uniform 方式确定性初始化;
  • 前向与get_delta_weight中均通过vera_A[:, : in_features]vera_B[: out_features, :]切片取用。

训练参数初始化

在 layer.py 中,每个适配器创建两个可训练参数:vera_lambda_b = ones(out_features)(全 1)、vera_lambda_d = randn(r);随后 layer.py 的reset_vera_parameters会将其重置为vera_lambda_d = d_initial(默认 0.1)、vera_lambda_b = 0。也就是说,训练起始时 VeRA 的增量权重为零,模型输出与基础模型完全一致,从恒等更新出发开始学习。文档同时提示d_initial推荐取较小值(≤0.1,论文 Table 6c)。

VeraConfig 配置参数全解析

VeraConfig定义于 config.py,是VeraConfigPeftType.VERA,见 peft_types.py)的配置类。各参数语义如下:

参数默认值说明
r256VeRA 的"秩"(参数维度)。由于 VeRA 可训练参数远少于 LoRA,建议取比 LoRA 更高的值(参考论文 Table 1)
target_modulesNone要应用 VeRA 的模块名列表或正则表达式,例如['q', 'v']'.*decoder.*(SelfAttention|EncDecAttention).*(q\|v)$'仅支持线性层
projection_prng_key0VeRA 的 PRNG 初始化种子。用于初始化vera_A/vera_B,或在加载未保存投影的 checkpoint 时重建投影
save_projectionTrue是否将vera_A/vera_B连同逐层lambda_b/lambda_d一起存入 state dict。设为True会增大 checkpoint,但保证任何平台都能正确重载
vera_dropout0.0VeRA 层的 dropout 概率
d_initial0.1vera_lambda_d向量的初始值,推荐 ≤0.1
fan_in_fan_outFalse被替换层若按(fan_in, fan_out)存储权重(如 GPT-2 的Conv1D)需设为True
bias"none"VeRA 偏置训练方式,可选'none''all''vera_only'。注意:若为'all''vera_only',即使停用适配器,模型输出也不会与基础模型完全一致
modules_to_saveNone除 VeRA 层外需要置为可训练并保存的模块列表(如序列分类/标记分类任务中随机初始化的classifier/score层)
init_weightsTrue是否用默认方式初始化 VeRA 层权重,除非清楚后果否则不要改动
layers_to_transformNone仅对指定层索引应用 VeRA 变换,可传列表或单个整数
layers_patternNone仅在layers_to_transform非空时使用,用于定位模型的nn.ModuleList(通常名为'layers''h'

在 config.py 的__post_init__中还有两条校验逻辑:

  • 指定了layers_pattern但未指定layers_to_transform时抛出ValueError
  • save_projection=False时发出警告:vera_A/vera_B将依据config.projection_prng_key重建,若想保证在所有系统配置上都能正确恢复 checkpoint,建议设回True

使用示例

文档与 model.py 中给出的最小示例:

from transformers import AutoModelForCausalLM from peft import VeraConfig, get_peft_model base_model = AutoModelForCausalLM.from_pretrained("facebook/opt-125m") config = VeraConfig(r=128) model = get_peft_model(base_model, config)

仓库中的实战示例 VeRA.ipynb 展示了序列分类任务的完整配置:

rank = 8 # for best results, increase this number peft_config = VeraConfig( r=rank, target_modules=["query", "value", "intermediate.dense"], task_type="SEQ_CLS", ) model = get_peft_model(model, peft_config)

注意该示例面向 RoBERTa 一类模型,target_modules采用了"intermediate.dense"这类带层级前缀的命名方式;在 constants.py 中,PEFT 为常见 transformers 架构预置了默认目标模块映射TRANSFORMERS_MODELS_TO_VERA_TARGET_MODULES_MAPPING(继承自 LoRA 映射,并针对 phi 系列覆盖为["q_proj", "v_proj"]),不传target_modules时会使用该预置映射。

保存与加载:save_projection 与 projection_prng_key 的权衡

VeRA 的 checkpoint 大小主要取决于是否保存共享投影矩阵,这是其部署优势的关键开关:

  • save_projection=True(默认)vera_A/vera_B随每层lambda_b/lambda_d一起存入 state dict。checkpoint 更大,但任何设备与任何 PyTorch 版本下都能精确恢复;
  • save_projection=False:不保存共享低秩矩阵,checkpoint 显著变小;加载时依据projection_prng_key固定随机种子重新生成。代价是无法保证在所有设备与所有未来 PyTorch 版本上可复现。若你重视可复现性,请保持True(默认值)。

源码层面的实现细节印证了这一点:

  • model.py 创建vera_A/vera_B时使用了BufferDict({}, persistent=config.save_projection)——persistent=False使它们不出现在 state dict 中;
  • model.py 的_get_adapter_state_dict会先剔除每层对共享投影的重复引用,再在save_projection=True时以模型级键base_model.vera_A.<adapter>base_model.vera_B.<adapter>形式写回,避免每个被适配层都重复存储同一份投影;
  • model.py 的_remap_adapter_state_dict_for_load在加载时对save_projection与实际 checkpoint 内容做一致性校验并给出明确警告。

对应测试见 test_vera.py:test_save_projection_true_restores_saved_projections验证保存的投影必须被精确恢复(先修改投影再保存,确保不是靠 PRNG 重新生成巧合一致);test_multiple_adapters_save_projection_false_contains_no_vera_A_vera_B则用safetensors直接检查save_projection=False时 checkpoint 中确实不含vera_A/vera_B键。

多适配器支持与共享约束

VeRA 天然支持在一个模型上叠加多个适配器,但共享投影带来两条硬性约束(均由 model.py 的_check_new_adapter_config与 layer.py 的update_layer强制检查):

  1. 所有适配器的projection_prng_key必须相同,否则add_adapter抛出ValueError(对应测试 test_vera.py);
  2. 所有适配器的save_projection必须一致(要么都保存,要么都不保存)。

此外,后添加的适配器会复用已有适配器的共享投影(代码中从现有适配器取一份参数引用),因此后添加适配器的层形状与秩不能超过已有共享矩阵的尺寸;若秩更小导致尺寸不满足,会抛出带有"以相反顺序加载适配器可能解决此问题"提示的ValueError

测试 test_vera.py 验证了共享语义的底层实现:同一 PRNG key 下,各层各适配器的vera_A/vera_B通过data_ptr()断言共享同一块内存vera_Avera_B彼此不共享),而vera_lambda_b/vera_lambda_d在层间与适配器间各自独立(不共享内存)。多适配器场景下,可通过set_adapter("default")/set_adapter("other")切换生效的适配器。支持的数据类型在 test_vera.py 中覆盖了 float32、float16 与 bfloat16。

当前限制

  • 仅支持nn.Linear(量化线性层与 transformers 的Conv1D亦被支持,见 model.py 的_get_tuner_layer_class);目标为其他模块类型(如卷积、Embedding)时无法使用。
  • 各被适配层的输入/输出维度不宜差异过大:由于共享一对投影矩阵并按最大形状创建,若模型中同时存在维度悬殊的层(例如 transformer 的上/下投影层),共享矩阵会被"过度供给"到最大形状,从而削弱 VeRA 的参数效率优势。

何时选择 VeRA

推荐场景

  • 希望在保持与 LoRA 相近效果的前提下,把可训练参数压到最低;
  • 需要存储或部署大量任务专属适配器——更小的适配器 checkpoint 直接降低存储成本;
  • 在内存或参数预算非常紧张的情况下微调超大规模语言模型(论文在 7B/13B 模型上的指令微调中展示了其应用)。

不推荐场景

  • 需要每个被适配层拥有独立低秩矩阵,以换取学习参数的最大灵活性;
  • 需要适配除nn.Linear以外的模块类型;
  • 模型各被适配线性层的输入/输出维度差异很大(共享矩阵会因"取最大形状"而过量分配)。

论文摘要指出:VeRA 相比 LoRA 大幅减少了可训练参数数量,同时保持相同性能,其做法正是跨层共享一对低秩矩阵、只学习小的缩放向量,并在 GLUE、E2E 基准、图像分类任务以及 7B/13B 语言模型指令微调上验证了有效性。仓库文档另附方法对比基准(method_comparison目录下的实验结果可作为横向参考,例如 method_comparison 中的运行与评估框架)。

进一步探索

  • 配置类完整源码与参数校验:src/peft/tuners/vera/config.py
  • 层实现(前向、merge/unmerge、delta weight):src/peft/tuners/vera/layer.py
  • 模型级实现(共享矩阵初始化、多适配器校验、state dict 处理):src/peft/tuners/vera/model.py
  • 专门针对共享权重挑战的测试套件:tests/test_vera.py
  • 序列分类实战 Notebook:examples/sequence_classification/VeRA.ipynb
  • 方法对比基准框架:method_comparison/README.md

【免费下载链接】peft🤗 PEFT: State-of-the-art Parameter-Efficient Fine-Tuning.项目地址: https://gitcode.com/gh_mirrors/pe/peft

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询