PPO Algorithm › Worked Example: Tracing a Single PPO Update

Michael BrenndoerferDecember 27, 20254 min read
Language AI HandbookAll chapters
Part 37 of 67Alignment and RLHF
Chapter 278PPO Algorithm › Worked Example: Tracing a Single PPO Update
Reading section 7 of 14 · Worked Example: Tracing a Single PPO Update · 4 minSection 7 of 14 · 4 min

Book contents

Section 7 of 14 · 4 min read

PPO Algorithm › Worked Example: Tracing a Single PPO UpdateOne section · Sections in this chapter7 / 14 ↓
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

Before implementing PPO in code, let's trace through one complete update step numerically. This example uses tiny numbers to keep the arithmetic tractable, but the procedure is identical for million-parameter networks.

Suppose we have collected a short trajectory with three timesteps. The states, actions, rewards, and value estimates are:

TimestepStateActionRewardV(st)V(s_t)log⁡πold(at∣st)\log \pi_{\text{old}}(a_t \mid s_t)
0s0s_0a0a_01.00.8-0.693
1s1s_1a1a_10.00.5-1.386
2s2s_2a2a_22.01.2-0.405

The episode terminates after timestep 2, so V(s3)=0V(s_3) = 0. We use γ=0.99\gamma = 0.99 and λ=0.95\lambda = 0.95.

Step 1: Compute TD errors. Working forward through the trajectory:

δ0=r0+γV(s1)−V(s0)=1.0+0.99×0.5−0.8=0.695δ1=r1+γV(s2)−V(s1)=0.0+0.99×1.2−0.5=0.688δ2=r2+γV(s3)−V(s2)=2.0+0.99×0.0−1.2=0.800\begin{aligned} \delta_0 &= r_0 + \gamma V(s_1) - V(s_0) = 1.0 + 0.99 \times 0.5 - 0.8 = 0.695 \\ \delta_1 &= r_1 + \gamma V(s_2) - V(s_1) = 0.0 + 0.99 \times 1.2 - 0.5 = 0.688 \\ \delta_2 &= r_2 + \gamma V(s_3) - V(s_2) = 2.0 + 0.99 \times 0.0 - 1.2 = 0.800 \end{aligned}

Step 2: Compute GAE advantages. Working backwards through the trajectory, initializing A^3=0\hat{A}_3 = 0:

A^2=δ2+γλA^3=0.800+0.99×0.95×0=0.800A^1=δ1+γλA^2=0.688+0.99×0.95×0.800=0.688+0.752=1.440A^0=δ0+γλA^1=0.695+0.99×0.95×1.440=0.695+1.354=2.049\begin{aligned} \hat{A}_2 &= \delta_2 + \gamma \lambda \hat{A}_3 = 0.800 + 0.99 \times 0.95 \times 0 = 0.800 \\ \hat{A}_1 &= \delta_1 + \gamma \lambda \hat{A}_2 = 0.688 + 0.99 \times 0.95 \times 0.800 = 0.688 + 0.752 = 1.440 \\ \hat{A}_0 &= \delta_0 + \gamma \lambda \hat{A}_1 = 0.695 + 0.99 \times 0.95 \times 1.440 = 0.695 + 1.354 = 2.049 \end{aligned}

The advantage at t=0t=0 is largest because the trajectory begins with a low value prediction (0.8) but transitions to states with higher-than-expected cumulative reward.

Step 3: Normalize advantages. Standard PPO normalizes advantages to have zero mean and unit variance. The mean is (2.049+1.440+0.800)/3=1.430(2.049 + 1.440 + 0.800) / 3 = 1.430. The variance is approximately 0.5330.533, giving standard deviation ≈0.730\approx 0.730. Normalizing:

A^0norm=(2.049−1.430)/0.730=0.848A^1norm=(1.440−1.430)/0.730=0.014A^2norm=(0.800−1.430)/0.730=−0.863\begin{aligned} \hat{A}_0^{\text{norm}} &= (2.049 - 1.430) / 0.730 = 0.848 \\ \hat{A}_1^{\text{norm}} &= (1.440 - 1.430) / 0.730 = 0.014 \\ \hat{A}_2^{\text{norm}} &= (0.800 - 1.430) / 0.730 = -0.863 \end{aligned}

Step 4: Compute probability ratios. After one gradient step, suppose the new policy has log-probabilities: log⁡πθ(a0∣s0)=−0.511\log \pi_\theta(a_0 \mid s_0) = -0.511, log⁡πθ(a1∣s1)=−1.204\log \pi_\theta(a_1 \mid s_1) = -1.204, log⁡πθ(a2∣s2)=−0.693\log \pi_\theta(a_2 \mid s_2) = -0.693.

r0=exp⁡(−0.511−(−0.693))=exp⁡(0.182)=1.20r1=exp⁡(−1.204−(−1.386))=exp⁡(0.182)=1.20r2=exp⁡(−0.693−(−0.405))=exp⁡(−0.288)=0.75\begin{aligned} r_0 &= \exp(-0.511 - (-0.693)) = \exp(0.182) = 1.20 \\ r_1 &= \exp(-1.204 - (-1.386)) = \exp(0.182) = 1.20 \\ r_2 &= \exp(-0.693 - (-0.405)) = \exp(-0.288) = 0.75 \end{aligned}

Step 5: Apply clipping with ϵ=0.2\epsilon = 0.2. The clip bounds are [0.8,1.2][0.8, 1.2].

For t=0t=0: r0=1.20r_0 = 1.20, A0norm=0.848A_0^{\text{norm}} = 0.848 (positive advantage). The unclipped term is 1.20×0.848=1.0181.20 \times 0.848 = 1.018. The clipped ratio is min⁡(1.20,1.20)=1.20\min(1.20, 1.20) = 1.20, so the clipped term is also 1.20×0.848=1.0181.20 \times 0.848 = 1.018. Both terms equal, so L0=1.018L_0 = 1.018. Note that r0=1+ϵr_0 = 1 + \epsilon exactly: the ratio is right at the boundary.

For t=1t=1: r1=1.20r_1 = 1.20, A1norm=0.014A_1^{\text{norm}} = 0.014 (small positive). Similarly L1=1.20×0.014=0.017L_1 = 1.20 \times 0.014 = 0.017.

For t=2t=2: r2=0.75r_2 = 0.75, A2norm=−0.863A_2^{\text{norm}} = -0.863 (negative advantage). The unclipped term is 0.75×(−0.863)=−0.6470.75 \times (-0.863) = -0.647. The clipped ratio clips 0.75 to 0.8, giving clipped term 0.8×(−0.863)=−0.6900.8 \times (-0.863) = -0.690. The min⁡(−0.647,−0.690)=−0.690\min(-0.647, -0.690) = -0.690 (more negative wins). So L2=−0.690L_2 = -0.690.

Here, r2=0.75<1−ϵ=0.8r_2 = 0.75 < 1 - \epsilon = 0.8, so clipping activates and the clipped objective is flatter (less negative) than the unclipped one. This means the optimizer does not receive a gradient signal pushing r2r_2 below 0.8 further, preventing excessive suppression of action a2a_2.

Step 6: Average the objective. LCLIP=(1.018+0.017+(−0.690))/3=0.115L^{\text{CLIP}} = (1.018 + 0.017 + (-0.690)) / 3 = 0.115.

This positive value indicates the gradient step improved the objective. On subsequent passes through the same data (multiple epochs), the ratios will update further, and clipping will activate when the policy has changed enough.