从PPO到SAPO:大模型RL算法演进与对抗对齐实践 周六上午十点我调好闹钟准时进了那场线上对谈标题写得很直白和SAPO一作聊聊大模型RL算法演进。说实话这类标题在社区里通常意味着两件事要么是硬核论文讲读要么是产品团队的大型广告。听下来是前者而且信息量比我预期高不少。SAPO这个工作我之前看过一些讨论但一直没完全想明白它和PPO、DPO那串方法之间的关系这场分享刚好把主线补上了。现在我把听到的内容、我自己的理解以及顺手在一个7B规模底座上做的一点验证整理成文。如果你也在做对齐相关的训练正在纠结用DPO还是继续用PPO或者只是想知道大模型RL这几年的算法是怎么一步步走过来的这篇应该能给你一些参考。1. 为什么我想聊这场分享大模型RL正卡在“能跑”与“跑得稳”之间如果放在一两年前RL训练圈子里大家最常问的问题是“能不能收敛”。现在这个问题基本没人问了大家更关心的是“能不能稳定复现一个还不错的结果”。这两个问题的差别非常大。我最近在手头某个开源底座上做对齐实验遇到的最头疼的事不是效果上不去而是同一个配置隔天重跑曲线能差出几个百分点。这种方差在小规模实验里还能忍一旦模型做到几十B甚至更大浪费的就全是真金白银的算力。为什么会有这种波动从工程角度看经典RLHF的训练管线实在太长了。奖励模型单独训一个参考模型单独加载一份策略模型和一个评论家模型都在反向传播四份模型同时常驻显存中间任何一个环节的数据分布漂移都会在后面的训练步里被放大。更关键的是策略模型很容易找到奖励模型打分里的“捷径”也就是大家常说的reward hacking它并不是真的变好了而是学会了利用奖励模型的盲区来刷高分。这个现象在PPO训练后期几乎必然出现只是程度轻重的问题。经典RLHF的一条基本链路是“先训奖励模型再用RL去优化它”。理论上这套组合拳很强但实战里你很快会发现奖励模型本身也是一个神经网络它也有自己的偏好和盲区。当策略模型开始针对奖励模型的盲区做定向优化时奖励分数会持续虚高同时人类的主观评估却在往下掉。你要不停拿验证集去盯着一旦发现奖励和人类判断开始背离就得赶紧回滚或调参。这种“边跑边盯”的节奏说实话非常消耗精力。也正因如此过去一年里社区里冒出了大批简化方案DPO系列、KTO、ORPO、GRPO都是想在去掉某个环节的同时保住大部分效果。SAPO这类对抗式方法也是在这条主线上长出来的但它不是简单地把管线砍短而是给训练过程加了一个“对手”让策略模型在对抗中自己收敛到更稳的位置。这个思路听起来有点像GAN但落到大模型训练上细节比GAN要复杂得多。一场分享能够把这条路的前因后果串起来比单看论文直接跳进公式里要友好太多。2. 从PPO到偏好优化再到SAPO大模型RL算法的主线演进2.1 经典RLHF的结构与成本账先把最经典的RLHF拆开看。整个流程通常需要四个模型同时参与策略模型负责生成参考模型负责计算KL约束奖励模型负责给出标量分数评论家模型负责估计优势函数。策略模型每走一步都要从参考模型那里拉一个KL惩罚防止它离初始SFT版本太远奖励模型给的分数则作为最终要优化的目标。理论上漂亮但工程上非常重尤其是在线采样环节策略每更新一次就要采一批新样本采样又得等推理完成整体训练速度基本被推理拖住。有时候你会看到很多团队为了稳定训练不得不把学习率压得很低然后用大量小步数去逼近。这种方式不是不行只是对超参数太敏感了。我见过不少团队在做RLHF时绝大多数时间不是花在数据上而是花在找“不会崩”的beta和KL系数上。奖励分数爆炸、策略熵迅速坍缩、生成文本复读机化现象五花八门根子都在约束和优化步长之间没找到平衡。2.2 DPO系列为什么能流行又留下了什么隐患DPO能火是有道理的它最大的贡献是发现可以直接从偏好对中构造一个隐式奖励函数然后绕开显式的奖励模型和RL采样循环。训练过程变成了给定一对回答chosen和rejected直接优化策略让它提高chosen的概率并压低rejected的概率。这一步把原来四模型训练压成了两模型甚至只有一个模型加参考模型就行。显存需求大降调参难度也大降很多团队一夜之间就能把SFT后的模型做一个像样的对齐版本。但便宜没好货这个道理在算法上也存在。DPO训练完之后你会发现模型虽然更符合偏好数据了但它开始不太愿意探索新写法生成内容的多样性明显下降。更深一层的问题是偏好对是静态的策略并没有在训练过程中获得“尝试后看到后果”的机会它只是在拟合一套人工标注的排序关系。当数据量不够大、覆盖不够全时DPO很容易过拟合到标注者的主观偏好上而不是真正学到“什么回答质量更高”。2.3 GRPO等低成本RL回到显式奖励但砍掉评论家GRPO这类工作想走另一条路保留显式RL的过程但去掉评论家模型让策略在同一个prompt下面采样多个回答用组内相对优劣来算归一化奖励再更新策略。这样的好处是训练成本比PPO低很多又因为有“在线采样”环节策略能真的看到自己生成的新样本探索能力比DPO强。但这套方案依然需要一个奖励信号来源要么是已有奖励模型要么是某种规则评分。奖励信号本身如果不干净组内比较也只是把噪音做了归一化并不会消除噪音。其实这几类方法背后隐藏着一个共同问题如何在不引入四模型训练负担的前提下保留显式RL的探索和动态约束能力。GRPO砍掉了评论家DPO砍掉了采样环节SAPO则走了另一条路径保留偏好数据和在线更新同时加了一个专门用来“制约主策略”的对手策略。一作在分享里放了一张演进图把这条线画得很清楚我们不是在找“PPO和DPO谁更强”而是在反复调整“约束强度”和“计算成本”的刻度。3. SAPO方法拆解对称对抗目标到底在优化什么3.1 用一句话理解SAPO的训练目标我在听这场分享之前对SAPO的理解一直停在“加了对抗训练”这种模糊层面。听完之后我更愿意这样概括SAPO让主策略和一个对手策略互相约束主策略的职责是最大化偏好数据里chosen和rejected的区分度对手策略的职责是不断从rejected方向上拆台逼着主策略无法靠“粗暴压低所有低概率回答”来刷分。可以找一个生活类比。假设你是篮球教练想让球员练好投篮如果只让球员自己在篮下反复投他很快会找窍门比如只挑熟悉的角度投或者出手时故意避开防守人。这时候你给他配一个真正的防守对手他的得分就不能只是“投进了”而是必须在有效防守下投进。主策略就是那个进攻球员对手策略就是那个防守球员。进攻和防守的对抗让训练不再朝着一个方向的捷径走。这里有一个很关键的点SAPO不是让对手策略把主策略全面压住那样主策略根本无法进步。它的对抗目标是“对称”的主策略在提升chosen方向的同时会被对手在rejected方向上拉住于是主策略必须找到一个真正能区分好坏回答的边界而不是简单地在概率空间里做偏移。3.2 一个示意性的损失构成在分享里一作给了一张目标函数拆解图但没有长时间停留。基于我自己的理解可以把它粗略地表示为下面这个方向帮助理解更新逻辑# 示意性目标细节以论文为准 # 主策略拉开偏好差异但受对手策略制约 main_loss score(chosen) - ratio * score(rejected) - kl_penalty # 对手策略盯住rejected方向防止主策略“刷分” opponent_loss score(rejected) - ratio * score(chosen)写成这样确实很粗糙真正实现里还有温度、归一化、参考策略约束等细节。但我想说明的是主策略和对手策略的梯度方向是互补的。主策略每把chosen概率推高一点对手策略就会提醒它你刚才压低过的那些rejected里有一些其实只是和chosen风格不同不代表内容差。于是下一次更新时主策略必须更细腻地判断“到底什么是被偏好方真正优于被拒绝方的因素”而不是简单记住标签关系。3.3 对抗机制为什么能抑制奖励黑客奖励黑客的本质是策略模型发现了目标函数里的漏洞然后专门往漏洞方向优化。常规KL约束是标量惩罚它只能限制策略离参考模型不要太远但限制了距离不一定限制方向。策略完全可以在KL半径内选一个“奖励虚高但质量变差”的方向走过去。SAPO里对手策略做的是方向约束它不关心主策略走了多远它只关心主策略有没有为了让自己的分数好看而低估rejected一类样本。被低估的样本会通过对手策略的loss发出强梯度逼主策略回头修正。这个机制还有一个额外好处训练稳定性提升了。单纯的偏好优化训练后期经常出现验证集上的交叉熵开始上升的情况说明已经过拟合。对抗过程相当于给优化目标引入了一个动态正则让主策略在每个局部区域都不能太过“舒服”梯度方向因此更平缓。从我的实验观察来看这种设计确实能让loss曲线的下游段变得更平滑虽然不能说完全消除波动但至少不会像纯DPO那样出现“训练到一半突然崩溃”的情况。4. 跑通SAPO风格训练的实操清单数据、参数与评估听完分享不等于会用了真正上手做一遍才是硬道理。我后来在自己一个7B规模的实验里试了SAPO风格的训练流程下面这些是我觉得最值得注意的点。4.1 数据准备偏好对之外还要什么SAPO的训练数据核心仍然是偏好对就是每个prompt配一个chosen和一个rejected。但和直接做DPO不同我建议对rejected样本的质量做一点挑选。太差的rejected会让训练任务变得过于简单模型学不到细腻区分质量接近chosen但确实有硬伤的rejected反而更能让对抗机制发挥作用。常规数据格式类似下面这样{prompt: 用户提问的内容, chosen: 高质量回答全文, rejected: 质量有明显缺陷但不至于完全文不对题的回答}还有一个容易被忽略的点去重。对抗训练对重复内容非常敏感重复样本会让对手策略把大量梯度集中在少数句子上导致主策略在这些位置被反复拉扯其他位置却缺乏监督。我自己的习惯是先按prompt去重一遍再按chosen的n-gram相似度清洗一轮尽量保证每个样本在语义空间上都有区分度。4.2 关键超参数与监控指标学习率方面我建议起步设得比DPO低一些。DPO我经常从1e-6到5e-6开始但SAPO因为两个策略互相对抗天然更容易出现震荡所以我先跑在1e-6左右稳定后再尝试上调。批大小尽量做大对抗估计对batch内的样本分布比较敏感batch太小会让对手策略的梯度方向变得很吵。温度参数如果设在0.7到1.0之间比较稳太低会让生成坍缩太高又会削弱偏好信号。另一个值得关注的是主策略和对手策略的更新频率比例。默认思路是两者同步更新但我试过主策略两步、对手策略一步的设置发现主策略在某些数据段能更快提升但对手策略如果更新得太慢对抗约束会变弱后面又得回调。所以如果你没有明确理由先按1比1跑再根据验证集表现微调。训练过程中不要只盯loss。我强烈建议把策略模型在验证集上的生成熵、参考模型的KL距离、以及chosen和rejected的平均对数概率差都记录成指标。生成熵掉得太快是坍缩前兆KL距离涨得太快说明已经明显跑偏。注意验证集上如果出现负对数似然在后期明显上升大概率是过拟合了偏好分布不是因为训练没有收敛。这时候不是继续加训练步数而是要回滚到更早的checkpoint看一下效果。4.3 评估设计不要只看对战胜率模型对齐效果评估可能是最难的部分。训练过程中如果只靠一个偏好模型打分看“chosen是不是比rejected高”等于是在用网络里的另一个偏见去检查这个偏见。我最后的做法是拆成三块第一用独立且风格不同的评估模型做盲评第二在小范围人工抽检里看回答的实际可用性第三跑一组通用能力测试看对齐之后有没有明显偏科。三块各看各的任何一块出现明显倒退都要重视。5. 从一作互动问答里挖出的经验几个常见误判直播后半段是问答环节一作被问了不少问题。有几个问题我感觉很典型几乎每个人上手前都会问一遍。5.1 “SAPO是不是一定比PPO更贵”答案没有那么绝对。PPO常驻模型是策略、参考、奖励、评论家四个SAPO常驻的是策略、参考、对手三个显存总账并不吃亏。从计算量上看SAPO不需要像PPO那样在每一步做复杂优势估计和GAE回溯反而省了一部分开销。但注意它确实多维护了一个对手策略如果你本来就计划用极简化DPO路线那SAPO肯定比DPO贵。选型还是要放在同一组约束条件下比较不能只盯着单一维度的成本。5.2 “我能不能直接在现有PPO训练代码上改成SAPO”现场也有人问了这个一作的建议很直接与其在PPO框架里硬改不如从偏好数据训练的框架上接SAPO。因为PPO的优势计算和策略更新逻辑跟对抗目标差别很大强行改造会留很多死代码。我当时觉得这个建议很对RL训练里“看起来差不多”的两套实现一旦换算法坑不是一个量级的。找一个干净的偏好优化开源实现替换训练目标保留参考策略和损失汇总逻辑要比在PPO管线里东拆西补更省时间。5.3 “DPO已经够用了为什么还要换”这个问题问得很实在。一作没有急着让所有人都换而是点了一下DPO的边界条件当你的偏好数据质量很高、覆盖很集中、任务本身不太需要探索时DPO仍然是一个性价比极高的方案。但如果你发现模型在分布外场景表现不稳或者在多轮任务里容易退化那就说明静态偏好学习已经不够了。SAPO这类带对抗约束的方法在多轮和长尾场景下更稳因为它始终有一个对手在阻止策略跑偏。另外被问到训练稳定性时一作提到了一句话我记得很清楚稳定性不是靠调小学习率换来的而是靠让目标函数每次都把梯度推向正确的方向换来的。对抗目标真正的价值就在这它给的梯度更不容易被局部奖励陷阱带走。当然这也不是万能药学习率过大时对抗本身也可能震荡所以该保守起步还是得保守。6. 听完分享后我自己打算怎么调整训练流程这场分享对我来说最大的收获不是某个公式而是把几个算法之间的选择逻辑理顺了。最近我在做对齐训练时会把三个指标放在最前面训练loss的平稳度、验证集上的生成多样性、以及新旧checkpoint在旧bad case上的表现。以前我习惯盯着基准分数看现在我觉得基准分数是事后验证训练过程里的分布漂移才是事前信号。我接下来的计划是这样的先在7B规模底座上认真跑一个SAPO风格的完整训练把数据清洗策略和超参配置固定下来同时用一个同样的数据集跑DPO做对照组两边用同一套评估体系比较。对比完之后再决定要不要把方案迁移到更大的模型上。这个节奏不激进但能积累可靠的经验。最后分享一个我自己会一直用的小技巧每次训练完一个checkpoint先不看新指标而是拿它去跑一遍原来经常出错的旧case再跑一遍已经表现不错的stay-good case。如果新模型能把旧case修好同时又没弄坏stay-good case那它才是真的变好了。这个判断方式简单粗暴但比看曲线图有用得多尤其适合用来快速发现早期reward hacking的苗头。SAPO让我多了一个可以比较的维度但最终做决定的还是这些贴近业务的评估直觉。