适配器模块提供了一种直接且易懂的参数高效微调(PEFT)方法。它不是修改大型预训练语言模型(LLM)的现有权重,而是在模型架构内部引入少量新的、可训练的参数,同时保持原始LLM权重不变。此方法大幅减少了每个下游任务所需更新和存储的参数数量,解决了大型语言模型微调中常见的计算和存储难题。核心理念在于将小型神经网络模块(即适配器)注入到预训练transformer的层中。这些适配器通常采用瓶颈架构设计,以保持参数高效性。适配器架构一个标准的适配器模块由两个投影层和一个非线性层组成(非线性层夹在中间)。它接收transformer子层(如多头注意力或前馈网络)的输出 $h$ 作为输入。下投影: 线性层将高维输入 $h \in \mathbb{R}^d$ 投影到更小的维度 $m$,其中 $m \ll d$。这个投影由权重矩阵 $W_{down} \in \mathbb{R}^{d \times m}$ 表示。非线性处理: 逐元素应用非线性激活函数 $\sigma$ (例如,ReLU, GeLU)。上投影: 另一个线性层使用权重矩阵 $W_{up} \in \mathbb{R}^{m \times d}$ 将结果再次投影回原始维度 $d$。此层通常初始化为接近零,以确保适配器在微调开始时对预训练模型的输出影响最小(与残差连接结合时类似于恒等变换)。残差连接: 适配器模块的输出被加回到原始输入 $h$。数学上,适配器层应用的变换可以表示为:$$ h' = h + W_{up}(\sigma(h W_{down})) $$在微调过程中,只有适配器参数($W_{down}$、$W_{up}$ 及相关偏置)被训练,而原始LLM参数保持不变。瓶颈维度 $m$ 是一个重要的超参数。更小的 $m$ 意味着可训练参数更少,但可能会限制适配器获取特定任务信息的能力。反之,更大的 $m$ 会增加容量,但会降低参数效率。典型的 $m$ 值比 $d$ 小好几个数量级。例如,如果 $d=4096$, $m$ 可能会选择在 64 到 256 之间。digraph G { rankdir=LR; node [shape=box, style=filled, fillcolor="#e9ecef", fontname="sans-serif"]; edge [fontname="sans-serif"]; subgraph cluster_adapter { label = "适配器模块"; bgcolor="#f8f9fa"; style=dashed; node [fillcolor="#a5d8ff"]; down [label="下投影\n(d -> m)"]; nonlin [label="非线性处理\n(σ)"]; up [label="上投影\n(m -> d)"]; down -> nonlin -> up; } input_h [label="输入 (h)", shape=ellipse, fillcolor="#ced4da"]; output_h_prime [label="输出 (h')", shape=ellipse, fillcolor="#ced4da"]; add [label="+", shape=circle, fillcolor="#ffec99", width=0.5, height=0.5, fixedsize=true]; input_h -> down [lhead=cluster_adapter]; up -> add [ltail=cluster_adapter]; input_h -> add [label=" 残差连接"]; add -> output_h_prime; }显示带有残差连接的适配器模块典型瓶颈架构的图示。放置策略适配器在transformer架构中的插入位置对其效果有显著影响。早期方案尝试了多种放置方式,形成了一些固定模式:顺序适配器(Houlsby 等人,2019): 这种有影响力的设计将适配器顺序放置在每个transformer块内的多头注意力子层和前馈网络(FFN)子层之后。在适配器输入之前通常会增加一个额外的层归一化。这确保了适配器在模型的整个深度中被一致地应用。Pfeiffer 等人(2020)的变体: 为提高效率和潜在性能,此变体仅将适配器放置在FFN子层之后,但在注意力机制后的投影层之后保留了适配器。它通常在适配器周围包含一个特定的层归一化结构。并行适配器: 一些研究考虑将适配器与transformer子层并行放置,可能会以不同方式组合它们的输出。然而,顺序放置仍然更常见。放置方式的选择会影响信息流以及特定任务的适应性如何与预训练表示交互。将适配器放置在注意力和FFN之后,可以修改transformer块的两个核心计算单元的输出。digraph G { rankdir=TD; node [shape=box, style=rounded, fontname="sans-serif", fillcolor="#e9ecef"]; edge [fontname="sans-serif"]; subgraph cluster_block { label = "Transformer 块"; bgcolor="#f8f9fa"; style=dashed; MHA [label="多头注意力"]; AddNorm1 [label="加法与归一化"]; Adapter1 [label="适配器 (可选)", fillcolor="#a5d8ff", style=filled]; FFN [label="前馈网络"]; AddNorm2 [label="加法与归一化"]; Adapter2 [label="适配器", fillcolor="#a5d8ff", style=filled]; MHA -> AddNorm1; AddNorm1 -> Adapter1 [label="Houlsby 放置方式"]; Adapter1 -> FFN; AddNorm1 -> FFN [label="Pfeiffer 放置方式 (跳过 Adapter1)"]; FFN -> AddNorm2; AddNorm2 -> Adapter2; } Input -> MHA; Adapter2 -> Output; # Invisible edges for layout edge [style=invis]; AddNorm1 -> AddNorm2; MHA -> FFN; }简化视图,比较transformer块内潜在的适配器放置方式(Houlsby 对比 Pfeiffer)。输入/输出表示与前/后块的连接。设计考量和权衡其他因素影响适配器性能:初始化: 将 $W_{up}$ 初始化为接近零对于防止微调开始时预训练模型功能的干扰是很重要的。 $W_{down}$ 通常使用 Kaiming 或 Xavier 等标准方法进行初始化。参数效率与性能: 主要的权衡点在于可训练参数数量(效率)与模型在下游任务上的表现之间。大幅减小 $m$ 会显著减少参数,但如果适配器容量不足,可能导致欠拟合。通常需要通过经验评估来找到最优的 $m$ 值。推理延迟: 尽管适配器增加的参数相对较少,但它们为每个适配层引入了额外的计算步骤(两个线性层和一个非线性层)。这可能导致推理延迟相对于原始模型或LoRA等修改现有操作的方法有所增加。适配器在效率和有效性之间提供了不错的平衡。它们将特定任务的知识隔离到独立的模块中,通过切换适配器即可轻松地在任务之间切换,而不会影响基础LLM。这种模块化在多任务场景中是一个重要的优势。然而,推理延迟的潜在增加以及调整放置位置和瓶颈大小的需求是重要的考量因素。与完全微调相比,适配器显著降低了适应成本,同时在许多自然语言处理任务上通常能取得有竞争力的表现。