Apply It — SAC vs PPO on JumperHard
Time
Reading: ~10 min · Training: ~20 min GPU / ~1 h CPU per algorithm
Three ways to see your AI
Godot (does SAC's policy look smoother than PPO's?) · TensorBoard (compare ep_rew_mean slopes side by side) · sample efficiency (how many env steps to reach the same reward?)
Prerequisites
1 · Why swap?
You've trained JumperHard with PPO in Unit 4 and read the SAC theory in the previous unit. Time to see the difference for yourself on a Godot env you already know.
PPO is on-policy: it samples a batch with the current policy, takes a few gradient steps on it, throws the batch away, and samples again. Simple, robust, easy to parallelise. SAC is off-policy: every transition goes into a replay buffer and gets reused for many gradient updates. On JumperHard's continuous action space, SAC's sample efficiency can dominate PPO — but each gradient step is more expensive, and the replay buffer eats RAM.
The point of this interlude is not to pick a winner. It's to feel the tradeoff on a project you already have running.
2 · Edit the training script
Open the Python training script you used for JumperHard in Unit 4 (train_jumperhard.py or whatever you named it). The PPO version looks roughly like this:
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import VecMonitor
from godot_rl.wrappers.stable_baselines_wrapper import StableBaselinesGodotEnv
env = StableBaselinesGodotEnv(env_path="JumperHard.x86_64", show_window=False)
env = VecMonitor(env)
model = PPO(
"MlpPolicy", env,
learning_rate=3e-4,
n_steps=2048,
batch_size=64,
n_epochs=10,
gamma=0.99,
clip_range=0.2,
ent_coef=0.0,
tensorboard_log="./tb_logs_ppo/",
verbose=1,
)
model.learn(total_timesteps=200_000)
model.save("jumperhard_ppo")
Make a copy and swap PPO for SAC:
from stable_baselines3 import SAC
from stable_baselines3.common.vec_env import VecMonitor
from godot_rl.wrappers.stable_baselines_wrapper import StableBaselinesGodotEnv
env = StableBaselinesGodotEnv(env_path="JumperHard.x86_64", show_window=False)
env = VecMonitor(env)
model = SAC(
"MlpPolicy", env,
learning_rate=3e-4,
buffer_size=200_000, # replay buffer capacity (transitions)
learning_starts=5_000, # collect random data first
batch_size=256,
tau=0.005, # soft-update rate for target nets
gamma=0.99,
train_freq=1, # one gradient step per env step
gradient_steps=1,
ent_coef="auto", # automatic entropy temperature
tensorboard_log="./tb_logs_sac/",
verbose=1,
)
model.learn(total_timesteps=200_000)
model.save("jumperhard_sac")
Notice what disappeared (n_steps, n_epochs, clip_range) and what appeared (buffer_size, learning_starts, tau, train_freq, gradient_steps, ent_coef). These are not the same algorithm with a different name — every line above maps to a different optimization story.
Replay buffer memory
buffer_size=200_000 with a small observation vector (JumperHard is ~tens of floats) is harmless. Bump the buffer for image observations and you'll feel it: 200 k × 84×84×4 bytes ≈ 5.6 GB. The buffer is the price of off-policy.
3 · Train both
Two terminal sessions, same env config, different algorithm:
Point a single TensorBoard at both logdirs so you can overlay the curves (requires TensorBoard ≥ 2.x — the version SB3 currently pins):
Now open localhost:6006 and watch rollout/ep_rew_mean for both runs simultaneously.
Done when
JumperHard has no published benchmark, so judge the comparison, not a score: (1) both runs show up as separate rollout/ep_rew_mean curves under the ppo and sac tags in one TensorBoard window, and (2) after both finish you can pick any reward level both curves reached, say which algorithm got there in fewer env steps, and explain the result either way — Section 1's replay-buffer reuse argument if SAC won, Section 4's troubleshooting list if it didn't (an honest "PPO won and here's the likely reason" passes too). If either run dies before producing a curve, fix that before comparing anything.
4 · What you'll see
Expected behaviour on JumperHard (this is expected, not a measurement — your numbers will vary with seed, hardware, and SB3 version):
- SAC's
ep_rew_meanrises in fewer environment steps. That is sample efficiency: SAC squeezes more out of each transition because the replay buffer lets every transition contribute to many updates. - PPO often wins on wall-clock time. JumperHard's environment step is cheap, PPO's gradient step is cheap, and PPO's data path is simpler. SAC's per-step cost (gradient step + target-net update + entropy temperature update) eats its sample-efficiency advantage in wall time on this env.
- SAC's policy can look smoother in Godot. Continuous action distributions with automatic entropy tuning often produce less jittery control than a clipped PPO policy that's still bleeding entropy.
- SAC is more sensitive to hyperparameters early on.
learning_startstoo low and the critic is fitting garbage;tautoo high and the target nets oscillate. PPO's hyperparameters are forgiving by comparison.
If you don't see SAC reach the same reward in fewer env steps, check ent_coef (auto-tuning may have decayed entropy too fast — try ent_coef=0.2 fixed) and buffer_size (too small means the buffer is dominated by stale early-training data).
5 · When to actually reach for SAC
A short decision guide once the experiment is done:
| Situation | Pick |
|---|---|
| Continuous actions, expensive simulation (real robot, physics-heavy sim) | SAC — sample efficiency matters more than wall-clock |
| Cheap parallel envs, discrete or continuous actions | PPO — easier to scale, more forgiving |
| You need stable training out of the box with little tuning | PPO |
| You want to push state of the art on continuous control benchmarks | SAC (or TD3) |
| Tight RAM budget, can't afford a replay buffer | PPO |
You'll meet SAC again in Phase 6 — Locomotion and Sim-to-Real, where its sample efficiency stops being a curiosity and becomes essential.
Stretch Goals
- Wall-clock vs steps. Re-train both with
timeand plot wall-clock seconds vs environment steps. Does SAC's sample-efficiency advantage translate into wall-clock advantage on JumperHard? Why or why not? - SAC on CrossTheRoad. Try the SAC script on the discrete-action CrossTheRoad env from Unit 3. It will fail or behave badly — figure out why before reading the SAC docs.
- Entropy-temperature sweep. Train SAC with
ent_coef ∈ {0.05, 0.1, 0.2, "auto"}and compare. What does the auto-tuner converge toward on JumperHard?
What's next
You've now seen PPO and SAC as users — picking an algorithm class and trusting the library. Next, you peel one layer off: CleanRL strips PPO down to ~400 lines of single-file PyTorch so you can read every gradient step. Useful when SB3 is too opaque to debug, when you need a custom loss, or when a paper's algorithm has no library implementation yet.