PEFT 中的 X-LoRA:基于 LoRA 专家混合的动态门控微调完整指南
2026/9/20 11:30:43 网站建设 项目流程

PEFT 中的 X-LoRA:基于 LoRA 专家混合的动态门控微调完整指南

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

X-LoRA(Mixture of LoRA Experts)是一种基于低秩适配(LoRA)的专家混合(MoE)式参数高效微调方法:它通过一个高粒度(token、layer、sequence 级别)的 scalings 矩阵,对一组冻结的 LoRA 适配器进行稀疏或稠密混合。本指南以 PEFT 仓库的 xlora.md 文档为核心,结合 xlora 源码目录 与 test_xlora.py,系统讲解 X-LoRA 的原理、双层前向机制、XLoraConfig 全部参数、构建与推理流程,帮助你直接用get_peft_model为任意 transformers 模型挂载一组 LoRA 专家并动态调配它们的贡献。

X-LoRA 是什么:冻结 LoRA 专家 + 动态门控

X-LoRA(论文:Mixture of LoRA Experts,Buehler & Buehler, APL Machine Learning, 2024)是 PEFT 中的一种方法,它基于一个高粒度(token、layer、sequence)的 scalings 矩阵,实现对 LoRA 专家的稀疏或稠密混合。由于专家是冻结的 LoRA 适配器、底座模型也是冻结的,需要微调的参数量被大幅压缩——实际只有 X-LoRA 门控分类器本身是可训练的。

X-LoRA 的一个独特优势是通用性:只要某个transformers底座模型上挂着 LoRA 适配器,X-LoRA 就可以直接套用。尽管采用了专家混合策略,完全不需要修改模型代码——PEFT 通过替换 LoRA 层的forward方法(见下文源码分析)在运行时注入 scalings,模型架构代码保持原样。

从源码结构看,X-LoRA 在 PEFT 中作为一等公民注册:src/peft/tuners/xlora/__init__.py通过register_peft_method(name="xlora", config_cls=XLoraConfig, model_cls=XLoraModel)完成注册,PeftType.XLORA定义于 peft_types.py,并从包顶层导出XLoraConfigXLoraModel(见 src/peft/init.py)。

论文摘要的核心表述是:从一组预训练的 LoRA 适配器出发,门控策略利用 hidden states 动态混合各适配层,使 X-LoRA 模型能够"从未用过"地组合深层逐层(deep layer-wise)能力来解决任务;这种设计受生物学中"通用性与多样性"原则启发,神经网络构建块在不同层级结构中被复用,因此 X-LoRA 可以轻松应用于任何现有 LLM 而无需修改底层结构。原论文的应用聚焦于生物材料分析、蛋白质力学与设计、分子设计等科学场景,但方法本身与领域无关。

核心机制:双层前向传播(dual forward pass)

X-LoRA 每个推理步骤都需要将底座模型运行两次,这是理解其原理与算力成本的关键:

  1. 第一次前向(scaling pass):在不挂载任何 LoRA 适配器的情况下运行底座模型,得到各 token 的 hidden states;同时注入一组"占位 scalings"(默认全为scaling_pass_value),使后续替换过的 LoRA 层能正常执行。
  2. 计算 scalings:将第一步得到的 hidden states 喂给 X-LoRA 分类器,分类器输出形状为(batch_size, seq_len, n_layers, n_classes)的 scalings 矩阵,其中n_layersLoRA 适配层的数量(不是模型层数),n_classes是专家(LoRA 适配器)数量。
  3. 第二次前向(real forward pass):把计算出的 scalings 注入到各 LoRA 层,重新运行模型。第二次运行的输出即为该步的最终结果。

由于这套双层前向机制,X-LoRA 能让模型"反思自己的知识"——每次生成都依据当前上下文动态重配架构,不同 prompt 下每个 token 激活的适配器组合都会变化。

源码中,这一流程由 model.py 的_enable_peft_forward_hooks上下文管理器实现:它注册一个模型的 pre-forward hook,hook 内部先调用internal_xlora_classifier.make_dummy_scalings生成占位 scalings,通过register_forward_pre_hook挂到每个LoraLayer上,然后在torch.no_grad()disable_adapter_layers()环境下执行 base model 前向,拿到output_hidden_states;接着调用self.internal_xlora_classifier(...)计算真实 scalings 并保存到internal_xlora_scalings,最后再挂载一次真实 scalings 的 hook 并执行第二次前向。测试test_forward_hooks_are_cleaned_up专门验证了 generate 多次调用 forward 时 hook 不会累积泄漏(详见 test_xlora.py)。

源码架构:分类器、替换层与组装流程

src/peft/tuners/xlora/目录包含 5 个文件,职责划分清晰:

文件职责
config.py定义XLoraConfig配置类
model.py定义XLoraModel,负责组装、加载专家、双层前向
classifier.py定义XLoraClassifierTemperatureScaledSoftmax门控网络
layer.py定义XLoraLayer及线性/嵌入/卷积三种替换层
init.py注册 PEFT 方法并导出公开 API

门控网络 XLoraClassifier

XLoraClassifier(classifier.py)是一个小型 MLP,输入为最后一层 hidden states,输出每个 token 对每个 (层, 专家) 的 scalings:

  • xlora_depth=1(默认)时,只有一个nn.Linear(config.hidden_size, n_classes)(若layerwise_scalings=True则为hidden_size → n_classes * n_layers),是一个线性门控;
  • xlora_depth > 1时,结构为Linear(hidden_size, xlora_size) → ReLU → Dropout → 若干Linear(xlora_size, xlora_size)隐藏层 → 最终 Linear
  • layerwise_scalings=False(默认),分类器只输出非逐层的 logits,源码通过logits.unsqueeze(2)+expand(-1, -1, n_layers, -1)将其广播复制到每一层;
  • enable_softmax=True时使用TemperatureScaledSoftmaxlogits / temperature后 softmax)归一化 scalings。

替换层 XLoraLayer 族

convert_layers_to_xlora(model.py)遍历 base model 的所有模块,按精确类型匹配lora.Linearlora.Embeddinglora.Conv2d,分别包装为XLoraLinearLayerXLoraEmbeddingLayerXLoraConv2dLayer,并直接替换module.forward为包装层的前向方法。包装层的核心逻辑(layer.py):

  1. 若适配器被禁用(adapters_disabled),直接返回 base layer 结果;
  2. get_maybe_topk_scalings取出本层 scalings(scalings[:, :, layer_number, :]),并按需执行 top-k 掩码与 top-k softmax;
  3. 对每个激活的适配器,将输入x乘以对应专家维度上的 scalings(apply_scalings_to_x),再走标准的 LoRA 路径lora_B(lora_A(dropout(x_mod))) * scaling * scaling_weight累加到 base 输出上。

其中global_scaling_weight作为乘子统一放大/缩小每个 LoRA 专家的输出;XLoraEmbeddingLayer还会额外处理带缩放因子的嵌入层(如 Gemma3 的embed_scale,有测试test_xlora_embed_scale_is_applied验证)。

组装流程 XLoraModel

XLoraModel.__init__(model.py)依次完成:

  1. DUMMY_TARGET_MODULES创建一个空的LoraModel骨架;
  2. 检查use_cache必须为False,否则抛ValueError
  3. 遍历config.adapters字典,调用_load_adapter_into_lora_model逐个自动加载LoRA 专家(等价于PeftModel.from_pretrained的行为,支持 hub 模型 id 或本地 checkpoint 路径);
  4. set_adapter激活全部专家,随后_maybe_freeze_all_adapters:当use_trainable_adapters=False时,把名字含.lora_的参数requires_grad=False,保证只有分类器可训练;
  5. 执行convert_layers_to_xlora替换 LoRA 层,并构建XLoraClassifiern_classes = len(adapters))。

XLoraModel还实现了_mark_only_adapters_as_trainable(空操作,因为 X-LoRA 要求专家冻结)、enable_adapter_layers/disable_adapter_layers(切换disabled标志),以及_get_adapter_state_dict(只保存internal_xlora_classifier相关键,即只序列化分类器权重)。

XLoraConfig 参数详解

XLoraConfig继承自PeftConfig(config.py),全部参数如下表:

参数类型默认值说明
hidden_sizeintNone(缺失时警告并置为 4096)底座模型的 hidden size,必须与实际模型匹配
adaptersdict[str, str]None(缺失时置为空 dict)适配器名字到 LoRA 模型 id(hub id 或本地目录)的映射,构造时会自动加载这些 LoRA 作为专家;from_pretrained加载时需通过关键字参数传入新的 adapters dict
enable_softmaxboolTrue是否对 X-LoRA 分类器输出施加 softmax
enable_softmax_topkboolFalse是否对 top-k 选中的 LoRA 专家施加 softmax;与enable_softmax互斥,且必须同时设置top_k_lora
softmax_temperaturefloat1.0softmax 温度,越小预测越尖锐
layerwise_scalingsboolFalse为 True 时为每个 LoRA 层(每个专家)分别生成 scalings;为 False 时 scalings 广播复制到每一层
top_k_loraintNone稀疏选择 top-k 个 LoRA 专家,代替默认的稠密方法;None表示稠密
xlora_depthint1X-LoRA 分类器的深度
xlora_sizeint2048X-LoRA 分类器的隐藏层大小,xlora_depth=1时无关
xlora_dropout_pfloat0.2X-LoRA 分类器的 dropout 概率,xlora_depth=1时无关
use_trainable_adaptersboolFalse是否让 LoRA 专家可训练(默认冻结专家,只训分类器)
scaling_pass_valuefloat0.0第一次前向(scaling pass)中占位 scalings 的填充值
global_scaling_weightfloat1.0乘以每个 LoRA 专家输出的全局缩放权重

__post_init__中还会做几项校验与告警:hidden_size/adapters缺失时给出 warning 并填充默认值;enable_softmax_topk=True但未设置top_k_lora时告警;enable_softmax_topkenable_softmax同时开启时告警"会导致更差的表现";top_k_lora < 1时告警。

另外,加载配置时adapters字段中的路径会被忽略,只使用字典的 key(配置重载后优先使用已保存的适配器),这点在XLoraConfig的 docstring 中已明确说明。

实战:用 get_peft_model 构建 X-LoRA 模型

以下是 model.py 文档字符串中的官方示例,展示了最典型的用法——基于 Mistral-7B-Instruct 与 int8 量化底座构建 X-LoRA:

import torch from transformers import AutoModelForCausalLM, AutoConfig, BitsAndBytesConfig from peft import XLoraConfig, get_peft_model, prepare_model_for_kbit_training model_config = AutoConfig.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1") config = XLoraConfig( task_type="CAUSAL_LM", hidden_size=model_config.hidden_size, xlora_depth=4, adapters={ "adapter_1": "./path/to/the/checkpoint/", "adapter_2": "./path/to/the/checkpoint/", "adapter_n": "./path/to/the/checkpoint/", }, ) int8_config = BitsAndBytesConfig(load_in_8bit=True) model = AutoModelForCausalLM.from_pretrained( "mistralai/Mistral-7B-Instruct-v0.1", trust_remote_code=True, attn_implementation="flash_attention_2", device_map="cuda:0", torch_dtype=torch.bfloat16, quantization_config=int8_config, ) model = prepare_model_for_kbit_training(model) xlora_model = get_peft_model(model, config)

要点解读:

  • 专家来源adapters的值可以是 Hub 上的 LoRA 模型 id,也可以是本地保存的 LoRA checkpoint 目录;每个专家的LoraConfig(如target_modules)可以互不相同,_load_adapter_into_lora_model会为每个专家独立读取并注入配置;
  • use_cache 必须关闭:构造器会强制校验model.config.use_cacheFalse,因此记得设置model.config.use_cache = False(测试 fixtures 中正是这样做的);
  • 专家默认冻结use_trainable_adapters=False时只有internal_xlora_classifier.*参数可训练,训练时用常规 Trainer 即可,所有 LoRA 专家不更新。

参考测试 test_xlora.py 的 fixture,最小可用配置也可以简化为:

peft_config = XLoraConfig( task_type=TaskType.CAUSAL_LM, peft_type=PeftType.XLORA, hidden_size=base_model.config.hidden_size, xlora_depth=8, adapters=adapters, # {"0": "path/to/lora-0", "1": "path/to/lora-1", ...} ) model = get_peft_model(base_model, peft_config).to(device)

推理与生成:generate 与运行时注意事项

XLoraModel.generate(model.py)在调用self.lora_model.generate前强制kwargs["use_cache"] = False(因为门控需要每步重新计算 hidden states,缓存与双层前向不兼容),生成结束后会再次_maybe_freeze_all_adapters,确保专家保持冻结。

其它运行时行为(均有测试覆盖,见 test_xlora.py):

  • disable_adapter上下文with model.disable_adapter():内禁用 X-LoRA 后,模型输出应与纯底座模型完全一致(test_disable_adapter_matches_base_modeltorch.allclose(atol=1e-5)验证,包括嵌入层变体);
  • 训练模式保持generate不会把模型悄悄切到 eval,调用前后model.training不变(test_generate_preserves_training_mode);
  • 生成后仍可训练generate之后分类器参数仍保持requires_grad=True、专家仍冻结(test_classifier_stays_trainable_after_generate);
  • DoRA 限制:X-LoRA 目前不支持 LoRA+DoRA 层,遇到use_dora的专家会直接抛ValueError(见 layer.py 的 TODO 注释)。

运行时调参与 scalings 调试 API

XLoraModel暴露了一组方法,可在构造后动态调整门控行为或观察 scalings:

方法作用
set_topk_lora(value)动态切换为 top-k 稀疏选专家;传None恢复稠密
set_global_scaling_weight(weight)设置全局专家输出缩放权重(默认 1.0)
set_scaling_pass_value(value)设置 scaling pass 的占位值;传None时自动用1/n_classes
get_global_scaling_weight()读取当前全局缩放权重
get_latest_scalings()返回最近一次预测的 scalings 张量,形状(batch_size, seq_len, n_layers, n_classes),未预测时返回None
enable_scalings_logging()/disable_scalings_logging()开启/关闭 scalings 日志(关闭不清空已有日志)
clear_scalings_log()清空日志
get_scalings_log()返回 scalings 日志列表的浅拷贝
get_bucketed_scalings_log()seq_len分桶返回日志(值为"位置列表 + 张量列表")

示例用法(对应测试test_scalings_logging_methods):

model.enable_scalings_logging() outputs = model.generate(input_ids=inputs.to(device), max_new_tokens=32) latest = model.get_latest_scalings() # (batch, seq, n_layers, n_classes) log = model.get_scalings_log() # 每个生成步的 scalings 列表 bucketed = model.get_bucketed_scalings_log() # 按 seq_len 分桶 model.disable_scalings_logging() model.clear_scalings_log()

set_topk_loraset_global_scaling_weight的修改会同步写回分类器的config(即internal_xlora_classifier.config),下次前向立即生效;get_maybe_topk_scalings中 top-k 通过对该层 scalings 做torch.topk后掩码实现,若同时开启enable_softmax_topk还会对非零部分做 per-token 归一化(测试test_per_token_normalization_with_softmax_topk验证每 token 权重和恒为 1)。需要注意enable_softmax_topkenable_softmax不要同时开启,否则会得到更差的门控结果。

保存、加载与仓库内的进一步参考

  • 保存与加载XLoraModel的状态字典只包含internal_xlora_classifier相关键(见_get_adapter_state_dict),因此save_pretrained只保存门控分类器;重新加载时通过PeftModel.from_pretrained(model=base_model, model_id=tmp_path)即可,且加载时adapters字段的路径会被忽略、以已保存适配器为准。测试test_save_load_functional验证了保存-加载前后生成 logits 完全一致(torch.equal),safe_serialization=False的 PyTorch 权重路径也覆盖在test_save_load_functional_pt中;
  • 测试:tests/test_xlora.py 提供了完整的参考实现——用 4 个不同target_modules组合的 LoRA checkpoint 构造 X-LoRA 模型,覆盖功能、逐层 scalings(layerwise_scalings=True)、嵌入层专家、top-k、softmax-topk、scaling pass 值、禁用适配器、日志与分桶等全部行为;
  • 推理示例:examples/xlora/README.md 介绍了xlora_inference_mistralrs.py,演示如何把保存的 X-LoRA/LoRA checkpoint 交给 mistral.rs 推理引擎(该引擎原生支持 X-LoRA,并采用 dual-KV cache、连续批处理、Paged Attention 等优化提升吞吐)。

引用

若在你的工作或研究中使用了 X-LoRA,请按仓库文档给出的 BibTeX 引用:

@article{10.1063/5.0203126, author = {Buehler, Eric L. and Buehler, Markus J.}, title = "{X-LoRA: Mixture of low-rank adapter experts, a flexible framework for large language models with applications in protein mechanics and molecular design}", journal = {APL Machine Learning}, volume = {2}, number = {2}, pages = {026119}, year = {2024}, month = {05}, doi = {10.1063/5.0203126}, }

小结

X-LoRA 在 PEFT 中的实现遵循"冻结专家 + 可训门控 + 双层前向"的简洁范式:XLoraConfig用一段字典声明专家集合与门控结构,get_peft_model一行完成自动加载、层替换与分类器构建。理解其 scalings 矩阵的(batch, seq, n_layers, n_classes)语义、layerwise_scalingstop_k_lora等关键开关,以及use_cache=False、DoRA 不支持等运行约束,即可在任意 transformers 底座上组合现有 LoRA 资产,获得随输入动态重配架构的专家混合模型。

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

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

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

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

立即咨询