DeepSeek:GRPO算法助力大模型训练资源优化剖析
GRPO,一种新兴的强化学习方法,是DeepSeek R1采用的训练办法。
此篇博客文章,笔者将从零基础起,循序渐进地为大家介绍一种在强化学习中极具实用价值的技术——GRPO(Group Relative Policy Optimization)。若你是首次听闻该概念,也无需慌乱,笔者会带领你从最基础的强化学习背景知识入手,逐步剖析其来龙去脉,接着结合实例讲解GRPO在实际应用中的思路与操作示例,最后对比其他近似方法,瞧瞧它和当下主流的PPO(近端策略优化)等方法究竟有何区别与关联。
强烈建议阅毕此帖后再研读另一帖——适度练习,强化记忆:【DeepSeek】大模型强化学习训练GRPO算法,你学会了吗?
GRPO原论文链接:https://arxiv.org/abs/2402.03300
GRPO中译文链接:https://blog.csdn.net/qq_38961840/article/details/145384346
为何要关注强化学习与策略优化?
在正式介绍GRPO之前,笔者想先探讨一个较为根本的问题:为何需要策略优化?又为何要重视强化学习? 实际上,无论是在推荐系统、对话系统中,还是在数学推理、大语言模型对齐(alignment)场景里,最终我们都期望模型能输出“更优”或“更契合某些偏好”的序列。深度强化学习(DRL)借助“奖励”(reward)来衡量我们期望的目标,从而对生成过程进行引导。策略优化(Policy Optimization)是其中一个关键的方法论。
在语言模型的应用中,比如要让模型解出数学题、满足人类对话偏好(例如避免不良输出,或给出更详尽解释),通常先用大规模的无监督或自监督训练打好基础,然后通过一些“监督微调”(SFT)进一步让模型初步符合需求。然而,SFT有时难以将人类或某些高层目标的偏好显式地整合进去。这时,“强化学习微调”便登场了。PPO是其中的代表性算法,但它也有自身的痛点,比如要维护额外的大价值网络,在大模型场景中对内存与计算的需求不容忽视。GRPO正是在这样的背景下应运而生。
回顾:强化学习中的基础概念
智能体、环境与交互
在传统的强化学习框架中,一般存在一个“智能体”(Agent)和一个“环境”(Environment)。智能体每一步会依据自身策略π(s)去确定一个动作a,然后环境会根据这个动作给出新的状态和一个奖励r,智能体收集这个奖励并继续下一步。这种循环往复构成一个时间序列过程,直至到达终止条件(如达成目标或超时等)。
不过在语言模型(尤其是大型语言模型,LLM)中,也能把一个“问题”(例如一段文本提示prompt)当作环境给出的状态,然后模型(智能体)产出下一个token(动作),不断重复,直到生成一段完整的回答;人类或额外的奖励模型再给予一个整段回答的质量分,或在每个token(或步骤)时刻给出一个局部奖励。虽然大语言模型看似与传统强化学习中的“马尔可夫决策过程(MDP)”有一些差异,但本质上也能抽象为状态—动作—奖励—状态—动作的机制。
状态、动作、奖励、策略
- 状态s:对于语言模型而言,可以把已经生成的token序列(以及当前问题)视为一种压缩后的状态;在传统RL里则是环境观测到的一些向量或特征。
- 动作a:在语言模型生成场景,动作可以是“在词表vocabulary里选出下一个token”;在机器人或游戏环境中就是“移动、旋转、跳跃”等操作。
- 奖励r:衡量好坏程度的指标。在语言模型对齐中,常见做法是训练一个奖励模型来打分;或者直接用规则判断回答是否正确等。
- 策略π:智能体在状态s下如何选动作a的概率分布函数π(a|s)。在语言模型里,这就是产生每个token的条件分布。
价值函数与优势函数:为何需要它们
在PPO等典型策略梯度方法中,通常还会引入一个价值函数(Value Function),它大致表示在当前状态下,未来能期望得到多少奖励;更进一步,我们可以在每个动作之后去看“优势函数(Advantage Function)”,衡量“这个动作比平均水平好多少”。为何要引入价值函数或优势函数?因为在训练时,如果仅有奖励的直接指引,每个样本都可能方差很大,收敛缓慢。价值函数的引入能够降低训练方差,提升训练效率。
从传统方法到近端策略优化(PPO)的发展历程
策略梯度与Actor-Critic范式
策略梯度方法(Policy Gradient)是强化学习中一种较为直接的做法:我们直接对策略函数πθ(a|s)进行建模,计算相应的梯度来最大化期望回报。它无需像价值迭代那样枚举所有状态-动作组合,也无需像Q-learning那样先学Q再做贪心决策。策略梯度能够很好地适应高维连续动作空间,以及更灵活的策略表示。
不过,如果单纯使用REINFORCE等策略梯度方法,每一步更新都可能有很大方差,甚至出现不稳定现象。为此,研究者们提出了Actor-Critic框架:将“策略”称作Actor,将“价值函数”称作Critic,两者共同训练,让Critic起到估计价值、降低方差的作用。
PPO的核心思路:clip与优势函数
后来出现了近端策略优化(PPO),它是在Actor-Critic的基础上,为了避免策略更新过猛导致训练不稳定,引入了一个剪切(clip)技巧,即把
πθ(at|st)/πθold(at|st)
这个概率比率限制在[1−ε, 1+ε]区间内。这样就能防止每次更新过度,从而保持相对稳定。但要在实践中实现PPO,需要在每个时间步都有一个价值网络去估计优势函数
At = rt + γVψ(st+1) − Vψ(st)
或者更常用的是广义优势估计(GAE),来让更新时的方差更小。但问题在于,当我们的模型规模急剧增大——如在数十亿甚至千亿参数的语言模型上应用PPO,会发现训练资源消耗巨大。因为这个价值网络本身通常要和策略网络“同样大”或近似大,并且需要在每个token都计算价值,从而带来可观的内存占用与计算代价。
PPO的局限性:模型规模与价值网络的负担
小模型时代,这或许还可以,但在当代的LLM背景下,我们需要极度节省训练内存与计算资源。尤其当进行RLHF(Reinforcement Learning from Human Feedback)或者别的对齐强化学习时,还要搭建奖励模型Reward Model、价值网络Critic Model,再加上本身的策略模型Actor Model,算力负担往往令人头疼。
这就是GRPO的问题背景:如何在保证PPO那样的收益(稳定、可控等)前提下,减少对昂贵价值网络的依赖? 背后的核心思路是:用“分组输出相互比较”的方式来估计基线(Baseline),从而免去对价值网络的全程参与。
GRPO(分组相对策略优化)是什么?
GRPO提出的动机:为何需要它
基于上节对PPO的简要回顾,你应该能感受到PPO在大模型时代的痛点。要么牺牲训练速度和成本,要么需要想其他方法来绕过价值网络的全程参与。而GRPO(全称Group Relative Policy Optimization)正是对这一问题的一种解答。
核心动机:在许多实际应用中,奖励往往只在序列末端才给一个分数(称之为Result/Oucome Supervision),或在每一步给一些局部分数(Process Supervision)。不管怎样,这个奖励本身通常是离散且比较稀疏的,让价值网络去学习每个token的价值,可能并不划算。而如果我们在同一个问题q上采样多份输出o₁, o₂, …, oG,对它们进行奖励对比,就能更好地推断哪些输出更好。由此,就能对每个输出的所有token做相对评分,无需明确地学到一个价值函数。
在数理推理、数学解题等场景,这个技巧尤其管用,因为常常会基于同一个题目q生成多个候选输出,有对有错,或者优劣程度不同。那就把它们的奖励进行一个分组内的比较,以获取相对差异,然后把相对优势视为更新策略的依据。
GRPO的关键点一:分组采样与相对奖励
GRPO中,“分组”非常关键:我们会在一个问题q上,采样GRPO份输出o₁, o₂, …, oG。然后把这组输出一起送进奖励模型(或规则),得到奖励分r₁, r₂, …, rG。下一步做什么呢?我们不是单纯地对每个输出和一个固定基线比较,而是先把r = {r₁, r₂, …, rG}做一个归一化(如减去平均值再除以标准差),从而得出分组内的相对水平。这样就形成了相对奖励r~i。最后我们会把这个相对奖励赋给该输出对应的所有token的优势函数。
简单来说:多生成几份答案,一起比较,再根据排名或分数差更新,能更直接、简洁地反映同一问题下的优劣关系,而不需要用一个显式的价值网络去学习所有中间时刻的估计。
GRPO的关键点二:无需价值网络的高效策略优化
因为不再需要在每个token上拟合一个价值函数,我们就能大幅节省内存——不必再维护和Actor同样大的Critic模型。这不仅是存储层面的解放,也是训练过程中的显著加速。
当然,GRPO也会引入一些新的代价:我们要为每个问题采样一组输出(不止一条),意味着推理时要多花点算力去生成候选答案。这种方法和“自洽性采样(Self-consistency)”思路也有点类似,如果你了解一些数学题多候选合并判断的做法,就能感受到其中的相通之处。
GRPO的原理剖析
数学公式:与PPO的对比
图1: PPO和GRPO的对比。GRPO放弃了价值模型,从分组得分中估计,显著减少了训练资源
先让我们回顾一下PPO的核心目标函数:在PPO的简化推导里,假设一次只更新一步,那么
JPPO(θ) = E[q∼P(Q), o∼πθold(O∣q)] [1/∥o∥ ∑ₜ=1^∥o∥ πθ(ot∣q, o<t)/πθold(ot∣q, o<t) At]
其中
- q是从一个训练集问题分布P(Q)中采样来的问题;
- o是在旧策略πθold下生成的输出序列;
- ∥o∥是输出序列的长度(token数);
- At是优势函数,需要一个单独价值网络Vψ来估计。
而GRPO所做的是:同样从问题分布中取到q,但这一次我们会针对同一个q采样出一组输出{o₁, …, oG}。对每个输出oᵢ做奖励打分rᵢ。然后相对化后,将它当作对各token的优势函数。最后也类似PPO的做法去最大化一个带有ratio的目标,只不过“价值函数”被分组相对奖励替代了。用更直观的话说:
JGRPO(θ) = E[1/G ∑ᵢ=1^G 1/∥oᵢ∥ ∑ₜ=1^∥oᵢ∥ min[rratio, clip(rratio, 1−ε, 1+ε)] · Āᵢ,t] − (KL正则项)
其中
- rratio = πθ(oᵢ,t∣q, oᵢ,<t)/πθold(oᵢ,t∣q, oᵢ,<t),
- Āᵢ,t是分组相对意义上的“优势”,我们下节会具体解释它是怎么来的;
- KL正则用来限制策略和一个参考策略(通常是初始SFT模型或当前θold)之间不要差异过大,以防训练崩坏。
分组得分与基线估计
那么Āᵢ,t到底怎么来?就是分组相对奖励:我们先把每个oᵢ的奖励rᵢ做如下归一化
r~i = (rᵢ − mean(r))/std(r)
然后令
Āᵢ,t = r~i
也就是说,输出oᵢ的所有token共享同一个分数r~i。它们的好坏相对于该分组内的平均水平来衡量,而不依赖外部价值网络去“拆分”或“插值”。这样我们就得到了一个无价值网络的优势函数,核心思路就是基于相互间的比较与排序。
如果用的是过程监督(process supervision),即在推理过程中的每个关键步骤都打分,那么就会略有不同。那时每个步骤都有一个局部奖励,就可以把它依时间序列累加或折算成与token对应的优势,这在后文示例里我们会详细展示。
一步步理解损失函数
我们把PPO/GRPO都视为一种“Actor优化”过程,每个token的梯度大致如下:
∇θJ(θ) = E[(gradient coefficient) · ∇θlogπθ(ot∣q, o<t)]
在PPO里,gradient coefficient里往往含有优势At以及ratio等信息;而在GRPO里,gradient coefficient变成了以分组奖励为基础的一些值。之所以说GRPO是PPO的一个变体,是因为它同样维持了ratio的范式,只不过优势函数来自“分组内相对奖励”,而非价值网络。
惩罚项与KL正则
最后补充一句,PPO中常见的KL惩罚手段或者clipping手段,在GRPO中都可以保留,以避免训练过程中的策略分布出现暴走。当然,也有一些更精细的做法,比如把per-token KL正则直接加到损失中,而不是只在奖励函数r里扣一个β·log(πθ/πref)。这在各家实现时略有不同,但思路都类似。
实例讲解:如何用GRPO解决一个简单问题
有了上文的理论基础后,笔者想通过一个简化的实例,帮你理清GRPO的实施逻辑。我们会从最基本的样本生成到分组打分再到反向传播,都梳理清楚。
实验场景与环境:示例说明
假设笔者有一个文本对话场景:系统给定一个问题q,模型需要给出回答o。我们有一个奖励模型来判断回答的好坏(比如回答是否准确、是否违反某些安全规范等),返回一个数值分r。为简单起见,先考虑结果监督(Outcome Supervision)的情境。
在这个设定下,每个问题q提供的“回合”只有一次——即输出一段文本o,即可拿到一个终端奖励r。要做GRPO,我们至少要对同一个q生成GRPO条回复o₁, o₂, ..., oG。
过程监督VS结果监督:过程奖励与末端奖励的对比
- 结果监督(Outcome Supervision):只有输出序列结束才打一个奖励,如回答对/错、得分多少。GRPO则把这个r同样分配给序列里每个token。
- 过程监督(Process Supervision):对中间推理步骤也有打分(比如计算正确一步就+1,错误一步就-1)。那就得收集多个时刻的奖励,然后累加到每个token或步骤上,再做分组相对化。
在绝大多数简单场景下,初学者往往更容易先实现结果监督的版本,这也正好方便讲解GRPO的主干思路。
分组采样的实现:batch内如何分组?
在实际操作中,我们往往会在一个batch中包含若干个问题q,对每个问题生成GRPO个答案。也就是说batch大小=B,每个问题生成GRPO个候选,那么一次前向推理要生成B*GRPO条候选。然后,每个候选都送奖励模型RM得到分数rᵢ。注意这样做推理开销不小,如果GRPO较大,会显著地增加生成次数,但换来的好处是,我们不再需要价值网络了。
实际伪代码示例
我们以结果监督为例,先给出一个简化版的伪代码,帮助你更好理解GRPO的操作流程。假设πθ是当前策略模型,πref是参考模型(一般初始可设为和πθ同一个拷贝,用于算KL正则),RM是奖励模型。
```
请注意这只是简化的示例,忽略了各种超参数细节
GPRO 伪代码 (结果监督)
for iteration in range(N_iterations):
# 1) 设置参考模型 pi_ref <- pi_theta
pi_ref = clone(pi_theta)
for step in range(M_steps_per_iter):
# 2) 从训练集中取一批问题 D_b
D_b = sample_batch(train_dataset, batch_size=B)
# 3) 让旧策略 pi_theta 生成 G 个输出
# o_i 表示第 i 个候选答案
batch_outs = []
for q in D_b:
outs_for_q = []
for i in range(G):
o_i = sample(pi_theta, q)
outs_for_q.append(o_i)
batch_outs.append