适配器即模型 —— ShadowPEFT 现已集成到 🤗 PEFT 库

Community Article
Published September 15, 2026

作者: Zongxi Li, Xianming Li, Tsz-fung Andrew Lee, Jing Li, Haoran Xie, Qing Li

This article is also available in English


ShadowPEFT 现在已经是 🤗 PEFT 库中原生支持的方法了:ShadowPEFT 采用了和 LoRA 完全不同的适配器结构设计,但它的用法和 LoRA 一样,都是调用 get_peft_model,适配器的保存和加载路径也完全一致,非常好上手。这篇文章会先简单回顾一下 PEFT 的技术发展,再用最直接、最不绕弯子的方式讲清楚 ShadowPEFT 到底改变了什么。紧接着我们会介绍它在库中的具体集成方式,最后把它和 LoRA、DoRA 放在一起在不同任务上做个对比。

Paper: ShadowPEFT: Shadow Network for Parameter-Efficient Fine-Tuning.


广为人知的参数高效微调(PEFT)

参数高效微调(PEFT)让我们可以在不更新预训练模型全部参数的情况下,把大模型适配到各种下游任务上 [1]。

全量微调会把模型的所有权重都更新一遍。这种做法当然有效,但带来的问题是:保存的检查点和原始模型一样大,优化器参数也极其占内存,而且如果要存很多个任务专用的副本,很快就会变得不现实。PEFT 用了一个很直觉的办法来让优化大模型变得可行:它把预训练模型冻住,只训练一小部分额外的参数来做任务的适配。推理的时候,这些额外参数引导冻结的骨干网络做任务;训练结束后,也只需要管理这些额外的参数,不用重复保存整个骨干模型的副本。

至于这些额外参数怎么挂上去,不同的方法各有不同。基于提示学习的方法 [2] 是在输入前面加一些可学习的 token。基于适配器的方法是在层之间插入一些小模块来注入任务适配所需要的知识。LoRA 一类方法 [3,4] 可以做到不改动原有架构,只是在选定的权重旁边加一个低秩更新:对于一个冻结的线性层 WW,训练矩阵 AABB,然后算 y=Wx+α(xA)By = Wx + \alpha \cdot (xA)B,其中 α\alpha 是个缩放系数。虽然方式不一样,但核心思想是一样的——不去动绝大部分模型参数,让一小部分可训练的参数来完成任务的适配。

PEFT 改变了我们微调大语言模型的方式。像 LoRA 这样的方法,不需要更新几十亿的参数,只需要学一小部分可训练的参数,使得微调的成本大大降低,这已经成为适配大语言模型的标配做法之一。这些方法现在基本都是通过 Hugging Face 的 🤗 PEFT 库 [5] 来用的。这个库已经成了在 Transformers 和 Diffusers 上做参数高效微调的主流工具。现在,ShadowPEFT 也集成到了这个库中。


ShadowPEFT 登场:适配器即模型

但是,LoRA 这一类方法有一个共同的特点:它们把任务表示成一组分散在不同权重上的、彼此独立的低秩更新。骨干网络确实会把表示一层层传下去,从而这些修改在计算过程中会产生相互影响;但这样适配机制本身并没有维护一个显式的、任务相关的状态,这个状态不会被层层更新和复用,这会导致微调过程缺少必要的全局感知。这就引出了一个很自然的问题:适配本身能不能拥有一个可维持且可更新的状态?

ShadowPEFT 就是从这个问题出发的。它不再把适配看成是一堆权重的扰动,而是引入了一个紧凑且完整的影子模型,这个模型自带一个可随层数而持续更新的隐藏状态:

s(0)s(1)s(2)s(L)s^{(0)} \rightarrow s^{(1)} \rightarrow s^{(2)} \rightarrow \cdots \rightarrow s^{(L)}

状态 s()s^{(\ell)} 在第 \ell 层承载着任务相关的信息。在每个 Transformer 层,ShadowPEFT 会做三件事:

  1. 影子注入(shadow → base): 把差异 hsh - s 通过一个低秩瓶颈投影一下,再加到块的输入上。这里的 hh是当前层冻结基模型产生的隐藏状态ss 是影子状态——也就是可训练的影子网络维护的一个持久的任务向量(如图 2a 所示)。
  2. 骨干模型编码: 冻结的 Transformer 层处理经过精炼的表示(如图 2b 所示)。
  3. 影子更新(base → shadow): 两个小型的 MLP 分别产生一个候选值和一个门控值;影子状态通过一个门控残差混合往前走,把累积的任务信号传到下一层(如图 2c 所示)。

Framework of ShadowPEFT

这样就形成了一个双向耦合的信息流动:适配器不只是个旁路网络,而是影子模型在帮骨干优化表示,同时骨干也在反过来更新影子。整个过程的核心就是在解码器中带着 (h, s) 一起走:每一层,冻结的块处理 h,影子网络把 s 里的信息注入到 h 中,然后用新的 h 更新 s。这样一来,后面的层既能看见累积下来的影子状态,也能看见最新的骨干表示。

为什么这一点很重要

把模型适配当成一个有状态的计算来做,就能解锁一些单纯靠权重增量很难做到的事情:

1. 跨层协调。 在分布式的更新方式里,每一层被适配的模块各自管各自的参数,独立初始化并且单独更新。但在 ShadowPEFT 里,影子状态贯穿了整个深度,为任务导向的适配提供了结构连续性。后面的层可以用影子在前面累积下来的信息:它学的不再只是“第 12 层要改什么”,而是“考虑到任务通路到目前为止积累的所有信息,第 12 层现在应该怎么做?”

门控残差更新会显式地保留一部分之前的状态,同时又融入了当前骨干表示的信息,这就给了适配一种深度方向上的协调机制。

2. 适配容量变成了一个模型规模的问题。 LoRA 的容量是由秩和被适配的矩阵数量决定的。ShadowPEFT 多了一个维度:任务专用的模型应该有多大?可以稳定地 scale 到多大?在我们的实验里,增加 ShadowPEFT 的参数规模,性能在一个很大的范围内都会跟着涨,而 LoRA 只能做到基本持平,DoRA 反而还下降了——这说明它们的缩放行为在本质上是不同的。我们可以不至于“眼下要修多少个参数”,而是可以继续探讨“任务专用的计算应该有多大的容量”。

3. 适配任务的组件本身成了一个独立的模型。 LoRA 的适配器之所以有意义,是因为它依附于某个特定的骨干网络;把骨干拿掉,LoRA 的参数就不能单独做预测了。ShadowPEFT 不一样:影子网络有自己的预测头,训练时会收到直接的任务监督信号。所以训练完的检查点既可以附着在骨干上做推理,也可以拆下来单独跑推理。这就为边端-云端部署提供了新可能:紧凑的影子在本地处理简单的请求,把难一点的交给附着在云端的完整模型做更复杂的推理。

4. 将预训练模型作为影子适配器。 我们随后发现了一个相当精彩的扩展场景。既然影子模块本身是一个可独立运行的功能模型,它自然可以初始化自另一个预训练 LLM。例如,一个较小的模型,如 Qwen3-0.5B,可以作为更大骨干模型(如 Qwen3-8B)的影子模型,通过继续训练这一较小影子模型来完成对大模型的适配。在这种配置下,小模型学习如何对大模型的表征进行细化和修正,使得适配能力可以跨模型尺度复用。这一视角将 PEFT 从“轻量级参数注入”进一步扩展为一种可复用的、跨尺度的适配方法。

传统 PEFT(LoRA 那种) ShadowPEFT
可训练参数 分散的:每个目标线性层上都有一个独立的低秩更新 集中的:一个影子网络 + 每层的小型注入/更新网络
更新发生在哪里 选定的 nn.Linear(或者提示词、插入的适配器) 每个解码器层
信息流动 单向的局部增量:y = Wx + (xA)B 双向的:先把影子注入基模型,再用基模型更新影子
训练完之后 合并进 W,或者把适配器留在同一个骨干上 可以继续附着,也可以用 unload_shadow() 把影子拆下来单独跑

Differences between LoRAs and ShadowPEFT

在生成和理解类的基准测试上,紧凑版的 ShadowPEFT 在与 LoRA、DoRA 差不多可训练参数量的情况下表现相当。接下来的内容会讲清楚这种结构是怎么接入 🤗 PEFT 库的——因为一个本身就是个模型的适配器,不能套用原来那种“合并再卸载”的老路子。


这个方法是怎么接入 PEFT 库的

ShadowPEFT 接入 PEFT 的方式和 LoRA 一样,也是需要定义三个组件:一个配置类ShadowConfig、一个 BaseTuner 模型、一个 BaseTunerLayer。 这样 get_peft_model(model, ShadowConfig(...)) 用起来就和 LoRA 几乎一模一样。检查点的保存和加载走的也是常规的 save_pretrained / from_pretrained 接口,底层只做了很少的键名映射。

使用示例

ShadowPEFT 当前已经合并到 PEFT main 分支,将随下一版本 PEFT 发布,目前可通过 main 分支调用:

pip install -U git+https://github.com/huggingface/peft.git

详细使用方式可参考官方文档:https://huggingface.co/docs/peft/main/en/package_reference/shadow#shadowpeft

以下是一个简单的示例:

from transformers import AutoModelForCausalLM
from peft import ShadowConfig, get_peft_model

# 加载冻结的基模型
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-8B")

# 配置 ShadowPEFT
config = ShadowConfig(
    r=8,                            # 注入瓶颈的秩
    shadow_num_hidden_layers=1,     # 影子骨干的大小
    shadow_model="mirror",          # 自动构建基模型的缩小版;
                                    # 也可以传一个预训练检查点,比如
                                    # 对 Qwen3-8B 可以用 "shadow-llm/Qwen3-0.6B-H8B"
    task_type="CAUSAL_LM"
)

# 包装基模型
model = get_peft_model(model, config)
model.print_trainable_parameters()

# 正常生成
out = model.generate(input_ids, max_new_tokens=32)

# 评估独立的影子路径(可以拆下来的小模型)
shadow = model.base_model.unload_shadow()   # 返回一个 DetachedShadowModel
shadow_out = shadow.generate(input_ids, max_new_tokens=32)

和 LoRA 最大的概念区别在于:LoRA 活跃在选定权重的“里面”,需要和主干模型合并起来才能发挥作用;ShadowPEFT 是一个独立的网络,训练结束后可以用 unload_shadow() 得到不含主干网络的影子模型做独立部署。

LoRA 和 ShadowPEFT 快速对比

方面 LoRA ShadowPEFT
作用对象 单独的线性层(q_proj 等) 整个解码器块
默认 target_modules 注意力投影 所有连续的解码器块
额外模块 没有 影子骨干 + 可选的头/投影
训练完之后 把增量合并进 W unload_shadow() 把这个独立网络拆下来

如果你已经熟悉 PEFT 库的结构,那 ShadowPEFT 就是在同样的骨架上多了三点变化:包装的是块而不是线性层,在解码器里带着 (h, s) 走,以及把适配器当作一个可以卸载的模型而不是一个可以合并的增量。


对比实验

为了验证 ShadowPEFT 的效果,我们把它和目前用得最多的两种 PEFT 方法 LoRA 和 DoRA 做了对比。所有实验都用 PEFT 库的默认超参数,在同一台机器(NVIDIA A100 80 GB)上进行。每种方法的详细配置文件都有链接。

MetaMathQA → GSM8K

设置:

  • 基模型:Llama-3.2-3B [6]
  • 训练数据:MetaMathQA [7](数学推理数据集)
  • 评测:GSM8K [8](小学数学应用题)
  • 主要指标:测试准确率(精确匹配)

配置文件:

方法 可训练参数量 测试准确率 训练时间 峰值内存 检查点大小
LoRA 9.18M 46.9% 15 分钟 22.3 GB 36.7 MB
DoRA 9.29M 46.2% 19 分钟 24.5 GB 37.2 MB
ShadowPEFT 8.66M 48.1% 17 分钟 28.2 GB 26.0 MB

ShadowPEFT 用比 LoRA 和 DoRA 略少的可训练参数,拿到了最高的测试准确率(48.1%)。它的峰值内存更高(28.2 GB 对 22.3 GB),这是因为影子骨干要对每个 token 都跑一次前向传播,还要维护双份的 KV 缓存——这是它那个持久任务状态机制带来的合理开销,而且可以通过缩小影子骨干来调节。另一方面,检查点只有 26 MB,远小于 LoRA 的 36.7 MB,这得益于紧凑的逐块模块设计和共享的骨干权重。总的来说,ShadowPEFT 在 GSM8K 上带来了明显的准确率提升,而且存储占用更小,很适合那些把测试性能放在首位的场景。

图像生成(DreamBooth)

设置:

  • 基模型:FLUX.2-klein-base-4B [9](扩散 Transformer)
  • 数据集:标准 PEFT 猫图 DreamBooth 集 [10](大约 20 张同一只猫的照片)
  • 指标:
    • DINOv2 余弦相似度(跟原图的相似度,越高越好)
    • 漂移(Drift)(对无关提示的输出变化,越低越好,说明没忘掉原来的知识)

配置文件:

方法 可训练参数量 DINO ↑ 漂移 ↓ 训练时间 峰值内存 检查点大小
LoRA 38.3M 0.671 0.274 10 分钟 10.8 GB 153 MB
DoRA 39.2M 0.682 0.250 17 分钟 12.7 GB 157 MB
ShadowPEFT 31.2M 0.717 0.244 8 分钟 10.3 GB 74.5 MB

ShadowPEFT 在所有指标上都超过了 LoRA 和 DoRA。它用更少的可训练参数(31.2M 对 38.3M)就达到了更高的主体保真度(DINO ↑ 0.717);双向的信息流动(注入 + 更新)让影子网络能比孤立的低秩增量更有效地精炼基模型的表示。漂移更低(0.244)说明它更好地保留了基模型的通用知识;影子网络的持久状态帮助它学会新概念的同时,不会覆盖掉冻结骨干原有的先验知识。另外,紧凑的影子骨干也减少了计算和存储的开销,所以训练更快(8 分钟 vs LoRA 10分钟 vs DoRA 17分钟),检查点储存也更小(74.5 MB)。

为了直观感受图像生成的质量,我们用同样的五个提示词各生成了一些样例:

f0d7e79dd519982dbd530622abd31900

可以看到,ShadowPEFT 生成的图像更自然,细节也更丰富。LoRA 和 DoRA 经常在主物体周围留下一圈白边,看起来像是“拼贴”上去的,整体质量不高。相比之下,ShadowPEFT 对提示语义的理解更好——比如第五行的橙色猫,毛色和纹理都很自然,而 LoRA/DoRA 的版本有明显的伪影。背景的融合也更平滑。

这些结果表明,ShadowPEFT 的结构优势,即通过影子状态实现跨层协调,直接转化成了更好的生成质量,尤其是在颜色、边缘这些细粒度的属性上。


ShadowPEFT 的超参数选择

下面是一些推荐的起始值,不是穷举调参的结果。

  • 注入秩(r)。 控制 W_down / W_up 的瓶颈维度(d → r → d)。从 8 或 16 开始试。注意 r 并不是总参数量的决定因素,影子骨干才是大头。
  • shadow_dropout 作用于瓶颈之前的差异 h − s。默认 0.2,可以试试 0.2–0.5。设大一点有助于正则化,减少遗忘。
  • shadow_alpha 注入修正的缩放系数。一开始设小一点:0.1–1.0,默认 0.1。设太大容易在训练初期不稳定。在某些场景下(比如用了较强的正则化),alpha > 1.0 也可以试试。
  • 影子骨干的大小。 大部分可训练参数都在这儿。通过 shadow_num_hidden_layersshadow_num_attention_headsshadow_hidden_sizeshadow_intermediate_size 来控制。用 shadow_model="mirror" 可以自动构建基模型的缩小版,或者传一个预训练检查点的路径(比如 "Qwen/Qwen3-0.6B")来从一个独立的小模型初始化骨干——这样做通常会加快收敛,最终效果也更好。

致谢

衷心感谢 Benjamin Bossan 博士(@BenjaminB)在将 ShadowPEFT 集成到 PEFT 库的过程中给予的支持。

引用

如果您觉得这项工作有帮助,欢迎引用我们:

@article{li2026shadowpeft,
  title={ShadowPEFT: Shadow Network for Parameter-Efficient Fine-Tuning},
  author={Li, Xianming and Li, Zongxi and Lee, Tsz-fung Andrew and Li, Jing and Xie, Haoran and Li, Qing},
  journal={arXiv preprint arXiv:2604.19254},
  year={2026}
}

参考文献

[1] Zeyu Han, Chao Gao, Jinyang Liu, Jeff Zhang, and Sai Qian Zhang. Parameter-efficient fine-tuning for large models: A comprehensive survey. Trans. Mach. Learn. Res., 2024.

[2] Haokun Liu, Derek Tam, Mohammed Muqeeth, Jay Mohta, Tenghao Huang, Mohit Bansal, and Colin A Raffel. Few-shot parameter-efficient fine-tuning is better and cheaper than in-context learning. Advances in Neural Information Processing Systems, 35:1950–1965, 2022.

[3] Edward J Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Liang Wang, Weizhu Chen, et al. Lora: Low-rank adaptation of large language models. ICLR, 2022.

[4] Shih-Yang Liu, Chien-Yi Wang, Hongxu Yin, Pavlo Molchanov, Yu-Chiang Frank Wang, Kwang-Ting Cheng, and Min-Hung Chen. DoRA: Weight-decomposed low-rank adaptation. In Forty-first International Conference on Machine Learning, 2024.

[5] https://github.com/huggingface/peft

[6] https://huggingface.co/meta-llama/Llama-3.2-3B

[7] https://huggingface.co/datasets/meta-math/MetaMathQA

[8] https://huggingface.co/datasets/openai/gsm8k

[9] https://huggingface.co/black-forest-labs/FLUX.2-klein-base-4B

[10] https://huggingface.co/datasets/google/dreambooth

Community

Sign up or log in to comment