2026/10/15 5:56:36

大模型微调实战指南:训练阶段、数据类型与PEFT

大模型微调实战指南:训练阶段、数据类型与PEFT 大模型微调这几年几乎成了每个算法团队的必修课但多数新手一上来就盯着某个 LoRA 脚本猛跑反而把最该先想清楚的几件事丢在了一边当前模型处于什么训练阶段、该用什么数据类型、微调到底动模型的哪一部分。这篇东西不打算教你怎么安装某个框架而是从训练阶段划分、数据类型选型和 PEFT 参数高效微调体系三个层面把一套能直接落到项目里的完整思路拆开讲清楚。适合正在做领域模型训练、手头有显卡但不太确定怎么下手、以及已经跑通过 LoRA 但说不清为什么这样做更好的人。1. 训练生命周期预训练、领域适配与指令对齐各阶段到底在解决什么问题1.1 为什么说训练阶段划分是干活的起点很多人在项目里把训练当成一个黑盒拿数据扔进去loss 降了就算成功。但这个思路在模型参数量上来之后会很快失效。预训练、领域预训练、指令微调、偏好对齐这几个阶段吃的数据不一样、算力需求不一样、训练目标不一样连数值稳定性策略都不一样。把阶段搞混最常见的结果就是你想让模型学会客服话术却拿着一批满世界都有公开文本去继续预训练浪费算力不说模型反而把通用能力给冲淡了。阶段划分的意义在于回答三个问题当前模型的短板是什么我要用哪一类数据去补这一轮训练改的是模型的哪些权重和能力不同阶段的答案完全不同。预训练阶段解决的是语言建模能力——就是模型懂不懂句子结构、常识和世界知识领域预训练解决的是某个行业语料里的术语、风格和隐含知识指令微调解决的是听人话、按格式办事偏好对齐则是在多个合格回答里挑出更符合人类偏好的那个方向。1.2 四个典型阶段的边界与数据形态在我实际接触到的项目里阶段之间的边界没那么干净但数据形态区别非常明显值得先理清楚。预训练阶段用的是大规模无标注文本目标函数就是下一个词预测。这个阶段通常不发生在普通团队手里成本太高但理解它的目标仍然有意义因为后续所有阶段都在跟它的产出打交道。领域预训练阶段很多人叫它继续预训练或 domain-adaptive pretraining用的是领域内的大规模无标注文本比如医疗病历、法律文书、代码仓库目标函数跟预训练一致只是数据分布换了。这个阶段的作用是让模型先熟悉这个领域的说话方式典型场景是代码模型预训练后再灌入内部 API 文档。指令微调阶段用的则是人工标注或模型生成的指令-回答对格式通常是system prompt user instruction assistant response。这一步解决的是指令跟随能力让模型学会在给定任务描述时输出符合格式要求的答案。偏好对齐阶段用的数据是同一个 prompt 的多个回答 人工排序目标函数从简单的交叉熵换成了偏好优化目标比如 DPO 或者 RLHF 中的 PPO。1.3 阶段选择的具体判断标准很多团队来问我微调总是不稳定怎么办。我一般先反问一句你确定你要做的是 SFT 吗判断方法其实很朴素。如果模型能很好理解你的问题只是回答内容不够专业、术语不对那是领域知识缺失应该做领域预训练或高质量 SFT如果模型答得驴唇不对马嘴连格式都守不住问题通常出在基础模型选型靠微调救不回来如果模型回答中规中矩但总在关键决策上跟你想要的风格相反那要补的是偏好对齐而不是堆更多指令数据。另外一个容易忽略的判断依据是数据量级。领域预训练通常需要上亿 token 才见效果而 SFT 用几万条高质量指令就能有明显改变。如果手里只有一两万条问答数据你在做的其实是 SFT不要跟别人说你在做领域预训练两者对数据规模和清洗策略的要求完全不同。1.4 阶段串联时的数据污染风险阶段划分有个容易踩的坑不同阶段的数据混在一起用。最典型的问题就是 SFT 数据里混入了和测试集高度同源的语料导致指标虚高一上线就崩。还有一种情况是领域预训练阶段把标注了正确答案的数据当无标注文本给模型预测模型在这个阶段只是学会了背答案并没有学会推理后面做 SFT 时难逃过拟合。我的建议是每个阶段的数据按来源独立存储、独立打标签训练时用脚本严格隔离。至少要在 data pipeline 里维护一张数据血缘表记录每一条数据被哪些阶段的哪一轮训练消费过。听着麻烦但模型出了问题回溯时它能救你命。2. 数据类型不止是精度从 FP32 到 BF16 再到量化每一步都是取舍2.1 精度与数值范围微调里最容易被忽视的数学基础训练时数据类型的核心指标是两个精度和数值范围。精度决定每次计算能区分多小的数值差异数值范围决定能表达多大和多小的数。FP32 用 8 位指数加 23 位尾数范围大、精度高但显存和算力开销也大。FP16 只有 5 位指数加 10 位尾数范围小容易出现上溢和下溢。BF16 用 8 位指数加 7 位尾数范围和 FP32 几乎一致但精度更粗跟 FP16 相反它牺牲了尾数位去保范围。对训练来说数值范围小是最致命的。FP16 在反向传播时梯度很容易变成 0 或者 inf而 BF16 因为保留了 FP32 的指数范围梯度基本不会溢出。这也是为什么现代大模型训练几乎都从 FP16 转向 BF16。但 BF16 精度粗的问题解决不了它适合训练中容忍噪声的场景不适合某些需要精确累加的运算。2.2 各训练阶段的数据类型选择矩阵不同阶段的推荐配置并不一样下表是我在 NVIDIA A 卡和国产加速卡上实测后比较稳定的组合训练阶段推荐精度备注大规模预训练BF16 FP32 主权重主权重用 FP32 做优化器状态forward/backward 用 BF16领域继续预训练BF16显存吃紧时可采用梯度累积 BF16指令微调全参数BF16 或 FP16取决于框架对 BF16 的支持情况LoRA / QLoRA 微调BF16base model 可量化至 NF4/FP8可训练的低秩矩阵建议保持较高精度推理INT8 / INT4 或 FP8不考虑训练追求吞吐和显存效率这里要特别说明QLoRA 里 base model 被量化到 NF4但 LoRA 分支本身通常仍用 BF16 或 FP16 做前向和反向。很多人误以为 QLoRA 就是所有参数都是 4 bit实际只有冻结的主干是 4 bit可训练的适配器保持高精度效果才能有保障。2.3 精度选择对收敛稳定性的实际影响我踩过最典型的一个坑是用 FP16 跑 LoRA 微调loss 在前几百步疯狂震荡死活不收敛。查了半天发现不是学习率问题而是 loss 数值在 FP16 下做累加时精度不够导致梯度下发的信号抖动。换成 BF16 后同样配置十分钟就稳定下降。这类问题在参数量比较大的模型上特别明显loss 本身已经很小比如降到 0.5 附近FP16 的精度很难表达这些细小的差异更新方向就开始乱飘。还有一个关联问题是优化器状态的数据类型。很多人只注意把模型参数设成 BF16但 Adam 的一阶二阶动量如果也用了低精度训练质量会明显下降。正确做法是模型参数用 BF16优化器状态保持 FP32。如果显存实在放不下再考虑像 Adafactor 这种本身对精度要求更低的优化器而不是硬把 Adam 的状态砍成 BF16。2.4 混合精度与损失缩放FP16 训练的自救方案如果你用的是老显卡不支持 BF16只能走 FP16那就必须理解 loss scaling 这个机制。Loss scaling 的原理很简单梯度在反向传播时容易发生下溢所以在前向计算之前先把 loss 整体放大一个倍数比如 1024等梯度反向算完后再缩小回去。这个操作能把梯度的数值抬进 FP16 可表达的范围。实际操作中要注意损失缩放系数太小导致梯度仍然下溢太大则可能上溢。现代框架里有动态 loss scaling会自动根据梯度是否出现 inf 或 NaN 来调整缩放系数。如果自己写训练循环一定要把这个逻辑实现好否则训练大概率中途崩掉。另外梯度裁剪的顺序也有讲究应该在反缩放之后、参数更新之前做梯度裁剪否则缩放系数会干扰裁剪的阈值判断。3. PEFT 参数高效微调体系LoRA 与量化组合为什么能成为事实标准3.1 从全量微调到参数高效微调的动机全量微调Full Fine-tuning要让每一个参数都参与更新对显存和算力的要求几乎跟重新预训练一个同规模模型差不多。拿一个 7B 模型来说全量微调的显存需求动辄 100GB 以上普通团队根本无法企及。参数高效微调PEFT的核心思想是冻结预训练模型的大部分权重只训练一小部分额外参数通过这少量参数去适配下游任务。PEFT 的意义不只是省显存更重要的是它保留了预训练模型的通用能力降低了灾难性遗忘的风险。全量微调会把模型往某个任务方向使劲拽容易让模型忘了它原本会的东西PEFT 因为只改了少量参数通用知识基本不动在任务多样性和专业能力之间拿了一个更稳的平衡。3.2 LoRA 的数学原理与低秩假设LoRA 是目前应用最广泛的 PEFT 方法它的核心假设是模型微调时产生的权重变化量 ΔW 是低秩的。也就是说虽然一个大矩阵比如 4096×4096看起来参数很多但真正有效的调整可以被一个低维子空间表达。实现上LoRA 将原始权重矩阵 W 冻结在旁边加两个小矩阵 A 和 B。设原始矩阵维度为 d×k则 A 维度为 d×rB 维度为 r×k其中 r 远小于 d 和 k。前向计算时输出变为 Wx BAx也就是说模型在原有变换基础上加了一个低秩校正项。训练时只更新 A 和 BW 保持不动。r 的选取直接决定了可训练参数量和表达能力之间的平衡。r8 或 r16 是常见起点但具体值要看任务复杂度。我实测过一个代码生成任务r16 比 r8 有明显提升而一个简单分类任务 r4 就已经够用。这个值的本质是控制微调能改多大程度的行为改得太多反而破坏原有能力。3.3 QLoRA把量化装进 LoRA 框架QLoRA 的本质是对冻结的基座模型做 4-bit 量化然后再挂 LoRA。这样做的好处非常直接基座模型显存占用直接砍到原来的四分之一以下7B 模型可以在一块 24GB 消费卡上微调。QLoRA 里有几个关键设计值得学习。首先是 NF44-bit NormalFloat数据类型它按正态分布的分位数做均匀量化比普通均匀量化在常见权重分布下的精度损失更小。其次是双量化Double Quantization它把量化常数本身再做一次量化进一步省显存。第三是分页优化器Paged Optimizer当显存不够时把优化器状态临时换到内存里需要时再取回来。实操中我发现 QLoRA 在大多数任务上效果能逼近 LoRA但注意它训练出来的模型推理时要稍微小心一点。如果直接把 LoRA 权重合并回量化后的 base model需要先把 base model 反量化到 BF16 再做合并否则数值误差会叠加。很多平台的 merge 脚本都处理过这件事但自己写脚本时不要忽略。3.4 其他 PEFT 方法的定位前缀微调、P-Tuning 与 Adapter除了 LoRA还有几个 PEFT 方法也值得了解因为某些场景下它们比 LoRA 更合适。Prefix-Tuning 在每一层 Transformer 的 attention 前插入一组可训练的前缀向量通过修改 attention 的 key/value 来影响输出。它的好处是可训练参数非常少但缺点是序列长度变长会占用额外显存而且效果对初始化方式很敏感。P-Tuning v2 在每一层加可训练的连续提示向量属于 Deep Prompt Tuning效果在一定规模模型上接近全量微调但在小模型上表现一般。Adapter 方法则在 transformer 块内部插入一个小型全连接网络参数量小但会引入额外的串行推理延迟。我个人的选型建议是能上 LoRA 就优先 LoRAQLoRA 用于显存受限场景如果想尝试不同方法Prefix-Tuning 适合生成任务P-Tuning 适合基座模型本身足够强的情况Adapter 在延迟不敏感且有多个下游任务需要重复部署时可以考虑。重要的是理解它们共同的出发点在尽量不动原始权重的前提下用一小部分额外参数撬动模型行为。4. 端到端微调流水线搭建数据组织、超参配置与评估闭环4.1 指令数据的格式设计别小看 prompt 模板的规范性SFT 阶段数据格式直接决定模型能不能学会指令跟随。我见过太多团队把精力花在攒数据量上却忽略 prompt 模板的一致性结果模型训练完一换提问方式就崩。指令数据的标准结构是三段式system message系统设定、user message用户输入、assistant message期望输出。训练时把 system 和 user 拼起来作为输入assistant 部分作为标签。关键点是 prompt 模板必须全局统一训练时用什么模板推理时也必须用同样的模板。很多模型卡在模板不一致这个问题上实际并不是模型笨而是模板里的特殊 token 位置不对。还有一个经常被忽视的细节标签部分是否需要包含 assistant 的开头 token。比如 Qwen 系模型训练时标签通常要加 |im_start|assistant\n否则模型在推理时可能不会主动带上这个开头导致生成结果格式异常。这类问题很琐碎但遇到一次就能让你记住一整年。4.2 数据清洗与数据配比质量比数量重要得多领域微调最常见的误区是数据越多越好。以我的经验几万条高质量、多样化的指令数据效果远好于几十万条重复度高的低质量数据。原因在于 SFT 阶段模型已经在预训练中掌握了语言能力它缺的不是知识量而是如何按指令格式组织答案的能力。这个能力只需要有限数量的高质量样本就能学会反而是重复样本会让模型对某些 prompt 模式过度拟合。清洗数据的步骤大致包括做精确去重和语义去重去掉与用户问题无关的长尾噪音检查答案与问题的相关性用规则加模型双重过滤截断超长样本控制序列长度分布。数据配比上建议主题分布尽量均衡避免某类任务数量过多导致模型输出风格向这个方向倾斜。如果想让模型保留多任务能力可以按一定比例混入通用指令数据通常的配比是领域数据比通用数据 8:2 到 9:1 之间。4.3 训练超参配置学习率、批次大小与序列长度PEFT 微调的常见超参设置与全量微调区别很大。LoRA 这类方法通常需要比全量微调更大的学习率因为可训练参数少梯度信号天然偏弱。一般建议从 1e-4 到 2e-4 之间起步搭配余弦退火调度器。如果发现 loss 下降缓慢但同时验证指标也在波动可以考虑调低到 5e-5 或 1e-5。批次大小受显存限制通常 7B 模型在 24GB 卡上LoRA 8 bit 优化器可以用 batch_size4、梯度累积 8 步。序列长度建议 2048 或 4096取决于你的任务类型。如果数据里有大量长文档序列长度太短会导致截断后信息不完整模型学到的只是片段拼凑。训练轮数上SFT 通常 2-3 个 epoch 就足够再多容易过拟合。我习惯用一个简单的检测方法如果训练 loss 持续下降但验证集上的人工抽查看不出明显改善甚至出现重复输出那就是在过拟合了应该提前停止。4.4 评估闭环离线指标、在线评测与人工抽样结合微调效果的评估不能只看 loss。我通常搭三层评估体系。第一层是离线指标比如针对生成任务的 BLEU、ROUGE针对分类任务的准确率、F1这些指标快、可自动化但无法捕捉语义质量。第二层是任务样例集评测准备一批覆盖不同难度、不同场景的典型 prompt跑完训练后统一用同一套模板推理再跟标准答案做对比。第三层是人工抽样让业务人员按几个维度打分如相关性、格式合规、风格一致性和有害性。前两层可以跑脚本第三层必须花钱花时间做尤其在上线前不能省。我自己还会额外加一个通用能力回退测试。因为 PEFT 的目标是尽量少破坏原有能力所以要在通用 benchmark 上抽样一批题再跑一遍看分数有没有明显下降。如果下降太多说明微调的方向可能太激进需要降低学习率或调小秩。5. 实战中的隐蔽问题与排查思路5.1 显存充足但 OOM罪魁祸首往往是序列长度和优化器状态很多人遇到 OOM 第一反应是调小 batch size但有些 OOM 跟 batch size 关系不大。比如序列长度一变attention 的计算量是平方级增长的如果你把数据里的超长样本直接塞进去某一批的显存峰值可能瞬间拉满。我的做法是在数据管线上先按序列长度做统计把长度超过某个阈值的数据单独拆出或截断而不是让采样器随机乱序避免某些 step 突然吃掉大量显存。优化器状态也是一大内存黑洞。Adam 保存两份动量参数量乘以二再加梯度又是参数量的一半。全参微调时 7B 模型动辄需要优化器状态 28GB。这也是 QLoRA 受欢迎的原因之一它只对低秩矩阵做优化器状态更新量级小非常多。如果你在跑全参微调建议先把优化器换到 AdamW 8bit或者干脆承认 PEFT 才是更现实的路线。5.2 训练 loss 正常但生成内容重复、格式崩坏这个现象在中文数据上很常见尤其是在小模型上。通常有两类原因。一类是数据模板混乱模型学到了多种 prompt 开头方式推理时在几种风格之间摇摆导致输出碎片化另一类是学习率偏大模型被快速推到某个局部极值生成时陷入重复采样。遇到这类问题我提供的排查链路是先检查推理模板与训练模板是否完全一致哪怕是空格和换行符差异都可能导致行为跳变再降低学习率重跑一个短实验看生成质量是否恢复最后检查数据中是否存在大量短问答对短问答会让模型过度学习一句话回答的惯性导致长文本生成能力退化。5.3 多卡训练时的批次不一致与随机种子问题PEFT 虽然可训练参数少但照样可以用多卡并行。最常见的坑是各卡的数据采样顺序不一致导致梯度更新时每张卡看到的 batch 分布差异很大损失收敛不稳定。解决方法是设置统一的随机种子并且检查 dataloader 的 shuffle 逻辑是否在每张卡上一致。框架内置的分布式采样器通常已经处理好了但如果自己手写数据流水线这块特别容易出错。另一个多卡相关的细节是梯度累积与 all-reduce 的交叉。梯度累积要在 all-reduce 之后再做 division否则相当于每张卡把梯度除以了不同的累积步数导致有效学习率出现偏差。你可以试着在训练日志里打印每张卡的 loss如果各卡 loss 走势明显分化通常是数据或随机性问题而不是模型问题。5.4 合并 LoRA 权重时的精度与格式隐患训练完成的 LoRA 权重要合并回基座模型才能用于常规推理。如果不做合并推理时需要同时载入 base 和 LoRA会增加部署链路复杂度有的推理框架并不直接支持这种形式。合并时要注意精度问题LoRA 分支通常是 BF16基座模型如果用 FP16需要先统一到同一精度再相加否则会出现精度截断误差。另外合并后的模型要再做一次完整的数值一致性测试比如跑十次同样 prompt确认与合并前的输出一致。很多平台的小版本迭代 bug 都出在这一步——LoRA 合并后忘记重新导出 tokenizer 配置导致推理时特殊 token 错位。我的经验是给合并流程单独写一个 regression 脚本固定若干 prompt比较合并前后模型的输出概率分布。如果分布差异大于某个阈值立即停下检查而不是直接把这个模型拿去上线。这是一个成本极低的防线但能拦住最丑陋的线上事故。5.5 数据泄漏与评估集污染最隐蔽的指标幻觉最后聊一个很多人迟早上当的问题。微调项目里训练集、验证集和测试集如果来自同一条数据源几乎必然存在泄漏。尤其是指令数据是从某个公开大语料里采样得到时验证集里很可能会出现训练集句子的近义复述导致评估指标虚高真实线上表现却惨不忍睹。防泄漏的操作层面建议有三条按原始文档而不是按句子划分数据集避免同一份文档被切进不同集合做 n-gram 重叠检查把与训练集高重叠的候选评估样本剔除从业务侧收集新的评估集最好是在模型训练前根本没见过的一批真实用户输入。最后一条虽然成本高但对上线后的真实效果判断价值最大。我的个人习惯是微调完先不急着上线拿 20 条线上真实 prompt 做一次人工盲测跟基座模型对比输出质量。这个操作十分钟就够但能让你在正式发布前把预期管理做对也能提前暴露不少训练数据配比的问题。尾声一段关于参数规模和数据质量的实践体会前面几节基本把训练阶段划分、数据类型和 PEFT 体系的技术骨架都搭了一遍。最后想说一点跟具体参数关系不大但直接影响项目成败的体会。我发现很多团队把 LoRA 的 rank 加到很大以为参数越多效果越好结果模型反而在已知任务上被拽变形又把领域数据堆到几十万条却连基础指令模板都没有整理干净。真正起作用的是数据够不够干净、模板是不是统一、评估是不是闭环、精度选择是不是匹配。宁可 rank 小一点、数据少一点也要先把路走通。跑通一版之后再根据业务反馈迭代数据配比这才是 PEFT 微调体系的正确使用方式。我在实际项目中总结出一个小技巧每次训练跑通之后顺手记录下基座模型版本、数据采样脚本的 commit、LoRA rank、学习率、精度配置和评估结果这几项。下次出问题时对着这张表排查效率能翻一倍。也欢迎你在自己的项目里按这个思路搭一套流程再回头来看很多一开始觉得玄学的问题其实都是阶段、精度和数据这三件事没有对齐造成的。