大模型训练与推理一致性:从Exposure Bias到RLVR的实践指南

大模型训练与推理一致性:从Exposure Bias到RLVR的实践指南 1. 为什么会聊“训练-推理一致性”这件事做 LLM 后训练这半年多我最大的一个感受是模型在训练脚本里跑出来的指标和真正部署到线上推理时的表现经常是两回事。尤其是进入 RL强化学习阶段之后这个问题会被放大得特别明显——训练时 reward 一路飙升eval 时却觉得模型越训越“怪”输出变长、格式不对、甚至学会了一些让人哭笑不得的作弊套路。所谓“训练-推理一致性”往浅了说是希望模型在训练阶段见过、学过的数据分布和推理阶段真正面对的任务分布尽量一致往深了说是让模型的优化目标、解码策略、采样参数、上下文长度、终止条件等所有环节在训练和推理两个阶段保持对齐。这件事听着像常识做起来却很反直觉因为传统的监督学习和标准的 RL 框架本质上是不会替你考虑“部署时模型怎么生成”的。这篇文章我会从根源把问题拆开曝光偏差到底是怎么回事RL 训练为何会把不一致放大以及我在实际做数学推理、代码生成这类任务时是怎么通过一组可落地的设置把训练和推理“往一个方向拽”的。适合刚接触 LLM 后训练、准备上手 RLHF/RLVR/GRPO 的工程师也适合已经跑通流程但总感觉线上效果低于预期的朋友。2. LLM 的 RL 训练里最容易产生不一致的四个源头2.1 Teacher Forcing 与自回归推理之间的缝隙先说最基本的。大家训练 GPT 类模型的标准做法是 teacher forcing给定一段真实的 token 序列作为前缀让模型预测下一个 token然后计算交叉熵损失。在训练时模型看到的每一个前缀都是 ground truth是“标准答案”的一部分。但在推理时模型是从一个起始 token 开始把自己上一轮生成的 token 拼到序列后面再继续预测下一个。也就是说推理时模型看到的前缀来源是自己的历史输出而不是标准答案。这个差异有个专门的名字叫 exposure bias。理论上它会导致错误累积生成过程中一旦出现一个不理想的 token后续所有预测都建立在这个有偏差的上下文之上。实际上在大模型这里由于训练数据量足够大、模型容量足够大exposure bias 通常不会让生成立刻崩掉但它依然会体现在一些很微妙的地方——比如模型在一定长度之后开始重复、逻辑跳跃、或者对关键中间步骤的依赖变弱。而在 RL 阶段exposure bias 的影响会成倍放大。为什么因为 RL 的 rollout 本身就是用当前策略自回归采样得到的如果我们在采样时用了某种解码参数而后面的奖励模型、优势估计都是基于这批 rollout 计算的那么模型真正学到的其实是“在这种采样参数下的行为”的优化。可到了推理阶段如果你换了 temperature、换了 top_p、甚至换成了 greedy decoding模型面对的局面就和训练时完全不同。这就是典型的训练-推理不一致。2.2 RL 优化目标与推理真实指标的错位第二个源头在目标函数层面。RLHF 最常见的流程是先训一个奖励模型再用 PPO 去优化这个奖励模型给出的分数。但奖励模型是学出来的它并不是推理时真正想要的那个东西。比如你做数学题线上唯一关心的就是最终答案对不对你做代码生成线上关心的是能不能通过单元测试。奖励模型不过是这些真实目标的一个“代理”。代理和真实目标之间永远有缝隙这个缝隙就是 reward hacking 的温床。模型会发现提高奖励模型分数的最快路径并不是提升真实能力而是去捕捉奖励模型里的统计偏差——比如写得和训练集中高分段样本更像、用更长的推理过程去迷惑奖励模型、或者反复输出某些“安全但无用”的格式。现在业界的应对方案是 RLVR也就是可验证奖励Rule-based Verifiable Reward。数学题就直接比对最终答案代码题就跑单元测试这样奖励就是真实的推理目标没有代理误差。但 RLVR 也不是万能药如果验证器本身不够严谨比如只比对最终数字而不检查推导过程模型照样能找到漏洞。所以目标函数层面的“一致性”不只是选 PPO 还是 GRPO 的问题更重要的是你用什么作为奖励信号以及这个信号和线上评判标准有多接近。2.3 采样参数不一致带来的分布漂移第三类不一致非常容易被忽视但影响往往立竿见影——采样参数。我们在 RL 训练时做 rollout通常会用 temperature0.7、top_p0.9 这种比较有随机性的采样目的是让策略有足够的探索空间。但到推理阶段很多团队为了追求稳定输出直接改成 greedy decoding或者 temperature0.1 这种接近贪心的设置。干过这事的人会发现训练时看 rollout 上 reward 挺高一换到贪心解码效果反而掉下去了。原因并不复杂模型在 RL 阶段学到的行为是适配高随机性采样环境的它习惯了“这次采样没抽到最优 token 没关系后面还有机会补救”这样的统计规律。当你把采样改成贪心模型相当于被放到了一个它从未训练过的场景里表现自然打折扣。反过来说也一样如果训练时 rollout 用的是低 temperature推理时却为了多样性拉高 temperature同样会有问题。正确的做法是把 rollout 采样参数和线上推理参数当成同一组超参来管理训练用多少推理就尽量用多少。如果线上场景实在需要低随机性那训练阶段就应该逐步降低 rollout 的 temperature而不是最后测试时才切换。2.4 上下文长度和终止条件的不一致最后这类不一致是我在实际项目里踩得最深的一个坑序列长度和终止条件。RL 训练里我们通常会设置一个最大生成长度 max_tokens比如 2048。rollout 时模型如果在 2048 个 token 内没有输出 EOS就会被强制截断并且这个截断本身会影响 reward要么给 0要么给一个惩罚。问题在于线上推理时我们可能出于延迟考虑把 max_tokens 设成 1024或者某类请求上下文特别长、达到了 4096这都会让模型处于一个训练时从未经历的“长度区域”。更隐蔽的是终止条件。数学任务里你可能希望模型输出一个最终答案后主动停止代码 Agent 任务里你希望模型生成完代码块就结束或调用工具。模型在 RL 训练中学习到的“停止”信号是被奖励函数塑造出来的。如果训练时奖励鼓励“生成到 EOS 才得高分”而推理时框架在检测到代码块结束时就直接截停那模型的生成策略就会被打乱。它可能还没写完就停了或者该停的时候继续啰嗦。总结一下训练-推理一致性问题不是某一个环节造成的而是 teacher forcing 范式、目标函数、采样策略、序列管理这四个层面共同作用的产物。接下来我重点讲如何在实际操作中逐一解决这些错位。3. 从数据和策略层面把训练和推理的目标统一起来3.1 能用规则验证器就别用训练出来的奖励模型先给一个最直接的建议如果任务允许优先使用规则验证器rule-based verifier而不是学习得到的奖励模型。原因就是前面说的规则验证器给出的分数就是真实推理目标不存在代理误差。以数学推理为例一个简单的验证器可以这样设计从模型的输出中提取“最终答案”部分和标准答案做字符串匹配或数值等价比较。代码任务就更直接把生成的代码放进预定义的单元测试里跑一遍pass 就算对。这种验证器虽然有“只看结果不看过程”的问题但至少它的标准是确定且可复现的模型很难通过“说得漂亮”来骗分。什么时候才需要上奖励模型当任务的主观性很强、不存在标准答案的时候比如开放性写作、对话质量评估。即便在这种情况下我也建议把奖励模型的训练数据尽量采集自“推理分布”也就是用当前策略亲自采样得到的样本而不是只用离线人工标注数据。否则奖励模型很容易在分布外样本上给出虚高分值RL 训练就会顺着这个虚高信号跑偏。3.2 用专家迭代和拒绝采样弥合训练分布与推理分布的沟壑第二个非常实用的策略是专家迭代expert iteration和拒绝采样rejection sampling。具体操作是这样的准备一批高质量 prompt用当前策略模型做采样每个 prompt 生成 N 个候选回复然后用验证器或奖励模型给这些候选打分。接下来只保留得分最高的那批样本把它们作为下一轮 SFT 或偏好优化的训练数据。相当于让模型去模仿“推理分布中表现最好的样本”而不是笼统地从所有 rollout 中学习。这个做法高明在哪里它直接把训练分布拉向了推理分布中偏好的子区域。传统 RL 是让模型在在线采样中探索并优化虽然能学到新东西但也容易产生分布漂移——模型跑到训练分布之外甚至跑到奖励函数失效的区域。专家迭代的每一步都做了筛选相当于给策略加了一个“安全护栏”让模型只在已经被验证过的高质量区域内学习。我自己的经验是专家迭代配合 GRPO 这类在线 RL 算法效果往往比单一使用某一种方法更稳。先用 SFT 热身再做两三轮拒绝采样 SFT等模型基础能力到位之后再用在线 RL 做最后的精调。这时候在线 RL 的探索空间已经被限制在了一个比较合理的范围内reward hacking 的概率会小很多。3.3 把采样参数和终止设置做成同一套“全局配置”前面提到rollout 参数和推理参数不一致会造成分布漂移。解决方式不仅仅是“记住要一致”而是应该把采样参数、最大长度、终止条件这些设置抽象成一份全局配置在训练和推理两个阶段共用。举例来说我会在一份 YAML 配置文件里定义这样几个字段temperature训练 rollout 和推理统一用 0.7top_p统一用 0.9max_tokens数学任务统一用 2048stop_sequences统一用[|endoftext|, \n\nFinal Answer:, /s]eos_token_id统一使用模型自带的 EOS同时把 endoftext 也加入停止条件这份配置同时被训练脚本和推理服务加载。也就是说训练时 rollout 怎么生成线上推理就怎么生成两边不再各设一套。这样做还有一个好处当你需要调整线上策略时比如为了降延迟把 max_tokens 从 2048 改成 1024你立刻就知道必须重新跑一轮 RL 或者至少做一轮针对性的采样验证而不是想当然地认为“模型应该能适应”。这里有同学可能会问推理时我实在想用 temperature0 的贪心解码因为业务上需要稳定输出怎么办我的建议是在训练初期可以用较高温度探索但在训练后期逐步把 rollout 的 temperature 降到接近线上推理的水平。比如前 60% 的训练用 0.7后 40% 用 0.3最后 10% 用 0.1。这会让模型逐渐适应低随机性采样下的生成方式减少推理阶段的落差。3.4 采用课程式长度控制让模型在RL阶段就学会“收敛”长度控制是我特别想强调的一点。在我做数学推理 RL 训练时发现模型经常为了多争取过程分而无限拉长推导过程甚至在得到正确答案之后还要再写一大段“验证”。这在训练时看起来无伤大雅因为 reward 并没有惩罚长度但部署到线上之后多出来的几百个 token 就是实打实的延迟和成本。要让模型学会“收敛”核心是在奖励设计里加入长度感知的信号。最简单的方式是在验证器的最终答案正确的前提下给一个和输出长度负相关的分数。比如reward correctness_reward - alpha * max(0, length - expected_length)。alpha 取多少需要调我一般从 0.001 开始试观察平均长度变化再调整。更高级一点的做法是课程式长度控制。训练初期不对长度做任何约束让模型自由探索完整解法等模型已经能稳定输出正确答案后再逐步收紧长度上限逼着模型精简推理步骤。这样模型不会因为一开始就被压缩长度而学不到完整解法也不会在后期无限膨胀。课程式设计的本质就是让“训练时的环境”逐步逼近“推理时的环境”这本身就是一种一致性优化。4. 实操记录用 GRPO 微调数学推理模型我是怎么对齐训练和推理的4.1 整体流程SFT、RLVR、行为快照三件套我拿一个具体的数学推理模型微调项目来走一遍完整流程。任务目标是提升模型在初中数学应用题上的 final answer 准确率。我没有直接用裸模型做 RL而是分了三步第一步SFT 热身。准备大约 5 万条带详细解题过程的样本让模型先学会规范的解题格式。这里要注意SFT 阶段的数据最好和目标任务的形式保持高度一致。比如线上推理时prompt 是“题目 请给出详细解题步骤和最终答案”那 SFT 数据的格式也应该是这样不要训练时一套格式、推理时另一套格式。第二步RLVR 训练。使用 GRPO 算法奖励信号直接用最终答案匹配的结果不额外训练奖励模型。rollout 参数和推理参数共用同一份配置temperature0.7top_p0.9max_tokens2048。KL 惩罚系数设置为 0.01防止策略和初始模型偏离太远。第三步行为快照。训练过程中每 500 步我就在固定 prompt 集上做一次完整的 rollout记录输出长度、最终答案准确率、格式规范率等指标。这些指标不是用 teacher forcing 算出来的 loss而是真正在 rollout 分布上测出来的。行为快照的意义在于它能让你尽早发现训练和推理之间的偏差而不是等整个训练跑完才在线上发现问题。4.2 GRPO 奖励设置里容易被忽略的两个细节GRPO 和 PPO 最大的区别之一是它不需要 value network而是基于同一个 prompt 的多条采样结果做组内相对归一化。这意味着奖励的绝对数值没有那么重要重要的是同组样本之间的相对排序。这个特性在缓解奖励信号漂移上很有优势但同时也带来两个容易被忽略的细节。第一个细节组内采样条数也就是每组的 rollout 数量对效果影响很大。如果组内样本太少比如只有 4 条相对排序的稳定性很差模型会因为某一次随机波动被错误地引导。我自己测试下来每组至少 8 条最好 16 条效果才比较稳定。代价是显存占用和采样时间增加但换来的是更平缓的策略更新。第二个细节KL 惩罚和奖励之间的平衡。GRPO 虽然去掉了 value network但通常还是会加一个对参考模型通常是 SFT 初始模型的 KL 惩罚防止策略跑飞。这个 KL 项和 reward 项的矛盾很微妙KL 太大模型学不到新东西KL 太小模型很快就会 reward hacking。我在这个项目里是用自适应 KL 系数如果当前 KL 超出目标区间比如设定 0.5 到 1.5就自动调整系数。实践中比固定系数省心很多。4.3 Eval 阶段一定别用 teacher forcing 指标糊弄自己训练结束后评估环节是我认为最容易自欺欺人的地方。很多团队看训练 loss 下降了、sft 后的 PPL 也正常就认为模型变好了结果上线一测效果反而变差。问题就在于这些指标全部来自 teacher forcing 分布无法反映自回归推理时的真实表现。我在这个项目里的 eval 方案是在固定测试集上用和推理完全一致的解码参数跑一遍生成再对生成结果做最终答案提取和匹配。同时做了三组对比评估方式说明teacher forcing accuracy给定完整标准答案前缀让模型预测最后答案 token 的准确率rollout accuracy模型从头自回归生成完整答案提取最终答案后的准确率best-of-n accuracy每个 prompt 采样 16 次取验证器得分最高的结果计算准确率这三组数据之间通常存在一个梯度teacher forcing 最高rollout 次之best-of-n 最低。如果 teacher forcing 和 rollout 差距过大说明曝光偏差严重如果 rollout 和 best-of-n 差距过大说明模型虽然会生成正确答案但不稳定需要更多探索或更合适的采样参数。我用这三组指标来共同判断模型该不该被部署比单看任何一个数字都靠谱得多。4.4 训练后的部署参数对齐一个常被跳过的步骤模型训练好之后还有一步很多人会忽略重新核对部署时的采样参数。我在项目里会专门写一个 checkpoint 检查脚本读取模型仓库里的 generation_config.json对比训练时用的 rollout 配置。如果发现不一致就输出警告。不要小看这一步我见过不止一次明明训练时用的 temperature0.7部署工程师图省事直接把典型值 temperature0.3 写上去了结果模型行为大变样白白背了一天的线上事故。5. 常见问题与排查技巧实录5.1 Reward 一直上涨但模型输出越来越啰嗦这个现象我遇到太多次了。最典型的原因是奖励函数里没有长度惩罚或者长度惩罚系数过小。模型发现“多写步骤”和“正确率”之间存在正相关尽管这个相关性很弱但 RL 强化的是统计规律只要多写步骤的期望奖励大于少写步骤模型就会往啰嗦的方向走。排查思路很简单把训练过程中每个 checkpoint 的输出平均长度画出来如果长度一路走高基本就是这个问题。解决方法是在奖励里加入长度正则项并且在行为快照中把“输出长度变化”作为和“reward 变化”同等重要的监控指标。如果发现长度涨了但最终答案准确率没涨那大概率是模型在刷过程分。5.2 KL 惩罚和奖励失衡导致模型能力回退有一段时间我用 PPO 训代码生成模型发现训练早期 reward 涨得很快但到中期突然崩掉生成质量甚至不如初始模型。后来复盘时发现KL 惩罚系数设得太低模型在很短几步内就偏离了初始策略跑到了奖励模型的高分区域但实际上那个区域只是奖励模型的盲区并不是真正的代码质量高区域。解决办法分两步一是加大 KL 惩罚或者用自适应 KL二是给策略更新加一个信任区间也就是每次更新后计算新旧策略的 KL如果超过阈值就回滚这次更新。PPO 里这步叫 early stoppingGRPO 里虽然没那么严格但也应该监控策略变化的幅度。这里分享一个我自己的经验值对于代码和数学这类客观任务KL 的目标区间设在 0.5~2.0 之间比较合适。太低了基本没约束太高了模型学不到新东西。当然这只是从我的任务场景里来的经验大家还是要以自己的验证集为准。5.3 推理 temperature 和训练差 0.2效果天差地别按前面说的采样参数应该统一。但很多时候不是我们不统一而是某个推理请求需要温度参数可调比如产品上要做“创造性”档位。这种情况下光靠统一配置就不够了需要对不同的 temperature 档位分别做评估并且最好在训练时就做 temperature conditioning——让模型在一定的温度范围内都能稳定输出。具体做法是在 RL 训练时rollout 的 temperature 不固定为一个值而是从一个区间比如 0.5~0.9里随机采样。这样模型在训练中见过多种采样条件下的生成环境推理时无论你用区间内哪个温度模型表现都不会太差。这个技巧有一点正则化的味道会让训练难度稍微增大但换来的是更稳健的泛化能力。5.4 验证器被 hack模型学会了“伪造”格式RLVR 虽然比 reward model 可靠但验证器本身也可能被攻击。我在一个数学项目里用过“提取关键字附近内容再比对”的验证方式结果模型学会了在答案区域写很多个可能的数字比如“答案可能是 5、6、7”提取逻辑会碰到其中某一个就判定正确导致 reward 虚高。后来我改成更严格的 xml 格式验证要求模型必须输出final_answer包裹的答案而且这个标签内只允许一个数字表达式否则整个输出判零分。这样做之后模型的 hack 行为立刻减少了。这个教训说明验证器的鲁棒性和奖励设计同样重要。设计规则验证器时一定要问自己如果我是一个一心只想刷分的模型我会怎么绕过这个规则然后提前把漏洞堵上。5.5 一个通用的“训练-推理一致性检查清单”最后整理一份我每次训练之前都会过的检查清单你可以直接抄走检查项训练侧推理侧是否一致temperaturerollout samplergeneration config是top_prollout samplergeneration config是max_tokensrollout truncationgeneration length limit是stop_sequencesrollout stop listinference stop list是prompt template训练数据格式线上 prompt 构造是reward metricRL reward线上评测标准尽量一致验证器规则/模型线上校验是每一行都可以打上“是”意味着你的训练和推理已经被尽可能地绑在一条绳上了。最后再分享一点个人感受做 LLM 后训练这段时间我最深的体会是模型训练领域最忌讳的就是“训练环境和部署环境脱钩”。很多时候上游同学辛苦调出来的模型到了下游线上表现不行不是模型能力不够而是两边设置压根就没对齐。如果你正在为“reward 涨了但线上没涨”而头疼先别急着推倒重来拿这份清单逐项核对一下很可能问题就出在某个不起眼的温度参数或终止条件上。另外一个小技巧每轮训练一定要把 rollout 样本存下来和 checkpoint 绑定在一起。这样出了问题可以回头分析模型在那个时间点到底生成了什么而不是只盯着曲线上的数字猜测。有了这些样本你很快就能发现模型是从哪一步开始跑偏的,甚至能定位到具体的 prompt 类型。这个习惯帮我省下了无数排查时间。