前缀微调与LoRA或适配器模块等方法有所不同,后者修改或增加模型结构中的权重。相反,它像提示微调一样,完全冻结原始大语言模型(LLM)的参数。然而,前缀微调不是仅仅学习添加到输入序列的嵌入,而是引入可学习参数,这些参数直接影响Transformer层内部的隐藏状态。其主要思想是生成一小段连续向量序列,称为前缀,这些向量预置到Transformer块的多头自注意力机制中的键($\mathbf{K}$)和值($\mathbf{V}$)矩阵前。这个前缀为模型在每个层提供可学习的、针对特定任务的上下文,引导其激活值趋向微调任务的预期输出。机制:将上下文注入注意力机制回顾标准自注意力计算,其中查询($\mathbf{Q}$)、键($\mathbf{K}$)和值($\mathbf{V}$)矩阵是使用学习到的权重矩阵($\mathbf{W}_Q, \mathbf{W}_K, \mathbf{W}_V$)从层的输入隐藏状态($\mathbf{H}$)导出的: $$ \mathbf{Q} = \mathbf{H} \mathbf{W}_Q $$ $$ \mathbf{K} = \mathbf{H} \mathbf{W}_K $$ $$ \mathbf{V} = \mathbf{H} \mathbf{W}_V $$ 注意力分数和输出随后基于这些矩阵计算得出。在前缀微调中,我们引入一个可学习的前缀张量$\mathbf{P}$,它包含前缀向量的参数。设前缀长度为$L_p$。此张量为键生成层特定前缀向量$\mathbf{P}K$,为值生成$\mathbf{P}V$,两者形状均为($L_p \times d{model}$),其中$d{model}$是模型的隐藏维度大小。这些前缀向量随后在注意力计算之前,沿着序列长度维度与原始的$\mathbf{K}$和$\mathbf{V}$矩阵拼接起来: $$ \mathbf{K}' = \text{concat}([\mathbf{P}_K, \mathbf{K}]) $$ $$ \mathbf{V}' = \text{concat}([\mathbf{P}_V, \mathbf{V}]) $$ 注意力机制随后使用这些修改过的键和值矩阵进行操作: $$ \text{Attention}(\mathbf{Q}, \mathbf{K}', \mathbf{V}') = \text{softmax}\left(\frac{\mathbf{Q} (\mathbf{K}')^T}{\sqrt{d_k}}\right) \mathbf{V}' $$ 其中$d_k$是键的维度。这种修改发生在每个Transformer层内部(或部分层),允许学习到的前缀影响模型在其整个深度上的内部表示。重要地,LLM的原始权重($\mathbf{W}_Q, \mathbf{W}_K, \mathbf{W}_V$、前馈层等)在训练期间保持不变。只有定义前缀$\mathbf{P}$的参数被更新。digraph G { rankdir=LR; node [shape=box, style=filled, fillcolor="#e9ecef", fontname="sans-serif"]; edge [fontname="sans-serif"]; subgraph cluster_transformer_layer { label = "Transformer 层"; bgcolor="#f8f9fa"; style=dashed; X [label="层输入 (H)"]; subgraph cluster_attention { label = "多头注意力"; bgcolor="#ffffff"; WQ [label="W_Q", shape=ellipse, fillcolor="#a5d8ff"]; WK [label="W_K", shape=ellipse, fillcolor="#a5d8ff"]; WV [label="W_V", shape=ellipse, fillcolor="#a5d8ff"]; Q [label="Q = H W_Q"]; K_orig [label="K_orig = H W_K"]; V_orig [label="V_orig = H W_V"]; Prefix_Params [label="可学习\n前缀参数", shape=cylinder, fillcolor="#ffe066"]; PK [label="P_K", fillcolor="#fff3bf"]; PV [label="P_V", fillcolor="#fff3bf"]; Concat_K [label="拼接", shape=invtrapezium, fillcolor="#96f2d7"]; Concat_V [label="拼接", shape=invtrapezium, fillcolor="#96f2d7"]; K_prefix [label="K' = [P_K; K_orig]"]; V_prefix [label="V' = [P_V; V_orig]"]; Attention [label="缩放点积\n注意力(Q, K', V')", shape=Mdiamond, fillcolor="#ffc9c9"]; Attn_Output [label="注意力输出"]; Prefix_Params -> PK [label="生成"]; Prefix_Params -> PV [label="生成"]; X -> WQ -> Q; X -> WK -> K_orig; X -> WV -> V_orig; PK -> Concat_K; K_orig -> Concat_K; PV -> Concat_V; V_orig -> Concat_V; Concat_K -> K_prefix; Concat_V -> V_prefix; Q -> Attention; K_prefix -> Attention; V_prefix -> Attention; Attention -> Attn_Output; } Attn_Output -> AddNorm1 [label="加法与归一化"]; AddNorm1 -> FFN [label="前馈网络"]; FFN -> AddNorm2 [label="加法与归一化"]; AddNorm2 -> Layer_Output [label="层输出"]; // Connections within the layer X -> AddNorm1 [style=invis]; // Ensure layout flow Attn_Output -> FFN [style=invis]; FFN -> Layer_Output [style=invis]; } }前缀微调在Transformer层注意力机制内的流程。从训练参数导出的可学习前缀向量($P_K, P_V$)在注意力计算前,与原始的键($K_{orig}$)和值($V_{orig}$)矩阵拼接。基础模型权重($W_Q, W_K, W_V$等)保持冻结。参数化与效率直接优化每个层的前缀向量$\mathbf{P}_K$和$\mathbf{P}V$仍然可能涉及大量参数($2 \times \text{num_layers} \times L_p \times d{model}$)。为了进一步提高参数效率,前缀微调常采用重参数化技术。不是直接学习形状为($L_p \times d_{model}$)的完整前缀张量$\mathbf{P}$,而是学习一个形状为($L_p \times k$)的更小矩阵$\mathbf{P}'$,其中$k$是一个小的中间维度($k \ll d_{model}$)。然后,这个更小的矩阵通过一个简单的全连接网络(通常只是一个线性层,有时是一个带有非线性的两层MLP)投影到完整维度,其权重也是可学习的: $$ \mathbf{P} = \text{MLP}(\mathbf{P}') $$ or simply $$ \mathbf{P} = \mathbf{P}' \mathbf{W}{up} $$ 只有$\mathbf{P}'$和小型投影网络(例如$\mathbf{W}{up}$)的参数被训练。这与直接学习$\mathbf{P}$相比,显著减少了可训练参数的数量,使得该方法效率很高,类似于LoRA中使用的低秩分解原理。如果投影网络被共享,可训练参数的总数将与层数无关,否则会线性增长但常数因子很小。与提示微调的比较前缀微调通过预置可学习向量来调整冻结的LLM,这一目标与提示微调一致,但在重要方面与提示微调不同:注入位置: 提示微调仅将可学习嵌入添加到输入序列嵌入中。前缀微调将可学习向量(前缀)注入每个Transformer层注意力机制内的隐藏状态(特别是键和值矩阵)。表达能力: 通过影响每一层的注意力计算,前缀微调可能对模型的内部处理和生成施加更强的控制,相比之下,提示微调的影响在更深层可能减弱。这通常意味着更好的性能,尤其是在复杂的生成任务上。参数数量: 前缀微调通常需要比提示微调稍多的参数(由于按层设置前缀,即使进行了重参数化),但仍仅占基础LLM总参数的一小部分(通常小于0.1%)。两者都比完全微调效率高得多。训练与实现训练过程包括:初始化前缀参数(例如,小型矩阵$\mathbf{P}'$和投影网络权重)。冻结预训练LLM的所有参数。执行标准监督微调(例如,在特定任务数据集上最小化交叉熵损失),其中梯度仅针对前缀参数计算。Hugging Face的PEFT等库提供了便捷的实现。配置前缀微调通常需要指定peft_type="PREFIX_TUNING"、num_virtual_tokens(对应于前缀长度$L_p$),以及可能与重参数化网络相关的参数。# 示例:使用 Hugging Face PEFT from peft import get_peft_model, PrefixTuningConfig, TaskType from transformers import AutoModelForCausalLM # 加载基础预训练模型 model_name = "meta-llama/Llama-2-7b-hf" base_model = AutoModelForCausalLM.from_pretrained(model_name) # 配置前缀微调 peft_config = PrefixTuningConfig( task_type=TaskType.CAUSAL_LM, # 指定任务类型 num_virtual_tokens=20, # 定义前缀长度 (Lp) # 可能还有其他重参数化选项 # e.g., prefix_projection=True inference_mode=False # 后续推理时设置为 True ) # 用PEFT配置包装基础模型 prefix_tuned_model = get_peft_model(base_model, peft_config) # 打印可训练参数——注意数量之少! prefix_tuned_model.print_trainable_parameters() # trainable params: 9,830,400 || all params: 6,748,211,200 || trainable%: 0.145679... # 继续使用'prefix_tuned_model'进行标准训练循环 # ... (定义优化器、数据加载器、训练步骤) ... # 只有前缀参数会接收梯度更新。优点与注意事项优点:高参数效率: 与基础模型大小相比,训练的参数数量非常少。资源节省: 显著减少训练和存储的内存需求(每个任务只需保存小型前缀)。保留基础模型: 原始LLM保持不变,便于重用并允许一个基础模型实例服务多个任务。性能表现好: 在许多任务上可以达到与完全微调相当的结果,特别是生成任务,由于其层级影响,常优于提示微调。注意事项:超参数调整: 前缀长度(num_virtual_tokens)是一个需要调整以获得最佳性能的重要超参数。重参数化网络的结构也会影响结果。可解释性: 学习到的前缀向量缺乏直接的人类可读意义,不像离散提示那样。实现复杂度: 修改注意力机制可能略微复杂,比简单地将嵌入预置到输入更复杂,尽管库将其抽象化了。前缀微调是PEFT中的一种选项,通过允许学习到的上下文引导冻结LLM在每个层级的内部工作,从而在参数效率和表达能力之间取得了平衡。它特别适合在计算资源受限的情况下将大型模型应用于特定的生成任务。