PEFT documentation

ShadowPEFT

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.21.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

ShadowPEFT

ShadowPEFT 通过一个与主干网络并行运行的轻量级、可预训练的影子网络,来增强冻结的基础仅解码器模型。一个小型影子主干网络生成初始影子状态 s^(0),随后该状态跟随基础模型的解码器循环运行:在每个目标块中,基础隐藏状态与影子状态之间的差异会被注入回块输入(即低秩校正),并且影子状态会根据由块输出计算出的门控残差更新进行推进。仅训练影子组件,基础模型保持冻结。

Input
  ├──► Shadow backbone (small, trainable) ──► s^(0)
  └──► Base model (frozen, large)
         block_0 ◄── inject(h, s) ─► h_0 ──► update ─► s_1
         block_1 ◄── inject(h, s) ─► h_1 ──► update ─► s_2
         ...                       (the (hidden, shadow) pair rides the loop together)

由于适配是层空间中依赖于输入的轨迹(影子状态随数据演化),而非静态的权重空间增量,ShadowPEFT 无法合并到基础权重中。调用 merge、merge_adapter 或 merge_and_unload 会引发显式错误。对于 Transformers 语言模型,您可以通过 model.base_model.unload_shadow() 单独获取轻量级影子网络,该函数返回一个独立的 DetachedShadowModel。由于重建完整的去噪器具有架构特异性,Diffusers 模型不支持独立卸载。

添加多个适配器、使用 set_adapter 在它们之间切换、删除它们以及启用/禁用它们,其操作方式与其他 PEFT 方法相同。由于影子状态是网络中的单条轨迹,因此同一时间只能有 一个 适配器处于激活状态。

影子骨干网络可以通过两种方式构建,由 ShadowConfig.shadow_model 控制:

  • "mirror"(默认值):会自动创建一个较小的影子骨干网络。语言模型使用基础架构的缩减副本。具有已注册后端的 Diffusers 架构会使用从选定的基础权重初始化的缩减类架构模型;没有后端的兼容架构则回退到逐 token 残差 MLP。当影子隐藏层大小与基础模型不同时,一个经过训练的投影层会弥合这一差异。
  • 模型 ID 或本地路径:骨干网络将作为较小的预训练模型加载。Transformers 模型使用 AutoModel;已注册的 Diffusers 后端定义其自身的兼容检查点加载方式。

架构感知的 Diffusers 支持会根据模型类自动选择。Flux2 目前拥有已注册的 backend;其他兼容的基于 transformer 的 Diffusers 模型则使用通用的 MLP 回退方案。由于重建完整的 denoiser 具有架构特异性,因此所有 Diffusers 模型均不支持独立的 unload_shadow()。

与 LoRA 风格的方法相比,ShadowPEFT 引入了更多的参数和计算开销(它会运行一个并行网络并封装整个解码器块),但其适配器是一个自包含的网络,可以集中训练、跨任务复用,并从一个预训练的小模型进行初始化。一个可选的辅助损失(auxiliary_loss_weight)将任务头应用于初始阴影状态 s^(0) 并将其加到任务损失中,从而鼓励可分离的阴影路径独立解决任务。对于因果语言模型,会复用基础输出头;请将 "lm_head" 包含在 modules_to_save 中,以便通过标准的 PEFT 机制对其进行训练和保存。

KV 缓存

ShadowPEFT 支持使用 dual KV 缓存进行增量解码:一个用于冻结的基础模型,另一个用于 shadow backbone。注入/更新是 token 局部的,因此新 token 只需要其自身的 shadow 状态 s;因果性保证了前缀基础键/值(在注入下计算)仍然有效。配对对象是 ShadowCache,当 use_cache=True 时作为 past_key_values 返回。您可以像往常一样将 use_cache=True 传递给 generate()。

out = model.generate(input_ids, max_new_tokens=32)  # dual KV cache enabled by default
# or explicitly:
out = model.generate(input_ids, use_cache=True, max_new_tokens=32)

use_cache=False 仍然有效,并且会在每一步重新处理完整序列(适用于调试)。

用法

from transformers import AutoModelForCausalLM
from peft import ShadowConfig, get_peft_model

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
config = ShadowConfig(r=8, shadow_num_hidden_layers=1, task_type="CAUSAL_LM")
model = get_peft_model(model, config)
model.print_healthcheck()

out = model.generate(input_ids, max_new_tokens=32)

要从较小的预训练模型初始化影子主干网络,请将其 id 或路径作为 shadow_model 传入:

config = ShadowConfig(shadow_model="Qwen/Qwen3-0.6B", task_type="CAUSAL_LM")
model = get_peft_model(base_model, config)

评估影子路径

默认情况下,模型输出(logits)是影子适配基础模型:影子校正会注入到基础模型每一层的隐藏状态中,因此logits已经反映了 ShadowPEFT(使用model.disable_adapter()可获取纯基础模型以作对比)。辅助损失还会额外训练影子路径,使其能够独立解决该任务。

要评估 Transformers 语言模型的独立影子网络(即可分离的轻量级模型——merge_and_unload 的 ShadowPEFT 对应物),请使用 unload_shadow()。它返回 head(projection(backbone(x))) 作为一个常规任务模型,您可以像评估其他模型一样对其进行评估:对于因果语言建模任务,它是一个具备生成能力的因果语言模型(支持 generate() 和 KV 缓存);对于序列分类任务,它会池化最后一个 token 并返回类别 logits。对 Diffusers 模型调用此方法会引发 NotImplementedError。

shadow = model.base_model.unload_shadow()  # a DetachedShadowModel (a PreTrainedModel)
shadow.eval()
# causal LM:
out = shadow.generate(input_ids, max_new_tokens=32)
# sequence classification:
logits = shadow(input_ids=input_ids, attention_mask=attention_mask).logits  # (batch, num_labels)

默认情况下(copy=False),返回的模型与 PEFT 模型共享其模块,而一个共享了冻结基础输入嵌入的 shadow backbone 通过一个非子模块的引用访问它们。这对于评估没有问题,但意味着 save_pretrained 会写入一个不包含嵌入表的检查点。当你想要保存或推送独立模型时,请传入 copy=True:

shadow = model.base_model.unload_shadow(copy=True)
shadow.save_pretrained("standalone-shadow")

API

ShadowConfig

类 peft.ShadowConfig

< >

( task_type: Optional[Union[str, TaskType]] = Nonepeft_type: Optional[Union[str, PeftType]] = Noneauto_mapping: Optional[dict] = Nonepeft_version: Optional[str] = Nonebase_model_name_or_path: Optional[str] = Nonerevision: Optional[str] = Noneinference_mode: bool = Falsetarget_modules: list[str] | str | None = Noneexclude_modules: list[str] | str | None = Noner: int = 8shadow_alpha: float = 0.1shadow_dropout: float = 0.2init_weights: bool = Trueshadow_model: str = 'mirror'shadow_num_hidden_layers: int | None = Noneshadow_hidden_size: int | None = Noneshadow_num_attention_heads: int | None = Noneshadow_intermediate_size: int | None = Noneshare_embeddings: bool = Trueupdate_hidden_size: int | None = Noneauxiliary_loss_weight: float = 0.05layers_to_transform: list[int] | int | None = Nonelayers_pattern: list[str] | str | None = Nonemodules_to_save: list[str] | None = None )

参数

  • target_modules (Optional[Union[list[str], str]]) — 需要用 shadow 机制包裹的 transformer 块(指整个 decoder 块,而非线性层)。可以是模块名称列表,也可以是正则表达式,例如 r'.*\.layers\.\d+$' 用于匹配所有 decoder 块。默认为 None,此时会包裹基础模型中的所有 decoder 块。请注意,被包裹的块必须是连续的,因为 shadow 状态会从第一个被包裹的块开始,沿着 decoder 循环一直延续到最后一个被包裹的块。
  • exclude_modules (Optional[Union[list[str], str]]) — 不使用 shadow 机制进行包装的模块名称,以列表或正则表达式形式给出。默认为 None。
  • r (int) — 低秩注入瓶颈 W_down / W_up 的秩。默认值为 8。
  • shadow_alpha (float) — 注入到块输入上的校正强度(公式 4)。默认值为 0.1。
  • shadow_dropout (float) — 在瓶颈层之前应用于差异信号的 dropout 概率(公式 3)。默认值为 0.2。
  • init_weights (bool) — 是否将 W_up 初始化为零,以便在训练开始时注入操作不产生任何影响(类似于 LoRA 的 B=0 约定)。除非您清楚自己在做什么,否则请勿更改此设置。默认值为 True。
  • shadow_model (str) — 如何构建影子主干网络。"mirror"(默认值)会构建一个与基础模型架构相同但层数更少/更小的全新主干网络(参见下方的 shadow_* 覆盖项)。注册了 Shadow 后端的 Diffusers 架构会使用一种缩减的类似架构模型;其他兼容的扩散 Transformer 则回退到通用的逐 token MLP。任何其他字符串都被视为模型 ID 或本地路径。“投影”类型的 Transformers 影子检查点(model_type == "causal_lm_with_hidden_projection",例如 shadow-llm/Qwen3-0.6B-H8B)会加载其预训练主干网络以及训练好的影子隐藏层 -> 基础隐藏层投影。显式的 Diffusers 检查点需要注册的后端;目前支持 Flux2。
  • shadow_num_hidden_layers (Optional[int]) — 自动构建的("mirror")shadow backbone 的深度。对于 Flux2,这会创建指定数量的 double-stream blocks 以及两倍数量的 single-stream blocks;对于通用的 Diffusers 回退方案,它表示 residual MLP blocks 的数量。默认为 None(1 层)。
  • shadow_hidden_size (Optional[int]) — 自动构建的 shadow backbone 的隐藏层大小;可能与基础隐藏层大小不同(会自动插入一个投影层)。架构后端可能会施加额外的约束;Flux2 的缩减操作必须保留其基础注意力头维度。默认值为 None(与基础模型相同)。
  • shadow_num_attention_heads (Optional[int]) — 自动构建的 shadow backbone 的注意力头数量。默认为 None(与基础模型相同)。此选项由感知架构的后端使用,但通用的 Diffusers MLP 回退机制不使用。
  • shadow_intermediate_size (Optional[int]) — 自动构建的 shadow backbone 的前馈层宽度。默认值为 None(对于通用的 Diffusers MLP 回退方案,其值与隐藏层大小相同)。
  • share_embeddings (bool) — 是否复用冻结的基础输入嵌入来为影子主干网络提供输入(通过 inputs_embeds),而不是使用影子主干网络自身的嵌入表。默认值为 True。
  • update_hidden_size (Optional[int]) — T(候选)和 G(门控)更新 MLP 的隐藏层宽度。默认为 None(使用 r)。
  • auxiliary_loss_weight (float) — 当传入 labels 时,添加到任务损失中的辅助影子损失(公式 8-9)的权重 lambda。扩散训练循环可以使用相同的值作为分离的降噪损失。设置为 0 可将其禁用。默认值为 0.05。
  • layers_to_transform (Optional[Union[list[int], int]]) — 要转换的块索引。如果传入列表,则对位于这些索引处的块应用影子机制。如果传入单个整数,则仅在该索引处应用。默认为 None(转换所有匹配的块)。
  • layers_pattern (Optional[Union[list[str], str]]) — 层模式名称,仅在 layers_to_transform 不同于 None 时使用。这是保存解码器块的 nn.ModuleList 的名称(通常为 "layers" 或 "h")。默认为 None。
  • modules_to_save (Optional[list[str]]) — 需要设置为可训练并在最终检查点中保存的额外模块(例如 "lm_head" 或分类器头)。默认为 None。

ShadowModel(ShadowPEFT)的配置类。

ShadowPEFT 通过一个小型的可训练并行 shadow 网络来增强冻结的基础仅解码器模型。Shadow 主干网络生成初始 shadow 状态 s^(0),随后该状态跟随基础模型的解码器循环运行:在每个目标块中,基础隐藏状态与 shadow 状态之间的差异会被注入回块输入(公式 2-4),并且 shadow 状态通过来自块输出的门控残差更新进行推进(公式 5-7)。仅训练 shadow 组件;基础模型保持冻结。由于这种适配是层空间中依赖于输入的轨迹,而非静态的权重增量,因此 ShadowPEFT 无法合并到基础权重中。

ShadowModel

类 peft.ShadowModel

< >

( modelpeft_config: Union[PeftConfig, dict[str, PeftConfig]]adapter_name: strlow_cpu_mem_usage: bool = Falsestate_dict: Optional[dict[str, torch.Tensor]] = None )

从预训练的 transformers 模型创建一个 ShadowPEFT 模型。

ShadowPEFT 通过一个小型可训练的并行 shadow 网络来增强冻结的基础 decoder-only 模型。shadow 主干生成初始 shadow 状态,该状态随基础解码器循环运行;在每个目标块中,基础隐藏状态与 shadow 状态之间的差异会被注入到该块中,并且 shadow 状态通过门控残差更新进行推进。仅训练 shadow 组件。有关配置,请参阅 ShadowConfig。

该方法无法合并到基础权重中(该适配是一个依赖于输入的轨迹,而非静态的权重增量);请改用 ShadowModel.unload_shadow() 来获取独立的影子网络。

shadow_auxiliary_loss

< >

( labels: Tensorattention_mask: torch.Tensor | None = None )

影子路径自身的任务损失,CE(shadow_head(s^(0)), labels)(未加权;forward 会应用权重)。

损失是在初始影子状态 s^(0)(即投影后的影子主干输出)上计算的——这恰好是独立的 unload_shadow() 模型所计算的 head(projection(backbone(x)))。正是这一点使得分离出的影子网络可以独立使用;如果在 s^(L)(最终状态,它依赖于基础模型的逐层输出,且无法独立存在)上对其进行训练,分离出的模型将得不到训练。

unload_shadow

< >

( adapter_name: str | None = Nonecopy: bool = False )

参数

  • adapter_name (str, 可选) — 要卸载其影子网络的适配器。默认为当前激活的适配器。
  • copy (bool,可选,默认为 False) — 如果为 True,则对返回的模型进行深拷贝,使其独立于当前模型(会占用更多内存)。如果为 False(默认值),则共享模块——类似于 merge_and_unload,后者是复用模块而非克隆模块。修改其中一个模型会影响另一个模型。

返回影子骨干网络(+ 头部)作为独立模型,不包含基础模型。

merge_and_unload 的 ShadowPEFT 对应方法:前者会返回已将适配层融合进去的基础模型,而此方法仅返回轻量级的影子网络,以实现高效/边缘推理。它执行 head(projection(backbone(x))) —— 由于逐块更新需要基础模型的输出,因此无法独立存在。对于语言模型,其结果表现得像普通的因果语言模型(支持 generate() 和 KV 缓存)。不支持 Diffusers 模型,因为重建完整的独立去噪器因扩散架构而异。

将结果赋值给一个变量并使用它;使用 copy=False 时,这些模块仍与该模型共享。

如果您打算对独立模型执行 save_pretrained,请传入 copy=True。共享冻结基础输入嵌入的阴影骨干网络(即默认的 "mirror" 配置)通过一个不属于返回模型子模块的引用来访问这些嵌入,因此 copy=False 的检查点会缺失嵌入表;而 copy=True 会重新附加一个私有副本,从而保存完整的检查点。

ShadowCache

类 peft.tuners.shadow.ShadowCache

< >

( base: Any = Noneshadow: Any = None )

用于增量 ShadowPEFT 解码的成对 KV 缓存。

自回归生成需要一个基础模型缓存(在影子注入下计算的键/值)以及一个独立的影子模型缓存(用于逐 token 推进 s^(0))。这是一个 Transformers Cache 子类:影响批次或序列布局的操作(reset、reorder、crop、repeat 和 select)会同时应用于两个缓存,而注意力元数据和更新则委托给基础缓存。未知的缓存属性也会委托给基础缓存,以兼容特定架构的缓存实现。

该包装器被有意设计为不可编译,因为两个缓存可能具有不同的层布局和隐藏层大小。不支持旧版元组转换,因为单个旧版元组无法同时表示两条路径。影子部分在基础前向传播之前被解包,并在输出时重新打包——参见 ShadowModel 钩子。

Update on GitHub