做LLM基础研究做到第四个核心专题,我反而觉得post-training(后训练)才是真正决定一个模型能不能落地、好不好用的主战场。前几个系列聊了预训练数据、模型架构和训练稳定性,不少同学以为基座模型训完就万事大吉,真拿一测就露馅:模型确实会续写,但让它按指令回答问题就答非所问,对话格式完全混乱,关键场合的安全拒答更是一塌糊涂。post-training解决的正是这“最后一公里”——把只会做文本续写的基座模型,变成听得懂人话、答得得体、知道什么该说什么不该说的可用模型。
这篇文章是我把后训练从数据到算法到评估完整跑过几轮之后的经验总结,也延续了本系列一贯的实操风格。全文覆盖数据准备、核心算法选型、训练稳定性排障、效果评估四大部分,适合正在做模型微调、对齐方案选型,或者想系统搭一套后训练pipeline的同学来读。我尽量把每个关键选择背后的“为什么”讲清楚,而不是只丢一堆结论。
1. Post-training到底在解决什么问题
1.1 从预训练到后训练:为什么基座模型不能直接给用户用
预训练阶段的目标是“下一个token预测”,模型从海量文本里学的是统计规律和世界知识,但它并不知道人类是怎么对话的。你给基座模型发一句“请把这段话翻译成英文”,它不会乖乖翻译,而是大概率继续往下编一段“请把这段话翻译成英文”之后最可能出现的内容,或者直接开始扯无关话题。这不是模型笨,而是它的训练目标里压根没有“理解指令并给出合理回应”这件事。
另一个问题是安全与价值观对齐。基座模型没有经过任何“什么能说、什么不能说”的训练,用户在对话里丢一个恶意问题,它可能真的会认真回答。预训练语料里既有正面的内容也有负面的内容,模型学到的只是“这些文本经常一起出现”,而不是“遇到这类问题我应该拒绝”。
我用一个不太严谨但很好理解的类比:预训练培养的是一个知识量极其渊博但完全不会社交的书呆子,脑子里的知识是有的,但一开口就让人尴尬。post-training就是给这位书呆子做社交训练、职业培训、价值观塑造,最后才能放出去面对真实用户。
1.2 后训练的技术版图:SFT、偏好对齐与持续优化
post-training不是单一技术,而是一整套方法组合。现在业界普遍把后训练拆成三个层次:
第一层是有监督微调(SFT,Supervised Fine-Tuning),用人工写好的“指令-回答”对让模型学会对话格式和基础响应能力。这一步解决的是“会不会说人话”的问题。
第二层是偏好对齐,主流方法包含RLHF(基于人类反馈的强化学习)、DPO(直接偏好优化)及其各种变体。这一步解决的是“回答得好不好”的问题——同一个问题可能有一万个合法回答,但人类更喜欢其中某些风格,比如更严谨、更简洁、更乐于助人。
第三层是面向具体场景的持续优化,包括领域知识注入的继续预训练、上下文长度扩展、工具调用能力增强、角色扮演风格定制等等。这一层通常要回到前面两层循环去做。
整个后训练工程往往要走好几轮:SFT一个版本,对齐一个版本,评估暴露问题,再补数据回去迭代。我在实际项目里经常遇到团队把SFT和DPO当成“跑一次脚本就完事”的流程,结果效果不稳定,本质就是没有把后训练当作一个持续迭代的系统来经营。
2. 后训练数据怎么准备:指令与偏好的双轨构建
2.1 指令数据:质量门槛与清洗方法
SFT的数据形式很简单,就是一组组的“指令-回答”对。实际落地时,一条合格的SFT样本应该包含清晰的任务描述、完整的上下文(如果有输入材料)、以及一个高质量的回答。
{ "instruction": "请总结下面这段产品描述的核心卖点", "input": "这款智能台灯支持无级调光和定时关灯,灯头可旋转180度,内置光传感器", "response": "核心卖点有三点:无级调光可适配不同场景;定时关灯省去手动操作;180度旋转灯头灵活调节照射方向。" }看起来简单,但这里的坑比表面深得多。我见过最多的错误是数据来源不可控。很多团队直接爬公开数据集拼出几十万条数据,完全不筛,拿到手就开训,结果模型学会了“答非所问”和“一句话反复说”。
我在项目里坚持一套指令数据清洗流程,按顺序执行:
- 精确去重和模糊去重,MiniHash加SimHash都做一遍,重复数据会让模型对某些格式化回答过度自信,挤压多样性。
- 规则过滤:筛掉回答过短(少于20个字)、包含乱码或异常重复标点、含URL堆积、语言标签错乱的数据。
- 长度分布检查:如果某类回答普遍超过800字,而任务本身只需要一两句话,这类样本会导致模型倾向长篇大论,后面评估时你会发现模型回答越来越啰嗦。
- 模型辅助过滤:用一个已经比较强的模型给候选回答打分,把正确率低、存在幻觉风险的样本剔除。这一步不能省,特别是在用合成数据扩充样本量的时候。
关于数据量,很多刚入门的同学有个误区,认为SFT数据越多越好。实际上在SFT阶段,几万条高质量数据的效果通常好过几十万条噪音数据。我做过对照组:同一个7B规模基座模型,用5万条清洗干净的数据,效果明显优于15万条未清洗的公开数据混合体。数据质量是后训练成本收益比最高的投入点,没有之一。
2.2 偏好数据:chosen与rejected怎么来
偏好数据是对齐训练的燃料。一条标准的偏好样本长这样:
{ "prompt": "周末想去郊外徒步,请推荐一条不适合新手的路线", "chosen": "由于您提到新手身份,建议先选择成熟步道积累经验,贸然挑战高难度路线存在安全隐患……", "rejected": "可以走从山脚到山脊的野路,全程没有补给点,需要自己带足水和干粮。" }构建偏好数据,核心是明确“什么算好、什么算差”。我一般把标注标准拆成四个维度:正确性(事实有没有错)、有用性(是不是真正回应了用户诉求)、安全性(有没有触碰安全红线)、表达规范性(结构是否清晰、是否冗余)。标注人员必须先对齐这四条标准,否则100个人能标出100个样子的chosen。
还要特别注意处理“两个回答都不好”的样本。我一开始犯过这个错误,把两个同样糟糕但相对好一点的回答强行组成pair,结果DPO训练时模型被逼着在两个错误选项之间做区分,学到的全是无意义的偏好噪声。后来我把标注规则改成:只有其中一个回答明显优于另一个时才保留,两个都不行就丢弃或者让模型重新生成。
偏好数据的来源我常用的有三条:人工标注记录、模型多候选生成后人工排序、以及用强模型做AI反馈初筛后再人工复核。第三条最省人力,但要注意AI反馈会引入强模型自身的偏见(比如偏好特定格式、特定措辞),所以严格来说AI初筛后的数据必须有人工抽检环节兜底。
2.3 数据配比与去重:防止模型学傻
数据配比是我每次做后训练都会反复调的东西。指令数据类型至少应该覆盖通用问答、代码、数学推理、中文特定场景、结构化写作、安全拒答这几大类。任何一个类别占比超过50%,模型都会朝那个方向“长歪”。
我习惯在SFT阶段做一个简单的类别抽样审查:把训练数据按任务类型打标后统计分布,再人工抽查每类20条样本,重点看回答风格是否均匀。如果发现代码类回答基本都是“直接给代码不解释”,而真实使用场景需要“先给思路再给代码”,就要调整这类数据的回答结构。
去重对偏好数据同样重要。同一个prompt多次出现,即使chosen和rejected稍有不同,也会让模型对这几个稀有样本过度拟合。我处理过的一个案例是某次DPO训练时模型突然对某个领域的所有问题都固定以“首先我需要确认您的问题”开头,排查后才发现是训练集里这个领域的偏好数据重复率超过30%造成的。训练前跑一轮去重,真的能省后面好几天排障时间。
3. 核心算法拆解:SFT、RLHF/PPO与DPO怎么选
3.1 SFT:先让模型学会“说话”
SFT的原理不复杂:拿预训练好的基座模型,在“指令-回答”对上继续做交叉熵损失训练,让模型学会在给定指令时输出符合预期的回答。但细节里有个关键点常被忽略——损失函数要只在回答部分的token上计算,指令部分的token不应参与损失回传。
# 核心代码示意:对非回答部分的 token 做掩码 labels = input_ids.clone() labels[~is_response_mask] = -100 # -100 表示忽略该位置的损失 loss = F.cross_entropy( logits.view(-1, vocab_size), labels.view(-1), ignore_index=-100 )这么做的原因很实际:我们不想让模型去预测“用户会问什么”,那是不可控的;我们只希望模型学会“面对这个指令时该怎么回答”。如果让指令部分的loss也参与回传,模型会把注意力分散到指令格式拟合上,反而削弱回答质量。
SFT训练的超参数我一般这样设:7B规模模型学习率1e-5到5e-5之间,用cosine衰减加3%左右的warmup;训练轮数控制在1到3轮,超过3轮基本就会过拟合并损失泛化能力。还有一个容易被忽略的点是对话模板的一致性,训练时用的user/assistant分隔标记必须和推理时完全一致,否则模型在线上表现会明显下滑。
3.2 RLHF/PPO:用奖励信号逼近人类偏好
SFT解决的是“模型能给出符合人类基本预期的回答”,但人类偏好远不止“格式对”这么简单。RLHF的思路是训练一个奖励模型打分,再用强化学习让策略模型最大化这个分数。
奖励模型的训练本身就是一个常见项目:输入prompt和候选回答,输出一个分数,用pairwise排序损失优化,让chosen得分高于rejected。这一阶段的核心是防止奖励模型过拟合到标注样本上,训练集和验证集要严格控制划分,否则奖励模型给出的是“背答案”式的分数。
PPO阶段更像在走钢丝。完整的PPO循环里同时有四个模型在运转:策略模型、价值模型(critic)、奖励模型、参考模型。策略模型每生成一批回答,奖励模型打分,同时用KL散度惩罚策略模型偏离参考模型太远,防止它为了拿高分而输出反常文本。
这里最经典的问题就是reward hacking。我在实际项目里见过策略模型为了刷奖励分,反复在回答里堆砌“非常抱歉,作为一个人工智能助手”之类的空话,因为奖励模型对这类措辞给出了偏高分数,模型很快发现了这个漏洞。应对手段我常用的有三个:调高KL惩罚系数、给奖励分数做缩放和裁剪、以及对生成结果做语义多样性约束。坦率讲,PPO的调参面很宽,效果上限高但方差也大,需要投入不少工程资源。
3.3 DPO及其变体:轻量级对齐的崛起
DPO之所以流行,是因为它把RLHF里显式的奖励建模和强化学习循环简化成了“一步到位的偏好优化”。DPO的核心洞察是:最优策略和奖励函数之间存在闭式解,所以可以直接用偏好数据构造一个分类式损失,让策略模型自己学出隐式奖励。
L_DPO = -log σ( β * ( log(πθ(y_c|x)/π_ref(y_c|x)) - log(πθ(y_r|x)/π_ref(y_r|x)) ) )公式里β控制模型偏离参考模型的强度,β越大,模型对chosen和rejected之间的差异越敏感。在项目里我一般把β初始值设为0.1到0.5之间,再根据训练后chosen的生成概率调整。
DPO最大的优点是训练稳定、显存开销低,不需要奖励模型,不需要四模型联动的RL循环,一个普通微调框架就能跑。对数据量要求也更宽松,几千对高质量偏好数据就能让模型有明显变化。缺点是它没有显式奖励信号,面对特别精细的安全约束或者多目标对齐时,控制力不如RLHF。
后来出现的ORPO进一步简化,把SFT和偏好优化合并在同一个损失里,不需要参考模型。KTO则支持无配对数据的偏好学习,适合偏好数据不完整的场景。我自己项目的选择逻辑是:资源有限且偏好数据在1万对以内,优先DPO;对安全合规要求极高、需要精细控制,才上PPO;如果连SFT都想省一轮,可以试试ORPO。
3.4 方法对比与选型建议
我把几个主流方法的对比整理成一张表,方便按项目条件快速选型:
| 方法 | 是否需要奖励模型 | 训练成本 | 稳定性 | 典型适用场景 |
|---|---|---|---|---|
| SFT | 否 | 低 | 高 | 让基座模型学会对话格式与基础能力 |
| RLHF/PPO | 是 | 高 | 偏低 | 精细安全对齐、多轮目标优化 |
| DPO | 否 | 中低 | 较高 | 偏好数据有限、求快求稳的中小型项目 |
| ORPO | 否 | 低 | 较高 | 希望SFT与对齐合并、节省流程的项目 |
| KTO | 否 | 中低 | 较高 | 只有单边反馈数据或数据不完整的场景 |
我的看法是,不要为了“技术潮流”去选方法。如果你连一套能稳定运行的后训练数据管线都没有,先用SFT把交互质量跑通;当SFT结果明显出现“模型知道答案但表达让人不舒服”的问题时,再引入DPO;最后如果业务方反馈的安全问题仍然控制不住,才考虑PPO那套重武器。方法贵在匹配资源,而不是越复杂越好。
4. 真实训练实操记录与避坑指南
4.1 训练超参与LoRA/全参的选择
进入训练环节,第一个要拍板的是全参数微调还是LoRA。全参数微调效果好但显存和训练时间开销大,LoRA在资源有限时是一个很好的折中。7B模型用LoRA我通常选rank 32,如果想要更强的下游能力提升可以上到64,但rank超过64后边际收益递减,反而增加过拟合风险。
一份我常用的SFT训练配置大致如下:
train_config = { "model": "base_7b", "fine_tune_mode": "lora", "lora_rank": 32, "lora_alpha": 64, "learning_rate": 2e-5, "lr_scheduler": "cosine", "warmup_ratio": 0.03, "per_device_batch_size": 2, "gradient_accumulation_steps": 32, "max_length": 2048, "num_epochs": 2, "gradient_checkpointing": True, }这里batch_size我没直接写死,而是算有效批量大小:2×32 = 64条样本。SFT阶段有效批量一般取64到256条,DPO阶段可以适当小一些,过大的batch会让偏好信号被平均稀释。梯度检查点必须开,不然7B模型的LoRA训练显存也会吃紧。
实操中我还坚持一个习惯:训练开始后先跑100步,看一眼初始loss是否符合预期。如果SFT的loss一开始就异常低,多半是数据里存在和基座模型原本输出几乎一样的“作弊样本”;如果loss完全不动,检查是否把labels掩码设错了。别等跑完整个epoch再回头看曲线,浪费时间。
4.2 训练不稳定:Loss飞升、NaN与reward hacking
后训练最常见的训练事故我按频率排个序:训练中途loss突然飞升、loss变NaN、以及PPO里的reward hacking。
Loss飞升我遇到最多的是SFT阶段,排查顺序是:先看是不是那一轮的batch数据有问题——把最近一个batch的样本单独打出来检查,十次里有七次是某条样本格式异常(比如instruction字段为空但response超长)。然后是学习率过高,cosine调度下后期步长太大也会引发现象级波动。最后才是框架层的数值问题。
NaN问题在LoRA+fp16训练里比较典型。我的经验是给需要稳定的层开bf16而不要无脑全量fp16,同时把loss缩放打开。如果NaN还是出现,检查数据里是否有异常长的序列,max_length截断后仍然存在的极端长度会导致注意力计算溢出。
DPO阶段则要留意一个特殊信号:loss不应无限趋近于0。DPO的理想结果是chosen和rejected之间拉开合理差距,但如果你看到loss已经贴到0附近,说明偏好对之间区分度过高或者出现了数据泄漏,模型在“背答案”。正常DPO训练中,loss会下降但保持在一个温和的区间,同时chosen/ rejected两条生成概率曲线会逐渐分离,这才是健康的状态。
判断DPO训练健康的简单标准: chosen生成概率持续上升,rejected生成概率持续下降, 两者差距稳定扩大,且loss不是骤降到0。4.3 过拟合与灾难性遗忘:别把模型练成复读机
后训练阶段模型体量小、数据量少,过拟合来得比预训练快得多。最明显的信号是训练loss接近0,但评测集上的效果不再提升甚至下滑。这时的模型通常表现为“样本内的格式化复读”:遇到训练集同主题问题就背答案,遇到稍有变化的问题就答非所问。
灾难性遗忘是另一个大坑。SFT和DPO只拿特定分布的数据训练,很可能把基座模型原有的通用能力覆盖掉。我最怕看到的线上现象是:模型的代码能力在SFT之后明显下降,甚至通用常识问答变差。解决办法有两个方向:一是在后训练数据里混入一定比例的通用数据(比如公开的高质量多领域指令数据),二是训练过程中持续跑一组通用基准做实时监测,一旦通用能力掉幅超过预设阈值就停止当前训练轮次。
还有一个很小的细节但影响很大:回答长度分布。SFT数据里若长回答过多,模型会变得越来越啰嗦,这在后续用大模型评测时还会被“长度偏好”进一步放大。我在数据准备阶段就会把回答长度分布拉到接近真实使用场景的比例,训练完再用一句“请用50字以内回答”之类的约束去做抽检,能很快暴露这个问题。
5. 后训练效果怎么评估才算靠谱
5.1 评估指标组合:自动基准+LLM评测+人工抽检
后训练的效果评估不能只看单一指标。我每次迭代至少开三路评估:第一路是标准自动基准,测模型的基础能力有没有退化,比如通用知识、数学推理、代码生成这些公开benchmark;第二路是针对指令跟随和开放域对话质量的评测,让一个能力较强的大模型当裁判,对“帮助性、正确性、安全性、表达质量”分别打分;第三路是人工抽检,找三到五个不同背景的人,对模型在真实场景里的回答做盲评。
很多人只跑第一路,觉得benchmark分数没掉就是成功,这是我在项目里踩过的坑。benchmark分数不降只能说明模型没变傻,不能说明模型变好了。真正体现后训练效果的指标往往是开放域对话质量、安全拒答准确率、以及“用户问题被真正解决的比例”。这三项靠自动基准很难测准,必须靠后两路评估补齐。
LLM评测的具体做法我也会固定一套提示词模板,要求裁判先给出理由再出分数,避免直接给分导致评分理由无法追溯。人工抽检的数量不用多,但一定要坚持每个版本都抽,哪怕只抽50条,也能第一时间发现裁判评测看不见的风格问题。
5.2 评估里的坑:长度偏见、位置偏差与数据污染
LLM当裁判虽然省人力,但对齐裁判的偏好和人类真实偏好需要额外小心。第一个坑是长度偏见:裁判普遍给长回答更高分,即使长回答里有大量废话。这会导致模型在DPO阶段被鼓励输出冗长内容。解决手段是在评测提示词里明确“精炼加分”,并统计裁判打分与回答长度的相关系数,如果相关性过高就换裁判模型或者调整评测模板。
第二个坑是位置偏差:如果两个回答的展示顺序固定,裁判会倾向选择排在前面的那个。我一般会让同一对回答正反序各评一次,两个顺序都有同向偏好才认定为有效胜出。第三个坑也是老生常谈的:数据污染。公开benchmark的题目几年没更新,后训练数据里很容易混入测试集的原题,分数虚高到无法参考。我每轮迭代都会单独留出一批新出题、且确保不进训练管线的评测集,作为唯一信任的指标。
6. 我把这几轮后训练做完后的真实体会
如果让我重新做一遍post-training,我会把顺序调成:先搭评测,再收数据,然后才碰训练脚本。当初我为了赶进度先跑训练,评测集拖到最后才准备,结果模型训完不知道改还是不改,连“这个版本到底比上个版本好在哪”都说不清楚。评测前置不是繁琐,是你的导航系统。
数据清洗带来的收益远比换算法更明显。我做过最成功的一次迭代,只是把SFT数据里的劣质回答替换成重写后的高质量回答,模型在开放域评测的胜率就提升了十多个百分点,而那次算法上根本没有变动。所以如果你的效果卡住了,先怀疑数据,其次才是方法。
最后还有一点经验:后训练不是一次性的“炼金仪式”,而是一个要持续迭代的工程系统。基座模型在升级,业务需求在变化,你的数据分布也要跟着演进。把数据管线、评测管线、训练脚本做成可以快速重复跑的标准流程,比任何一次性的“神奇调参”都更有价值。训练过程中记得时刻保存checkpoint并且完整记录每一轮的参数和数据版本,某次实验失败时能快速回滚到上一个稳定版本,这能救你无数次。
后训练的门槛不高,但做扎实的细节很多。希望这篇总结能帮正在搭或者准备搭后训练流程的同学少走几条弯路。后面这个系列我还会继续拆解评估指标的构建细节和长文本能力的后训练方案,到时候再回来聊。