☰
大模型微调实战:训练阶段、PEFT与数据类型选型避坑指南
2026/10/15 5:56:05 网站建设 项目流程

最近在做一个垂直领域问答系统的落地项目,同行问得最多的三个问题基本是:训练阶段到底怎么划分、微调用什么数据类型、为什么大家都在折腾PEFT参数高效微调体系。这些问题听起来基础,但真到动手配置的时候,坑一个接一个。这篇文章把训练阶段划分、数据类型选择、PEFT参数高效微调体系这三件事串起来讲透,顺便把我实测过的参数组合和踩坑记录列出来,方便你直接照着抄。如果你是刚开始接触大模型微调,或者已经跑过一两个脚本但不太理解为什么要那样配置,读完应该能理清不少头绪。

1. 训练阶段划分:预训练、监督微调与对齐到底在做什么

1.1 预训练阶段:让模型掌握语言的基本规律

大模型训练的第一个阶段是预训练。这个阶段输入的是海量纯文本数据,来源可能是网页、书籍、论文、代码仓库,规模一般按TB计算。模型通过自回归或掩码语言建模的方式不断预测下一个词,最终学到的是词与词之间的统计规律、句法结构、基础事实和一部分推理能力。

预训练产出的基座模型,本质上只是一个“语言通”。你说什么它都能接,但它并不知道什么是“回答问题”,也不存在指令遵循的概念。很多人第一次拿到预训练权重直接对话,会觉得输出前言不搭后语,就是因为这个模型只见过“接着往下写”的任务,没有见过“你说一句我答一句”的任务。预训练阶段消耗的算力非常夸张,通常需要大规模集群连续训练几十天,普通团队基本不会自己去碰。

实操中你需要关心的只有一件事:你拿到的基座模型是否已经经过指令微调或对齐。如果拿一个已经带对话能力的模型继续做任务微调,新数据会和固有行为产生冲突,效果不一定更好,有时反而变差。所以在开始任何微调之前,先确认基座的状态,这个信息通常会写在模型卡或README里,不要想当然。

1.2 监督微调阶段:让模型学会“按指令办事”

监督微调(SFT)是日常落地和我个人项目中使用最多的阶段。它的输入是“指令-回答”对,有时会带上下文,目标很直接:让模型在给定指令时输出期望的回答。数据规模通常从几千条到几十万条不等,单机单卡就能完成训练。这也是PEFT参数高效微调体系最适合发挥的地方。

SFT阶段最大的特点是少量高质量数据就能改变模型行为。我之前做过一个垂直领域问答项目,只用大概两万条人工清洗过的数据,就把一个开源基座模型改造成基本能用的领域助手。反过来,堆十万条低质量数据,效果反而更差。SFT的本质是教模型“该说什么”,不是教它“学会新知识”。一条错误示范会被模型当作标准答案学进去,这比不教更糟。所以数据清洗、去重、格式统一,在这个阶段比模型结构本身更影响最终效果。

1.3 对齐阶段:让模型输出更安全、更符合偏好

对齐阶段通常指RLHF或DPO这一类方法,目标不是继续提高准确率,而是让模型输出符合人类偏好。比如用户问危险问题模型需要拒绝,用户给开放性问题模型需要给出更有帮助的回答。对齐依赖奖励模型或偏好数据,实现复杂度比SFT高不少。

对绝大多数业务场景而言,真正需要的是SFT阶段。对齐应该在模型已经稳定“听懂指令”之后再做。如果SFT没做好,模型连基础格式都不规范,直接上对齐会浪费大量时间。阶段划分的核心逻辑是:预训练把语言能力补齐,SFT把任务能力补上,对齐调风格和安全边界。用PEFT做业务的同学,99%的精力放在SFT就可以了。

三阶段对比如下:

阶段数据形式训练目标算力消耗PEFT适用性
预训练海量纯文本预测下一个词极高一般不适用
监督微调指令-回答对学习指令遵循中等非常适用
对齐偏好数据/奖励模型符合人类偏好较高部分适用

2. 数据类型选型:FP32、FP16、BF16、INT8、INT4如何组合使用

2.1 浮点精度基础:指数位和尾数位的关系

先讲最基础的浮点表示。FP32是单精度浮点,1位符号+8位指数+23位尾数;FP16是半精度,1位符号+5位指数+10位尾数;BF16是1位符号+8位指数+7位尾数。乍看只是位宽不同,实际影响很大。

FP16的指数位只有5位,能表示的最大值大约65504。训练时一旦梯度或中间激活超过这个值,就会直接变成inf。BF16的指数位和FP32一样是8位,所以范围内基本不会溢出,但尾数只有7位,数值精度有限。这个差别解释了为什么很多新手在FP16下训练时loss会突然变成nan——不是学习率问题,而是数据一波动就溢出了。改成BF16之后,问题基本消失。

2.2 混合精度训练:主权重高精度,计算用低精度

大模型训练通常用混合精度。具体做法是:主权重和优化器状态保存为FP32,前向和反向计算使用BF16或FP16。这样显存占用大约减半,数值稳定性也好。现在的训练框架基本把逻辑封装好了,设置bf16=True就能用,不需要手动处理梯度缩放。

但有一点很多人没意识到:AdamW优化器本身要保存一阶动量和二阶动量,这两个都是FP32,主权重也是FP32。所以在BF16训练流程里,权重其实各有一个BF16副本(计算用)和一个FP32主副本(更新用)。这也是为什么全参数微调显存需求很大,而PEFT只更新少量参数,就能省下大量优化器状态的显存。

2.3 训练量化与推理量化:不要混为一谈

INT8和INT4量化通常用于推理部署,比如把模型权重从FP16压到INT8甚至INT4,显存占用和推理速度都有明显改善。但是在训练中,常规的INT4权重没法直接做反向传播,所以出现了QLoRA这一类方法:用NF4的4bit格式存基座模型权重,同时保留高精度的可训练LoRA参数。这相当于把量化训练和PEFT结合在一起。

我的建议是:微调阶段优先用BF16+LoRA或NF4量化+LoRA;推理部署阶段再用INT8或INT4量化。不要在训练阶段为了省显存直接把基座模型加载成FP16,尤其是老显卡,很容易遇到精度溢出的问题。另一个经验是,只要显卡支持BF16,训练就不要用FP16,收益非常明显。

常用精度对比:

精度类型符号位指数位尾数位典型用途注意事项
FP321823主权重、优化器状态显存占用大
FP161510老显卡混合精度训练容易溢出,需要loss缩放
BF16187新显卡混合精度训练大模型训练默认选择
INT8---推理量化精度损失较小
INT4 (NF4)---推理量化、QLoRA训练适合显存受限场景

3. PEFT参数高效微调体系:三种主流技术路线与选型

3.1 为什么需要参数高效微调:全参数微调有多奢侈

全参数微调在大模型时代非常奢侈。一个7B模型的全参微调,单张24G显存基本跑不动,即使跑得动,每个任务都会产出一整套完整权重,部署维护成本极高。更麻烦的是全量微调容易造成“灾难性遗忘”,模型在学新任务的同时把原有通用能力丢掉。

PEFT的思路是只更新极少量参数,同时尽量保留基座模型的原有能力。可训练参数占比通常在0.1%到1%之间,显存需求大幅降低,单卡微调大模型成为可能。这也是PEFT参数高效微调体系在社区里迅速普及的根本原因。

3.2 LoRA:低秩分解思路

LoRA是当前最主流的PEFT方案。它把权重增量写成两个低秩矩阵B和A的乘积,训练时冻结原始权重,只优化B和A。假设一个模块的维度是2048x2048,全量权重参数超过400万个,而LoRA用r=8的话,参数量大约是2乘2048再乘8,约3万个,差距上百倍。

LoRA有几个关键超参必须理解。r是秩,太小表达能力不足,太大又退化成近似全量更新,常规从8开始试,复杂任务可以到16或32;alpha是缩放系数,通常设成r的1到2倍;dropout用在LoRA矩阵上防止过拟合,经验值0.05左右。target_modules决定LoRA挂在哪些模块,常见选择是注意力模块的q/k/v/o,有些场景把mlp也加进去效果更好。需要注意的是,r不是越大越好。我实测过一些分类任务,r=16反而比r=8差,多出来的参数量没什么收益,还更容易过拟合。

3.3 QLoRA:量化与LoRA的结合

QLoRA解决的是显存不够的问题。基座模型用NF4格式量化到4bit,存储显存大幅缩水,前向计算时再反量化为BF16。这套方案能在保持接近全参数微调效果的同时,把微调门槛降到单卡可跑。我自己在12G显存的环境里用QLoRA微调过7B级别的模型,per_device_batch_size只能设1,配合梯度累积到等效batchsize16,训练基本稳定。

QLoRA还有一个容易忽略的细节:它对基座模型本身是有损的,量化4bit会丢失一部分信息。少数任务上QLoRA效果明显不如纯LoRA,建议在效果和显存之间做权衡。如果显卡能跑纯LoRA,优先纯LoRA;实在跑不动,再上QLoRA。

3.4 前缀学习和Adapter:两种不同的思路

Prefix-Tuning和P-Tuning v2是另一类PEFT方案,它们在输入序列前插入可学习的“虚拟token”或前缀向量,冻结模型主体权重。参数量很小,在部分自然语言理解任务上效果不错,尤其适合序列标注、分类这类结构化任务。缺点是会让训练序列变长,占用额外显存,生成式任务上表现通常不如LoRA。

Adapter的做法是在Transformer层之间插入小型前馈网络,训练时只更新Adapter。好处是参数少、任务隔离性好,多个任务可以共享基座模型,各挂各的Adapter。坏处是引入额外顺序计算,推理延迟会增加。如果对推理时延敏感,需要好好评估。

三类PEFT方案对比:

方案可训练参数推理影响推荐任务缺点
LoRA很低(0.1%左右)几乎无生成式对话、指令微调选择target_modules有技巧
QLoRA很低几乎无显存受限的大模型微调4bit量化有信息损失
Prefix/P-Tuning极低输入序列变长NLU分类、序列标注生成任务效果一般
Adapter低推理延迟增加多任务多适配器场景推理路径非标准

选型建议很简单:做生成式助手,首选LoRA或QLoRA;做轻量NLU任务,P-Tuning v2值得试试;要在一个基座上挂多个任务,Adapter的多路复用有天然优势。

4. 实操项目:基于LoRA和QLoRA完成一次指令微调

4.1 环境准备与依赖版本

开始前先确认环境。Python版本建议3.9以上,然后依次装PyTorch、Transformers、PEFT、bitsandbytes这几个库,版本尽量保持稳定,不要全装最新版,否则经常出现接口不兼容。特别是bitsandbytes,老版本加载4bit模型时经常报错,建议装最近几月内有维护的版本。

装完依赖后,先验证显卡是否支持BF16。用一个很小的Tensor测试,如果支持,配置里直接开bf16=True省很多事。很多环境问题其实都出在这里:先确认依赖,再跑数据预处理,不要一上来就训练完整模型,定位问题会容易很多。

4.2 数据准备:从JSON到Token序列

SFT数据常见的格式是JSON,每条记录包含instruction、input、output三部分。有input就拼在instruction后面,没有就只保留instruction。加载后要转换成模型认识的token序列,核心是把文本拼接成固定格式,然后tokenizer编码。

这个环节有三个关键点。第一,max_length不要随便设太长,128到2048之间按任务复杂程度选,设太长浪费显存,设太短答案会被截断。第二,padding策略要注意,训练时右侧padding是常规做法,但推理生成时建议改成左侧padding,避免模型被迫生成padding部分。第三,tokenizer必须有pad_token,很多基座模型没有设置,需要手动将pad_token设置为eos_token,否则训练时会告警。

强烈建议在正式训练前做一次小样本过拟合测试。拿100条数据,用稍大的学习率跑几十个step,如果loss能压到很低,说明数据pipeline没装错。这个习惯帮我排查了至少三次莫名其妙的问题,比直接投诉训练框架有用得多。

4.3 加载基座模型并配置QLoRA

加载4bit模型的代码示意如下:

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig import torch bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) tokenizer = AutoTokenizer.from_pretrained("your_model_path") model = AutoModelForCausalLM.from_pretrained( "your_model_path", quantization_config=bnb_config, device_map="auto", trust_remote_code=True, )

加载后立刻设置pad_token,并开启gradient_checkpointing。然后构造LoRA配置并包装模型:

from peft import LoraConfig, get_peft_model, TaskType lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, r=8, lora_alpha=16, lora_dropout=0.05, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], ) model = get_peft_model(model, lora_config)

包装完打印一下可训练参数量,确认LoRA参数占总参数的比例正常,一般在0.1%左右。如果比例异常高,多半是target_modules写错了,多写了一些不是注意力投影的模块名。

4.4 训练参数设置与Trainer

训练参数直接影响效果,下面是一组经过验证的配置:

from transformers import TrainingArguments, Trainer training_args = TrainingArguments( output_dir="./lora_output", num_train_epochs=3, per_device_train_batch_size=1, gradient_accumulation_steps=8, learning_rate=2e-4, warmup_ratio=0.03, lr_scheduler_type="cosine", bf16=True, gradient_checkpointing=True, logging_steps=20, save_steps=200, save_total_limit=2, )

几个关键参数说下原因。learning_rate用2e-4左右,比全参数微调的1e-5到3e-5高一个数量级,因为LoRA只更新少量参数,可更新的参数少了,步长自然可以更大。gradient_accumulation_steps用来扩大等效batch size,显存不够时是标配。gradient_checkpointing开启后显存占用会降低,但训练速度会变慢,需要自己取舍。save_total_limit设成2,避免磁盘被checkpoint塞满。

4.5 推理、合并与导出

训练完成后,直接用LoRA权重做推理时需要同时加载基座模型和adapter。如果想把模型部署成完整权重,需要先合并再保存:

model = model.merge_and_unload() model.save_pretrained("./merged_model") tokenizer.save_pretrained("./merged_model")

合并操作建议在CPU上做,显存不足时可以换内存跑。合并后的权重等于原始权重加增量,可以直接当普通模型用。这里有个容易踩的坑:很多人训练完直接加载未合并的原始模型做测试,效果当然没有变化,以为是训练失败。先确认adapter有没有正确加载,再怀疑模型没训练好。

5. 常见问题与排查技巧:显存溢出、Loss异常与效果不佳

5.1 显存溢出:先定位瓶颈再优化

显存溢出是最常见的问题,原因无非这么几类:max_length太长、batch_size太大、没开gradient_checkpointing、数据加载时每条padding到相同长度造成无谓浪费。排查思路是先用一条最短样本测试显存占用,然后逐项放宽。

有一个冷门但常见的原因:训练参数里设置了eval集,但没关评估时的显存翻倍。Trainer在训练过程中会周期性地跑评估,如果评估数据量比较大,显存压力会骤然上升。降低评估频率或把eval关掉,OOM问题可能就消失了。

5.2 Loss变成NaN或一直不下降

Loss变NaN先检查精度:FP16改成BF16,大概率解决问题。再检查学习率:如果你拿全参数微调的5e-6来跑LoRA,loss会下降极慢,但2e-4用在LoRA上是常规操作。然后检查数据:样本里有没有空字符串、有没有截断后output为空的情况,这些会在batch里制造异常的padding。

Loss不下降则先看小样本过拟合测试能不能跑通。如果小样本也降不下去,多半是数据拼接或标签位置写错了,重点检查input_ids和labels的对齐关系,尤其是offset的处理。

5.3 训练完成但生成效果不理想

效果不好先验证推理配置。很多人把temperature设成0.7甚至更高,模型输出发散;设得太低又容易重复。常规做法是temperature设在0.1到0.3之间,top_p设在0.9。另一个常见问题是do_sample设False时模型只用贪心解码,输出会比较机械,但它至少应该能正确回答训练集里的问题。

如果训练集里都答不对,那说明模型没有学到模式,回查数据格式。如果训练集里答得对但测试集不行,大概率是数据分布太单一,90%的训练样本都是同一个模板,模型只会照葫芦画瓢,换个说法就崩。解决办法是增加数据多样性,而不是单纯增加数量。

5.4 过拟合与灾难性遗忘

SFT过拟合的表现是loss很低但生成时词汇重复、没有多样性。对策是增加数据多样性、适当调高lora_dropout到0.1、减少训练轮次到2或3。遇到过拟合时,先降epoch,再调dropout,不要一上来就动rank。

灾难性遗忘则是另一种体验,模型学会了新任务但忘了原来的通用能力。解决方法是训练数据里混入10%到20%的通用指令数据,让模型在学习新技能的同时保留已有能力。

问题排查速查表:

现象典型原因处理建议
显存溢出batch过大或序列过长开gradient_checkpointing,减max_length
Loss变成NaNFP16精度溢出换成BF16,关闭loss缩放
Loss不下降数据标签错位用小样本过拟合测试验证pipeline
训练完没效果没加载adapter权重推理前确认是否加载或合并adapter
输出重复过拟合减少epoch,调高dropout
通用能力下降灾难性遗忘混入通用指令数据

6. 进阶经验与避坑清单

到这里,训练阶段、数据类型、PEFT体系的核心内容基本覆盖完了。最后分享几条实操中积累的经验,不算总结,就是一些日常能用到的小习惯。

第一,每次实验先记录三样东西:显存占用、单step耗时、loss曲线。这三项能快速定位绝大多数问题。不要嫌麻烦,等出问题再回忆就晚了。

第二,不要盲目追求大的rank。LoRA的r从8开始几乎是通用做法,任务变复杂先加数据质量,再加r。我在多个任务上对比过,r=16并没有稳定优于r=8,而训练时间和显存倒是实打实增加了。

第三,混入通用指令数据是缓解灾难性遗忘最有效的技巧之一。我通常按9比1的比例混,也就是九份领域数据配一份通用指令数据,模型既学得到新任务,也不至于忘记老本领。

第四,如果你准备长期做模型微调,把数据预处理脚本和评估脚本固化下来,做成模板。每次新项目只是换数据、换任务文本,其他逻辑不动。这套流程跑顺之后,从拿到数据到完成一次LoRA微调,基本能在半天内完成。

第五,关于模型能力的天花板,PEFT能改变的是行为、格式、风格,很难凭空塞入大量模型原来不具备的知识。如果任务需要大量新知识,优先考虑检索增强或继续预训练,把希望全压在SFT微调上通常会失望。

我个人在实际操作中的体会是:先把pipeline跑通,用最小资源验证数据质量,再逐步放大模型规模和数据量。这个思路让我少走了很多弯路。希望这篇文章对你有帮助,也欢迎带着你的踩坑记录来交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询