train_rl, around GRPO, and expresses each published variant (Dr. GRPO, DAPO, RLOO, GSPO) as a set of configuration fields of that one loop rather than as a separate program. Chapters 10 to 19 all train with it. We then run it on the BugFix gym and meet a problem that no choice of objective fixes: the agent learns to pass training tasks without reading the code. Diagnosing that problem with behavior statistics and fixing it in the environment is the core of Section 9.4, and it decides the task setting the rest of the book uses. The chapter ends with small controlled ablations of the variants, labeled for what they are: mechanism demonstrations on a 158k-parameter model, not benchmark evidence.
9.1 GRPO as in DeepSeekMath
DeepSeekMath (Shao et al., 2024) defines GRPO for a question with sampled outputs from : with outcome advantages shared by every token of output , and the per-token KL estimate , the estimator. Read against the earlier chapters, it is four choices:- Group sampling. attempts per task in the same step (DeepSeekMath reports ).
- A critic-free advantage. The group mean as the baseline and the group’s standard deviation as the scale:
group_advantagesof Section 7.5. - PPO’s clipped per-token ratio (Section 8.2), aggregated by averaging each sequence’s tokens and then the sequences:
seq_mean_token_meanof Section 6.6. - A KL penalty to a frozen reference policy in the loss, with coefficient (reported as 0.04), rather than as a per-token reward as in RLHF-style PPO.
RLConfig collects every setting that train_rl reads. The defaults are the book’s GRPO configuration on the version of the gym that Section 9.4 arrives at. Fields that belong to a variant are introduced in Section 9.5.
token_mean replaces seq_mean_token_mean, for the length reasons of Section 6.6, revisited in Section 9.7. kl_coef is 0, as in DAPO and the public coding-agent recipes of Chapter 20; Chapter 10 studies the KL term. And each batch is split into 2 minibatches, so the clip is exercised but the update stays close to on-policy (Section 8.6).
9.2 The training loop
train_rl is assembled from five pieces, each saved separately so that later chapters can replace one: an environment factory and a group sampler, behavior statistics, advantages, the clipped update, and the loop itself.
Sampling groups
make_env builds one environment as the configuration describes it. EditBudgetEnv, the environment it builds by default, is defined in Section 9.4; with max_edits=None it returns Chapter 4’s BugFixEnv unchanged. sample_groups lays tasks out in consecutive groups of , which is the layout group_advantages expects, and runs all episodes in lockstep with Chapter 5’s rollout_batch. When cfg.rename is set, each episode’s task passes through randomize_names (Section 9.4) with a Python random stream seeded from the torch generator, so the whole step is reproducible from one seed. The env_factory argument is the hook for custom environments and rewards (Chapter 11 uses it).
What the agent did
Reward alone cannot distinguish an agent that reads the code from one that guesses well, and Section 9.4 depends on telling them apart.behavior_stats reports, per episode: the hidden-test pass rate, the reward, the number of read, test, and applied edit calls, whether the first applied edit was the task’s correct op, the number of turns, and the number of policy tokens. The correct op of each task comes from the family table of Chapter 4, so the renamed tasks of Section 9.4 are scored by their original identity.
Advantages and the KL term
compute_advantages turns a collated batch into one advantage per episode. Rewards may first be shaped by DAPO’s overlong penalty (Section 9.5); the three advantage modes are Chapter 7’s estimators. kl_penalty is the per-token estimate of from tokens sampled by , with the log-ratio written as : , , . Chapter 10 derives their bias, variance, and the gradients they produce as losses.
The update
policy_loss dispatches on the ratio level: per-token ratios go to Chapter 8’s ppo_policy_loss, sequence-level ratios to gspo_policy_loss (Section 9.5). rl_update is Chapter 8’s PPO phase without the critic. It freezes and, when a reference model is given, , by one no-grad forward pass over the batch (the trainer recomputes old_logp, as Section 8.6 recommends, at the sampling temperature), then runs cfg.epochs passes of cfg.minibatches clipped steps. The KL term is averaged over policy tokens. Entropy is computed from the logits the update already has, so logging it costs nothing.
The loop
sample_step draws prompts_per_step distinct training tasks and samples their groups. With dynamic sampling it keeps only groups whose rewards differ and samples more tasks until the batch is full or the round limit is reached; Section 9.5 explains the choice. It returns the kept episodes and everything sampled, because the cost of a step is what was sampled.
train_rl runs steps iterations of sample, advantage, update, and log. It trains model in place and returns a history with one dictionary per step: the behavior statistics of everything sampled, the update’s statistics (clip_frac, approx_kl, ratio range, entropy, grad_norm, and kl_ref when a reference is used), the number of episodes sampled, the fraction of sampled groups whose rewards were all equal (zero_adv_frac), and, every eval_every steps, the pass rate on eval_tasks with 4 episodes per task. When kl_coef > 0 and no reference is passed, the reference is a frozen copy of the starting model.
evaluate samples n_samples episodes per task under the same environment settings as training and returns the behavior statistics. The default is sampled pass@1 at temperature 1; Chapter 17 treats evaluation properly.
9.3 GRPO on the gym as built
We first train exactly the setup of Chapter 6: the base agent, the gym of Chapter 4 with its function names and unlimited edits. Only the update changes, to GRPO with groups of 8 on 8 tasks per step.rename=False and max_edits=None select the original gym.
9.4 Shortcut features in environment design
A shortcut feature is an input that predicts the reward on the training distribution without carrying the information the task is meant to require. Optimizers find shortcuts because they are cheaper than the intended skill. The behavior statistics point at the BugFix gym’s first one: the issue text.issue : to an op needs no information from the environment at all. On held-out tasks the names are words it has never been trained on, and the map has nothing to say. Real benchmarks leak in the same way. SWE-Bench+ (Aleithan et al., 2024) reports that 32.67% of the successful patches it examined on SWE-bench had the solution “directly provided in the issue report”, and SWE-Bench Illusion (Liang et al., 2025) reports that models identify the buggy file path from the issue text alone with up to 76% accuracy, dropping to up to 53% on repositories outside the benchmark, which the authors read as memorization (Chapter 20). In both cases the issue carries information that a real engineer would have to obtain by reading code.
The fix belongs in the environment: make the shortcut carry no information. randomize_names renames the function in a task’s issue and source to a name drawn at random from all 30 function names of the gym, excluding words already in the source. Every name stays in the Chapter 5 vocabulary, so no tokenizer change is needed. Applied independently to every episode, the name becomes noise: each name appears with every family equally often, and a policy can no longer predict the op from it.
name, so logs and FIX_OP still identify it. We rename the clamp task twice and check that every renamed issue and source of every task encodes without unknown tokens.
The second shortcut
Renaming shrank the gap between training and held-out pass rates from 0.58 to 0.12 (0.59 against 0.48), and it did not produce reading: 0.01 reads per episode. The first edit is now right in 22% and 18% of episodes, the rate of a uniform guess, and the policy applies about 2.5 edits per episode instead. It has switched to the other strategy Chapter 6 found: stacking edits. A wrong op usually leaves the source unchanged or changes it harmlessly, so applying several ops in a fixed order repairs several families without looking. How far can that go?blind_pass_rate applies a fixed sequence of ops to every task and runs the hidden tests.
EditBudgetEnv refuses edits beyond max_edits: the call uses a turn and returns no change, and the source is untouched.
read observation, or the test output before the edit. We retrain once more from the base agent, now with the book’s default configuration: renamed functions and one edit.
The exploration problem underneath
With both shortcuts closed, the policy did not learn to read either. Its pass rate stays at the blind ceiling, 0.19 on training tasks and 0.15 held-out, and its reads fall from 0.30 per episode at the first step to 0.02. The batch reward wanders between 0.14 and 0.34 as the policy shifts its single guess from one op to another. The environment now requires reading, and the policy gradient still cannot find it. The reason is in the base agent’s own samples. RL reweights what the policy already does: an action sequence gains probability only if it earns higher rewards than its group’s other attempts. We measure how often the base agent reads before its first edit, and how often that edit is then right.random and blind play in which reading was never followed by an informed choice. Reading therefore earns the same reward as not reading, its advantage is zero in expectation, and noise and the faster-improving alternatives drive it out. This is an exploration failure, and closing shortcuts cannot fix it: an environment can make a skill necessary, but RL can only amplify a skill the policy already exhibits occasionally.
A pretrained language model is in a better position than this base agent: it has seen code being read and used, and it sometimes does the right thing for the right reason. The book’s analogue is a base agent whose demonstrations include a small share of informed play. make_rl_base adds one demonstration set from Chapter 4’s oracle to every four random and four blind sets, so one demonstration in nine reads and then applies the right op.
test call is not needed with one edit, since nothing can be done with its result, and the policy keeps it because nothing penalizes it, a reminder of Chapter 7’s point that outcome advantages also credit useless steps.
The control is informative in a way we did not expect when we designed it. The RL base agent learns to read on the original gym too, and reaches the same held-out pass rate, 0.838, with slightly more stacking (1.21 edits per held-out episode against 0.99). Once the starting policy explores, reading is the best strategy in either gym, and the shortcuts no longer win.
Figure 9.1 collects the five runs. The two changes do different jobs. The starting policy decided whether the skill was learned: from the Chapter 6 base agent, no environment produced reading, and from the RL base agent, both did. The environment decides what a pass rate means. In the original gym, a pass rate of 0.6 can come from reading, from names, or from blind stacking, and only behavior statistics tell them apart. In the anonymized one-edit gym, anything above 0.2 must come from information gathered during the episode, so a later chapter whose change damages exploration (a strong KL penalty, an entropy collapse, a hackable reward) shows up as a drop toward 0.2 instead of a silent switch to a shortcut.
Evidence. Measured here, one seed per setting, 80 steps of 64 episodes. The qualitative pattern (memorize, then stack, then stall, then read) matched what we saw in exploratory runs with other learning rates and seeds while preparing the chapter; those runs are not reported. The numbers are tiny-model demonstrations of a mechanism. The general claims are about the mechanism: a policy gradient amplifies whatever features predict reward in the policy’s own samples, so shortcut features must be removed from the environment, and the intended skill must be present, however rarely, in the starting policy.The default setting for the rest of the book. From here on, chapters train with
RLConfig() defaults, which use the anonymized gym (rename=True) with one edit per episode (max_edits=1), starting from make_rl_base(tok, seed), and evaluate with evaluate(..., cfg=cfg) on heldout_tasks. With these settings, a pass rate above 0.2 can only come from information gathered in the episode, and the behavior statistics in every history make that checkable. Chapters that study a shortcut on purpose, such as reward hacking in Chapter 11, change the reward or the environment through RLConfig.reward or env_factory and keep everything else.
9.5 The variants as diffs
Each public variant of GRPO changes a few of the choices listed in Section 9.1. The table states each as a diff against DeepSeekMath’s GRPO and gives theRLConfig fields that implement it. The source column summarizes what each paper specifies, as recorded in Chapter 20; dates are first arXiv versions.
Dr. GRPO removes the two normalizations whose biases Sections 6.6 and 7.5 measured: the division by each output’s length, which reduces the per-token penalty on long failures, and the division by the group’s standard deviation, which upweights tasks the policy almost always solves or almost always fails.
DAPO makes four changes. Clip-higher raises so that rare tokens with positive advantages can grow faster (Section 8.6 measured that the upper clip binds mostly on them). Dynamic sampling drops groups whose rewards are all equal, whose advantages are exactly zero, and samples more tasks until the batch is full, so every kept episode carries gradient. Token-level loss is
token_mean. Overlong reward shaping adds a penalty that grows linearly from 0 to over a buffer before the maximum length. In the gym the length that matters is the turn budget, so overlong_buffer=2 penalizes episodes that use the fifth or sixth turn by and . DAPO also describes overlong filtering, masking truncated episodes out of the loss, which DeepSWE’s “compact filtering” generalizes to timeouts and step limits.
RLOO uses the leave-one-out baseline of Section 7.5 and treats the whole generation as one action, so it is REINFORCE with a per-task baseline. Its advantage is times Dr. GRPO’s, an overall scale that Adam largely absorbs.
GSPO replaces the per-token ratio by one ratio per sequence, the geometric mean of its token ratios, , and clips . Every token of a sequence then shares one clip decision and one importance weight. Its authors motivate this by the mismatch between a sequence-level reward and token-level corrections, and report that it stabilized training of mixture-of-experts models. Because is an average of log-ratios, it moves much less than individual token ratios, which is why its reported clip ranges are three orders of magnitude smaller than PPO’s.
VARIANTS is a starting point for experiments, not a reproduction of any paper: learning rates, group sizes, batch sizes, and models differ from the papers’, and only the listed fields change. Each preset runs for 40 steps from the RL base agent.
9.6 Controlled ablations at tiny scale
The presets change several fields at once. A controlled ablation changes one field of the default configuration and holds everything else fixed, including the seeds: each arm below runs 40 steps from the same RL base agent with seeds 0, 1, and 2, and is evaluated on the held-out tasks with 8 episodes per task. Five arms make 15 runs.seq_mean_token_mean, and raising to 0.28 all land inside that spread. Clip-higher could not have done anything else: the clip binds on 0.15% of tokens in this configuration, so the bound it relaxes is almost never reached. The two effects that stand out are mechanisms, not rankings. seq_mean_token_mean produced the longest episodes, 14.9 policy tokens against 12.9 (Section 9.7 follows this up). Dynamic sampling had the highest late reward and held-out pass rate in all three seeds, 0.81 to 0.85, and it sampled 123 episodes per step instead of 64 to get there.
Evidence. Measured here: three seeds, 40 steps, one 158k-parameter model on 20 training tasks. These are mechanism demonstrations. Differences smaller than the spread across seeds are not differences, and none of these results predicts the effect of the same change on a large model, a long-horizon agent, or a different reward. The reported benefits of these choices in the source papers come from math reasoning at much larger scale (Chapter 20).
9.7 Design problems in practice
Problem: group size versus number of prompts at a fixed sample budget. Symptom: at the same number of episodes per step, one configuration learns hard tasks and another stalls on them, or most of each batch carries zero advantage. Cause: a group of attempts at a task with pass rate has zero variance with probability . Small groups waste attempts on degenerate groups for tasks that are nearly always solved or failed, and they give noisier baselines. Large groups cover fewer distinct tasks per step, so each step’s gradient is about fewer tasks. Options: (a) large for hard, sparse-reward tasks, where a success is rare and must be sampled to be learned from. (b) Many prompts with small for broad task distributions with medium pass rates. (c) Adaptive per task from its running pass rate. Default: with 8 prompts at this scale; measure the zero-variance fraction and the number of distinct tasks per step before changing it. The block below fixes the budget at 64 episodes and measures both quantities for the RL base agent and the trained policy.
Problem: zero-advantage groups and filtering. Symptom: as training succeeds, the share of all-pass groups rises, the effective batch shrinks, and progress slows; withtoken_mean, the zero-advantage episodes still count in the normalizer and dilute every other token’s weight. Cause: a zero-variance group has exactly zero advantage under every baseline of Chapter 7, so its episodes cost rollouts and contribute nothing but tokens to the denominator. Options: (a) dynamic sampling, which keeps the effective batch full and pays in extra rollouts. (b) Filter the zero-advantage groups without resampling, which keeps the cost and shrinks the batch. (c) A curriculum that drops tasks the policy always solves or never solves (Chapter 14). Default: logzero_adv_fracevery step; filter without resampling when it is moderate, and add a curriculum when whole tasks saturate. Dynamic sampling pays off when rollouts are cheap relative to updates, which is the opposite of agent RL. The default run’s history shows the trend.
token_mean still counted their tokens in its denominator. Dynamic sampling refills the batch, and its cost rises with the same trend, from about 110 episodes per step early to about 150 late, more than twice the nominal 64.
Problem: length growth. Symptom: the mean number of policy tokens or turns per episode rises over training without a matching rise in pass rate, often in failing episodes. Cause: the aggregation sets each token’s weight (Section 6.6). Underseq_mean_token_mean, a failing episode’s total penalty is fixed, so each of its tokens is penalized less when it is longer, and extra calls in failures are cheap. A reward that does not cost turns adds nothing against it. Options: (a) a length-proportional aggregation (token_mean,constant, orseq_mean_token_sum). (b) Overlong shaping or a step cost, which change the task and must leave room for useful information gathering (Chapter 12). (c) Masking truncated episodes. Default: (a), with the lengths of successes and failures logged separately. The ablation histories compare the two aggregations directly.
seq_mean_token_mean, episodes had about 14 policy tokens from the first ten steps on, against 11.6 rising to 13.4 under token_mean, with more turns (4.36 against 4.07) and slightly more reads (1.06 against 1.00 per episode). The difference appears early because the base agent’s failures are long and per-sequence normalization charges each of their tokens less. In 40 steps on a six-turn budget the effect is small; in agent episodes with hundreds of turns, it is the mechanism Dr. GRPO identifies as response-length bias.
Problem: choosing the loss aggregation. Symptom: two runs that differ only in a “normalization detail” learn at different speeds or drift in length, and a change of batch composition changes the effective learning rate. Cause:token_meancouples the per-token weight to the batch’s total length;seq_mean_token_meanweights trajectories equally and tokens of long ones less;constantandseq_mean_token_sumkeep per-token weights fixed and need the learning rate tuned for their scale. Options: the four modes ofaggregate_loss. Default:token_meanfor this book, because it is length-neutral within a batch, widely used (DAPO), and its batch-dependent scale is easy to monitor through the logged length;constant(Dr. GRPO) when lengths change a lot during training, since it removes even the batch coupling. Avoidseq_mean_token_meanfor multi-turn agents, whose episode lengths vary by an order of magnitude and correlate with success; the previous block measured its effect on this gym.
Summary
- GRPO is PPO’s clipped surrogate with a group baseline instead of a critic and, in DeepSeekMath’s form, a per-sequence token mean and a KL penalty in the loss. Its published variants are diffs in a handful of fields;
RLConfigandtrain_rlimplement them as options of one loop. - On the gym as built, GRPO reproduced Chapter 6’s memorization: 0.98 on training tasks, 0.40 held-out, zero reads, a first edit chosen from the function name.
- Shortcut features live in the environment. Renaming functions per episode removed the name shortcut and most of the train/held-out gap, and the policy switched to stacking edits, which a blind sequence can push to 0.60. Limiting episodes to one edit removed that shortcut and exposed an exploration failure: the base agent almost never followed a read with an informed edit, so there was nothing to amplify.
- A base agent with one informed demonstration in nine, at the same pass rate, learned read, edit, test, submit within 80 steps and reached 0.84 on held-out tasks, on the anonymized gym and, as a control, on the original one. The starting policy decides whether a skill is learned; the environment decides whether a pass rate certifies it. The anonymized one-edit gym with
make_rl_baseis the book’s default from here on. - At tiny scale, the single-field ablations of std normalization, aggregation, and clip-higher were within seed noise, and clip-higher had nothing to act on with a clip fraction of 0.15%. Two mechanisms were visible:
seq_mean_token_meanlengthened episodes, and dynamic sampling learned more per update while sampling 1.9 times as many episodes, a cost that grew as zero-advantage groups rose to 80% of the batch. GSPO with its paper’s ranges clipped 31% of sequences on 12-token episodes. These are mechanism demonstrations, not benchmark evidence.
Exercises
- Show that with one minibatch and one epoch, GRPO’s update equals the REINFORCE update of Chapter 6 with a group-normalized advantage. Then show what changes with two minibatches. Which tokens of the second minibatch can be clipped?
- Change an assumption: rerun Section 9.4’s default with
make_base_agent(tok, kinds=('random', 'blind') * 8 + ('oracle',)), one informed demonstration in 17. Does the policy still learn to read? Find the smallest informed share at which it does within 80 steps, and relate it to the measured read-then-right probability of the starting policy. - The renaming draws names from all 30 gym functions, including the held-out ones. Restrict the pool to training-task names and re-measure the held-out pass rate of the default run. What does the difference tell you about the role of the held-out names’ embeddings?
- Implement CISPO (Chapter 20) as
ratio_level='cispo': the loss per token. Which tokens keep a gradient that PPO’s clip would remove? Compare its clip-related statistics with the default on the run of Section 9.6. - Make the KL term matter: train the default configuration with
kl_coefin {0.01, 0.1, 1.0} and logkl_refand the pass rate. At what coefficient does the policy stop learning to read? Chapter 10 explains why the answer depends onkl_kind. - Measure the GSPO preset’s clip fraction and the spread of its sequence ratios. Are the paper’s ranges, chosen for long math outputs, appropriate for 12-token episodes? Derive how the standard deviation of scales with the number of policy tokens if the token log-ratios were independent.
- Design review: a team’s SWE agent reaches 70% on its training repositories and 35% on a held-out set, and its trajectories rarely open files before editing. Using Section 9.4, list the shortcut features you would look for in the task data and the environment, the behavior statistics you would ask for, and the two kinds of fix you would consider.