从原理到实操:深度拆解LoRA、Adapter、Prefix三大主流微调方案

📅 2026/7/29 3:31:31 👁️ 阅读次数 📝 编程学习
从原理到实操:深度拆解LoRA、Adapter、Prefix三大主流微调方案

01传统大模型微调

大模型的微调,传统大模型如Bert、T5、Bart在进行微调的时候,有三种微调的方法。

  • 第一微调所有参数;
  • 第二调整部分参数(冻结住部分层);
  • 第三对模型尾部加一些层,训练新加层。

但是这三种方法针对这些传统大模型,都是几百兆的大模型,并不大了。 现在的模型都是几亿、几十亿参数,很少用这种方法来调了。

以chatglm2-6b模型为例进行微调

https://www.modelscope.cn/models/ZhipuAI/chatglm2-6b/summary

训练数据的格式每行都是一个问答对,问题的字段是context,答案字段是target目标,拼成这样一个json字符串格式。

通过代码tokenize_dataset_rows.py对数据进行分词:

首先读取输入context问题,读取输出答案,问题分词和答案分词,把问题分词和答案分词相加作为一个整体的input。

分词核心是保证问题/答案各自的分词准确,保留语义边界,方便格式控制(加分隔符、截断),避免跨单元的错误token生成;合并相加核心是将独立的token序列转换成模型能处理的连续输入,让模型学习问答间的语义关联,完成训练样本的构建;本质是“先拆后合”,拆是为了精准,合是为了关联,最终服务于对话模型的训练效果。

BPE分词把常见组合字符串拼在一起,google分词工具sentencepiece,它可以实现BPE会把所有的文本都转成UTF-8,再来统计UTF-8的编码哪些容易在一起,所以有可能几个汉字对应一个TOKEN,也有可能2个TOKEN对应一个汉字。

最大序列长度max_seq_length这个参数是指输入+输出,如果太大会给模型显存造成很大压力,如果输入输出长度超过了300,它会把后面那一节给截断。处理完生成的TOKEN数据,就可以用来进行训练。这个数据是一个二进制数据。

加载大模型,前面层数都冻结住,只训练最后2层或10层;

所有参数60亿左右,符合模型6b参数,训练最后二层的参数26万;

  • 冻结参数:5977241600
  • 可训练参数:2663642400
  • 所有参数:6243584000

执行代码finetune_freeze_parameter.py进行训练;

上图是训练代码,读取分词好的token数据,ids是所有输入问题+答案,seq_len问题的长度,labels预测的标签。

-100表示特殊字符不预测,正常来说语言模型输入A B C D E F,对应的预测为-100、-100、-100,d、e、 f,前面三个-100表示a、b、c 这三个词它预测出来的东西不存在,真正梯度下降法是从后面这些开始,前面这个-100是告诉模型这里预测出来的结果当它不存在,不参加任何梯度下降法,只有后面的参加。输入部分也就是问题不需要任何预测,只要把答案输出预测出来就行了。

用了max_length以后,如果输入长于max_length直接截断,如果短于 max_length,就把它给补0,补0的地方不需要预测。

训练调参对显存的要求,比如要调6B的参数,batchsize=10,需要的显存是6*6B*10=360g显存。所以不可能去调6B,调个0.2B就可以了,batchsize也不能调太大,6是个常数。训练方法一般是Adam,显存里保存的数据有:

  • 首先把模型存进去,模型是6B就是6B个float32浮点数
  • Adam梯度下降法,存每层的前向结果,还要存之前步骤的梯度结果

因为训练方法依赖于前一步的结果,这些是经过一些比较粗略的参数估量,基本上就是n*6*6B,6跟模型的结构有关系,Transformer结构都是6左右,这是一个比较粗算的一个经验公式。

模型可训练参数,因为显卡的限制

  • max_length也不能太大(平均长度*1.5);
  • 模型可训练参数不能太多;
  • batch_size理论上来说越大越好,但也不能太大(2^n);

序列长度大,不影响模型的参数,模型参数仍然是6B,因为每个序列它共用的都是这些参数,但长度过大不影响模型参数,会影响前向结果,中间结果就要保存很多,虽然能够输出长文本,但会严重影响训练。

  • 首先统计平均长度(去除异常值)把那种特别短和特别长的先给它去掉,一般平均长度*1.5作为max_length。
  • 第二步确定好需要训练的参数,微调/全量微调等方法。
  • 第三步batch_size理论越大越好,所以会不断的调整batch size,比如先调128,如果挂了就96、64这样慢慢往下减,一般是2^n次幂,这样可以把显存的性能榨到最干。

训练参数一定要用float32,如果用float16,可能会出现Nan或者****inf,数据精度表达问题,强行用一个低精度的来表示可能会出现错误或者溢出。

同一层参数一定要一个类型,不同层的参数可以是不同的类型。严格来说每个参数的类型都可以不一样,只不过Pytorch只能以层为单位最小细粒度去指定。这个其实就是一种量化,这个用8位来量化(load_in_8bit),这是真正的量化。

微调传统微调大模型使用场景:

  • 第一数据量大,调的参数越多需要的数据量越大,TOKEN数>=可调参数量,这样才能保证效果好。
  • 第二机器算力足,显存大小决定能否训练,显卡flops决定训练时间。
  • 第三个是场景要非常垂直。

这是大模型的一个全量微调,这种直接微调有一些不足的地方,

  • 第一就是说参数多,显存不容易放下。
  • 参数多,需要对应更大的数据。
  • 参数多,不容易收敛。
  • 参数多,调参时间过长。

02大模型微调三件套

这就是为什么大模型很少全量微调,除了微调还有调参三件套。

  • 第一是prefix tuning或prompt tuning。在问题前加一些提示词,让它来回答问题,只不过它加的提示词是一个抽象的TOKEN来训练这个TOKEN的参数,从果倒退因。

  • 第二是Adapter-Tuning,在Transformer模块里加一层适配器,像是一种串联的外挂。

  • 第三是在求attention的时候加一层Lora外挂,像是一种并联的外挂。

    这是现在大模型微调的三件套,它最终的目的是节约内存。

三大类方法一个是Prompt-Tuning,一个是适配器adapter,还有个 LoRA。这三大方法都可以理解成给模型打外挂。每个方法都是通用的,但是大模型是各自搞的,导致实现方法的代码不通用,需要特殊开发。

02-1Prompt-tuning

第一个方法Prompt-tuning

问大模型“我的狗叫什么名字?”,大模型肯定不知道,在前头可以加一个 prompt“我养了只狗,它叫旺财”,再问大模型“我的狗叫什么名字?”它就可以回答出来了,就能理解语义了,前头这个叫做prompt工程,改变输入来改变输出。在前头加一些虚拟的TOKEN,这个TOKEN没有实实在在的意义,它对应的并不是一个真实的词,只是个虚拟TOKEN。比如再问它“我的狗叫什么名字”,这些TOKEN在模型里头都对应一个向量,把它带进训练,这个TOKEN是在词典之外,相当于在词典之外再额外加n个虚拟TOKEN。

把整个一句话输入给模型,模型输出的可能是叫小明,做模型训练的时候,通过Loss反向调整来改变TOKEN,只调这些词对应的embedding,但是模型本身参数不变。相当于给模型以TOKEN的形式添加可调的参数,TOKEN词表和模型是完全一一对应的。

这是它的一个基本思想,有多种实现方法,产生了不同的套路,只做硬提示叫prompt工程,硬提示对应的另外一套方法叫LangChain。Prompt-Tuning是软提示,靠参数调整模型的输出,这两种方法都可以实现模型的效果。

Prefix-Tuning叫软提示,在每一层的TOKEN之前构建一个prefix,这个TOKEN类似于刚这个t1、t2…tn,模型去学这些词所对应的embedding,每个TOKEN直接对应embedding,训练起来不容易收敛,比如对应128维的embedding可能不容易收敛,让它对应一个64维的一个 embedding, 64维的embedding会通过一个MLP的神经网络把它再转成一个128维的embedding, 这个网络参数是可训练的。训练这个网络就行了,这样会容易收敛。可以前面加MLP结构,也可以不加。

除了Prefix推理以外,刚是加了MLP, 在前头又可以加lstm。比如t1、t2、t3、t4等各对应一个embedding,经过一个lstm层,形成四个新的t1、t2、t3、t4,再经过MLP, 变成我们想要的,相当于说在这里加了一个LSTM,对应图上的prompt encoder。

为什么要加LSTM,基本思想就是认为这些词这些软token,虽然是虚拟不见的,但它们之间其实应该是有关联的,希望通过一个lstm能够提取一些上下文特征。第一种方法和第二种方法都是在每一层输入都加。

大模型的结构都是Transformer,一层层Transformer,核心是attention就是self-attention,self attention就是k=q=v,三个共用一套向量,红色表示它有三个t1 t2 t3,三个软TOKEN,第一层加3个软 TOKEN,第二层加另外3个软TOKEN,这两个不相等,第三层也另加3个,前入的方法只在第一层加,第一层加完,第二层加,第三层加,每一层都对应向量维数,比如向量维度是128维,软token的个数是3个,一共有三层,就是3*3*128。

每一层做attention的时候,这些虚拟的层只做k和v不做q,就是说第二层不要求输出结果,黑色的是进行attention输出的结果。就是它做q的时候,把第二层黑色给生成了。红色上下相邻的两层,并不是生成的,是完全虚拟出来的,所以只需做K V,就是说在生成它的时候它参与进来了,而这个跟下面一层没有任何关系,而右边黑色的是跟下面的这些是有关系的,这个虚拟层是在每一层中间都会随机出来一个向量,红色的是外挂,只训练红色的东西,但黑色的要参与运算。

黑色是模型本身,每个红色的点都是一个完全随机初始化的向量,红色的点之间没有任何关联性。黑色的点它是由下面这些点通过self-attention 算出来的,而红色的点就是一个完全随机出来的向量,跟下面这些没有任何关系。

这个就是P-Tuning V2,每一层都初始化向量,当然这个向量红色的点也有一个变种,每一个红色对应一个向量,直接训练这个向量,变种就是一个红色的点对应一个向量,这个向量再经过一个MLP。

一般来说大模型微调都可以使用Transformer提供的peft库专门做prefix-tuning和lora这两种方法。

执行finetune_prompt.py进行微调,

在训练的时候,除了传原生glm模型以外,参数pre_seq_len每层都加上128的软TOKEN对应的向量,因为传了参数,框架就会认为冻结住6B的参数。

训练完之后,查看微调后的和原生的对比:

02_2适配器Adapter

适配器,正常在Transformer多抽头attention,前向神经网络,Layer Norm,两个非线性就出来了。在前向神经网络和Layer Norm中间强插一层串联,在原有的网络中强插进去一个Adapter叫做适应器。

适应器的结构特别简单,输入维度d经过前向网络把它变小,经过非线性变换,再投回来完成,加个softmax,典型的Resnet结构,Resnet结构进来的是d,出来的也是d, 所以它不会影响整个网络的结构。

在调模型的时候,只调适应器,其他东西全冻住。有框架的时候就用框架,没有框架的时候可以自己实现一下。

冻住模型的所有参数,在这里实现两个模块,第一个是Adapter, 两个线性的全连接层w1/w2, (in_features, mid_features)代表上图输入的d和m,d先变成m,m再经过一个非线性再变回去,从下连接到上边的这条线short cart,y=0.1y+x,0.1便于收敛。

再搞一个CombinedModel层,由两个模型传入,model1是原有的Ffl,model2是适配器,输入x先进入原有的model1再进入适配器model2,再输出,相当于把适配器和原有的全连接层绑在一起。

float16的数据类型进行推理没问题,但在训练的时候需要float32,不适用它不报错,但是它训练出来就不对,所以必须在这里给这两层转成float32,训练必须用float32,推理float16、float32都可以。

把输入x先转成32可以计算,算完以后它出去切模型又变成了16,所以再把它给转回来,再转成16,就是这个适配器。

看下这个模型结构,看打印出这个模型结构,它核心是0-27这28层block, 它是设在多抽头attention出来之后的MLP,所以真正要打外挂的这一层先attention q k v转一下,把做完多抽头attention经过全连接的这一层其实对应的就是Feed forward layer。

transformer.encoder.layers一共有0~27,一个个取,每个s都对应这个模块,取s.self_attention.dense对应上图红框出来的模块,让这个模块先取一下它的in_features,就是它的长d,输出向量作为适配器的输入,把适配器和原有的全连接层绑在一起来替代。

正常来说是每一层都可以加适配器,每一层都可以加个适配器,但每层加适配器导致可训练参数特别多,机器显存扛不住。所以只给第一层加个适配器,适配器的参数是需要训练的。

加载模型的时候,把模型放在GPU里,自己生成的适配器还有绑在一起的模型默认是在CPU,所以要使用CUDA放进GPU。

打印下改造好的模型:

第0个GLMBlock,原生是027,这里变成127,把0单独抽出来了。

之前微调训练完之后就直接保存模型,但是这里没有配置文件,真正训练的东西只有适配器,所以把适配器给保存起来就可以了,用的时候再把适配器重新拼一下就可以了。

02_3Lora

在工作中最常用的一种方法Lora。

最基本思路,比如h=w0*x,x是输入特征,w0是训练好的,h是输出,这个是模型的一层。如果说模型参数量非常大,h是1000维,x是500维,w是一个1000×500的矩阵,就是50万的参数,如果要直接微调训练50万个参数会造成非常难调,一是需要很大的数据量,二是对内存要求非常高。

简化就是原模型不调,给它加一个外挂,来调这个外挂,把这两个结果加起来,也就是并联在一起,这个是线性代数中的低秩分解。

直接调W0的代价太高,外挂一个ΔW,只调ΔW,把ΔW变成两个矩阵相乘:B*A,这两个矩阵分别通过r低秩分解的方式进行一个降维,这样学习的参数就会变得非常少,Lora就是用低秩分解的方法来做。

有一个n*m的矩阵W0,它等价于A*B,W0(n*m)=A(n*r)*B(r*m),A*B的参数是n*r+r*m,比如n=1000、m=100、r=10,那么原来是10万个参数,这里是1万+1000=1.1万个参数,把W0通过这种方式来表示,参数量瞬间只有原来的1/10,能达到同样的效果,这个叫做低质分解。

r是类似于一个超参,一般来说都会设的非常小。矩阵的秩,W0是n*m,W0有这么多个参数,它都是具有自由度的,用这种方式n*r+r*m来表示它自由度就会降低了很多,它带来的好处是参数量的急剧下降,r是a*b =W0矩阵的秩,秩是指一个矩阵它实际的维度。

上图矩阵它只对应1个方程,这两个方程是完全等价的,维度是2×2,但实际上它的秩等于1。

微调模型之前,W0应该是等于一个全零矩阵,如果W0刚开始不等于一个全零矩阵,就是一个有偏的矩阵,因为这个是已经训练好的,在刚开始训练的时候能够保持跟原先是一样的,再慢慢的去调W0,W0=A*B,让w=0,可以是a=0 或b=0,只要有一个为0,那么它就为0,但是不能让两个都为0,那另外一个不为零的,要进行随机初始化参数,随机初始化参数最好的方法就是正态分布,用正态分布来随机采样是最符合先验的,b为0,a为正态分布采样,为什么两个不能同时为0。

W=W0+UV,U V就是刚说的A B,求导,损失函数对U V求导,如果U V 同时为0,就无法学习了。

任务领域越垂直,做一个医疗领域的,做一个法律领域的,做一个金融领域的,领域越垂直,r应该大一点,可训练的参数相对来说比较多一点,从外界获取的信息就会多一点,如果 r=1就没有可学习的。如果领域越通用,是一个泛化的场景,比如做个旅游,写个诗,做一些日常生活的问答, r就小一点。

r设的越大,对原来的模型破坏的越严重,就会产生灾难性遗忘,灾难性遗忘在前面方法中都存在这个问题,比如Prefix Tuning如果TOKEN数量比较多,也是在破坏原有的模型,一样就会产生一种灾难性遗忘,适配器的参数很多,也会产生灾难性遗忘。

大模型的核心Transformer,Transformer=self attention+FFN,self attention是一个词向量,经过三个矩阵Wq、Wk、Wv变成了三个不同的向量,这三个不同的向量再做self attention,再做attention,多抽头其实是一样的,多抽头只不过是多变化了几组而已。一个向量通过三个矩阵变成三个不同的向量,再进行attention操作,这个就是self attention,Lora就用在词向量经过三个矩阵(Wq+w0)这之间,改变这个向量,就是在训练self attention这个部分,在微调self attention这个位置。

有个Lora之后,Q K V它其实是同一个向量变成q k v,但没有必要这3个都变。

attention基本思想,v的加权求和,加权权重由q k来决定。只要调一个就足够了,Q K不需要两个都调。

比如Q K最简单的模式,q和k做内积,最终其实要改变的是内积的结果,改变两个是可以改变这个结果,但其实改变一个就完全可以达到同样的效果,所以就没有必要去同时改变两个,prefix跟这个思想是不一样的。prefix没有q的概念,它是改变k v 。

v是需要改变的, q k二选一就可以了。

Lora它计算量并没有减少,它在推理的时候它的计算量还增加了,但是好处是只更新了部分参数,内存占用量变小了,内存占用量有个粗略的公式:6*可训练参数+2*不可训练参数,这是最终的内存占用量。所以说它可训练参数这一块变得比较小,整个原始的模型都是不可训练参数,而可训练的参数就是加的外挂,加的外挂比较小,因此它内存占用量比较小。真正的训练瓶颈并不在Flops,并不在运算力上,而在显存上。

Lora这个方法是目前最为主流的,真正的重点是在内存不足的情况下Lora效果最好。

以量化的int 8的方式加载模型,这是一个真正的模型量化,load_in_8bit设成true只能在Linux下跑,在Windows下跑会缺包。

做Lora配置,大模型有三种常用套路,这个是TaskType.CAUSAL_LM。

r=finetune_args.lora_rank,

GLM是perfix decoder,这个类型跟attention结构是一样的,只是连接的线不一样,self attention本身是一模一样的。在调Lora的时候是一样的,只是mask不一样,所以它们两个是可以混用的。dropout就是有些参数随机剪枝。

把原有的模型传进去,再传一个设置,新出来的模型打上了Lora外挂,后面的训练就可以直接训练了,打上Lora外挂的时候,它会把原有参数都冻结住,运行finetune_lora.py代码进行微调。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费