Amazon Nova Forge、マルチターン RL のカスタム報酬関数機能を強化
本文の状態
日本語全文を表示中
詳細モードで約19分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
AWS Machine Learning Blog
AWS は Amazon Nova Forge を通じて、マルチターン強化学習におけるカスタム報酬関数の設計を可能にする BYOO 機能とサーバーレス型オプションを一般提供し、モデルの振る舞いを学習させるための基盤を整備した。
AI深層分析を開く2026年8月15日 02:28
AI深層分析
キーポイント
BYOO を活用した柔軟な報酬設計
Amazon Nova Forge は Bring Your Own Orchestration (BYOO) 機能により、開発者が独自環境でカスタム報酬ロジックを実行し、マルチターンやエージェントタスクにおける複雑な学習目標を定義できる。
強化学習微調整(RFT)の多段階対応
強化学習微調整は単一の応答評価ではなく、ツール呼び出しやコード実行を含む一連のステップ全体における累積報酬を最適化し、SFT と比較して分布外タスクへの汎化性能を向上させる。
サーバーレス型マルチターン RL の提供
環境管理を不要とするサーバーレス型のマルチターン強化学習オプションが一般利用可能となり、インフラ構築コストをかけずに学習プロセスを開始できる選択肢が増えた。
報酬関数の設計と安全性の確保
GRPOが学習できる複合的な多ターン報酬関数を設計する方法と、報酬内でモデル生成コードを安全に実行する手法を示す。
トレーニングの信頼性を高めるための計測
各コンポーネントを計測してトレーニングが何を学習しているかを信頼できるようにする重要性について述べる。
重要な引用
In multi-turn reinforcement learning (RL), your custom reward function decides what the model actually learns.
Designing a reward that holds up over multi-turn, agentic tasks is one of the hardest parts of customizing Amazon Nova models.
Multi-turn RFT extends this to agents that act over a sequence of steps, such as calling tools, executing code, or recovering from a mistake.
This post focuses on the reward function itself: how to design a composite multi-turn reward that Group Relative Policy Optimization (GRPO) can learn from.
編集コメントを表示
編集コメント
マルチターン強化学習の実用化において、報酬関数の設計がボトルネックとなっていた課題に対し、BYOO とサーバーレスの二つのアプローチで解決策を示した点は意義深い。特に、モデルの学習プロセスを可視化し信頼性を高めるためのインストゥルメンテーションの重要性に言及している点も注目される。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
多段階強化学習(RL)において、カスタム報酬関数はモデルが実際に何を学ぶかを決定します。わずかな誤りが、訓練曲線は健全に見えるにもかかわらず、間違ったことを静かに学習させてしまう可能性があります。多段階やエージェントタスクに耐えうる報酬を設計することは、Amazon Nova モデル をカスタマイズする上で最も困難な部分の一つです。
多段階トレーニングでは、Amazon Nova Forge は Bring Your Own Orchestration (BYOO) 機能を通じて、報酬ロジックをユーザー自身の環境で実行します。これにより、良い成果がどのようなものかを定義することに注力でき、Nova Forge がターンを超えたロールアウト、メッセージのやり取り、会話状態の調整を担当します。
また、環境管理を避けたいチーム向けに、サーバーレス型の多段階 RL オプションも提供しており、現在一般利用可能です。本稿では BYOO のアプローチについて解説します。
Amazon Nova では複数のカスタマイズ手法が用意されていますが、その中でもリインフォースメント・ファインチューニング(RFT)は特筆すべきアプローチです。RFT は、モデルに対して反復的なフィードバックを通じて望ましい行動を学習させることができます。
SFT(Supervised Fine-Tuning:教師あり微調整)とは異なるアプローチを取る RFT では、注釈付きの推論パスを持つ手作業で選別された例を必要としません。代わりに、モデル自身の出力に対する評価信号から学習します。マルチターン RFT は、この考え方をツール呼び出しやコード実行、ミスの回復など一連のステップにわたって行動するエージェントへと拡張したものです。単一の応答を採点するのではなく、全体の軌跡を通じて累積報酬を最適化します。
RFT の核心となるのは「リワード関数」です。これはモデルを導くスコアリング機構であり、まさにあなたが設計する部分です。

図 1 — 同じ計算量でポストトレーニングを行った後の、分布外(OOD)性能。共有チェックポイントからの結果です。RL はすべてのタスクバリアントにおいて OOD 汎化能力を向上させる一方、SFT は低下します。Chu et al., 2025 より転載
本稿では、報酬関数そのものに焦点を当てます。具体的には、Group Relative Policy Optimization (GRPO) が学習できるような複合的な多ターン報酬の設計方法について解説します。また、報酬関数の内部でモデルが生成したコードを安全に実行する方法や、トレーニング中の学習内容を信頼できるよう各コンポーネントを計測(インストルメント)する重要性についても触れます。
本シリーズの前編では、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(Reinforcement Fine-Tuning)は、現在のモデルから生成された回答をサンプリングし、報酬関数で評価する仕組みです。Nova Forge では、この報酬関数は別途訓練したモデルではなく、コードとして記述したグラダー(採点者)となります。これは出力を検証するルールベースのチェック(検証可能な報酬を用いた強化学習)也可以是、別の大規模言語モデル(LLM)に回答を評価させる「LLM-as-Judge」という手法です。
RFT はその後、報酬の高い回答がより生成されやすいよう、モデルの重みを調整します。Nova Forge では GRPO を採用しています。各会話において、GRPO は報酬関数を使って K 個のモデルによるロールアウトを順位付けし、その中で最も評価された回答を用いて、バッチ内の正規化された報酬(アドバンテージ)に基づきモデルを更新します。GRPO を用いた RFT は、初期の SFT(Supervised Fine-Tuning)と比較して明確な性能向上をもたらす基盤技術です。
報酬信号が学習に影響を与えるのは、グループ内での変動を通じてのみです。ある項目がグループ内のすべての回答で同じ値をとる場合、それはアドバンテージに寄与せず、したがって勾配にも寄与しません。
Nova Forge における報酬関数の実行方法は、タスクの種類によって異なります。単発の RFT(Reinforcement Fine-Tuning)では、報酬を AWS Lambda 関数として登録し、レシピ内で reward_lambda_arn を指定して参照します。
しかし、本記事で取り上げるようなマルチターン(多段階)タスクは、1 回の Lambda 呼び出しの制限を超えています。会話のやり取りが複数回にわたったり、スコアリング処理に長時間を要したりするケースでは、Lambda の 15 分という実行時間制限に抵触してしまうためです。
こうしたケースでは Nova Forge は BYOO(Bring Your Own Environment)を採用します。設定ファイルで rollout.delegate: true を指定し、環境と報酬ロジックを実行するコンテナを Amazon ECS などの外部環境に用意します。Nova Forge はロールアウト処理を外部環境に委譲し、完了したエピソードデータを収集してトレーニングに活用します。
コンテナは、多段階の対話と会話状態を管理します。具体的には、ユーザーシミュレータを実行し、コードを実行し、検証器(verifier)を呼び出します。その後、サンプルごとの集計報酬(aggregate_reward_score)と、オプションで各構成要素ごとのスコアリスト(metrics_list)を返します。
本シリーズの第1部では、このインフラストラクチャおよびその AWS Cloud Development Kit (AWS CDK) によるデプロイについて解説しました。今回の記事は報酬の評価に焦点を当てます。
報酬評価の仕組み
トレーニングジョブは、各プロンプトに対して Nova モデルから候補となるロールアウトを生成します。マルチターンタスクにおけるロールアウトとは、単一の応答ではなく、一連のターン(=軌道)を含む完全なエピソードのことです。
報酬関数は各ロールアウトを受け取り、以下の 3 つのステップを実行します。
- タスクロジックの実行: 会話型タスクの場合、モデルの応答に対して順次反応するユーザーシミュレータを含めることができます。
- 完成した軌道の評価: タスクの正しさや中間行動信号、ペナルティなど、1 つ以上の報酬コンポーネントにわたって軌道を評価し、各項目を
metrics_listを通じて報告します。 - ロールアウトごとの集約報酬の返却: 学習プロセスでグループ内アドバンテージに変換される
aggregate_reward_scoreを返します。

このサイクルは多数のトレーニングステップにわたって繰り返され、モデルがシーケンス全体を通じて累積報酬を最大化するように段階的に調整されていきます。モデルが最適化するのは、実際にはあなたの報酬関数が何を評価しているかによって決まります。これは、あなたが「書いたつもり」のものとは必ずしも一致しないことを示しています。
Choosing the structure of a multi-turn reward
単一のスカラー報酬はゲーム化されやすく、マルチターンタスクでは単一のターミナル報酬だけでは学習に必要な信号が希薄になりがちです。そのため、実運用環境のマルチターン報酬では通常、3 つのシグナルを組み合わせます。
エピソードレベル(アウトカム)報酬は、最終的な成果物が目標を満たしたかどうかを捉えます。例えば、「ユニットテストに合格したか」「ワークフローが完了したか」などが該当します。これは最終的に最も重視すべき点に焦点を当てていますが、学習初期には信号が希薄で、ほぼゼロに近い値になりがちです。
ターンレベル(行動)報酬は、モデルが望ましい中間行動を示しているかを捉えます。具体的には、「行動する前に質問するか」「適切なツールを呼び出すか」「ループに陥らないか」などが該当します。アウトカム報酬だけでは信号が希薄すぎて教えきれない行動の形成には最適ですが、設計を慎重に行わないと、実際の進捗がない状態でも報酬を獲得できてしまうリスクがあります。
ペナルティは、推測や繰り返し、停止といった失敗モードを明確に抑制するために設けられます。これにより良策と悪策が区別され、オプティマイザが勾配を認識できるようになります。
これらの要素を組み合わせることで、モデルは行動とアウトカムの両方を学習できます。一方のコンポーネントが他方を隠蔽したり、信号不足に陥ったりすることはありません。この投稿の後半では、具体的な設計例を示します。実際のタスクに対して 4 つのコンポーネントからなる報酬を設計し、その中でモデル生成コードを安全に実行する仕組みについて解説します。さらに、そのような報酬が崩壊しかねない落とし穴と、それに対する対策についても順を追って説明していきます。
実例:Amazon Nova Lite 2.0 にコーディング前に質問するよう教える
500 件以上の多様なプログラミング課題を対象に、複数ターンにわたる共同コーディングタスクを構築しました。Amazon Nova Lite 2.0 を用いて、Amazon SageMaker HyperPod 上で GRPO と Low-Rank Adaptation (LoRA) を組み合わせたマルチターン RFT(Reinforcement Fine-Tuning)を実施し、報酬ロジックは顧客が管理する環境コンテナ内で実行されるように設計しました。これは Nova Forge の BYOO(Bring Your Own Environment)パスに相当します。
タスクの仕組みは以下の通りです。
- モデルには、簡潔でありながら要件が不完全なコーディング依頼が表示されます。
- ユーザーシミュレータは完全な仕様を秘匿しており、モデルが質問した際にのみ詳細を明かします。
- 各ターンでモデルは、追加の質問を行うかコードを実装するかを選択します。質問を選んだ場合はシミュレータが回答し会話は続行されます。一方、コードを実装するとロールアウトが終了し、報酬処理系が隠されたユニットテストに対してそのコードを実行して正答性を評価します(モデル生成コードの安全な実行については後述します)。
この設計の意図は、推測による実装では誤ったコードが生成される一方で、適切な質問を行うことで隠れた要件が明らかになり、正しいコードに到達できる点にあります。タスク自体が「まず質問せよ」という行動を強制するように設計されています。
報酬関数の設計
目標とする振る舞いを直接的かつ独立して評価可能にしつつ、失敗パターンには明確なペナルティを与える必要があります。本タスクでは、報酬は以下の 4 つの要素の加重和として定義されます:
| コンポーネント | 重み | 定義 |
|---|---|---|
correctness | 1.0 | 最終コード上で実行された隠しユニットテストの合格割合 |
asked_before_coding | 0.6 | 1 巡目で質問してその後コードを実行した場合に 1.0、それ以降で質問して実行した場合に 0.6、それ以外(ゲートなし)の場合は 0 |
guessed_immediately | 0.4 | ペナルティ:最初の巡で質問なしにコードを実行した場合、-1.0 |
loop_penalty | 0.2 | 最後の 2 巡の類似度が 80% を超える場合、-0.5 |
設計を導く2つの原則があります。第一に、目指す行動の制限を外すことです。asked_before_coding は「正しさ」が条件となるのではなく、独立して評価されます。ただし、モデルは最終的にコードを実行することを求められるため、「永遠に質問し続けるが回答しない」という抜け穴を塞ぐことができます。 (原文の技術表記: correctness)
次に、失敗モードに対するペナルティを設定します。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モデル生成コードの安全な実行
correctness コンポーネントは、モデルが生成したコードをユニットテストに対して実行します。強化学習(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)項が発散する状態です。そして「報酬の崩壊」は、信号が劣化してグループ内の変動が消え、学習が静かに停止してしまう現象です。
最初の二つは通常、ログや損失曲線、KL 曲線で警告を発します。しかし、「崩壊」は最も危険です。集約された報酬、損失、そして生成長さの曲線がすべて健全に見えても、あなたが依存しているコンポーネントが何の貢献もしていない可能性があります。
このセクションでは、今回のタスクで最も時間を要した二つの「崩壊」の失敗と、それらを検出する方法について解説します。
報酬が単一の戦略に収束するケース
以前のバージョンでは、この報酬関数で「質問ボーナス」は correctness にゲートされていました。つまり、最終コードが合格した場合のみ、質問に対する報酬が得られる仕組みでした。また、会話の短さを評価する効率性項も追加していました。
その結果、トレーニングが崩壊しました。モデルは 1 巡目で推測に固執するようになり、平均報酬が凍結し、GRPO のアドバンテージはゼロになってしまったのです。
2 つの設計上のミスが原因でした。まず、ゲートが到達不可能な条件の後ろに配置されていました。こうした難易度の高いタスクでは正答率がほぼゼロだったため、要求ボーナスはほとんど発動しませんでした。最適化プロセスにとって、報酬を与えたい行動は見えない状態だったのです。
2 つ目の問題は、効率性項に退化した最適解が存在していたことです。ターン数を減らすことが最大化されるため、ポリシーが単一の非決定的なターンへと収束してしまいました。すべての完了結果が似通ってしまい、グループ内の変動が消滅、学習が停止してしまったのです。
解決策は前節で示した設計です。報酬を与えたい行動をゲートから外し、失敗モードを明示的にペナルティとして課すことです。この 2 つを両立させることで、同じグループ内で異なる戦略がそれぞれ異なる報酬を生み出し続けるようになります。これにより、GRPO が学習するために必要な分散(バリアンス)が維持されます。
静かに死滅したコンポーネント
GRPO グループ内のすべての完了結果に対して報酬コンポーネントが同じ値を返す場合、そのグループ内変異はゼロになります。その結果、重み付けを最大にしても、そのコンポーネントはアドバンテージや勾配に一切寄与しません。
変動し続ける他のコンポーネントによって、集計された報酬、ポリシーの損失、アドバンテージ、完了ターン数などが健全に見えるため、学習曲線からはこの問題が検出されません。コード報酬でよく見られる原因の一つは、正答性を評価するスコアラーです。
原文を表示
In multi-turn reinforcement learning (RL), your custom reward function decides what the model actually learns. A subtly wrong reward can quietly teach the wrong thing while every training curve looks healthy. Designing a reward that holds up over multi-turn, agentic tasks is one of the hardest parts of customizing Amazon Nova models. For multi-turn training, Amazon Nova Forge runs your reward logic in your own environment through its Bring Your Own Orchestration (BYOO) capability. You can focus on defining what a good outcome looks like while Nova Forge coordinates rollouts, message passing, and conversation state across turns. Nova Forge also offers a serverless multi-turn RL option, now generally available, for teams that prefer not to manage that environment. This post uses the BYOO path.
Amazon Nova offers multiple customization approaches, with reinforcement fine-tuning (RFT) standing out because it can teach models the behaviors you want through iterative feedback. RFT takes a different approach from supervised fine-tuning (SFT). Rather than requiring curated examples with annotated reasoning paths, it learns from evaluation signals on the model’s own outputs. Multi-turn RFT extends this to agents that act over a sequence of steps, such as calling tools, executing code, or recovering from a mistake. It optimizes cumulative reward across the whole trajectory rather than grading a single response. At the heart of RFT lies the reward function: the scoring mechanism that guides the model, and the part you design.

This post focuses on the reward function itself: how to design a composite multi-turn reward that Group Relative Policy Optimization (GRPO) can learn from. This post also shows how to execute model-generated code safely inside the reward, and why to instrument each component so you can trust what training is learning. Part 1 of this series covers the Amazon SageMaker HyperPod and Nova Forge infrastructure. It also covers the training configuration that runs these rewards. We close with the pitfalls that can quietly collapse a reward, drawn from a real run where the highest-weighted component silently contributed no learning signal at all. We show how to catch them. The code throughout is illustrative. Use it as a starting point for your own reward implementation.
Prerequisites
To follow along, you need the following:
- An Amazon Nova Forge subscription, which provides the Nova Customization SDK and the multi-turn RFT APIs.
- The multi-turn RFT infrastructure from Part 1 of this series:
An Amazon SageMaker HyperPod cluster, a customer-managed environment on Amazon Elastic Container Service (Amazon ECS).
- An Amazon Simple Storage Service (Amazon S3) bucket for rollout data and checkpoints.
- The example code for this post, including the reward environment and a walkthrough, from the aws-samples/sample-nova-multi-turn-rl-infra repository.
- The custom reward environment is opt-in: in cdk.json, set use_custom_env to “true” and custom_env_id to your environment ID (for example, “my-custom-env”) before you deploy. By default the stack uses the built-in wordle environment.
- Familiarity with reinforcement fine-tuning and GRPO.
Building custom rewards with Amazon Nova Forge
RFT works by sampling completions from the current model and scoring them with a reward function. In Nova Forge, the reward function is a grader you write in code, and not a separately trained reward model. It can be a rule-based check that verifies the output (reinforcement learning with verifiable rewards), or it can call another large language model (LLM) to judge the response, an approach known as LLM-as-Judge.
RFT then adjusts the model weights to make higher-reward completions more likely. Nova Forge uses GRPO. For each conversation, GRPO uses the reward function to rank K model rollouts. GRPO uses the highest-ranked model completions to update the model according to the normalized reward (the advantage) of the batch. RFT with GRPO is a fundamental technique achieving noticeable performance gains over initial SFT.
A reward signal influences learning only through the variation it creates within a group. If a term takes the same value for every completion in a group, it contributes nothing to the advantage. It therefore contributes nothing to the gradient.
How your reward function runs with Nova Forge depends on the task. With single-turn RFT, you register the reward as an AWS Lambda function and point your recipe at it through reward_lambda_arn. Multi-turn tasks like the one in this post exceed what a single Lambda invocation supports. Multi-turn conversations and long-running scoring run past the 15-minute Lambda invocation limit. For these, Nova Forge uses BYOO. You set rollout.delegate: true and run your environment and reward logic in an environment container, for example on Amazon ECS. Nova Forge delegates each rollout to your environment. It then collects the completed episodes back for training. Your container manages the multi-turn interaction and conversation state: it runs the user simulator, executes code, and calls a verifier. It then returns an aggregate reward per sample (aggregate_reward_score), plus an optional list of per-component scores (metrics_list). Part 1 of this series covers this infrastructure and its AWS Cloud Development Kit (AWS CDK) deployment. This post focuses on the reward.
How reward evaluation works
The training job generates candidate rollouts from the Nova model for each prompt. In a multi-turn task, a rollout is a full *episode* with a sequence of turns (a *trajectory*), not a single response. Your reward function receives each rollout and performs three steps:
- Runs the task logic. For a conversational task, this can include a user simulator that responds to the model turn by turn.
- Scores the completed trajectory across one or more reward components (for example, task correctness, an intermediate-behavior signal, and penalties), reporting each through metrics_list.
- Returns an aggregate reward per rollout (aggregate_reward_score), which training turns into within-group advantages.

This cycle repeats over many training steps, progressively shaping the model to maximize cumulative reward across the whole sequence. The model optimizes toward whatever your reward actually rewards, which, as we show, is not always what you think you wrote.
Choosing the structure of a multi-turn reward
Single scalar rewards are straightforward to game, and a single terminal reward is often too sparse to learn from in multi-turn tasks. Most production multi-turn rewards therefore combine three kinds of signal: outcome rewards, behavioral rewards, and penalties.
Episode-level (outcome) rewards capture whether the final artifact satisfied the goal. For example, did the unit tests pass, or did the workflow complete? They target the thing you ultimately care about, but they tend to be sparse and near-zero early in training.
Turn-level (behavioral) rewards capture whether the model exhibited the intermediate behavior you want, such as asking before acting, calling the right tool, or avoiding loops. They are best for shaping behavior the outcome reward is too sparse to teach, though they can be earned without real progress if not designed carefully. Penalties explicitly discourage a failure mode such as guessing, repeating, or stalling. They separate good and bad strategies so the optimizer sees a gradient.
Combine these so the model learns both the behavior *and* the outcome, without one component masking or starving the other. The rest of this post makes that concrete. We design a four-component reward for a real task and execute model-generated code safely inside it. Then we walk through the pitfalls that can collapse such a reward and how to fix them.
Worked example: Teaching Amazon Nova Lite 2.0 to ask before coding
We built a multi-turn collaborative-coding task over 500 unique programming tasks. We trained Amazon Nova Lite 2.0 on it with multi-turn RFT, using GRPO with Low-Rank Adaptation (LoRA), on Amazon SageMaker HyperPod, implementing the reward inside a customer-managed environment container (the Nova Forge BYOO path).
The mechanics are as follows:
- The model sees a brief, under-specified coding request.
- A user simulator holds the full specification privately and reveals a detail only when the model asks.
- Each turn, the model either asks a clarifying question or commits code. If it asks, the simulator answers and the conversation continues. If it commits code, the rollout ends and your reward handler executes that code against hidden unit tests to score correctness. (Running model-generated code safely is a concern we return to later.)
The design intent is that guessing produces wrong code, while asking surfaces the hidden detail and leads to correct code. “Ask first” should be forced by the task.
Designing the reward
Make the target behavior directly and independently rewardable, and penalize the failure mode explicitly. For this task, the reward is a weighted sum of four components:
| Component | Weight | Definition |
|---|---|---|
correctness | 1.0 | fraction of hidden unit tests passing on the final code |
asked_before_coding | 0.6 | 1.0 if asked on turn 1 then committed; 0.6 if asked later then committed; else 0 (un-gated) |
guessed_immediately | 0.4 | penalty: -1.0 if the first turn is code with no question |
loop_penalty | 0.2 | -0.5 if the last two turns are more than 80% similar |
Two principles drive the design. First, un-gate the behavior you want: asked_before_coding is credited on its own, not conditioned on correctness, but it does require the model to eventually commit code, which closes the “ask forever, never answer” loophole. Second, penalize the failure mode: guessed_immediately makes guessing strictly worse than asking, which restores variation between strategies within a GRPO group, the variation the algorithm needs to produce a gradient.
Call these component scorers inside the reward handler in the environment container, and report each value through 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.0Executing model-generated code safely
The correctness component runs model-generated code against unit tests. Model output under RL is optimized through exploration, so treat it as not validated. The container runs in its own isolated execution environment, but you should still take precautions. Do not expose credentials or network to the generated code. Apply resource limits and run in a temporary directory. Use a per-run random sentinel so the model cannot forge the result by writing the expected marker to stderr. For execution that requires additional isolation, call a dedicated sandbox. This harness shows the pattern:
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
...Also validate the number of tests actually run against the number expected, so the model cannot dilute the score with its own trivially-passing tests. For reward functions deployed in live environments, implement these security measures rather than treating them as optional.
Pitfalls: What makes a reward collapse, and how to fix it
Multi-turn reward design has a well-known set of failure modes. Reward hacking is where the model games a proxy instead of achieving the goal. Training instability is where updates diverge and entropy collapses or the Kullback-Leibler (KL) term blows up. Reward collapse is where the signal degenerates until within-group variation disappears and learning quietly stops. The first two usually announce themselves in transcripts or in loss and KL curves. Collapse is the dangerous one: aggregate reward, loss, and completion-length curves can all look healthy while a component you’re counting on contributes nothing. This section covers the two collapse failures that cost us the most time on this task, and how to catch them.
When a reward collapses to a single strategy
An earlier version of this reward gated the asking bonus behind correctness. You earned the asking reward only if the final code also passed. It also added an efficiency term that rewarded shorter conversations. Training collapsed. The model converged to guessing on turn one. The mean reward froze, and the GRPO advantage went to zero.
Two design errors caused it. First, the gate sat behind an unreachable condition. Correctness was near zero on these hard tasks, so the asking bonus almost never fired. The behavior we wanted to reward was invisible to the optimizer. Second, the efficiency term had a degenerate optimum. Fewer turns maximized it, so the policy collapsed onto a single, non-committal turn. Every completion looked alike, within-group variation vanished, and learning stopped.
The fix is the design in the previous section: un-gate the behavior you want, and penalize the failure mode explicitly. With both in place, distinct strategies keep producing distinct rewards within a group, which preserves the variance GRPO needs to learn.
Silently dead component
When a reward component returns the same value for every completion in a GRPO group, its within-group variance is zero. As a result, it contributes nothing to the advantage or the gradient, even at the highest weight. The components that still vary keep aggregate reward, policy loss, advantage, and completion length looking healthy, so the curves never reveal it. One common cause in code rewards is a correctness scorer t
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み