news 2026/9/20 21:09:09

PEFT 中的 RoAd(2D 旋转适配):从原理到微调、量化与混合批次推理的完整实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PEFT 中的 RoAd(2D 旋转适配):从原理到微调、量化与混合批次推理的完整实践指南

PEFT 中的 RoAd(2D 旋转适配):从原理到微调、量化与混合批次推理的完整实践指南

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

RoAd(2D Rotary Adaptation)是 PEFT 提供的一种参数高效微调方法:它通过学习一组作用于隐藏维度对的 2×2 旋转矩阵(及可选缩放因子)来适配大语言模型,以不足 0.1% 的可训练参数取得与 LoRA 等主流方法相当甚至更优的效果。本文以 docs/source/package_reference/road.md 为骨架,结合 road 调谐器源码 与 road_finetuning 示例,完整讲解 RoAd 的数学原理、RoadConfig 全部配置参数、微调训练流程、bitsandbytes 量化支持,以及同批次多适配器混合推理的实现细节,帮助你在实际项目中直接落地 RoAd。

RoAd 是什么:用稀疏 2D 旋转代替低秩更新

RoAd 的核心思想非常简洁:把隐藏向量切分成许多 2 维向量,然后用各自独立的 2×2 旋转矩阵去旋转每一个 2 维向量;为了获得额外灵活性,每个旋转矩阵还会乘上一个可训练的缩放因子 α。从 layer.py 的 RoadLayer 文档字符串可以看到,对向量R @ x的变换可展开为两个逐元素公式:

  • y₀ = x₀ * α * cosθ - xₙ * α * sinθ
  • yₙ = x₀ * α * sinθ + xₙ * α * cosθ

其中缩放因子 α 与角度 θ 为每个元素对分别学习,且当使用 road_2、road_4 变体时,旋转矩阵中的 4 个分量还可以各不相同(详见下文「变体」一节)。

与 LoRA 的批量低秩更新相比,RoAd 的稀疏旋转可以改写为简单的逐元素(element-wise)运算,因此在前向传播与推理时不引入昂贵的批量矩阵乘法。这一特性带来两个直接收益:

  1. 极低的参数开销:适配器只需要存储角度 θ 与缩放 α,而不是完整的矩阵 R,可训练参数占比低于 0.1%;
  2. 更高的服务吞吐:当同一批次中同时处理异构请求(即同时服务多个适配器)时,逐元素运算的开销远小于 LoRA 的批量低秩更新,显著提升混合批次推理的吞吐。

此外,RoAd 还能无缝融入分布式交换干预(distributed interchange intervention)框架:把稀疏的 2D 旋转理解为在隐藏表示学习子空间中的任务特定干预。这些正交子空间可以相互组合,从而在不额外微调的情况下合并多种任务行为(如多语言能力、指令跟随),实现模块化、可解释的 LLM 适配。

需要指出的是,上述「不足 0.1% 参数、更高吞吐、可组合」等表述均来自 road.md 与 examples/road_finetuning/README.md 的官方描述,具体效果以你自己的基准测试为准;仓库的 method_comparison/MetaMathQA 基准目录中提供了 RoAd 在 MetaMathQA 任务上的实测结果 JSON 文件可供参考。

使用约束与适用前提

在动手之前,先明确 RoAd 的边界条件(来自 road.md 与源码):

  • 学习率:RoAd 微调通常需要比 LoRA 等方法更高的学习率,建议设为1e-3 左右
  • 目标模块:当前 RoAd仅支持线性层(torch.nn.Linear,从 model.py 的分发逻辑可以看到,不支持的模块类型会直接抛出ValueError
  • 量化支持:可用于 bitsandbytes 量化的模型(4-bit 或 8-bit),对应实现见 bnb.py 中的Linear8bitLtLinear4bit
  • 混合批次推理:不同 RoAd 适配器在同一批次内混合推理的能力与 LoRA 相同,可参考 lora.md 中「Inference with different LoRA adapters in the same batch」一节,源码实现位于 model.py 的_enable_peft_forward_hooks与 layer.py 的_mixed_batch_forward

RoadConfig:核心配置参数详解

RoadConfig 定义在 src/peft/tuners/road/config.py,继承自PeftConfig。其关键参数如下:

参数默认值说明
variant"road_1"RoAd 变体,可选road_1/road_2/road_4,详见下文
group_size64元素分组大小,决定元素如何配成 2D 向量进行旋转
init_weightsTrue是否执行 RoAd 权重的默认初始化
target_modulesNone要替换为目标模块名的列表或正则字符串
modules_to_saveNone除 RoAd 层外需要置为可训练并保存的模块列表

__post_init__中(config.py),配置会做三件事:

  1. peft_type固定为PeftType.ROAD
  2. target_modules传入列表则转换为set
  3. 校验合法性:variant必须是road_1road_2road_4之一;group_size必须为正且能被 2 整除,否则抛出ValueError

variant:三种参数规模档位

variant直接决定每个层级的可训练参数量,对应 config.py 与 layer.py 中的实现:

  • road_1:所有元素对共享同一组缩放与角度。每个应用 RoAd 的层只存储数量等于输出隐藏维度的参数(实际为out_features // 2,配对被复用),参数最少;
  • road_2:每个元素使用独立的缩放与角度,参数量为road_12 倍out_features个);
  • road_4:每个元素使用两个不同的缩放与角度(旋转矩阵两列分别学习),参数量为road_14 倍out_features * 2个)。

三种变体对应的参数准备逻辑在 layer.py 的_prepare_cols中实现:road_1通过repeat_interleave复用组内group_size // 2个参数;road_2每个元素独立;road_4把参数 reshape 为(-1, 2, group_size),前半段用于第一列(alpha_1 * cosθ),后半段用于第二列(alpha_2 * sinθ)。

group_size:分组配对与推理速度

group_size定义元素如何分组为 2D 向量:在每个组内,元素 0 与元素group_size/2配对,元素 1 与元素group_size/2+1配对,依此类推。这种「前一半与后一半配对」而非相邻配对的方式,是特意设计的——它不改变模型性能(因为元素是无序的),但对推理速度有影响,在 VLLM 等场景下尤为明显。

  • 官方建议:group_size 至少取 32,推荐 64(默认值)以获得最佳速度;
  • 硬性约束:模型的隐藏维度(使用张量并行时为每个分区的隐藏维度)必须能被group_size整除。因此对于隐藏维度很小的模型,你可能需要调小该参数。若out_features % group_size != 0,layer.py 会抛出ValueError

target_modules 与 modules_to_save

  • target_modules:传字符串时执行正则匹配;传列表时做精确匹配或以列表元素为后缀的匹配;传"all-linear"通配符时选择所有 linear/Conv1D 模块(若模型是PreTrainedModel,输出层会被排除);若不指定,则按模型架构自动选择(RoAd 的目标模块映射TRANSFORMERS_MODELS_TO_ROAD_TARGET_MODULES_MAPPING由 LoRA 的映射复制而来,见 constants.py)。对于未知架构,会抛出错误,此时需手动指定target_modules
  • modules_to_save:例如在 Sequence Classification 或 Token Classification 任务中,随机初始化的classifier/score最终层需要被设为可训练并保存,就通过该参数指定。

权重初始化

road.md 未展开初始化细节,但从 layer.py 的reset_parameters可以看到:

  • init_weights=True(默认):road_thetann.init.zeros_置零、road_alphann.init.ones_置为 1,即初始为恒等旋转,保证训练开始时与基础模型行为一致;
  • init_weights=Falseroad_theta用均值 0、标准差 0.5 的正态分布初始化,road_alpha用均值 1、标准差 0.5 的正态分布初始化。

config 的帮助文本特别提醒:除非你完全清楚自己在做什么,否则不要修改init_weights默认值。

快速开始:用 RoadConfig 微调因果语言模型

最直接的入口是 examples/road_finetuning/README.md 提供的快速开始代码:

from peft import RoadConfig, get_peft_model from transformers import AutoModelForCausalLM, AutoTokenizer, 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") road_config = RoadConfig(variant="road_1") peft_model = get_peft_model(model, road_config) trainer = Trainer( model=peft_model, train_dataset=dataset, dataset_text_field="text", max_length=2048, tokenizer=tokenizer, ) trainer.train() peft_model.save_pretrained("road-llama-3-8b")

注意:示例 README 中RoadConfig(variant="1")属于示例中的写法,而配置校验(config.py)要求变体必须严格等于road_1/road_2/road_4,因此请使用variant="road_1"这类完整取值。

微调时请务必把学习率调高到1e-3 附近(相对 LoRA 而言),这是 road.md 明确给出的建议。

完整训练脚本与命令行参数

仓库在 examples/road_finetuning/road_finetuning.py 提供了开箱即用的完整训练脚本,支持 Hugging Face Trainer、4-bit 量化与推送 Hub。核心配置段如下(road_finetuning.py):

from peft import RoadConfig, get_peft_model, prepare_model_for_kbit_training # RoAd config for the PEFT model road_config = RoadConfig( variant=variant, # road_1 / road_2 / road_4 target_modules=( road_target_modules.split(",") if road_target_modules else ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] ), ) # get the peft model with RoAd config model = get_peft_model(model, road_config)

脚本内部支持量化路径(road_finetuning.py):通过BitsAndBytesConfig(load_in_4bit=True, ...)加载模型后调用prepare_model_for_kbit_training(model, use_gradient_checkpointing=True),与 RoAd 无缝配合。

运行方式(examples/road_finetuning/README.md):

# 普通微调 python examples/road_finetuning/road_finetuning.py \ --base_model meta-llama/Meta-Llama-3-8B \ --data_path timdettmers/openassistant-guanaco # 4-bit 量化微调 python examples/road_finetuning/road_finetuning.py \ --base_model meta-llama/Meta-Llama-3-8B \ --quantize

完整参数示例(含学习率、变体与目标模块):

python road_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 1e-3 \ --cutoff_len 512 \ --val_set_size 500 \ --quantize \ --eval_step 10 \ --save_step 100 \ --device "cuda:0" \ --variant road_1 \ --road_target_modules "q_proj,k_proj,v_proj,o_proj" \ --hub_model_id "YOUR_HF_REPO" \ --push_to_hub

训练完成后可用save_pretrained保存适配器;加载时与普通 PEFT 模型一致,通过AutoModel.from_pretrained即可直接使用。

推理与合并:逐元素运算背后的数学

RoAd 前向传播的逐元素实现位于 layer.py 的_apply_road

x_grouped = x.reshape(-1, 2, group_size // 2) x1 = x_grouped[:, 0, :] x2 = x_grouped[:, 1, :] rotate_half_x = torch.stack((-x2, x1), dim=1).reshape(x.shape) result = x * first_col + rotate_half_x * second_col

即先按组把向量切成前后两半并配对,构造「旋转一半」((-x2, x1),与 RoPE 的 rotate_half 思路一致),再与预计算的first_colsecond_colα*cosθα*sinθ)逐元素相乘累加——整个旋转过程不出现稠密矩阵乘法,这正是 RoAd 高效推理的基础。

如果需要把适配器合并进基础权重,layer.py 给出了合并公式:

R @ (W @ x + b) = (R @ W) @ x + R @ b

左侧是未合并状态下的使用方式(用高效的逐元素实现替代矩阵乘法),右侧是合并后(R @ W)成为新权重、R @ b成为新偏置。具体合并实现见Linear.merge(layer.py),它通过_get_delta_weight由角度与缩放重建分块对角旋转矩阵 R 并与权重做torch.matmulsafe_merge=True时会在副本上检查 NaN 再写入。合并后的unmerge则利用torch.linalg.inv求逆还原(由于旋转矩阵未必正交,因此用逆而非转置,layer.py)。

对 bitsandbytes 量化模型,合并逻辑在 bnb.py 中:8-bit 合并(Linear8bitLt.merge)与 4-bit 合并(Linear4bit.merge)都会先将量化权重反量化、与 R 相乘后重新封装为bnb.nn.Int8Params/bnb.nn.Params4bit,且官方在源码中提示:对量化层合并/反合并可能因舍入误差导致生成结果与未合并时略有差异

同批次混合多适配器推理

RoAd 与 LoRA 一样支持在同一个批次中为不同样本使用不同适配器。调用方式与 lora.md 中的「Inference with different LoRA adapters in the same batch」一致:在模型 forward 时传入adapter_names列表(长度与输入批次一致),其中可用"__base__"表示不使用任何适配器。

其底层实现路径是:

  1. model.py 的_enable_peft_forward_hooks:校验adapter_names中不存在的适配器名(防止拼写错误)并注入 forward 预钩子;训练模式下传adapter_names会直接抛错;
  2. layer.py 的_mixed_batch_forward:先跑一次基础层前向,再按adapter_names把批次切分为子批次,对每个子批次分别应用对应适配器的_apply_road,最后写回原位置。由于 RoAd 是逐元素运算,这种「按样本分派」的开销远小于逐样本执行批量矩阵乘法,这正是文档所述高吞吐的来源;
  3. 若存在已合并的适配器,同时传入adapter_names会抛出错误(layer.py),需先调用unmerge_adapter

该能力在测试侧同样有覆盖:tests/testing_common.py_test_mixed_adapter_batches明确将RoadConfigLoraConfig一并列为支持混合适配器批次的配置类(tests/testing_common.py)。

配置与测试验证

  • 配置合法性:tests/test_config.py 将RoadConfig纳入全量配置测试矩阵,验证其默认值、__post_init__校验(非法 variant、非偶数 group_size 抛错)以及保存/加载往返一致性;
  • 与框架集成RoadConfig在 auto.py 与 peft_types.py 中注册为PeftType.ROAD,因此AutoPeftModelget_peft_model等统一入口均可用;
  • 目标模块映射:复用 LoRA 的映射表(constants.py),意味着主流 Transformers 架构开箱即用。

小结

RoAd 是 PEFT 家族中「以最简几何操作换取极致参数效率」的代表方法:2×2 旋转 + 缩放即可完成适配,逐元素前向天然适配混合批次高吞吐服务,稀疏旋转还可作为可组合、可解释的子空间干预。落地时记住三个要点:学习率用 1e-3 量级、只作用于线性层、group_size需整除隐藏维度。需要深入时,可继续阅读 road 调谐器源码(config / layer / model / bnb 四个文件)与 road_finetuning 示例,并结合 method_comparison/MetaMathQA 的实测数据自行评估效果。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 21:08:59

从源码构建 JAX:jaxlib、hermetic Python、测试与文档开发全指南

从源码构建 JAX:jaxlib、hermetic Python、测试与文档开发全指南 【免费下载链接】jax Composable transformations of PythonNumPy programs: differentiate, vectorize, JIT to GPU/TPU, and more 项目地址: https://gitcode.com/gh_mirrors/jax/jax 本文是…

作者头像 李华
网站建设 2026/9/20 21:07:04

技能熔炉:让 SKILL.md 安装像 brew install 一样简单

如果你用过 DeepSeek Harness,大概会有同感:模型调度、上下文管理、工具调用这些核心功能做得再好,最后拦住你的往往是“技能到底怎么装”。SKILL.md 本来是一种很优雅的技能描述格式——一个 Markdown 文件,带上 YAML 头信息&…

作者头像 李华
网站建设 2026/9/20 21:04:08

vue-element-adm模板:Vue3+Vite6+TS后台管理系统工程化实践

简介:基于Vue 3、Vite 6、TypeScript与Element Plus构建的后台管理前端模板,并配套后端源码,适合需要快速搭建中后台系统,或希望系统学习前后端分离开发流程的开发者。压缩包共含271个文件,其中包含90个Vue组件、88个T…

作者头像 李华