一、问题背景:为什么我要折腾LoRA
先说结论:全量微调7B模型对个人开发者基本不可行。我之前尝试用DeepSpeed ZeRO-3在单卡3090上跑Qwen2.5-7B的全量微调,即使开了梯度检查点和8-bit优化器,显存还是直接OOM——7B模型全量微调的显存需求大约在80GB以上(参数14GB + 梯度14GB + Adam状态56GB,再加上激活值)。
而我的实际需求很朴素:让一个通用7B模型在法律合同审查这个垂直场景下回答问题更专业。基座模型Qwen2.5-7B-Instruct在通用benchmark上表现不错,但一问到"这份租赁合同里甲方提前解约的违约金条款是否合理"这类问题,回答就开始泛泛而谈,经常漏掉关键法条。
LoRA(Low-Rank Adaptation)的思路很简单:冻结原模型权重,在Attention的q_proj、k_proj、v_proj、o_proj等线性层旁挂两个低秩矩阵A和B,只训练这两个小矩阵。对于7B模型,r=16时LoRA参数量大约只有2000万,占原模型的0.3%左右,显存和存储成本都大幅下降。
再叠加QLoRA的4-bit NormalFloat量化,把基座模型压到4-bit加载,进一步把显存门槛拉低到24GB以内。这就是本文的完整方案。
二、环境与版本
环境版本这种事必须写清楚,不然复现时全是坑:
GPU: NVIDIA RTX 3090 (24GB) x 1
CUDA: 12.1
PyTorch: 2.3.1
transformers: 4.44.2
peft: 0.12.0
bitsandbytes: 0.43.3
trl: 0.9.6
accelerate: 0.33.0
datasets: 2.20.0
Python: 3.10.14
基座模型用 Qwen/Qwen2.5-7B-Instruct,从ModelScope拉取(国内网络更稳)。注意transformers 4.44.x 对Qwen2.5的原生支持已经比较完善,不需要额外装trust_remote_code的补丁,但加载时仍建议保留该参数。
三、方案设计
整体方案分四步:
- 数据准备:把原始合同审查问答整理成Alpaca格式(instruction/input/output),清洗后约3.2万条,按98:2划分训练/验证集。
- 模型量化加载:用bitsandbytes以4-bit NF4量化加载基座模型,计算类型用bfloat16(3090支持bf16,比fp16数值更稳)。
- LoRA注入:在q/k/v/o_proj和gate/up/down_proj上挂LoRA,r=16,alpha=32,dropout=0.05。
- 训练与评估:用trl的SFTTrainer,cosine学习率调度,2e-4峰值学习率,batch size通过梯度累积凑到16,训练3个epoch。
为什么r选16而不是8或32?我做了个小规模对比:r=8时验证集loss下降偏慢且最终比r=16高约0.03;r=32时效果提升不明显( 问:租赁合同中约定"甲方提前解约需支付两个月租金作为违约金",租客已住满一年,甲方提前30天通知解约,违约金是否合理?
基座:一般来说违约金需要双方协商,两个月租金是否合理要看合同约定……(泛泛而谈,未引法条)
微调后:根据《民法典》第585条,违约金以补偿性为主、惩罚性为辅。两个月租金作为违约金,若未显著高于甲方实际损失(如重新招租的空置期损失),一般认定有效。但若租客能证明实际损失远低于两个月租金,可依第585条第2款请求法院酌减。建议……(引用法条,给出可操作建议)
七、总结
这次LoRA微调验证了几件事:单张24GB消费卡完全能跑7B模型的领域微调,QLoRA + LoRA的组合把门槛从"需要A100"降到"一张3090",3.2万条数据、6.5小时训练就能拿到24个百分点的领域准确率提升。核心经验是:r=16加MLP层target_modules是性价比甜点,warmup别省,merge一定要做,loss曲线要结合下游指标看而不是只盯数值。
后续我打算试试DoRA(peft 0.12已支持)看能否在同样配置下再提1-2个点,以及把训练数据扩到8万条看是否还有增益。代码和数据清洗脚本已整理,有需要的可以在评论区交流。