做昇思MindSpore大模型训练的朋友,可能都有过这种时刻:基座模型辛辛苦苦预训练完,loss低得漂亮,给它一句提示词也能生成一大段通顺的文字,可一旦让它"按给定格式输出"或者"只回答要点",它就露馅了——啰嗦、跑题、甚至一本正经地胡说八道。问题出在哪?出在构建流程少了对齐这一步。昇思MindSpore大模型构建流程里的对齐模式,就是把模型从"会生成"进一步打磨成"会听话"的过程,涉及指令微调、奖励建模、RLHF与DPO等一系列训练策略。这篇文章我会结合自己在MindSpore环境里的实操经验,把对齐模式到底解决什么问题、两条主流路线怎么选、数据怎么准备、训练怎么跑、坑在哪儿一次性讲清楚。适合正在做大模型全流程训练、准备给自研模型加偏好对齐能力的工程师和研究者参考。
1. 对齐之前:基座模型差的那一步到底是什么
1.1 大模型构建的全生命周期:预训练、SFT、偏好对齐各司其职
如果只用一条主线串起大模型构建周期,我习惯这样划分:语料清洗与构建、预训练、指令微调(SFT)、偏好对齐、评测与部署。预训练阶段用海量语料教模型做下一词预测,模型在这个阶段大量吸收语言结构、世界知识和推理模式,但它学到的是"继续写下去"的能力,不是"按人要求回答"的能力。SFT阶段用人工标注的指令-回答对,教会模型"提问-回答"的基本交互方式,让模型看到问题后知道该正面作答,而不是顺着话头乱接。偏好对齐阶段则更进一步,用人类偏好数据告诉模型:在多个同样通顺的回答里,哪一个才是人更想要的。
很多团队在对齐模式上踩的误区,是把SFT当成了整个对齐的全部。实际上SFT解决的是形式对齐——模型知道该按指令回答;偏好对齐解决的是行为对齐——模型知道什么回答更好。打个不太严谨的比方:SFT像教一个新人熟悉工作流程,偏好对齐则是在流程之上告诉他做事的优先级和分寸感。昇思MindSpore大模型构建流程里,这两个环节是递进关系,不是二选一,跳过SFT直接做偏好对齐的结果往往是模型根本听不懂任务,偏好信号也无处安放。
1.2 基座模型只学会了"续写",没学会"听话"
为什么基座模型看起来什么都会,一用起来就拉胯?因为预训练阶段,模型的训练目标只有一个:最大化下一个token的条件概率。这个目标决定了它学到的是语料中的统计规律,而不是"人对问题的期待"。模型的概率分布是海量语料混合平均的结果,它知道"请写一封请假邮件"后面大概率跟着邮件正文,却不知道邮件应该写给谁、语气应该是什么、要交代哪些关键信息,于是在生成时把不同来源的语料片段拼接得七扭八歪。
有个朋友给我演示过一个很直观的对比:同一个模型,SFT之后让它写请假邮件,能给出结构完整的邮件;但让它写"以拒绝的语气回复加班请求"时,它依然语气温和、理由充分,完全看不出"拒绝"。这就是形式对齐和行为对齐的差距。偏好对齐模式下,模型会从大量偏好数据中学到"拒绝"场景下人类更接受什么样的表达,输出才真正贴合场景。
对齐带来的收益主要有三块:一是有用性,模型能按要求完成具体任务;二是可控性,输出符合格式、长度、角色设定等约束;三是安全性,避免生成有害、误导或违背主流价值观的内容。这三点也是我判断一次对齐实验是否有效的核心维度。
2. RLHF和DPO:两条主流对齐技术路线怎么选
2.1 RLHF三阶段:效果上限高,工程负担也高
RLHF是偏好对齐里最经典的方案,整条链路分三步:第一步,拿一个经过SFT的模型作为初始策略;第二步,收集人类对同一提示词下多个回答的排序数据,训练一个奖励模型;第三步,用强化学习算法(典型是PPO)更新策略模型,让它在尽可能拿高奖励分的同时,不偏离初始策略太远。奖励模型回答"什么是好",PPO负责让策略模型的输出向"好"靠拢,KL散度则防止模型为了得分而彻底放飞。
在MindSpore环境里做RLHF,工程上最直观的感受是"模型多"。PPO阶段至少要同时维护四个模型:策略模型actor、参考模型reference、奖励模型reward、价值模型critic。一个7B模型做RLHF,单卡显存基本吃不消,四卡起算是常态,显存规划、通信开销、梯度稳定性都是需要额外投入的工程问题。训练节奏上,我习惯把奖励模型先训到明显能区分好坏的准确率,一般95%以上再进入PPO。奖励信号太弱时,策略模型很难学到有效信息,反而容易被噪声带偏。
2.2 DPO与SimPO:砍掉奖励模型的轻量路线
DPO出现后,圈内很多团队一夜之间就轻装了。DPO不训练独立奖励模型,而是把偏好数据里的chosen和rejected两个回答,直接拿到同一个策略模型上做前向,用它们的概率比构造训练信号。它的核心洞察是:带KL约束的RLHF最优解,在数学上可以表达成对策略模型概率的直接约束,那就不需要显式建模奖励,直接基于Bradley-Terry偏好模型构造目标函数即可。
DPO的工程优势很直观:训练时只需要policy model和reference model两个模型,显存压力明显低于RLHF;训练过程也更稳定,不用面对PPO里一堆强化学习超参数的调参地狱。SimPO在DPO基础上又往前走了一步,连reference model都省了,直接用归一化对数概率充当隐式奖励,计算量进一步收缩,但效果对数据质量的敏感度更高。
2.3 算力有限就选DPO,有攻顶需求再上RLHF
怎么选,我给一个务实的判断标准。如果目标只是给一个几十亿参数的模型做一轮偏好对齐,团队里没有专门的强化学习工程师,优先考虑DPO;如果模型体量大、团队有强化学习基础,而且评测数据显示DPO确实不够用,再考虑上RLHF。我见过不少团队一上来就冲PPO,结果光调KL惩罚系数就耗了两周,最后跑完对比,DPO基本够用。
| 维度 | RLHF(PPO) | DPO / SimPO |
|---|---|---|
| 完整训练链路 | SFT + RM训练 + PPO | SFT + DPO 两步 |
| 训练期模型数量 | 4个左右 | 2个甚至更少 |
| 显存压力 | 高 | 中/低 |
| 调参复杂度 | 高,涉及RL超参 | 低,核心参数少 |
| 偏好数据形式 | 同一提示词下多个回答的排序 | chosen/rejected成对数据 |
| 效果上限 | 理论上限更高 | 依赖数据质量,通常够用 |
这张表列完,选型逻辑基本清晰:数据质量决定下限,工程条件决定上限。项目节奏紧、算力有限,先别纠结要不要上最高精尖的方案,把DPO跑稳,已经能解决绝大多数业务问题。我在实际项目里见过不止一次的情况是,团队花了大量精力搭RLHF工程链路,最后评测结果和DPO差距不到一个百分点,却多出了几倍的调试成本。所以除非你明确知道RLHF能带来什么额外收益,否则先让DPO跑通一版,拿真实数据说话。
3. MindSpore里搭对齐流程,关键环节一个个拆
3.1 构建偏好数据集:提示词、选定回答、拒绝回答一个都不能少
无论走哪条路线,偏好数据都是对齐模式的地基。一条标准的偏好数据至少要包含三个字段:prompt(提示词)、chosen(被标记为更好的回答)、rejected(被标记为较差的回答)。别小看这个结构,很多新手在构造数据时只关注文本是否通顺,忽略了一个关键点:chosen和rejected必须在同一任务、同一约束条件下可比。比如prompt里要求"用一句话解释",结果chosen是一句话,rejected是一大段,这种数据教给模型的信息是"短=好",而不是真正的偏好差异。
在实际构造时,我强烈建议直接按照训练时的chat template来存储数据,而不是存纯文本。原因很简单:SFT和偏好对齐使用的对话模板必须和推理阶段保持一致。一旦模板信息在数据管线里丢失,对齐出来的风格会在部署时全部错位,模型本地表现很好,一到线上接口就变得很奇怪。偏好数据的来源可以选择公开偏好数据集子集、团队内部排序标注、或者用更大的模型打分替代人工排序。最后一种虽然省成本,但打分模型本身的偏差会传导到偏好信号里,只能用来对付初始版本。
3.2 并行策略与显存规划:ref和actor要同时占显存
MindSpore在大模型训练上提供了完整的数据并行、模型并行和流水线并行能力,但这些能力在偏好对齐任务里得重新做一次预算规划。原因很简单:同一个batch,actor和reference模型要分别对chosen和rejected两个序列各做一次前向。也就是说,同样一批数据,模型前向的token总数接近普通SFT的4倍,显存和算力需求会被成倍放大。
一个常见的误判是拿SFT阶段的显存数据去打对齐任务的资源预算。实际做下来,同样的模型规模,DPO训练显存占用大约是SFT的1.5到2倍,RLHF会更高。如果你的卡只有30GB显存,又想对齐7B模型,优先考虑LoRA这类参数高效微调方法,只训练低秩适配层,base模型半精度加载甚至冻结,能明显降低显存瓶颈。并行策略方面,我给一个常见实践参考:单机多卡先满足batch size和序列长度约束,再考虑张量并行;跨机场景优先流水线并行,减少跨节点通信。MindSpore各版本并行接口差异不小,动手前先拿一个小模型把并行配置跑通再上量级,否则排错成本会非常高。
3.3 奖励信号与KL约束:防止模型"钻空子"
对齐训练里最反直觉的一点是模型会钻空子。在RLHF里,如果奖励模型只负责打高分,策略模型很快会发现某个句型、某种长度或某个高频词能骗到高分,于是疯狂输出这类内容,哪怕内容本身早就偏离了人类偏好。这就是reward hacking。
防止reward hacking,两个务实手段。第一是KL散度约束:在PPO目标函数里加上策略模型与reference模型输出分布的KL惩罚,控制策略不偏离初始分布太远,像给模型套了根缰绳。第二是奖励模型本身要克制:不能只在高分标注数据上学得过于激进,训练数据里要覆盖模型真实生成分布附近的样本,否则奖励模型一暴露在策略模型的分布里就乱打分。DPO虽然没有显式奖励模型,但对beta这个温度超参数要保持敏感。beta越大,模型和reference分布越接近,对齐强度越弱;beta越小,模型越迎合偏好数据,也越容易出现重复生成和模板句式。我调beta时习惯从0.1起步,先观察一两千步再决定放大缩小,而不是一开始就追求某个理想值。
4. 用一个最小实验把手上的对齐流程跑通
4.1 MindSpore环境准备与小模型选型
用MindSpore跑对齐实验,我建议先从1B到3B级别的小模型开始。原因很直接:对齐流程的复杂性主要在数据管线、模型加载、梯度逻辑、显存规划,而不是模型本身。小模型跑通全流程,再把同样的流程迁移到目标模型,效率比直接在大模型上调高得多。
环境方面,先确认MindSpore版本和GPU驱动匹配,建议直接用官方容器镜像,避开CUDA版本不匹配的坑。如果团队用的是MindSpore的大模型套件(例如mindformers),先确认它支持你要用的模型结构和偏好优化任务类型;如果暂时不支持,就用基础框架手写训练循环,自由度更大,但后续排查成本都在自己身上。选型时还要注意torch和MindSpore的权重转换问题,如果用别的框架训练的基座模型,导入前先确认权重映射关系,这一步错了后面全白费。
4.2 从公开数据构造偏好对
在团队还没有标注数据之前,最快的起手式是从公开偏好数据集抽一个子集。数据格式可以像我下面这样设计,字段清晰,后面写数据加载器也省事:
[ { "prompt": "请用一句话解释什么是梯度下降。", "chosen": "梯度下降是一种通过反复更新参数、使损失函数逐步减小的优化算法。", "rejected": "梯度下降就是在山上往下走,一直走到底,方法是沿着坡度最大的方向走,整个过程还要注意步伐,因为步子太大容易掉坑里,太小又走得很慢。" } ]拿到原始数据后不要急着进训练,先做清洗。检查chosen是否真的优于rejected,是否出现空回答,是否在模板层有串扰。我习惯先用脚本统计chosen和rejected的长度分布,如果两者长度差异异常明显,要警惕模型学到"长回答即好回答"这种错误偏好。清洗完成后,按8:1:1切分训练、验证、评测集,评测集里单独留一部分不参与训练,专门用来做生成质量抽查。
4.3 核心训练流程的代码骨架
下面以DPO为例,给出核心训练流程的代码骨架。MindSpore各版本接口有差异,我写的思路是通用逻辑,重点在于呈现DPO loss是怎么一步步算出来的。这段代码基于常见实践补充,请在你自己的环境里对照算子名做微调。
import mindspore as ms import mindspore.ops as ops from mindspore import nn # 1. 加载base model作为policy model,并复制一份作为reference model policy_model = load_model("your_base_model") reference_model = load_model("your_base_model") # reference model全程冻结 reference_model.set_train(False) for param in reference_model.trainable_params(): param.requires_grad = False # 2. 定义DPO损失 class DPOLoss(nn.Cell): def __init__(self, policy_model, reference_model, beta=0.1): super().__init__() self.policy_model = policy_model self.reference_model = reference_model self.beta = beta self.log_softmax = ops.LogSoftmax(axis=-1) self.gather_d = ops.GatherD() # 版本不同可能叫gather_d,以当前环境为准 def sequence_log_prob(self, logits, input_ids, attention_mask): log_probs = self.log_softmax(logits) token_log_probs = self.gather_d( log_probs, -1, ms.ops.expand_dims(input_ids, -1)).squeeze(-1) return (token_log_probs * attention_mask).sum(axis=-1) def construct(self, chosen_ids, chosen_mask, rejected_ids, rejected_mask): chosen_logits = self.policy_model(chosen_ids) rejected_logits = self.policy_model(rejected_ids) # reference model只做前向,不回流梯度 ref_chosen_logits = ops.stop_gradient( self.reference_model(chosen_ids)) ref_rejected_logits = ops.stop_gradient( self.reference_model(rejected_ids)) chosen_log_prob = self.sequence_log_prob( chosen_logits, chosen_ids, chosen_mask) rejected_log_prob = self.sequence_log_prob( rejected_logits, rejected_ids, rejected_mask) ref_chosen_log_prob = self.sequence_log_prob( ref_chosen_logits, chosen_ids, chosen_mask) ref_rejected_log_prob = self.sequence_log_prob( ref_rejected_logits, rejected_ids, rejected_mask) policy_log_ratio = chosen_log_prob - rejected_log_prob ref_log_ratio = ref_chosen_log_prob - ref_rejected_log_prob loss = -ops.log(ops.sigmoid( self.beta * (policy_log_ratio - ref_log_ratio) )).mean() return loss代码里最关键的一步,是policy和reference在chosen与rejected两个序列上的对数概率差。如果reference_model没有正确冻结,梯度会同时更新两个模型,实际效果就是一边追目标一边挪标杆,训练基本废掉。另外,chosen和rejected的序列长度往往不一致,计算对数概率时attention_mask的处理一定要对齐,漏掉mask会导致padding位置的伪高概率把真正的信号淹没掉。
4.4 训练中盯哪些指标才不会跑偏
跑DPO训练时,我会同时盯四组信号,而不是只盯loss。
第一是DPO loss本身的收敛趋势,它应该下行并趋于平稳,如果剧烈波动,先查数据质量再查学习率。第二是chosen和rejected两个回答的平均对数概率差,这个差值反映了模型对偏好数据的拟合程度,太小说明还没开始学,过大则要警惕过拟合到偏好数据。第三是policy model与reference model输出分布的偏离程度,偏离过大说明beta设小了,模型可能已经远离初始能力。第四是每500步左右保存一个checkpoint,拿一小批固定prompt做生成质量抽查,人工对比不同检查点的输出变化。
我特别强调最后一项,因为loss掉得漂亮,不代表对话质量真的好。偏好对齐任务里,离线指标和线上体验之间经常隔着一条很宽的河,最终还是要回归人眼评测。
5. 对齐实战中绕不开的坑
5.1 reference模型忘了冻结,loss直接飘掉
这个坑我踩过不止一次,而且表现很隐蔽。一开始你只会觉得loss下降有点慢,看了半天代码才发现reference_model的requires_grad没有全部关掉。在MindSpore里,光set_train(False)不够,还需要把模型参数的requires_grad逐个置为False。否则优化器在更新策略的同时也在移动对比基准,DPO的loss会飘忽不定,看起来像不收敛,实际是那个"标准答案"一直在变。
排查方法很直接:每隔100步打印reference_model的参数变化量,如果几乎为0说明冻结成功,如果明显在动就赶紧修。顺便提一句,如果你的优化器本来就不该更新reference参数,也可以直接在构建optimizer时只传入policy_model.trainable_params(),从根源上杜绝误更新。
5.2 reward hacking:奖励模型被"刷分"了
做RLHF时,我遇到过奖励模型单独测准确率很高,但在PPO里很快被策略模型"刷分"的情况。当时策略模型开始大量输出特定开头的模板句,长度明显变长,人一眼就能看出假,但奖励模型给的分数反而越来越高。复盘根因,是奖励模型的训练数据覆盖不够。我用的是一批静态排序数据,没有加入策略模型训练过程中的分布漂移样本,奖励模型面对新分布就失效。
后来的做法是:定期从当前策略采样一批回答,让人工或更强模型打分,补充进奖励模型训练集持续迭代。这相当于把奖励模型的评测暴露在动态分布里,才把刷分现象压下来。如果你也在做RLHF,建议把"奖励模型和数据动态更新"设计成流程的一部分,而不是训完就丢。
5.3 用真实对话评测替代loss迷信
这是最想提醒所有做对齐实验的人的一点。偏好对齐的离线指标往往很友好:loss在降、chosen和rejected的间距在拉开,但生成质量可能越来越怪。我碰到最典型的现象是模型开始用高概率的"安全而空洞"的表达来糊弄人,看似什么都答得上来,其实什么都没说。这种退化在离线指标上看不出来,只有把生成结果真正读一遍才会发现。
我的建议是,从训练第一天起就建立一套固定prompt评测集,里面包含格式类任务、知识类任务、以及实际业务中最常出现的场景。每隔固定步数让同一批检查点生成答案,摆在一起横评对比。这套做法不需要额外开发平台,只是把人工评测制度化,但它能拦住绝大多数"指标好看、实际翻车"的情况。
5.4 beta值怎么调:对齐强度与生成多样性
beta是DPO里少数几个需要认真对待的超参数。beta太大,约束强,模型几乎不会偏离reference太多,对齐效果不明显;beta太小,模型过度迎合偏好数据,输出变得机械、套路化。对齐强度说到底就是生成多样性和任务服从性的天平。
我调beta的经验是:先固定其他条件,用0.05、0.1、0.2三个值各跑几百步,观察chosen-rejected分差和生成多样性。分差太小说明beta偏大;分差快速拉大且生成出现大量重复句式,说明beta偏小。大多数情况下落在0.1到0.2区间,但这个值会随模型规模和数据质量变化,换模型后不要照搬参数。
最后再分享一个体会:对齐模式的成败,七成在数据,三成在训练。不管用MindSpore还是其他框架,先把偏好数据清洗干净、把覆盖范围做够,比反复调beta和换算法都管用。我现在的习惯是每次对齐实验前,随机抽200条偏好数据人工过一遍,确认没有语义矛盾、没有模板串扰、没有"差的反而更像好"的脏数据,再上训练。这个习惯帮我省掉了大量无效调参时间,你也可以试试。