AWS · ML 博客

使用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 构建自定义奖励

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 模型生成候选展开。在多轮任务中,一次展开是一个完整的回合,包含一系列轮次(一个轨迹),而不是单个响应。你的奖励函数接收每次展开并执行三个步骤:

  1. 运行任务逻辑。对于对话任务,这可以包括一个用户模拟器,逐轮响应模型。
  2. 通过一个或多个奖励组件(例如,任务正确性、中间行为信号和惩罚)对完成的轨迹进行评分,并通过 metrics_list 报告每个组件。
  3. 为每次展开返回一个聚合奖励(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
        ...

还要验证实际运行的测试数量与预期数量是否一致,以便模型无法用自己微不足道的通过测试来稀释分数。对于部署在实时环境中的奖励函数,请实施这些安全措施,而不是将其视为可选项。

陷阱:什么导致奖励失效,以及如何修复

多轮奖励设计有一系列众所周知的失败模式。

前两种通常会在记录或损失和 KL 曲线中显现出来。失效是危险的一种:聚合奖励、损失和补全长度曲线都可能看起来正常,而你依赖的某个组件却没有任何贡献。本节介绍在此任务上花费我们最多时间的两种失效,以及如何发现它们。

当奖励崩溃为单一策略时

此奖励的早期版本将提问奖励门控在正确性之后。只有当最终代码也通过时,你才能获得提问奖励。它还添加了一个效率项,奖励较短的对话。训练崩溃了。模型收敛到在第一轮猜测。平均奖励冻结,GRPO 优势变为零。两个设计错误导致了这种情况。

首先,门控位于一个不可达的条件之后。在这些困难任务上,正确性接近零,因此提问奖励几乎从未触发。我们想要奖励的行为对优化器来说是不可见的。其次,效率项有一个退化的最优解。更少的轮次使其最大化,因此策略崩溃为单一的、不表态的轮次。每次补全看起来都一样,组内差异消失,学习停止。

修复方法是上一节中的设计:取消你想要的行为的门控,并明确惩罚失败模式。两者都到位后,不同的策略会在组内持续产生不同的奖励,这保留了 GRPO 学习所需的方差。

静默失效的组件

当奖励组件在 GRPO 组内的每次补全中返回相同的值时,其组内方差为零。因此,即使权重最高,它也对优势或梯度没有任何贡献。仍然变化的组件使聚合奖励、策略损失、优势和补全长度看起来正常,因此曲线永远不会揭示问题。

代码奖励中一个常见的原因是正确性评分器在每次展开时都返回 0,因为测试工具从未执行模型的输出。这可能是由于入口点名称不匹配、导入失败或设置错误导致每个测试在其断言运行之前就失败。在我们的运行中,情况正是如此:模型的澄清提问率从大约 34% 上升到 96%。代码正确性几乎没有变化,因为正确性评分器在每次展开时都返回相同的值。

要发现失效的组件,请跟踪每个组件的组内标准差,而不是聚合奖励曲线。聚合曲线会将失效通道隐藏在活跃通道之后。如果该离散度为零或接近零,则该组件没有在训练,无论其权重如何。代码奖励中常见的根本原因是正确性评分器卡在 0,因为测试工具从未真正绑定并运行模型的输出。修复它并确认离散度变为非零。

进行监控以便及早发现

一些习惯可以及早发现这些失败,并且本可以在第一天就发现我们的问题:

清理

本文中的训练运行和环境使用 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 客户的实用解决方案。他拥有卢森堡大学工程学博士学位,博士研究是与剑桥大学合作进行的。

译自 AWS · ML 博客 · 录于 二〇二六年八月十七日