PEFT

介绍参数高效微调(PEFT)技术及其应用

什么是参数高效微调

  • 基本思想

    • 像 BERT、GPT-3 以及其他 Transformer 这样的现代大规模模型,通常拥有数亿甚至数十亿个参数
    • 针对每一个新任务都对这些模型进行完整微调,不仅计算开销巨大,而且内存占用也很高,尤其是在需要为多个任务分别维护模型副本的情况下
    • 参数高效微调(Parameter-Efficient Fine-Tuning,PEFT)方法正是为了解决这一问题而提出的
    • 其核心思想是:只调整模型中极小一部分参数,或者在模型中引入轻量级的附加模块,同时将绝大多数预训练权重保持冻结状态
    • 通过这种方式,可以在显著降低存储和计算成本的前提下,将预训练模型高效地适配到新的任务上
  • PEFT 的类型

    PEFT Methods

  • PEFT 可以大致分为三类

    • 引入额外参数(Additive-based):包括适配器(Adapters)、软提示(Soft prompts)、hard prompts
    • 选择部分参数(Selective-based):选择参数进行更新
    • 引入重参数(Reparametrization-based):LoRA(Low-rank Adaptation)系列

Additive-based Methods

基本思想

  • 基于额外参数的方法通过向预训练模型中引入额外的参数或层,并且只训练这些新加入的部分来实现微调

  • 这是 PEFT 方法中规模最大、研究也最为广泛的一类。

  • 在这一大类中,又发展出了两个重要的子方向:Adapter 类方法和 Soft Prompt(软提示)方法

  • Adapters

  • Soft Prompts

    • 语言模型提示旨在通过修改输入文本来控制语言模型的行为,输入通常由任务描述和少量上下文示例组成,然而,这类方法往往难以优化,并且受限于模型的最大输入长度
    • 为了解决这些问题,引入了 “软提示”(Soft prompts)的概念,其核心思想是:通过梯度下降直接微调模型输入嵌入的一部分,这样就把在离散空间中搜索提示的问题,转化为了一个连续优化问题
    • 软提示既可以只作用在输入层(Prompt Tuning),也可以扩展到模型的所有层(Prefix Tuning)
    • 近期的一些研究还探索了对软提示进行预训练或部分复用,以进一步降低微调成本
  • 基于额外参数的方法是一种非常多样化的参数高效微调技术,其范围不仅限于 Adapter 和 Soft Prompt

  • 为什么要增加参数

    • 尽管这些方法向网络中引入了额外参数,但它们依然能够在速度和内存占用上带来显著提升,原因在于,它们大幅减少了梯度和优化器状态的规模

    • 以 Adam 为例,对于每 1 字节的可训练参数,需要额外 1 字节来存储梯度,以及 2 字节来存储优化器状态(梯度的一阶矩和二阶矩),因此在实际训练中,模型训练所需的 GPU 内存通常是模型权重本身的 12–20 倍

    • 通过减少优化器状态和梯度的内存占用,并对冻结的模型参数进行量化,基于额外参数的方法使得微调更大规模的网络成为可能,或者允许使用更大的 microbatch size,从而提升 GPU 上的训练吞吐率。其中,batch size 的关系为

      Batch size=microbatch size×gradient accumulation×num. devices\text{Batch size} = \text{microbatch size} \times \text{gradient accumulation} \times \text{num. devices}
    • 此外,由于需要更新的参数数量更少,PEFT 方法中的优化器更新步骤相比全量微调要快得多,这一点在实际中非常明显

    • 同时,在分布式训练环境下,优化参数数量的减少还会显著降低通信量,这对大规模训练尤为重要

Adapters

Adapters

  • 基本思想

    • 全参数微调的参数量 O(∣θ∣)O (|\theta|),显存、通信、checkpoint 成本极高
    • 但是下游任务通常只需要改变模型的 “行为偏置”,而不是重塑整个表示空间
    • Adapter 的核心思想是冻结原模型参数,只引入一小组可训练参数,用极低成本实现任务适配
  • 模型结构

    • Adapter 是插入在 Transformer 层中的一个小瓶颈网络(bottleneck module)

      Adapter(x)=x+Wupσ(Wdownx)\mathrm {Adapter}(x) = x + W_{\text{up}} \sigma (W_{\text{down}} x )
      • x∈Rhx \in \mathbb {R}^h:Transformer 层的隐藏状态
      • Wdown∈Rr×hW_{\text{down}} \in \mathbb {R}^{r \times h}、Wup∈Rh×rW_{\text{up}} \in \mathbb {R}^{h \times r}
      • r≪hr \ll h:瓶颈维度
      • σ\sigma:非线性激活函数(ReLU / GELU)
    • 每个 Adapter 模块中的第一个全连接层会将输入投影到一个低维表示空间中;第二个全连接层再将该低维表示投影回原始的输入维度

    • 这样,低秩瓶颈限制了 Adapter 的表达能力,而残差连接保证初始状态等价于原模型,不会破坏预训练分布

  • 参数量计算

    • hidden size = hh、bottleneck = rr、Transformer 层数 = LL

    • 每层 Adapter 参数量:

      ∣Wdown∣+∣Wup∣=hr+rh=2hr|W_{\text{down}}| + |W_{\text{up}}| = hr + rh = 2hr
    • 全模型 Adapter 参数量:

      2hrL2hrL
    • 全参数微调参数量:

      O(h2L)O (h^2 L)
    • 比例:

    AdapterFull FT≈rh\frac {\text{Adapter}}{\text{Full FT}} \approx \frac {r}{h}
    • 当 h=4096h = 4096、r=16r = 16 时,参数比例 ≈0.4%\approx 0.4\%,参数规模显著更小
  • 根据最初的 Adapter 论文结果,使用 Adapter 方法训练的 BERT 模型,在仅训练 3.6% 参数的情况下,就可以达到与全参数微调的 BERT 模型相当的性能

  • 同时,可以针对不同的任务使用不同的 Adapter,主干则是共享的

  • 伪代码

    def transformer_block_with_adapter(x):
        residual = x
        x = self_attention(x)
        x = AdapterLayers (x) # adapter
        x = LayerNorm(x + residual)
        residual = x
        x = FullyConnectedLayers (x)
        x = AdapterLayers (x) # adapter
        x = LayerNorm(x + residual)
        return x
  • Pfeiffer 发现

    • 如果只在每个 Transformer block 中的 self-attention 之后(LayerNorm 之后)插入一个 adapter,其性能几乎与在同一个 block 中插入两个 adapter(通常是 attention 后一个、MLP 后一个)相当

    • attention 后的 hidden state 已经包含了任务相关的结构性信息,token 之间的依赖模式,在 attention 之后插入 adapter 收益较大

    • 在 MLP 后加一个 adapter,能学到的新自由度其实高度相关,信息增益很小,但参数和计算翻倍

    • LayerNorm 之后的表示具有数值尺度稳定、各层分布一致、更接近 “模型的自然表示空间” 的性质,从而使得 adapter 接收的是一个 “干净、稳定、语义明确” 的表示,这样 adapter 的低秩更新更容易学习,不容易破坏主干网络,这也是为什么 Adapter、LoRA、Prefix / Prompt(在隐空间)几乎都偏好 LN 后或 residual 分支中插入

Prompt-Tuning

  • 参考论文:The Power of Scale for Parameter-Efficient Prompt Tuning

  • Soft / Hard Prompt Tuning

    • Hard Prompt Tuning 指使用真实的离散 token,作为 prompt 拼接到输入中,prompt 本身是自然语言或符号,不可微、不可直接反向传播 —— 也就是一般提到的提示工程
    • Soft Prompt Tuning 也即一般的 Prompt / Prefix Tuning,这时 prompt 不再是离散 token,而是可训练的连续向量,直接作为 embedding 使用
  • 核心问题:全参数微调代价太高,而 Adapter 仍然需要改动网络内部结构或权重,是否能只通过 “输入” 来控制模型行为?

  • Prompt / Prefix Tuning 的统一思想:冻结整个 Transformer,仅学习一小段连续向量,通过影响注意力分布来 “操控” 模型

  • 本质上:不改模型,不加模块,只改模型看到的上下文表示

  • Prompt Tuning 学习的是一段连续向量 prompt,直接拼接到输入 embedding 前面

    • 设原始输入 token embedding:

      X=[x1,x2,…,xT],xi∈RhX = [x_1, x_2, \dots, x_T], \quad x_i \in \mathbb {R}^h
    • 可训练 prompt:

      P=[p1,p2,…,pm],pi∈RhP = [p_1, p_2, \dots, p_m], \quad p_i \in \mathbb {R}^h
    • 模型实际输入为:

      [P;X][P ; X]
    • 其中 PP 即为学习的参数,而 XX​ 是数据,Transformer 权重完全冻结

  • Prompt Tuning 的参数量:

    m⋅hm \cdot h
    • 当 m=20∼100m = 20 \sim 100、h=4096h = 4096 时,参数量 ∼105\sim 10^5,相比 Adapter / LoRA 更小
  • 训练时,梯度只更新 prompt embedding,非常稳定

  • 推理时,prompt 作为额外 token,序列长度增加 mm,注意力计算成本略增

  • 本质上,Prompt Tuning 是在调整模型的 “条件分布起点”,改变输出风格、任务偏好

Prefix-tuning

Prefix-tuning

  • Prompt Tuning 的核心限制在于 prompt 只在 embedding 层起作用,深层 Transformer 只能 “间接感知”

  • Prefix Tuning 的关键改进:在每一层注意力中都插入可学习前缀

  • 形式化定义

    • Transformer 第 ll 层注意力:

      Attn(Ql,Kl,Vl)\mathrm {Attn}(Q_l, K_l, V_l)
    • Prefix Tuning 学习的是:

      PlK∈Rm×h,PlV∈Rm×hP_l^K \in \mathbb {R}^{m \times h}, \quad P_l^V \in \mathbb {R}^{m \times h}
    • 实际注意力计算变为:

      Attn(Ql,[K(PlK);K(X)],[V(PlV);V(X)])\mathrm {Attn} \Big ( Q_l, [K (P_l^K); K (X)], [V (P_l^V); V (X)] \Big)
    • Prefix 不参与 Query,只影响 Key / Value,且在每一层都生效

      img

  • 参数规模

    • 若每层都有 prefix,参数量为

      2⋅m⋅h⋅L2 \cdot m \cdot h \cdot L
    • 其中,22 来自 KK 和 VV,LL 是 Transformer 层数

    • 为了压缩参数,实际常共享低维 prefix,使用 MLP 投影到各层

  • 训练时,梯度流入每一层,比 Prompt Tuning 更强

  • 推理时,prefix 不增加序列长度,但会增加注意力 Key / Value 缓存

  • Prefix Tuning 实际上是在学习每一层的注意力偏置,这意味着能影响深层语义,并模拟一部分 Adapter 的效果,但仍然不改 FFN / 权重

  • 伪代码

    def transformer_block_with_prefix(x, soft_prompt):
        soft_prompt = FullyConnectedLayers(soft_prompt)		# Prefix
        x = concatenate([soft_prompt,x],dim=seq_len)    	# Prefix
        residual = x
        x = self_attention(x)
        x = LayerNorm(x + residual)
        residual = x
        x = FullyConnectedLayers(x)
        x = LayerNorm(x + residual)
        return x

Adaptive Prefix Tuning

  • 参考论文:Towards Adaptive Prefix Tuning for Parameter-Efficient Language Model Fine-tuning

  • 出发点

    • 普通 Prefix Tuning 的隐含假设是 prefix 是静态的,与输入 XX 无关,对同一任务的所有样本使用同一组 PlK,PlVP_l^K, P_l^V
    • 这带来的问题是表达能力受限,无法针对不同输入动态调整注意力偏置,在复杂任务(推理、多域、多风格)上效果不稳定
    • 实际上,静态 prefix ≈ 固定注意力 bias,但多样性要求输入不同,prefix 也不同,于是引出 Adaptive Prefix Tuning
  • 基本思想

    • Prefix 不再是参数本身,而是由输入条件生成的函数输出,即:
    PlK,PlV=gϕ(X)P_l^K, P_l^V = g_\phi (X)
    • 其中,gϕg_\phi 是一个小型可训练网络,ϕ\phi​ 是唯一需要更新的参数,而 Transformer 主模型依然保持完全冻结

    • 对于标准 Prefix Tuning,第 ll 层注意力:

      Attn(Ql(X),[K(PlK);K(X)],[V(PlV);V(X)])\mathrm {Attn} \big (Q_l (X), [K (P_l^K); K (X)], [V (P_l^V); V (X)] \big)
    • 其中,PlK,PlVP_l^K, P_l^V 是直接可训练参数,与输入无关

    • Adaptive Prefix Tuning 引入条件生成器 gϕg_\phi:

      (PlK,PlV)=gϕ(h(X))(P_l^K, P_l^V) = g_\phi (h (X))
    • 其中 h(X)h (X) 通常是输入 embedding 的池化或某一层的 CLS /mean 表示

    • 这样,注意力变为:

      Attn(Ql(X),[K(gϕK(h(X)));K(X)],[V(gϕV(h(X)));V(X)])\mathrm {Attn} \Big (Q_l (X), [K (g_\phi^K (h (X))); K (X)], [V (g_\phi^V (h (X))); V (X)] \Big)
  • 参数规模

    • 假设 prefix 长度 mm、hidden size hh、条件向量维度 dd、层数 LL

    • 普通 Prefix:2⋅m⋅h⋅L2 \cdot m \cdot h \cdot L

    • Adaptive Prefix(共享 MLP):d⋅h+2⋅m⋅hd \cdot h + 2 \cdot m \cdot h

    • Adaptive Prefix(每层独立):L⋅(d⋅h+2⋅m⋅h)L \cdot (d \cdot h + 2 \cdot m \cdot h)

    • 参数量略高于静态 Prefix,但远低于 Adapter / LoRA

  • 根本区别

    • 标准 Prefix Tuning 学习的是 EX[attention bias]\mathbb {E}_X [\text{attention bias}]
    • Adaptive Prefix Tuning 学习的是 attention bias(X)\text{attention bias}(X)​
  • 伪代码

    def adaptive_adjustment(x, layer_index):
        alpha = sigmoid(W[layer_index, ...] @ x)
        adaptive_factor = lambda_[layer_index] * alpha
        return adaptive_factor
    
    
    def prefix_tuning(x, layer_index):
        # adaptive gating and scaling
        adaptive_factor = adaptive_adjustment(x, layer_index)
    
        # soft prompt
        soft_prompt = FFN(soft_prompt)
        soft_prompt *= adaptive_factor
    
        # regular prefix-tuning
        x = concat([soft_prompt, x], dim=seq)
        return transformer_block(x)

Intrinsic Prompt Tuning (IPT)

  • 参考论文:Exploring Universal Intrinsic Task Subspace via Prompt Tuning

  • 出发点

    • IPT 针对的是 Soft Prompt Tuning 的两个核心痛点:Prompt tuning 在新任务上收敛慢,每个任务都要训练一整段 prompt,参数量仍然不小(m×hm \times h​)
    • IPT 的核心假设是:虽然 soft prompt 定义在高维空间 Rm×h\mathbb {R}^{m \times h},但不同任务的 prompt 实际上位于一个低维的 “内在任务子空间” 中,即原始 prompt:P∈Rm×hP \in \mathbb {R}^{m \times h},展平后:p∈Rmhp \in \mathbb {R}^{m h},存在低维表示 z∈Rdz \in \mathbb {R}^d,其中 d≪mhd \ll m h
    • 这与 Intrinsic Dimension Hypothesis、Lottery Ticket / Low-rank adaptation 在思想上是一致的
  • 算法流程

    • 阶段一:标准 Prompt Tuning(多任务)
      • 给定一组任务 T1,…,TK{T_1, \dots, T_K}​,对每个任务,独立训练一个 soft prompt
    • 阶段二:学习 prompt 的低维表示(Autoencoder)
      • 将每个 prompt 展平:pi∈Rmhp_i \in \mathbb {R}^{m h},输入自编码器
      • Encoder 学到 “任务内在坐标”,Decoder 学到 “从子空间到 prompt 的映射”
    • 阶段三:新任务的 IPT 训练
      • 对一个新任务,丢弃 Encoder,冻结 Decoder(或微调)
      • 只训练一个低维向量 z∈Rdz \in \mathbb {R}^d​
      • 每个新任务只需要训练 dd 个参数,而不是 m×hm \times h
  • 参数规模对比 ——Prompt Tuning:m×hm \times h,IPT:dd,其中 d≪mhd \ll m h

  • 伪代码

    def autoencoder(soft_prompt):
        soft_prompt = soft_prompt.flatten()
        P = FFN_A(soft_prompt)  # encoder
        P = FFN_B(P)            # decoder
        P = P.reshape(prompt_len, hidden)
        return P
    
    def ipt_model(x):
        P = FFN_B(intrinsic_subspace_prompt)
        P = P.reshape(prompt_len, hidden)
        x = concat([P, x], dim=seq)
        return model(x)

P-Tuning

Selection-based Methods

基本思想

  • 最早的选择式 PEFT 方法之一是只微调网络顶部的少数几层
  • 现代方法通常基于层的类型,或者基于模型的内部结构来进行选择,例如只微调模型中的偏置参数,或者仅微调特定的参数行
  • 稀疏训练是选择式 PEFT 的另一种形式,它往往不显式考虑模型结构,而是根据精心设计的选择准则来决定哪些参数参与微调
  • 然而,稀疏参数更新在工程实现和计算效率方面面临诸多挑战
  • 近年来的一些研究在参数重配置(Parameter Reconfiguration)以及 NxM 稀疏性方面对这些问题进行了部分缓解
  • 但即便如此,在当前主流硬件上,完全不受约束的非结构化稀疏仍然不具备实用性

BitFit (Bias Fine-Tuning)

Sparse Fine-Tuning

Reparametrization-based Methods

基本思想

  • 基于重参数化的 PEFT 方法通过利用低秩表示来最小化可训练参数的数量

  • 神经网络具有低维表示这一观点,已经在深度学习的经验研究和理论分析中被广泛探讨

  • Aghajanyan et al. (2020) 证明了在低秩子空间中同样可以有效地进行微调,同时发现对于规模更大的模型,或者预训练时间更长的模型,所需进行适配的子空间维度反而更小

  • 他们提出的方法被称为 Intrinsic SAID,该方法利用 Fastfood 变换对神经网络参数的更新进行重参数化

  • 这一思想启发了多种后续方法,包括 IntrinsicSAID、LoRA 以及 KronA

  • 在这些方法中,最为人熟知的是 Low-Rank Adaptation(LoRA),其采用一种简单的低秩矩阵分解来参数化权重更新:

    δW=WdownWup\delta W = W_{\text{down}} W_{\text{up}}
  • 这种方法实现起来非常直接,并且已经在参数规模高达 1750 亿的模型上进行了验证

  • 此外,一些工作还探索了使用 Kronecker 积进行重参数化:

    δW=A⊗B\delta W = A \otimes B
  • 这种方式在秩大小与参数数量之间提供了更优的权衡

Intrinsic SAID

  • 参考论文:Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning

  • Intrinsic SAID 研究的是一个非常根本的问题

    • 微调一个大模型时,真正 “有用” 的参数自由度到底有多少?

    • 换句话说,虽然模型参数维度是 DD(动辄 10910^9),但是否只需要在一个远小于 DD 的子空间中更新,就能达到同样效果?

    • 这就是 Intrinsic Dimensionality(内在维度) 问题

  • Aghajanyan 等人的研究发现

    • 为了达到同样的微调性能,模型越大,所需的内在可调维度越低

    • 即原始参数为 θ0∈RD\theta_0 \in \mathbb {R}^D,而实际需要优化的自由度:d≪Dd \ll D

    • 这为两个方向提供了理论基础:

      • 模型越大,越适合参数高效微调

      • 不必显式地修改少量参数(如 Adapter),也可以低维控制 “全模型更新”

  • Intrinsic SAID 的核心技术是 Fastfood Transform,它定义了一个映射:

    F:Rd→RDF : \mathbb {R}^d \rightarrow \mathbb {R}^D
    • 并用它来表示参数更新:

      θ=θ0+F(θd)\theta = \theta_0 + F (\theta_d)
    • θd∈Rd\theta_d \in \mathbb {R}^d 是唯一需要训练的参数

    • F(θd)F (\theta_d) 是一个投影到全参数空间的 “低秩扰动”

    • Fastfood 是一种随机但结构化、计算高效、不需要显式存储 D×dD \times d 矩阵的高维扩展方法

    • 它的关键性质是计算复杂度:O(Dlog⁡d)O (D \log d),内存复杂度:O(D)O (D),可以近似一个随机高斯投影

    • 直觉上,θd\theta_d​ 是 “控制旋钮”,而 Fastfood 把它 “扩散” 成作用于所有参数的更新方向

  • Intrinsic SAID 与 “选择性微调” 的根本区别

    • 其他 PEFT 方法(Adapter / LoRA / Prompt):只更新模型的一小部分参数,其余参数完全冻结

    • Intrinsic SAID:更新所有模型参数,但更新方向被限制在一个低维子空间中

    • 也就是说,参数空间是 DD 维,但可达更新空间是 dd 维

  • Intrinsic SAID 在工程上几乎不可用

    • Fastfood 本身需要 O(D)O (D) 内存,虽然不训练 DD 个参数,但 Fastfood 需要存储结构化映射,并在每一步生成 DD 维更新
    • 每次更新都要对所有参数生效,无法像 LoRA / Adapter 那样局部注入
    • Intrinsic SAID 不适合 ZeRO / FSDP,不适合参数分片,不适合增量 checkpoint

LoRA

LoRA

  • 基本思想

    • 设一个大模型包含一个权重矩阵 W∈Rin×outW \in \mathbb {R}^{\text{in} \times \text{out}}

    • 例如总参数规模是 7B,在标准微调中,反向传播会学习一个与 WW​ 同形状的更新量:

      ΔW∈Rin×outWupdated=W+ΔW\Delta W \in \mathbb {R}^{\text{in} \times \text{out}}\\ W_{\text{updated}} = W + \Delta W
    • 问题在于,ΔW\Delta W 和 WW 一样大,需要存梯度、optimizer states(Adam: 2×FP32),显存和带宽成本与模型规模线性增长,这在 7B、70B 甚至 175B 模型上是不可承受的

    • LoRA(Hu et al., 2022)基于一个关键观察:虽然 WW 是高维的,但任务相关的权重更新 ΔW\Delta W 通常位于一个低秩子空间

    • 因此,LoRA 不直接学习 ΔW\Delta W​,而是令:

      ΔW=WAWBWA∈Rin×r,WB∈Rr×out,r≪min⁡(in,out)\Delta W = W_A W_B\\ W_A \in \mathbb {R}^{\text{in} \times r}, \quad W_B \in \mathbb {R}^{r \times \text{out}}, \quad r \ll \min (\text{in}, \text{out})
    • 这就是 LoRA 的低秩分解

  • 参数量节省

    • 原始微调需要优化的参数量:∣ΔW∣=in×out|\Delta W| = \text{in} \times \text{out}
    • LoRA 需要优化的参数量:∣WA∣+∣WB∣=r(in+out)|W_A| + |W_B| = r (\text{in} + \text{out})
    • 当 rr 很小时(如 4、8、16),参数量下降 10–100×,梯度、optimizer states 同比例下降,显存占用显著减少
  • LoRA 的关键工程优势在于:

    • 训练时从不构造 ΔW\Delta W,只在前向中引入一个额外的低秩路径
    • 前向计算为 h=xW+α⋅xWAWBh = xW + \alpha \cdot x W_A W_B,其中缩放因子通常是 α=1r\alpha = \frac {1}{r}
  • 训练阶段:冻结 WW,只训练 WA,WBW_A, W_B,梯度只回传到低秩矩阵

  • 推理 / 合并阶段:可以显式合并权重 W′=W+αWAWBW' = W + \alpha W_A W_B​,推理时无额外计算开销,与原模型结构完全一致

  • LoRA 的思想直接继承自 Intrinsic SAID:

    • Intrinsic SAID:更新在低维子空间中,低维变量 → Fastfood → 更新所有参数(不实用)

    • LoRA:用显式低秩矩阵来参数化这个子空间,低维变量 → 低秩矩阵 → 局部权重(工程可行)

    • LoRA 是 Intrinsic SAID 的 “工程化版本”

  • LoRA 应用到哪些部分

    • 最常见的位置是多头注意力中的 WQW_Q、WKW_K、WVW_V、WOW_O
    • 原因是注意力对任务适配高度敏感,权重矩阵形状规则、适合低秩分解
    • 但后续研究表明对所有线性层应用 LoRA,效果最好,包括 MLP 的 up /down/gate 投影
  • 输出计算方法为:

h=Wx+αrBAxh = Wx + \frac {\alpha}{r} BAx
  • 其中,WW 表示预训练权重(旧知识),αrBA\frac {\alpha}{r} BA 表示增量权重 ΔW\Delta W 的近似(新知识)

  • 理论上说,当 rr 较小时,提取的是 ΔW\Delta W 中信息含量最丰富的维度,此时信息精炼,但不全面

  • 当 rr 较大时,我们的低秩近似越逼近 ΔW\Delta W,此时信息更加全面,但带来的噪声也越多(含有很多冗余无效的信息)

  • 基于这个猜想,当第一次做实验时,尽量把 rr 调得大些,例如 32、64,并假设在这个秩下,低秩权重已经非常近似 ΔW\Delta W 了,因此这时设置 α=r\alpha = r,意味着我们假定 LoRA 低秩微调的效果和全参数微调持平

  • 那么接下来,往小的 rr 进行尝试,这时把 α\alpha 固定住,意味着随着 rr 的减小,αr\frac {\alpha}{r} 会越来越大,这样做的原因是:

    • 当 rr 越小时,低秩矩阵表示的信息精炼,但不全面
    • 我们通过调大 αr\frac {\alpha}{r},来放大 forward 过程中新知识对模型的影响
    • 当 rr 越小时,低秩矩阵表示的信息精炼,噪声 / 冗余信息少,此时梯度下降的方向也更加确信,所以可以通过调大 αr\frac {\alpha}{r},适当增加梯度下降的步伐,也就相当于调整 learning rate 了
  • 伪代码

    def lora_linear(x):
        h = x @ W          # frozen base weight
        dh = x @ W_A @ W_B # low-rank update
        h += scale * dh    # scaling
        return h

KronA

image-20260121172103254

  • LoRA 的局限

    • LoRA 用的是低秩矩阵乘法来参数化权重更新:

      δW=WAWB\delta W = W_A W_B
      • 其中 WA∈Rin×rW_A \in \mathbb {R}^{\text{in} \times r}、WB∈Rr×outW_B \in \mathbb {R}^{r \times \text{out}}
    • 这在工程上非常高效,但有一个结构性限制:rank⁡(δW)≤r\operatorname {rank}(\delta W) \le r

    • 也就是说,参数量和可表达的秩是线性绑定的,想要更高表达能力,就必须增大 rr,参数量随之线性增加

    • KronA 的核心想法是:能不能在相同参数预算下,得到更高 “有效秩” 的更新?

  • KronA 的核心思想:用 Kronecker 积代替矩阵乘法

    • KronA 不再使用矩阵乘法,而是改为

      δW=WA⊗WB\delta W = W_A \otimes W_B
    • 其中 WA∈Rn×mW_A \in \mathbb {R}^{n \times m}、WB∈Rk×lW_B \in \mathbb {R}^{k \times l}、δW∈R(nk)×(ml)\delta W \in \mathbb {R}^{(n k) \times (m l)}

    • 关键性质是 Kronecker 积的秩:

      rank⁡(A⊗B)=rank⁡(A)⋅rank⁡(B)\operatorname {rank}(A \otimes B)=\operatorname {rank}(A) \cdot \operatorname {rank}(B)
    • 这意味着如果 AA 和 BB 都是低秩的,它们的 Kronecker 积可以拥有显著更高的秩

  • 为什么 Kronecker 积更划算

    • 假设 LoRA:参数量 ∼r(in+out)\sim r (\text{in} + \text{out}),秩 ≤r\le r,KronA:参数量 ∼nm+kl\sim nm + kl,秩 ≤rank⁡(A)rank⁡(B)\le \operatorname {rank}(A)\operatorname {rank}(B)
    • 在合理拆分矩阵形状的情况下,KronA 用相同甚至更少参数,可以表达更复杂的线性变换结构,尤其适合具有二维或块结构的权重矩阵
    • 直觉上,LoRA 是 “一条低维通道”,而 KronA 是 “两个低维结构的张量化组合”
  • 和 LoRA 一样,KronA 也不会显式构造 Kronecker 积,而是直接计算:

    x(WA⊗WB)x (W_A \otimes W_B)
    • 利用 Kronecker 积的恒等式:

      vec⁡(AXB)=(B⊤⊗A)vec⁡(X)\operatorname {vec}(AXB)=(B^\top \otimes A)\operatorname {vec}(X)
    • KronA 的实现思路是先把向量 xx reshape 成矩阵,然后左乘 A⊤A^\top,再右乘 BB​,最后 flatten 回向量

  • 在 GLUE 上,参数预算约为 0.07%0.07\% 时:

    • 性能:KronA ≥ LoRA ≈ Adapter ≈ Compacter
    • 推理速度:KronA 显著快于 Adapter 和 Compacter
    • 训练参数量:相同
    • 其优势主要来自更高表达秩、无中间层、纯线性算子
  • KronA-resB—— 带残差的并行 Adapter

  • 伪代码

    # same as x @ kronecker_product(A, B)
    def kronecker_vector_prod(x, A, B):
        x = x.reshape(A.shape[1], B.shape[1])
        x = A.T @ x @ B
        return x.reshape(-1)
    
    def krona_linear(x):
        x = x @ W                      # frozen base weight
        x += kronecker_vector_prod(x, W_A, W_B)
        return scale * x
  • Kronecker 积定义为:

    A⊗B=[a1,1B⋯a1,nB⋮⋱⋮am,1B⋯am,nB]A \otimes B =\begin {bmatrix} a_{1,1} B & \cdots & a_{1,n} B \\ \vdots & \ddots & \vdots \\ a_{m,1} B & \cdots & a_{m,n} B \end {bmatrix}
    • 其本质是用 AA 的每个元素去缩放整个 BB,构造一个具有强结构性的 “大矩阵”

    • PyTorch 中可用 einsum 实现 batch 版本:

      def batched_kronecker_product(a, b):
          bs, i, j = a.shape
          bs, k, m = b.shape
          res = einsum("bij,bkm->bikjm", a, b)
          return res.view(bs, i - k, j - m)

DoRA

Refer to caption

  • 方向变化与模长变化的关系

    • 设某一层权重矩阵为 WW,比较微调前后:

      • 方向变化:ΔD\Delta D
      • 模长变化:ΔM\Delta M
    • DoRA 对全量微调的多个 checkpoint 和多层权重做了统计,发现:

      • full fine-tuning 中,ΔD\Delta D 与 ΔM\Delta M 呈显著负相关
      • 要改变方向很多,模长变化往往很小
      • 模长变化很大时,方向反而变化较小
    • 但在 LoRA 微调中,现象恰好相反:

      • ΔD\Delta D 与 ΔM\Delta M 正相关
      • 方向变化越大,模长也被迫一起变大
    • 这说明 LoRA 在结构上把 “方向” 和 “模长” 耦合在了一起

    • 这是由于在 LoRA 的权重更新中,WAWBW_A W_B 同时决定了权重方向和权重整体模长,优化器无法 “只调方向、不动模长” 或 “只调模长、不改方向”

  • DoRA 借鉴了 weight normalization 的思想,将权重拆成:

    W=m⋅v,∣v∣2=1W = m \cdot v,\quad |v|_2 = 1
    • 其中 vv 表示方向(direction),mm​ 表示模长(magnitude,可学习标量)
    • 方向由 LoRA-style 的低秩更新负责,模长由一个独立的可学习参数 mm​ 负责
    • 这种重参数化带来的直接后果是改变 WA,WBW_A, W_B 主要影响 vv(方向),改变 mm​ 只影响整体尺度
    • 因此模型可以用很小的模长变化实现大的方向旋转,或保持方向稳定,仅缩放权重强度
    • 这在理论上恢复了 full fine-tuning 中观察到的现象
  • DoRA 在低秩设置下提升尤其明显

    • 在 rank 很小(如 4, 8)时 LoRA 的表达能力严重受限,每一次方向改变都会 “浪费” 参数去同时调整模长
    • 而 DoRA 把有限的低秩参数全部用于 “方向调整”,把 “模长自由度” 单独交给 mm
    • 结果是在极低 rank 下,DoRA 的有效表达能力显著高于 LoRA,指令微调、few-shot 场景中 sample efficiency 更高
  • 伪代码

    def initialize_m(W):
        m = Parameter(l2_norm(W), requires_grad=True)
        return m
    
    def dora_linear(x):
        W_cur = W + (W_A @ W_B)
        v = W_cur / l2_norm(W_cur)
        W_cur = m * v
        return x @ W_cur + b
    

GLoRA(Generalized-LoRA)

GLoRA

  • 标准 LoRA 只通过 “加法” 改变权重,不改变输入、激活或偏置的缩放结构,表达能力受限于低秩与仅加性更新,这在复杂任务(分布变化大、非线性强)上可能不够

  • GLoRA 的假设是 LoRA 只改变 “权重的加性部分” 还不够,更强的适配需要权重缩放、激活缩放、偏置修正,但仍希望训练时参数可学习推理时不引入额外结构或计算,因此提出了 Generalized-LoRA(GLoRA)

  • GLoRA 对线性层的更新为:

    f(x)=(W0+W0A+B)x+CW0+Db0+E+b0f (x) = (W_0 + W_0 A + B) x + C W_0 + D b_0 + E + b_0
    • 冻结参数 W0W_0:预训练权重、b0b_0:预训练偏置

    • 可训练参数:

      • AA:权重缩放(右乘),相当于对原始权重进行任务相关的 re-scaling,能表达通道级、子空间级的适配
      • BB:LoRA-style 权重增量,等价于 LoRA 的 ΔWx\Delta W x,负责主要的方向性变化
      • CC:输出平移(activation shift),类似一个 data-independent 的残差,用于任务级 bias /prior 注入
      • DD:偏置缩放
      • EE​:偏置平移
    • 其中 A、B、C 矩阵都可以进行低秩分解来减少参数量;A、C、D、E 可以退化为对角矩阵 / 向量 / 标量,从而严格控制参数预算

  • 模型合并,所有可学习参数在训练后都可以合并进:

    • 新权重:

      W′=W0+W0A+BW' = W_0 + W_0 A + B
    • 新偏置:

      b′=b0+CW0+Db0+Eb' = b_0 + C W_0 + D b_0 + E
  • 伪代码

    def glora_linear(x):
        # frozen parameters
        # W_0: pre-trained weight
        # b_0: pre-trained bias (optional)
    
        # learnable GLoRA parameters
        # A: weight scaling
        # B: LoRA-style additive update
        # C: activation shift
        # D: bias scaling
        # E: bias shift
    
        # effective weight
        W_cur = W_0 + W_0 @ A + B
    
        # main linear transformation
        h = x @ W_cur + C @ W_0
    
        # bias adjustment (optional)
        if bias_present:
            h = h + D * b_0 + E + b_0
    
        return h

AdaLoRA

  • 参考论文:AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning

  • 出发点

    • 标准 LoRA 有一个关键超参数:秩 rr,问题在于:
      • rr 在训练前固定,但是不同层、不同任务对 rank 的需求差异极大
      • 统一使用相同 rr 会导致:有些层 rank 过高,浪费参数,而有些层 rank 不够,限制性能
    • AdaLoRA 的核心目标是在给定参数预算下,自动把 rank 分配到 “更重要” 的层和方向上,训练过程中动态降低不重要的 rank
  • AdaLoRA 将 LoRA 的更新写成一种 SVD-like 形式:

    W=W0+WAΛWBW = W_0 + W_A \Lambda W_B
    • 其中 WA∈Rdin×rW_A \in \mathbb {R}^{d_{\text{in}} \times r}、WB∈Rr×doutW_B \in \mathbb {R}^{r \times d_{\text{out}}}、Λ∈Rr×r\Lambda \in \mathbb {R}^{r \times r} 是对角矩阵

    • 可以把它类比为:

      ΔW≈∑i=1rλiuivi⊤\Delta W \approx \sum_{i=1}^r \lambda_i u_i v_i^\top
    • 每一维 rank 都有一个独立的重要性系数 λi\lambda_i,而 rank 是否 “有用”,由 λi\lambda_i 决定

  • AdaLoRA 的核心思想是剪掉不重要的奇异值

    • 并非所有 rank 维度对 loss 同样重要,如果某个 λi\lambda_i 对 loss 影响很小,就可以把这一 rank 删除

    • 因此 AdaLoRA 在训练中不断计算每个 rank 维度的重要性分数,剪掉分数最低的 rank,逐步降低有效秩

    • AdaLoRA 定义 importance score 来衡量当前 rank 维度对 loss 的影响程度

      • 分数依赖于参数本身 WA,WB,λW_A, W_B, \lambda 和梯度信息 dWA,dWB,dλdW_A, dW_B, d\lambda
      • 形式上是一个加权组合
      • 为了避免抖动,AdaLoRA 使用指数滑动平均(EMA)平滑 importance score
    • Rank 剪枝(pruning)过程

      • 给定当前 rank rr 和 目标剪枝数 kk

      • 计算 importance score s∈Rrs \in \mathbb {R}^r,找到最小的 kk 个维度

      • 对这些维度执行剪枝:将对应的 λi=0\lambda_i = 0,停止这些维度的梯度更新,等价于删除对应 rank

      • 这样,有效 rank 从 rr 变为 r−kr-k​

    • 为了让 SVD-like 解释成立,AdaLoRA 还引入了 WAW_A 和 WBW_B 的正交正则项,目的是为了减少不同 rank 维度之间的信息冗余,让每个 λi\lambda_i​ 更 “可解释”

  • 相关实验发现,Transformer 的浅层不需要高 rank 更新,而深层对 rank 更敏感,AdaLoRA 会自动把 rank “迁移” 到深层

  • AdaLoRA 的代价

    • 需要维护额外状态变量:II、Iˉ\bar I、Uˉ\bar U、SS
    • 剪枝过程依赖梯度统计和 EMA
    • 当模型很大或初始 rank 很高时,内存开销显著
    • 所以后来的工作尝试选择更轻量 rank 或无需额外状态的自适应 LoRA
  • 伪代码

    def prune(W_A, W_B, lambda_, dW_A, dW_B, dlambda, k):
        # calculate importance score for each rank dimension
        s = importance_score(W_A, W_B, lambda_, dW_A, dW_B, dlambda)
    
        # find bottom-k least important ranks
        indices = find_bottom_k(s, k)
    
        # prune corresponding rank dimensions
        W_A[:, indices] = W_A[:, indices].detach()
        W_B[indices, :] = W_B[indices, :].detach()
    
        lambda_[indices] = 0
        lambda_[indices] = lambda_[indices].detach()
    
        return W_A, W_B, lambda_
    
    def ada_lora_linear(x):
        # frozen pretrained weight
        h = x @ W_0
    
        # adaptive low-rank update
        h += x @ W_A @ lambda_ @ W_B
    
        return h

GaLore

  • 参考论文:GaLore: Memory-Efficient LLM Training by Gradient Low-Rank Projection

  • GaLore 要解决的根本问题

    • 大多数 PEFT 方法(LoRA、Adapter、Prefix 等)的共同特点是冻结大部分模型参数、只训练一小部分参数,用 “参数受限” 换取 “显存节省”
    • 但这样做有一个根本代价:表达能力下降,和全量微调之间存在不可避免的性能差距
    • GaLore 的核心问题意识是全量微调真正吃显存的不是参数本身,而是优化器状态(动量和方差),特别是 Adam 类优化器需要存储和参数等大的一阶动量和二阶动量,总显存 ≈3×∣θ∣\approx 3 \times |\theta|
    • GaLore 不减少可训练参数数量,而是在低秩子空间中表示梯度、做动量更新、做正则化,然后再把更新后的梯度投影回原空间
    • 即参数是 full-rank,但是梯度统计是 low-rank
  • GaLore 和 LoRA 的对比

    LoRAGaLore
    是否冻结参数是否
    是否低秩参数是(ΔW\Delta W)否
    是否低秩梯度否是
    显存节省来源参数 + 梯度优化器状态
    表达能力受限接近全量微调
  • 数学流程

    • 假设当前参数为 ww,梯度为 G=∇wLG = \nabla_w \mathcal {L}

    • (1) 梯度的低秩分解

      • 对梯度矩阵做 SVD 分解:

        G=UΣV⊤G = U \Sigma V^\top
      • 选择较小的一侧作为投影基,若 m<nm < n,选 UU,否则选 VV,记投影矩阵为 PP

    • (2) 梯度投影到低维空间

      G′=P⊤GG' = P^\top G
      • G′G' 是低维梯度,后续优化器状态都只维护在这个空间中
    • (3) 低维空间中的 Adam 更新

      • 在低维空间维护一阶动量 mm 和二阶动量 vv,并做标准 Adam 更新: g^=mv+ϵ\hat g = \frac {m}{\sqrt {v} + \epsilon}
    • (4) 投影回原始参数空间

      G~=α⋅(Pg^)\tilde G = \alpha \cdot (P \hat g)
    • (5) 最终参数更新:

      w←w−ηG~w \leftarrow w - \eta \tilde G
  • 假设参数维度 DD,投影维度 d≪Dd \ll D,Adam 状态显存从 O(D)O (D) 降低到 O(d)O (d),但参数本身仍是 full-rank

  • GaLore 的理论分析表明大模型的梯度天然集中在低秩子空间,只在该子空间中维护历史统计,不会显著损害优化轨迹

  • 伪代码

    def update_momentum(grad_):
        # first-order momentum
        m = beta_1 * m + (1 - beta_1) * grad_
        m /= (1 - beta_1**t)
    
        # second-order momentum
        v = beta_2 * v + (1 - beta_2) * grad_**2
        v /= (1 - beta_2**t)
    
        return m, v
    
    def galore_update(w, grad, lr):
        # low-rank decomposition of gradient
        u, s, v = SVD(grad)
    
        m_dim, n_dim = u.shape[-1], v.shape[0]
        P = u if m_dim < n_dim else v
    
        # project gradient to low-dimensional space
        grad_ = P.T @ grad
    
        # update optimizer states in low-dim space
        m, v = update_momentum(grad_)
    
        # gradient regularization (Adam-style)
        g = m / (sqrt(v) + eps)
    
        # project back to original space
        grad = alpha * (P @ g)
    
        # parameter update
        return w - lr * grad

Quantization and LoRA (QLoRA)

QLoRA

  • LoRA 仍然基于大模型本身

    • LoRA 的基本假设是冻结 backbone LLM,只训练低秩矩阵 ΔW=WAWB\Delta W = W_A W_B
    • 这在训练参数数量上非常省,但存在一个被忽略的问题:冻结的 backbone 仍然以 FP16 / BF16 / FP32 存在于 GPU 中
    • 也就是说,LoRA 解决了 “反向传播”,但没解决 “模型本体太大”
  • QLoRA 的目标是

    • 在不损失 LoRA 效果的前提下,把 frozen backbone 的显存占用压到极限
    • 关键洞察是冻结权重不需要高精度,只要前向数值足够稳定即可
  • 4-bit NormalFloat (NF4)

    • QLoRA 使用了一种新的 4-bit 量化格式:NormalFloat 4(NF4)

    • 假设权重服从近似正态分布,使用非均匀量化点,在 4-bit 下最大化信息保留

    • 相比 INT4 和 FP4,NF4 在语言模型权重上表现更稳定

    • 这样,backbone 权重从 BF16(2 bytes)降到 4-bit(0.5 bytes),显存直接缩小 4 倍

  • Double Quantization(量化的量化)

    • 量化权重通常需要 scale 和 zero-point,这些量化常数本身是 FP16 / FP32,会带来额外显存开销
    • QLoRA 引入对量化常数再做一次量化,即权重被量化,量化参数也被量化
  • Optimizer State Paging(CPU–GPU 分页)

    • 即使 LoRA 参数很小,Adam 仍然可能在梯度 spike、梯度累积、多任务 batch 时造成显存峰值
    • QLoRA 的解决方案是将 optimizer states 放在 CPU,按需 paging 到 GPU,这使得单卡消费级 GPU 也能训 65B 模型
  • QloRA 在训练时:

    • backbone:4-bit NF4 + frozen
    • LoRA 参数:FP16 / BF16 + trainable
    • 前向:量化权重 on-the-fly dequant
    • 反向:梯度只流经 LoRA
  • 一个现实问题是 QLoRA 训练完成后,通常希望部署一个全量化模型,但研究发现对 LoRA-tuned 模型再做 post-training quantization,性能明显下降,原因在于 LoRA 学到的更新假设权重是高精度的,导致训练阶段与部署阶段数值分布不一致,从而引出了 Group-wise Quantization

混合方法

  • 一些方法将多个 PEFT 类别中的思想结合在一起,这种做法允许在不同的算法权衡之间进行组合,以针对特定目标进行优化,例如减少可训练参数的数量

    • MAM Adapter 同时结合了 Adapter 和 Prompt Tuning
    • UniPELT 在此基础上进一步引入了 LoRA
    • Compacter 和 KronAresB 通过使用 Kronecker 积对 Adapter 进行重参数化,从而减少其参数量
  • 下表比较了不同 PEFT 方法在存储(disk)、内存(RAM)以及计算效率方面的表现

    • 计算效率包括反向传播成本(BP)的降低以及推理阶段的额外开销(inference overhead)

    • ✓ 表示该方法在该维度上优于全参数微调,✗ 表示劣于全参数微调

    • A:基于额外参数的方法

    • S:选择性方法

    • R:基于重参数化的方法

MethodTypeDiskRAMBPInference overheadNotes
Adapters Houlsby et al. (2019)A✓✓✗+ FFN
AdaMix Wang et al. (2022)A✓✓✗+ FFN
SparseAdapter He et al. (2022b)AS✓✓✗+ FFN
Cross-Attn tuning Gheini et al. (2021)S✓✓✗No overhead
BitFit Ben-Zaken et al. (2021)S✓✓✗No overhead
DiffPruning Guo et al. (2020)S✓✗✗No overhead
Fish-Mask Sung et al. (2021)S✓✗✗No overhead
LT-SFT Ansell et al. (2022)S✓✗✗No overhead
Prompt Tuning Lester et al. (2021)A✓✓✗+ input
Prefix-Tuning Li and Liang (2021)A✓✓✗+ input
Spot Vu et al. (2021)A✓✓✗+ input
IPT Qin et al. (2021)A✓✓✗+ FFN and input
MAM Adapter He et al. (2022a)A✓✓✗+ FFN and input
Parallel Adapter He et al. (2022a)A✓✓✗+ FFN
Intrinsinc SAID Aghajanyan et al. (2020)R✓✗✗No overhead
LoRA Hu et al. (2022)R✓✓✗No overhead
DoRA Liu et al. (2024)R✓✓✗No overhead
UniPELT Mao et al. (2021)AR✓✓✗+ FFN and input
Compacter Karimi Mahabadi et al. (2021)AR✓✓✗+ FFN
PHM Adapter Karimi Mahabadi et al. (2021)AR✓✓✗+ FFN
KronA Edalati et al. (2022)R✓✓✗No overhead
KronAresB Edalati et al. (2022)AR✓✓✗+ linear layer
(IA) 3 Liu et al. (2022)A✓✓✗+ gating
Attention Fusion Cao et al. (2022)A✓✓✓+ decoder
LeTS Fu et al. (2021)A✓✓✓+ FFN
Ladder Side-Tuning Sung et al. (2022)A✓✓✓+ decoder
FAR Vucetic et al. (2022)S✓✓✗No overhead
S4-model Chen et al. (2023)ARS✓✓✗+ FFN and input
  • 表 2 展示了不同 PEFT 方法在不同模型规模(<1B、<20B、>20B 参数)上的评估情况,以及文献中报告的典型可训练参数比例
    • % Trainable parameters:可训练参数占模型总参数的百分比

    • % Changed parameters:训练后发生变化的参数占比

方法可训练参数比例发生变化的参数比例<1B<20B>20B
Adapters0.1–60.1–6✓✓✓
AdaMix0.1–0.20.1–0.2✓✗✗
SparseAdapter2.02.0✓✗✗
BitFit0.05–0.10.05–0.1✓✓✓
DiffPruning2000.5✓✗✗
Fish-Mask0.01–0.50.01–0.5✓✓✗
Prompt Tuning0.10.1✓✓✓
Prefix-Tuning0.1–4.00.1–4.0✓✓✓
IPT56.056.0✓✗✗
MAM Adapter0.50.5✓✗✗
Parallel Adapter0.50.5✓✗✗
Intrinsic SAID0.001–0.1100✓✓✗
LoRA0.01–0.5约 30✓✓✓
DoRA0.01–0.5约 30✗✓✗
UniPELT1.01.0✓✗✗
Compacter0.05–0.07约 0.1✓✓✗
PHM Adapter0.2约 1.0✓✗✗
KronA0.07约 30.0✓✗✗
KronAresB0.07约 1.0✓✗✗
(IA) 30.020.02✗✓✗
Ladder Side-Tuning7.57.5✓✓✗
FAR6.6–26.46.6–26.4✓✗✗
S4-model0.5>0.5✓✓✗

LlaMA-Adapter

LlaMA-Adapter

  • 详细细节

    LlaMA-Adapter-detail

  • 核心结构

    • Adapter Prompt

      • LLaMA-Adapter 引入了一组 learnable adapter tokens,长度通常很短(如 10–20 tokens),维度与 LLaMA hidden size 相同,不来自 tokenizer,直接作为 embedding 学习
      • 但关键区别在于:它们不是简单拼接在输入序列前,而是通过 attention 机制注入
    • Zero-initialized Attention

      • Adapter Prompt 通过一个额外的 attention 分支注入:

        • Query:来自原始 token

        • Key / Value:来自 adapter prompt

        • 该 attention 分支 初始化为 0

        • 初始阶段几乎不影响原模型行为

      • 这带来两个重要好处:

        • 训练稳定(不破坏预训练分布)
        • 适合小数据 / 指令微调
    • 在每一层 Transformer block 中原始 self-attention 保持不变,新增一条 adapter-attention 路径,两者输出相加

    • 可以理解为,LLaMA 主干负责 “语言能力”,而 Adapter Prompt 负责 “新任务偏置”

  • 前向传播过程

    • 设 XX 为原始 token hidden states,PP 为 adapter prompt(可学习)

    • 原 attention:Attnorig(X)\text{Attn}_{\text{orig}}(X)

    • Adapter attention:Attnadapter(Q=X, K=P, V=P)\text{Attn}_{\text{adapter}}(Q=X,\ K=P,\ V=P)

    • 最终输出:

      X′=X+Attnorig(X)+Attnadapter(X,P)X' = X + \text{Attn}_{\text{orig}}(X) + \text{Attn}_{\text{adapter}}(X, P)
  • 伪代码

    def zero_init_attention(soft_prompt, x):
        """
        soft_prompt: learnable adapter tokens (num_prompt, hidden)
        x: input hidden states (seq_len, hidden)
        """
        # linear projections
        Q = x @ W_q                     # queries from input tokens
        K = soft_prompt @ W_k_adapter   # keys from adapter prompt
        V = soft_prompt @ W_v_adapter   # values from adapter prompt
    
        # scaled dot-product attention
        attn_scores = Q @ K.T / sqrt(d_k)
        attn_weights = softmax(attn_scores, dim=-1)
    
        # attention output
        y = attn_weights @ V
    
        # zero initialization ensures no effect at start of training
        y = y @ W_o_adapter             # W_o_adapter is zero-initialized
    
        return y
    
    def transformer_block_with_llama_adapter(x, gating_factor, soft_prompt):
    	residual =x
    	y= zero_init_attention(soft_prompt, x) # llama-adapter: prepend prefix
    	x= self_attention(x)
    	x = x+ gating_factor * y  # llama-adapter: apply zero_init_attention
    	x = LayerNorm(x+residual)
    	residual = x
    	x = FullyConnectedLayers(x)
    	x = AdapterLayers(x)
    	x = LayerNorm(x + residual)
    	return x
这篇文档有帮助吗?

On this page

页面信息