解读 VeRL 框架源码:PPO 和 GRPO 算法如何计算优势 (Advantage)

发布时间:2026/11/5 1:31:49
解读 VeRL 框架源码:PPO 和 GRPO 算法如何计算优势 (Advantage) 最近正在入门大模型 RL 微调阅读 VeRL 框架代码。在这篇博客中我们从 VeRL 源码入手解读一下 ppo 和 grpo 算法的 advantage 计算方法# 来自 verl/trainer/ppo/core_algos.pyregister_adv_est(AdvantageEstimator.GAE)# or simply: register_adv_est(gae)defcompute_gae_advantage_return(token_level_rewards:torch.Tensor,values:torch.Tensor,response_mask:torch.Tensor,gamma:torch.Tensor,lam:torch.Tensor,):Adapted from https://github.com/huggingface/trl/blob/main/trl/trainer/ppo_trainer.py Args: token_level_rewards: (torch.Tensor) shape is (bs, response_length) values: (torch.Tensor) shape is (bs, response_length) response_mask: (torch.Tensor) shape is (bs, response_length). [EOS] mask. The token after [EOS] have mask zero. gamma is (float) discounted factor used in RL lam: (float) lambda value when computing Generalized Advantage Estimation (https://arxiv.org/abs/1506.02438) Returns: advantages: (torch.Tensor) shape: (bs, response_length) Returns: (torch.Tensor) shape: (bs, response_length) withtorch.no_grad():nextvalues0lastgaelam0advantages_reversed[]gen_lentoken_level_rewards.shape[-1]fortinreversed(range(gen_len)):deltatoken_level_rewards[:,t]gamma*nextvalues-values[:,t]lastgaelam_deltagamma*lam*lastgaelam# skip values and TD-error on observation tokensnextvaluesvalues[:,t]*response_mask[:,t](1-response_mask[:,t])*nextvalues lastgaelamlastgaelam_*response_mask[:,t](1-response_mask[:,t])*lastgaelam advantages_reversed.append(lastgaelam)advantagestorch.stack(advantages_reversed[::-1],dim1)returnsadvantagesvalues advantagesverl_F.masked_whiten(advantages,response_mask)returnadvantages,returns# NOTE(sgm): this implementation only consider outcome supervision, where the reward is a scalar.register_adv_est(AdvantageEstimator.GRPO)# or simply: register_adv_est(grpo)defcompute_grpo_outcome_advantage(token_level_rewards:torch.Tensor,response_mask:torch.Tensor,index:np.ndarray,epsilon:float1e-6,norm_adv_by_std_in_grpo:boolTrue,config:Optional[AlgoConfig]None,)-tuple[torch.Tensor,torch.Tensor]: Compute advantage for GRPO, operating only on Outcome reward (with only one scalar reward for each response). Args: token_level_rewards: (torch.Tensor) shape is (bs, response_length) response_mask: (torch.Tensor) shape is (bs, response_length) index: (np.ndarray) index array for grouping epsilon: (float) small value to avoid division by zero norm_adv_by_std_in_grpo: (bool) whether to scale the GRPO advantage config: (Optional[AlgoConfig]) algorithm configuration object Note: If norm_adv_by_std_in_grpo is True, the advantage is scaled by the std, as in the original GRPO. If False, the advantage is not scaled, as in Dr.GRPO (https://arxiv.org/abs/2503.20783). Returns: advantages: (torch.Tensor) shape is (bs, response_length) Returns: (torch.Tensor) shape is (bs, response_length) scorestoken_level_rewards.sum(dim-1)id2scoredefaultdict(list)id2mean{}id2std{}withtorch.no_grad():bszscores.shape[0]foriinrange(bsz):id2score[index[i]].append(scores[i])foridxinid2score:iflen(id2score[idx])1:id2mean[idx]torch.tensor(0.0)id2std[idx]torch.tensor(1.0)eliflen(id2score[idx])1:scores_tensortorch.stack(id2score[idx])id2mean[idx]torch.mean(scores_tensor)id2std[idx]torch.std(scores_tensor)else:raiseValueError(fno score in prompt index:{idx})foriinrange(bsz):ifnorm_adv_by_std_in_grpo:scores[i](scores[i]-id2mean[index[i]])/(id2std[index[i]]epsilon)else:scores[i]scores[i]-id2mean[index[i]]scoresscores.unsqueeze(-1)*response_maskreturnscores,scores以上的两段代码来自 VeRL 框架分别实现了PPO 中的 GAE广义优势估计和GRPO组相对策略优化的 advantage 计算方法。下面我们逐一解读并对比它们的设计思路。文章目录1 PPO GAE 优势计算 (compute_gae_advantage_return)1.1 函数签名1.2 核心逻辑GAE 原理2 GRPO 优势计算 (compute_grpo_outcome_advantage)2.1 函数签名2.2 核心逻辑GRPO 原理3 PPO(GAE) 与 GRPO 优势计算对比1 PPO GAE 优势计算 (compute_gae_advantage_return)1.1 函数签名defcompute_gae_advantage_return(token_level_rewards:torch.Tensor,values:torch.Tensor,response_mask:torch.Tensor,gamma:torch.Tensor,lam:torch.Tensor,):token_level_rewards: 形状(batch_size, response_length)每个 token 的即时奖励通常由奖励模型或规则给出。如果是 LLM 做数学题整个回答的 reward 可能只会给在最后一个 token。values: 形状与 reward 相同Critic 网络输出的每个 token 的状态价值估计。response_mask: 形状相同标记哪些 token 属于有效响应[EOS] 之后的 token 被 mask 掉值为0。gamma: 折扣因子控制未来奖励的重要性。lam: GAE 中的 λ 参数平衡偏差与方差。1.2 核心逻辑GAE 原理GAE 的递推公式为δ t r t γ ⋅ V ( s t 1 ) − V ( s t ) , A t δ t γ ⋅ λ ⋅ A t 1 δ_t r_t γ·V(s_{t1}) - V(s_t), \quad A_t δ_t γ·λ·A_{t1}δt​rt​γ⋅V(st1​)−V(st​),At​δt​γ⋅λ⋅At1​其中A t A_tAt​是优势V VV是价值函数。δ t Q ( s t , a t ) − V ( s t ) \delta_t Q(s_t, a_t) - V(s_t)δt​Q(st​,at​)−V(st​)而 advantage 的这种计算方法相当于不仅考虑当前时刻的 advantage还以 γ 的 discount factor 考虑未来的 advantage。代码中从最后一个 token 反向计算避免了显式循环的多次张量操作同时利用response_mask处理变长序列。nextvalues,lastgaelam0,0advantages_reversed[]gen_lentoken_level_rewards.shape[-1]fortinreversed(range(gen_len)):# 计算 TD 误差 deltadeltatoken_level_rewards[:,t]gamma*nextvalues-values[:,t]# 递推计算优势lastgaelam_deltagamma*lam*lastgaelam# 用 mask 控制哪些位置的 value 和 advantage 要传播nextvaluesvalues[:,t]*response_mask[:,t](1-response_mask[:,t])*nextvalues lastgaelamlastgaelam_*response_mask[:,t](1-response_mask[:,t])*lastgaelam advantages_reversed.append(lastgaelam)advantagestorch.stack(advantages_reversed[::-1],dim1)# 恢复正序returnsadvantagesvalues# 计算 returns (用于价值网络更新)advantagesverl_F.masked_whiten(advantages,response_mask)# 对有效 token 做白化标准化returnadvantages,returns关键点使用response_mask确保填充位置不参与计算也不传播梯度。masked_whiten对每个 batch 的有效 token 进行标准化均值 0方差 1有助于稳定训练。最终返回的advantages和returns形状均为(batch_size, response_length)。2 GRPO 优势计算 (compute_grpo_outcome_advantage)2.1 函数签名defcompute_grpo_outcome_advantage(token_level_rewards:torch.Tensor,response_mask:torch.Tensor,index:np.ndarray,epsilon:float1e-6,norm_adv_by_std_in_grpo:boolTrue,config:Optional[AlgoConfig]None,):token_level_rewards: 形状(batch_size, response_length)每个 token 的奖励。这里假设是结果监督outcome supervision即一个响应只对应一个标量奖励如正确/错误因此所有 token 的奖励和等于该响应的最终得分。response_mask: 同上标记有效 token。index: 形状(batch_size,)每个样本所属的组 ID例如同一个 prompt 的不同采样。GRPO 需要在组内进行标准化。norm_adv_by_std_in_grpo: 如果为 True则按原始 GRPO 方式除以标准差如果为 False则只减均值Dr.GRPO 风格。epsilon: 防止除零。2.2 核心逻辑GRPO 原理GRPO 不使用 Critic 网络而是用同一 prompt 下多个采样输出的平均奖励作为 baseline。advantage 就是组内标准化的奖励将 token 奖励求和得到每个 response 的标量得分scorestoken_level_rewards.sum(dim-1)# shape: (batch_size,)按 index 分组计算每个 group 的均值和标准差id2scoredefaultdict(list)foriinrange(bsz):id2score[index[i]].append(scores[i])foridxinid2score:scores_tensortorch.stack(id2score[idx])id2mean[idx]torch.mean(scores_tensor)id2std[idx]torch.std(scores_tensor)如果组内只有一个样本均值为 0标准差为 1避免除零。根据均值和标准差对每个得分进行标准化foriinrange(bsz):ifnorm_adv_by_std_in_grpo:scores[i](scores[i]-id2mean[index[i]])/(id2std[index[i]]epsilon)else:scores[i]scores[i]-id2mean[index[i]]标准化后每个组内的平均优势为 0可能方差为 1。每个 token 都被赋值为相同的标量 advantage非响应位置仍为 0scoresscores.unsqueeze(-1)*response_mask# shape: (bs, response_length)returnscores,scores# 这里 returns 和 advantages 相同GRPO 不更新价值网络关键点GRPO 完全抛弃了价值网络baseline 来自同组的其他样本避免了训练 Critic 的开销和不稳定性。标准化操作可以在组内消除奖励尺度差异使策略更新更稳健。返回的两个张量相同因为 GRPO 没有独立的 returns 概念一般直接使用优势更新策略。3 PPO(GAE) 与 GRPO 优势计算对比维度PPO GAEGRPObaseline 来源Critic 网络估计的状态值V ( s t ) V(s_t)V(st​)同一 prompt 下多个样本的平均奖励优势形式每个时间步t tt都有一个 advantageA t A_tAt​整个响应共用一个标量 advantage广播到每个 token是否需要价值网络是否处理变长序列通过 mask 控制逐 token 计算汇总为标量后广播mask 仅用于置零计算复杂度较高需维护价值网络并做反向传播较低仅需组内统计适用场景一般 RLHF需要细粒度信用分配结果监督任务如数学推理、代码生成可节省资源总结PPO-GAE代码是标准的 GAE 实现考虑了 mask 和最终的 whitening适用于需要 Critic 的在线策略优化。GRPO代码则利用了结果监督和分组归一化的思想简化了优势计算适合大模型微调中每个响应只有一个标量奖励的场景如 RLHF 中的“正确/错误”信号。这两段代码清晰体现了两种算法在设计哲学上的差异PPO 追求细粒度时序信用分配而 GRPO 通过组内比较降低方差并简化实现。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询