使用Amazon Nova Forge为多轮强化学习定制奖励函数
Custom reward functions for multi-turn reinforcement learning with Amazon Nova Forge
本文介绍如何在Amazon Nova Forge上通过自带编排(BYOO)为多轮强化微调(RFT)设计复合奖励函数。作者使用组相对策略优化(GRPO)训练Amazon Nova Lite 2.0完成500多个协作编码任务,奖励包含正确性、提问行为、猜测惩罚和循环惩罚四个组件。文章详述安全执行模型生成代码的方法,并分析奖励失效陷阱:组件组内方差为零时即使权重最高也不贡献梯度,需监控各组件标准差而非聚合曲线。
在多轮强化学习(RL)中,你的自定义奖励函数决定了模型实际学习的内容。一个细微错误的奖励可能会悄悄教给模型错误的行为,而所有训练曲线看起来却一切正常。设计一个在多轮、智能体任务中保持有效的奖励函数,是定制 Amazon Nova 模型中最困难的部分之一。对于多轮训练,Amazon Nova Forge 通过其自带编排(BYOO)能力,在你的环境中运行奖励逻辑。你可以专注于定义好的结果是什么样的,而 Nova Forge 则协调各轮次的展开、消息传递和对话状态。Nova Forge 还提供无服务器多轮 RL 选项,现已正式可用,适合不希望管理该环境的团队。本文采用 BYOO 路径。
Amazon Nova 提供多种定制方法,其中强化微调(RFT)尤为突出,因为它可以通过迭代反馈教会模型你想要的行为。RFT 与监督微调(SFT)方法不同。它不需要带有注释推理路径的精选示例,而是从模型自身输出的评估信号中学习。多轮 RFT 将此扩展到在一系列步骤中行动的智能体,例如调用工具、执行代码或从错误中恢复。它优化的是整个轨迹的累积奖励,而不是对单个响应进行评分。RFT 的核心在于奖励函数:引导模型的评分机制,也是你需要设计的部分。
图 1 — 从共享检查点进行同等计算后训练后的分布外(OOD)性能。RL 在所有任务变体上提升了 OOD 泛化能力,而 SFT 则使其下降。改编自 Chu 等人,2025
本文聚焦于奖励函数本身:如何设计一个复合多轮奖励,使组相对策略优化(GRPO)能够从中学习。本文还展示了如何在奖励内部安全地执行模型生成的代码,以及为什么需要对每个组件进行监控,以便你信任训练所学到的内容。本系列的第 1 部分涵盖了 Amazon SageMaker HyperPod 和 Nova Forge 基础设施,以及运行这些奖励的训练配置。最后,我们总结了可能悄悄导致奖励失效的陷阱,这些陷阱来自一次真实运行,其中权重最高的组件默默地没有贡献任何学习信号。我们展示了如何发现它们。文中的代码仅供说明。请将其作为你自己奖励实现的起点。
前提条件
要继续,你需要以下内容:
- 一个 Amazon Nova Forge 订阅,提供 Nova Customization SDK 和多轮 RFT API。
- 本系列第 1 部分中的多轮 RFT 基础设施:一个 Amazon SageMaker HyperPod 集群,一个在 Amazon Elastic Container Service(Amazon ECS)上的客户管理环境。
- 一个用于存放展开数据和检查点的 Amazon Simple Storage Service(Amazon S3)存储桶。
- 本文的示例代码,包括奖励环境和演练,来自
aws-samples/sample-nova-multi-turn-rl-infra仓库。自定义奖励环境是可选的:在cdk.json中,部署前将use_custom_env设置为"true",并将custom_env_id设置为你的环境 ID(例如,"my-custom-env")。默认情况下,该堆栈使用内置的 wordle 环境。 - 熟悉强化微调和 GRPO。
使用 Amazon Nova Forge 构建自定义奖励
RFT 的工作原理是从当前模型采样补全,并使用奖励函数对其进行评分。在 Nova Forge 中,奖励函数是你用代码编写的评分器,而不是单独训练的奖励模型。它可以是一个基于规则的检查,用于验证输出(带可验证奖励的强化学习),也可以调用另一个大型语言模型(LLM)来评判响应,这种方法称为 LLM-as-Judge。然后,RFT 调整模型权重,使获得更高奖励的补全更有可能发生。
Nova Forge 使用 GRPO。对于每个对话,GRPO 使用奖励函数对 K 个模型展开进行排序。GRPO 根据批次的归一化奖励(优势)使用排名最高的模型补全来更新模型。使用 GRPO 的 RFT 是一种基础技术,与初始 SFT 相比,能实现显著的性能提升。
奖励信号仅通过其在组内产生的差异来影响学习。如果一个项在组内的每次补全中取值相同,那么它对优势没有任何贡献,因此对梯度也没有贡献。
你的奖励函数如何与 Nova Forge 运行取决于任务。对于单轮 RFT,你将奖励注册为 AWS Lambda 函数,并通过 reward_lambda_arn 将你的配方指向它。像本文这样的多轮任务超出了单次 Lambda 调用的支持范围。多轮对话和长时间运行的评分会超过 Lambda 15 分钟的调用限制。对于这些情况,Nova Forge 使用 BYOO。你设置 rollout.delegate: true,并在环境容器中运行你的环境和奖励逻辑,例如在 Amazon ECS 上。Nova Forge 将每次展开委托给你的环境,然后收集完成的回合用于训练。你的容器管理多轮交互和对话状态:它运行用户模拟器,执行代码,并调用验证器。然后,它为每个样本返回一个聚合奖励(aggregate_reward_score),以及一个可选的逐组件分数列表(metrics_list)。本系列的第 1 部分涵盖了此基础设施及其 AWS Cloud Development Kit(AWS CDK)部署。本文重点介绍奖励。
奖励评估的工作原理
训练任务为每个提示从 Nova 模型生成候选展开。在多轮任务中,一次展开是一个完整的回合,包含一系列轮次(一个轨迹),而不是单个响应。你的奖励函数接收每次展开并执行三个步骤:
- 运行任务逻辑。对于对话任务,这可以包括一个用户模拟器,逐轮响应模型。
- 通过一个或多个奖励组件(例如,任务正确性、中间行为信号和惩罚)对完成的轨迹进行评分,并通过
metrics_list报告每个组件。 - 为每次展开返回一个聚合奖励(
aggregate_reward_score),训练将其转换为组内优势。
图 2 — 单次多轮展开:Nova Forge 委托给你的环境容器,该容器询问模拟器或运行提交的代码,然后为 GRPO 返回奖励分数
这个循环在许多训练步骤中重复,逐步塑造模型以最大化整个序列的累积奖励。模型会朝着你的奖励实际奖励的方向优化,正如我们将展示的,这并不总是你认为你编写的内容。
选择多轮奖励的结构
单一的标量奖励容易被利用,而在多轮任务中,单一的最终奖励通常过于稀疏,难以学习。因此,大多数生产环境中的多轮奖励结合了三种信号:结果奖励、行为奖励和惩罚。
- 回合级(结果)奖励 捕获最终产物是否满足目标。例如,单元测试是否通过,或工作流是否完成?它们针对你最终关心的事情,但在训练早期往往稀疏且接近零。
- 轮次级(行为)奖励 捕获模型是否表现出你想要的中间行为,例如在行动前询问、调用正确的工具或避免循环。它们最适合塑造结果奖励过于稀疏而无法教授的行为,但如果设计不当,它们可以在没有真正进展的情况下获得。
- 惩罚 明确阻止失败模式,如猜测、重复或停滞。它们区分好的和坏的策略,使优化器看到梯度。
组合这些信号,使模型既能学习行为也能学习结果,同时避免一个组件掩盖或饿死另一个组件。本文的其余部分将具体说明这一点。我们为一个真实任务设计了一个四组件奖励,并在其中安全地执行模型生成的代码。然后,我们探讨可能导致此类奖励失效的陷阱以及如何修复它们。
实例演练:教 Amazon Nova Lite 2.0 在编码前先提问
我们构建了一个包含 500 多个独特编程任务的多轮协作编码任务。我们使用多轮 RFT,在 Amazon SageMaker HyperPod 上使用带有低秩适配(LoRA)的 GRPO 训练了 Amazon Nova Lite 2.0,并在客户管理的环境容器中实现了奖励(Nova Forge BYOO 路径)。具体机制如下:
- 模型看到一个简短、说明不完整的编码请求。
- 一个用户模拟器私下持有完整规格,并且仅在模型提问时透露一个细节。
- 每一轮,模型要么提出澄清问题,要么提交代码。
- 如果它提问,模拟器会回答,对话继续。
- 如果它提交代码,展开结束,你的奖励处理器会针对隐藏的单元测试执行该代码以评估正确性。(安全运行模型生成的代码是我们稍后会讨论的问题。)
设计意图是猜测会产生错误的代码,而提问会揭示隐藏的细节并导致正确的代码。“先提问”应该由任务本身强制实现。
设计奖励
使目标行为直接且独立地可获得奖励,并明确惩罚失败模式。对于此任务,奖励是四个组件的加权和:
| 组件 | 权重 | 定义 |
|---|---|---|
| correctness | 1.0 | 最终代码通过的隐藏单元测试的比例 |
| asked_before_coding | 0.6 | 如果在第 1 轮提问然后提交,则为 1.0;如果在之后提问然后提交,则为 0.6;否则为 0(非门控) |
| guessed_immediately | 0.4 | 惩罚:如果第一轮是代码而没有提问,则为 -1.0 |
| loop_penalty | 0.2 | 如果最后两轮超过 80% 相似,则为 -0.5 |
两个原则驱动了设计。首先,取消你想要的行为的门控:asked_before_coding 独立计分,不以正确性为条件,但它确实要求模型最终提交代码,这关闭了“永远提问,从不回答”的漏洞。其次,惩罚失败模式:guessed_immediately 使猜测严格劣于提问,这恢复了 GRPO 组内策略之间的差异,即算法产生梯度所需的差异。
在环境容器的奖励处理器中调用这些组件评分器,并通过 metrics_list 报告每个值:
def asked_before_coding(completion, answer, **kw) -> float:
msgs = _messages(completion, kw)
first_q = _first_question_turn(msgs, parser)
final = _final_code(completion, parser)
committed = bool(final) and not _is_question(final)
if first_q == 1 and committed:
return 1.0 # asked first, then committed (ideal)
if first_q is not None and committed:
return 0.6 # asked later, then committed
return 0.0 # never asked, or asked but never committed
def guessed_immediately(completion, answer, **kw) -> float:
for m in _assistant_turns(completion, kw):
code = _code_of(parser.parse(m["content"]))
return -1.0 if (code and not _is_question(code)) else 0.0
return 0.0
安全执行模型生成的代码
正确性组件会针对单元测试运行模型生成的代码。RL 下的模型输出是通过探索优化的,因此将其视为未经验证的。容器在其自己的隔离执行环境中运行,但你仍然应该采取预防措施。不要向生成的代码暴露凭据或网络。应用资源限制并在临时目录中运行。使用每次运行的随机哨兵,以便模型无法通过向 stderr 写入预期标记来伪造结果。对于需要额外隔离的执行,调用专用的沙箱。此测试工具展示了该模式:
import resource, secrets, subprocess, sys, tempfile
from pathlib import Path
def run_tests(code: str, test: str, timeout_s: int = 30) -> float:
nonce = secrets.token_hex(8) # unforgeable per-run marker
harness = (
"import sys, unittest, json\n"
f"{code}\n\n{test}\n\n"
'if __name__ == "__main__":\n'
" r = unittest.TextTestRunner(stream=sys.stderr, verbosity=0).run(\n"
" unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]))\n"
f" sys.stderr.write('__{nonce}__' + json.dumps("
"{'total': r.testsRun, 'passed': r.testsRun - len(r.failures) - len(r.errors)}) + '__"
f"{nonce}__')\n"
)
def _limit():
resource.setrlimit(resource.RLIMIT_CPU, (timeout_s, timeout_s))
resource.setrlimit(resource.RLIMIT_AS, (2 * 1024**3, 2 * 1024**3)) # 2 GB
resource.setrlimit(resource.RLIMIT_NPROC, (64, 64))
with tempfile.TemporaryDirectory() as cwd:
path = Path(cwd) / "h.py"
path.write_text(harness)
try:
proc = subprocess.run(
[sys.executable, str(path)],
capture_output=True,
text=True,
timeout=timeout_s,
cwd=cwd,
env={"PATH": "/usr/bin"}, # no creds, no network env
preexec_fn=_limit,
)
except Exception:
return 0.0
# parse the nonce-delimited summary and validate the test count before scoring
...
还要验证实际运行的测试数量与预期数量是否一致,以便模型无法用自己微不足道的通过测试来稀释分数。对于部署在实时环境中的奖励函数,请实施这些安全措施,而不是将其视为可选项。
陷阱:什么导致奖励失效,以及如何修复
多轮奖励设计有一系列众所周知的失败模式。
- 奖励黑客 是指模型利用代理指标而不是实现目标。
- 训练不稳定 是指更新发散,熵崩溃或 Kullback-Leibler(KL)项爆炸。
- 奖励失效 是指信号退化,直到组内差异消失,学习悄然停止。
前两种通常会在记录或损失和 KL 曲线中显现出来。失效是危险的一种:聚合奖励、损失和补全长度曲线都可能看起来正常,而你依赖的某个组件却没有任何贡献。本节介绍在此任务上花费我们最多时间的两种失效,以及如何发现它们。
当奖励崩溃为单一策略时
此奖励的早期版本将提问奖励门控在正确性之后。只有当最终代码也通过时,你才能获得提问奖励。它还添加了一个效率项,奖励较短的对话。训练崩溃了。模型收敛到在第一轮猜测。平均奖励冻结,GRPO 优势变为零。两个设计错误导致了这种情况。
首先,门控位于一个不可达的条件之后。在这些困难任务上,正确性接近零,因此提问奖励几乎从未触发。我们想要奖励的行为对优化器来说是不可见的。其次,效率项有一个退化的最优解。更少的轮次使其最大化,因此策略崩溃为单一的、不表态的轮次。每次补全看起来都一样,组内差异消失,学习停止。
修复方法是上一节中的设计:取消你想要的行为的门控,并明确惩罚失败模式。两者都到位后,不同的策略会在组内持续产生不同的奖励,这保留了 GRPO 学习所需的方差。
静默失效的组件
当奖励组件在 GRPO 组内的每次补全中返回相同的值时,其组内方差为零。因此,即使权重最高,它也对优势或梯度没有任何贡献。仍然变化的组件使聚合奖励、策略损失、优势和补全长度看起来正常,因此曲线永远不会揭示问题。
代码奖励中一个常见的原因是正确性评分器在每次展开时都返回 0,因为测试工具从未执行模型的输出。这可能是由于入口点名称不匹配、导入失败或设置错误导致每个测试在其断言运行之前就失败。在我们的运行中,情况正是如此:模型的澄清提问率从大约 34% 上升到 96%。代码正确性几乎没有变化,因为正确性评分器在每次展开时都返回相同的值。
要发现失效的组件,请跟踪每个组件的组内标准差,而不是聚合奖励曲线。聚合曲线会将失效通道隐藏在活跃通道之后。如果该离散度为零或接近零,则该组件没有在训练,无论其权重如何。代码奖励中常见的根本原因是正确性评分器卡在 0,因为测试工具从未真正绑定并运行模型的输出。修复它并确认离散度变为非零。
进行监控以便及早发现
一些习惯可以及早发现这些失败,并且本可以在第一天就发现我们的问题:
- 监控每个组件对优势的贡献,而不仅仅是每个组件的奖励。 通过
metrics_list报告每个组件,并跟踪其均值和组内标准差。任何组内方差接近零的组件对学习都没有贡献,无论其权重如何。你可能会将平坦的奖励均值 0.000 视为“这些任务太难了”,但平坦的组内方差是明确的。 - 按你正在测试的组件排序阅读记录,而不是按总奖励排序。 按总奖励排序会将失效组件隐藏在活跃组件之后。按可疑组件排序会立即暴露问题。
- 消融或恢复你声称正在工作的每个组件。 如果移除一个组件没有任何变化,那么它就没有在工作。如果恢复一个组件恢复了你认为已经优化的指标,那么它就不在目标函数中。
- 为组内方差而设计。 GRPO 从同一提示的补全之间的差异中学习。不可达的门控、退化的塑形最优解和饱和项都会消除这种差异并停止学习,即使奖励看起来正常。取消目标行为的门控并惩罚失败模式,以便策略分离。
- 注意一个密集奖励饿死另一个。 一旦我们密集的提问奖励饱和,稀疏的正确性奖励就无法推动策略。如果行为塑形项占主导地位,你关心的结果项可能永远得不到梯度。考虑在塑形项饱和后降低其权重,或提高结果项的权重。
- 将模型输出视为未经验证的。 对生成的代码的任何执行进行沙箱处理(无凭据、无网络、资源限制),并使验证器不可伪造(随机哨兵、测试计数验证)。
清理
本文中的训练运行和环境使用 SageMaker HyperPod 和 Amazon ECS 资源,这些资源在运行时会产生费用。完成实验后,请按照本系列第 1 部分中的拆除步骤删除 SageMaker HyperPod 集群和 Amazon ECS 环境,这将停止最大的费用。如果不再需要,请从 Amazon S3 存储桶中删除展开数据和检查点。
结论
奖励函数是 RFT 中你设计的部分,也是细微失败所在之处。在你的运行中,模型可能会学习你训练的行为,而你关心的某个项对学习没有任何贡献,并且没有聚合指标会揭示这一点。更好的监控,而不是更好的算法,解决了这个问题。测量每个组件对优势的贡献,通过你正在测试的组件的视角阅读记录,并消融你声称正在工作的部分。
通过 Amazon Nova Forge 上的自定义奖励函数,你可以完全控制奖励,这意味着正确实现它的责任在于你。有关使这些运行可复现的基础设施和 AWS CDK 部署,请参阅本系列的第 1 部分。
致谢
特别感谢 Mahima Chaudhary 对本文的审阅和贡献。
关于作者
Maria Masood Maria 专注于智能体 AI、强化微调和多轮智能体训练。她在机器学习方面拥有专业知识,涵盖大型语言模型定制、奖励建模以及为 AI 智能体构建端到端训练管道。Maria 内心是一位可持续发展爱好者,喜欢园艺和制作拿铁咖啡。
Nick Biso Nick 是 AWS Professional Services 的机器学习工程师。他使用数据科学和工程解决复杂的组织和技术挑战。此外,他在 AWS 云上构建和部署 AI/ML 模型。他的热情还延伸到对旅行和多元文化体验的喜爱。
Laurent Mombaerts Laurent 是 AWS 全球销售和营销部门科学与经济团队的高级应用科学家。他的研究涵盖 LLM 后训练、多模态智能体系统以及搜索和信息检索,重点是推动 AI 进步转化为 AWS 客户的实用解决方案。他拥有卢森堡大学工程学博士学位,博士研究是与剑桥大学合作进行的。