RLHF with PPO is the classic recipe for teaching a language model to produce responses people prefer: sample responses from the current model, score them with a reward model trained on human comparisons, and update the model with Proximal Policy Optimization while a KL penalty keeps it close to where it started. It is the third stage of the pipeline that produced InstructGPT: SFT first, a reward model second, PPO third. Most of what came after, from DPO to RLOO, ReMax and GRPO, is defined by which parts of this recipe it removes. Knowing what each part does tells you which simplification you can afford.
Where it came from#
PPO itself is a general RL algorithm, published by Schulman, Wolski, Dhariwal, Radford and Klimov in 2017 for robotics and Atari. Its contribution was a cheap way to take several gradient steps on the same batch of experience without the policy running away: clip the probability ratio between new and old policy so the objective stops rewarding changes beyond a small trust region.
The language-model application came from OpenAI's alignment team. Ziegler et al. (2019) fine-tuned GPT-2 with PPO against a reward model learned from human comparisons, and introduced the KL-shaped reward that every RLHF implementation still uses. Stiennon et al. (2020) scaled it to summarisation and showed policies trained this way beat larger supervised models in human evaluation. Ouyang et al. (2022) applied it to instruction following, where a 1.3B InstructGPT model was preferred by labelers over the 175B GPT-3 base. Anthropic's helpful-and-harmless assistant (Bai et al., 2022) and Llama 2 (Touvron et al., 2023) followed with their own PPO-based pipelines. The problem it solves is that "good response" has no closed-form definition. Supervised learning can only imitate demonstrations; RL against a learned reward lets the model explore and be graded on outputs nobody wrote.
The objective#
The KL-regularised RL problem
Let be the policy (the model being trained), the frozen SFT model, the reward model's score of response to prompt , and the KL coefficient. RLHF maximises
The KL term does two jobs. It stops the policy from drifting into text the reward model has never seen, where its scores are unreliable, and it preserves the fluency and knowledge of the SFT model. This objective has a known optimum, which DPO exploits and which we use to check our toy run below:
PPO does not compute (the sum over all responses is intractable); it climbs toward it by sampling.
Responses as token-level episodes
PPO treats generation as an episode. The state is the prompt plus the first tokens, the action is the next token, and the response ends at the end-of-sequence token at step . The KL penalty is spread over tokens using the per-token log-ratio, and the reward model's score arrives only at the last token:
Summed over the episode, the KL part is a single-sample estimate of the sequence-level KL. This shaping is what Ziegler et al. introduced, and it is why PPO for RLHF needs a value function: a reward that appears only at the end has to be credited back to the tokens that earned it.
Advantages with GAE
A value model predicts the expected remaining reward from each position. The temporal-difference error and the generalised advantage estimate (Schulman et al., 2015) are
with . is the discount (1.0 in almost every RLHF setup, since responses are short and finite) and trades bias for variance: gives the Monte Carlo return minus the baseline, trusts the value model completely. The value target is the return .
The clipped surrogate
With the probability ratio against the policy that generated the data, PPO maximises
and the value model minimises a (usually also clipped) squared error to . The minimum makes the objective pessimistic. When an action has positive advantage and its probability has already risen more than , the clipped term wins and its gradient is zero. When the change goes the wrong way, the unclipped term wins and the gradient pushes back at full strength. The policy gradient in the unclipped region is the familiar .
How the siblings simplify it
| Method | Advantage for token of sample | Networks in memory |
|---|---|---|
| PPO (this page) | from GAE with a learned | policy, value, reference, RM |
| RLOO | , same for all tokens | policy, reference, RM |
| ReMax | policy, reference, RM | |
| GRPO | , KL moved into the loss | policy, reference, RM or verifier |
| DPO | none: closed-form solution of the same objective on offline pairs | policy, reference |
Worked example by hand#
Take a four-token response with , , . The per-token log-ratios against the reference are 0.10, -0.05, 0.20 and 0.00, the reward model scores the full response at 1.0, and the value model predicts 0.5, 0.6, 0.4, 0.8. The shaped rewards are -0.010, +0.005, -0.020 and 1.000 (the RM score lands on the EOS token). TD errors work backwards from the end: ; ; ; . Accumulating with gives advantages 0.419, 0.347, 0.570, 0.200. Token 2 gets the largest credit because the value model under-predicted what followed it; token 1 gets less than its neighbours because its negative TD error says the value model was over-optimistic there.
Now the clip, with and . If an update has pushed the token's ratio to 1.3, the unclipped term is 2.6, the clipped term 2.4, and the minimum is 2.4: a constant in , so no gradient. That token has moved enough. If the ratio fell to 0.7 instead, the unclipped term 1.4 is smaller than the clipped 1.6, so the gradient flows and pulls the probability back up. With the cases mirror: a ratio of 1.3 keeps its gradient (objective -2.6), a ratio of 0.7 is clipped (objective -1.6, no gradient). Clipping only ever removes the incentive to move further in the direction the advantage already favours.
The algorithm#
inputs: SFT model π_sft, reward model r_φ, prompt set D
π_θ ← π_sft; π_ref ← π_sft (frozen); V_ψ ← r_φ backbone + new scalar head
repeat for N iterations:
# rollout phase (no gradients)
sample B prompts x from D
y ~ π_θ(·|x) with temperature 1.0, up to L_max tokens
logp_old = log π_θ(y_t | s_t) per token
logp_ref = log π_ref(y_t | s_t) per token
values = V_ψ(s_t) per token
score = r_φ(x, y) one per sequence (penalise if no EOS)
r_t = -β (logp_old - logp_ref); r_T += score
A_t, R_t = GAE(r_t, values, γ=1, λ=0.95); whiten A over the batch
# update phase
for epoch in 1..E (E = 1 to 4):
for minibatch in shuffle(rollouts):
ρ = exp(log π_θ(y_t|s_t) - logp_old)
L_pi = -mean(min(ρ A, clip(ρ, 1-ε, 1+ε) A))
L_v = 0.5 mean(max((V_ψ - R)^2, (clip(V_ψ, V_old ± ε_v) - R)^2))
step on L_pi + c_v L_v
optionally adapt β toward a KL target
Data. PPO needs prompts only; responses are generated online. Typical prompt sets run from 10k to a few hundred thousand, drawn from the same distribution the reward model was trained on. InstructGPT's PPO set was on the order of tens of thousands of prompts. A run of 1,000 iterations at 512 prompts each sees about half a million rollouts, so prompts are reused across epochs of the outer loop.
Implementation details that matter. Huang et al. (2024) reproduced the OpenAI summarisation results and catalogued the details that decide whether PPO works: score with the reward model at the EOS token and give a fixed penalty to responses that never emit EOS; normalise the reward model's outputs before RL so their scale is known; initialise the value model from the reward model; whiten advantages per batch; and use separate networks for policy and value. Zheng et al. (2023) reached similar conclusions in "Secrets of RLHF", singling out the token-level KL penalty and reward normalisation as the constraints that keep training stable.
Hyperparameters that matter#
| Knob | Starting value | What it controls |
|---|---|---|
| KL coefficient | 0.01 to 0.1 (or adaptive with a target KL of 3 to 10 nats per sequence) | How far the policy may leave the SFT model. The most important knob. |
| Clip | 0.2 | Trust region per update. Lower (0.1) if ratios blow up. |
| Policy learning rate | 1e-6 to 3e-6 (full), around 1e-5 (LoRA) | Step size. RLHF is far more sensitive here than SFT. |
| Value learning rate | Same or up to 5× the policy's | A lagging critic gives noisy advantages. |
| GAE , | 0.95, 1.0 | Bias vs variance of credit assignment. |
| PPO epochs E | 1 to 4 | Sample reuse. More epochs, more clipping, more instability. |
| Rollout batch | 256 to 1024 prompts, 4 to 16 minibatches | Gradient noise. |
| Sampling temperature | 1.0 | Must match the policy whose log-probs you use, or the ratio is wrong. |
| Value coefficient | 0.1 to 1.0 | Only matters if policy and value share parameters. |
Ziegler et al. used an adaptive controller instead of a fixed : after each batch, compute the error and set with a small gain such as 0.1. This holds KL roughly constant, which is often easier to reason about than a fixed coefficient whose effect depends on the reward model's scale. Tune in this order: fix and the learning rate on a short run while watching KL and clip fraction, then lengthen the run, then adjust batch size if curves are noisy.
Compute and memory#
PPO keeps four networks: the policy and value model are trained, the reference and reward model are inference-only. For full fine-tuning with Adam in mixed precision, a trained model costs about 16 bytes per parameter and a frozen bf16 model 2 bytes. With a policy of parameters and RM and value models of parameters each:
For a 7B policy with a 7B value and reward model that is about GB of model state before activations, which is why PPO at 7B usually means a multi-GPU node with ZeRO-3 sharding, and at 70B means multiple nodes. LoRA on the policy and value model cuts the trained-model term sharply, and some pipelines share a backbone between policy and value with two heads at the cost of interference between the losses.
Time is dominated by generation. Autoregressive sampling of long responses runs at a small fraction of training throughput, so in many setups the rollout phase takes the majority of wall-clock time. Modern stacks put generation on a dedicated inference engine (for example vLLM) and sync weights after each update. Relative to SFT on the same number of response tokens, a PPO run typically costs 5× to 20× more compute; relative to DPO on a comparable number of pairs, several times more, plus the reward model you had to train first.
A toy run: PPO against its closed-form target#
Because the KL-regularised objective has a known optimum, we can check what PPO actually converges to. We built a single-step "language model" over 50 possible responses with reference logits drawn from (reference entropy 3.55 nats), a true reward per response drawn from , and a proxy reward model equal to the truth plus Gaussian error whose standard deviation grows from 0.25 on common responses to 1.75 on responses the reference rarely produces, mimicking an RM that is unreliable off-distribution (correlation with truth 0.80). PPO used the shaped reward , a moving-average baseline, whitened advantages, clip 0.2, 4 epochs per batch of 64 samples, learning rate 0.3 on the logits, 400 iterations, seed 1.
| PPO final KL | PPO proxy reward | PPO true reward | KL | proxy | true | |
|---|---|---|---|---|---|---|
| 2.0 | 0.20 | 0.83 | 0.58 | 0.21 | 0.84 | 0.59 |
| 1.0 | 0.73 | 1.54 | 1.07 | 0.70 | 1.51 | 1.06 |
| 0.5 | 1.71 | 2.22 | 1.44 | 1.74 | 2.24 | 1.44 |
| 0.3 | 3.24 | 2.80 | 1.55 | 3.16 | 2.78 | 1.54 |
| 0.2 | 4.32 | 3.07 | 1.57 | 4.39 | 3.09 | 1.57 |
| 0.1 | 2.86 | 2.19 | 1.69 | 4.97 | 3.18 | 1.58 |
| 0.05 | 2.94 | 2.20 | 1.69 | 4.98 | 3.18 | 1.58 |
For , PPO lands within about 0.13 nats of the analytic optimum and within 0.08 of its proxy reward: the algorithm does solve the objective it is given. Two things in the table are worth more than that confirmation.
First, look at the gap between proxy and true reward. At it is 0.25; at it is 1.50. Going from 1.7 to 4.4 nats of KL buys 0.85 units of proxy reward and only 0.13 units of true reward. Most of the extra optimisation is spent exploiting the reward model's errors on rare responses. The chart shows the same thing along the whole frontier: proxy reward keeps climbing with KL while true reward flattens.
Second, at PPO does not reach the optimum. Within 80 iterations it collapsed onto a single response with proxy reward 2.2 and stayed there for the remaining 320; the optimum puts its mass on a different, rarer response with proxy reward 3.2. Once the policy is nearly deterministic, it stops sampling alternatives and has no way to discover them. That is entropy collapse, and in this toy the collapsed policy happened to have higher true reward by luck of the draw, which is a reminder that a run's final number can mislead in either direction. With a weak KL penalty, the policy you get depends on what it found first.
Failure modes and how to detect them#
Reward hacking. RM score rises smoothly while held-out win rate (human or LLM judge) stalls or falls. Responses grow longer, adopt a fixed template, or repeat flattering phrases. Plot judge win rate against KL, not steps, and stop where it flattens. Raise , or retrain the RM on samples from the current policy.
KL blow-up. KL jumps by several nats in a few iterations, often with the clip fraction (share of tokens where clipping is active) above 0.3 and the approximate KL between old and new policy per update above 0.05. Cause: learning rate too high, too many PPO epochs, or a sampling mismatch (temperature, top-p or a different tokenizer path between generator and trainer, which makes wrong from the first step). Check that averages 1.0 on the first minibatch of each iteration; if it doesn't, generator and trainer disagree.
Negative KL estimates. The per-token log-ratio estimator can go negative in expectation if responses are truncated or sampled with top-p, which the policy then exploits by producing tokens with low policy probability. Sample with pure temperature 1.0 during training.
Value model lag. Explained variance of the value predictions stays near zero or negative, and advantages look like noise. Increase the value learning rate, initialise the critic from the RM, or warm it up for a few iterations before updating the policy.
Entropy collapse. Per-token entropy drops steadily and samples for the same prompt become near-identical. Exploration stops and the run plateaus, as in the toy above. Watch entropy and the number of distinct responses per prompt; raise or lower the learning rate.
Length exploitation and truncation. Mean response length climbs until it hits the maximum and many responses end without EOS. Apply the missing-EOS penalty and track the fraction of truncated responses.
Capability regression. Benchmarks the SFT model was good at (math, code, factual QA) drop. InstructGPT's PPO-ptx variant mixes a pretraining language-modelling loss into the PPO update to counter this; a cheaper check is a fixed regression suite run every few hundred steps.
When to use PPO, and what to use instead#
| Situation | Recommendation |
|---|---|
| Open-ended quality (helpfulness, style) with a good RM and multi-node compute | PPO: token-level credit and on-policy exploration are worth the cost |
| Same, but memory-bound or you want fewer moving parts | RLOO or ReMax: drop the value model, often match PPO |
| Verifiable rewards (math answers, unit tests) | GRPO, DAPO or RLVR recipes |
| Fixed preference pairs, limited compute, no RM | DPO or its variants |
| Want online data without RL infrastructure | Online DPO or rejection sampling fine-tuning |
| Long reasoning traces where errors are mid-solution | PPO or GRPO with a process reward model |
| Small team, first preference-tuning project | Start with DPO; move to online RL once the RM and evals are trustworthy |
Minimal implementation sketch#
The core of a PPO update in PyTorch, given rollouts already generated and scored. TRL and OpenRLHF provide complete trainers with generation, sharding and logging; the loss below is what they compute.
import torch
def gae(rewards, values, mask, gamma=1.0, lam=0.95):
# response-only tensors [B, T], right-padded; mask is 1 on real tokens
B, T = rewards.shape
adv = torch.zeros_like(rewards); last = torch.zeros(B, device=rewards.device)
for t in reversed(range(T)):
alive = mask[:, t + 1] if t + 1 < T else torch.zeros(B, device=rewards.device)
next_v = values[:, t + 1] * alive if t + 1 < T else alive
delta = rewards[:, t] + gamma * next_v - values[:, t]
last = (delta + gamma * lam * alive * last) * mask[:, t]
adv[:, t] = last
returns = adv + values
m = mask.bool()
adv = (adv - adv[m].mean()) / (adv[m].std() + 1e-8) # whiten
return adv * mask, returns
def shaped_rewards(logp_old, logp_ref, score, mask, beta=0.05):
r = -beta * (logp_old - logp_ref) * mask
last = mask.sum(1).long() - 1 # index of final response token
r[torch.arange(r.size(0)), last] += score
return r
def ppo_loss(logp_new, logp_old, adv, v_new, v_old, returns, mask, eps=0.2, eps_v=0.2):
ratio = torch.exp(logp_new - logp_old)
pg = -torch.min(ratio * adv, torch.clamp(ratio, 1 - eps, 1 + eps) * adv)
v_clip = v_old + torch.clamp(v_new - v_old, -eps_v, eps_v)
vf = 0.5 * torch.max((v_new - returns) ** 2, (v_clip - returns) ** 2)
n = mask.sum()
stats = {"clipfrac": (((ratio - 1).abs() > eps).float() * mask).sum() / n,
"approx_kl": (0.5 * (logp_new - logp_old) ** 2 * mask).sum() / n}
return (pg * mask).sum() / n, (vf * mask).sum() / n, stats
Log at least: mean RM score, KL to reference per sequence, clip fraction, approximate KL per update, value explained variance, entropy, mean response length and the truncation rate. Those eight numbers diagnose nearly every failure in the previous section. In POFE these are the default panels on an RL run, next to a judge win-rate curve plotted against KL.
Further reading#
Schulman et al. (2017) for PPO and Schulman et al. (2015) for GAE. Ziegler et al. (2019) for the KL-shaped reward and adaptive KL controller. Stiennon et al. (2020) and Ouyang et al. (2022) for the full RLHF pipeline at scale. Huang et al. (2024) and Zheng et al. (2023) for the implementation details that separate working runs from broken ones. Ahmadian et al. (2024) for the argument that PPO's machinery is more than LLM fine-tuning needs, which leads to RLOO.
Frequently asked questions#
Why does RLHF need a KL penalty?
The reward model is only reliable near the distribution it was trained on. The KL penalty to the frozen SFT model limits how far the policy can drift into text where the reward model's scores are wrong, and preserves the SFT model's fluency and knowledge.
What are the four models in PPO-based RLHF?
The policy (trained), the value model or critic (trained), the frozen reference SFT model used for the KL penalty, and the frozen reward model that scores each response.
What KL coefficient beta should I use for RLHF?
Values between 0.01 and 0.1 are common, or an adaptive controller that targets a fixed per-sequence KL. Tune it by plotting judge or human win rate against KL and choosing the point where gains flatten.
Where does the reward go in token-level PPO for language models?
Each token receives a negative KL term, -beta times the log-ratio of policy to reference probability, and the reward model's score is added only at the final token. GAE then distributes credit backwards using the value model.
Is PPO better than DPO?
PPO is online and can exploit a reward model on fresh samples, which often helps on open-ended tasks with a strong reward model. It costs several times more compute and engineering. DPO is the better first choice with fixed preference pairs and limited compute.
How much GPU memory does RLHF with PPO need?
Roughly 16 bytes per parameter for each trained model (policy and value) plus 2 bytes per parameter for each frozen model, plus activations and generation cache. A 7B policy with 7B value and reward models needs about 250 GB of model state.
References#
- John Schulman, Filip Wolski, Prafulla Dhariwal, Alec Radford, Oleg Klimov (2017). Proximal Policy Optimization Algorithms. arxiv.org/abs/1707.06347
- John Schulman, Philipp Moritz, Sergey Levine, Michael Jordan, Pieter Abbeel (2015). High-Dimensional Continuous Control Using Generalized Advantage Estimation. arxiv.org/abs/1506.02438
- Daniel M. Ziegler, Nisan Stiennon, Jeffrey Wu, Tom B. Brown, et al. (2019). Fine-Tuning Language Models from Human Preferences. arxiv.org/abs/1909.08593
- Nisan Stiennon, Long Ouyang, Jeff Wu, Daniel M. Ziegler, et al. (2020). Learning to summarize from human feedback. arxiv.org/abs/2009.01325
- Long Ouyang, Jeff Wu, Xu Jiang, Diogo Almeida, et al. (2022). Training language models to follow instructions with human feedback. arxiv.org/abs/2203.02155
- Yuntao Bai, Andy Jones, Kamal Ndousse, Amanda Askell, et al. (2022). Training a Helpful and Harmless Assistant with Reinforcement Learning from Human Feedback. arxiv.org/abs/2204.05862
- Hugo Touvron, Louis Martin, Kevin Stone, Peter Albert, et al. (2023). Llama 2: Open Foundation and Fine-Tuned Chat Models. arxiv.org/abs/2307.09288
- Shengyi Huang, Michael Noukhovitch, Arian Hosseini, Kashif Rasul, Weixun Wang, Lewis Tunstall (2024). The N+ Implementation Details of RLHF with PPO: A Case Study on TL;DR Summarization. arxiv.org/abs/2403.17031
- Rui Zheng, Shihan Dou, Songyang Gao, Yuan Hua, et al. (2023). Secrets of RLHF in Large Language Models Part I: PPO. arxiv.org/abs/2307.04964
- Arash Ahmadian, Chris Cremer, Matthias Gallé, Marzieh Fadaee, et al. (2024). Back to Basics: Revisiting REINFORCE Style Optimization for Learning from Human Feedback in LLMs. arxiv.org/abs/2402.14740