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
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
| Domain | What it does | Sandbox |
|---|---|---|
math | Reads 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 text | pure Python |
code | Executes Python against expected outputs | best-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_schema | Validates output against a JSON Schema and scores completeness | pure Python |
All three live in soup_cli/trainer/rewards.py and are routed through the existing GRPO trainer.
Generate training data
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:
- The text after the last
####. - The last
\boxed{}. A space before the brace and nested braces are allowed. - The text after
The answer isorAnswer:(also**Answer**:, and on the next line). The number is read from its own clause, soThe answer is 41 apples, not 42.is 41, andThe 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 trained | What was wrong | What 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#### 42 | The 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: accuracy | It 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 = 7 | A 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_execis 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 withpython -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,subprocessorctypes. OS-level isolation is added only on Linux with Python 3.12 or newer (theos.unsharecall, and only if the kernel allows unprivileged user namespaces; otherwise it falls back silently) and on macOS throughsandbox-exec. Do not useverifiable_domain: codeon 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_verifynever useseval()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_domainis a PydanticLiteralso arbitrary strings can't reach the dispatcher.
See also
- Training methods — GRPO
- Autopilot —
--goal reasoningwritestask: grpoand leavesreward_fnat its default ofaccuracy; setreward_fn: verifiableyourself 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.