RLVR — Reinforcement Learning from Verifiable Rewards

v0.25.0 adds deterministic reward signals for GRPO training on math, code, and JSON-schema tasks. No reward model, no human labels.

Enable it

yaml
base: Qwen/Qwen3-8B
task: grpo

data:
  train: math_problems.jsonl
  format: chatml

training:
  reward_fn: verifiable
  verifiable_domain: math   # math | code | json_schema
  lr: 1e-5
  epochs: 1
  lora: { r: 16, alpha: 32 }

Built-in reward functions

DomainWhat it doesSandbox
mathReads the final answer of the completion and of the gold with one parser (see How the answer is read), compares a numeric gold by value with tolerance and any other gold as normalised textpure Python
codeExecutes Python against expected outputsbest-effort sandbox: subprocess, 5 s timeout, 10 KB output cap, 512 MB address-space limit on POSIX. Network isolation is not guaranteed (see Safety)
json_schemaValidates output against a JSON Schema and scores completenesspure Python

All three live in soup_cli/trainer/rewards.py and are routed through the existing GRPO trainer.

Generate training data

bash
soup data generate \
  --prompt "Math word problems, each with a single exact numeric answer" \
  --provider ollama --count 500 --output verifiable.jsonl

--prompt is required, and --template accepts only code, conversation, qa, preference and reasoning; there is no verifiable template and no flag that selects the verifiable helper (--domain exists, but it only picks the domain of the reasoning template). soup_cli/data/templates/verifiable.py builds problems with ground-truth answers you can verify at training time, but it is a library helper with no command-line selector.

As of v0.76.0, --template reasoning tells the generator how to end each solution so the gold can be read by these rewards: math rows end with a #### <number> line, logic and code rows with an Answer: <answer> line (#1349). Before that the prompt asked only for "the final answer", and a gold such as So there are 42 apples. made soup train refuse the dataset before generation.

How the answer is read

Since v0.76.0 (#1226). GRPO's accuracy reward and the math domain read the completion and the gold with the same parser, so a spelling understood on one side is understood on the other. Before this, only the completion was parsed, and a reference such as 6*7=42\n#### 42 was compared raw.

The answer is whichever of these the text states:

  1. The text after the last ####.
  2. The last \boxed{}. A space before the brace and nested braces are allowed.
  3. The text after The answer is or Answer: (also **Answer**:, and on the next line). The number is read from its own clause, so The answer is 41 apples, not 42. is 41, and The answer is 42 because 6*7=42. is 42.

A completion that states none of these is scored by its last number. A phrase that names more than one distinct value is a hedge and scores 0.0 whatever the gold (The answer is either 41 or 42.).

Both sides are normalised the same way: $ and \(...\) delimiters, trailing punctuation, thousands separators (1,000, 1{,}000), the Unicode minus, \text{} and similar wrappers, degree marks and the compact \frac12 spellings. A variable prefix is kept, so x = 3 does not match y = 3. A numeric gold is compared by value; any other gold, such as \frac{14}{3}, is compared as normalised text. Units are not stripped (42 apples does not match 42) and nothing is evaluated (\frac{1}{2} does not equal 0.5).

Scoring: accuracy returns 1.0 or 0.0, with no partial credit. math returns 1.0 within the tolerance, 0.6 for a near-miss (within max(100 x tolerance, 1e-2)) and 0.0 otherwise; its near-miss band is unchanged.

A gold that states no single final answer is refused before generation, with its split, row and field and a count of the other such rows: a multi-line reference with no marked answer, a hedge, a #### Solution heading over a worked solution, or x = with nothing after it. When some golds are not numbers, validation prints how many are compared as text, so a dataset that mixes numeric and LaTeX answers (MATH-500, for example) loads. PPO's accuracy and math rewards use the same scoring, but PPO's data is not validated this way.

Re-run this

If you trainedWhat was wrongWhat to do
GRPO with reward_fn: accuracy, or verifiable with verifiable_domain: math, on a dataset whose reference was an Alpaca output or a ShareGPT or ChatML turn such as 6*7=42\n#### 42The reference was compared raw, so every correct completion scored 0.0, every group had zero reward variance and GRPO got no signal, with no warning (#1226)Re-run. A run whose logged reward sat at 0.0 learned nothing from the reward
GRPO with reward_fn: accuracyIt paid 0.5 whenever the gold string appeared anywhere in the completion (#### 420 for a gold of 42) (#1226)Treat accuracy rewards from earlier versions as partly wrong credit; re-run if the task mattered
GRPO with accuracy or math on MATH-style golds written with \text{}, a degree mark, \frac12 or a variable prefix such as x = 7A correct completion in one spelling scored 0.0 against a gold in another (#1346)Re-run if the logged reward looked low for a model that was answering correctly

Safety

  • code_exec is a best-effort sandbox, not an isolation boundary, and Soup says so in a warning panel the first time it runs. Each completion runs in a short-lived subprocess started with python -I -S, with a 5 s timeout, a 10 KB output cap, a throwaway working directory and a reduced environment. On Linux and macOS it also gets resource limits (512 MB address space, CPU time, file size, open files, processes); on Windows it gets none of those. There is no restricted-builtins mechanism.
  • Network access is not blocked reliably. The guard is a Python-level socket patch, and generated code can bypass it through os.system, subprocess or ctypes. OS-level isolation is added only on Linux with Python 3.12 or newer (the os.unshare call, and only if the kernel allows unprivileged user namespaces; otherwise it falls back silently) and on macOS through sandbox-exec. Do not use verifiable_domain: code on a host that holds secrets or on untrusted data; run training in a container or VM with no network interface. Soup Cans v2 describes the same isolation tiers.
  • math_verify never uses eval() on model output. Answers are read by the shared answer parser described above and compared as numbers or as normalised text; nothing is evaluated.
  • verifiable_domain is a Pydantic Literal so arbitrary strings can't reach the dispatcher.

See also

  • Training methods — GRPO
  • Autopilot — --goal reasoning writes task: grpo and leaves reward_fn at its default of accuracy; set reward_fn: verifiable yourself to use RLVR

Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.