LLaMA, ChatGLM, BLOOM的参数高效微调实践
好家伙!去年我捧着一堆中文领域数据,眼巴巴盯着手上那几张A100显卡,心里跟猫抓似的——全量微调?想都不敢想!那玩意儿光显存就能把我吃破产。但我不甘心啊,偏要微调出一个能用的对话模型。于是,我把当时吹得天花乱坠的轻量化方案都撸了一遍:LoRA、Prefix Tuning、Adapter……一样没落下。这一玩就是大半年,踩过的坑都能养一池鱼了。LLaMA、ChatGLM、BLOOM三个基座轮番上阵,后面还折腾中文词表扩充,又是新一轮踩坑。今天这篇就当是蹲在坑边写的日记,给后来人递根绳子。你准备好接了吗?
跟全量微调死磕大半年,我悟了:LoRA才是那个“悄悄的进村,打枪的不要”!
1. 先扔结论:LoRA,稳得像老狗
我拿LLaMA-7B做了个铁桶实验:单机8卡40G A100,DeepSpeed ZeRO3加CPU offload,float16混合精度,不搞激活重计算;单卡batch_size=4,最大序列长度512,梯度累加4步,学习率1e-4;数据用Alpaca中文版,训了3个epoch。结果你猜怎么着?
Prompt Tuning和Prefix Tuning这俩哥们,验证损失死活下不去,跟全量微调比起来差了一个银河系。LLaMA-Adapter好点,但照样追不上LoRA。而LoRA呢?只比全量微调高了那么一丢丢,可训练参数呢?只有全量的0.1%左右!
说实话,Prefix Tuning效果差我一点都不意外。它干的事就是在每个Transformer层前硬塞一个随机初始化的张量——你想想,模型本来在高速路上开得好好的,突然每个路口都冒出一块不明不白的路牌,能不翻车吗?Prompt Tuning虽然也是加软提示,但只塞在输入层,范围小,所以比Prefix Tuning稍好,但也就是从“炸”变成“小炸”。
LLaMA-Adapter聪明点,搞了个门控机制,训练过程里慢慢放提示入场。我翻过那篇论文,门控参数是可学的,一开始几乎封闭,随着训练小步打开,对原有知识的干扰小多了。但跟LoRA比,还是差口气。
LoRA为什么牛? 它压根没碰模型原有的计算路径,直接在旁边搭了条低秩旁路。前向的时候,原始权重W₀的路径照常跑,旁边再多跑一条B×A的分支,最后两个结果一加完事。训练完还能把B×A合并回W₀,推理时零额外开销。干净利落,一点也不拖泥带水。
从损失曲线看,LoRA的曲线跟全量微调基本贴在一起,验证损失只高了那么一层皮。准确率呢?预测下一个词的能力达到了全量微调的97%以上。我跟你说,这个数在实战里已经香得冒泡了。
实操tip给你备好:LoRA的秩r,我试了8、16、32。7B模型上8和16差别不大,但32会让训练时间明显变长。注入模块放在self-attention的query和value上就行,有人放到了所有线性层,效果没涨,计算量却翻倍。听我一句劝:就放Q和V,别贪心。
2. 中文词表扩充:两阶段才是正解,一步到位那是坑
LLaMA原始分词器对中文的态度,简直可以用“暴殄天物”来形容。举个栗子——“男儿何不带吴钩,收取关山五十州”,它分出来是24个token!每个汉字后面跟一堆<0xE5>这样的字节碎片。Chinese LLaMA扩充词表后,同样的句子只要14个token。效率差距明摆着。
但!直接扩充词表有风险。新词没有预训练好的词向量,随便初始化就开整,模型原有的语义知识会被冲得七零八落。我一开始试了一阶段方案——同时训练embedding和transformer层权重,结果英文能力崩了,中文也没学好。脸都绿了。
后来我学乖了,改成两阶段。你看这操作:
第一阶段:只训练新扩的embedding矩阵。原始embedding矩阵从V×h扩展到V'×h,新词表的向量追在末尾,原词表的向量冻死不动。在中文语料上做自回归语言模型预训练,transformer层全部冻结。目标很明确:让新embedding学会中文token之间的关系,同时保证原来的英文表征不受打扰。跑了大概10万步,中文困惑度从原来的50多降到了20左右,英文困惑度纹丝不动。
第二阶段:加入LoRA权重,同时放开embedding的训练。但这里的“放开”是半开:只允许新embedding和LoRA参数更新,原始embedding继续冻着。transformer层通过LoRA旁路来适应新词表带来的分布变化。训练数据上,我混了20%的英文语料,防止灾难性遗忘。这一步很关键。
踩坑记录:第一阶段learning rate设大了,
读者评论 2