1. 为什么KL散度惩罚是RLHF训练里最容易被忽视的暗礁
如果你最近在折腾大语言模型的强化学习微调,不管是PPO还是GRPO,大概率都遇到过这样一种情况:训练刚开始的时候reward涨得挺好看,policy loss也在降,但跑着跑着模型突然开始胡言乱语,输出重复、逻辑断裂,甚至直接退化成“复读机”。你去看日志,reward可能还在涨,但生成质量已经崩了。这个时候十有八九,问题就出在KL散度惩罚这一项上。
KL散度惩罚,英文叫KL Divergence Penalty,在RLHF(Reinforcement Learning from Human Feedback)的语境下,它做的事情说白了就一句话:约束当前策略模型不要跑得离参考模型太远。参考模型通常是SFT(Supervised Fine-Tuning)之后的模型,也就是那个已经能好好说话、指令跟随还不错的版本。强化学习的目的是让模型更符合人类偏好,但如果没有约束,模型会为了拿高reward而走捷径,输出一些人类看起来莫名其妙但reward model给高分的文本。KL惩罚就是那根拴着风筝的线,让模型在探索的同时不至于飞到外太空。
这个项目标题“大语言模型研究17——强化学习中KL散度惩罚问题”,核心关注的就是这根线该怎么拴、拴多紧、什么时候松什么时候紧。涉及的关键词包括大语言模型、强化学习、KL散度、PPO、GRPO,这些都是当前LLM对齐训练里绕不开的技术栈。适合的读者是那些已经跑过或者正准备跑RLHF训练、对PPO或GRPO有基本了解、但在调参和稳定性上踩过坑的从业者。如果你还在用默认的KL系数跑训练然后纳闷为什么模型会崩,这篇内容应该能帮你省下不少试错时间。
我自己的经验是,KL惩罚这一项看起来只是一个系数加一个散度计算,但它背后牵扯到的东西非常多:参考模型的选择、KL估计器的方差、系数是固定还是自适应、GRPO里KL怎么放、batch内不同样本的KL差异怎么处理,每一个点都能让训练结果天差地别。接下来我会把这些拆开,结合实操中踩过的坑,把KL散度惩罚这件事讲透。
2. KL散度惩罚的核心机制与方案选型逻辑
2.1 KL散度在RLHF里到底惩罚的是什么
先把这个概念说清楚。在RLHF的PPO训练中,我们有一个策略模型π_θ(也就是正在训练的LLM),一个参考模型π_ref(通常是冻结的SFT模型),还有一个reward model。对于每一个prompt,策略模型生成一个response,reward model给这个response打分,然后PPO用这个分数来更新策略。
但这里有个问题:reward model只是一个近似,它并不能完美代表人类偏好。策略模型会找到reward model的漏洞,生成一些reward很高但实际很差的文本。这就是所谓的reward hacking。KL散度惩罚的作用就是限制策略模型偏离参考模型的程度,因为参考模型至少是一个已知的、行为合理的模型。
具体来说,KL惩罚项通常写成:
KL(π_θ || π_ref) = E_{x~π_θ}[log π_θ(x) - log π_ref(x)]在PPO的损失函数里,这一项会被乘以一个系数β,加到总损失里。β越大,策略模型越不敢偏离参考模型;β越小,策略模型越自由,但也越容易崩。
这里有一个很关键的细节:KL散度的方向。我们用的是KL(π_θ || π_ref)而不是KL(π_ref || π_θ)。这两个是不一样的。前者是“策略模型看到自己生成的文本时,参考模型觉得有多意外”,后者是“参考模型生成的文本,策略模型觉得有多意外”。在RLHF里我们用前者,因为我们要惩罚的是策略模型生成了参考模型不太可能生成的文本。这个方向的选择直接影响了惩罚的效果,用反了会导致训练信号完全错误。
2.2 为什么PPO和GRPO对KL的处理不一样
PPO和GRPO都是策略梯度方法,但它们在KL惩罚的处理上有本质区别。PPO是标准的actor-critic架构,有一个value network来估计优势函数,KL惩罚通常直接加在reward里或者加在loss里。GRPO(Group Relative Policy Optimization)是DeepSeek提出来的方法,它去掉了value network,改用一组样本的相对reward来计算优势。这个结构差异导致KL惩罚的放置位置和计算方式都需要调整。
在PPO里,常见的做法是在每个token的reward里减去β * KL_per_token。也就是说,reward不再是单纯的reward model输出,而是:
total_reward = reward_model_score - β * KL(π_θ || π_ref)这样做的好处是KL惩罚直接影响了优势函数的计算,策略更新时会自然考虑到偏离参考模型的代价。但缺点是KL的计算是逐token的,方差比较大,尤其是当response长度不一致时,不同样本的KL惩罚差异会很大。
GRPO因为没有value network,优势是通过组内归一化得到的。KL惩罚通常直接加在最终的优势值上,或者作为loss的一个独立项。GRPO的论文里用的是后者,把KL项直接加到策略损失里。这样做的好处是实现简单,但坏处是KL惩罚和策略梯度的交互方式变了,需要重新调β。
我实测下来的感受是,PPO的KL惩罚更“平滑”,因为它是逐token影响的,策略模型在每个token上都会感受到约束。GRPO的KL惩罚更“粗暴”,它是在整个response级别上施加的,容易出现某些response被惩罚得很厉害而另一些几乎没感觉的情况。这就引出了下一个问题:β该怎么设。
2.3 固定β还是自适应β:一个让人纠结的选择
最开始的RLHF实现(比如OpenAI的InstructGPT)用的是固定β,通常设在0.01到0.1之间。但这个范围太宽了,0.01和0.1的效果可能完全相反。后来大家发现固定β有个根本问题:训练初期策略模型偏离参考模型不多,KL惩罚应该小一点让模型充分探索;训练后期策略模型已经偏离很多了,KL惩罚应该大一点防止崩掉。固定β没法适应这个变化。
于是自适应β出现了。自适应β的核心思路是设定一个目标KL值(比如6或者10),然后根据当前KL值动态调整β。如果当前KL超过目标,就增大β;如果低于目标,就减小β。这就像一个恒温器,让KL保持在一个合理的范围内。
但自适应β也有坑。首先是目标KL值的选择,设太小模型学不到东西,设太大模型会崩。其次是调整的步长,调太快会导致β震荡,调太慢又跟不上训练动态。我试过的一个比较稳的方案是:
target_kl = 6.0 kl_coef = 0.1 kl_coef_min = 0.001 kl_coef_max = 1.0 adjust_rate = 1.5 if current_kl > target_kl * 1.5: kl_coef = min(kl_coef * adjust_rate, kl_coef_max) elif current_kl < target_kl / 1.5: kl_coef = max(kl_coef / adjust_rate, kl_coef_min)这个逻辑是:只有当KL偏离目标超过50%时才调整,避免频繁微调导致的震荡。调整倍率1.5是一个经验值,实测下来比较稳。上下限的设置是为了防止β跑到极端值,导致训练完全停滞或者完全失控。
2.4 参考模型的选择:不是随便拿一个就行
参考模型的选择经常被忽视,但它对KL惩罚的效果影响巨大。最常用的选择是SFT模型,也就是在人类标注数据上微调过的模型。但这里有几个变体:
第一种是用SFT模型作为参考,这是最标准的做法。SFT模型已经能好好说话,KL惩罚约束的是“不要偏离SFT的行为太远”。
第二种是用base model作为参考,也就是没有经过SFT的原始模型。这样做的话KL惩罚会非常强,因为base model和策略模型的输出分布差异很大,KL值会很高,β需要设得很小。一般不推荐,除非你有特殊需求。
第三种是用一个更强的模型作为参考,比如用GPT-4级别的模型。这样做的好处是参考模型本身质量很高,策略模型偏离它意味着质量下降。但坏处是你需要一个很强的模型来做推理,成本很高。
我自己的经验是,如果你的SFT模型质量还不错,就用SFT模型做参考。如果SFT模型本身就有问题(比如有幻觉、重复),那KL惩罚会把这些问题也约束进去,策略模型很难通过RLHF来修正。这种情况下可能需要先重新做SFT,或者用一个更干净的参考模型。
还有一个细节:参考模型在训练过程中是冻结的,但它的推理也是要消耗显存的。如果你用LoRA做SFT,参考模型可以是base model加LoRA权重,也可以是合并后的模型。前者省显存但推理慢一点,后者快但占显存。这个取舍取决于你的硬件条件。
3. KL散度惩罚的实操细节与参数计算
3.1 KL估计器的选择与方差问题
KL散度的计算在理论上是简单的,但在实操中,我们只能从采样中估计KL。不同的估计器方差不同,直接影响训练的稳定性。
最常用的估计器是k1估计器:
KL ≈ log(π_θ(x) / π_ref(x))这个估计器是无偏的,但方差很大。因为它是逐token计算的,每个token的log ratio可能波动很大,尤其是当π_θ和π_ref在某些token上差异很大时。
另一个是k3估计器:
KL ≈ (π_ref(x) / π_θ(x)) - log(π_ref(x) / π_θ(x)) - 1这个估计器是有偏的,但方差小很多。在PPO的实现里(比如TRL库),默认用的是k3估计器。我实测下来,k3确实比k1稳,尤其是在训练初期KL值比较大的时候。但k3的偏差在KL值很小时会变得明显,所以如果你的目标KL设得很小(比如小于1),可能需要考虑k1。
还有一个细节是KL的聚合方式。是逐token算完取平均,还是先求和再平均?逐token取平均的话,长response和短response的KL贡献是一样的;先求和的话,长response的KL贡献更大。在PPO里通常是逐token算然后取平均,因为每个token的生成都是一个决策点。但在GRPO里,因为是response级别的优势,KL通常也是response级别聚合的。
这里有一个我踩过的坑:如果你的response长度差异很大,逐token平均KL会导致短response的KL被高估,长response的KL被低估。因为短response里如果有一个token的KL很大,平均下来就很大;长response里同样的KL被稀释了。解决办法是在计算KL时用mask,只计算有效token的KL,然后按有效长度平均。这个在TRL库里有现成的实现,但如果你自己写训练循环,很容易漏掉。
3.2 β的初始值与调度策略
β的初始值怎么设,这个问题没有标准答案,但有一些经验规律。
如果你的参考模型是SFT模型,策略模型也是从SFT模型初始化的,那么训练初期KL应该接近0。这个时候β可以设小一点,比如0.01到0.05,让模型有足够的空间探索。随着训练进行,KL会逐渐增大,β也需要相应增大。
如果用的是自适应β,初始值其实不太重要,因为几个step之后就会调整到合理范围。但初始值会影响前几个step的训练,如果设得太大,前几个step策略模型几乎不更新;设得太小,前几个step可能KL就爆了。我一般设0.04作为初始值,实测下来比较稳。
β的调度策略有两种:一种是基于step的线性或余弦调度,比如从0.01线性增加到0.1;另一种是基于KL的自适应调度。前者简单但不够灵活,后者复杂但更适应训练动态。我倾向于用自适应,因为LLM训练的动态变化太快了,固定调度很难跟上。
但自适应也有一个问题:如果KL突然爆了(比如某个batch的reward特别高,策略模型一下子跑偏了),自适应β会迅速增大,可能导致接下来几个step策略模型几乎不更新。这其实是一种保护机制,但也会浪费一些训练step。我的做法是加一个平滑项:
kl_coef = 0.9 * kl_coef_old + 0.1 * kl_coef_new这样β的变化不会太剧烈,给策略模型一个缓冲。
3.3 逐token KL与序列级KL的取舍
前面提到了逐token KL和序列级KL的区别,这里展开说一下实操中的取舍。
逐token KL的好处是粒度细,每个token的生成都受到约束。坏处是方差大,而且计算量大。在PPO里,因为本身就是逐token更新的,逐token KL是自然的选择。但在GRPO里,因为是序列级别的优化,逐token KL需要额外聚合,反而增加了复杂度。
序列级KL的好处是简单,一个response一个KL值,直接加到优势上就行。坏处是粒度粗,一个response里如果只有少数几个token偏离很大,序列级KL可能被平均掉,约束不够精确。
我试过两种方案,最后在GRPO里用的是混合方案:逐token算KL,然后取最大值的某个比例(比如0.5倍最大值加0.5倍平均值)作为序列级KL。这样既能捕捉到偏离最大的token,又不至于被单个异常token主导。这个方案不是标准做法,但实测下来比纯平均或纯最大值都稳。
还有一个细节是KL的截断。有些实现会对逐token KL做截断,比如超过某个阈值就截断,防止个别token的KL过大影响整体。这个做法有争议,因为截断会引入偏差,但在训练稳定性上确实有帮助。我一般设截断阈值为10,超过的按10算。这个值怎么来的?其实是拍脑袋的,但实测下来10以上的KL基本都是异常值,截断掉影响不大。
3.4 不同训练阶段的KL惩罚策略
LLM的RLHF训练通常分为几个阶段,每个阶段KL惩罚的策略应该不一样。
第一阶段是冷启动阶段,也就是训练刚开始的几百个step。这个时候策略模型和参考模型还很接近,KL值很小。KL惩罚应该设得很小,让模型充分探索。我一般在这个阶段把β设在0.01到0.02,目标KL设在3到5。
第二阶段是稳定训练阶段,策略模型开始偏离参考模型,reward也在稳步上升。这个时候KL惩罚要适中,既不能让模型跑偏,也不能限制太死。β通常在0.04到0.08,目标KL在6到10。
第三阶段是收敛阶段,策略模型的reward增长放缓,KL值趋于稳定。这个时候KL惩罚可以适当增大,防止模型在收敛点附近震荡。β可以设到0.1以上,目标KL在10到15。
但这个阶段划分不是绝对的,具体要看你的reward曲线和KL曲线。如果reward还在涨但KL已经很大了,说明模型在reward hacking,需要增大β。如果reward不涨了但KL很小,说明模型还没充分探索,需要减小β。
我自己的做法是画一张双轴图,左轴是reward,右轴是KL,然后观察两条曲线的相对走势。如果KL涨得比reward快,说明β太小;如果reward涨得比KL快,说明β合适;如果reward不涨KL也不涨,说明β太大或者学习率太小。
4. 常见问题排查与避坑经验实录
4.1 KL值突然飙升怎么办
这是最常见的问题。训练到一半,KL值突然从个位数跳到几十甚至上百,reward也跟着崩。原因通常有几个:
第一个原因是某个batch的reward异常高,策略模型一下子往那个方向跑偏了。这种情况在GRPO里尤其常见,因为GRPO的优势是组内归一化的,如果组内有一个样本的reward特别高,它的优势会很大,策略模型会过度拟合这个样本。
解决办法是加reward clipping,把reward限制在一个合理范围内。比如reward model的输出通常在-10到10之间,超过的截断。另外可以在优势计算时加一个标准差归一化,防止单个样本的优势过大。
第二个原因是学习率太大,策略模型更新步子太大,一下子跑出了参考模型的分布范围。解决办法是降低学习率,或者加梯度裁剪。我一般设学习率为1e-6到5e-6,梯度裁剪为1.0。
第三个原因是参考模型和策略模型的tokenizer不一致,导致KL计算错误。这个坑很隐蔽,因为tokenizer不一致时,log prob的计算会对不上,KL值会异常。解决办法是确保参考模型和策略模型用同一个tokenizer,或者在计算KL时对齐token。
第四个原因是数据问题,比如某个prompt特别长或者特别短,导致KL计算异常。解决办法是过滤掉过长或过短的样本,或者在KL计算时做长度归一化。
排查KL飙升的步骤我一般是这样:先看是全局飙升还是个别batch飙升,如果是全局的,检查学习率和β;如果是个别batch的,检查那个batch的数据和reward分布。然后看是逐token KL飙升还是序列级KL飙升,如果是逐token的,检查是哪些token的KL大,通常是那些生成了罕见token的位置。
4.2 模型输出退化但KL值正常
这种情况更隐蔽:KL值看起来很正常,在目标范围内,但模型输出质量明显下降,开始重复、胡言乱语。这说明KL惩罚没有起到应有的约束作用。
原因通常是参考模型本身有问题。如果参考模型在某些输入上就会输出重复文本,那么KL惩罚约束的是“不要偏离这个重复行为”,策略模型学到的就是重复。解决办法是换一个更干净的参考模型,或者在SFT阶段就解决重复问题。
另一个原因是KL惩罚的粒度不对。如果用的是序列级KL,而退化只发生在response的局部(比如结尾重复),序列级KL可能捕捉不到。解决办法是改用逐token KL,或者加一个局部KL惩罚项。
还有一个原因是reward model本身有问题,给退化文本打了高分。这种情况下KL惩罚再强也没用,因为reward信号就是错的。解决办法是重新训练reward model,或者加一个人工规则来惩罚重复。
我遇到过一次这种情况,排查了很久才发现是reward model对短文本有偏好,导致策略模型学会了生成很短的、无意义的回复。KL值一直很正常,因为策略模型没有偏离参考模型太多,但输出质量就是不行。后来在reward里加了一个长度惩罚项才解决。
4.3 GRPO里KL惩罚的特殊问题
GRPO因为去掉了value network,KL惩罚的处理和PPO不太一样,有几个特殊问题。
第一个问题是KL惩罚和组内归一化的交互。GRPO的优势是组内归一化的,如果组内所有样本的KL都很大,归一化之后KL惩罚的影响会被稀释。解决办法是把KL惩罚加在归一化之后,而不是之前。
第二个问题是GRPO的样本效率。因为GRPO需要一组样本,如果组内样本的KL差异很大,优势估计的方差会很大。解决办法是增大组的大小,或者对KL做组内归一化。
第三个问题是GRPO的KL惩罚系数需要重新调。PPO的β经验值在GRPO里不一定适用,因为GRPO的损失函数结构不同。我实测下来,GRPO的β通常要比PPO小一些,因为GRPO的更新步子更大,需要更小的KL惩罚来平衡。
还有一个GRPO特有的问题是参考模型的计算开销。GRPO需要为每个样本计算KL,如果组很大,参考模型的前向计算量会很大。解决办法是用更小的参考模型,或者用LoRA来减少计算量。
4.4 KL惩罚与其他正则项的配合
KL惩罚不是唯一的正则项,实际训练中还会用到其他正则,比如熵正则、L2正则等。这些正则项之间会相互影响,需要协调。
熵正则鼓励策略模型保持探索,KL惩罚鼓励策略模型不要偏离参考模型。这两个目标有时候是冲突的:熵正则让模型更随机,KL惩罚让模型更确定。解决办法是调小熵正则的系数,或者让KL惩罚主导。
L2正则约束模型参数不要太大,KL惩罚约束模型输出分布不要偏离。这两个目标通常是一致的,但L2正则太强会限制模型的学习能力。我一般设L2正则系数为0.01,KL惩罚系数根据训练阶段调整。
还有一个是reward shaping,也就是在reward里加一些人工设计的项,比如长度惩罚、重复惩罚。这些项和KL惩罚是互补的,KL惩罚约束的是分布层面的偏离,reward shaping约束的是具体行为。我一般会加一个重复惩罚,系数设0.1,防止模型输出重复文本。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| KL值突然飙升 | 学习率太大 | 检查学习率和梯度范数 | 降低学习率,加梯度裁剪 |
| KL值突然飙升 | reward异常高 | 检查reward分布 | 加reward clipping |
| KL值突然飙升 | tokenizer不一致 | 对比参考模型和策略模型的tokenizer | 统一tokenizer |
| KL值正常但输出退化 | 参考模型有问题 | 用参考模型单独推理看输出 | 换参考模型或重新SFT |
| KL值正常但输出退化 | KL粒度太粗 | 检查是逐token还是序列级KL | 改用逐token KL |
| KL值正常但输出退化 | reward model有问题 | 用reward model打分退化文本 | 重新训练reward model |
| GRPO里KL惩罚无效 | 归一化稀释了KL | 检查KL加在归一化之前还是之后 | 加在归一化之后 |
| GRPO里KL惩罚无效 | β太大或太小 | 扫描β值看KL曲线 | 重新调β |
| 训练不稳定 | 自适应β震荡 | 检查β的变化曲线 | 加平滑项,降低调整速率 |
| 训练不稳定 | 熵正则和KL冲突 | 检查熵和KL的走势 | 调小熵正则系数 |
5. 一套可复现的KL惩罚配置方案
5.1 完整参数配置与代码框架
基于上面的分析,我整理了一套在PPO和GRPO里都实测过的KL惩罚配置。这套配置不是最优的,但在我试过的几个模型规模(1.5B到7B)和几个数据集上都比较稳。
先看PPO的配置:
# PPO KL惩罚配置 kl_coef_init = 0.04 kl_coef_min = 0.001 kl_coef_max = 0.5 target_kl = 8.0 kl_estimator = "k3" # 用k3估计器,方差小 kl_clip = 10.0 # 逐token KL截断 kl_smooth = 0.9 # β平滑系数 adjust_rate = 1.5 # β调整倍率 adjust_threshold = 1.5 # KL偏离目标的阈值 # 训练循环里的KL处理 def compute_kl_penalty(log_probs, ref_log_probs, mask): kl_per_token = log_probs - ref_log_probs kl_per_token = torch.clamp(kl_per_token, max=kl_clip) kl_per_token = kl_per_token * mask kl_mean = kl_per_token.sum() / mask.sum() return kl_mean def adjust_kl_coef(current_kl, kl_coef): if current_kl > target_kl * adjust_threshold: new_coef = min(kl_coef * adjust_rate, kl_coef_max) elif current_kl < target_kl / adjust_threshold: new_coef = max(kl_coef / adjust_rate, kl_coef_min) else: new_coef = kl_coef return kl_smooth * kl_coef + (1 - kl_smooth) * new_coefGRPO的配置稍有不同:
# GRPO KL惩罚配置 kl_coef_init = 0.02 # GRPO的β通常比PPO小 target_kl = 6.0 kl_estimator = "k3" kl_clip = 10.0 kl_smooth = 0.9 adjust_rate = 1.3 # GRPO的调整速率可以慢一点 adjust_threshold = 1.5 # GRPO里KL加在优势归一化之后 def compute_grpo_advantage(rewards, kl_values): # 先算组内归一化优势 mean_reward = rewards.mean() std_reward = rewards.std() + 1e-8 advantages = (rewards - mean_reward) / std_reward # 再减KL惩罚 advantages = advantages - kl_coef * kl_values return advantages这套配置的关键点在于:β的初始值、目标KL、调整速率都是经过实测的,不是拍脑袋来的。但我要强调的是,这些值不是万能的,你需要根据自己的模型规模、数据分布、reward model质量来微调。
5.2 监控指标与日志记录
KL惩罚的调参离不开监控。我一般会记录以下指标:
第一个是逐step的KL均值、最大值、最小值。均值反映整体偏离程度,最大值反映最极端的偏离,最小值反映是否有样本几乎没偏离。如果最大值远大于均值,说明有个别样本的KL异常大,需要检查数据。
第二个是β的变化曲线。如果β频繁震荡,说明调整速率太快;如果β一直贴着上限或下限,说明目标KL设得不合理。
第三个是reward和KL的比值。这个比值反映的是“每单位KL换来了多少reward”。如果比值在下降,说明KL惩罚的边际收益在递减,可能需要调整β。
第四个是逐token KL的分布。我一般会画一个直方图,看KL的分布是长尾还是集中。如果是长尾,说明有少数token的KL很大,可能需要加截断。
日志记录我一般用wandb或者tensorboard,每10个step记录一次。记录的内容包括:KL均值、KL最大值、β值、reward均值、policy loss、value loss(PPO)、熵值。这些指标放在一起看,基本能判断训练是否健康。
5.3 不同模型规模的KL惩罚调整
模型规模不同,KL惩罚的策略也需要调整。我试过1.5B、3B、7B三个规模,感受如下:
1.5B的模型,策略模型和参考模型的分布差异通常比较大,因为小模型的表达能力有限,SFT之后的行为可能不够稳定。KL惩罚需要设大一点,β初始值0.06左右,目标KL10左右。
3B的模型,分布差异适中,β初始值0.04,目标KL8左右。
7B的模型,分布差异较小,因为大模型的SFT效果通常更好,策略模型和参考模型更接近。β初始值0.02,目标KL6左右。
但这个规律不是绝对的,还要看SFT的质量。如果SFT做得很好,即使小模型KL也可以设小一点。如果SFT做得差,大模型也需要大β。
还有一个规模相关的细节是KL的计算精度。小模型的log prob数值范围可能比较大,KL计算时容易出现数值不稳定。解决办法是用float32计算KL,或者在log prob上做clip。大模型通常没这个问题,但计算量大,需要考虑显存。
5.4 从零开始调KL惩罚的实操步骤
如果你拿到一个新模型和新数据,不知道怎么设KL惩罚,可以按以下步骤来:
第一步,先用一个很小的β(比如0.001)跑100个step,观察KL的自然增长。这个时候KL惩罚几乎不起作用,KL反映的是策略模型在reward驱动下的自然偏离速度。如果KL增长很快(比如100个step就到20),说明reward信号很强,需要较大的β。如果KL增长很慢(100个step才到2),说明reward信号弱,β可以小一点。
第二步,根据第一步的观察设一个初始β,然后跑500个step,观察KL是否稳定在目标附近。如果KL持续上升超过目标,增大β;如果KL一直低于目标,减小β。
第三步,在500到2000step之间,用自适应β让KL稳定在目标附近。这个时候观察reward曲线,如果reward还在涨,说明β合适;如果reward停滞,说明β太大;如果reward震荡,说明β太小或者调整速率太快。
第四步,2000step之后,如果reward增长放缓,可以适当增大β,让模型在收敛点附近更稳定。但不要一下子增太多,否则模型会停止学习。
这个流程我跑过几次,基本能在3000step内找到比较合适的β。当然,如果你的数据或reward model有特殊问题,可能需要更长时间。
5.5 一些反直觉的经验
最后分享几个我在调KL惩罚时遇到的违反直觉的经验。
第一个反直觉的经验是:KL惩罚不是越大越好。很多人觉得KL惩罚大一点更安全,模型不会崩。但实际上KL惩罚太大会导致模型几乎不更新,reward不涨,训练完全停滞。我试过β=1.0的情况,KL确实很小,但reward也完全不涨,模型和SFT版本几乎一样。所以KL惩罚要“恰到好处”,既要约束又要给空间。
第二个反直觉的经验是:KL值小不一定好。KL值小说明策略模型和参考模型接近,但如果参考模型本身就不够好,KL值小意味着策略模型也没学好。我遇到过一种情况,KL一直很小,但reward也不涨,后来发现是参考模型在某些输入上输出很差,策略模型被约束在了一个差的行为模式里。解决办法是换参考模型,而不是继续调β。
第三个反直觉的经验是:GRPO的KL惩罚不一定比PPO简单。虽然GRPO去掉了value network,看起来更简单,但KL惩罚的处理反而更复杂,因为涉及到组内归一化和KL的交互。我一开始以为GRPO可以照搬PPO的KL配置,结果发现完全不行,β需要重新调,KL的放置位置也需要调整。
第四个反直觉的经验是:KL惩罚和reward model的质量是耦合的。如果reward model很准,KL惩罚可以小一点,因为reward信号本身就足够约束模型。如果reward model不准,KL惩罚需要大一点,防止模型利用reward model的漏洞。所以调KL惩罚之前,先确保reward model的质量是过关的。
这些经验都是我在实际训练中踩坑踩出来的,希望对你有帮助。KL散度惩罚这件事,理论上看很简单,但实操中的细节非常多,每一个细节都可能影响训练结果。多观察、多记录、多对比,慢慢就能找到适合自己场景的配置。