【强化学习】深度解析 GRPO:从原理到实践的全攻略
文章目录
- 一、提出背景
- 二、核心思想
- 2.1 组内相对奖励
- 2.2 去价值网络设计
- 2.3 稳定优化机制
- 2.4 PPO vs GRPO
- 三、算法原理
- 3.1 生成响应(Generating completions )
- 3.2 计算优势值(Computing the advantage)
- 3.3 估计KL散度(Estimating the KL divergence)
- 3.4 计算损失(Computing the loss)
- 3.5 重要性采样*(Importance Sampling)
- 四、代码示例
- 4.1 设计奖励函数
- 4.2 GRPO训练
- 五、总结
一、提出背景
GRPO(Group Relative Policy Optimization,群组相对策略优化) 是一种基于强化学习的策略优化算法,旨在提升大语言模型在复杂任务(如数学推理、编程)中的表现。其提出背景主要源于传统强化学习(RL)方法在大语言模型(LLM)训练中的局限性,特别是在计算效率、训练稳定性和资源消耗方面的挑战。
- 高计算成本:传统的PPO(Proximal Policy Optimization)需要同时训练策略模型(Actor)和价值函数模型(Critic),导致显存占用和计算开销翻倍,尤其在大模型场景下资源消耗巨大。
- 训练复杂性:PPO依赖价值网络估计优势函数(Advantage),而奖励模型通常仅对完整序列打分,导致token级奖励信号不匹配,影响训练稳定性。
- 样本效率低:PPO需频繁采样新数据,训练过程耗时且对超参数敏感。
GRPO最初在 DeepSeekMath 中提出,用于提升模型在开放域数学问题上的推理能力,后扩展至 DeepSeek-R1 等通用推理模型。结合LoRA(低秩适配)技术,GRPO可在消费级GPU上微调小模型,降低了RLHF门槛。
二、核心思想
GRPO(Group Relative Policy Optimization,群组相对策略优化)算法的核心思想是通过组内样本的相对比较替代传统强化学习中的绝对价值估计,从而简化训练流程、提升计算效率并保持策略优化的稳定性。
2.1 组内相对奖励
- 多响应生成:对同一输入提示(prompt),并行生成多个响应(即一个“组”),形成组内样本。
- 相对优势计算:通过组内样本的奖励值(由奖励模型或人类标注给出)的归一化比较计算每个响应的相对优势,替代传统PPO中依赖价值网络估计的绝对优势(Advantage)。
- 核心公式:
相对优势 A ^ i = R i − μ R σ R + ϵ \text{相对优势} \hat{A}_i = \frac{R_i - \mu_R}{\sigma_R + \epsilon} 相对优势A^i=σR+ϵRi−μR
其中 R i R_i Ri 为第 i i i 个响应的奖励, μ R \mu_R μR 和 σ R \sigma_R σR 分别为组内奖励的均值和标准差, ϵ \epsilon ϵ 为平滑项。
2.2 去价值网络设计
- 消除Critic模型(Critic-free):传统PPO需要额外训练价值网络(Critic)来估计状态值函数,而GRPO直接利用组内奖励的统计特性(如均值和方差)计算相对优势,省去Critic的显存和计算开销。
- 显存优化:仅需维护策略模型(Actor)和参考模型(Reference Model),降低显存占用。
2.3 稳定优化机制
- KL散度惩罚:约束策略模型与参考模型的输出分布差异,防止过度偏离初始策略:
L KL = β ⋅ KL ( π θ ∥ π ref ) \mathcal{L}_{\text{KL}} = \beta \cdot \text{KL}(\pi_\theta \| \pi_{\text{ref}}) LKL=β⋅KL(πθ∥πref) - 策略裁剪(Clipping):类似PPO,对策略更新的幅度进行裁剪,确保训练稳定性:
L clip = min ( π θ ( a ∣ s ) π old ( a ∣ s ) A ^ i , clip ( π θ ( a ∣ s ) π old ( a ∣ s ) , 1 − ϵ , 1 + ϵ ) A ^ i ) \mathcal{L}_{\text{clip}} = \min\left( \frac{\pi_\theta(a|s)}{\pi_{\text{old}}(a|s)} \hat{A}_i, \text{clip}\left(\frac{\pi_\theta(a|s)}{\pi_{\text{old}}(a|s)}, 1-\epsilon, 1+\epsilon\right) \hat{A}_i \right) Lclip=min(πold(a∣s)πθ(a∣s)A^i,clip(πold(a∣s)πθ(a∣s),1−ϵ,1+ϵ)A^i) - 归一化优势:组内奖励的标准化处理减少了极端值的影响,使梯度更新更平滑。
2.4 PPO vs GRPO
PPO
近端策略优化 (Proximal Policy Optimization, PPO) 是一种 Actor-Critic (行动者-评论家) 强化学习算法,通过最大化以下目标函数来优化 LLM:
J PPO ( θ ) = E [ q ∼ P ( Q ) , o ∼ π θ old ( O ∣ q ) ] 1 ∣ o ∣ ∑ t = 1 ∣ o ∣ min [ π θ ( o t ∣ q , o < t ) π θ old ( o t ∣ q , o < t ) A t , clip ( π θ ( o t ∣ q , o < t ) π θ old ( o t ∣ q , o < t ) , 1 − ϵ , 1 + ϵ ) A t ] \mathcal{J}_{\text{PPO}}(\theta) = \mathbb{E}[q \sim P(Q), o \sim \pi_{\theta_{\text{old}}}(O|q)] \frac{1}{|o|} \sum_{t=1}^{|o|} \min \left[ \frac{\pi_\theta(o_t|q, o_{<t})}{\pi_{\theta_{\text{old}}}(o_t|q, o_{<t})} A_t, \text{clip} \left( \frac{\pi_\theta(o_t|q, o_{<t})}{\pi_{\theta_{\text{old}}}(o_t|q, o_{<t})}, 1-\epsilon, 1+\epsilon \right) A_t \right] JPPO(θ)=E[q∼P(Q),o∼πθold(O∣q)]∣o∣1t=1∑∣o∣min[πθold(ot∣q,o<t)πθ(ot∣q,o<t)At,clip(πθold(ot∣q,o<t)πθ(ot∣q,o<t),1−ϵ,1+ϵ)At]
其中, π θ \pi_\theta πθ 和 π θ old \pi_{\theta_{\text{old}}} πθold 分别是当前和旧的策略模型, q q q 和 o o o 分别是从问题数据集和旧策略 π θ old \pi_{\theta_{\text{old}}} πθold 中采样的问题和输出。 ε \varepsilon ε 是 PPO 中引入的用于稳定训练的 clipping(裁剪)相关超参数。 A t A_t At 是优势函数(Advantage),它通过应用广义优势估计(GAE)计算得出。
GRPO
GRPO 放弃了价值模型,转而从分组得分中估计基线,从而显著减少了训练资源。对于每个问题 q q q,GRPO 从旧策略 π θ old \pi_{\theta_{\text{old}}} πθold 中采样一组输出 { o 1 , o 2 , ⋯ , o G } \{o_1, o_2, \cdots, o_G\} {o1,o2,⋯,oG},然后通过最大化以下目标函数来优化策略模型:
J GRPO ( θ ) = E [ q ∼ P ( Q ) , { o i } i = 1 G ∼ π θ old ( O ∣ q ) ] 1 G ∑ i = 1 G 1 ∣ o i ∣ ∑ t = 1 ∣ o i ∣ { min [ π θ ( o i , t ∣ q , o i , < t ) π θ old ( o i , t ∣ q , o i , < t ) A ^ i , t , clip ( π θ ( o i , t ∣ q , o i , < t ) π θ old ( o i , t ∣ q , o i , < t ) , 1 − ϵ , 1 + ϵ ) A ^ i , t ] − β D K L [ π θ ∥ π ref ] } \mathcal{J}_{\text{GRPO}}(\theta) = \mathbb{E}[q \sim P(Q), \{o_i\}_{i=1}^G \sim \pi_{\theta_{\text{old}}}(O|q)] \frac{1}{G} \sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \left\{ \min \left[ \frac{\pi_\theta(o_{i,t}|q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t}|q, o_{i,<t})} \hat{A}_{i,t}, \text{clip} \left( \frac{\pi_\theta(o_{i,t}|q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t}|q, o_{i,<t})}, 1-\epsilon, 1+\epsilon \right) \hat{A}_{i,t} \right] - \beta D_{KL} \left[ \pi_\theta \| \pi_{\text{ref}} \right] \right\} JGRPO(θ)=E[q∼P(Q),{oi}i=1G∼πθold(O∣q)]G1i=1∑G∣oi∣1t=1∑∣oi∣{min[πθold(oi,t∣q,oi,<t)πθ(oi,t∣q,oi,<t)A^i,t,clip(πθold(oi,t∣q,oi,<t)πθ(oi,t∣q,oi,<t),1−ϵ,1+ϵ)A^i,t]−βDKL[πθ∥πref]}
其中, ε \varepsilon ε 和 β \beta β 是超参数, A ^ i , t \hat{A}_{i,t} A^i,t 是基于仅在每个组内的输出的相对奖励计算的优势函数。
特性 | PPO | GRPO |
---|---|---|
优势估计 | 依赖价值网络(Critic) | 组内奖励归一化(无Critic) |
显存占用 | 高(需同时训练Actor+Critic) | 低(仅需Actor) |
奖励信号 | Token级或序列级 | 组内序列级相对比较 |
训练稳定性 | 对超参数敏感 | 通过归一化和KL约束更稳定 |
适用规模 | 大模型需分布式训练 | 适合单卡中等规模模型 |
GRPO通过 “组内相对比较” 和 “去价值网络” 两大创新,将强化学习从依赖绝对价值估计转变为基于群体统计的排序优化,在保持PPO稳定性的同时显著降低计算成本。(这一思想也可视为对 对比学习 (Contrastive Learning)和 排序学习(Learning-to-Rank)在RLHF领域的巧妙迁移)
三、算法原理
GRPO的工作原理,可分为四个主要步骤:生成响应、计算优势值、估计KL散度、计算损失。
3.1 生成响应(Generating completions )
对于每个问题 q q q,从旧策略模型 π θ old \pi_{\theta_{\text{old}}} πθold 中采样一组输出 { o 1 , o 2 , ⋯ , o G } \{o_1, o_2, \cdots, o_G\} {o1,o2,⋯,oG}。
3.2 计算优势值(Computing the advantage)
针对每组生成的响应序列,通过奖励模型计算其奖励值,生成对应的 G G G 个奖励值 r = { r 1 , r 2 , ⋯ , r G } \mathbf{r} = \{r_1, r_2, \cdots, r_G\} r={r1,r2,⋯,rG}。
接着,对这些奖励进行标准化处理:
A ^ i , t = r i − mean ( r ) std ( r ) \hat{A}_{i,t} = \frac{r_i - \text{mean}(\mathbf{r})}{\text{std}(\mathbf{r})} A^i,t=std(r)ri−mean(r)
优势函数的作用就是计算某一个输出的token数值相对于平均输出的优劣势。如果某一个输出的奖励高于平均值,则结果是正的,反之低于平均值,结果是负的。这样策略模型会更倾向于生成那些具有较高奖励的输出。
这种基于组内相对比较的方法在组内引入竞争机制,推动模型生成更好的回答,也正是GRPO命名的由来。
3.3 估计KL散度(Estimating the KL divergence)
KL散度用于衡量当前新策略 π θ \pi_\theta πθ和参考策略 π ref \pi_{\text{ref}} πref之间的分布差异,目标是限制 π θ \pi_\theta πθ和 π ref \pi_{\text{ref}} πref偏离太远。使用Schulman等人(2020年)引入的近似器进行估计。该近似器定义如下:
D K L [ π θ ∥ π ref ] = π ref ( o i , t ∣ q , o i , < t ) π θ ( o i , t ∣ q , o i , < t ) − log π ref ( o i , t ∣ q , o i , < t ) π θ ( o i , t ∣ q , o i , < t ) − 1 , D_{KL} \left[ \pi_\theta \| \pi_{\text{ref}} \right] = \frac{\pi_{\text{ref}}(o_{i,t} | q, o_{i,<t})}{\pi_\theta(o_{i,t} | q, o_{i,<t})} - \log \frac{\pi_{\text{ref}}(o_{i,t} | q, o_{i,<t})}{\pi_\theta(o_{i,t} | q, o_{i,<t})} - 1, DKL[πθ∥πref]=πθ(oi,t∣q,oi,<t)πref(oi,t∣q,oi,<t)−logπθ(oi,t∣q,oi,<t)πref(oi,t∣q,oi,<t)−1,
3.4 计算损失(Computing the loss)
目标是在最大化优势值的同时约束模型接近参考策略,因此损失函数定义为:
L GRPO ( θ ) = − 1 G ∑ i = 1 G 1 ∣ o i ∣ ∑ t = 1 ∣ o i ∣ [ min ( π θ ( o i , t ∣ q , o i , < t ) π θ old ( o i , t ∣ q , o i , < t ) A ^ i , t , clip ( π θ ( o i , t ∣ q , o i , < t ) π θ old ( o i , t ∣ q , o i , < t ) , 1 − ϵ , 1 + ϵ ) A ^ i , t ) ⏟ 裁剪策略梯度项 − β D KL [ π θ ∥ π ref ] ⏟ KL散度惩罚项 ] \mathcal{L}_{\text{GRPO}}(\theta) = -\frac{1}{G} \sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \left[ \underbrace{\min\left( \frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})} \hat{A}_{i,t}, \text{clip}\left( \frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})}, 1-\epsilon, 1+\epsilon \right) \hat{A}_{i,t} \right)}_{\text{裁剪策略梯度项}} - \underbrace{\beta D_{\text{KL}} \left[ \pi_\theta \| \pi_{\text{ref}} \right]}_{\text{KL散度惩罚项}} \right] LGRPO(θ)=−G1i=1∑G∣oi∣1t=1∑∣oi∣ 裁剪策略梯度项 min(πθold(oi,t∣q,oi,<t)πθ(oi,t∣q,oi,<t)A^i,t,clip(πθold(oi,t∣q,oi,<t)πθ(oi,t∣q,oi,<t),1−ϵ,1+ϵ)A^i,t)−KL散度惩罚项 βDKL[πθ∥πref]
公式最左边, t t t 对应token-level优势,即一个句子中,每个token对应的优势是一样的。 G G G 表示每组生成的响应数量,最终需要取平均。 ∣ o i ∣ |o_i| ∣oi∣表示第 i i i 个响应的序列长度(Token数量), 1 ∣ o i ∣ \frac{1}{|o_i|} ∣oi∣1 表示对每一个生成回答的所有的token取平均。
裁剪策略梯度项表示缩放后的优势,其中Clip函数用于限制新策略与旧策略之间的比值,通过将比值限制在 1 − ϵ , 1 + ϵ 1-\epsilon, 1+\epsilon 1−ϵ,1+ϵ之间,防止新策略相对于旧策略的数值有较大的更新变动。这种机制确保策略更新处于控制范围内,稳定性更强,收敛性更好。公式表示为:
clip ( π θ ( o i , t ∣ q , o i , < t ) π θ old ( o i , t ∣ q , o i , < t ) , 1 − ϵ , 1 + ϵ ) A ^ i , t \text{clip} \left( \frac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})}, 1-\epsilon, 1+\epsilon \right) \hat{A}_{i,t} clip(πθold(oi,t∣q,oi,<t)πθ(oi,t∣q,oi,<t),1−ϵ,1+ϵ)A^i,t
KL散度惩罚项惩罚偏离参考策略的行为,确保策略更新的稳定性并保留通用推理能力。 β \beta β表示的是惩罚系数。公式表示为:
β D K L ( π θ ∥ π ref ) \beta D_{KL} \left( \pi_\theta \| \pi_{\text{ref}} \right) βDKL(πθ∥πref)
- 当 D K L D_{KL} DKL变大时,惩罚增加,迫使策略更新更加保守,不要偏离太远。
- 当 β \beta β变大的时候,表示在原有的优势上增加惩罚,让模型更新幅度变小,更加保守。
3.5 重要性采样*(Importance Sampling)
GRPO算法的最终的优化目标是让新策略在旧策略的样本上,尽可能多地保留高奖励的输出。重要性采样是其核心机制之一,通过复用旧策略(参考策略)生成的样本来优化新策略,从而提升训练效率和样本利用率。
重要性采样的本质
重要性采样(Importance Sampling, IS)是一种通过从易采样的分布估计难采样分布的期望的方法。其核心思想是使用重要性权重来修正采样的偏差。
例如,我们想计算某个难采样分布下的期望:
E x ∼ p hard [ f ( x ) ] = ∫ f ( x ) p hard ( x ) d x \mathbb{E}_{x \sim p_{\text{hard}}}[f(x)] = \int f(x)p_{\text{hard}}(x)dx Ex∼phard[f(x)]=∫f(x)phard(x)dx
那我们就可以从另一个易采样的分布进行采样,使用重要性采样估计,表示为:
E x ∼ p hard [ f ( x ) ] = E x ∼ p easy [ p hard ( x ) p easy ( x ) f ( x ) ] = ∫ f ( x ) p hard ( x ) p easy ( x ) p easy ( x ) d x \mathbb{E}_{x \sim p_{\text{hard}}}[f(x)] = \mathbb{E}_{x \sim p_{\text{easy}}}\left[\frac{p_{\text{hard}}(x)}{p_{\text{easy}}(x)} f(x)\right]=\int f(x)\frac{p_{\text{hard}}(x)}{p_{\text{easy}}(x)}p_{\text{easy}}(x)dx Ex∼phard[f(x)]=Ex∼peasy[peasy(x)phard(x)f(x)]=∫f(x)peasy(x)phard(x)peasy(x)dx
其中, p hard ( x ) p easy ( x ) \frac{p_{\text{hard}}(x)}{p_{\text{easy}}(x)} peasy(x)phard(x) 就是重要性权重,用于修正分布差异。
GRPO中的重要性采样
在GRPO中,训练目标是最大化新旧策略的比值乘以Advantage的数值:
π θ ( o i , t ∣ q , o i , < t ) π θ old ( o i , t ∣ q , o i , < t ) A ^ i , t \frac{\pi_\theta(o_{i,t} | q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} | q, o_{i,<t})} \hat{A}_{i,t} πθold(oi,t∣q,oi,<t)πθ(oi,t∣q,oi,<t)A^i,t
其中,新旧策略的比值 π θ π θ old \frac{\pi_\theta}{\pi_{\theta_{\text{old}}}} πθoldπθ就是重要性采样中的重要性比值。GRPO通过重要性采样与裁剪机制(Clipping)结合,限制策略更新幅度,从而防止过度优化导致模型崩溃。
四、代码示例
4.1 设计奖励函数
在实际使用GRPO(Group Relative Policy Optimization)训练时,设计奖励函数的核心思路需要围绕强化学习的目标和任务特性展开,同时兼顾训练稳定性和计算效率。以下整理出一些关键设计原则:
- 核心任务导向:奖励函数必须直接反映任务的核心目标。例如:
- 数学推理:正确答案给予最高奖励。
- 格式要求:对符合特定结构(如
<reasoning>...</reasoning><answer>...</answer>
)的响应给予额外奖励。 - 逻辑严谨性:通过关键词(如 “sum”、“unique”)或推理长度间接评估逻辑质量。
- 多目标权衡:
- 若任务需要平衡多个子目标(如正确性+格式+推理深度),可通过加权求和或分层奖励实现。
- 稀疏 vs 稠密奖励:
- 稀疏奖励(如仅对最终答案打分)需结合组内相对比较提取有效信号。
- 稠密奖励(如对每一步推理打分)更适合复杂任务,但需注意奖励膨胀问题。
- 归一化与标准化:
- 组内奖励需归一化(如Z-score)以消除不同提示(prompt)间的尺度差异。
- 对原始奖励进行裁剪(如限制到
[-5, 5]
)防止极端值干扰训练。
- 可解释性:
- 每个奖励分项(如格式、正确性、关键词)应清晰定义,便于调试和调整权重。
以下是一个示例:
分项类型 | 示例 | 设计要点 |
---|---|---|
正确答案奖励 | reward_correct_answer() 检测 \boxed{A-D} 是否匹配目标答案 | 奖励值需显著高于其他分项(如+2.0),突出核心目标。 |
格式合规奖励 | reward_strict_format() 检查XML标签完整性 | 分层设计(严格格式1.0分,宽松格式0.5分),引导模型遵循规范。 |
推理质量奖励 | reward_reasoning_keywords() 统计关键词命中数 | 避免过度依赖关键词,可结合语义分析(如LLM自评)提升鲁棒性。 |
结构完整性奖励 | xmlcount_reward_func() 计算标签位置和数量 | 对标签缺失或冗余进行惩罚(如 -0.001/多余字符),确保输出整洁。 |
长度适度奖励 | reward_reasoning_length() 根据单词数分段打分 | 防止模型生成过长或过短的无效内容(如“短于20词无奖励”)。 |
import re
def reward_correct_answer(responses, answers, **kwargs) -> list[float]:"""根据答案的正确性给予奖励。"""pattern = r"<answer>.*?\\boxed{([A-D])}.*?</answer>"rewards = []for resp, target in zip(responses, answers):match = re.search(pattern, resp, re.DOTALL)if match and match.group(1) == target:rewards.append(2.0) # 正确选项,高奖励else:rewards.append(0.0) # 错误或无法提取,无奖励return rewardsdef reward_strict_format(responses, **kwargs) -> list[float]:"""奖励符合严格格式 <reasoning>...</reasoning><answer>...</answer> 的回答"""pattern = r"^<reasoning>\n.*?\n</reasoning>\n<answer>\n\\boxed{[A-D]}\n</answer>$"return [1.0 if re.fullmatch(pattern, r.strip(), re.DOTALL) else 0.0 for r in responses]def reward_soft_format(responses, **kwargs) -> list[float]:"""奖励符合宽松格式 <reasoning>...</reasoning><answer>...</answer> 的回答"""pattern = r"<reasoning>.*?</reasoning>.*?<answer>.*?[A-D].*?</answer>"return [0.5 if re.search(pattern, r, re.DOTALL) else 0.0 for r in responses]def count_xml(text: str) -> float:"""根据 XML 标签的出现次数和位置给予分数"""score = 0.0if text.count("<reasoning>\n") == 1:score += 0.125if text.count("\n</reasoning>\n") == 1:score += 0.125if text.count("\n</answer>") == 1:score += 0.125score -= max(0, (len(text.split("\n</answer>")[-1]) - 1)) * 0.001return max(0.0, score)def xmlcount_reward_func(responses, **kwargs) -> list[float]:"""使用 count_xml 函数计算每个回答的分数。"""return [count_xml(r) for r in responses]def reward_reasoning_length(responses, **kwargs) -> list[float]:"""根据推理部分的长度给予奖励。- 长度小于 20 个单词,无奖励。- 长度在 20 到 50 个单词之间,奖励 0.25。- 长度在 50 到 100 个单词之间,奖励 0.5。- 长度超过 100 个单词,奖励 0.75。"""rewards = []for r in responses:score = 0.0match = re.search(r"<reasoning>(.*?)</reasoning>", r, re.DOTALL)if match:content = match.group(1).strip()length = len(content.split())if length < 20:score += 0.0elif length < 50:score += 0.25elif length < 100:score += 0.5else:score += 0.75rewards.append(max(0.0, score))return rewardsdef reward_reasoning_keywords(responses, **kwargs) -> list[float]:"""根据推理部分是否包含特定关键词给予奖励。每命中一个关键词,奖励 0.1 分,上限为 0.5 分。"""key_terms = ["row", "column", "sum", "unique", "digit", "no repeat", "clue"]rewards = []for r in responses:match = re.search(r"<reasoning>(.*?)</reasoning>", r, re.DOTALL)if not match:rewards.append(0.0)continuecontent = match.group(1).lower()hits = sum(1 for k in key_terms if k in content)rewards.append(min(hits * 0.1, 0.5)) # 命中多个关键词,奖励上限 0.5return rewards# 合并所有奖励函数
def combined_reward_func(prompts, completions, response, **kwargs) -> list[float]:responses = completionspattern = r"^<reasoning>\n.*?\n</reasoning>\n<answer>\n\\boxed{([A-D])}\n</answer>$"answers = [re.search(pattern, answer, re.DOTALL).group(1) for answer in response]rca = reward_correct_answer(responses, answers)rstf = reward_strict_format(responses)rsof = reward_soft_format(responses)xrf = xmlcount_reward_func(responses)rrl = reward_reasoning_length(responses)rrk = reward_reasoning_keywords(responses)# 返回每个回答的总奖励return [rca[i] + rstf[i] + rsof[i] + xrf[i] + rrl[i] + rrk[i] for i in range(len(responses))]
4.2 GRPO训练
0. 安装trl库
git clone https://github.com/huggingface/trl.git
cd trl/
pip install -e .
1. 加载训练数据和模型
from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import GRPOConfig, GRPOTrainer
import torch
from datasets import Dataset
import jsonwith open("grpo_data.json", 'r') as file:data = json.load(file)
dataset = Dataset.from_list(data)model_dir = "xxx"
model = AutoModelForCausalLM.from_pretrained(model_dir,torch_dtype=torch.bfloat16,device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_dir)
2. 配置GRPO训练参数
关键参数说明:
- num_generations: 每个prompt生成4个候选答案(用于奖励计算)
- max_completion_length: 生成文本最大长度(8192 tokens)
- gradient_accumulation_steps: 4步累积梯度,模拟batch_size=4
更多参数说明详见:https://huggingface.co/docs/trl/main/en/grpo_trainer
training_args = GRPOConfig(learning_rate = 5e-6,adam_beta1 = 0.9,adam_beta2 = 0.99,weight_decay = 0.1,warmup_ratio = 0.1,lr_scheduler_type = "cosine",optim = "paged_adamw_8bit",logging_steps = 10,per_device_train_batch_size = 1,gradient_accumulation_steps = 4, # 增加到 4 以获得更平滑的训练num_generations = 4, # 如果内存不足,请减少max_prompt_length = 2048,max_completion_length = 8192,num_train_epochs = 1, # 设置为 1 以进行完整的训练save_steps = 200,max_grad_norm = 0.1,logging_dir = "output_grpo/logs",output_dir = "output_grpo",
)
3. 初始化GRPO训练器
trainer = GRPOTrainer(model = model,reward_funcs = combined_reward_func,args = training_args,train_dataset = dataset,
)
4. 启动训练
trainer.train()
五、总结
GRPO(群组相对策略优化)是一种高效的大语言模型强化学习微调算法,通过组内相对比较和去价值网络设计,显著降低了传统PPO的计算开销。其核心创新在于利用组内响应奖励的归一化比较替代绝对价值估计,结合KL散度惩罚和策略裁剪机制确保训练稳定性。该算法特别适合数学推理、代码生成等复杂任务,能在单卡中等规模模型上高效运行。
GRPO的优势包括计算效率高、适配人类偏好数据特性、训练过程稳定等,但也存在对组大小敏感、长序列优化等挑战。实践建议优先保证核心目标奖励权重,结合渐进式训练策略,适用于资源受限但需要高质量微调的场景,为RLHF提供了一种轻量化解决方案。