PPO for Language Models › PPO Objective for Language Models

Michael BrenndoerferDecember 28, 20259 min read
Language AI HandbookAll chapters
Part 37 of 67Alignment and RLHF
Chapter 279PPO for Language Models › PPO Objective for Language Models
Reading section 7 of 15 · PPO Objective for Language Models · 9 minSection 7 of 15 · 9 min

Book contents

Section 7 of 15 · 9 min read

PPO for Language Models › PPO Objective for Language ModelsOne section · Sections in this chapter7 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

The PPO objective for language models combines the clipped surrogate loss from standard PPO with the KL-shaped reward structure we just described. Understanding how these pieces fit together reveals why PPO works for alignment: it maximizes expected reward while maintaining two separate safeguards against instability. The KL penalty prevents the policy from drifting too far from the reference in absolute terms, while the clipping mechanism prevents any single update step from moving too far from the data collection policy.

Think of the full PPO objective as a three-way balancing act. The reward signal pulls the policy toward higher-scoring behaviors. The KL penalty pulls it back toward the reference model. The clipping mechanism ensures that each individual gradient step stays within a safe neighborhood. These three forces together create a training dynamic that is far more stable than any one of them alone.

Combining all these elements, the PPO objective for language models takes the following form. For a batch of prompt-response pairs, we compute:

LPPO(θ)=E(x,y)∼πθold[∑t=1Tmin⁡(ρt(θ)A^t,clip(ρt(θ),1−ϵ,1+ϵ)A^t)]\mathcal{L}_{\text{PPO}}(\theta) = \mathbb{E}_{(x,y) \sim \pi_{\theta_{\text{old}}}} \left[ \sum_{t=1}^{T} \min\left( \rho_t(\theta) \hat{A}_t, \text{clip}(\rho_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right]

where:

  • θ\theta: the parameters of the language model being optimized
  • E(x,y)∼πθold\mathbb{E}_{(x,y) \sim \pi_{\theta_{\text{old}}}}: the expectation over trajectories generated by the policy that collected the data
  • ρt(θ)\rho_t(\theta): the probability ratio between the current and old policies at step tt
  • A^t\hat{A}_t: the estimated advantage at step tt, showing how much better the chosen action was compared to the average
  • TT: the length of the generated sequence
  • ϵ\epsilon: the clipping parameter (typically 0.1 or 0.2) that defines the trust region

The min operator serves a important purpose in this objective. It takes the conservative lower bound between the unclipped and clipped objectives. This keeps updates cannot be too aggressive. If the advantage is positive, showing that the chosen action was better than expected, clipping limits how much we increase the probability of the action. If the advantage is negative, showing the action was worse than expected, clipping limits how much we decrease the probability. This prevents large updates that could collapse the policy.

The Probability Ratio

The probability ratio ρt(θ)\rho_t(\theta) is the core mechanism that allows PPO to reuse trajectory data across multiple gradient steps. Without it, policy gradient methods can only take one gradient step per batch of data, making them extremely sample-inefficient. With the ratio, we can check whether the current policy has drifted too far from the data-collection policy and clip the gradient accordingly.

The ratio is defined as:

ρt(θ)=πθ(yt∣x,y<t)πθold(yt∣x,y<t)\rho_t(\theta) = \frac{\pi_\theta(y_t | x, y_{<t})}{\pi_{\theta_{\text{old}}}(y_t | x, y_{<t})}

where:

  • πθ(yt∣x,y<t)\pi_\theta(y_t | x, y_{<t}): the probability of the token yty_t under the current policy (being updated)
  • πθold(yt∣x,y<t)\pi_{\theta_{\text{old}}}(y_t | x, y_{<t}): the probability of the same token under the policy that collected the data (frozen for this update step)
  • yty_t: the token generated at step tt
  • xx: the input prompt
  • y<ty_{<t}: the sequence of tokens generated prior to step tt

This ratio measures how the policy has changed since the data was collected. A ratio of 1.0 means the policy assigns exactly the same probability as before. A ratio greater than 1 means the policy now assigns higher probability to this token. A ratio less than 1 means the probability has decreased. By basing updates on this ratio rather than directly on log probabilities, PPO can perform multiple gradient steps on the same batch of data without the policy drifting too far from the data-collection policy.

The key insight here is why we use the ratio rather than log probabilities directly. In standard REINFORCE, we would multiply the log probability by the advantage and take a gradient step. This works for a single step but fails for multiple steps: after updating the policy, the gradient computed from the old trajectories is no longer valid because the old trajectories were generated by a different policy. The ratio corrects for this by weighting each token's contribution by how much the current policy differs from the data-collection policy.

Advantage Estimation with GAE

The advantage A^t\hat{A}_t is estimated using Generalized Advantage Estimation (GAE) applied to the KL-shaped rewards. GAE is a technique that balances the bias-variance tradeoff in advantage estimation by combining multi-step returns through exponential weighting:

A^t=∑l=0T−t(γλ)lδt+l\hat{A}_t = \sum_{l=0}^{T-t} (\gamma \lambda)^l \delta_{t+l}

where:

  • γ\gamma: the discount factor (usually near 1.0 for RLHF)
  • λ\lambda: the GAE smoothing parameter (typically 0.95)
  • TT: the length of the generated sequence
  • δt+l\delta_{t+l}: the temporal difference (TD) error at step t+lt+l

This summation calculates an exponentially weighted average of future TD errors. The parameter λ\lambda controls the bias-variance trade-off: higher values rely more on observed returns (reducing bias but increasing variance), while lower values rely more on value estimates (reducing variance but potentially introducing bias from imperfect value predictions). Setting λ=1\lambda = 1 recovers Monte Carlo returns, while λ=0\lambda = 0 gives pure one-step TD learning. The typical value of 0.95 balances these considerations.

The TD error δt\delta_t measures the surprise at each step: the difference between the observed reward plus the estimated value of the next state, and the estimated value of the current state:

δt=r~t+γV(st+1)−V(st)\delta_t = \tilde{r}_t + \gamma V(s_{t+1}) - V(s_t)

where:

  • r~t\tilde{r}_t: the KL-shaped reward at step tt
  • γ\gamma: the discount factor
  • V(st)V(s_t): the value function estimate for state sts_t (how good is the current context expected to be?)
  • V(st+1)V(s_{t+1}): the value function estimate for the next state (defined as 0 if t=Tt=T)

The TD error captures whether the transition was better or worse than expected. If the reward plus next-state value exceeds the current-state value, the transition was unexpectedly good, and the TD error is positive. If the sum falls short, the transition was unexpectedly bad, and the TD error is negative. These signals, accumulated through GAE, produce advantage estimates that indicate which actions led to better outcomes than the value function predicted.

In[17]:
Code
import numpy as np


def compute_gae(rewards, values, gamma=1.0, lam=0.95):
    """
    Compute Generalized Advantage Estimation.

    Args:
        rewards: Per-token (shaped) rewards
        values: Value function estimates for each state
        gamma: Discount factor
        lam: GAE lambda parameter

    Returns:
        advantages: GAE advantage estimates
        returns: Target returns for value function
    """
    T = len(rewards)
    advantages = np.zeros(T)
    last_gae = 0

    # Assume terminal state has value 0
    next_value = 0

    for t in reversed(range(T)):
        delta = rewards[t] + gamma * next_value - values[t]
        advantages[t] = last_gae = delta + gamma * lam * last_gae
        next_value = values[t]

    returns = advantages + values
    return advantages, returns


def compute_ppo_loss(
    log_probs_new, log_probs_old, advantages, clip_epsilon=0.2
):
    """
    Compute clipped PPO policy loss.

    Args:
        log_probs_new: Log probs under current policy
        log_probs_old: Log probs under policy that collected data
        advantages: Advantage estimates (should be normalized)
        clip_epsilon: Clipping parameter

    Returns:
        PPO policy loss (to be maximized/negated for minimization)
    """
    # Probability ratio
    ratio = np.exp(log_probs_new - log_probs_old)

    # Clipped ratio
    clipped_ratio = np.clip(ratio, 1 - clip_epsilon, 1 + clip_epsilon)

    # PPO surrogate objectives
    surrogate1 = ratio * advantages
    surrogate2 = clipped_ratio * advantages

    # Take minimum (conservative update)
    loss = np.minimum(surrogate1, surrogate2)

    return loss.mean(), ratio, clipped_ratio
In[18]:
Code
# Demonstrate PPO loss computation
np.random.seed(123)

# Simulate a trajectory
seq_len = 5
shaped_rewards = np.array([0.02, -0.05, 0.03, -0.01, 0.85])  # KL-shaped
values = np.array([0.4, 0.5, 0.45, 0.6, 0.7])  # Value estimates

# Compute advantages
advantages, returns = compute_gae(shaped_rewards, values)

# Normalize advantages
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

# Log probabilities (old policy collected data, new policy being optimized)
log_probs_old = np.array([-1.2, -0.9, -1.5, -1.1, -0.8])
log_probs_new = np.array([-1.1, -0.85, -1.6, -1.0, -0.75])

# Compute loss
loss, ratio, clipped_ratio = compute_ppo_loss(
    log_probs_new, log_probs_old, advantages
)
Out[19]:
Console
PPO Loss Computation:
--------------------------------------------------
Advantages (normalized): [ 1.022 -0.099  1.171 -0.611 -1.484]
Probability ratios: [1.105 1.051 0.905 1.105 1.051]
Clipped ratios: [1.105 1.051 0.905 1.105 1.051]

PPO loss (to maximize): -0.0299
Out[20]:
Visualization
Line chart showing PPO objective versus probability ratio for positive advantage, with a plateau above 1.2 showing trust region clipping.
PPO clipping under positive advantage: the objective rises linearly with the probability ratio but flattens once the ratio exceeds 1 plus epsilon. This ceiling prevents the algorithm from over-reinforcing already-likely actions simply because the ratio suggests doing so would improve the surrogate objective.
Line chart showing PPO objective versus probability ratio for negative advantage, with a floor below 0.8 showing trust region clipping.
PPO clipping under negative advantage: the objective falls linearly but flattens once the ratio drops below 1 minus epsilon. This floor prevents excessive suppression of actions that have only moderate negative advantage, protecting against overly aggressive updates.

The positive advantage at the final step reflects the high reward, while negative values indicate steps that yielded lower-than-expected value. The probability ratios stay within the trust region defined by the clipping parameter. This shows how PPO maintains stability. When a ratio exceeds 1+ϵ1 + \epsilon or falls below 1−ϵ1 - \epsilon, the clipped ratio takes over, preventing the gradient from pushing the policy further in that direction.