Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
The last chapter trained against written constitutional principles. A critic can rank two answers using those rules, but two reviewers can still disagree, and a constitution doesn't execute.
Some targets don't need that kind of judge. Ask a model how many concurrent jobs fit on 14 GPU workers with 12 slots each. You don't need a preference label. You need 168.
Reinforcement Learning with Verifiable Rewards (RLVR) trains from rewards assigned by executable checks rather than a learned preference model.[1] Use it when an answer, program, schema, or constraint can be tested against a precise contract.
Two outputs show the boundary. One drafts an incident update, where tone and completeness are partly subjective. The other writes a Python function that either passes unit tests or fails them. Tülu 3 introduced the name RLVR for this post-training stage. DeepSeek-R1 used the same idea under the label rule-based rewards inside a broader multi-stage pipeline.[1][2]
Keep the 14×12 prompt nearby. A candidate that boxes 168 can earn reward. A candidate that boxes 148 can't. Whether the written reasoning was valid is a separate question, and that gap comes back whenever the verifier only sees the boxed number.
A verifier can fail in the opposite direction. If it awards a point for seeing \boxed{168} anywhere, a model can learn to print the token without solving the problem. Training reward rises; held-out correctness stays flat. The checker, not the prose around it, defines the pressure.

The alignment space
RLVR sits in the post-training stack next to SFT, RLHF, and DPO. Large language models usually learn instruction following from demonstrations and preference signals first. The design question is which of those signals the task can honestly provide.
Supervised Fine-Tuning (SFT) imitates selected demonstrations. For the GPU prompt, SFT can show a worked 14×12 solution. It copies patterns in those traces. It doesn't score a newly sampled 168 against an external checker.
Reinforcement Learning from Human Feedback (RLHF)[3] trains a reward model to predict preference labels. That fits tone or helpfulness, and it depends on label collection plus reward-model validity outside the labeled set.
Direct Preference Optimization (DPO)[4] removes the online reward-model RL loop and trains from chosen/rejected pairs. Those pairs can come from humans, constitutions, or other pipelines. They're still preference comparisons, not executed correctness checks.
RLVR specializes in tasks where success can be programmatically verified: a final math answer, code under tests, a JSON schema, or an instruction constraint such as "exactly three bullet points." You skip a preference label for each rollout. Validity is only as strong as the specification, parser, test suite, and sandbox that assign reward.
| Method | Reward signal | Scalability | Task scope |
|---|---|---|---|
| SFT | Demonstration data | Limited by demonstration supply | Any task with demonstrations |
| RLHF | Learned reward from preference labels | Requires labels and reward-model checks | Open-ended behavior goals |
| DPO | Chosen/rejected pairs | Avoids online reward-model RL | Tasks expressible as preferences |
| RLVR | Executable verification | Repeatable if the verifier is cheap | Outcomes covered by a verifier |
The trade is breadth for a directly executable signal. Start from what this checker can prove. A coding lab can verify unit tests, type checks, and exact-output constraints. It still needs a human for whether the solution is maintainable or clear.
That contract starts before sampling. A task generator needs checkable answers and a difficulty range that produces both passes and failures. An easy prompt gives every rollout the same reward; an impossible prompt gives every rollout zero. In either case, group-relative optimization has nothing to compare.

Defining verifiable rewards
In the simplest outcome-only setup, a verifiable reward function takes a problem and a generated response and returns a binary signal:
That's the teaching form. Tülu 3 used the same shape with a constant success reward chosen in pilots and then left fixed, plus a penalty when generation hit the length cap without an end-of-sequence token.[1] The scale is a hyperparameter. The contract isn't: only verifiably correct completions get the success reward.
A second tiny prompt makes the outcome-only blind spot obvious: "Is 97 a prime number? Format your answer as or ."
Why is a binary verifier attractive for RLVR?
Answer
It removes ambiguity from the reward signal. If code, tests, or a proof checker can mark outputs right or wrong, training doesn't need a human preference model for every sample.
- Iteration 1: "Yes, because it ends in 7." The final answer happens to be correct, so an outcome-only verifier returns 1.0, even though the stated reason is invalid.
- Iteration 2: "No, 97/3 = 32.3." The answer is wrong, so the verifier returns 0.0.
- Iteration 3: "97 is prime because it's not divisible by 2, 3, 5, or 7." The answer is correct and the reasoning is solid, so the verifier returns 1.0.
Iterations 1 and 3 get the same reward. An outcome verifier does not tell the optimizer which correct answer used valid reasoning. Across many problems, reasoning patterns that correlate with correct outcomes may become more likely, but that's an empirical training result, not information contained in a single final-answer reward.
This check verifies only the boxed verdict, so an unsupported guess and a valid divisibility check receive identical reward.
1import re
2
3def verdict_reward(response: str, expected: str) -> float:
4 matches = re.findall(r"\\boxed\{(Yes|No)\}", response)
5 return 1.0 if matches == [expected] else 0.0
6
7rollouts = {
8 "lucky_reason": r"It ends in 7, so it must be prime. \boxed{Yes}",
9 "valid_check": r"Check divisors at most sqrt(97): 2, 3, 5, 7 fail. \boxed{Yes}",
10 "wrong_answer": r"97 is divisible by 3. \boxed{No}",
11}
12
13for name, response in rollouts.items():
14 print(f"{name}: reward={verdict_reward(response, 'Yes')}")1lucky_reason: reward=1.0
2valid_check: reward=1.0
3wrong_answer: reward=0.0Many RLVR systems start with an all-or-nothing outcome signal because a deterministic checker can compute it repeatedly. Others add rule-based terms, so reward stays executable without being purely binary. Sparse credit assignment is the cost: if a response makes one late arithmetic error, a final-answer checker gives zero even if earlier steps were useful.
Checkable isn't complete: Verifiable means checkable, not complete. A unit-test verifier can prove whether one generated function matches the tested cases. It can't prove that a correct answer came from a general strategy unless the evaluation tests that generalization.

In practice, systems often mix correctness rewards with other rule-based terms. DeepSeek-R1-Zero combined accuracy rewards with format rewards so the thinking process sat in parseable tags before the final answer.[2]
Read verification as its own pipeline: extract, normalize, check, then record a reason for rejection. A rising zero-rate can mean harder prompts, parser drift, or genuine model regression. Without failure categories, those causes look identical in a training curve.
Outcome vs. process supervision
The simplest form is outcome supervision: score the final answer. An RLVR setup can do this with a rule-based checker. Related work has also trained learned verifiers to score final answers.[5] Feedback is sparse.
Process supervision scores intermediate steps instead of only the final result. Lightman et al. compare outcome and process reward models trained with human labels on mathematical reasoning steps. That's adjacent to RLVR, not itself proof that each process score is programmatically verifiable.[6] A formal system can instead run a checker after each proof step. Either version offers more localized credit, but it needs a trustworthy step-level signal.

Caption: Outcome supervision scores the final answer. Process supervision supplies step-level feedback, either from labels or from a checker when one exists, at higher construction cost.
Examples of verifiers
Mathematics
Check whether the extracted final answer matches ground truth, even if the reasoning path is different. The verifier below requires exactly one boxed field and compares values with fractions.Fraction, so and match while missing or duplicated boxes fail closed.
This is small enough to run locally. Production math verifiers add more normalization, a computer-algebra fallback, timeouts, and adversarial parser tests. Don't treat Fraction as a full symbolic simplifier.
1from fractions import Fraction
2
3def extract_boxed_exprs(text: str) -> list[str]:
4 marker = "\\boxed{"
5 expressions: list[str] = []
6 offset = 0
7 while (start := text.find(marker, offset)) != -1:
8 depth = 0
9 expr_chars: list[str] = []
10 for ch in text[start + len(marker):]:
11 if ch == "{":
12 depth += 1
13 expr_chars.append(ch)
14 elif ch == "}":
15 if depth == 0:
16 expressions.append("".join(expr_chars).strip())
17 offset = start + len(marker) + len(expr_chars) + 1
18 break
19 depth -= 1
20 expr_chars.append(ch)
21 else:
22 expr_chars.append(ch)
23 else:
24 return []
25 return expressions
26
27def math_verifier(problem: str, answer: str, ground_truth: str) -> float:
28 predicted_fields = extract_boxed_exprs(answer)
29 expected_fields = extract_boxed_exprs(ground_truth)
30 if len(predicted_fields) != 1 or len(expected_fields) != 1:
31 return 0.0
32 try:
33 predicted = Fraction(predicted_fields[0].replace(" ", ""))
34 expected = Fraction(expected_fields[0].replace(" ", ""))
35 except (ValueError, ZeroDivisionError):
36 return 0.0
37 return 1.0 if predicted == expected else 0.0
38
39equivalent_score = math_verifier("Compute half of one.", r"The answer is \boxed{0.5}.", r"\boxed{1/2}")
40wrong_score = math_verifier("Compute 14 times 12.", r"\boxed{148}", r"\boxed{168}")
41ambiguous_score = math_verifier("Compute 14 times 12.", r"Maybe \boxed{168} or \boxed{148}.", r"\boxed{168}")
42print(equivalent_score)
43print(wrong_score)
44print(ambiguous_score)
45print(f"equivalent_passes={equivalent_score == 1.0}")
46print(f"wrong_answer_rejected={wrong_score == 0.0}")
47print(f"ambiguous_answer_rejected={ambiguous_score == 0.0}")11.0
20.0
30.0
4equivalent_passes=True
5wrong_answer_rejected=True
6ambiguous_answer_rejected=TrueCode generation
Running generated code against a strong suite of hidden tests checks concrete behavior. It doesn't prove full correctness unless the suite is complete, but it's stronger than judging code by surface form. The local example runs candidate code in a separate Python process and times out slow attempts. It isn't a security sandbox. Production systems still need containers, seccomp, filesystem isolation, network controls, and resource limits.
1import json
2import subprocess
3import sys
4from dataclasses import dataclass
5
6@dataclass
7class TestCase:
8 inputs: tuple[int, ...]
9 expected: int
10
11def code_verifier(problem: str, code: str, test_cases: list[TestCase]) -> float:
12 cases = [(case.inputs, case.expected) for case in test_cases]
13 runner = (
14 code
15 + "\nimport json\n"
16 + f"cases = {cases!r}\n"
17 + "passed = all(solve(*inputs) == expected for inputs, expected in cases)\n"
18 + "print(json.dumps({'passed': passed}))\n"
19 )
20 try:
21 completed = subprocess.run(
22 [sys.executable, "-I", "-c", runner],
23 capture_output=True,
24 text=True,
25 timeout=1.0,
26 check=False,
27 )
28 except subprocess.TimeoutExpired:
29 return 0.0
30 if completed.returncode != 0:
31 return 0.0
32 try:
33 result = json.loads(completed.stdout)
34 except json.JSONDecodeError:
35 return 0.0
36 return 1.0 if result == {"passed": True} else 0.0
37
38cases = [TestCase((14, 12), 168), TestCase((8, 12), 96)]
39good = "def solve(a, b):\n return a * b\n"
40bad = "def solve(a, b):\n return a + b\n"
41
42good_score = code_verifier("multiply two integers", good, cases)
43bad_score = code_verifier("multiply two integers", bad, cases)
44print(good_score)
45print(bad_score)
46print(f"good_passes={good_score == 1.0}")
47print(f"bad_rejected={bad_score == 0.0}")11.0
20.0
3good_passes=True
4bad_rejected=TrueVisible tests aren't enough when the policy can memorize examples. Here a candidate passes the two development cases and fails a held-out pair. A reward based only on visible tests would reinforce the wrong program.
1from collections.abc import Callable
2
3def shortcut_multiplier(a: int, b: int) -> int:
4 known = {(14, 12): 168, (8, 12): 96}
5 return known.get((a, b), 0)
6
7def pass_rate(fn: Callable[[int, int], int], cases: list[tuple[int, int, int]]) -> float:
8 passed = sum(fn(a, b) == expected for a, b, expected in cases)
9 return passed / len(cases)
10
11visible = [(14, 12, 168), (8, 12, 96)]
12held_out = [(7, 9, 63), (11, 13, 143)]
13
14print(f"visible_pass_rate={pass_rate(shortcut_multiplier, visible):.0%}")
15print(f"held_out_pass_rate={pass_rate(shortcut_multiplier, held_out):.0%}")
16print(f"ship={pass_rate(shortcut_multiplier, held_out) == 1.0}")1visible_pass_rate=100%
2held_out_pass_rate=0%
3ship=FalseInstruction constraints
Not every verifiable task is math or code. Tülu 3 also trained RLVR on precise instruction-following prompts whose constraints a program can check, such as required bullet counts or word limits in IFEval-style tasks.[1] The reward is still fail closed: if the constraint checker can't confirm the contract, the score is 0.
1def constraint_reward(response: str) -> float:
2 bullets = [line for line in response.splitlines() if line.startswith("- ")]
3 return 1.0 if len(bullets) == 3 else 0.0
4
5valid = "- name the GPU\n- name the slot count\n- name the job total"
6too_short = "- name the GPU\n- name the slot count"
7print(constraint_reward(valid))
8print(constraint_reward(too_short))
9print(f"three_bullets_pass={constraint_reward(valid) == 1.0}")
10print(f"two_bullets_rejected={constraint_reward(too_short) == 0.0}")11.0
20.0
3three_bullets_pass=True
4two_bullets_rejected=TrueFormal logic
Proof assistants such as Lean or Isabelle can check that a candidate proof term satisfies a formal theorem under their kernel and imported definitions. In production, a verifier wraps the theorem statement and candidate proof into a script, then returns 1.0 only if the prover accepts the full artifact.
The prover call is infrastructure-specific: Lean version, package cache, sandbox, timeout. Keep the reward conversion boring and testable, and keep the theorem-prover runner behind a clear boundary.
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class ProverResult:
5 accepted: bool
6 stderr: str
7
8def proof_reward(result: ProverResult) -> float:
9 return 1.0 if result.accepted else 0.0
10
11accepted = proof_reward(ProverResult(accepted=True, stderr=""))
12rejected = proof_reward(ProverResult(accepted=False, stderr="unknown identifier"))
13print(accepted)
14print(rejected)
15print(f"accepted_maps_to_one={accepted == 1.0}")
16print(f"rejected_maps_to_zero={rejected == 0.0}")11.0
20.0
3accepted_maps_to_one=True
4rejected_maps_to_zero=TrueWhat can't be verified?
RLVR is effective but narrow. It works best when the task has an objective spec and a verifier that fails closed. That rules out many open-ended tasks, or at least forces you to break them into smaller verifiable sub-problems. It's not a clean fit for:
- Open-ended writing: "Write three status-page headlines for an outage" has no objective truth.
- Subjective analysis: "Explain the trust impact of a release delay" has many valid answers.
- Safety alignment: deciding if a response is harmful usually requires policy judgment or an AI proxy, which is closer to RLHF or RLAIF than a deterministic correctness check.
Why must an RLVR verifier fail closed?
Answer
If parsing errors, timeouts, or malformed outputs accidentally receive reward, training will amplify those loopholes. A failed verifier should return 0 or no reward, not a best-effort pass.
Group relative policy optimization (GRPO)
DeepSeek-R1 used Group Relative Policy Optimization (GRPO), an algorithm introduced in DeepSeekMath that avoids a learned value model by estimating advantages from a group of samples for the same prompt.[7] DeepSeek-R1 later used GRPO in its reasoning-training stages.[2] GRPO is an optimizer, not a synonym for RLVR. Tülu 3 trained its RLVR stage with PPO.[1]
The problem with PPO
In standard PPO[8], a critic (value function) predicts how good a state is. The critic estimates expected future reward .
In the terminal-reward setting common to LLM training, where reward is observed only at the end of a sequence, the advantage often simplifies to:
is the final reward and is the critic's value estimate. More generally, the advantage uses a temporal-difference error , often computed via Generalized Advantage Estimation over the full sequence.
Training a critic alongside an LLM adds model memory and compute. With terminal rewards, it must also predict eventual success from partial generations, which gets hard when a long response can later revise an earlier mistake.[2]
The GRPO solution
GRPO drops the separate critic. Instead of learning a value function that tries to predict how good any reasoning trajectory is, it computes advantages relative to the other samples generated for the exact same prompt in the current batch.
For the 14×12 prompt, sample several outputs. Some reach 168 and receive high reward. Others miss it. The successful outputs are better than average for this sampled group. You don't need a learned critic that estimates difficulty across prompts. You do need reward variation inside each group.
For a prompt you sample a group of outputs . The advantage for output is the z-score of its reward within that group:
Positive advantages push the policy toward those trajectories. Negative advantages push it away. Local normalization compares attempts at the same prompt difficulty. It doesn't guarantee stable training: if every sampled output gets the same reward, the group contributes no comparative signal.
Handle that boundary before dividing. For rewards [1, 1, 1, 1] or [0, 0, 0, 0], the group standard deviation is zero, so the literal formula becomes 0 / 0; production implementations must mask the group, use a guarded denominator, or apply another explicitly defined normalization policy.
What does a positive GRPO advantage mean?
Answer
That sample scored above the group average for the same prompt. The optimizer should increase probability of the tokens in that trajectory relative to worse samples.
This concrete example uses samples for the GPU prompt. The verifier returns 1.0 for boxed 168 and 0.0 otherwise:
| Sample | Answer | Reward | Group mean | Group std | Advantage |
|---|---|---|---|---|---|
| 1 | 168 | 1.0 | 0.5 | 0.5 | +1.0 |
| 2 | 148 | 0.0 | 0.5 | 0.5 | -1.0 |
| 3 | 156 | 0.0 | 0.5 | 0.5 | -1.0 |
| 4 | 168 | 1.0 | 0.5 | 0.5 | +1.0 |
| 5 | 170 | 0.0 | 0.5 | 0.5 | -1.0 |
| 6 | 168 | 1.0 | 0.5 | 0.5 | +1.0 |
| 7 | 144 | 0.0 | 0.5 | 0.5 | -1.0 |
| 8 | 168 | 1.0 | 0.5 | 0.5 | +1.0 |
Four samples boxed 168, so their rewards are 1.0. Four missed, so their rewards are 0.0. The group mean is , and the population standard deviation (divide by , not ) is 0.5. Sample 1's advantage is , so the optimizer increases the probability of the tokens that led there. Sample 2's advantage is , so those tokens get pushed down.
Every correct answer gets the same positive advantage, and every wrong answer gets the same negative advantage. GRPO doesn't know why sample 1 was correct. It only knows that sample 1 beat the average for this prompt. Over many prompts, the policy learns which reasoning patterns reliably produce above-average rewards.

The update resembles a PPO-clipped objective with group-relative advantage and a KL penalty. DeepSeek-R1 writes a sequence-level sketch.[2] DeepSeekMath applies the clipped surrogate per generated token, averages inside each sequence, then averages across the group.[7] Keep the sequence-level form for intuition, then treat token-level ratios as the implementation contract:
Here is a sequence-level policy-ratio shorthand, is the normalized group advantage, is the clipping threshold, and controls the drift penalty toward the reference policy.
Implementation contract: during rollout, store old-policy token log-probs. At train time, recompute new-policy token log-probs, form per-token ratios, share the same group advantage across tokens of that sample under outcome supervision, and mask padding. DAPO later argued that averaging loss first by sequence (sample-level) underweights long traces and proposed token-level loss, plus a higher policy-ratio clip on the upper bound to slow entropy collapse.[9] Reimplementing GRPO from the sequence-level sketch alone will mis-state gradient scale and clipping.
The Kullback-Leibler (KL) term penalizes drift from a reference policy. It can constrain movement toward a verifier exploit. It can't prove that the verifier represents the intended behavior.
If every sample in the group gets the same reward, the standard deviation is 0 and vanilla GRPO gets no learning signal from that group. That's why prompt difficulty, group size, and reward shaping matter. DeepSeekMath reported for its math RL run.[7] DeepSeek-R1's Figure 2 samples 16 responses per AIME question to stabilize evaluation averages. That isn't a published training group size .[2] DAPO later used 16 training rollouts per prompt and dynamic sampling: drop all-pass and all-fail groups and keep sampling until the batch has mixed outcomes.[9]
Common mistake: Saying that three equal rewards out of four collapse the GRPO signal. They don't: the one different outcome creates reward variance. Signal disappears when every sampled output gets the same reward, such as all failures on a prompt that's too hard or all passes on a prompt that's too easy.
This diagnostic identifies which prompt groups supply comparative signal before an update. The mixed group is usable even though three out of four answers fail. The all-fail and all-pass groups carry no relative ranking information.
1def population_std(values: list[float]) -> float:
2 mean = sum(values) / len(values)
3 variance = sum((value - mean) ** 2 for value in values) / len(values)
4 return variance ** 0.5
5
6groups = {
7 "mixed": [0.0, 0.0, 0.0, 1.0],
8 "all_fail": [0.0, 0.0, 0.0, 0.0],
9 "all_pass": [1.0, 1.0, 1.0, 1.0],
10}
11
12for name, rewards in groups.items():
13 std = population_std(rewards)
14 informative = std > 0.0
15 print(f"{name}: std={std:.3f}, informative={informative}")1mixed: std=0.433, informative=True
2all_fail: std=0.000, informative=False
3all_pass: std=0.000, informative=FalseThe next snippet is a scalar sketch of the GRPO math: each sampled trajectory is one log-probability, which is enough to test group-normalized advantage, PPO-style clipping, and the KL estimator published with GRPO, .[7] A production trainer applies the objective at token level and stores old-policy log probabilities during rollout. Use population std (divide by ). PyTorch's default std divides by and will shrink on small groups unless you set it to unbiased/population mode.
Before the optimizer step, predict its sign. A positive advantage should increase a sampled trajectory's log-probability; a negative advantage should decrease it; an all-equal group should do neither. The scalar lab checks those three cases.
At initialization every ratio is 1, the clip is inactive, and the KL term is 0, so one SGD step on the mean clipped surrogate moves each log-probability by .
1import math
2
3def group_advantages(rewards: list[float]) -> list[float]:
4 mean = sum(rewards) / len(rewards)
5 std = (sum((reward - mean) ** 2 for reward in rewards) / len(rewards)) ** 0.5
6 if std == 0.0:
7 return [0.0] * len(rewards)
8 return [(reward - mean) / std for reward in rewards]
9
10def clipped_surrogate(ratio: float, advantage: float, epsilon: float = 0.2) -> float:
11 clipped = min(max(ratio, 1.0 - epsilon), 1.0 + epsilon)
12 return min(ratio * advantage, clipped * advantage)
13
14def kl_unbiased(log_pi: float, log_ref: float) -> float:
15 log_ratio_ref = log_ref - log_pi
16 return math.exp(log_ratio_ref) - log_ratio_ref - 1.0
17
18rewards = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]
19advantages = group_advantages(rewards)
20advantages_match = advantages == [1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0]
21
22log_probs = [0.0] * 8
23old_log_probs = [0.0] * 8
24reference_log_probs = [0.0] * 8
25epsilon = 0.2
26beta = 0.01
27lr = 0.1
28group_size = len(rewards)
29
30policy_term = -sum(
31 clipped_surrogate(math.exp(new - old), advantage, epsilon)
32 for new, old, advantage in zip(log_probs, old_log_probs, advantages, strict=True)
33) / group_size
34kl_term = sum(
35 kl_unbiased(new, ref)
36 for new, ref in zip(log_probs, reference_log_probs, strict=True)
37) / group_size
38loss = policy_term + beta * kl_term
39
40updated = [log_prob + lr * advantage / group_size for log_prob, advantage in zip(log_probs, advantages, strict=True)]
41step_direction_ok = updated[0] > 0 and updated[1] < 0
42
43print("advantages:", [round(value, 1) for value in advantages])
44print("updated log probs:", [round(value, 3) for value in updated])
45print(f"loss={loss:.6f}")
46print(f"advantages_match={advantages_match}")
47print(f"step_direction_ok={step_direction_ok}")1advantages: [1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0]
2updated log probs: [0.013, -0.013, -0.013, 0.013, -0.013, 0.013, -0.013, 0.013]
3loss=0.000000
4advantages_match=True
5step_direction_ok=TrueThe batch loss is 0 at this initialization because the four and four advantages cancel in the mean. The update still moves: each trajectory's log-probability changes by . GRPO is an optimizer, not a synonym for RLVR. Tülu 3 trained its RLVR stage with PPO. DeepSeekMath introduced GRPO as a PPO variant, and DeepSeek-R1 used GRPO in its reasoning stages.[1][7][2] Both optimizers can consume rule-based verifier rewards.
| Feature | PPO-style policy optimization | GRPO |
|---|---|---|
| Baseline | Predicted by a separate critic model | Mean reward of the sampled group |
| Additional value-model cost | Trains a critic/value model | Avoids a separate critic |
| Main estimation risk | Critic must predict returns | Groups with equal rewards give no relative signal |
| Compatible reward signals | Learned or rule-based reward | Learned or rule-based reward, including binary checks |
Self-check: In the worked table above, what would happen to the advantages if all 8 samples got the same reward? Why does that make prompt difficulty and group size important in practice?
GRPO often fits RLVR for three engineering reasons. There's no critic to hold in GPU memory. The group mean is local to the current prompt, so you skip training a value head to predict returns from partial reasoning traces. Binary rewards are easy to read: when a group contains both passes and failures, passes get positive advantage and failures negative. If all candidates pass or all fail, advantages are zero, so prompt selection and exploration still have to produce mixed outcomes often enough to learn.
DeepSeek-R1 training pipeline
RLVR success depends on the pipeline as well as the algorithm. DeepSeek-R1 used a multi-stage process that alternates supervised fine-tuning and reinforcement learning to improve checked reasoning, readability, and broader assistant behavior.[2]
On AIME 2024, DeepSeek-R1-Zero's reported pass@1 rose from 15.6% to 77.9% during rule-based RL from DeepSeek-V3-Base. Figure 1 also reports 86.7% with consistency over 16 samples.[2] Those numbers are the empirical stake for the pipeline below. They aren't a promise that every base model and verifier will move the same way.

Caption: DeepSeek-R1's reported pipeline starts with readable cold-start behavior, then applies GRPO with rule-based rewards on reasoning tasks, including a language-consistency reward. Later stages collect filtered traces into SFT data, mix in broader instruction data, and combine reasoning rewards with general-behavior reward models.
Stage 1: cold start (not mandatory, often useful)
DeepSeek-R1-Zero showed that DeepSeek-V3-Base could improve on reported reasoning evaluations through rule-based RL without a preliminary SFT phase.[2] That result doesn't mean every base model or verifier will train usefully from zero-reward-heavy rollouts.
DeepSeek still added cold-start data for DeepSeek-R1 because R1-Zero had poor readability and mixed languages. The paper describes collecting thousands of long-CoT examples, filtering for a readable response pattern, and using them to initialize the model before RL.[2] In other projects, cold start also helps when the base model's initial pass rate is so low that almost every rollout gets zero reward.
Stage 2: reasoning RL (GRPO)
This stage trains on math, code, and logic prompts using GRPO with rule-based rewards. In DeepSeek-R1-Zero, those rewards were accuracy plus format. In DeepSeek-R1, the first RL stage also added a language-consistency reward (the share of target-language words in the chain of thought) to reduce mixed-language traces, with the paper reporting a slight performance trade-off in its ablation.[2]
For rule-graded prompts, a response that fails the checked outcome doesn't receive the accuracy reward no matter how persuasive its text is. That still leaves coverage risk: a pass can reflect a shortcut if the verifier or evaluation set fails to test it.
Stage 3: rejection sampling and SFT
Once the RL policy is producing higher-scoring traces, they can become new demonstrations. DeepSeek-R1 used rejection sampling, retaining checked-correct responses for rule-gradable data and using DeepSeek-V3 judgments for some expanded reasoning data.[2]
That stage produced about 600k reasoning samples plus about 200k non-reasoning samples, for roughly 800k total.[2] This turns exploratory RL behavior into a stable supervised dataset and helps recover capabilities that pure reasoning RL doesn't optimize for, such as writing quality and everyday instruction following.
Stage 4: final RL
Pure reasoning RL can produce a model that's strong on benchmarks but rough around the edges. In the final stage, DeepSeek runs RL again, this time on a broader mix of scenarios.[2]
This uses a mix of:
- Rule-based rewards to maintain strong reasoning capabilities.
- Reward models (RLHF) for helpfulness and harmlessness in general conversation.
This final stage is intended to combine reasoning performance with broader helpfulness and harmlessness objectives. Those outcomes still have to be measured rather than assumed.
Observed reasoning patterns
Outcome-checked RL doesn't label each reasoning step. In DeepSeek-R1-Zero, the authors report longer responses and visible patterns such as verification, reflection, and exploration of alternatives during training.[2] Treat those as observed output behaviors, not proof of an internal reasoning mechanism.
Self-verification patterns
An output may pause and check its work.
"Wait, let me double-check that calculation. is , not . I made a mistake."
DeepSeek-R1-Zero wasn't trained from step-level labels saying "write let me check here." Its paper reports a sharp increase in the use of "wait" during reflection later in training.[2] The cautious interpretation is that RL changed the distribution of generated traces and that some reflective-looking traces co-occurred with improved checked outcomes. An outcome-only verifier doesn't establish why those traces improved.
Backtracking patterns
An output may abandon one approach and try another.
"Counting slots as seems wrong. Let me multiply instead."
This can look search-like on the surface, but the training loop is still autoregressive generation plus reward-weighted updates, not an explicit tree-search controller. A verifier credits the final checked outcome. It doesn't separately establish that the pivot was necessary.
Extended thought
In DeepSeek-R1-Zero, average response length jumped sharply during training alongside reported accuracy gains.[2] This connects RLVR to test-time compute because trained policies may emit longer traces on reasoning tasks. Longer traces aren't automatically better. Their value has to be measured against correctness, latency, and token cost.
A useful self-check: suppose a model trained with outcome rewards starts saying "Wait, let me double-check that" before finalizing answers. What can you conclude from the output pattern, and what would require separate evaluation? The reward confirms correct final answers, not the necessity or faithfulness of the written reflection.
Research caution: It remains open how much RLVR creates new problem-solving behavior versus eliciting behavior already likely under the base model. Shao et al. report that on Qwen2.5-Math-7B, MATH-500 rose 21.4 points with random rewards and 13.8 points with format-only rewards, versus 29.1 points with ground-truth rewards. Comparable spurious rewards gave little benefit or harmed Llama3 and OLMo2 variants.[10] They connect this result to GRPO clipping and model-specific high-prior behaviors. Practical takeaway: compare against weak or spurious-reward baselines and validate across model families and held-out tasks. Track pass@1 and pass@k. A rising training reward with flat held-out pass@k is usually hacking, memorization, or probability mass shifting among traces the base model could already sample.
Reward hacking and failure modes
Any time you optimize a metric, a policy can exploit gaps in that metric. RLVR is no exception. If the verifier checks only final answers, a memorized answer, leaked target, or weak parser can receive reward without demonstrating general solution ability. DeepSeek-R1 explicitly identifies reliable reward construction as a limitation once tasks can't be graded by dependable rules.[2]
Don't train against a verifier before adversarially testing it. Any malformed output or shortcut that earns reward can be reinforced by the optimization loop.
Format gaming
A format-heavy reward can favor output wrappers over correctness. If the verifier gives substantial credit for \boxed{...} before checking the value, it can reinforce neatly formatted wrong answers. A parser that accepts any matching line also risks rewarding output that contains several contradictory answers.
High format compliance with low task accuracy means the reward function prizes formatting before correctness. Make correctness dominant and fail closed on wrong or ambiguous contents, even if the wrapper is present.
This example compares a broken format-first reward with a correctness-gated version. A wrong boxed 148 should never beat an unboxed 168 just because it's easy to parse.
1import re
2
3def boxed_value(response: str) -> int | None:
4 matches = re.findall(r"\\boxed\{(\d+)\}", response)
5 return int(matches[0]) if len(matches) == 1 else None
6
7def broken_reward(response: str, expected: int) -> float:
8 value = boxed_value(response)
9 return 0.7 if value is not None else (0.3 if str(expected) in response else 0.0)
10
11def gated_reward(response: str, expected: int) -> float:
12 value = boxed_value(response)
13 return 1.0 if value == expected else 0.0
14
15wrong_boxed = r"The answer is \boxed{148}."
16right_plain = "The answer is 168."
17
18print(f"broken_prefers_wrong={broken_reward(wrong_boxed, 168) > broken_reward(right_plain, 168)}")
19print(f"gated_wrong_boxed={gated_reward(wrong_boxed, 168)}")1broken_prefers_wrong=True
2gated_wrong_boxed=0.0Shortcut exploitation
If a training set has shortcuts (for example, one multiple-choice position is correct much more often), optimizing checked training reward can favor that shortcut. A code verifier that treats execution errors as passing outcomes creates an even more direct loophole.
Mitigation strategies
To prevent policies from exploiting the verifier, several defensive practices help during training:
- Explicit verifier contracts: use isolated execution and fail closed on any parsing error. Symbolic checking belongs here when the answer isn't a plain integer.
- Difficulty calibration: include prompts where rollouts produce both passing and failing outputs often enough for group-relative learning. All-zero groups contribute no comparative advantage. DAPO-style dynamic sampling filters those groups out of the update batch.[9]
- Held-out tests: evaluate on independent problems and, when possible, an independently implemented checker so a parser shortcut isn't mistaken for generalization.
- Length controls: cap rollout length or add carefully tuned penalties so the model doesn't learn to think forever without improving correctness. DAPO adds a soft overlong punishment near the generation cap rather than a hard truncate-and-fail that can punish otherwise valid traces.[9]

When RLVR breaks
Set an evidence gate before training. Pause if training reward rises while held-out pass@k, format validity, or broad instruction metrics fall. Inspect verifier coverage, leakage, and reward components before adding steps or widening the task mix.
Forcing a checker onto a subjective task
A reward function that secretly depends on taste, safety judgment, or fuzzy grader text is forcing an objective RL method onto a subjective task. Ask whether a deterministic checker, theorem prover, unit test suite, database constraint, or schema validator can grade the output. If not, use RLHF, DPO, Reinforcement Learning from AI Feedback (RLAIF), or decompose the task into smaller verifiable checks.
Treating cold-start SFT as always required or never useful
DeepSeek-R1-Zero improved reported reasoning-task results from DeepSeek-V3-Base without a preliminary SFT phase.[2] DeepSeek-R1 still used cold-start SFT because readability and language consistency mattered. Measure the base model's initial pass rate and output quality. If almost every rollout gets zero reward or the traces are unreadable, cold-start data may make RL more tractable.
Celebrating format accuracy
A verifier that rewards \boxed{} too strongly can teach box-writing instead of problem-solving. Keep correctness dominant, run adversarial parser tests, and track real task accuracy separately from format compliance.
Ignoring general capability drift
A model trained heavily on math or code RLVR can become better at those tasks while getting worse at normal instruction following. Use KL anchoring, mix in general instruction data during later SFT stages, and run broad evaluations such as writing, safety, and everyday chat alongside math or code metrics.
This release gate catches a reasoning gain that comes with an unacceptable instruction-following regression. The numbers are illustrative. Each team should define thresholds before training.
1baseline = {"checked_reasoning": 0.61, "instruction_following": 0.92, "false_refusal": 0.04}
2candidate = {"checked_reasoning": 0.74, "instruction_following": 0.81, "false_refusal": 0.13}
3
4violations = []
5if candidate["checked_reasoning"] <= baseline["checked_reasoning"]:
6 violations.append("no reasoning gain")
7if candidate["instruction_following"] < baseline["instruction_following"] - 0.03:
8 violations.append("instruction following regressed")
9if candidate["false_refusal"] > baseline["false_refusal"] + 0.02:
10 violations.append("false refusals increased")
11
12print(f"reasoning_gain={candidate['checked_reasoning'] - baseline['checked_reasoning']:+.0%}")
13print(f"ship={not violations}")
14print(violations)1reasoning_gain=+13%
2ship=False
3['instruction following regressed', 'false refusals increased']Treating RLVR and distillation as rivals
RLVR can improve checked outcomes for a teacher. Distillation transfers sampled teacher behavior into a smaller model. Decide which bottleneck you have: if the teacher fails verifiable evaluations, train against better checks; if a capable teacher is too expensive to serve, consider distillation.
A tiny verifier lab
To understand RLVR, build a verifier yourself. You don't need a GPU cluster or a billion-parameter model. A Python script and a small set of arithmetic problems are enough to see the mechanics.
Treat a verifier edit like a model edit. Freeze held-out prompts, replay malformed and adversarial outputs, compare failure categories, and resume RL only after checker behavior is stable.
Checkpoint: Before building the verifier, state the exact contract: require one answer field, extract one number, compare with tolerance, and fail closed on missing, duplicated, or malformed fields. If that contract feels vague, re-read the math verifier example above.
Exercise: write a verifier that takes a model's raw text output and returns 1.0 if the answer is correct and 0.0 otherwise. Use the following prompt and ground-truth pairs:
| Prompt | Ground truth |
|---|---|
| "A GPU batch runner has 14 workers with 12 slots each. How many concurrent jobs fit?" | 168 |
| "A scheduler runs 8 jobs per wave. How many waves are needed for 96 jobs?" | 12 |
| "A request budget is 47.50 GPU-seconds with a 6% overhead. What's the total?" | 50.35 |
Step 1: Implement extract_answer(text: str) -> str | None that accepts exactly one \boxed{...} answer field. Reject missing or duplicated fields rather than guessing which number the model intended as final.
Step 2: Implement verify(prompt: str, response: str, ground_truth: float) -> float that extracts the predicted answer, compares it to the ground truth with a small tolerance (e.g., abs(predicted - expected) < 1e-3), and returns 1.0 or 0.0.
Step 3: Test your verifier against these three model outputs for the first prompt:
- "There are 14 workers and 12 slots each. 14 × 12 = 168. The answer is ."
- "The total is ."
- "It might be or ."
Expected results: output 1 should return 1.0. Outputs 2 and 3 should return 0.0. The third response contains the correct value, but its final answer is ambiguous.
Start from this minimal solution:
1import re
2
3def extract_answer(text: str) -> str | None:
4 boxed = re.findall(r"\\boxed\{(-?\d+(?:\.\d+)?)\}", text)
5 return boxed[0] if len(boxed) == 1 else None
6
7def verify(prompt: str, response: str, ground_truth: float) -> float:
8 answer = extract_answer(response)
9 if answer is None:
10 return 0.0
11 try:
12 predicted = float(answer)
13 except ValueError:
14 return 0.0
15 return 1.0 if abs(predicted - ground_truth) < 1e-3 else 0.0
16
17prompt = "A GPU batch runner has 14 workers with 12 slots each. How many concurrent jobs fit?"
18correct_score = verify(prompt, r"There are 14 workers and 12 slots each. 14 * 12 = 168. The answer is \boxed{168}.", 168)
19wrong_score = verify(prompt, r"The total is \boxed{148}.", 168)
20ambiguous_score = verify(prompt, r"It might be \boxed{168} or \boxed{148}.", 168)
21
22print(correct_score)
23print(wrong_score)
24print(ambiguous_score)
25print(f"correct_passes={correct_score == 1.0}")
26print(f"wrong_rejected={wrong_score == 0.0}")
27print(f"ambiguous_rejected={ambiguous_score == 0.0}")11.0
20.0
30.0
4correct_passes=True
5wrong_rejected=True
6ambiguous_rejected=TrueStep 4 (optional): add a format reward. Give +0.2 for using \boxed{} correctly, and +0.8 for a correct answer inside the box. What behavior does this mixed reward encourage? Does the model still get a positive reward if the box is present but the answer is wrong? For a strict production verifier, decide whether that partial credit is worth the gaming risk or whether correctness should gate every positive reward.
DeepSeek-R1-Zero combined accuracy rewards with format rewards targeting a parseable response structure.[2] Mixed rewards require careful testing so formatting doesn't dominate correctness.
RLVR and distillation
RLVR and distillation aren't mutually exclusive. DeepSeek-R1 used RL in its teacher pipeline, then fine-tuned smaller dense models on the same 800k generated training samples.[2] The systems distinction is clean: RLVR optimizes a policy against checks, while distillation trains a student from teacher outputs.
| Aspect | RLVR | Distillation |
|---|---|---|
| Training signal | Online reward from a verifier | Offline supervision from teacher outputs |
| What it optimizes | Checked success under a specified contract | Imitation of teacher behavior |
| Compute profile | Online sampling, verification, and RL updates | Supervised training over collected traces |
| Dependency | Needs a reliable verifier | Needs a useful teacher and clean trace data |
| Best use | Improve checked outcomes for selected tasks | Transfer teacher behavior into smaller models |
| Main failure mode | Reward hacking or sparse-credit collapse | Student inherits teacher blind spots and data coverage limits |
DeepSeek-R1 makes this trade-off concrete. Distilling into Qwen2.5-32B outperformed a large-scale RL run on Qwen-32B-Base (DeepSeek-R1-Zero-Qwen-32B) on the paper's reasoning benchmarks.[2] That's evidence for testing distillation when a strong teacher exists, not a universal ranking of the two methods.
RLVR still matters because distillation doesn't optimize the student online against a verifier. If the teacher's checked outcomes are insufficient, improving that policy against well-tested verifiers is a different operation from transferring its sampled outputs.