PPO can look like one large optimization algorithm from the outside. Inside an Isaac Lab run, it is easier to understand as two alternating phases: collect a rectangle of experience from many robot worlds, then revisit that fixed rectangle in shuffled mini-batches to improve the policy.
The shape to remember
24 environment steps × 4,096 parallel environments = 98,304 transitions. That rectangle is one rollout. PPO computes targets over it, learns from it, then starts a fresh one.
This article follows Isaac Lab develop on September 1, 2026, which pins RSL-RL 5.4.1. Its Unitree Go2 rough-terrain configuration uses 4,096 environments, 24 rollout steps, four mini-batches, and five learning epochs. The dimensions are representative, not requirements. Change the two collection settings below to see how the batch grows.
One PPO iteration, step by step
One iteration · the complete data path
Collect → build targets → learn → repeat
- Collect01Observe
[N, O] - Collect02Act + step
a, V, r, d - Collect03Store
[T, N, …] - Targets04GAE
A, R - Learn05Sample
[M, …] - Learn06Optimize
θ → θ′ - Repeat07Fresh rollout
cursor → 0
Interactive walkthrough · trace every tensor
Open one stage at a time
01 · ObservationManager
Turn N worlds into model-ready rows
Isaac Lab computes every configured observation term across all environments at once, processes each term, and concatenates terms along the feature axis. One row belongs to one environment slot.
- Input
- simulator state + commands
- Transform
- compute → modify → noise → clip → scale
- Output
TensorDict · batch [N]- Invariant
- one shared network, N rows
base velocityjoint statecommandsprevious action[N, Opolicy]
[N, Opolicy]
optional asymmetric setups use a separate privileged critic group
N; observation terms only change the feature width O.02 · Actor, critic, environment
Cache first, then advance physics
The actor samples one action row per environment and the critic predicts one value. PPO freezes that pre-step snapshot before Isaac Lab transforms actions, runs four physics ticks, computes feedback, and resets only finished slots.
- Input
obst · [N, O]- Policy
at · [N, A]- Critic
Vt · [N, 1]- Weights
- fixed for all T collection steps
[N, O]
at [N,A]
also μ, σ and summed log πold
Vt [N,1]
obst+1 is already the reset-state observation. The stored action remains the actor’s original sample, not the optional action-clamped tensor.03 · RolloutStorage
Complete one time row, then move the cursor
process_env_step() attaches post-step reward and done to the cached snapshot. After 24 rows, storage contains 98,304 transitions arranged time-major.
- Before step
- obs, action, value, old policy stats
- After step
- reward + done
- One row
[N, …]- Full rollout
[T, N, …]
obs [N,O]action [N,A]V [N,1]log π [N]μ, σ [N,A]reward [N]done [N]scalar fields reshape to [N,1]
[T,N] leading axes. RSL-RL does not allocate a separate next_obs tensor.04 · Bootstrap + GAE
Walk backward without crossing resets
The critic evaluates the final observation once. GAE then scans from T−1 to 0, carrying later evidence backward until a done mask cuts the chain.
- Bootstrap
V(obsT) · [N,1]- Direction
t = T−1 … 0- Actor target
A · [T,N,1]- Critic target
R · [T,N,1]
[N,O][N,1]δt = rt + γ(1−dt)Vt+1 − VtAt = δt + γλ(1−dt)At+1Rt = At + Vt[T,N,1][T,N,1]Vt+1 comes from the next stored value, except at the last row where VT is used. Timeout rewards are already corrected, but the recursion is still masked.05 · Flatten + shuffle
Change the view, not the data
For a feed-forward policy, every field needed by the PPO update is flattened in the same order, indexed by one random permutation, and split into four equal mini-batches. RSL-RL reuses that partition for five learning epochs.
- Before
[T, N, …]- Flatten
[B, …] · B = T × N- Mini-batch
[M, …] · M = B ÷ 4- Reuse
- 4 batches × 5 epochs = 20 updates
[24,576, …][24,576, …][24,576, …][24,576, …]- epoch 1
- epoch 2
- epoch 3
- epoch 4
- epoch 5
obs [M,O]action [M,A]old V / log π / A / R [M,1]old μ, σ [M,A]
B is not divisible by four, the remainder is omitted; this Go2 batch divides exactly.06 · PPO update
Re-score stored actions, then update once
The current networks evaluate the exact actions collected earlier. PPO compares current and frozen behavior probabilities, combines actor, critic, and entropy terms, clips gradients, and runs one optimizer step per mini-batch.
- Frozen
- action, old log π, old V, A, R, μ, σ
- Recomputed
- new log π, value, entropy, μ, σ
- Clip
- objective incentive at
1 ± ε - Output
- updated actor θ′ + critic φ′
obsstored actionlog πoldVoldARlog πθVφentropyμθ, σθr = exp(log πθ − log πold) = 1.28
07 · Fresh on-policy data
Keep the weights, overwrite the rollout
The actor and critic were updated in place. Storage resets only its write cursor, unfinished episodes continue from obsT, and the next act() starts a fresh rollout with the new policy.
- Keep
- θ′, φ′, obsT, allocated tensors
- Reset
- storage write cursor → 0
- Do not reset
- all environments globally
- Replace
- old rollout with fresh on-policy data
- 1act with θ′one batched policy
- 2collect T new steps4,096 continuing world slots
- 3fill fresh rollout98,304 transitions
- 4run 20 updatesθ′ → θ″, φ′ → φ″
Observe · use the stage tabs or arrow keys
The important separation is temporal. During collection, RSL-RL runs the actor and critic under inference mode while Isaac Lab advances the world. During learning, the simulator stops advancing while the optimizer revisits the fixed rollout. Network weights do not change halfway through a rollout.
The collection phase in code
The current on-policy runner reduces to this shape:
obs = env.get_observations()
for t in range(num_steps_per_env):
actions = ppo.act(obs) # caches obs, action, V, old log π
next_obs, reward, done, extras = env.step(actions)
ppo.process_env_step(next_obs, reward, done, extras)
obs = next_obs
ppo.compute_returns(obs) # bootstrap + backward GAE
ppo.update() # shuffled mini-batches, then clear cursor
There is a subtle but useful detail in that ordering. The transition is assembled across two calls. act() records what was known before physics: obst, at, Vt, the old action log-probability, and the action distribution parameters. After Isaac Lab steps, process_env_step() attaches rt and dt and copies the finished record into time row t.
Isaac Lab resets ended environment slots before returning the next observation. For a slot whose done flag is true, obst+1 is already the initial state of its next episode. The done mask is therefore essential when GAE runs backward.
What is actually in the rollout buffer?
With N environments, T rollout steps, action width A, and observation-group width Og, the core tensors are:
| Field | Shape | Why PPO keeps it |
|---|---|---|
Observation group g |
[T, N, Og] |
Re-run actor and critic during learning |
| Sampled action | [T, N, A] |
Score the exact behavior action again |
| Reward, done | [T, N, 1] |
Build return and stop credit at episode boundaries |
| Old value | [T, N, 1] |
Compute advantage and optionally clip value updates |
| Old action log-probability | [T, N, 1] |
Form the PPO probability ratio |
| Old Gaussian mean and standard deviation | [T, N, A] |
Measure analytic KL for adaptive learning rate |
| Return, advantage | [T, N, 1] |
Critic target and actor learning signal |
This structure is usually called RolloutStorage rather than a replay buffer for a reason. It holds one fixed-horizon, on-policy batch. After the PPO update, only the write cursor is reset, and the next rollout overwrites the old one.
GAE turns rewards into a learning signal
Rewards alone do not say whether one action was surprisingly good given the state. The critic supplies a baseline. RSL-RL computes a temporal-difference residual and then accumulates it backward:
TD residualδt = rt + γ(1 − dt)Vt+1 − Vt
AdvantageAt = δt + γλ(1 − dt)At+1
Return targetRt = At + Vt
The γ term discounts distant outcomes. λ controls how much later evidence flows backward: lower values lean toward low-variance one-step estimates; higher values use longer credit chains. Before optimization, advantages are normalized over the rollout by default.
Timeouts need separate care. For a slot marked time_outs, RSL-RL v5.4.1 applies rt ← rt + γVt before storage, using the value cached before the step—not a value of a terminal observation. The done mask remains set, so the backward GAE recursion still cannot cross the reset boundary.
Sampling changes the view, not the data
The rollout rectangle is time-major because that makes collection and GAE natural. A feed-forward policy does not need that structure during the gradient update, so RSL-RL flattens [T, N, …] to [B, …], where B = T × N, then selects a random permutation.
For the Go2 example:
- Rollout:
24 × 4,096 = 98,304transitions - Mini-batch:
98,304 ÷ 4 = 24,576transitions - Optimizer steps:
4 mini-batches × 5 epochs = 20 - Reuse: every transition participates once per epoch, then is discarded after the update
The current feed-forward generator creates one permutation and reuses that partition across the learning epochs. If the batch is not divisible by the mini-batch count, the remainder is omitted; common Isaac Lab configurations choose divisible sizes. Recurrent policies cannot freely flatten across resets, so their generator preserves padded trajectory fragments and accompanying masks.
What PPO clips—and what it does not
For every mini-batch, the actor calculates a new log-probability for the action that the old policy actually sampled. Their difference becomes a ratio:
rt(θ) = exp(log πθ(at|st) − log πold(at|st))
r = 1 means the action is just as likely now. PPO limits the benefit of moving this ratio beyond 1 ± ε; it does not simply clamp every gradient or replace the sampled action.
The minimized training loss combines three signals:
- Clipped surrogate loss: improve probability for positive-advantage actions and reduce it for negative-advantage actions, without earning extra objective improvement for moving too far.
- Value loss: move the critic toward
Rt; the Go2 configuration used here enables clipped value loss. - Entropy bonus: resist collapsing the Gaussian action distribution too quickly.
After backpropagation, actor and critic gradients are norm-clipped and the optimizer updates both networks. An optional adaptive schedule compares the old and current distributions with analytic KL and changes the learning rate; it does not stop the epoch early.
Three details that make the mental model click
The old policy is stored as numbers
Thousands of Isaac Lab environments do not each own a policy copy. One actor handles the whole [N, …] batch. PPO’s behavior-policy reference comes from the stored old log-probabilities and distribution parameters. The same actor module is updated in place before the next rollout.
“Done” belongs to the transition that just ended
Isaac Lab steps, computes reward and termination, resets ended slots, and only then computes the returned observation. Storage keeps the pre-step observation and the post-step reward/done pair; it does not need a second full observation tensor for every transition.
Action clipping is a different clip
PPO’s ratio clip constrains the learning objective. If the Isaac Lab RSL-RL wrapper is also configured to clip actions, that clamp happens on the tensor sent to the environment. The stored action and its log-probability still describe the actor’s original sample.
Takeaway
One PPO iteration in Isaac Lab is a disciplined change of tensor layout:
- Isaac Lab produces observation groups shaped by parallel environments.
- The actor and critic add action, value, and old-policy statistics.
- Environment feedback completes one row in
[T, N, …]rollout storage. - A final value bootstrap and backward GAE create returns and advantages.
- Feed-forward training flattens to
[T × N, …]and selects mini-batches. - PPO revisits the stored actions, clips the actor’s optimization incentive, updates actor and critic, and discards the rollout.
The simulator makes experience wide; time makes it tall; GAE makes it useful; mini-batch sampling makes it trainable. Then the updated policy goes back to the worlds and earns the next rectangle.
Sources and further reading
- Isaac Lab’s RSL-RL 5.4.1 dependency pin at this source snapshot
- Isaac Lab ObservationManager
- Isaac Lab manager-based environment step
- Isaac Lab’s RSL-RL vectorized wrapper
- Go2 PPO runner configuration
- Default 4,096-environment velocity scene
- RSL-RL v5.4.1 on-policy runner
- RSL-RL v5.4.1 rollout storage
- RSL-RL v5.4.1 PPO implementation
- Original PPO paper