Skip to main content

[LLM 5/10] GRPO: Deleting the Value Network and Letting a Group of Answers Be Its Own Baseline

· 26 min read
Kobkrit Viriyayudhakorn
CEO, iApp Technology

The last chapter ended on DPO's gap: it can only rank answers someone prepared in a file. And chapter 3 paid PPO's full price: 4 models in VRAM, plus an entire value network to train on the side. This chapter combines the good halves of both — the model samples its own answers to learn from, genuine RL — but deletes the value network wholesale, using a statistical observation so simple it's almost irritating nobody framed it sooner: if you sample several answers to the same problem, the group's mean reward is already the baseline the value network was trying to estimate. And if the problem can be graded by code, we need no human preference data at all — zero pairs, zero baht.

Open in Colab05_grpo.ipynb

1. The Problem

Set the problem up like this: teach Qwen3-0.6B to solve Thai math word problems, where the final answer is a single number that a one-line == can grade.

Walk through the tools of the past three chapters one at a time, and none of them fits this job:

MethodCan the model sample its own answers and learn from them?Human labels neededModels in VRAM
SFT (ch. 2)No — imitates the answer key onlyA human-written answer per example1
PPO (ch. 3)YesPreference pairs to train a reward model4
DPO (ch. 4)No — purely offlinePreference pairs2 (1 with LoRA)
  • SFT teaches imitation of a worked solution, but never lets the model try and fail on its own — it never sees which of its own lines of reasoning lead to the right answer.
  • PPO lets the model try, but charges you a reward model trained from preference pairs plus an entire value network — for a task whose reward can be written directly as a Python function.
  • DPO deletes RL elegantly, but can only rank answers that already exist in the dataset. A math problem needs the model to explore many paths and reinforce the ones that reach the right answer.

So this chapter's question is narrow and sharp: which parts of PPO are truly necessary, and which can be deleted, when our reward can be checked by code?

2. What We're Going to Do

Go back to the value network's job in chapter 3: it exists to answer one question — "on average, roughly what reward should this prompt earn?" — to serve as a baseline subtracted from the actual reward. Answers "better than average" get positive gradient; "worse than average," negative. Without that baseline, the policy gradient is so noisy it can barely train at all.

PPO answers that question by training an entire extra model to predict this average. GRPO answers it by sampling until you can see it with your own eyes:

The core idea of this chapter

Sample GG answers from the same prompt and average the group's rewards — that mean is already, by definition, an unbiased estimate of "the expected reward for this prompt." It is the very thing the value network tries to approximate, except it needs no training, no loading, and can never drift wrong. The entire value network can therefore be deleted. And when the reward is checked by code (numeric answer right or wrong), the reward model and the human preference data vanish along with it — zero labels remain.

This is GRPO (Group Relative Policy Optimization), proposed by Shao et al. (2024) in DeepSeekMath, and it is the same engine that trained DeepSeek-R1. The broader approach carries the collective name RLVR (RL with Verifiable Rewards) — RL whose reward comes from a verifier, not from human taste.

And let's set expectations straight from the top of the chapter: current evidence points to this kind of RL mostly "sharpening" ability the base model already has at pass@8 so it surfaces at pass@1, rather than creating new capability from zero. We return to this with measuring instruments in section 9.

3. The Equations

3.1 The group-relative advantage — the whole heart in one line

A^i=rimean(r1,,rG)std(r1,,rG)\hat A_i = \frac{r_i - \operatorname{mean}(r_1,\dots,r_G)}{\operatorname{std}(r_1,\dots,r_G)}
  • GG = the number of answers sampled from the same prompt (8 in this chapter)
  • rir_i = the reward of answer ii
  • Every token of answer ii shares the same single A^i\hat A_i across the whole sequence — unlike PPO, which chases per-token advantage through the value network and GAE

In plain language: "is this answer better or worse than my own other attempts at the same problem?" No comparison across problems, no predicting the future — only a competition inside the group.

This short equation has one consequence that matters enormously: if the whole group earns identical rewards (all right or all wrong), every A^i\hat A_i is zero and that batch teaches nothing at all. Remember that sentence — it will become both the chapter's number-one trap and its most important metric.

3.2 The full GRPO objective

JGRPO(θ)=E[1Gi=1G1oit=1oi{min ⁣(ρi,tA^i, clip(ρi,t,1ϵ,1+ϵ)A^i)βDKL[πθπref]}]\mathcal{J}_{\text{GRPO}}(\theta) = \mathbb{E}\left[\frac{1}{G}\sum_{i=1}^{G}\frac{1}{|o_i|}\sum_{t=1}^{|o_i|}\Big\{\min\!\big(\rho_{i,t}\,\hat A_i,\ \operatorname{clip}(\rho_{i,t},\,1-\epsilon,\,1+\epsilon)\,\hat A_i\big) - \beta\,\mathbb{D}_{\text{KL}}\big[\pi_\theta \,\|\, \pi_{\text{ref}}\big]\Big\}\right]

where ρi,t=πθ(oi,tq,oi,<t)πθold(oi,tq,oi,<t)\rho_{i,t} = \dfrac{\pi_\theta(o_{i,t} \mid q, o_{i,<t})}{\pi_{\theta_{\text{old}}}(o_{i,t} \mid q, o_{i,<t})} is the token's probability ratio against the policy at sampling time.

Read it piece by piece, because every piece has already passed before your eyes in this series:

  • min(, clip())\min(\cdot,\ \operatorname{clip}(\cdot)) = the same PPO clip from chapter 3, nothing new — keeps steps from wandering too far from the point where the rollouts were sampled
  • 1oi\frac{1}{|o_i|} = per-token averaging, keeping long answers from outsized influence (think of the length bias from chapter 4)
  • βDKL\beta\,\mathbb{D}_{\text{KL}} = the same leash as ever, tied to the same πref\pi_{\text{ref}} as chapters 3 and 4

What deserves the closest reading is what is not in the equation: no V(s)V(s), no GAE, no critic loss. The whole line runs on just two models (πθ\pi_\theta and πref\pi_{\text{ref}}) and reward numbers from a verifier.

3.3 The KL term isn't computed directly — meet the k3 estimator

True KL divergence requires summing over the entire vocabulary at every position, which is expensive and unnecessary. GRPO estimates it from the tokens already sampled, using an estimator nicknamed k3:

D^k3=πref(oi,t)πθ(oi,t)logπref(oi,t)πθ(oi,t)1\hat{\mathbb{D}}_{k3} = \frac{\pi_{\text{ref}}(o_{i,t})}{\pi_\theta(o_{i,t})} - \log\frac{\pi_{\text{ref}}(o_{i,t})}{\pi_\theta(o_{i,t})} - 1

The question students always ask (and should): why not just use log(πθ/πref)\log(\pi_\theta/\pi_{\text{ref}}) directly, when its expectation is already the KL?

The answer: the naive estimator (called k1) is indeed unbiased, but individual samples can go negative — about 40% of samples come out negative, even though KL cannot be negative by definition — and the variance is very high. At realistic batch sizes the estimate swings so hard that the penalty alternately pushes and pulls.

k3 fixes both at once. Let x=πref/πθx = \pi_{\text{ref}}/\pi_\theta and observe two facts:

  1. The inequality x1logxx - 1 \geq \log x always holds, so k3 =(x1)logx0= (x-1) - \log x \geq 0 on every sample
  2. Eπθ[x]=πθπrefπθ=1\mathbb{E}_{\pi_\theta}[x] = \sum \pi_\theta \cdot \frac{\pi_{\text{ref}}}{\pi_\theta} = 1, so the (x1)(x-1) term has expectation zero — it is a control variate that cancels the noise of logx-\log x without touching the expectation

The result is an estimator exactly as unbiased, with variance lower by an order, that can never go negative. Figure 5.3 will show you the difference with your own eyes.

3.4 An advanced note: dividing by std isn't as pure as it looks (Dr.GRPO)

The division by std(r1..rG)\operatorname{std}(r_1..r_G) in equation 3.1 smuggles in one bias: groups whose rewards are nearly identical (small std — say 7 right out of 8) get their advantages amplified by an enormous factor, while groups that genuinely disagree (large std — which carry the most information) get comparatively muted. The net effect is a gradient tilted toward problems the model nearly already agrees with itself about. The Dr.GRPO work (Liu et al., 2025) proposes dropping the std division entirely, keeping only the mean subtraction — which remains a perfectly correct baseline. The widget in section 4 has a toggle so you can try both.

3.5 Unbiased pass@k — the tool section 9 will need

Sample nn answers per problem, get cc correct, and ask "given a budget of kk tries, would at least one be right?":

pass@k^=1(nck)(nk)\widehat{\text{pass@}k} = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}

The fraction at the back is the probability that drawing kk from nn finds nothing but wrong answers. The formula people commonly misuse is 1(1c/n)k1-(1-c/n)^k, which is systematically biased in your favor when nn is small (this is why the HumanEval paper by Chen et al. 2021 needed a separate appendix on exactly this). Keep this formula close — it is the yardstick for judging whether GRPO "creates" new capability or merely "sharpens" what exists.

4. Seeing the Equations

What one group teaches — and which groups teach nothing at all

A two-panel bar chart. The left panel shows positive and negative advantages of 8 answers around the group mean. The right panel shows a degenerate group where every answer earns the same reward, making all advantages zeroA two-panel bar chart. The left panel shows positive and negative advantages of 8 answers around the group mean. The right panel shows a degenerate group where every answer earns the same reward, making all advantages zero

Figure 5.1Left: the advantages of a real 8-answer group under this chapter's reward shaping (correct +1.0, format +0.3, Thai +0.2) — the zero line is exactly the group mean. Right: a group with identical rewards throughout; every advantage is zero, the gradient is zero

The left panel is equation 3.1 at work: the two answers that did everything right (r = 1.5) get a strong positive push, while the answer that earned only the format bonus (r = 0.3) gets pushed down despite its positive reward — because the criterion is not "is it good" but "is it better than its groupmates." The right panel is the silent death mode of all of GRPO: identical rewards = zero std = zero learning.

Enter your own rewards and watch the advantages change live — and don't miss unticking the Divide by std box to see the Dr.GRPO difference from section 3.4 with your own eyes:

Try a group:

Rewards r_i (G = 8)

Unchecked is the Dr.GRPO variant: it keeps the centring but drops the std, removing the bias toward low-variance groups.
r1 = 1.001.00r2 = 0.00-1.00r3 = 1.001.00r4 = 1.001.00r5 = 0.00-1.00r6 = 0.00-1.00r7 = 1.001.00r8 = 0.00-1.00Â = 0 (no update)
mean(r)0.5000
std(r)0.5000
max |Â|1.000
Formula(r − μ) / σGRPO

Watch the std term.Dividing by std(r) = 0.500 rescales this whole group. A group that happened to be near-unanimous gets a large multiplier and dominates the update, even though it carries less information than a group that genuinely disagreed. Untick the box to see the same rewards without the rescaling.

What GRPO deletes from PPO

A horizontal bar chart comparing the memory of PPO's four models against GRPO's two, with the value network crossed out and the reward model turned into a Python function using zero memoryA horizontal bar chart comparing the memory of PPO's four models against GRPO's two, with the value network crossed out and the reward model turned into a Python function using zero memory

Figure 5.2Models in VRAM counted in fp16 weights of Qwen3-0.6B (1.11 GB per copy): PPO loads 4 copies, GRPO 2 — the value network is replaced by the group mean, and the reward model by a Python function

Notice that the two blocks that vanished are exactly the two that had to be trained (the value net) or pre-trained (the reward model). What's left is the policy and the reference — and chapter 4 already taught us LoRA lets those two share base weights. The net model cost of GRPO in this chapter's notebook therefore equals plain SFT.

k3 versus k1: equally unbiased, not equally usable

Histograms comparing the distributions of the k1 and k3 KL estimators, and a running-mean plot showing both converge to the true KL but k3 is much steadierHistograms comparing the distributions of the k1 and k3 KL estimators, and a running-mean plot showing both converge to the true KL but k3 is much steadier

Figure 5.3A synthetic example with a known answer: π_θ = N(0,1), π_ref = N(0.5,1), making the true KL exactly 0.125 — k1 spreads wide and goes negative on roughly 40% of samples, while k3 never goes negative and its std is almost three times lower (0.18 versus 0.50)

The right panel is the practical point: both lines converge to the same answer (both unbiased), but at sample counts matching a real batch (tens to hundreds) the k1 line still swings hard, while k3 is steady enough to serve as a trustworthy penalty from the earliest steps.

5. Setting Up the Environment

Open Colab and pick Runtime → Change runtime type → T4 GPU (the free tier is enough).

The series-wide warning worth re-reading every chapter

The Colab T4 is Turing architecture (SM 7.5), which does not support bfloat16 and does not support FlashAttention-2.

But Qwen3-0.6B's config.json declares torch_dtype: bfloat16. So torch_dtype="auto" is a trap — your code will crash or run bizarrely slowly without telling you why.

torch_dtype=torch.float16      # not bfloat16
attn_implementation="sdpa" # not flash_attention_2
fp16=True # in GRPOConfig (not bf16=True)
cap = torch.cuda.get_device_capability(0)
print("compute capability:", cap) # T4 = (7, 5)
print("native bf16:", cap[0] >= 8) # T4 -> False
print("torch says :", torch.cuda.is_bf16_supported()) # T4 -> True (counts emulation!)
is_bf16_supported() lies on a T4

Recent torch returns True on a T4 because it counts emulation as support — which is far slower than fp16. Gate on compute capability ≥ 8.0 (Ampere and up) instead. This was a real bug, caught only by running the notebook on Colab.

Two models for the price of one — the same recipe as chapter 4

The policy is the LoRA adapter from chapter 2; πref\pi_{\text{ref}} is the same base with the adapter off. TRL knows this mechanism natively: if model is a PeftModel, it loads no separate reference.

from peft import PeftModel

base = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-0.6B",
torch_dtype=torch.float16,
attn_implementation="sdpa",
).cuda()

policy = PeftModel.from_pretrained(base, "kobkrit/qwen3-0.6b-th-sft-lora", is_trainable=True)
This chapter's real budget is generated tokens, not training steps

GRPO is online RL: before every weight update, fresh answers must be sampled from the model. This chapter's full config generates up to 128 problems × 8 answers × 256 tokens = 262,144 tokens, versus chapter 4, which merely forward-passed text already sitting in a file — different worlds entirely.

T4 time therefore goes to generation, not backprop, and this is why the notebook sets FAST_MODE = True as the default (64 problems × 4 answers × 192 tokens ≈ 49k tokens, ~10 minutes), while the full config used for reported results (~18 minutes) is one flag flip away — we say this out loud because an article that won't tell you "which config the pretty numbers came from" is lying to you by half a sentence.

6. Preparing the Data

We use VISAI-AI/gsm8k-thai — a Thai translation of the GSM8K math word-problem set. We sample 128 problems from the train split and keep a separate held-out set for evaluation, untouched during training.

from datasets import load_dataset

ds = load_dataset("VISAI-AI/gsm8k-thai", split="train")
ds = ds.shuffle(seed=42).select(range(128))

# The notebook's system prompt is the Thai original of this instruction —
# the model is being trained to reason in Thai:
SYSTEM = "Think step by step inside <think>...</think>, then end with the numeric answer on the last line."

def to_prompt(ex):
return {
"prompt": [{"role": "system", "content": SYSTEM},
{"role": "user", "content": ex["translated_question"]}],
"answer": extract_final_int(ex["translated_answer"]), # the GSM8K answer key sits after "####"
}

train_ds = ds.map(to_prompt, remove_columns=ds.column_names)

Notice there is no chosen/rejected column and no human label of any kind — only problems and numeric answers. What replaces the labels is three reward functions checked entirely by code:

import re

THAI_DIGITS = str.maketrans("\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59",
"0123456789") # Thai digit glyphs 0-9

def extract_final_int(text):
text = text.translate(THAI_DIGITS) # in case the model answers in Thai numerals
tail = text.split("</think>")[-1] # grade only what follows the thinking
nums = re.findall(r"-?\d[\d,]*", tail)
return int(nums[-1].replace(",", "")) if nums else None

def reward_correct(completions, answer, **kwargs): # +1.0 final answer correct
return [1.0 if extract_final_int(c) == a else 0.0
for c, a in zip(completions, answer)]

def reward_format(completions, **kwargs): # +0.3 has a non-empty <think>
pat = re.compile(r"<think>.+?</think>", re.DOTALL)
return [0.3 if pat.search(c) else 0.0 for c in completions]

def reward_thai(completions, **kwargs): # +0.2 genuinely thinks in Thai
def th_ratio(s):
letters = [ch for ch in s if ch.isalpha()]
return sum("\u0e01" <= ch <= "\u0e5b" for ch in letters) / max(len(letters), 1) # Thai Unicode block
return [0.2 if th_ratio(c) > 0.5 else 0.0 for c in completions]

The total reward of one answer is the sum of all three: at most 1.5, at least 0.0. (The THAI_DIGITS table exists because Thai has its own digit glyphs, U+0E50 through U+0E59 — a model that writes the correct answer in Thai numerals still deserves its +1.0. And the U+0E01 through U+0E5B comparison is simply the Thai Unicode block, the same range behind the series' th_ratio metric.)

Why shaped sub-rewards, not just "right/wrong"

Look back at figure 5.1's right panel: the source of all learning is variation within the group. Early in training, a 0.6B model gets math problems right only rarely — if the reward were right/wrong alone, most groups would be [0,0,0,0,0,0,0,0]: zero std, zero gradient, training for free and getting nothing. The format and Thai-language sub-rewards keep groups varied enough to learn from before the model ever starts answering correctly. This is reward shaping in the most literal sense — and, in large letters: it also opens doors for cheating. See trap 1 in section 9 for which clause the model found its loophole in (it really did — the evidence is in the notebook).

7. The Main Code

7.1 GRPOTrainer — this time the trainer is a first-class citizen

In chapter 3 we had to squint at TRL's PPOTrainer, stuck in semi-experimental status with an API shifting nearly every minor version. GRPOTrainer is a different story entirely: it is the trainer TRL showcases in the wake of R1, and it takes rewards as plain Python functions, no model-wrapping of any kind.

from trl import GRPOConfig, GRPOTrainer

FAST_MODE = True # default: finishes in ~10 minutes on a free T4
# False = the full config used for reported results (~18 minutes)

cfg = GRPOConfig(
output_dir="grpo-out",
num_generations=4 if FAST_MODE else 8, # G — the group size
max_completion_length=192 if FAST_MODE else 256,
max_prompt_length=256,
temperature=1.0, # do not lower — diversity within the group is the fuel
beta=0.04, # KL weight (computed with k3 from section 3.3)
epsilon=0.2, # the same clip range as PPO
learning_rate=1e-6, # lower than DPO still — see the warning below
per_device_train_batch_size=16, # must divide evenly by num_generations
gradient_accumulation_steps=2 if FAST_MODE else 4,
num_train_epochs=1,
fp16=True, # the T4 has no bf16
logging_steps=1,
)

trainer = GRPOTrainer(
model=policy, # PeftModel → free reference
reward_funcs=[reward_correct, reward_format, reward_thai],
args=cfg,
train_dataset=train_ds,
)
trainer.train()

Let's do the budget arithmetic in the open: the full config is 128 problems × 8 answers = 1,024 completions, divided by an effective batch of 64 completions per step = 16 optimizer steps — that is genuinely all. Nearly all the remaining time is spent generating roughly 262k tokens (at most) before each step.

Two numbers that must always move together

per_device_train_batch_size counts completions, not prompts, and must divide evenly by num_generations, because members of the same group must sit in the same batch for the group's mean/std to be computable. Misalign them and TRL errors out at trainer construction — which is good. Failing loudly beats failing silently.

Online RL's learning rate must be the lowest in the series

The series' progression is SFT 2e-4 → DPO 5e-6 → GRPO 1e-6. The reason: GRPO's training data is answers sampled by the current model. If the weights move hard enough that the language starts to bend, the next generation of answers bends with it, and the reward collapses across the board — online RL's mistakes compound, unlike supervised learning, where the training data isn't going anywhere.

7.2 Computing the advantage by hand — all the arithmetic in one place

So that GRPOTrainer isn't a black box, the notebook has a cell that does equation 3.1's arithmetic in the open:

import torch

def group_advantages(rewards, G, eps=1e-4):
"""rewards: [B], laid out in groups of G from the same prompt"""
r = rewards.view(-1, G) # [B/G, G]
mean = r.mean(dim=1, keepdim=True)
std = r.std(dim=1, keepdim=True)
return ((r - mean) / (std + eps)).view(-1) # Dr.GRPO: delete "/ (std + eps)"

r = torch.tensor([1.5, 0.3, 0.5, 1.5, 0.3, 0.0, 0.3, 0.5]) # the group from figure 5.1
print(group_advantages(r, G=8))
# → [+1.56, -0.55, -0.20, +1.56, -0.55, -1.08, -0.55, -0.20]

These eight lines are everything GRPO adds on top of the plain PPO clip. Compare that to chapter 3's value network + GAE, which had to be trained alongside the policy the whole way — this is the best trade in the series.

8. Results

The notebook measures 4 things and writes them to results.json:

  1. Mean reward per step — should climb (this is what the optimizer sees)
  2. The fraction of groups with nonzero std, per step — the metric almost nobody plots
  3. pass@1 and pass@8 on held-out, using the unbiased formula from section 3.5, with a Wilson 95% CI
  4. Mean completion length per step — read side by side with accuracy
Metric (full config)Before trainingAfter training
pass@1, held-out (95% CI)??
pass@8, held-out (unbiased)??
mean reward per group??
mean answer length (tokens)??
fraction of groups with std > 0 (first step → last step)??
Metric number 2 is GRPO's vital sign

A flat mean reward reads two ways: "the model has saturated" or "learning stopped a long time ago." What separates the two cases is the fraction of groups with nonzero std — the moment it touches zero, every batch after that is a silent no-op: the loss still prints, steps still tick, the GPU still runs hot, but the gradient is exactly zero at every step (figure 5.1's right panel multiplied across the batch). Train another hour and get exactly the same model. The notebook always plots this line next to mean reward — and it should become your habit in every RLVR project.

The promised mini-R1 moment

The DeepSeek-R1 paper has a very famous plot: answer length growing on its own, together with accuracy, with nobody telling the model to think longer — the model discovered by itself that writing out its reasoning in more detail brings reward. Our notebook plots the same pair (completion length and accuracy per step) at miniature scale. If you see both lines drift upward together even slightly, that is the same mechanism as R1 in your own test tube. And if length grows while accuracy stays flat, suspect reward hacking first, always (trap 1, section 9).

Prompt
Promptอธิบายว่าทำไมท้องฟ้าถึงเป็นสีฟ้า แบบสั้น ๆ

base

Thai 18%41 tokens
The sky appears blue because of Rayleigh scattering. ท้องฟ้า is blue เพราะ light scatter ครับ. Shorter wavelengths scatter more than longer ones.

sft

Thai 99%78 tokens
ท้องฟ้าเป็นสีฟ้าเพราะแสงอาทิตย์กระทบกับโมเลกุลของอากาศแล้วเกิดการกระเจิงแบบเรย์ลี ซึ่งแสงสีน้ำเงินที่มีความยาวคลื่นสั้นกว่าจะกระเจิงได้มากกว่าแสงสีแดง เราจึงมองเห็นท้องฟ้าเป็นสีฟ้าครับ

Showing the built-in sample.

9. Comparison

Four chapters, four methods, measured on the same task (the same held-out Thai math problems) — this table is where the whole series pays out, because the rightmost column has never appeared in any chapter before:

Methodpass@1 (95% CI)Answer lengthTraining timeModels in VRAMHuman label cost
SFT (ch. 2)???1A human-written answer for every example
PPO (ch. 3)???4Preference pairs to train the reward model
DPO (ch. 4)??~9 min2 (1 with LoRA)~500 preference pairs
GRPO (this chapter)??~18 min2 (1 with LoRA)Zero

Read this table right to left: the series' arc is a progressive shedding of human labels — from an answer for every example → preference pairs → zero — with the machinery underneath growing simpler as well. The one condition that makes the last column zero is that the task must be checkable by code — engrave that somewhere.

Does RL create new capability, or sharpen the old?

A two-panel plot. The left panel shows a probability sharpening function fixed at zero. The right panel shows pass@1 bars rising sharply toward the old pass@8 ceiling line while pass@8 rises much lessA two-panel plot. The left panel shows a probability sharpening function fixed at zero. The right panel shows pass@1 bars rising sharply toward the old pass@8 ceiling line while pass@8 rises much less

Figure 5.4A toy model with the mechanism fully specified (RL multiplies the odds of problems it has ever solved by ×8, but cannot touch problems at p = 0): pass@1 leaps toward the old pass@8 ceiling — an illustration of the mechanism only; the measured numbers are in the notebook

The logic behind this figure is stronger than it looks: GRPO learns from advantages, which can only be nonzero when at least one answer in the group does better than its peers — meaning a problem the base model never solves no matter how it samples (p = 0) can never send a learning signal into the system. What RLVR does well is take ability scattered across pass@8 and concentrate it at pass@1. Research in 2025 (Yue et al.) even measured that at very large kk, the base model can beat the post-RL model. This does not make GRPO worthless — real users get one answer; pass@1 is the number that matters — but it does mean you shouldn't read a climbing reward curve and conclude the model "got smarter." Mostly, it got steadier.

Traps to watch for

1. Reward hacking: farming +0.3 with an empty <think> The notebook's first version of the format reward used the regex <think>.*?</think> (the crucial detail: .*? accepts the empty string). Within a few steps the model discovered that printing a bare, empty <think></think> and then guessing a number collects +0.3 for free every time — far cheaper than actually thinking. Mean reward climbed beautifully; accuracy didn't move. The notebook keeps the caught-in-the-act samples for you to see, then fixes the pattern to .+?, forcing real content. The lesson: the model does not optimize what you meant. It optimizes what you wrote.

2. Identical rewards across the whole group → figure 5.1's right panel, the lesson The smaller the group, the likelier every answer earns the same reward — at G=2G = 2, two coins land the same way very often. Groups smaller than 4 burn a large share of compute for nothing, and a mean estimated from two samples is high-noise anyway. G=8G = 8 is the widely used balance point (our FAST mode accepts dropping to 4 in exchange for time — and says so out loud).

3. Temperature too low = killing diversity at the source Lower the temperature and all 8 answers come out nearly identical → identical rewards → back to trap 2. Never carry inference habits (low temperature for stability) into rollout collection. We set temperature=1.0 because diversity within the group is the fuel of the entire learning system.

4. The bottleneck is generation, not backprop — budget the right pile If a run is slow, don't reach for batch size or the optimizer first — look at the ~262k-token budget in section 5. The levers that work, strongest first: lower max_completion_length, lower num_generations, use fewer problems. (Production systems solve this with an inference engine like vLLM, which TRL can attach to, but that is beyond free Colab.)

10. Summary

  • The group mean is a baseline that needs no training — sample GG answers from the same prompt, and PPO's entire value network becomes unnecessary
  • Code-checkable rewards = zero human labels — the point the series has been climbing toward since chapter 2
  • Advantage is relative within the group — an answer with positive reward can still be pushed down, if its groupmates did better
  • A group with identical rewards teaches nothing — always plot the fraction of groups with std > 0; it is the line between "saturated" and "stopped learning long ago with nobody noticing"
  • k3 makes the KL penalty usable at small batch sizes — as unbiased as the log-ratio, but never negative and far lower variance
  • Dividing by std hides a bias — Dr.GRPO cuts it, keeping only the mean subtraction; try it yourself in the section 4 widget
  • Reward shaping is necessary and dangerous — sub-rewards prevent all-zero groups early on, but open the door to score farming
  • RLVR mostly "sharpens," it doesn't "create" — pass@1 climbs toward the old pass@8 ceiling; measure both, always
Limitations of this experiment

GRPO needs a reward that code can check. Math problems qualify; code qualifies (run the tests). But "write a polite, natural-sounding Thai email" has no checking function — open-ended work like that is the territory of preference data and chapter 4's DPO. The two chapters complement each other; neither replaces the other. Choose the tool by the shape of the reward, not by the algorithm's novelty.

Don't interpret the results as the model "getting smarter" — our evidence and the full-scale research both point to RLVR mostly reorganizing the probability of abilities that already existed at pass@8 so they surface reliably at pass@1. If you want genuinely new knowledge, go back to chapter 1 (CPT).

And as ever: 128 problems and 16 optimizer steps demonstrate the mechanism; they are not real training. DeepSeek-R1 used problems by the hundred thousand and compute many orders of magnitude beyond ours. What transfers to real scale is the understanding: the group is the baseline, diversity is the fuel, and the reward verifier is the thing the model will always find the loopholes in for you.

Next chapter: Context Distillation — that long system prompt you pay for on every single call to the model: how to distill it into the weights so the model behaves accordingly without ever seeing the prompt again.

References

  1. Shao et al. (2024). DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models — DeepSeekMath: where GRPO comes from
  2. DeepSeek-AI et al. (2025). DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning — R1: RL on verifiable rewards at real scale
  3. Liu et al. (2025). Understanding R1-Zero-Like Training: A Critical Perspective — Dr.GRPO: the std-division bias discussed in section 3
  4. Ahmadian et al. (2024). Back to Basics: Revisiting REINFORCE Style Optimization for Learning from Human Feedback in LLMs — plain REINFORCE may suffice -- read with the value-network deletion
  5. Schulman et al. (2017). Proximal Policy Optimization Algorithms — the original PPO paper: the clipped surrogate in section 3
  6. Chen et al. (2021). Evaluating Large Language Models Trained on Code — the unbiased pass@k estimator used in section 9

The writing, code and notebooks in this series are licensed under CC BY-NC-SA 4.0 — reuse and adapt them freely with attribution, for non-commercial purposes, and share your adaptations under the same terms. Third-party models and datasets referenced here keep their own licences.