news 2026/9/13 20:45:11

verl 强化学习奖励函数实战指南:从 RewardManager 机制到自定义打分函数实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
verl 强化学习奖励函数实战指南:从 RewardManager 机制到自定义打分函数实现

verl 强化学习奖励函数实战指南:从 RewardManager 机制到自定义打分函数实现

【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl

本文聚焦 verl(HybridFlow)框架中数据集奖励函数(Reward Function)的设计与实现:从RewardManager的执行机制、DataProto输入约定,到 GSM8k/MATH 等预实现打分函数的内部原理,再到通过custom_reward_function配置自定义奖励函数的完整实操。读完本文,你将能够为任意数据集编写、注册并接入自己的奖励函数,让 PPO/GRPO 等后训练流程正确计算每条响应的 reward。

一、奖励函数在 verl 后训练流程中的定位

verl 是一个面向大模型强化学习后训练(RL Post-Training)的框架。在 PPO 等在线策略训练中,模型生成响应后,训练循环必须为每条响应计算一个数值奖励(reward)用于优势估计与策略更新。verl 的做法是:每个数据集要么配套实现一个奖励函数,要么接入一个奖励模型(Reward Model)来打分

仓库中的奖励函数实现集中在verl/utils/reward_score目录,官方当前已预实现:

  • GSM8k:基于字符串匹配的规则打分(见 verl/utils/reward_score/gsm8k.py);
  • MATH:解析\boxed{}答案并做数学表达式规范化等价比较(见 verl/utils/reward_score/math_reward.py);
  • 此外还内置了math_dapoprime_math(Numina 系列)、geo3ksearch_r1_like_qa_emprime_code(代码类)与sandbox_fusion(沙箱执行)等更多打分实现,统一由default_compute_scoredata_source分发(见 verl/utils/reward_score/init.py)。

而对于 RLHF 类数据集(如full_hh_rlhf),verl 采用Reward Model打分;代码生成类数据集(如 APPS)则通过SandBox 沙箱执行验证正确性。

二、RewardManager:奖励计算的统一入口

在 PPO 后训练脚本入口 verl/trainer/main_ppo.py 中,verl 实现了RewardManager来调用预实现的奖励函数为每条响应计算分数。其核心工作流为:

  1. 接收一个DataProto作为输入;
  2. 取出其中的响应 token 与 ground truth;
  3. 对响应 token 做 detokenize,得到响应字符串;
  4. 将响应字符串与 ground truth 字符串交给compute_score_fn计算每条响应的分数。

2.1 DataProto 输入约定

RewardManager__call__接收的DataProto中需要包含以下字段:

字段说明
input_idsattention_mask经过 chat_template 处理后的输入,包含 prompt 与 response 两部分
responses模型生成的响应 token
ground_truth当前 prompt 的标准答案字符串,存放在DataProtonon_tensor_batch中,需在数据预处理阶段写入 parquet 文件
data_source当前 prompt 所属数据集名称,同样存放在non_tensor_batch中,用于分发到对应的打分函数

以仓库内置的NaiveRewardManager(见 verl/experimental/reward_loop/reward_manager/naive.py)为例,其run_single方法展示了完整的数据读取逻辑:

data_source = data_item.non_tensor_batch["data_source"] ground_truth = data_item.non_tensor_batch["reward_model"]["ground_truth"] extra_info = data_item.non_tensor_batch.get("extra_info", {}) response_str = await self.loop.run_in_executor( None, lambda: self.tokenizer.decode(valid_response_ids, skip_special_tokens=True) ) result = await self.loop.run_in_executor( None, lambda: self.compute_score( data_source=data_source, solution_str=response_str, ground_truth=ground_truth, extra_info=extra_info, ), )

几点实现细节值得注意:

  • 对多序列输出,NaiveRewardManager只取最后一个序列计算奖励(data = data[-1:]),并通过attention_mask截取有效响应长度后再解码;
  • ground_truth存放在non_tensor_batch["reward_model"]["ground_truth"]中,extra_info还可携带num_turnsrollout_reward_scores、工具调用附加字段等信息传入奖励函数;
  • 打分函数返回值既可以是纯数字,也可以是包含score键的字典(此时其余键会作为reward_extra_info透传,用于记录acc等额外指标)。

三、预实现奖励函数解析

3.1 GSM8k:字符串匹配 + 格式约束

GSM8k 的打分逻辑(见 verl/utils/reward_score/gsm8k.py)采用"强格式约束"设计:强制模型在四个####之后输出最终答案,再通过字符串匹配与 ground truth 比对。评分规则:

  • 答案完全正确:得 1 分;
  • 格式正确(能提取出答案但与 ground truth 不符):得 0.1 分(format_score=0.0的默认值在不同调用场景下可配置);
  • 格式不正确(无法提取答案):得 0 分。

核心的答案提取函数extract_solutiongsm8k.pyL20-L49)支持两种模式:

  • strict(默认):用正则#### (\-?[0-9\.\,]+)匹配####后的数字,取最后一个作为最终答案,同时剔除了,$符号——这种模式同时检验了模型的输出格式;
  • flexible:直接取字符串中最后一个合法的数字,不要求####格式。

源码中还包含一个实用优化:当solution_str超过 300 个字符时,只对末尾 300 字符做正则匹配(_SOLUTION_CLIP_CHARS = 300),因为数学题的最终答案通常位于末尾,可以显著加速长字符串的正则扫描。

3.2 MATH:boxed 答案解析 + 数学表达式规范化

MATH 的打分实现(见 verl/utils/reward_score/math_reward.py)沿用了 lm-evaluation-harness 中hendrycks_math的做法,核心流程为:

  1. last_boxed_only_string:定位响应中最后一个\boxed{...}(兼容\fbox\boxed写法),提取括号内内容;
  2. remove_boxed:剥掉\boxed外壳拿到答案字符串;
  3. is_equiv:通过strip_string对两侧字符串做大量规范化后比较是否等价。

strip_string的处理极其细致,包括但不限于:去除换行、\left/\right、美元符号、百分号与单位;把\tfrac/\dfrac统一为\frac;把\sqrt3修正为\sqrt{3};把a/b改写为\frac{a}{b};把0.5归一为\frac{1}{2}等等。正是这一套规范化,使得\frac{1}{2}1/20.5等不同写法都能被正确判定为等价答案。

3.3 更多内置打分器与按数据集分发

所有内置打分器统一由 verl/utils/reward_score/init.py 中的default_compute_scoredata_source字符串分发(L44-L107)。当前支持的主要数据集别名包括:

  • openai/gsm8kgsm8k.compute_score
  • lighteval/MATHDigitalLearningGmbH/MATH-lightevalHuggingFaceH4/MATH-500math_reward.compute_score(源码注释提示可手动pip install math-verify后切换为math_verify.compute_score以提升精度);
  • math_dapomathmath_dapo_reasoningaime前缀 →math_dapo.compute_score
  • numina_*系列 →prime_math.compute_score
  • codecontestsappscodeforcestaco→ 优先走sandbox_fusion.compute_score(需要配置沙箱 URL),否则回退prime_code.compute_score
  • hiyouga/geometry3kgeo3k.compute_score,其实现将格式奖励与答案正确性奖励加权组合(见 verl/utils/reward_score/geo3k.py);
  • searchR1_*系列 →search_r1_like_qa_em.compute_score

对于未覆盖的data_source,会直接抛出NotImplementedError——这也提示我们:遇到新数据集时,正确姿势是编写自定义奖励函数(见下一节)。

四、自定义奖励函数:完整实操

4.1 通过配置指定自定义函数

verl 支持在独立的 Python 文件中实现自定义奖励函数,并通过配置项指定,无需修改框架源码。相关配置位于 verl/trainer/config/reward/reward.yaml:

# configs for the reward computation _target_: verl.workers.config.RewardConfig # 启动的 reward manager 数量,用于并行化奖励计算 num_workers: 8 # 自定义奖励函数定义 custom_reward_function: # 存放自定义奖励函数的文件路径 path: null # 文件中奖励函数的名称,默认是 'compute_score' name: compute_score

其中RewardConfig的数据结构定义可参考 verl/workers/config/reward.py(num_workers默认 8,另有reward_managerreward_modelsandbox_fusion三个子配置块)。完整配置项的逐条说明可进一步查阅配置文档(原文档指引参见config-explain-page,仓库内对应示例说明见 docs/examples/config.rst)。

4.2 函数签名约定

自定义奖励函数的参数签名固定为:

def my_reward_fn(data_source, solution_str, ground_truth, extra_info=None): return len(solution_str) / 100

参数含义:

参数说明
data_source数据集名称,可用于在同一个函数内区分不同数据集的打分逻辑
solution_str模型生成的响应文本(detokenize 之后)
ground_truth标准答案字符串
extra_info附加信息(可选),可包含num_turns、工具调用字段等

返回值应为数值奖励;如果返回字典,则需包含score键作为奖励值,其余键会作为reward_extra_info记录。

4.3 场景一:单函数测试

如果只测试一个自定义奖励函数,最简单的做法是:把函数命名为compute_score,配置中只填写custom_reward_function.pathname留空(默认值即为compute_score)。

4.4 场景二:多函数对比测试

当需要对比多个奖励函数时,可在同一个文件中实现多个函数,通过修改custom_reward_function.name切换,而无需改动path

# my_reward.py def compute_score(data_source, solution_str, ground_truth, extra_info=None): return 1.0 if solution_str == ground_truth else 0.0 def length_based_score(data_source, solution_str, ground_truth, extra_info=None): return len(solution_str) / 100 def keyword_score(data_source, solution_str, ground_truth, extra_info=None): return 0.5 if "answer:" in solution_str.lower() else 0.0

然后分别为每次试验设置:

custom_reward_function: path: /path/to/my_reward.py name: length_based_score # 每次试验只改这里

这种方式特别适合在脚本中批量跑多组奖励函数对比试验。

4.5 源码层面的加载机制

自定义函数并非简单地import,而是通过动态加载机制完成(见 verl/trainer/ppo/reward.py 中的get_custom_reward_fn,L50-L86):

  • config.reward.custom_reward_function读取pathname,若未配置path则返回None(此时回退到预实现函数);
  • 通过verl.utils.import_utils.load_extern_object(module_path, object_name)动态导入;
  • 配置中若提供了reward_kwargs,会与调用时的 kwargs 合并(以配置为准)后partial包装;
  • 若自定义函数是async协程函数,会被自动识别并走异步调用路径,便于接入远程/异步奖励源。

load_reward_manager(同文件 L111-L157)中,若未配置自定义函数且配置了sandbox_fusion.url,则会自动用default_compute_score结合沙箱 URL、并发信号量(默认max_concurrent=64)与内存上限(默认1024MB)构造打分函数,用于代码类数据集的沙箱执行验证。

五、数据预处理与端到端接入

要确保奖励函数正常工作,数据预处理阶段必须把data_sourceground_truth写入non_tensor_batch。仓库提供了现成的数据预处理脚本作为参考,例如:

  • examples/data_preprocess/gsm8k.py:构造 GSM8k 训练/测试 parquet,写入 prompt 与标准答案;
  • examples/data_preprocess/math_dataset.py:MATH 数据集同理;
  • examples/data_preprocess/full_hh_rlhf.py:RLHF 数据集示例,可配合 Reward Model 打分。

接入流程可归纳为:数据预处理写入ground_truth/data_source→ 训练配置指定custom_reward_function.path(或使用预实现函数)→ PPO 入口的RewardManager自动完成 detokenize 与打分。跑通后可参考 examples/grpo_trainer/run_qwen3_8b_fsdp.sh 等启动脚本,结合实际数据集验证奖励是否符合预期。

六、小结

verl 的奖励函数体系设计清晰、扩展成本低:预实现函数覆盖 GSM8k、MATH、代码、几何等多类数据集,RewardManager统一调度,custom_reward_function提供零侵入的自定义入口。对开发者而言,接入一个新数据集只需三步:预处理出ground_truthdata_source、按(data_source, solution_str, ground_truth, extra_info)签名编写打分函数、在配置中指定pathname。若需多函数对比试验,仅需在脚本中切换name即可,无需改动任何框架代码。

【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 20:43:31

设计模式学习记录

用一个实际例子来学习,TaskCat是一个任务管理器,主要聚焦在增删改查这些逻辑!但它有一些问题:--无法撤销 — done 或 delete 执行后无法回退--输出格式死板 — 只能打表格,想加 JSON/Markdown 输出就要改 TaskCat 类--…

作者头像 李华
网站建设 2026/9/13 20:42:40

Qt打包工具实战对比:依赖管理、插件机制与跨平台部署方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 20:42:00

老虎检测数据集构建与YOLO模型训练全指南

1. 老虎检测数据集概述在计算机视觉领域,目标检测是一项基础而重要的任务,而高质量的数据集是算法研发和模型训练的前提。老虎检测数据集作为特定物种的专项数据集,在野生动物保护、生态监测、智能安防等领域具有独特价值。这个数据集通常包含…

作者头像 李华
网站建设 2026/9/13 20:38:09

GAMMA_SOFTWARE-64-18.04在Ubuntu 18.04上的安装实践与故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 20:33:18

Rockchip Android工位机DMA-BUF泄漏导致黑屏根因分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华