GRPO explained in depth.
diving into why group relative policy optimization (grpo) is needed
hi there! let’s dive deep into group relative policy optimization (grpo), a reinforcement learning (rl) method that’s been a game-changer, especially for fine-tuning large language models (llms) on tasks like reasoning and math problems. if you’re new to rl or need a refresher, i’d suggest checking out this post: Proximal Policy Optimisation. it’ll get you up to speed so we can focus entirely on grpo here.
this is going to be a long, detailed journey, and i’ll walk you through it like we’re chatting over coffee. we’ll explore why grpo is necessary, building up the concepts brick by brick, deriving every equation step by step, and weaving in the reasoning behind each piece without explicitly labeling it as intuition. my goal is to make the math feel natural and motivated, as if we’re discovering it together. let’s get started!
the challenge of optimizing policies in rl
in rl, we’re training an agent—like an llm generating text—to make decisions that maximize a total reward over time. the agent follows a policy, a strategy that picks an action (like the next token to generate) based on the current state (like the prompt and text so far). this policy is a neural network with parameters , written as , which gives the probability of choosing action in state .
picture a robot navigating a maze. at each time step , it’s in state , picks an action using its policy, receives a reward , and moves to state . the robot’s goal is to rack up as much reward as possible over time. our job is to tweak the policy’s parameters to maximize the expected total reward, which we’ll call the objective . in policy gradient methods, we compute the gradient of with respect to and use gradient ascent to adjust in the direction that increases the reward. but this process is tricky, especially for something as complex as an llm. let’s break down the challenges by starting with the basics.
vanilla policy gradients: the starting point
let’s begin with the simplest policy gradient method to understand where things go wrong. our objective is the expected total reward starting from an initial state, following the policy . imagine the robot starting at the maze entrance—we want to know the average total reward it can expect by following . we can write this as:
here, is the total discounted reward starting from the initial state . the robot collects rewards at each step, but future rewards are worth less than immediate ones—think of getting $1 today versus $1 next year. to account for this, we use a discount factor (between 0 and 1), so:
the expectation is taken over all possible trajectories (sequences of states, actions, and rewards) the robot might experience under . a trajectory is a sequence , and its probability depends on the policy and the environment’s dynamics. the probability of a trajectory is:
where is the initial state distribution, is the policy’s probability of choosing in , and is the environment’s transition probability. so we can rewrite as:
where is the total discounted reward for trajectory . to improve the policy, we need the gradient . since depends on through , we need to differentiate this integral. let’s compute the gradient:
since doesn’t depend on , we focus on . there’s a handy trick here: notice that , because:
substituting this in, we get:
now, let’s compute . first, take the log of the trajectory probability:
differentiate with respect to :
the terms and don’t depend on , so their gradients are zero. the only terms that depend on are the policy terms:
plug this back into the gradient:
in practice, we approximate this by using , the total discounted reward from time onward:
this leads to the policy gradient form:
to use this, we sample trajectories, compute for each step, and update to make high-reward actions more likely. but there’s a problem: can vary a lot between trajectories. one path might get a huge reward by chance, while another gets almost nothing, even if the actions were similar. this high variance makes the gradient noisy, causing the robot to overcorrect—like always turning left because one left turn got lucky. also, a single high-reward trajectory can cause a massive update to , drastically changing the policy and potentially ruining performance. we need to reduce this variance and control how much the policy changes.
reinforce with a baseline: tackling variance
let’s improve this by adding a baseline to reduce variance, leading to the reinforce algorithm with a baseline. instead of using directly, we subtract a baseline to compute the advantage. the idea is to measure how much better an action is compared to the average, rather than looking at the raw reward. we define the advantage as:
here, is a baseline, often chosen as the expected reward in state , which we approximate with a value function that estimates the average future reward starting from under . the policy gradient becomes:
let’s see why this reduces variance. without the baseline, our update term is . the variance of this term depends on , which can swing widely. for example, if ranges from -100 to 100 across trajectories, the updates will be all over the place. now, with the baseline, we use . if we choose to be close to the expected value of in state , i.e., , then:
this centers around 0, reducing its fluctuations. to quantify this, let’s compare the variances. the variance of is:
now, the variance of :
since , the second term is near 0, and the first term, , is the variance of around , which is smaller than the variance of around its mean. this makes the updates more stable, as doesn’t swing as much as .
we estimate using a separate neural network, called the critic, which we train to predict the expected reward. but this comes with challenges. the critic is another network, often as big as the policy network, doubling our memory and compute needs. for an llm with billions of parameters, this is a major issue—imagine needing twice the gpu memory! also, if the critic’s estimate is off, becomes misleading, leading to poor updates. training the critic is tough, especially in llm tasks where rewards are sparse, like in math problems where only the final answer gets a reward, and earlier steps get nothing. we need a way to compute advantages without a critic, as it’s too resource-heavy and error-prone.
proximal policy optimization (ppo): controlling policy changes
let’s look at ppo, a method that addresses the issue of large policy updates. ppo uses a trust region to ensure we don’t change the policy too much at once. the idea is to keep the new policy close to the old one, so we don’t risk a performance drop—like the robot suddenly changing its entire maze strategy and getting lost.
ppo compares the new policy to the old policy (before the update) using a probability ratio:
if , the new policy makes more likely; if , it makes less likely. a natural objective would be to maximize:
this approximates locally, because for small changes in , , and the gradient of this objective matches the policy gradient. but if gets too large or too small, the update can be too aggressive. let’s derive this issue. if , we want to increase , so grows. without a limit, could become very large, causing a huge update that overshoots and harms performance.
ppo introduces clipping to prevent this:
where is a clipping parameter (like 0.2). the function ensures stays between and (e.g., 0.8 to 1.2). if , we increase , but cap it at ; if , we decrease , but cap it at . the ensures we take the smaller of the two terms, limiting the update size.
to see why this works, consider the unclipped term . if and grows to, say, 10, the update would heavily favor , potentially ignoring other good actions. with clipping, if , we use instead, keeping the update bounded. this trust region approach makes ppo more stable than vanilla policy gradients.
but ppo still relies on a critic to compute , bringing back the same memory and accuracy issues. plus, for llms, generating samples and optimizing is already costly, and ppo’s critic and multiple epochs add to this burden. we need a method that avoids the critic, cuts compute costs, and maintains stability.
why grpo is the answer
let’s summarize the gaps in these methods and see how grpo fills them:
- critic overhead: ppo needs a critic, doubling memory needs. grpo gets rid of the critic by estimating advantages directly from the policy’s outputs.
- stability in llm training: llms have huge action spaces (e.g., a 50,000-token vocabulary), and big updates can lead to reward hacking—like generating gibberish that scores high. grpo uses clipping and a kl penalty to keep updates safe.
- variance without a critic: without a critic, we risk high variance, as in vanilla policy gradients. grpo reduces variance by sampling multiple outputs and normalizing their rewards.
- efficiency for reasoning: in llm reasoning tasks, like solving math problems, rewards are sparse—only the final answer matters. grpo’s group sampling fits this setup by comparing multiple answers to the same prompt.
imagine training an llm to solve math problems. instead of relying on a critic, you let the model generate several answers to the same problem, score them (like checking if the answer is correct), and use those scores to adjust the policy. grpo makes this process efficient and stable—let’s build it up.
building grpo: group sampling and advantages
for each prompt (the state , like a math question), grpo generates outputs using the current policy . each output is a sequence of tokens—think of as one complete answer to the math problem, made up of tokens , where is the sequence length.
scoring the outputs
we need to evaluate each output to guide the policy. since is a sequence, we score it token by token, considering the partial sequence up to each position. for the -th token in sequence , we compute a reward that combines a raw score from a reward model with a penalty to prevent the policy from straying too far.
let’s define the raw reward first. we use a reward model , which scores the partial sequence given the prompt . this could be, for example, a measure of how correct the partial answer is at step . in a math problem, if the partial sequence is leading toward the correct solution, might give a positive score; if it’s going off track, the score might be negative.
now, we don’t want the policy to deviate too much from a reference policy (often the initial supervised fine-tuned model), because large deviations can lead to reward hacking—where the model learns to exploit the reward model in unintended ways, like generating nonsense that somehow scores high. to control this, we add a penalty based on the kl divergence between and .
the kl divergence measures how much one probability distribution differs from another. for two policies in a given state, the kl divergence from to is:
in our case, the state at step is the prompt plus the tokens generated so far, . we’re interested in the action , the -th token. the kl divergence for this step would be:
we don’t compute the full kl divergence in practice, because summing over all possible actions (e.g., the entire vocabulary) is expensive. instead, we approximate its effect by penalizing the log ratio for the action that was actually taken:
this is the log of the ratio of probabilities under the two policies. if , the ratio is greater than 1, and the log is positive; if assigns a lower probability, the log is negative. we subtract this term, scaled by a coefficient (like 0.1), to penalize deviations from . so the per-token reward is:
if makes much more likely than , the log term is positive, and the penalty reduces the reward, discouraging large deviations. if makes less likely, the log term is negative, increasing the reward, but ensures this effect is controlled.
to get the total reward for the sequence , we sum over all tokens:
this gives us a reward for each output , balancing the quality of the sequence (via ) with a constraint on policy deviation (via the kl penalty).
computing advantages
now we have rewards for the outputs. we need to compute advantages to guide the policy update, but without a critic. grpo’s insight is to use the group of outputs to create a relative measure of performance.
imagine the outputs as different attempts at solving the same math problem. some answers might be correct (high reward), while others are wrong (low reward). we want to encourage the policy to favor the better attempts, but we need a baseline to compare against. instead of using a critic, grpo uses the group itself as the baseline by normalizing the rewards:
where is the average reward across the group:
and is the standard deviation:
let’s see why this normalization helps. without normalization, we’d use directly, but the raw rewards can vary widely depending on the task. for example, if rewards range from -10 to 10, the updates would be noisy, similar to the variance issue in vanilla policy gradients. by subtracting , we center the rewards around 0:
dividing by scales the values so the variance becomes 1:
this gives us a standardized measure: means performed better than the group average, and means it performed worse. for simplicity, we use the same advantage for all tokens in sequence , though some implementations compute per-token advantages. this group-relative approach reduces variance without needing a critic, as the group provides the context for comparison.
the grpo objective
now we use these advantages to update the policy, ensuring the updates are safe. grpo adopts a clipped objective similar to ppo:
where:
here, is the state at step , which in our case is , and is the token . the expectation is over all tokens in all sequences. the clipping with (like 0.2) ensures we don’t change the policy too much, just like in ppo, while guides the update based on the group-relative performance.
the grpo algorithm
let’s put it all together:
- generate outputs for each prompt using .
- compute rewards for each output by summing per-token rewards with the kl penalty.
- calculate advantages by normalizing the rewards.
- optimize using gradient ascent over a few epochs.
- set , and repeat.
wrapping up
grpo is a clever solution for llm training, removing the need for a critic, reducing variance through group sampling, and ensuring stability with clipping and kl penalties. it’s especially well-suited for reasoning tasks, making rl training more efficient and practical. if you have questions or want to dig deeper, let me know!