深入解析 PEFT 中的 RandLora:全秩参数高效微调的原理与实战
2026/9/21 1:43:03 网站建设 项目流程

深入解析 PEFT 中的 RandLora:全秩参数高效微调的原理与实战

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

RandLora(RandLoRA)是 🤗 PEFT 内置的一种参数高效微调方法,它用多个固定随机低秩基的线性组合来构造全秩权重更新,在保留 LoRA 参数量与内存效率的同时突破低秩约束对性能的天花板。本文以examples/randlora_finetuning/下的官方指南与训练脚本为核心,结合src/peft/tuners/randlora/的源码实现,完整讲解 RandLora 的设计动机、配置项、训练脚本用法、与 LoRA/VeRA 的对比以及底层实现细节,读完即可用一行命令复现 RandLora 微调并完成与 LoRA 的对比实验。

一、RandLora 是什么:一次对低秩范式的挑战

RandLora 是发表于 ICLR 2025 的参数高效微调(PEFT)方法,与 LoRA、VeRA 同属"随机基 + 可训练缩放"家族,但核心差异在于:它执行的是全秩(full-rank)权重更新

原文档 examples/randlora_finetuning/README.md 对这一动机有清晰交代:LoRA 及其变体通过低秩分解大幅压缩可训练参数量,但低秩更新的表示能力本身存在上限——当任务复杂、需要表达更丰富的权重变化时,低秩约束会限制性能增益,即使提高 LoRA 的秩(rank)也是如此。RandLora 正是为了回答论文中提出的关键问题:LoRA 与全量微调之间的性能差距,究竟来自"可训练参数变少",还是来自"更新矩阵的秩不足"?

RandLora 的答案是通过线性缩放随机基(linearly scaling random bases)构造全秩更新:

  • 随机基是多个低秩矩阵的集合,这些低秩矩阵的秩之和 ≥ 被适配权重矩阵的满秩;
  • 可训练参数只有两个对角缩放矩阵(向量),分别记为randlora_lambda(λ)与randlora_gamma(γ),它们与随机基相乘的方式与 VeRA 的更新类似;
  • 为了维持低内存占用,RandLora 使用自定义的反向传播函数(UniqueBaseGrad),避免为反向传播在内存中保存不必要的基。

在 PEFT 仓库中,该方法的注册、配置、层实现与量化支持分别位于 src/peft/tuners/randlora/config.py、src/peft/tuners/randlora/layer.py、src/peft/tuners/randlora/model.py 与 src/peft/tuners/randlora/bnb.py,并在 src/peft/tuners/randlora/init.py 中通过register_peft_method(name="randlora", ...)注册为RANDLORA类型。完整 API 文档见 docs/source/package_reference/randlora.md。

二、核心原理:多个低秩随机基如何拼出全秩更新

2.1 基的数量与秩的反比关系

假设被适配的线性层最小维度为min_dim,配置的基秩为r,则需要的随机基数量num_bases在 src/peft/tuners/randlora/layer.py 中计算:

num_bases = min(self.in_features, self.out_features) / r self.num_bases = int(num_bases) if num_bases.is_integer() else int(num_bases) + 1 # Full rank

num_bases × r ≥ min_dim,保证所有基的秩之和不小于被适配矩阵的满秩。这一设计带来一个与 LoRA完全相反的特性(原文档与 docs/source/package_reference/randlora.md 均强调):

RandLora 的可训练参数量与秩r成反比num_bases × r恒定,降低r会增加基的数量,进而增加基特有的可训练对角矩阵数量;提高r则减少可训练参数。

因此使用RandLoraConfig时,r不再是"越大越好"的旋钮,而是需要按目标参数量反向调节。

2.2 可训练参数:两个对角缩放矩阵

每个适配器只包含两个可训练参数(layer.py):

self.randlora_lambda[adapter_name] = nn.Parameter(torch.randn(r, self.num_bases), requires_grad=True) self.randlora_gamma[adapter_name] = nn.Parameter( torch.ones(self.num_bases, min(self.out_features, self.in_features)) / max(self.out_features, self.in_features), requires_grad=True, )

初始化时randlora_lambda置零、randlora_gamma设为常数(见 layer.py 的reset_randlora_parameters),保证训练初期适配器输出为零、模型行为与基座一致,这也是标准 LoRA 式初始化惯例。

缩放系数scaling = randlora_alpha / r(layer.py),与 LoRA 的alpha/r形式一致。

2.3 共享随机基与切片

与 VeRA 类似,RandLora 的randlora_A/randlora_B随机基是全局共享的:按所有被适配层中每个维度的最大尺寸初始化,前向时对每个层切片出所需子矩阵(src/peft/tuners/randlora/model.py 初始化,layer.py 切片)。文档中的示例:适配形状为 (100, 20) 与 (80, 50) 的两个线性层,会生成形状 (rank, 50) 与 (100, rank) 的共享 A/B 矩阵,再为 (100, 20) 层切出 (rank, 20) 与 (100, rank) 的子矩阵。

_find_dim(model.py)遍历被适配模块求出最大 in/out 维度,若未找到兼容层会抛出 "No layers types compatible with RandLora were found" 错误。

2.4 内存优化:自定义反传函数

为了不在反向传播中保存冗余中间张量,RandLora 在 layer.py 定义了UniqueBaseGrad自定义 autograd 函数:

class UniqueBaseGrad(torch.autograd.Function): @staticmethod def forward(ctx, randlora_A, randlora_lambda, randlora_gamma): out = randlora_lambda[:, :, None] * randlora_A * randlora_gamma[None,] ctx.save_for_backward(randlora_A, randlora_lambda, randlora_gamma) return out @staticmethod def backward(ctx, grad_output): randlora_A, randlora_lambda, randlora_gamma = ctx.saved_tensors ... grad_randlora_lambda = torch.einsum("kbj,kvj,bj->kb", grad_output, randlora_A, randlora_gamma) grad_randlora_gamma = torch.einsum("kbj,kvj,kb->bj", grad_output, randlora_A, randlora_lambda) return None, grad_randlora_lambda, grad_randlora_gamma

其巧妙之处在于:同一组随机基被所有被适配层共享,因此对randlora_lambda/randlora_gamma的梯度可跨层(batch 维 k)聚合,无需为每层单独保留一份基的副本,这正是原文档所说的"随机基共享策略带来内存优势"。

三、配置详解:RandLoraConfig 全参数

RandLoraConfig(src/peft/tuners/randlora/config.py)继承自PeftConfigpeft_type自动设为RANDLORA。核心参数如下:

参数默认值说明
r32随机基的秩。与可训练参数量成反比:调低r增加参数,调高r减少参数
target_modulesNone要应用 RandLora 的模块名列表或正则,仅支持线性层;例如["q_proj", "v_proj"]".*decoder.*(SelfAttention|EncDecAttention).*(q\|v)$"
randlora_alpha640缩放系数,通常取基秩的 20 倍;过大会导致数值不稳定,此时应降低学习率或该系数
randlora_dropout0.0适配器层 dropout 概率
sparseFalse使用三元稀疏随机基(仅含 -1、0、1),-1/1 的赋值概率各 1/6、0 为 2/3;有助减少过拟合(见下方原理)
very_sparseFalse高度稀疏随机基,-1/1 的赋值概率为1/√D(D 为最小维度);可进一步减少过拟合,但通常会降低性能,需谨慎使用
save_projectionTrue是否把全局randlora_A/randlora_B存入 state dict。设为False可显著缩小 checkpoint,但恢复时依赖projection_prng_key的固定随机种子,无法在所有设备与 PyTorch 版本上保证可复现
projection_prng_key0用于确定性初始化 A/B 基的 PRNG 种子;新增适配器时所有适配器必须使用相同 key
fan_in_fan_outFalse目标层是否以 (fan_in, fan_out) 存储权重,GPT-2 的Conv1D需设为True
bias"none"可选"none""all""randlora_only";注意设"all"/"randlora_only"后即使禁用适配器,输出也不再等价于基座模型
modules_to_saveNone除 RandLora 层外需置为可训练并保存的模块(如分类头)
init_weightsTrue是否用默认初始化初始化 RandLora 层权重,非明确原因不要改动
layers_to_transform/layers_patternNone指定仅对部分层索引应用变换,layers_pattern仅在该索引列表非常见层模式时使用

两个关键配置的底层行为值得展开:

  • 稀疏基的生成(model.py):_init_randlora_A_randlora_B_sparse先生成均匀随机张量,按阈值1/(2*sparsity)置为 -1/0/1 三元基,再做标准差归一化。sparse模式下sparsity=3very_sparse模式下sparsity=√min_dim(model.py 的_pre_injection_hook据此分发)。原文档指出:目前实现属于 proof-of-concept,稀疏性并未用于加速或省内存,但其"矩阵元素多为 0"的结构天然起到正则化作用、可缓解过拟合。
  • checkpoint 体积与可复现性save_projection=False时,A/B 基从 state dict 中排除(通过BufferDict(persistent=config.save_projection),见 model.py),加载时依据projection_prng_key重建。__post_init__会在该配置下给出警告(config.py)。仓库测试 tests/test_randlora.py 中的test_multiple_adapters_save_projection_false_contains_no_randlora_A_randlora_B验证了该模式下 state dict 确实不含randlora_A/randlora_B

四、快速开始:三行切换 LoRA → RandLora

原文档给出的最小示例(已修正其中get_peft_model(model, lora_config)的笔误为randlora_config):

import torch from peft import RandLoraConfig, get_peft_model from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer from datasets import load_dataset model = AutoModelForCausalLM.from_pretrained("huggyllama/llama-7b", device_map="auto") tokenizer = AutoTokenizer.from_pretrained("huggyllama/llama-7b") dataset = load_dataset("timdettmers/openassistant-guanaco", split="train") randlora_config = RandLoraConfig() peft_model = get_peft_model(model, randlora_config) trainer = Trainer( model=peft_model, train_dataset=dataset, dataset_text_field="text", max_length=2048, processing_class=tokenizer, ) trainer.train() peft_model.save_pretrained("randlora-llama-7b")

要点与原文档一致:无需改变任何标准 PEFT 训练流程,只需把LoraConfig换成RandLoraConfig。唯一的反直觉点在于可训练参数量与r成反比——降低r增加参数,提高r减少参数,规划参数量预算时务必注意。

五、实战脚本:randlora_finetuning.py 全参数指南

仓库在 examples/randlora_finetuning/randlora_finetuning.py 提供了完整可运行的微调脚本(基于examples/dora_finetuning/dora_finetuning.py改写)。直接运行:

python examples/randlora_finetuning/randlora_finetuning.py --base_model meta-llama/Meta-Llama-3-8B --data_path timdettmers/openassistant-guanaco

默认即加载 RandLora 配置的 PEFT 模型。脚本支持的参数与默认值如下(对应argparse定义,见 randlora_finetuning.py):

参数默认值说明
--base_modelhuggyllama/llama-7b基座模型路径或名称
--data_pathtimdettmers/openassistant-guanaco数据集路径或名称
--output_dirpath/to/output微调输出目录
--batch_size1批大小
--num_epochs1训练轮数
--learning_rate3e-4学习率
--cutoff_len512分词截断长度
--val_set_size500验证集大小
--use_loraFalse(flag)改用 LoRA 而非 RandLora
--quantizeFalse(flag)使用 4-bit 量化
--eval_step10评估间隔
--save_step100保存间隔
--deviceauto训练设备,auto时自动探测加速器
--rank32RandLora 基秩
--randlora_alpha640RandLora alpha
--randlora_dropout0.05dropout
--randlora_target_modulesNone(默认 k_proj,v_proj)逗号分隔的目标模块列表
--sparseFalse(flag)使用稀疏随机基
--very_sparseFalse(flag)使用高度稀疏随机基
--hub_model_id/--push_to_hub推送模型到 Hub

5.1 与 LoRA 快速对比

原文档强调:想快速和 LoRA 对比,只需加--use_lora,并把--randlora_alpha降到 2 倍秩:

python examples/randlora_finetuning/randlora_finetuning.py --base_model meta-llama/Meta-Llama-3-8B --data_path timdettmers/openassistant-guanaco --use_lora --rank 32 --randlora_alpha 64

脚本内部(randlora_finetuning.py)在--use_lora时构造LoraConfig(r=rank, lora_alpha=randlora_alpha, target_modules=..., lora_dropout=randlora_dropout, bias="none"),否则构造RandLoraConfig(...)——两个分支共用同一组命令行参数,保证对比时除方法本身外其余设置完全一致。

5.2 稀疏基与量化

  • 稀疏训练:--sparse(稀疏)或--very_sparse(高度稀疏),脚本会原样传入RandLoraConfig。原文档提示稀疏矩阵有助减少过拟合。
  • 4-bit 量化(QRandLora):加--quantize,脚本内部使用BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4")加载基座,并通过prepare_model_for_kbit_training做 k-bit 训练准备(randlora_finetuning.py)。PEFT 为量化场景提供了Linear8bitLtLinear4bit实现(src/peft/tuners/randlora/bnb.py),二者通过 model.py 的_create_new_module按目标层类型自动分发。

5.3 自定义目标模块

默认只适配 Llama 的k_projv_proj(README 中也说明默认是 key/value 层)。向更多层添加适配器会线性增加内存与参数开销,可自定义:

python examples/randlora_finetuning/randlora_finetuning.py --randlora_target_modules "q_proj,k_proj,v_proj"

脚本按逗号拆分传入target_modules。由于 RandLora 的目标模块映射直接复用 LoRA 的映射表(TRANSFORMERS_MODELS_TO_RANDLORA_TARGET_MODULES_MAPPING = TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING.copy(),见 src/peft/utils/constants.py),各主流架构的默认目标模块与 LoRA 一致,且支持target_modules=None时自动应用默认映射。

5.4 完整示例命令

python examples/randlora_finetuning/randlora_finetuning.py \ --base_model "PATH_TO_MODEL" \ --data_path "PATH_TO_DATASET" \ --output_dir "PATH_TO_OUTPUT_DIR" \ --batch_size 1 \ --num_epochs 3 \ --learning_rate 3e-4 \ --cutoff_len 512 \ --val_set_size 500 \ --quantize \ --eval_step 10 \ --save_step 100 \ --device "auto" \ --rank 32 \ --randlora_alpha 640 \ --randlora_dropout 0.05 \ --randlora_target_modules "k_proj,v_proj" \ --hub_model_id "YOUR_HF_REPO" \ --push_to_hub

该脚本的训练配置也值得留意:warmup_steps取总步数的 10%,weight_decay=0.01,并且通过gradient_accumulation_steps = 16 // batch_size保证累积后批大小不小于 16(脚本注释明确建议维持这一最小批量以获得良好性能,randlora_finetuning.py)。

六、RandLora vs. LoRA:为什么值得换

原文档对二者的定位非常明确:

  • 挑战低秩范式:RandLora 学习全秩更新。论文实验表明 LoRA 的低秩约束会在可训练参数增加(秩提高)时限制性能增益,而 RandLora 用固定随机基的线性组合突破这一瓶颈。
  • 适用场景优先推荐用于 LoRA 欠拟合的困难任务;同时"如果提高 LoRA 的秩能改善你的任务表现,RandLora 大概率会表现更好"。
  • 预期收益:在可训练参数量相当时,RandLora 预计优于 LoRA,尤其是等效 LoRA 秩大于 4 的较大参数量区间。

代价是两点限制:

  1. 对缩放系数敏感:性能依赖较大的randlora_alpha(通常取基秩的 20 倍,即默认640/32)。过大的缩放系数可能使训练不稳定,此时应降低学习率或调小randlora_alpha
  2. 极低秩下训练变慢:降低r会增加基数量,RandLora 在非常低的秩(典型如r < 4)下训练时间相比 LoRA 会明显增加。不过这不影响推理——适配器可以合并回预训练权重矩阵(merge/unmerge实现见 layer.py,量化层对应实现见 bnb.py)。

七、RandLora vs. VeRA:同门不同路

RandLora 与 VeRA 都用随机基组合来回应 LoRA 的某些局限,但解决的问题不同:

  • VeRA的目标是超越 rank-1 LoRA 进一步压缩可训练参数
  • RandLora的目标是消除随可训练参数增长而出现的低秩性能瓶颈

由此得到两个预期结论(原文档原话):

  1. 在需要更多可训练参数的困难任务上,RandLora 预计优于 VeRA;
  2. 得益于随机基共享策略,RandLora 相比 VeRA 具有更低的内存占用

从实现看,这一内存优势确有依据:A/B 基在RandLoraModel层面全局共享一份(self.randlora_A/self.randlora_B,模型级 BufferDict),各层只保存自己的randlora_lambda/randlora_gamma。仓库测试test_randlora_A_randlora_B_share_memory断言了不同层之间randlora_A/randlora_Bdata_ptr()相同(即共享同一份内存),而test_randlora_lambda_dont_share_memory验证了每个适配器/每层的 λ、γ 各自独立(tests/test_randlora.py)。

八、底层实现:共享基、多适配器与保存加载

8.1 前向计算路径

被适配层的前向(layer.py)可概括为:

result = self.base_layer(x, *args, **kwargs) for active_adapter in self.active_adapters: dropout = self.randlora_dropout[active_adapter] update_B, update_A = self.get_scaled_bases(active_adapter, device=x.device) scaling = self.scaling[active_adapter] result = result + F.linear(F.linear(dropout(x), update_B), update_A) * scaling

get_scaled_bases(layer.py)完成三件事:从共享 A/B 基切片出当前层所需子矩阵、通过UniqueBaseGrad把 λ/γ 应用到最小的基(randlora_A)上、展平矩阵维度以减少内存;随后根据min_dim与 in/out 维度的关系决定update_A/update_B的相乘顺序,以最小化可训练参数。get_delta_weight(用于合并)则计算(update_B.T @ update_A.T).T * scaling

8.2 多适配器约束

由于 A/B 基全局唯一,新增第二个适配器时(layer.py)会复用已有适配器的基,并做形状兼容校验:若新适配器需要更大的基(更高的r或更大的层维度),会抛出明确的 ValueError,提示"加载适配器时按逆序加载可能解决"。

同时 model.py 的_check_new_adapter_config强制要求:所有适配器的projection_prng_key必须一致、save_projection取值必须统一,否则直接报错。这些约束正是为了维护"一份共享基、多个可训练对角矩阵"的数据结构的正确性。

8.3 初始化细节

  • 普通模式(model.py):A/B 基用带显式torch.Generator(device="cpu").manual_seed(config.projection_prng_key)的 Kaiming Uniform 初始化(_kaiming_init,a=√5),保证给定 key 时结果完全确定;随后除以自身标准差做归一化(源码注释说明这是经验上最优的归一化方式)。
  • 稀疏模式:三元稀疏基 + 标准差归一化,如 5.2 节所述。

8.4 仓库基准与测试验证

仓库method_comparison目录中提供了 RandLora 在 MetaMathQA(Llama-3.2-3B)与图像生成(FLUX.2 klein)上的真实基准记录:

  • 配置快照 method_comparison/MetaMathQA/experiments/randlora/llama-3.2-3B-default/adapter_config.json:r=32randlora_alpha=640randlora_dropout=0.0save_projection=truetarget_modules=["q_proj","v_proj"],与本文所述默认配置一致。
  • 运行记录 method_comparison/MetaMathQA/results/randlora--llama-3.2-3B-default.json:在 L40S 上 5000 步训练成功,num_trainable_params=9,289,728(约 929 万),num_total_params=3,222,039,552(约 32.2 亿),即可训练参数占比约 0.29%——可作为理解"参数高效"的直观参考。注意这些是仓库内单次实验记录,不宜推广为普适结论。

单元测试方面,tests/test_randlora.py 覆盖了共享内存语义、多适配器 PRNG 一致性校验、save_projection=False的保存/加载与 state dict 内容、不同形状层的共享基切片(test_randlora_different_shapes断言randlora_A.shape == (r, 1, in_features)randlora_B.shape == (out_features, 1, r))以及多种 dtype 下的训练/推理,可作为理解实现语义的补充材料。

九、限制与注意事项小结

综合原文档与源码,使用 RandLora 前请确认以下约束:

  1. 仅支持nn.Linear(含Conv1D,后者需fan_in_fan_out=True),不支持卷积等其它层类型;
  2. 可训练参数随r反比变化,与 LoRA 直觉相反,规划预算时勿照搬 LoRA 的调参习惯;
  3. randlora_alpha默认较大(640),高学习率下易数值不稳,训练发散时优先降学习率或降 alpha;
  4. 极低秩(r < 4)训练速度明显慢于 LoRA,但合并后推理无额外开销;
  5. 多适配器共享同一份随机基,projection_prng_keysave_projection必须在所有适配器间保持一致;
  6. 若追求 checkpoint 最小化可设save_projection=False,但可复现性依赖 PRNG 种子,跨设备/跨 PyTorch 版本无法保证,默认True更稳妥。

十、引用

使用 RandLora 开展研究时,可引用原论文:

@inproceedings{2025_ICLR_RandLoRA, title="{RandLoRA: Full rank parameter-efficient fine-tuning of large models}", author="Albert, Paul and Zhang, Frederic Z. and Saratchandran, Hemanth and Rodriguez-Opazo, Cristian and van den Hengel, Anton and Abbasnejad, Ehsan", booktitle="{International Conference on Learning Representations (ICLR)}", year="2025" }

总结:RandLora 用"多个固定低秩随机基之和达到满秩 + 仅训练两个对角缩放矩阵"的设计,在维持 LoRA 级参数效率的同时提供全秩更新的表达能力。无论你是在复现论文、为困难任务寻找比 LoRA 更强的 PEFT 基线,还是想快速对比稀疏基与量化组合的效果,都可以从 examples/randlora_finetuning/randlora_finetuning.py 出发,一行命令开始实验。

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

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

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

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

立即咨询