大型语言模型(LLMs),特别是基于Transformer架构的模型,在预训练阶段需要大量的计算资源和时间投入。一旦在海量文本数据上完成预训练,这些模型便蕴含了通用语言知识和推理能力。一种常见且有效的方式,可以将这些预训练知识运用到特定应用中,便是通过微调。标准微调是指将预训练模型进一步在一个较小、面向特定任务的数据集(例如情感分析、问答、摘要)上进行训练。在此过程中,模型的所有参数或大部分参数通过梯度下降进行更新,以优化其在目标任务上的性能。尽管这种方法有效,但随着模型规模的持续增大,它也带来了一些重大挑战:完全微调的负担计算成本: 微调通常比预训练需要更少的数据和迭代次数,但它仍然涉及通过整个网络进行梯度反向传播,而这个网络通常包含数十亿乃至数万亿参数。对于GPT-3(1750亿参数)或PaLM(5400亿参数)这样的模型,更新所有权重需要大量的GPU资源(例如,多张高端GPU,如A100或H100)和相当长的训练时间,每个任务通常需要数小时甚至数天。如果一个组织需要针对数十甚至数百个不同任务调整基础LLM,那么累计计算开销将变得非常庞大。存储和内存开销: 每个经过完全微调的模型本质上都是原始大型模型的一个独立副本,只是权重略有调整。为每个下游任务存储一个单独的数十亿参数模型会导致庞大的存储需求。例如,一个以FP16精度存储的1750亿参数模型大约需要350GB的磁盘空间。并发部署多个此类模型以服务不同的应用程序会使问题更加严重,需要海量昂贵的高带宽GPU内存(HBM)。考虑一个场景,你需要专门的模型用于客户支持工单分类、内部文档摘要和代码生成辅助,而这些模型都源自相同的基础LLM。完全微调将产生三个独立的、大型模型检查点。部署复杂性: 管理众多大型独立模型的部署生命周期(更新、监控、服务)在操作上很复杂。为每个特定任务的模型推出更新或扩展推理端点需要大量的_基础设施管理,并可能导致资源碎片化。灾难性遗忘: 当在特定任务上进行微调时更新所有参数,模型有丢失部分预训练阶段获得的通用知识的风险。这种现象被称为灾难性遗忘,它会降低模型在刚被微调的任务之外的其他任务上的性能。虽然正则化或仔细选择学习率等技术可以在一定程度上减轻这种影响,但这仍然是一个问题,尤其是在多个任务上进行序列微调时。参数高效的理由这些挑战共同促使人们寻找更高效的适应方法。理想情况下,我们希望技术能够使预训练的LLM针对下游任务专业化,同时只修改其一小部分参数。这种方法通常被称为参数高效微调(PEFT)。PEFT的核心思想是冻结预训练模型的大部分权重,并引入少量新的、可训练参数,或只修改现有参数的极小部分。如果成功,这将带来显著优势:计算量减少: 训练涉及更新的参数少得多,大幅减少了适应所需的计算需求(GPU时间、能耗)。存储需求极小: 无需为每个任务存储完整的模型副本,只需保存一小组修改或新增的参数。这通常将每个任务的存储开销从数百千兆字节减少到兆字节。部署简化: 只需加载单个大型基础模型副本,不同的微小“任务向量”(训练好的PEFT参数)可以被替换或并发使用来服务多个任务,极大地简化了服务基础设施。减轻遗忘: 由于原始模型的大部分保持不变,PEFT方法在预训练期间学到的通用能力方面通常表现出更少的灾难性遗忘。设想一个使用PEFT方法的场景。与其更新基础模型 M 的所有 N 个参数,我们引入一小组 k 个新参数,其中 $k \ll N$。import torch import torch.nn as nn from transformers import AutoModelForCausalLM # 示例基础模型 # 加载一个大型预训练模型 base_model = AutoModelForCausalLM.from_pretrained( "gpt2-xl" # 示例:15亿参数 ) N = sum(p.numel() for p in base_model.parameters()) print(f"基础模型参数量:{N:,}") # --- 标准微调 --- # 在标准微调中,所有参数都需要梯度 # for p in base_model.parameters(): # p.requires_grad = True # optimizer = torch.optim.AdamW(base_model.parameters(), lr=1e-5) # ... 训练循环更新所有 N 个参数 ... # 所需存储:约 N * 2 字节(对于FP16) # --- 参数高效微调 --- # 冻结基础模型 for p in base_model.parameters(): p.requires_grad = False # 定义一小组*新的*可训练参数(例如,适配器层) # 假设这些新参数总共有'k'个元素,k << N class SimpleAdapter(nn.Module): def __init__(self, input_dim, bottleneck_dim): super().__init__() self.down_proj = nn.Linear(input_dim, bottleneck_dim) self.up_proj = nn.Linear(bottleneck_dim, input_dim) self.activation = nn.ReLU() def forward(self, x): # 假设残差连接在外部处理 return self.up_proj(self.activation(self.down_proj(x))) # 假设适配器被适当地添加到 # 基础模型结构中 # (实际实现取决于具体的PEFT方法,如LoRA、 # 适配器等) # 示例:实例化适配器参数 adapter_params = [] # 用于保存可训练适配器参数的列表 # ... 创建和收集适配器参数的代码 ... # k = sum(p.numel() for p in adapter_params) # print(f"PEFT参数量 (k):{k:,} (其中 k << N)") # 只优化一小组适配器参数 # optimizer_peft = torch.optim.AdamW(adapter_params, lr=1e-4) # ... 训练循环只更新 k 个参数 ... # 所需存储:约 N * 2 字节(基础模型)+ k * 2 字节(任务向量) # 每个任务的存储开销:k * 2 字节标准微调与参数高效微调(PEFT)之间的参数量和存储影响比较。PEFT旨在使 k 远小于 N。“后续章节将考察特定的PEFT技术,例如适配器模块和与专家混合(Mixture-of-Experts)相关的方法,详细说明它们如何实现这种效率,同时保持在下游任务上的强大性能。这些技术对于使大型模型在工程实践中更具实用性和适应性正变得日益重要。”