如果你最近在跑大模型训练的实验,或者正被“预训练、监督微调、奖励模型、强化学习”这一串名词搞得一头雾水,那这篇文章就是写给你看的。这四个阶段通常被统称为大语言模型训练的四个阶段,但大多数资料只是囫囵吞枣地列个名字,没人告诉你它们到底在解决什么问题、数据长什么样、训练时该盯哪个指标、最容易在哪个环节翻车。我会按自己在训练任务里的实际经验,把这条流水线从头到尾拆开讲清楚,顺便把那些常规文档里不会写的坑也一并倒出来。不管你是准备从零训练一个小模型,还是只想搞懂为什么某个模型聊天像个真人、另一个却像个复读机,这篇都能给你一张可对照的全局地图。
1. 先看全景:大语言模型训练到底在练什么
1.1 四个阶段分别解决四个不同的问题
先说结论:这四个阶段不是并列关系,而是一条递进的流水线。预训练解决的是“语言能力和世界知识”的问题,让模型能够流畅地接话、知道大量事实;监督微调(SFT)解决的是“听懂指令、按格式输出”的问题;奖励模型(RM)解决的是“如何评判一段回复好坏”的问题;强化学习(RLHF)解决的是“让模型在反馈中调整自己的行为”的问题。
这四个问题必须分步解决,不能合并。我见过不少新手直接拿对话数据去预训练,结果模型只是把对话背了下来,换个问法就懵。原因很简单:预训练语料和对话语料的分布差异太大,模型在预训练阶段建立的是对自然语言的统计理解,你突然让它去学“用户说A,模型答B”的映射,它只能死记硬背。
打个比方:预训练像是让一个人泡在图书馆里通读海量书籍,形成语感和知识面;SFT是教他学会按题目要求作答,别东扯西扯;奖励模型是给他配一个阅卷老师;RLHF则是阅卷老师盯着他做题,每做一道就现场点评,让他越做越符合评分标准。少了任何一环,最终产物都是偏的。
1.2 一条token在四个阶段里的完整旅程
跟着一条数据走一遍,会比抽象概念直观得多。假设原始语料里有一句话“今天天气不错,适合出去跑步”。在预训练阶段,模型读进去的是一串被切分好的token,它的任务是看到前几个token后预测下一个token。比如看到“今天天气不错”就去猜“适合”,猜错了就更新参数。整个预训练过程就是海量文本里做几万亿次这样的接龙。
到了SFT阶段,这条数据会变成“用户:今天天气怎么样?助手:今天天气不错,适合出去跑步”。模型不再对所有位置都做预测,而是只对“助手”回复部分的token计算损失,指令部分会被掩码掉,目的是防止模型把指令本身也背下来。
到了奖励模型阶段,同样的一个用户问题,会让SFT模型生成多个候选回答,再由人类标注员或AI判定哪个更好。奖励模型要学习的就是“更好”这个偏好信号。最后到了RLHF阶段,策略模型针对同一个问题生成回答,奖励模型给出分数,策略模型根据这个分数调整参数,循环往复。
这条旅程看起来很顺,但每个阶段的数据规模、训练时长、评测方式完全不同,这也是为什么很多人把单个阶段跑通了,却接不住整条流水线。
1.3 分阶段训练背后的根本原因
还有一个常被忽略的点:为什么不能一步到位?最直接的原因是优化目标不一致。预训练的目标是最大化下一个token的似然概率,这个目标造不出“服从指令”的能力,因为互联网文本里多数句子根本不是指令。SFT的目标是模仿高质量回答,但模仿只能让模型学会已有的行为,没法让它理解“什么样的回答更好”。奖励模型学的是偏好排序,但它本身不生成内容。RLHF才是真正让模型根据外部反馈去改变策略的环节。
另一个原因是工程上的考量:每个阶段的失败模式不一样,分阶段才能定位问题。如果模型最终表现很差,你可以通过各阶段评测快速定位是底座能力不够,还是指令跟随没学好,还是奖励信号出了问题。一步到位的方案出了问题根本无处下手。
2. 预训练阶段:从海量文本里学出语言底座
2.1 预训练到底在学什么:文字接龙背后的含义
预训练采用的方法是自回归语言建模,听起来很高级,本质就是文字接龙。给模型一段前缀,让它预测最可能的下一个token;再把预测结果拼回前缀,继续预测下一个。这个循环在训练时被并行化处理,每个位置的目标都是“根据左侧上下文预测当前token”。
就这么一个简单目标,为什么能学到语法、常识、推理能力和大量世界知识?因为语言的天然结构里包含了这些信息。一个句子里的主谓宾搭配,隐藏着语法规则;一段百科文本里的事实性描述,隐藏着世界知识;多步推理类的段落,隐藏着逻辑链条。当模型的参数量足够大、训练数据足够多时,它会把语言中规律性的东西压缩进参数里。
我自己跑预训练时最直观的感受是:loss下降不代表模型“聪明”了,很多时候它只是把高频搭配记牢了。只有当序列长度拉长、数据覆盖度足够广时,模型才会表现出对低频知识和长程依赖的建模能力。所以预训练阶段的关键词不是“效果”,而是“规模”和“稳定性”。
2.2 主流配置与超参参考
预训练阶段的核心配置有几个,我整理成一张表方便对照。
| 配置项 | 典型参考值 | 为什么这么设 |
|---|---|---|
| 序列长度 | 起步2k~4k,后期扩展到8k~128k | 序列太短学不到长程依赖,但太长会大幅增加算力开销 |
| 全局批量大小 | 总token数在几十万到几百万之间 | 太小的batch会让梯度噪声大,训练不稳定 |
| 学习率 | 峰值1e-4量级,配合warmup和cosine衰减 | 预训练初期模型参数随机,大学习率直接崩 |
| 优化器 | AdamW | 权重衰减和自适应学习率的组合更稳 |
| 词元化 | BPE或SentencePiece类方法,词表几万到十几万 | 能平衡序列长度和词汇覆盖度 |
| 梯度裁剪 | 1.0左右 | 防止个别batch中的异常token把梯度炸飞 |
注意一点:上面只是起点,不同的模型规模和数据规模需要重新调。小模型用大学习率可能没事,大模型用同样的学习率可能直接发散。前几轮实验建议先用小规模数据把训练管线跑通,再去调这批参数。
2.3 预训练阶段最常见的两类翻车现场
第一个翻车现场是loss尖峰。曲线本来平滑下降,突然某一步loss跳高,然后又降回来。这种情况绝大多数不是参数问题,而是数据问题:语料里混入了重复文本、乱码文本或者异常长的token序列。我的排查习惯是先看触发loss尖峰那几条样本的原始内容,而不是去改学习率。
第二个翻车现场是“loss很低但能力很弱”。模型在验证集上的困惑度已经降得很低了,但下游任务评测表现平平。这个认知误区要纠正:低困惑度只说明模型拟合了训练集的文本分布,并不代表它具备有用的知识或推理能力。训练数据的质量会直接限制模型能力的上限,光堆算力救不回来。
预训练阶段结束后,先做一轮基础能力评测再进入SFT,不要嫌麻烦。这一步能帮你确认底座到底是“健康的”还是“带病上岗”,否则后面所有阶段的问题都会被底座问题污染,排查起来非常痛苦。
3. 监督微调阶段:教模型学会“听人话”
3.1 SFT的本质是模仿与格式化,不是知识注入
很多刚接触训练的人会误以为SFT就是给模型灌输新知识,这个理解是错的。SFT阶段的数据量通常只有几万到几十万条,这个规模根本装不下太多新知识,它能做的是“重新调整模型的输出形状”。
预训练之后的模型是一个“什么都能接话但不知道如何接指令”的状态。SFT用一批高质量的对话数据教它:当用户以某种方式提问时,你应该用什么样的格式、语气和内容来回应。它更像是在已有能力之上做了一层适配,而不是从零学习。
这个认知会直接影响你对数据质量的要求。如果SFT数据本身事实错误或不自然,模型很快会把这种坏习惯学到。反过来,高质量数据哪怕只有几千条,也能让模型表现出明显的指令跟随能力提升。我自己跑实验时发现,整理数据的时间应该远大于调参的时间,这点在SFT阶段尤其明显。
3.2 数据格式和损失掩码的实操写法
SFT阶段一个最容易忽略但极其关键的细节是:损失只计算在助手回复部分,不计算在用户指令和系统提示上。如果不做掩码,模型会去记忆用户问题,训练时表现正常,推理时却容易复述系统提示。
下面是一个简化的数据构造伪代码,展示掩码逻辑。
def build_sft_sample(system_prompt, user_query, assistant_response): # 把多轮对话拼接成模型实际看到的token序列 input_text = ( f"<|system|>\n{system_prompt}\n" f"<|user|>\n{user_query}\n" f"<|assistant|>\n{assistant_response}" ) input_ids = tokenizer(input_text)["input_ids"] # 默认全用 -100 掩码掉,只保留 assistant 部分计算损失 labels = [-100] * len(input_ids) # 找到 assistant 部分的起始位置,从那里开始计算损失 assistant_start = input_text.find(f"<|assistant|>\n") assistant_start_token = len(tokenizer(input_text[:assistant_start])["input_ids"]) for i in range(assistant_start_token, len(input_ids)): labels[i] = input_ids[i] return input_ids, labels这个写法就是防止模型把系统提示和用户问题当预测目标。没有这一行的数据,训练出来的模型在对话时往往会“话痨”式地重复上下文,非常令人崩溃。
另外,chat template也很重要。不同模型的对话模板不同,SFT阶段必须严格执行统一的模板格式并加上特殊分隔符,否则推理阶段就会出现格式错乱。
3.3 SFT阶段我必须提醒你的几个坑
第一个坑是训练轮数过多导致过拟合。SFT数据量通常不大,模型很快就能把训练loss压到接近0,但此时模型开始背答案,换一种问法就表现崩坏。常见的做法是只训练1到3个epoch,配合验证集观察。
第二个坑是灾难性遗忘。如果微调时的学习率太高,预训练阶段学到的知识会被迅速冲掉。应对手段是降低学习率(建议比预训练峰值低一个数量级),以及在有足够把握前不要盲目加训练步数。
第三个坑是回复长度崩坏。我见过SFT之后模型回答变成了长篇大论,原因往往是数据里长回复比例过高,模型学会了“越长越好”。训练时要刻意让数据集中回复长度分布相对自然,而不是一边倒。
4. 奖励模型阶段:给模型配一个“质量裁判”
4.1 为什么非要多训练一个打分器
到了这一步,我们需要回答一个问题:怎么判断模型生成的内容是好是坏?文本质量、帮助性、无害性这些维度没有天然的分数,靠规则更是死路一条。最可靠的做法是学习人类的偏好:让标注员对多个候选回复排序,然后用这个排序信号训练一个专门的模型来打分,这个模型就是奖励模型。
有人会问:为什么不直接用SFT模型来打分?答案很直接:SFT模型只学会了“如何生成合理的回答”,没有专门学习过“不同回答之间孰优孰劣”这种比较任务。而且在实际标注里,只有对比多个候选时,人类偏好才是稳定的;让一个人给单独一条回答打绝对分,反而更难保持一致。
奖励模型的本质是一个“偏好代理”,它的输出是一个标量分数。训练起来也不复杂,但要做到好用,细节非常多。
4.2 排序数据、成对损失与训练细节
奖励模型的数据形态是:同一个提示词下有两到四个候选回复,标注员按质量排出顺序。最常用的建模方式是把排序转化为成对比较,使用Bradley-Terry模型。
简单说,奖励模型给好的回复打高分,给差的回复打低分,然后用交叉熵拉大两者的差距。损失函数可以写成这样:
def pairwise_loss(good_score, bad_score): # 期望 good_score 明显大于 bad_score import torch return -torch.log(torch.sigmoid(good_score - bad_score)).mean()再补充几个实操细节。奖励模型的输入通常不只有一个回复,而是把提示词和候选回复拼在一起,通过模型得到一个标量输出,这意味着原来模型的输出头(词表大小的logits)要替换成一个单维度的线性层。训练时max length保证足够容纳提示词加回复,batch里尽量让同一提示词的多个候选同时出现,这样模型才能学到对比关系,而不是记住绝对分。
还有一个细节:奖励分数本身不需要有绝对意义,重要的是相对排序。所以训练之后,你在验证集上应该看“排序准确率”,而不是看分数曲线。
4.3 奖励模型训练中的隐蔽问题
奖励模型最大的隐患是“钻空子”。它会找到训练数据里与真实质量无关但能区分排序的捷径特征,比如回复长度、格式整齐度、某些高频词。模型学到的这些捷径,在训练集上表现不错,到了真实场景里排序准确率会明显下降。
我的做法有两个:一是刻意构造一些“质量高但长度短”和“质量低但长度长”的对抗样本喂进去;二是在验证集里混合一些分布外的提示词,专门观察奖励模型在这些样本上的表现。如果排序准确率大幅低于训练集,说明它已经开始钻空子了。
另一个问题是奖励漂移。奖励模型训练时表现良好,进入RLHF阶段后模型会逐渐生成偏离奖励模型训练分布的文本,导致奖励模型的打分失真。解决办法之一是后续定期用最新策略模型生成候选、重新标注、迭代奖励模型。这个循环很累,但绕不过。
5. 强化学习阶段:让模型在反馈中自我进化
5.1 强化学习回路:生成、打分、更新
RLHF阶段的核心流程是三步循环:策略模型生成回复,奖励模型对回复打分,然后通过策略梯度方法更新策略模型的参数。这个回路每走一圈,模型就朝“高分回复”的方向挪一步。
为什么不能用“挑出高分回复”继续做SFT?因为SFT本质上是从已有样本中模仿,模型没有机会尝试那些没被标注过的输出;而强化学习允许模型自己去探索,让它在探索中学会“什么不该做”。比如一个回复起初分数很低,但模型在RL阶段尝试了不同的措辞、结构、内容取舍,最终发现某类表达能拿高分,这类调整是SFT做不到的。
PPO是这个环节最常用的优化算法,但它不是没有痛点。它需要同时维护策略模型、参考模型、奖励模型和value模型,显存开销很大,训练稳定性也很考验调参功力。我自己跑的时候最直观的感受是:RLHF像在钢丝上走路,reward一涨就高兴,但下一秒可能整体崩盘。
5.2 让训练稳定下来的KL约束机制
RLHF阶段最关键的工程机制是KL散度约束。没有约束的话,策略模型会快速找到一个“刷分”的局部最优:它不再说人话,而是专挑能骗过奖励模型的表达。典型的reward hacking现象是:奖励分数不断上涨,但人类看模型输出质量反而下降了。
常规做法是对参考模型(通常是SFT模型)计算KL散度并施加惩罚,最终的奖励公式类似:
def total_reward(score, kl_div, beta): # beta 控制惩罚力度,通常取值在 0.01 到 0.1 之间 return score - beta * kl_divbeta值调起来很讲究。太大模型几乎不敢偏离参考模型,训练半天等于白做;太小模型会放飞自我,输出逐渐失去人类语言的分布特征。经验上先固定学习率,用不同beta跑一两个小步实验,观察平均reward和平均KL的变化趋势,再决定往哪个方向调。
KL约束还有一个好处:它让奖励模型在打分时不用承担太多“约束模型说人话”的任务,奖励模型只管判断内容好坏,语言规范性交给KL约束来兜底,职责分离之后两边都更稳。
5.3 不想走PPO?常见的替代路线
现在很多实际项目不再严格走RLHF的完整四阶段,而是改用一些简化方案。
一种常见替代是DPO(直接偏好优化)。它绕过显式的奖励模型,直接在成对偏好数据上优化策略模型,让模型偏好“好回复”的概率上升。好处是不需要为奖励模型单独浪费训练和推理成本,坏处是它对偏好数据的质量更敏感,也没有显式的KL约束机制那么灵活。
另一种替代是RLAIF,用AI模型替代人类来给回复排序,然后再走RLHF或DPO。AI标注的精度通常低于人类,但它成本低、扩展快,在很多细分场景里已经够用了。在资源有限的情况下,我个人的建议是优先尝试DPO,因为工程链路短、更容易稳定复现。
5.4 RLHF阶段的常见问题速查
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| reward上升但输出质量变差 | reward hacking | 加大KL惩罚,或换测试数据人工盲测 |
| 训练loss震荡剧烈 | 学习率偏大 / 数据过脏 | 降低学习率,检查prompt分布 |
| KL发散很快 | beta太小 | 增大beta,或减少单次更新步数 |
| 训练不收敛 | 采样温度设置不当 | 固定策略模型温度,使用参考采样 |
| 显存不足 | 多个模型同时驻留显存 | 尝试模型分片,或降低batch大小 |
6. 四个阶段的工程衔接与成本参考
6.1 四个阶段的数据规模、算力与时间对比
很多人以为训练一个大模型就是从头到尾跑一次,实际上四个阶段的花费和资源量级完全不同。感知一下差异,能帮你规划自己的实验路线。
| 阶段 | 数据规模 | 训练资源量级 | 时间量级 |
|---|---|---|---|
| 预训练 | 数千亿到数万亿token | 几十到上千张GPU | 数周到数月 |
| 监督微调 | 几千到几十万条对话 | 单卡到几十张GPU | 几小时到几天 |
| 奖励模型 | 几万到几十万个排序样本 | 单卡到几十张GPU | 几小时到两天 |
| 强化学习 | 数万到数十万次采样生成 | 几十到上百张GPU | 数天到数周 |
预训练阶段是把大部分预算花掉的地方,可它产出的是一个“没对齐”的底座。后面的SFT和RLHF虽然数据规模更小,却直接决定用户最终感受到的使用体验。阶段之间不要只盯着单一指标的提升,我习惯在每个阶段结束时做一次人工评估,用一组固定的问题集看整体输出变化。
6.2 从零训练还是基于开源模型二次开发
这是所有新项目都会面临的路线选择。从零预训练的优势是你可以完全控制数据、词表和训练配方,适合垂直领域有大量独有语料的场景;劣势是成本极高、试错空间很小。基于某个开源模型做SFT和RLHF,则能大幅压缩前期成本,适合快速验证产品方向。
为了保险起见,除非团队有充足的算力和数据工程经验,我一般建议先基于开源模型做后两个阶段,跑通之后再评估是否要训练自己的底座。几乎没有哪个项目是因为SFT做得好而失败,大多数是因为数据和评测没做好就匆匆进入了训练。
6.3 训练流程里的工程与心态建议
把整个流程串起来之后,有几条经验值得写在这里。
第一,每个阶段结束都要保存独立的检查点,并且在切到下一个阶段之前跑一套完整的评估。否则问题出在“底座能力不足”还是“后续对齐失败”,很难区分。
第二,数据质量永远是第一位的。预训练数据去重要做,SFT数据的格式一致性要把关,奖励模型数据要多轮交叉标注校验。数据拖后腿的时候,用什么算法都救不回来。
第三,不要迷恋某个指标。预训练看loss,SFT看格式遵循能力,奖励模型看排序准确率,RLHF看最终人工盲测。这四个指标互相不能替代。我自己见过最典型的错误就是SFT阶段拿着训练loss一压再压,结果模型变笨了还不自知。
我个人在实际操作中的体会是,这个流程最折磨人的不是某一个算法有多难,而是阶段切换之间的系统性配合。预训练阶段你还能靠曲线判断状态,到了RLHF阶段就必须引入人工评估,否则很容易出现奖励分数漂亮、实际输出不堪入目的反差。建议每个团队在最开始就建立好一套固定的评测集和人工盲测机制,这比任何花哨的训练技巧都更能决定项目的天花板。