☰
大模型SFT与RL微调实战:从数据准备到调优的完整路线
2026/10/19 12:49:22 网站建设 项目流程

大模型训练这两年已经从“能跑通”进化到“要跑好”,SFT(监督微调)和RL(强化学习)基本上成了提升模型能力的两个必答题。但我在跟很多刚入行的同学交流时发现,大部分人要么停留在“知道有这么回事”,要么被一堆GRPO、KL散度、reward hacking的术语劝退。这篇文章不绕弯子,直接把我自己调模型时踩过的坑、验证过有效的方法,从数据准备到超参选择再到问题排查,串成一条可以照着操作的路线。不敢说看完你就能调出顶级模型,但至少能让你少走几个月的弯路。

这更适合正在做模型微调实践、或者准备从SFT往RL方向进阶的算法工程师和独立开发者。哪怕你是刚接触大模型的小白,只要懂一点深度学习的皮毛,也可以对照着一步步来。

1. 先想清楚:SFT和RL分别解决什么问题

1.1 预训练、SFT、RLHL各自的分工

很多刚入门的朋友容易把这几件事混成一个概念,其实它们解决的是完全不同的阶段问题。

预训练是让模型学会语言本身,目标是预测下一Token,这个阶段的模型像是一个读过海量资料、但不知道怎么回答问题的“博学者”。它知道知识,但不知道人类期待它以什么格式、什么口吻、什么结构来回应。

SFT就是在预训练模型的基础上,给一批“问题-标准回答”的成对数据,让模型模仿这些示例学会怎么回答。这一步的核心是模仿,模型从中学到的不只是内容,更重要的是回答的结构、风格和信息组织方式。我做SFT时有个很直观的感受:如果数据里都是分点回答、步骤清晰的格式,模型就会大概率主动往这个格式靠,哪怕内容并不需要这样组织。这既是优势,也是需要警惕的地方——格式过拟合在SFT里非常常见。

RL则是把“模仿”升级为“目标优化”。SFT只要求模型“像示例一样回答”,但示例往往不是最优回答。有些任务光是模仿不够,还需要模型在探索中去发现更好的策略。RL通过设计奖励信号,让模型在生成过程中反复试错,逐步提高得分。RL调的是策略,让模型不仅会说人话,还会说“能拿到更高奖励的人话”。

这里有个关键认知:SFT更像是给模型打地基,RL是在这地基上装修。地基没打好,RL再怎么优化也很难突破上限。我在实际项目中见过好几例,SFT阶段数据质量一般,然后寄希望于RL去救,结果奖励一路涨、真实效果却稀烂。原因很简单,RL是在SFT给出的策略附近探索,基础策略本身有问题,探索方向也就偏了。

1.2 什么时候该用SFT,什么时候必须上RL

需要明确一点:不是所有场景都需要RL。

如果任务类型是“格式改写”“抽取摘要”“分类打标”,SFT就能很好解决,因为这些任务的核心是理解并遵循指令,正确答案空间比较明确。我在做结构化信息抽取的Demo时,只做了SFT,效果就已经远超直接用预训练模型,根本不需要碰RL。

但如果任务是开放式生成,比如让模型写代码、写故事、做数学推理、让回答更符合人类偏好,这时候“标准答案”很难定义,那就需要RL登场了。RL的核心价值在于:当无法穷举出所有好答案时,让模型自己在搜索空间里探索,并通过奖励信号学会哪个方向更容易获得高分。比如数学推理,SFT数据里只有一种解题路径,但最优秀的路径可能并不在训练集里,RL就能在探索中找出那类被训练数据忽略的更优策略。

另外还有一种情况值得用RL——需要抑制模型的某些倾向。比如SFT之后模型可能过于啰嗦,什么都拆成一二三,也可以用RL的奖励函数设计来引导模型学会更简洁的表达。甚至token预算、格式约束这类偏工程的问题,也能通过奖励设计变成模型自己学会的“习惯”,这比写硬编码规则要鲁棒得多。

我个人的决策参考表大致是这样的:

任务类型推荐路径原因
指令遵循、格式提取、分类仅SFT标准答案明确,模仿即可
开放生成、创意写作、通用对话SFT + RL偏好优化需要探索高分策略
数学推理、代码生成SFT + RL过程/结果奖励需要发现超越样本的更优路径
急需控制长度、格式等风格属性SFT + 轻量RL奖励可以直接代显式属性
数据量非常小(几百条以内)仅SFT,甚至考虑few-shotRL需要更多探索空间

现在还流行绕过SFT直接做RL的玩法,比如一些从基座直接强化学习的方案。我试过一轮,在小数据集上确实可行,但对奖励设计的要求很高,模型容易进入“自由发挥”的乱飘状态。对大多数人来说,先SFT再RL仍然是性价比最高的路径。

1.3 一个完整的训练流程长什么样

先给你一张我平时跑项目的流程图谱,帮你建立整体感。注意这里不涉及任何代码,只讲流程:

  • 基座模型选型
  • 数据准备与清洗(SFT阶段)
  • 第一轮SFT训练 → 生成候选模型
  • 评测与筛选 → 决定进入RL还是继续优化SFT数据
  • 构建奖励模型(或使用规则奖励/外部打分器)
  • RL训练(PPO或GRPO等)
  • 多轮评估与人工盲测
  • 防退化检查(通用能力是否下降)
  • 上线与持续迭代

这套流程我跑过很多遍,最核心的心得是:评估节点一定要前置。不要等RL训练完才想起来做评测,而是SFT出来一个版本就立刻拿去测,测完决定下一步动作,这样能省掉大量白训的时间。

2. SFT细节拆解:数据质量比你想象的更重要

2.1 数据数量:几千条也能做大文章

很多人一上来就问“SFT需要多少数据”,好像这是个固定值。不存在的。

以我自己的实验经验来看,在基座模型能力比较强的前提下,精心整理的四五千条高质量指令数据,效果经常会好过直接用网上的百万级混合数据集。原因在于SFT的核心目标是教模型“遵循指令的格式和风格”,而不是教知识——知识已经在预训练阶段存进去了。SFT数据的作用更像是一个“使用说明书”,告诉模型怎么把已经学会的东西拿出来用。

所以我给自己定了个数据原则:能用5000条解决的事,绝不上10万条。数据少意味着更容易做质量控制,更容易排查bad case,训练成本也更低,迭代速度反而更快。

当然,这不意味着数据越少越好。如果任务本身覆盖了多个领域、多种语言、多种格式,数据量需要相应上调,但优先把每个维度的多样性做够,而不是简单求多。

2.2 指令数据的四个质量维度

我在清洗SFT数据时,会重点核查四件事:

第一,格式一致性。同一个数据集的指令头如果一会儿是“请回答下面的问题”,一会儿是“你是一名助手”,模型就会学到不稳定的服从行为。我处理时会统一改成一套模板,比如全部使用“### 指令:... ### 回答:...”的结构,实测格式统一后模型遵循指令的稳定性有明显提升。

第二,答案完整性。别只看开头几句,要通读整个回答。我踩过最大的坑是:一个数学问答数据集的答案是错的,但模型照单全收学会了错误解法。所以在SFT阶段,答案的准确性怎么强调都不为过。你要记得,模型不会分辨数据里的对错,它只会学到“这样答会被表扬”。

第三,多样性覆盖。同样是“讲个笑话”这个指令,我得确保训练集里既有短笑话、也有段子式回答、还有绕口令式的回答。多样性是防止模型过拟合到单一回答模式的关键。

第四,复杂性分层。简单指令和复杂指令要有合理的配比。全上简单指令,模型就只能做简单任务;全上复杂指令,基础能力可能不稳。我常用6:3:1的配比——六成普通难度,三成偏复杂,一成极端困难。

2.3 模板设计:不要忽略这一层

SFT里有个很容易被忽视的细节——指令模板。

现在很多模型基座带有自己的Chat模板,比如用特殊Token代表对话开始、助手回复等。有些开发者在微调时偷懒,直接用自己的模板训练,结果模型学到了双套模板,推理时行为就很怪异。我的建议是:除非有强需求,否则直接用基座自带的chat template,你只需要把数据组织成对应的对话格式即可。

曾有一次我为了统一项目内部格式,强行改了模板结构,结果模型生成内容尾巴经常多出奇怪的结束Token。排查了很久才发现是模板问题,回退到基座的模板就好了。这里我想提醒大家:这一层往往能卡住你两三天时间,别侥幸。

2.4 SFT训练超参与Loss观察

SFT阶段超参选择相对RL简单很多,但我还是总结了一套基线值:

超参推荐范围我的默认值
Epoch1-32(数据高品质时1即可)
学习率1e-6 到 1e-52e-6(7B以上模型)
Batch Size取决于显存,尽量大32以上稳妥
Warmup50-200步100步
Max Length不截断有效答案2048或4096

关于epoch多说一句:SFT不是训得越多越好。我有过惨痛教训,高质量数据训到第5轮,个别任务上的表现反而下降了。这种现象叫“过拟合到数据噪声”,具体表现是Loss还在降,但实际评估分在掉。所以我现在的做法是每轮结束都在验证集上测一次,选择评估分最高的那版checkpoint,而不是等训练全部结束才选。

Loss曲线观察也有讲究:SFT的loss一般会平稳下降,最后收敛在1.0以下(视词表大小和任务难度不同会浮动)。如果你看到loss在训练中途突然跳高,大概率是学习率太高,或者batch里出现了极端长样本影响了梯度。

另外,LoRA的rank选择值得一说。我测试过32、64、128三档,发现对于7B和13B级别的模型,rank=64已经足够表达复杂的指令映射关系,再大收益有限、显存压力却明显增加。rank太小(比如8),则容易欠拟合,具体表现是验证集上风格不稳定。

2.5 LoRA合并和基座参数冻结的取舍

现在聊SFT基本绕不开参数高效微调。我自己实践下来,LoRA在绝大部分任务上和全参微调的效果差距很小,但显存和迭代效率的优势巨大。不过有几点需要特别注意:

  • 冻结基座参数时,要确保训练数据里的“语言表述”和基座分布接近,否则LoRA能记住任务格式、却改不了底层的语言风格。
  • 如果做多轮SFT,每一次都在上一版LoRA基础上继续叠加效果往往不好。我更建议保留基座权重,每次单独训LoRA,最后合并或动态加载。
  • 合并权重之后,最好在合并模型的生成结果上重新抽检一遍,因为合并操作在某些框架里会带来一点点精度损失,虽然小,但不排除影响极端长文本的末尾token预测。

我在跑一个面向长文档问答的项目时,合并LoRA后抽样发现中间某层输出有轻微漂移,导致一个原本能对齐的引用格式偶尔出错。后来我把LoRA放回和训练时一致的推理路径里,问题就消失了。这个坑在部署链路复杂的项目里特别容易爆,值得提前排查。

3. RL训练调优:从奖励设计到策略优化

3.1 搭建一条完整的RL训练链路

RL不是单个算法,而是一条链路。链路里任何一个环节出问题,模型都会受到连带影响。

我惯用的链路是这样的:

  • 策略模型(SFT出的模型)
  • 奖励模型或奖励函数(可以用训练好的RM、也可以用规则计算分数)
  • 参考模型(通常是SFT模型本身,用于计算KL散度,防止模型跑偏)
  • 生成rollout数据 → 计算奖励 → 更新策略 → 循环

这个链路搭建阶段最容易踩坑的是“参考模型没正确冻结”。有一次我写训练代码时不小心把参考模型也设成了可训练,结果KL散度一直在高位抖动,模型生成逐渐失控。后来加了requires_grad_(False)才恢复正常。这种问题不细看loss根本发现不了。

3.2 奖励模型:RL的灵魂在画像是谁给的

RL最核心也最容易被低估的部分是奖励模型。很多人一上来就盯着PPO、GRPO的算法参数,却忽略了“奖励信号本身准不准”。如果奖励模型给出的分数和真实人类偏好不一致,RL优化到最后就是一个“高分低能”的模型。

训练奖励模型的数据结构是pair-wise的:同一问题给两个回答,标注者标明哪个更好。一个常见误区是只用人类标注数据训练RM。标注数据很贵也很稀缺,更可行的办法是“冷启动+持续迭代”:先用一小批高质量人工标注做初始RM,然后让策略模型生成一批新样本,交给RM打分,再抽一部分高分歧样本人工复核,不断扩充RM训练集。这样可以大幅降低标注成本。

奖赏模型的训练里还有几个经验值:

  • 奖励模型的训练集最好覆盖“近似但不同水平”的回答对,如果两个回答差异太大,模型学到的边界会很粗,对细微提升不敏感。
  • Batch内尽量让同指令的回答对在一个batch里,这样模型对比学习的效果更好。
  • Reward模型训练epoch不宜过多,我测下来2个epoch左右就足够,继续训容易把训练数据背下来,在分布外样本上打分失真。

另外强调一个真正的“灵魂”问题:如果RL任务本身可以用程序算分——比如数学题的正确率、代码题单测通过数、格式化约束是否满足——那优先用规则奖励,不要训练RM。规则奖励干净、无偏、不会reward hacking,能让调试复杂度降低一大半。我最近跑数学推理类项目就直接用结果正确性作为信号,完全不依赖RM,稳定性高很多。

3.3 PPO vs GRPO:选型经验

PPO是RLHF里的经典主力。它通过优势函数估计(GAE)和重要性采样来更新策略,稳定但计算量较大。GRPO则是近两年我越来越喜欢的方案——它在做组内相对比较时不需要额外的critic模型,显存占用更小,训练也更稳定。

我自己的选型逻辑是这样的:

  • 显存很紧张、或想快速跑通原型:优先GRPO,适合小团队快速迭代。
  • 任务奖励信号噪声大、需要精细控制每次更新的幅度:PPO配合精心调好的critic更稳。
  • 希望对每个prompt生成多组response做组内对比(比如让模型生成8个答案,取这四个里最好的方向):GRPO天然贴合这种结构,因为它在组内做比值。

GRPO里的组数(group size)很值得调。我测试过组数从4到16的变化,组数越大优势估计越稳,但计算量等比例增大。通常在性能和资源间我选8左右,比较均衡。组数太小(比如2),优势估计方差偏大,loss容易波动。

3.4 KL系数:管住模型别跑飞的缰绳

RL训练时模型很容易钻空子,比如为了拿奖励突然改变输出风格,甚至开始复读垃圾内容。为了防这种情况,通常会在奖励里加入KL散度惩罚——惩罚模型偏离参考模型(通常是SFT模型)太远。

KL系数(通常记为β)需要仔细调。β太大会导致模型不敢探索,最终产出和SFT版本差不多,RL白跑;β太小则模型彻底放飞,输出越来越离谱,甚至出现重复token、语无伦次。

我调β的思路是:先设一个偏大的值,例如0.1,跑一小段试探训练,观察平均KL散度和奖励均值。如果KL迅速冲高,就调大β;如果KL变化平坦、奖励也不长,就调小β。理想状态是KL散度在训练过程中有波动但不爆炸,奖励均值稳定上升。

还有一个小技巧:可以在训练前期把β调大一点,保护模型基础能力,后期逐步减小,释放更多探索空间。这种退火思路我在几个项目里测试,和创新效果比固定β要好一点,但注意别减太猛,否则末尾又容易崩。

3.5 reward hacking的实战案例

reward hacking(奖励攻击)是RL训练里最经典的“翻车”现象:模型发现了奖励函数里的漏洞,用意想不到的方式拿到了高奖励,但实际效果完全不对劲。

我记忆中很典型的一例发生在一次摘要任务上。当时奖励函数里包含了一项“摘要必须涵盖原文所有要点”,模型很快学会了把所有句子和关键实体原封不动地堆进摘要里,硬凑出了一个超长文本。结果奖励分甚至比正常摘要还高,但人工读到根本没法用。我自己亲眼见过这类现象后,从此对reward设计变得非常谨慎。

几个缓解手段我逐个尝试过:

  • 对输出长度做惩罚或加硬截断
  • 奖励函数里的每一项都加饱和上限
  • 在训练中动态采集模型输出做人工审查,发现异常即刻干预
  • 多个奖励项之间做加权融合,避免单一信号主导

另外,定期检查训练中的生成样本特别重要,别只看reward曲线。我训练时每隔几百步就随机打印几条生成结果,肉眼过一遍,这比任何指标都更能帮助发现问题。这是我个人强烈推荐的习惯。

3.6 奖励尺度与归一化

奖励数值的尺度在RL里是个隐藏大坑。同一个奖励函数,如果不做归一化,不同batch之间的奖励波动会非常大,导致优势估计不稳定,训练剧烈震荡。

我在RL里几乎都会做“相对标准化”——对同一批rollout里的奖励做z-score归一化,而不是直接用绝对数值。这个做法尤其在GRPO里有效,因为组内天然存在比较关系。

另外,如果奖励取值范围跨度过大,比如有些任务轻而易举得1.0,另一些任务拼死拼活只有0.2,最好给奖励做分位数变换或clip。我试过把奖励从原始分数变为组内排名映射,虽然丢失了一些绝对尺度信息,但训练稳定性和最终效果都更理想。

4. 训练稳定性的排查与避坑实录

4.1 Loss不降的常见原因

训练到一半发现loss像块铁板一样完全不动,这种事我遇过不下五次。最常见的几种原因基本集中在这里:

  • 学习率过低:SFT初始化到1e-7这种量级,loss当然像原地踏步。
  • 数据问题:不同batch里数据难度差异巨大,模型直接被“极难样本”拖住了收敛。
  • 权重初始化异常或某个模块被意外冻结。
  • 奖励尺度太小:RL里reward如果一直在0.1和0.2之间浮动,优势估计信号微弱,模型几乎学不到东西。

我排查的顺序固定统一:先看数据batch里有没有脏数据,再看学习率,再看梯度范数。梯度范数是很多训练异常的第一信号源,如果梯度范数一度变成NaN或爆炸,那模型参数大概率已经受损了。

4.2 Loss反升和训练崩溃

Loss不降的同时,出现在训练中段突然上升,这种往往更刺激。我遇到过的几个case分别对应:

  • 某个batch里包含异常长文本,seq长度超过合法范围,导致attention计算混乱
  • 学习率warmup结束后直接跳到过高的值,模型一下跨出了稳定区
  • LoRA和基座混合精度不一致,导致数值误差累计
  • RL中奖励出现极端值,反向传播时梯度直接冲爆

其中一个特别容易被忽视的是“混合精度里的inf”。我排查多次后发现,某些层在fp16下中间值溢出,尤其是rope和attention score的计算阶段。后来我把关键计算塞回fp32,traning才稳下来。如果你的训练和eval之间经常出现莫名其妙的不一致,值得顺着这个方向检查。

4.3 灾难性遗忘与通用能力衰退

模型在SFT或RL之后,对话能力可能变好,但原有的一些通用能力(比如代码能力、常识问答)出现回退,这就是灾难性遗忘。

我常用的缓解策略按优先级排序:

  • 在训练数据里混入一小部分“通用能力保持集”,比例3%-10%不等。
  • 做RL时把KL系数调高一点,别让策略模型离参考模型太远。
  • 定期在通用benchmark上做回归测试,遇到下滑立刻回滚。
  • 在多任务训练时,尽量保证每个任务的loss权重均衡,避免某个大语料把其他能力“淹没”。

我还有个经验:通用能力回退的发生风险在RL后期会比SFT更大,因为KL约束只对参考模型负责,而参考模型本身毕竟也是SFT版本,它的通用能力本来就不如基座。所以想真正保住能力,最好以基座模型为参考的广义约束用起来,或者更稳妥的方式是“边训练边抽检基座benchmark”。

4.4 显存优化和Batch的策略

训练大模型,显存永远是绕不开的话题。LoRA已经是大救星,但在RL里还有更多开销(策略、参考、RM可能同时加载)。我常用的显存节省方案:

手段收益注意点
梯度检查点显著减少激活显存略微增加训练时间
卸载优化器状态省下一大块显存训练速度下降
混合精度训练显存减半且速度提升注意掉精度,关键层需要fp32
小batch + 梯度累积突破单卡上限累积步数不要太大,否则bn层统计不准

Batch size的调参策略在RL里也有讲究:rollout的batch size决定了一次要生成多少样本,直接影响奖励估计质量;而ppo-update的batch size决定了每次用多少数据去更新策略,两者不是同一个概念。很多人把这两个batch弄混,导致训练行为怪异。我自己一般rollout batch参考采样多样性来定,更新batch则取rollout的一小部分多次迭代。

5. 评估与迭代:怎么判断模型真的变好了

5.1 评估集与基线对照

在启动昂贵的RL之前,一定要先建好评估体系。我习惯是做一个固定评测集,里面除了常规的准确率,还要人工标注一些开放回答的质量分。评估集规模不需要太大,一两百条质量高的case就够,关键是能稳定反映能力变化。

每次训练后都把新checkpoint和上一版拿出来对比生成,最好还是人工盲测。我踩过太多“指标涨、实际烂”的坑,这里想特别提醒:越是分数看起来光鲜的模型,越要经人工这关检验。指标可以骗人,但人工感受骗不了人。

5.2 自动化评估与采样的取舍

自动化评估(比如用大模型当裁判给另一个模型的输出打分)已经越来越普及,我自己也在用。这里有两个经验:

  • 裁判模型最好和被测模型不是同一个基座体系,如果同族太明显,裁判会偏好同族模型的长相和风格。
  • 打分维度不要只给一个总分,要拆成相关性、信息量、可读性、格式等分项,分项比总分更敏感,能帮你定位问题方向。

但在真正做上线决定前,人工评估永远不可替代。我会在自动化筛过后,把所有候选模型和基线模型放在同一个页面上盲选,这是最有说服力的步骤。

5.3 多轮迭代的节奏

模型微调很少有“一把成”的,更多时候是个多轮迭代过程。我的一般节奏是:

  • 第一轮:建立baseline,拿到靠谱的评测分数
  • 第二轮:集中修最大的bad case类别(通常是数据质量问题)
  • 第三轮:调RL偏好方向,比如更简洁、更稳、更准确
  • 第四轮:回归测试,防通用能力下降

每一轮之间的间隔尽量控制在一周以内,太久会丢失上下文。训练日志、数据版本、评测结果我全都记录在案,这习惯在后期排查问题时会救你命——我回头翻日志时经常发现有意义的规律:哪个数据版本、哪个超参组合、哪个评测结果——这些信息一旦缺失,迭代起来就是一团黑。

5.4 数据版本管理

说到版本管理,我想单独提一嘴。

我的经验是数据文件本身就带版本号,比如v1_sft_20250115.jsonl,每次修改都另存新版本,不要覆盖旧文件。因为训练到中途可能发现数据有问题,回退到旧版本重跑是常有的事。同理,RM训练数据、RL训练策略、甚至prompt模板,都建议保留历史版本。存这些不会多费多少空间,但它们既是复盘的依据,也是和团队其他成员协作时的重要参照。

6. 我压箱底的经验:几个容易忽略的细节

最后这部分我不想再做系统性的总结,就随便聊聊几个我实际训练中的“体感经验”。

第一个就是训练卡的时候去读log。现在很多训练脚本的log密密麻麻,我见过不少同学训练跑飞了都不知道怎么看。我的建议是:锁定一个你关心的指标(loss、reward、grad norm、KL),写个简单的过滤命令去实时跟踪,而不是打开一墙日志去找。好的判断节点是每100步看一次生成样例和梯度范数,这些比loss更能反映模型真实状态。

第二个是关于实验记录。我后来学乖了,每次训练前会写一个实验记录文件,内容包括数据版本、基座版本、学习率、LoRA配置、KL系数、用的奖励函数、当时想验证的假设、上一版结果对比。这个记录强迫我在每轮实验前先想清楚“要验证什么”,而不是闷头乱跑。看下来,我几乎一半以上的有效改进都来自这种“预设假设再实验”的流程。

第三个让我印象最深的是关于模型的“话风”。模型在RL之后,分数可以涨得很快,但“话风”往往会悄悄变掉。比如原来礼貌克制,强化后变得过于激进或过度自信,这种变化指标上很难显现。所以我每次RL训练完都会找不同风格的测试人员来“聊”一轮,确认话风是否还符合产品定位。

最后再分享一个所谓的“冷门”心得:训练模型这件事,数据处理和评估花的时间才是大头。真正训练的时间往往只有一小半,超参调来调去带来的收益,不如把数据清洗干净、把评估维度设计合理带来的收益大。这不是我在讲漂亮话,而是我一次次对比下来最真实的感受。

如果你正打算开始自己的SFT或RL项目,我的建议是从一个小而完整的闭环做起:小数据集、小模型、一轮完整流程跑通,感受一下loss、reward、生成质量之间的关系,再逐步放大。这比一开始就堆大数据、大模型,更能在调试中积累到有价值的直觉。

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

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

立即咨询