PEFT documentation
ShadowPEFT
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
< source >( 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
< source >( 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() 来获取独立的影子网络。
影子路径自身的任务损失,CE(shadow_head(s^(0)), labels)(未加权;forward 会应用权重)。
损失是在初始影子状态 s^(0)(即投影后的影子主干输出)上计算的——这恰好是独立的 unload_shadow() 模型所计算的 head(projection(backbone(x)))。正是这一点使得分离出的影子网络可以独立使用;如果在 s^(L)(最终状态,它依赖于基础模型的逐层输出,且无法独立存在)上对其进行训练,分离出的模型将得不到训练。
unload_shadow
< source >( adapter_name: str | None = Nonecopy: bool = False )
返回影子骨干网络(+ 头部)作为独立模型,不包含基础模型。
merge_and_unload 的 ShadowPEFT 对应方法:前者会返回已将适配层融合进去的基础模型,而此方法仅返回轻量级的影子网络,以实现高效/边缘推理。它执行 head(projection(backbone(x))) —— 由于逐块更新需要基础模型的输出,因此无法独立存在。对于语言模型,其结果表现得像普通的因果语言模型(支持 generate() 和 KV 缓存)。不支持 Diffusers 模型,因为重建完整的独立去噪器因扩散架构而异。
将结果赋值给一个变量并使用它;使用 copy=False 时,这些模块仍与该模型共享。
如果您打算对独立模型执行 save_pretrained,请传入 copy=True。共享冻结基础输入嵌入的阴影骨干网络(即默认的 "mirror" 配置)通过一个不属于返回模型子模块的引用来访问这些嵌入,因此 copy=False 的检查点会缺失嵌入表;而 copy=True 会重新附加一个私有副本,从而保存完整的检查点。
ShadowCache
用于增量 ShadowPEFT 解码的成对 KV 缓存。
自回归生成需要一个基础模型缓存(在影子注入下计算的键/值)以及一个独立的影子模型缓存(用于逐 token 推进 s^(0))。这是一个 Transformers Cache 子类:影响批次或序列布局的操作(reset、reorder、crop、repeat 和 select)会同时应用于两个缓存,而注意力元数据和更新则委托给基础缓存。未知的缓存属性也会委托给基础缓存,以兼容特定架构的缓存实现。
该包装器被有意设计为不可编译,因为两个缓存可能具有不同的层布局和隐藏层大小。不支持旧版元组转换,因为单个旧版元组无法同时表示两条路径。影子部分在基础前向传播之前被解包,并在输出时重新打包——参见 ShadowModel 钩子。