2025年还在纠结LLM Post-Training怎么做的团队,大概率已经被基座模型的“难用”折磨过好几轮了。预训练阶段大家拼的是算力和数据规模,但等模型真正从训练集群里出来,你会发现它只是一个“文本接龙机器”——知识是有的,行为完全是野的。让它写代码,它给你输出一篇散文;让它解方程,它跳过中间步骤直接报答案;更别提让它遵循“只输出JSON”这种基础要求,它能把注释都塞进JSON里。这个阶段如果你不做Post-Training,也就是对齐和微调,那前面预训练投进去的钱基本等于白烧。
这篇文章,是我自己带队做后训练的实际经验总结,把2025年中这个时间点上,一支中型团队能落地、不太吃运气、也不依赖天价算力集群的LLM Post-Training完整方案拆开讲一遍。内容包括数据怎么筛怎么做配比、SFT阶段怎么调参、DPO和GRPO怎么选型、评估集怎么设计才不被骗、训练稳定性怎么保证。适合正在训练自家模型、或者被开源基座的“行为问题”折磨的算法工程师和研究员。全文不堆数学公式,重点是可复现的配方和筛选思路,以及那些文档里不会写、只有跑过才知道的坑。
1. 后训练与预训练的本质差异:模型有知识,不等于会用知识
1.1 预训练模型的本质:一个非常优秀的“接话者”
先想清楚一件事:预训练阶段模型学到的,是“在给定上文之后,哪一个token出现的概率最高”。这个目标决定了它天然擅长续写、补全、模仿风格,但不擅长“满足用户意图”。所谓Post-Training,本质就是要把这个“接话者”改造成“任务执行者”。
我见过很多第一次接触大模型的同学有个误解,觉得基座模型已经“什么都会”,只是“没调教好”。这个说法对了一半。基座模型在百科、代码、常识这些分布密集的领域确实具备很强的知识,但它输出内容时几乎没有“服从性”概念——它不知道什么时候该停,不知道什么时候该按固定格式输出,也不知道面对冲突指令时该偏向哪一方。这些都是预训练阶段的目标函数里根本不存在的信号,必须通过后训练注入。你可以把预训练想象成给一个人灌了二十年书,但他从来没被要求“回答一个问题”;后训练才是教他在真实对话里“好好说话”的那一步。
1.2 2025年年中主流的后训练流水线长什么样
到2025年中,各家开源的训练方案虽然细节不同,但骨架已经高度收敛,大致是四步:
- 可选:领域继续预训练(Domain Continue Pretraining),把行业语料补充进基座,让模型先“见”足够多的领域文本,再去做指令对齐;
- SFT(监督微调),用高质量的人类示范数据教会模型“怎么回答一个具体问题”;
- 偏好对齐,常见的是DPO或者GRPO,让模型在多个候选答案之间学会“选更好的那个”;
- 评估与迭代,跑评测集、抽检badcase、再补数据,循环往复。
第四步经常被写论文的人忽略,但它恰恰是实战里耗时最长的环节。一个完整的后训练周期,数据准备往往占掉60%以上的时间,SFT和偏好对齐的训练时间反而不长。这和很多人的直觉是相反的。另外提醒一句:这四步不是必须全做。如果预算和人力有限,SFT加一套靠谱的评估就足够解决大多数“基座太野”的问题,后面的DPO和GRPO属于锦上添花。
2. 数据环节的功夫在诗外:SFT与偏好数据的真实门槛
2.1 SFT样本的“难度阶梯”:从低垂果实到硬骨头
SFT数据的质量直接决定后训练的上限。我看到过不少团队急于求成,把网上爬来的指令数据简单清洗一下就丢进训练集,结果模型确实变“听话”了,但也变“笨”了——因为它学了很多低质量的低级问答。我的经验是把SFT样本分成三个难度档位,按阶梯式比例混入:
- 第一档:常规指令问答(占比60%左右)。要求格式规范、答案明确,例如“用一句话解释什么是阻尼振动”。这一档负责教会模型基本的对话礼仪和格式服从。
- 第二档:多步骤任务(占比25%左右)。包括代码生成、表格处理、多条件约束的文案改写等。这一档负责教会模型“先思考再动手”,输出结构完整的中间过程。
- 第三档:高难度推理(占比15%左右)。包含数学证明、逻辑链分析、复杂调试。这一档不在数量,而在质量,宁可少也要保证每一步推导都是对的。
有同学会问,这个比例怎么来的?说实话没有严格理论支撑,更多是经验筛选出来的。我自己的体会是:如果基座本身推理能力弱,第三档适当提高;如果基座已经很强、只是“不听话”,那就把第一档拉高,重点治“服从”。每一档数据里还要做一次去重,特别是网上爬来的数据,很多是同一段话换了个问法反复出现,不去重会让模型在某个区域严重偏科。
2.2 偏好数据的构造:win/tie/lose之外的隐形标准
到了偏好对齐阶段,数据形态变成了“同一问题给多个答案,告诉模型哪个更好”。这里有一个经常被忽视的点:对比数据里的“差答案”比“好答案”更重要。原因很简单,偏好学习的本质是让模型学到“好答案和差答案之间的边界在哪里”。如果差答案太差(比如明显是乱写的),模型很容易学会;如果差答案只是“还行但与好答案有细微差距”,模型才能学会真正的精细判别。
我在构造偏好对时会故意混入三种负样本:明显错误型(格式错、事实错)、部分正确型(步骤对但结论错)、冗长废话型(内容对但绕来绕去)。前两类负责拉低下限,第三类专门用来对抗模型最常见的“话痨病”。
另外还有一组隐形标准大家容易漏:长度偏差和位置偏差。在做人工标注或利用线上反馈构造偏好对时,如果好答案总是更长,模型就会学到“写得越长越好”;如果被拒答案总是排在前面,模型可能学到“别学第一个答案”。所以我在每轮数据清洗里都会专门统计chosen和rejected的平均长度差异,若差值超过10%,就要考虑重新抽样或修正标注标准。
2.3 数据配比经验:领域、通用、安全三者的比例
最后说配比。假设你是一个垂直行业团队,想训练一个领域助手模型,常见的配比是:
| 数据类别 | 占比 | 说明 |
|---|---|---|
| 领域指令数据 | 45% | 行业问答、业务模拟,格式贴近真实线上场景 |
| 通用指令数据 | 30% | 保持模型的通用能力和对话流畅度 |
| 代码与推理数据 | 15% | 维持推理能力,防止领域微调后“降智” |
| 安全与拒答数据 | 10% | 敏感话题拒答、边界声明、多轮纠错 |
这个配比的核心理念是“别把鸡蛋放在一个篮子里”。如果领域数据占比超过60%,模型在你自己的评测集上会很好看,但一通通用能力测试就会发现全面滑坡,这是典型的灾难性遗忘。反过来,如果通用数据占比过高,那后训练的意义就打了折扣。我自己一般会在第一轮用这个比例跑评测,再根据短板微调——连续两轮评测都掉分的类别,就加数据或者调整它在采样里的权重。
3. SFT阶段的超参与工程细节:一份能复现的稳定配方
3.1 学习率、epoch与样本权重:为什么“少而精”有效
SFT阶段,模型从预训练那里继承的知识已经很丰富了,所以这里的目标不是“学新知识”,而是“调整输出行为”。这决定了它的超参风格和从头训练完全不同。以我自己常用的配置为例:
| 超参数 | 推荐值 | 备注 |
|---|---|---|
| 学习率 | 1e-5 ~ 2e-5 | 过大会破坏预训练特征,过小则训练不动 |
| epoch | 1 ~ 3 | 数据量大时1个epoch就够,绝不超过3 |
| batch size | 按总样本数调整,建议256~512 | 用梯度累积模拟大batch |
| 权重衰减 | 0.01 ~ 0.05 | 轻微防过拟合 |
| 学习率调度 | cosine或线性衰减到1/10 | 避免末期参数剧烈震荡 |
| 最大序列长度 | 8192~16384 | 根据业务最长样本定,长序列要packing |
为什么epoch不能大?我见过一个团队把SFT跑了好几个epoch,训练loss降得非常好看,结果线上评测反而变差了。原因就是重复多轮后模型开始“背答案”,把训练集里的细节(包括错误)都记住了,到了新问题上泛化能力反而下降。一般来说,如果SFT数据量在100万条以上,1个epoch足够了;只有数据量很小(比如不足10万条)时才需要2~3个epoch。样本权重方面,如果某一档数据特别少又特别重要,比如高难度推理,可以在采样时给它额外乘一个权重系数,但要控制总权重别超过1.5,否则训练会在这类样本上过拟合。
3.2 Packing、loss mask与数据顺序
SFT阶段工程师最容易忽略的是工程细节,其中最典型的是sequence packing(序列打包)。现在的基座模型大多支持8192甚至更长的上下文,但业务里的SFT样本很多只有几百到一两千token。如果每条样本单独当作一个训练序列,GPU算力会浪费在大量的padding token上。解决方式是packing:把多条短样本拼接成一个长序列,同时在attention和loss计算时通过mask让每段样本“互不可见”。
这里有个关键点:loss mask一定要做对。拼接后的序列里,只有每条样本的答案部分要计算loss,指令部分和padding部分都必须mask掉。如果忘了给指令部分加mask,模型会反复学习“怎么重复指令”,最后表现得像个复读机。我自己排查过的一个线上事故,就是模型回答时总爱先复述一遍用户问题,最后定位到SFT脚本里mask逻辑写错了,把指令部分的loss也算进去了。
另外一个容易踩的坑是数据顺序。混洗策略看起来是小事,但影响很大。我建议把三个难度档位的样本充分混洗后再切分batch,避免同一批全是简单问答导致模型在这一步loss雪崩式下降、下一步又反弹。可以简单记录每一步的loss曲线,如果出现周期性锯齿,大概率是数据顺序问题。下面是一段简化版的packing逻辑示例,仅供参考:
def pack_samples(samples, max_len=8192): packed = [] cur_tokens = [] cur_masks = [] cur_labels = [] for s in samples: # 每条样本: prompt + response; mask: 只有response部分参与loss if len(cur_tokens) + len(s["input_ids"]) > max_len: packed.append({"input_ids": cur_tokens, "loss_mask": cur_masks, "labels": cur_labels}) cur_tokens, cur_masks, cur_labels = [], [], [] cur_tokens.extend(s["input_ids"]) cur_masks.extend(s["loss_mask"]) cur_labels.extend(s["labels"]) if cur_tokens: packed.append({"input_ids": cur_tokens, "loss_mask": cur_masks, "labels": cur_labels}) return packed工程上很多框架自带packing实现,不必自己造轮子,但loss mask的语义一定要自己去确认一遍,别直接信任默认行为。
4. 对齐阶段:DPO还是GRPO,那个绕不开的KL问题
4.1 为什么DPO是绝大多数团队的默认起点
到了偏好对齐阶段,2025年中这个时间点,DPO依然是很多团队的第一选择,原因很现实:它不需要训练一个奖励模型,不需要在线采样,训练稳定,计算开销也比PPO那一类方法低一个数量级。
DPO的核心思路是把“奖励最大化”这个强化学习目标转化为“隐式奖励下的分类目标”:给定chosen和rejected两个答案,让策略模型在参考模型的约束下提高chosen的概率、降低rejected的概率。这里有个超参数beta,它控制模型偏离参考模型的自由度。beta越大,模型越保守,和参考模型越接近;beta越小,模型越激进,越容易被偏好数据牵着走。
我的经验是beta在0.05~0.3之间选,先用0.1跑一个短实验,看模型在评测集上的表现和训练集上的准确率。如果训练准确率涨得飞快但评测集原地踏步,说明beta偏大,模型过拟合到偏好数据上了;如果训练准确率涨得很慢,可以适当调小beta。
DPO有一个被低估的隐藏角色:参考模型。大部分实现里参考模型就是SFT阶段产出的模型副本,用的是同一个checkpoint。但如果你在SFT和DPO之间改过对话模板、加过system prompt,参考模型的输出分布可能已经和策略模型错位了,这时候DPO的训练信号会变得很怪。所以跑DPO之前,务必确认参考模型和策略模型用的是同一套对话模板和tokenizer。
4.2 GRPO那条KL曲线到底在说什么
如果任务对推理能力要求很高(数学、代码、逻辑判断),DPO往往不够用,因为它只能从静态偏好对里学习,没法在训练中“尝试新的解法”。这时候GRPO这类在线强化学习方法就登场了。
GRPO的大致做法是:让当前策略模型对同一个问题采样多个答案,根据规则或判分模型给出每个答案的得分,然后组内比较、计算优势值,推动模型往高分组的方向更新。和传统PPO不同,GRPO不单独训一个critic模型,通过组内baseline来估计优势,显存和实现复杂度都友好很多。
很多人第一次跑GRPO会被那张KL曲线吓到:KL散度一开始猛涨,然后回落,觉得是不是出了bug。其实这是正常的。训练初期策略模型还没来得及收敛,在探索新答案时分布自然偏离参考模型,KL涨;随着训练推进,模型在“收益”和“保守”之间找到平衡,KL回落并稳定在一个平台。重点看的是KL有没有“一路狂飙永不回头”,如果出现这种情况,说明奖励设计有问题,模型找到了钻空子的路径,比如靠格式来刷格式奖励、靠堆字数来刷长度的廉价奖励。
我自己的经验是:GRPO的奖励设计要遵循“少而硬”原则。优先用可自动判定的硬奖励(数学答案对错、代码能否通过测试用例、JSON能否解析),而不是依赖一个可能被刷爆的神经网络判分器。推理任务里,“格式奖励”(要求输出包含“思考”和“答案”两段)是必要的,但它只负责约束结构,不负责评判内容;把格式奖励权重设得太高,模型会学会“把废话包装成思考”,而不是真的思考。
4.3 什么时候应该上“推理强化学习”
2025年开源社区最出圈的一批模型,几乎都在后训练里加入了显式的推理强化学习阶段。但很多团队跟风之后发现效果并不理想,原因不是方法不行,而是“上不上”取决于你的基座能力。
一个比较实用的判断标准是:先用SFT后的模型跑一批数学/代码评测题,观察它对复杂问题的回答过程。如果模型大多数时候能给出“步骤合理但最终答案差一点”的回答,说明它的推理潜质已经在了,GRPO可以把这些“差一点”推成“完全对”。如果模型面对复杂题直接输出胡言乱语,连基本逻辑链都组织不起来,那先不要急着上RL,回去补SFT数据和继续预训练更实在。RL的本质是放大和择优,不是无中生有。
5. 后训练评估:别让单一指标骗了你
5.1 训练loss与真实表现之间的割裂
我见过太多团队在训练结束后兴冲冲地汇报“loss降到多少”,但一上业务评测就翻车。后训练的loss是一个很钝的指标,它只反映模型在训练分布上的拟合程度,完全不反映模型在真实使用场景中的表现。更隐蔽的问题是“评测集过拟合”。如果你为了看到好数字,反复在同一套公开评测集上测试并据此调参,模型的行为会逐渐向评测集偏移,到头来公开榜单分数挺高,业务场景一问就废。这不是玄学,而是因为你无意识地把评测集的分布“泄漏”进了训练决策里。
5.2 评估集的三层设计:域内、域外、对抗
我自己在设计评估集时一定会分成三层:
| 层次 | 构成 | 目的 |
|---|---|---|
| 域内业务集 | 真实业务场景的脱敏样本,从日志里抽,而不是自己编 | 判断“能不能用” |
| 通用能力集 | 公开题库、常规问答、代码题 | 监控通用能力是否退步 |
| 对抗/边界集 | 格式约束、长指令、多轮上下文、误导性问题 | 找行为边界,防badcase |
这三层不是平行的,权重也不同。域内业务集占评估权重的50%以上,因为后训练的最终目标是服务业务;通用能力集作为回归底线;对抗集用来发现“平时测不出来但一上线就炸”的边界问题。我建议对抗集里固定放几类题:要求只输出JSON的题、上下文长度超过训练数据上限的题、包含明显逻辑陷阱的题、以及需要拒绝回答的敏感问题。每轮训练后都跑同一套对抗集,并记录badcase数量。我自己的经验是,如果对抗集的badcase数量没有下降,但域内业务集分数在涨,这通常意味着模型在“讨好”评估分布,而不是真正学会理解用户意图。
5.3 遗忘检测:把“旧能力”纳入回归测试
最后是灾难性遗忘。后训练阶段,尤其是SFT和偏好对齐,都会对模型的原始能力造成一定冲刷。最典型的表现是:领域能力上来了,通用百科知识变差了;指令服从变好了,代码生成多语言能力下降了。要防这个,最有效的办法是把“旧能力”纳入回归测试。
具体来说,每轮后训练之前保留一份固定的“能力基线测试集”——这个测试集在项目开始时就定好,包含数学、代码、常识、多语种等维度的题目,原则上不再改动。每轮训练完跑一遍,把得分和上一轮对比。如果单项得分掉超过5个点,就要考虑是不是数据配比中通用数据太少了,或者训练超参太激进。我自己习惯做一张“能力雷达表”,每轮迭代后更新一次,从图上能非常直观地看到哪些能力在丢失、哪些在涨。不要等上线后再让用户帮你发现“模型变笨了”,那个代价比多花几天时间跑回归要大得多。
6. 训练稳定性的工程细节:能跑完比跑得快更重要
6.1 显存、吞吐与序列长度的平衡
后训练阶段虽然比预训练轻,但也不是随便一台机器就能跑的。以常见的7B到13B规模模型为例,如果用bf16混合精度训练,序列长度8192、batch size加起来大约256条样本,单机多卡(8卡、每卡80G显存)是可以扛住的。但如果模型到70B级别,后训练基本就要上多机并行和更激进的显存优化了。
我的经验是,在后训练阶段不要盲目追求长序列。如果业务样本90%都在2048 token以内,那训练序列设成4096就够用,强行拉到8192只会带来显存压力和更低的吞吐。只有当评估发现“长上下文能力”确实是短板时,再针对性加入长样本并提升max_len。吞吐量优化方面,混合精度、梯度检查点、序列packing这三件套是性价比最高的。开梯度检查点大概会慢20%~30%,但显存占用几乎减半,后训练阶段完全值得。还有一个小技巧:如果数据里有大量重复的system prompt,可以用一些框架的“提示缓存”能力,不过这属于锦上添花,不决定成败。
6.2 checkpoint、日志与事故恢复:跑崩了别从头再来
后训练实验动辄几小时到几天,一旦中途崩了,如果没有合理的保存策略,损失的不只是时间,还有已经花掉的算力成本。我现在的标准做法是:每N步保存一个checkpoint,N一般取总步数的5%左右;同时每保存一个checkpoint就顺带跑一次小规模评测(用固定的200到500条快速评测样本),把loss曲线和快速评测得分画在一张图上。这样即使后期发现某个checkpoint效果最好,也能精准回退到那个点,而不是“最后保存的模型就是最好的”。
日志方面,除了loss和KL这类常规指标,我强烈建议记录每个batch的“数据统计特征”:平均长度、mask有效token占比、chosen和rejected的长度差等。因为很多训练异常不是突然出现的,而是在某一类数据进来后逐渐累积的。有了日志,定位问题时能省一晚上时间。
最后是随机种子。后训练阶段的可复现性往往被低估。我遇到过同一个配置,换了一台机器、一个随机种子,最终评测分数出现1到2个点的波动。所以在项目里指定统一的随机种子,并在实验记录里记录种子值,是保证迭代可比性的基本功。
7. 几个真实踩过的坑和收尾建议
7.1 偏好对采样参数:温度与top-p的“隐形影响”
我在构造DPO训练对时踩过大坑:采样chosen和rejected的生成参数不一致。当时为了让rejected更差一点,我用更高的温度去采样负样本,结果模型学到了一个奇怪策略——它对“高温度风格”的文本产生强烈排斥,连带着正常回答也变短了。后来我把所有候选答案都用同一组采样参数生成(temperature=0.7,top_p=0.9),只看内容质量来分chosen和rejected,问题就消失了。这里的经验是:偏好数据的“难度差异”应该来自内容质量,而不是生成参数。如果你人为制造差异,模型学到的就不是“什么答案更好”,而是“什么风格我不喜欢”。
7.2 别贪多:增量式后训练的节奏
后训练不是“做得越多越好”。我见过团队把SFT、DPO、GRPO全上一个遍,训完模型反而比只做SFT的版本更不稳定。后训练每加一个阶段,都会消耗一部分模型的“余量”,包括泛化余量和行为余量。正确的节奏是:先跑SFT打底,评测;再上DPO修行为,评测;确认有必要再上GRPO强化推理。每一步都确认“这一步确实带来了收益”再往前走,而不是照搬别人的全流程。如果预算只够做一个阶段,那就做SFT加上一套靠谱的评估,这个组合能解决大多数问题。
7.3 最后一点个人体会
如果只让我留一条忠告,那就是:把后训练当成一个“工程问题”而不是“论文问题”来对待。论文里一个方法只要在某个评测集上高0.3分就算有效,但实际做模型的人关心的是它在真实业务里能不能稳定不出错。数据、配比、评估、稳定性,每一环都比“选哪个对齐算法”更影响最终效果。希望这份手册能帮你少走我走过的弯路,至于那些只有跑过才懂的细节,还是得自己上手跑一轮,才能真正变成你的经验。