跳到主要内容

GRPO后训练死了,死于没人看的那几个尖峰

林默
林默

· 阅读约 5 分钟

8月12号挂arXiv那篇GCPO,我一开始没点开。组里有人丢到群里,说GRPO后训练那些老毛病——reward还涨着、eval忽然掉、length一路膨胀——终于有人从参数几何的角度给了个解释。那几天我正被一个9B模型的后训练搞得很烦,训练日志翻烂了也找不出任何要死的迹象:reward缓慢上爬,entropy还算体面,只有response length从三百多词悄悄爬到五百多。等eval数学题正确率掉下来,再回头找哪一步开始的,那轮早就没参考价值了。

第一反应是"又是训练不稳定"。这五个字最偷懒,等于什么都没说。真正该问的是:性能掉下来之前,权重方向到底发生了什么?

GCPO问的正好是这个。他们定义了一个几何量,principal-subspace overlap——每一步rollout更新跟预训练权重的主奇异子空间有多少重叠。不是离预训练权重多远,不是对SFT分布的KL,就是看这一步更新是不是踩进了预训练表示最集中的那几个方向。维度校正那边我就不展开了,反正各层子空间维度不一样,不校正低维方向天然会被低估。

把这东西按step画出来之后,出现了一项我觉得全文最值钱的特征:平均重叠非常低,低到会让人当成测量噪声。但把原始序列不降噪地铺开,会看见短得惊人的尖峰,通常只持续一两步。这些尖峰出现之后,eval才跟着掉。

先停一下,问个问题:为什么平均低还能致命?

因为训练里要命的事从来不是平均出来的。平均把瞬时事件压成一个不起眼的小台阶。尖峰出现的那一步,更新短暂跟预训练主奇异子空间对上了;后面几步尖峰没了,但已经偏掉的方向会沿着这个子空间继续走。等均线终于有反应,已经是事后验尸。

大家的监控习惯几乎全是这套平均逻辑。average KL、滑动平均entropy、几十步reward均值——全是为了让曲线好看牺牲瞬时信息。这些指标适合稳定期看趋势,当预警就是自己骗自己。真正的报警得留原始step值,盯最近窗口里的最大值,不是均值。

我之前写监控脚本时犯过这个错。失败版长这样:

# 每一步更新后投影到主奇异子空间,算重叠
U, _, _ = torch.linalg.svd(W_pretrained)
P = U[:, :k] @ U[:, :k].T
overlap = torch.norm(P @ dW) / (torch.norm(dW) + 1e-8)

# 报警别用这个
alarm = overlaps[-50:].mean()

按均值写,跑了两轮我都觉得这指标没用,曲线平得像死人心电图。后来把mean换成max over window,才发现是我自己把心电监护仪接成了呼吸机——只显示每分钟平均,骤停那一下根本不在表上。

既然问题出在更新偶尔一脚踩进主奇异子空间,最直接的防御就不是事后检测,是从构造上让你踩不进去。GCPO做的是硬性双边正交投影:把每一步更新投影到主奇异子空间的互补子空间里,几何上直接砍掉那部分方向。注意,不是加惩罚项,不是调小学习率,就是在更新里把那几个方向的分量直接归零。

这里得停一下,因为这个区别比看上去重得多。惩罚项是软的,尖峰那步量大一点照样冲过去;学习率调小是把好方向也等比例放慢。硬投影是物理隔离——不让那个方向的任何分量进更新,一步都进不去。手法粗暴,但它在尖峰面前不会失效。

代价也明摆着。硬投影默认预训练权重那几个主奇异方向在后训练阶段不该被大动,这假设在论文实验里成立,相当于用一条先验收回了稳定。万一某个下游任务真要重塑这些方向,这个先验会不会碍事,现在谁也不知道。论文在Qwen3-8B和GLM4-9B上跑了数学推理、代码生成、工具使用,GCPO压过GRPO,也压过DAPO、GSPO这些近期变体,最高抬了2.37个点,跟基础模型比最高27.69个点。通用能力没塌,长度膨胀没了,策略熵稳住了。一个看起来像限制的方法,反过来把之前被尖峰吞掉的性能捡了回来。

我一开始也以为这是精度换稳定。后来才猜,它滤掉的很可能不是有效更新,而是rollout采样和优势估计里漏出来的噪声方向。那些尖峰本来就不承载你想保留的泛化信息。

你可能会反对:这不就是把预训练记住的东西锁死了,跟LoRA那类低秩约束有什么区别?差别在目标。LoRA是把更新限定在一个低秩子空间,但你并不清楚那个子空间和主奇异子空间是否重叠;GCPO是明确把预训练主方向从更新里排除出去。一个是"只许你走这些路",另一个是"这些路你别踩"。后者来自对崩溃现象的观察,不是为了省显存。

DAPO、GSPO这几个变体都不是没努力,各自试图从不同角度把GRPO训稳,结果在论文设置里都被GCPO压过去了。至少在这个实验里,先从更新方向管住,比从目标函数那头修补更直接。

再往下的一层我还没剥透:这些瞬时尖峰本身是从哪来的?某些输入片段诱发,还是rollout采样偶然,还是优化器动量在某个点共振?论文暴露了症状、给了构造护栏,但根因我还没读到一个让我信服的解释。这层先放着,下次接着剥。

所以那天晚上那个9B模型的事,根子可能也在这里。我不太想再花力气争论这方法是不是又一个刷benchmark的名字——先把自己那套只看平均的监控改掉,每一轮把原始overlap的最大值印在训练日志最上面。死人不会提前发均值报告,丢掉的细节不会因为平均之后就变得不重要。我要看的是尖峰,尤其是那些被平滑掉之后显得特别安静的尖峰。

林默
林默

从具体轶事入口,一问一答把默认对的判断剥到设计权衡,主动暴露走过的弯路。

查看主页 →