Skip to main content
In group relative policy optimization (GRPO), the reward function is the only learning signal. For each prompt, the model generates several completions, your function scores each one, and the update favors completions that scored above their group’s mean. This page shows how to use the cookbook’s built-in datasets, register your own dataset and reward, and choose a reward shape. It also shows how to test a reward and check a baseline score before you spend GPU time on training.

Requirements

  • A cookbook checkout. To set one up, see the RL quickstart.
  • A dataset where each row has a prompt and a target to score completions against, either on Hugging Face or in a form you can load into the same shape.

Write a reward function

A reward function takes the model’s decoded completion and the row’s target, and returns a float:
A good reward function:
  • Returns values in [0, 1]: This keeps advantages on a predictable scale.
  • Varies across completions: A reward that scores most completions the same leaves GRPO nothing to learn from. A group where every completion scores the same has zero-variance advantages and is skipped. Before training, score a sample of the base model’s outputs and check that the scores spread.
  • Is deterministic: The same prediction and target always score the same.
  • Is fast: It runs at least batch_size * group_size times per step, and more often with dynamic sampling or validation, or when it doubles as the metric. The cookbook scores single-turn completions one at a time, so a reward that waits on an API call, a model, or a subprocess adds that wait to every step. To score with something slow, such as a judge model, write a one-turn environment instead, because the cookbook runs every episode’s async step concurrently.

Built-in datasets

The cookbook registers four datasets, each paired with a reward. To train on one, pass its name as dataset on GRPOConfig. Their entries in post_training_cookbook/datasets/registry.py also serve as working examples of the fields described in Register a dataset and reward.

Register a dataset and reward

The cookbook’s dataset registry maps each dataset name to how its rows become prompts, what its target is, and how its completions are scored. The steps below register a dataset of Python coding problems, where each row has a prompt and an expected_output.
1

Write the reward function

Add your reward function to post_training_cookbook/datasets/rewards.py. This example runs the generated code and compares its standard output with the expected output:
The cookbook scores every completion twice, once with compute_reward and once with compute_metric, which falls back to the reward when you don’t set one. The lru_cache decorator returns the cached score for the second call, so each completion’s code runs only once.
2

Register the dataset

Add a DatasetConfig entry to post_training_cookbook/datasets/registry.py:
To train on data that isn’t on Hugging Face, save it as a JSON Lines file named train.jsonl, one row per line, in a folder of its own. Then set hf_name to the folder’s path, such as "./my_problems", hf_config to None, and hf_split to "train". The cookbook loads the folder with the Hugging Face datasets library, which reads train.jsonl into the train split. A relative path resolves from the directory you run the recipe in.
3

Test the reward

Run the reward on hand-written completions before you train on it:
Then add the same cases to tests/datasets/test_rewards.py and run uv run pytest tests/datasets. Include one edge case per failure mode: an empty completion, a very long completion, output in an unexpected format, and, for this reward, a timeout.
4

Create and run the recipe

Create recipes/grpo/code_output.py:
A code solution takes more tokens than a math answer, so this recipe sets max_sample_tokens higher than the GSM8K recipe does, and temperature=0.8 gives the completions in each group more room to diverge. Run it with uv run python recipes/grpo/code_output.py.

Reward shapes

Binary: 1.0 for correct, 0.0 for wrong, like exact_match_reward above. Use it for tasks with a single checkable answer, such as math and factual questions. Tiered: Partial credit at several levels. Use it when partial progress deserves credit, such as in multi-step problems or in puzzles where getting the format right is a large part of the task.
Continuous: A distance or similarity score. Use it for tasks where approximate answers have value, such as translation or summarization.
Separate reward and metric: The reward you train on and the metric you report can differ. A smooth, tiered reward gives GRPO a gradient to follow, while a strict binary metric tells you how often the model is actually right:
compute_reward drives the advantages. compute_metric is what validation reports as val/accuracy.

Design guidance

  • Reward the format you want: If you want \boxed{} answers, give partial credit for the format even when the content is wrong. The model has to learn to emit the format before it can get the content right.
  • Penalize degenerate outputs: Return 0.0 for empty responses, repetition, or outputs that don’t attempt the task.
  • Guard against reward hacking: Watch val/accuracy alongside training reward. If training reward rises while validation accuracy falls, or validation response length creeps up, the model has found a way to score well on your reward without solving the task.
  • Sandbox code execution: A reward that executes model output, like the code example above, needs a timeout and should run isolated from your machine, for example in a container or a code sandbox.
  • Don’t rely on the main thread: Rewards can run off the main thread, for example when a sweep trains each arm in its own thread. Timeouts built on signal.alarm fail there, so use a subprocess timeout like the one in the code example.

Check a baseline before training

To see how a model performs on your task before you spend GPU time training it, run one of the cookbook’s recipes/serverless_eval/ scripts. Each script scores a hosted chat model on a dataset’s validation split, without a training session or model resource:
The scripts cover gsm8k, deepmath, game24, and the multi-turn tool-use environment. To score your own registered dataset, copy a script and change its dataset. Each run writes its episodes to runs/serverless_eval_<label>_<model>_<timestamp>/episodes.json. To score Qwen3.8 27B before training, deploy Qwen/Qwen3.8-27B-FP8 on dedicated model inference. The scripts take no arguments, so edit the script’s model to the endpoint string. The same flow works for a model you trained. For an adapter attached to a multi-LoRA deployment, use the adapter’s model string, such as your-project-slug/my-sweep:ml_abc123. Then run the script with TOGETHER_BASE_URL set to the dedicated inference host:
Stop the endpoint when you’re done, because it bills while it runs. The baseline isn’t directly comparable to training-time validation, because it applies no codec, token budget, or prompt-length filter. Watch the eval/length_capped metric in the output, which reports the fraction of episodes the endpoint cut off at max_tokens. If it’s high, the score measures truncation rather than ability, so raise max_tokens, especially for models with thinking on.

Multi-turn rewards

In a multi-turn environment, the reward comes from the environment’s step result instead of a (prediction, target) function. It’s assigned per episode, not per completion. For details, see Multi-turn environments.

Next steps

Reinforcement learning with GRPO

The settings that shape how your reward turns into updates.

Multi-turn environments

Reward whole episodes of tool use instead of single completions.

Monitoring

The metrics that show whether your reward is giving the model a signal.

RL quickstart

Run the GSM8K recipe your dataset will slot into.