Skill Tracer
Skills › Scientific Agent Skills › stable-baselines3 › vectorized_envs.md

vectorized_envs.md

618 lines · skills/stable-baselines3/references/vectorized_envs.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Vectorized Environments in Stable Baselines3
2
3Targets SB3 2.9.0 / Gymnasium 1.3.0. Code snippets are illustrative fragments
4sharing imports and environment/model variables; run process-based examples in
5a Python file under a main guard. The bundled training script and repository
6tests exercise DummyVecEnv and SubprocVecEnv with small CPU environments.
7

Vectorized Environments in Stable Baselines3

Targets SB3 2.9.0 / Gymnasium 1.3.0. Code snippets are illustrative fragments sharing imports and environment/model variables; run process-based examples in a Python file under a main guard. The bundled training script and repository tests exercise DummyVecEnv and SubprocVecEnv with small CPU environments.

Raw lines 1 to 7
8## Overview
9
10Vectorized environments stack multiple independent environment instances into a single environment that processes actions and observations in batches. Instead of interacting with one environment at a time, you interact with `n` environments simultaneously.
12**Benefits:**
13- **Speed:** Process parallelism can help expensive environments; measure IPC overhead
14- **Batching:** Collect transitions from independent environments; this does not guarantee better sample efficiency
15- **Required for:** Frame stacking and normalization wrappers
16- **Better for:** On-policy algorithms (PPO, A2C)

Overview

Vectorized environments stack multiple independent environment instances into a single environment that processes actions and observations in batches. Instead of interacting with one environment at a time, you interact with n environments simultaneously.

Benefits:

  • Speed: Process parallelism can help expensive environments; measure IPC overhead
  • Batching: Collect transitions from independent environments; this does not guarantee better sample efficiency
  • Required for: Frame stacking and normalization wrappers
  • Better for: On-policy algorithms (PPO, A2C)
Raw lines 8 to 17
18## VecEnv Types

VecEnv Types

Raw lines 18 to 19
20### DummyVecEnv
22Executes environments sequentially on the current Python process.
24```python
25import gymnasium as gym
26from stable_baselines3.common.vec_env import DummyVecEnv
28# Method 1: Using make_vec_env
29from stable_baselines3.common.env_util import make_vec_env
31env = make_vec_env("CartPole-v1", n_envs=4, vec_env_cls=DummyVecEnv)
33# Method 2: Manual creation
34def make_env():
35 def _init():
36 return gym.make("CartPole-v1")
37 return _init
39env = DummyVecEnv([make_env() for _ in range(4)])
40```
42**When to use:**
43- Lightweight environments (CartPole, simple grids)
44- When multiprocessing overhead > computation time
45- Debugging (easier to trace errors)
46- Single-threaded environments
48**Performance:** No actual parallelism (sequential execution).

DummyVecEnv

Executes environments sequentially on the current Python process.

import gymnasium as gym
from stable_baselines3.common.vec_env import DummyVecEnv

# Method 1: Using make_vec_env
from stable_baselines3.common.env_util import make_vec_env

env = make_vec_env("CartPole-v1", n_envs=4, vec_env_cls=DummyVecEnv)

# Method 2: Manual creation
def make_env():
    def _init():
        return gym.make("CartPole-v1")
    return _init

env = DummyVecEnv([make_env() for _ in range(4)])

When to use:

  • Lightweight environments (CartPole, simple grids)
  • When multiprocessing overhead > computation time
  • Debugging (easier to trace errors)
  • Single-threaded environments

Performance: No actual parallelism (sequential execution).

Raw lines 20 to 49
50### SubprocVecEnv
52Executes each environment in a separate process, enabling true parallelism.
54```python
55from stable_baselines3.common.vec_env import SubprocVecEnv
56from stable_baselines3.common.env_util import make_vec_env
58env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)
59```
61**When to use:**
62- Computationally expensive environments (physics simulations, 3D games)
63- When environment computation time justifies multiprocessing overhead
64- When you need true parallel execution
66**Important:** Requires wrapping code in `if __name__ == "__main__":` when using forkserver or spawn:
68```python
69if __name__ == "__main__":
70 env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)
71 model = PPO("MlpPolicy", env)
72 model.learn(total_timesteps=100000)
73```
75**Performance:** True parallelism across CPU cores.

SubprocVecEnv

Executes each environment in a separate process, enabling true parallelism.

from stable_baselines3.common.vec_env import SubprocVecEnv
from stable_baselines3.common.env_util import make_vec_env

env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)

When to use:

  • Computationally expensive environments (physics simulations, 3D games)
  • When environment computation time justifies multiprocessing overhead
  • When you need true parallel execution

Important: Requires wrapping code in if __name__ == "__main__": when using forkserver or spawn:

if __name__ == "__main__":
    env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)
    model = PPO("MlpPolicy", env)
    model.learn(total_timesteps=100000)

Performance: True parallelism across CPU cores.

Raw lines 50 to 76
Starts an agent spawn :66
77## Quick Setup with make_vec_env
79The easiest way to create vectorized environments:
81```python
82from stable_baselines3.common.env_util import make_vec_env
83from stable_baselines3.common.vec_env import SubprocVecEnv
85# Basic usage
86env = make_vec_env("CartPole-v1", n_envs=4)
88# With SubprocVecEnv
89env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)
91# With custom environment kwargs
92env = make_vec_env(
93 "MyEnv-v0",
94 n_envs=4,
95 env_kwargs={"difficulty": "hard", "max_steps": 500}
98# With custom seed
99env = make_vec_env("CartPole-v1", n_envs=4, seed=42)
100```

Quick Setup with make_vec_env

The easiest way to create vectorized environments:

from stable_baselines3.common.env_util import make_vec_env
from stable_baselines3.common.vec_env import SubprocVecEnv

# Basic usage
env = make_vec_env("CartPole-v1", n_envs=4)

# With SubprocVecEnv
env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)

# With custom environment kwargs
env = make_vec_env(
    "MyEnv-v0",
    n_envs=4,
    env_kwargs={"difficulty": "hard", "max_steps": 500}
)

# With custom seed
env = make_vec_env("CartPole-v1", n_envs=4, seed=42)
Raw lines 77 to 101
102## API Differences from Gymnasium
104SB3 VecEnv is not Gymnasium VectorEnv. Do not pass a Gymnasium vector environment
105directly to an SB3 algorithm or reuse its autoreset/info conventions:

API Differences from Gymnasium

SB3 VecEnv is not Gymnasium VectorEnv. Do not pass a Gymnasium vector environment directly to an SB3 algorithm or reuse its autoreset/info conventions:

Raw lines 102 to 106
107### reset()
109**Gymnasium Env:**
110```python
111obs, info = env.reset()
112```
114**VecEnv:**
115```python
116obs = env.reset() # Returns only observations (numpy array)
117# Access info via env.reset_infos (list of dicts)
118infos = env.reset_infos
119```
121**Seeding and options:** Call `vec_env.seed(seed=seed)` and/or `vec_env.set_options(options)` before the initial `reset()`. Seed and options are discarded after each `reset()` call.
123**Truncation vs termination:** When an episode ends, check `infos[env_idx]["TimeLimit.truncated"]` to distinguish timeout/truncation from natural termination. It is computed as `truncated and not terminated`, so simultaneous flags count as
124termination for bootstrapping. Bootstrap through pure truncation using the
125`terminal_observation`, not the auto-reset observation; do not bootstrap through
126termination. SB3 algorithms implement this handling internally.

reset()

Gymnasium Env:

obs, info = env.reset()

VecEnv:

obs = env.reset()  # Returns only observations (numpy array)
# Access info via env.reset_infos (list of dicts)
infos = env.reset_infos

Seeding and options: Call vec_env.seed(seed=seed) and/or vec_env.set_options(options) before the initial reset(). Seed and options are discarded after each reset() call.

Truncation vs termination: When an episode ends, check infos[env_idx]["TimeLimit.truncated"] to distinguish timeout/truncation from natural termination. It is computed as truncated and not terminated, so simultaneous flags count as termination for bootstrapping. Bootstrap through pure truncation using the terminal_observation, not the auto-reset observation; do not bootstrap through termination. SB3 algorithms implement this handling internally.

Raw lines 107 to 127
128### step()
130**Gymnasium Env:**
131```python
132obs, reward, terminated, truncated, info = env.step(action)
133```
135**VecEnv:**
136```python
137obs, rewards, dones, infos = env.step(actions)
138# Returns 4-tuple instead of 5-tuple
139# dones = terminated | truncated
140# actions is an array of shape (n_envs,) or (n_envs, action_dim)
141```

step()

Gymnasium Env:

obs, reward, terminated, truncated, info = env.step(action)

VecEnv:

obs, rewards, dones, infos = env.step(actions)
# Returns 4-tuple instead of 5-tuple
# dones = terminated | truncated
# actions is an array of shape (n_envs,) or (n_envs, action_dim)
Raw lines 128 to 142
143### Auto-reset
145**VecEnv automatically resets environments when episodes end:**
147```python
148import numpy as np
150obs = env.reset() # Shape: (n_envs, obs_dim)
151for _ in range(1000):
152 actions = np.array([env.action_space.sample() for _ in range(env.num_envs)])
153 obs, rewards, dones, infos = env.step(actions)
154 # If dones[i] is True, env i was automatically reset
155 # Final observation before reset available in infos[i]["terminal_observation"]
156```

Auto-reset

VecEnv automatically resets environments when episodes end:

import numpy as np

obs = env.reset()  # Shape: (n_envs, obs_dim)
for _ in range(1000):
    actions = np.array([env.action_space.sample() for _ in range(env.num_envs)])
    obs, rewards, dones, infos = env.step(actions)
    # If dones[i] is True, env i was automatically reset
    # Final observation before reset available in infos[i]["terminal_observation"]
Raw lines 143 to 157
158### Terminal Observations
160When an episode ends, access the true final observation:
162```python
163obs, rewards, dones, infos = env.step(actions)
165for i, done in enumerate(dones):
166 if done:
167 # The obs[i] is already the reset observation
168 # True terminal observation is in info
169 terminal_obs = infos[i]["terminal_observation"]
170 print(f"Episode ended with terminal observation: {terminal_obs}")
171```

Terminal Observations

When an episode ends, access the true final observation:

obs, rewards, dones, infos = env.step(actions)

for i, done in enumerate(dones):
    if done:
        # The obs[i] is already the reset observation
        # True terminal observation is in info
        terminal_obs = infos[i]["terminal_observation"]
        print(f"Episode ended with terminal observation: {terminal_obs}")
Raw lines 158 to 172
173## Training with Vectorized Environments

Training with Vectorized Environments

Raw lines 173 to 174
175### On-Policy Algorithms (PPO, A2C)
177On-policy algorithms benefit greatly from vectorization:
179```python
180from stable_baselines3 import PPO
181from stable_baselines3.common.env_util import make_vec_env
182from stable_baselines3.common.vec_env import SubprocVecEnv
184# Create vectorized environment
185env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)
187# Train
188model = PPO("MlpPolicy", env, verbose=1, n_steps=128)
189model.learn(total_timesteps=100000)
191# With n_envs=8 and n_steps=128:
192# - Collects 8*128=1024 steps per rollout
193# - Updates after every 1024 steps
194```
196Benchmark environment count and keep `n_steps * n_envs` and PPO minibatch
197divisibility explicit; changing rollout size also changes optimization.

On-Policy Algorithms (PPO, A2C)

On-policy algorithms benefit greatly from vectorization:

from stable_baselines3 import PPO
from stable_baselines3.common.env_util import make_vec_env
from stable_baselines3.common.vec_env import SubprocVecEnv

# Create vectorized environment
env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)

# Train
model = PPO("MlpPolicy", env, verbose=1, n_steps=128)
model.learn(total_timesteps=100000)

# With n_envs=8 and n_steps=128:
# - Collects 8*128=1024 steps per rollout
# - Updates after every 1024 steps

Benchmark environment count and keep n_steps * n_envs and PPO minibatch divisibility explicit; changing rollout size also changes optimization.

Raw lines 175 to 198
199### Off-Policy Algorithms (SAC, TD3, DQN)
201Off-policy algorithms can use vectorization but benefit less:
203```python
204from stable_baselines3 import SAC
205from stable_baselines3.common.env_util import make_vec_env
207# Use fewer environments (1-4)
208env = make_vec_env("Pendulum-v1", n_envs=4)
210# Set gradient_steps=-1 for efficiency
211model = SAC(
212 "MlpPolicy",
213 env,
214 verbose=1,
215 train_freq=1,
216 gradient_steps=-1, # Do 1 gradient step per env step (4 total with 4 envs)
218model.learn(total_timesteps=50000)
219```
221Use step-based `train_freq` with multiple environments; episode-based collection
222is restricted to a single environment. `gradient_steps=-1` matches updates to
223collected transitions after warmup; it is a compute/learning tradeoff.

Off-Policy Algorithms (SAC, TD3, DQN)

Off-policy algorithms can use vectorization but benefit less:

from stable_baselines3 import SAC
from stable_baselines3.common.env_util import make_vec_env

# Use fewer environments (1-4)
env = make_vec_env("Pendulum-v1", n_envs=4)

# Set gradient_steps=-1 for efficiency
model = SAC(
    "MlpPolicy",
    env,
    verbose=1,
    train_freq=1,
    gradient_steps=-1,  # Do 1 gradient step per env step (4 total with 4 envs)
)
model.learn(total_timesteps=50000)

Use step-based train_freq with multiple environments; episode-based collection is restricted to a single environment. gradient_steps=-1 matches updates to collected transitions after warmup; it is a compute/learning tradeoff.

Raw lines 199 to 224
225## Wrappers for Vectorized Environments

Wrappers for Vectorized Environments

Raw lines 225 to 226
227### VecNormalize
229Normalizes observations and rewards using running statistics.
231```python
232from stable_baselines3.common.vec_env import VecNormalize
234env = make_vec_env("Pendulum-v1", n_envs=4)
236# Wrap with normalization
237env = VecNormalize(
238 env,
239 norm_obs=True, # Normalize observations
240 norm_reward=True, # Normalize rewards
241 clip_obs=10.0, # Clip normalized observations
242 clip_reward=10.0, # Clip normalized rewards
243 gamma=0.99, # Discount factor for reward normalization
246# Train
247model = PPO("MlpPolicy", env)
248model.learn(total_timesteps=50000)
250# Save model AND normalization statistics
251model.save("ppo_pendulum")
252env.save("vec_normalize.pkl")
254# Load for evaluation
255env = make_vec_env("Pendulum-v1", n_envs=1)
256env = VecNormalize.load("vec_normalize.pkl", env)
257env.training = False # Don't update stats during evaluation
258env.norm_reward = False # Don't normalize rewards during evaluation
260model = PPO.load("ppo_pendulum", env=env)
261```
263**When to use:**
264- Continuous control tasks (especially MuJoCo)
265- When observation scales vary widely
266- When rewards have high variance
268**Important:**
269- Statistics are NOT saved with model - save separately
270- Disable training and reward normalization during evaluation

VecNormalize

Normalizes observations and rewards using running statistics.

from stable_baselines3.common.vec_env import VecNormalize

env = make_vec_env("Pendulum-v1", n_envs=4)

# Wrap with normalization
env = VecNormalize(
    env,
    norm_obs=True,        # Normalize observations
    norm_reward=True,     # Normalize rewards
    clip_obs=10.0,        # Clip normalized observations
    clip_reward=10.0,     # Clip normalized rewards
    gamma=0.99,           # Discount factor for reward normalization
)

# Train
model = PPO("MlpPolicy", env)
model.learn(total_timesteps=50000)

# Save model AND normalization statistics
model.save("ppo_pendulum")
env.save("vec_normalize.pkl")

# Load for evaluation
env = make_vec_env("Pendulum-v1", n_envs=1)
env = VecNormalize.load("vec_normalize.pkl", env)
env.training = False  # Don't update stats during evaluation
env.norm_reward = False  # Don't normalize rewards during evaluation

model = PPO.load("ppo_pendulum", env=env)

When to use:

  • Continuous control tasks (especially MuJoCo)
  • When observation scales vary widely
  • When rewards have high variance

Important:

  • Statistics are NOT saved with model - save separately
  • Disable training and reward normalization during evaluation
Raw lines 227 to 271
272### VecFrameStack
274Stacks observations from multiple consecutive frames.
276```python
277from stable_baselines3.common.vec_env import VecFrameStack
279import ale_py
280import gymnasium as gym
281from stable_baselines3.common.env_util import make_atari_env
282gym.register_envs(ale_py)
284# ALE repeats once; SB3 AtariWrapper performs the four-frame skip.
285env = make_atari_env("ALE/Pong-v5", n_envs=8,
286 env_kwargs={"frameskip": 1},
287 wrapper_kwargs={"frame_skip": 4})
289# Stack 4 frames
290env = VecFrameStack(env, n_stack=4)
292# AtariWrapper produces HWC grayscale; stacking here gives (n_envs, 84, 84, 4).
293# SB3 wraps HWC for its CNN; VecFrameStack concatenates along the channel axis.
294model = PPO("CnnPolicy", env)
295model.learn(total_timesteps=1000000)
296```
298**When to use:**
299- Atari games (stack 4 frames)
300- Environments where velocity information is needed
301- Partial observability problems

VecFrameStack

Stacks observations from multiple consecutive frames.

from stable_baselines3.common.vec_env import VecFrameStack

import ale_py
import gymnasium as gym
from stable_baselines3.common.env_util import make_atari_env
gym.register_envs(ale_py)

# ALE repeats once; SB3 AtariWrapper performs the four-frame skip.
env = make_atari_env("ALE/Pong-v5", n_envs=8,
                     env_kwargs={"frameskip": 1},
                     wrapper_kwargs={"frame_skip": 4})

# Stack 4 frames
env = VecFrameStack(env, n_stack=4)

# AtariWrapper produces HWC grayscale; stacking here gives (n_envs, 84, 84, 4).
# SB3 wraps HWC for its CNN; VecFrameStack concatenates along the channel axis.
model = PPO("CnnPolicy", env)
model.learn(total_timesteps=1000000)

When to use:

  • Atari games (stack 4 frames)
  • Environments where velocity information is needed
  • Partial observability problems
Raw lines 272 to 302
303### VecVideoRecorder
305Records videos of agent behavior.
307```python
308from stable_baselines3.common.vec_env import VecVideoRecorder
310env = make_vec_env("CartPole-v1", n_envs=1,
311 env_kwargs={"render_mode": "rgb_array"})
313# Requires moviepy, FFmpeg, and pygame-ce for CartPole.
314# Record videos
315env = VecVideoRecorder(
316 env,
317 video_folder="./videos/",
318 record_video_trigger=lambda x: x % 2000 == 0, # Record every 2000 steps
319 video_length=200, # Max video length
320 name_prefix="training"
323model = PPO("MlpPolicy", env)
324model.learn(total_timesteps=10000)
325```
327**Output:** MP4 clips in `./videos/`; close the outer wrapper to flush the last
328clip. Frames accumulate in memory, so keep `video_length` bounded. A periodic
329trigger records clips at vector-step intervals, not one video per episode.

VecVideoRecorder

Records videos of agent behavior.

from stable_baselines3.common.vec_env import VecVideoRecorder

env = make_vec_env("CartPole-v1", n_envs=1,
                   env_kwargs={"render_mode": "rgb_array"})

# Requires moviepy, FFmpeg, and pygame-ce for CartPole.
# Record videos
env = VecVideoRecorder(
    env,
    video_folder="./videos/",
    record_video_trigger=lambda x: x % 2000 == 0,  # Record every 2000 steps
    video_length=200,  # Max video length
    name_prefix="training"
)

model = PPO("MlpPolicy", env)
model.learn(total_timesteps=10000)

Output: MP4 clips in ./videos/; close the outer wrapper to flush the last clip. Frames accumulate in memory, so keep video_length bounded. A periodic trigger records clips at vector-step intervals, not one video per episode.

Raw lines 303 to 330
331### VecCheckNan
333Checks for NaN or infinite values in observations and rewards.
335```python
336from stable_baselines3.common.vec_env import VecCheckNan
338env = make_vec_env("CustomEnv-v0", n_envs=4)
340# Add NaN checking (useful for debugging)
341env = VecCheckNan(env, raise_exception=True, warn_once=True)
343model = PPO("MlpPolicy", env)
344model.learn(total_timesteps=10000)
345```
347**When to use:**
348- Debugging custom environments
349- Catching numerical instabilities
350- Validating environment implementation

VecCheckNan

Checks for NaN or infinite values in observations and rewards.

from stable_baselines3.common.vec_env import VecCheckNan

env = make_vec_env("CustomEnv-v0", n_envs=4)

# Add NaN checking (useful for debugging)
env = VecCheckNan(env, raise_exception=True, warn_once=True)

model = PPO("MlpPolicy", env)
model.learn(total_timesteps=10000)

When to use:

  • Debugging custom environments
  • Catching numerical instabilities
  • Validating environment implementation
Raw lines 331 to 351
352### VecTransposeImage
354Transposes image observations from (height, width, channels) to (channels, height, width).
356```python
357from stable_baselines3.common.vec_env import VecTransposeImage
359import ale_py
360import gymnasium as gym
361gym.register_envs(ale_py)
362env = make_vec_env("ALE/Pong-v5", n_envs=4)
364# Convert HWC to CHW format
365env = VecTransposeImage(env)
367model = PPO("CnnPolicy", env)
368```
370**When to use:**
371- When environment returns images in HWC format
372- SB3 expects CHW format for CNN policies

VecTransposeImage

Transposes image observations from (height, width, channels) to (channels, height, width).

from stable_baselines3.common.vec_env import VecTransposeImage

import ale_py
import gymnasium as gym
gym.register_envs(ale_py)
env = make_vec_env("ALE/Pong-v5", n_envs=4)

# Convert HWC to CHW format
env = VecTransposeImage(env)

model = PPO("CnnPolicy", env)

When to use:

  • When environment returns images in HWC format
  • SB3 expects CHW format for CNN policies
Raw lines 352 to 373
374## Advanced Usage

Advanced Usage

Raw lines 374 to 375
376### Custom VecEnv
378Create custom vectorized environment:
380```python
381from stable_baselines3.common.vec_env import DummyVecEnv
382import gymnasium as gym
384class CustomVecEnv(DummyVecEnv):
385 def step_wait(self):
386 # Custom logic before/after stepping
387 obs, rewards, dones, infos = super().step_wait()
388 # Modify observations/rewards/etc
389 return obs, rewards, dones, infos
390```

Custom VecEnv

Create custom vectorized environment:

from stable_baselines3.common.vec_env import DummyVecEnv
import gymnasium as gym

class CustomVecEnv(DummyVecEnv):
    def step_wait(self):
        # Custom logic before/after stepping
        obs, rewards, dones, infos = super().step_wait()
        # Modify observations/rewards/etc
        return obs, rewards, dones, infos
Raw lines 376 to 391
392### Environment Method Calls
394Call methods on wrapped environments:
396```python
397env = make_vec_env("MyEnv-v0", n_envs=4)
399# Call method on all environments
400env.env_method("set_difficulty", "hard")
402# Call method on specific environment
403env.env_method("reset_level", indices=[0, 2])
405# Get attribute from all environments
406levels = env.get_attr("current_level")
407```

Environment Method Calls

Call methods on wrapped environments:

env = make_vec_env("MyEnv-v0", n_envs=4)

# Call method on all environments
env.env_method("set_difficulty", "hard")

# Call method on specific environment
env.env_method("reset_level", indices=[0, 2])

# Get attribute from all environments
levels = env.get_attr("current_level")
Raw lines 392 to 408
409### Setting Attributes
411```python
412# Prefer an explicit underlying-env setter, applying changes at reset.
413env.env_method("set_difficulty", "hard")
415# For existing wrapped attributes, Gymnasium searches the wrapper chain:
416env.env_method("set_wrapper_attr", "max_steps", 1000, indices=[1, 3])
417```

Setting Attributes

# Prefer an explicit underlying-env setter, applying changes at reset.
env.env_method("set_difficulty", "hard")

# For existing wrapped attributes, Gymnasium searches the wrapper chain:
env.env_method("set_wrapper_attr", "max_steps", 1000, indices=[1, 3])
Raw lines 409 to 418
419### Checking Attributes
421```python
422# Check if attribute exists on all sub-environments (added SB3 2.6.0)
423if env.has_attr("current_level"):
424 levels = env.get_attr("current_level")
425```

Checking Attributes

# Check if attribute exists on all sub-environments (added SB3 2.6.0)
if env.has_attr("current_level"):
    levels = env.get_attr("current_level")
Raw lines 419 to 426
427## Performance Optimization

Performance Optimization

Raw lines 427 to 428
429### Choosing Number of Environments
431**On-Policy (PPO, A2C):**
432```python
433# General rule: 4-16 environments
434# More environments = faster data collection
435n_envs = 8
436env = make_vec_env("CartPole-v1", n_envs=n_envs)
438# Adjust n_steps to maintain same rollout length
439# Total steps per rollout = n_envs * n_steps
440model = PPO("MlpPolicy", env, n_steps=128) # 8*128 = 1024 steps/rollout
441```
443**Off-Policy (SAC, TD3, DQN):**
444```python
445# General rule: 1-4 environments
446# More doesn't help as much (replay buffer provides diversity)
447n_envs = 4
448env = make_vec_env("Pendulum-v1", n_envs=n_envs)
450model = SAC("MlpPolicy", env, gradient_steps=-1) # 1 grad step per env step
451```

Choosing Number of Environments

On-Policy (PPO, A2C):

# General rule: 4-16 environments
# More environments = faster data collection
n_envs = 8
env = make_vec_env("CartPole-v1", n_envs=n_envs)

# Adjust n_steps to maintain same rollout length
# Total steps per rollout = n_envs * n_steps
model = PPO("MlpPolicy", env, n_steps=128)  # 8*128 = 1024 steps/rollout

Off-Policy (SAC, TD3, DQN):

# General rule: 1-4 environments
# More doesn't help as much (replay buffer provides diversity)
n_envs = 4
env = make_vec_env("Pendulum-v1", n_envs=n_envs)

model = SAC("MlpPolicy", env, gradient_steps=-1)  # 1 grad step per env step
Raw lines 429 to 452
453### CPU Core Utilization
455```python
456import multiprocessing
458# Use one less than total cores (leave one for Python main process)
459n_cpus = max(multiprocessing.cpu_count() - 1, 1)
460env = make_vec_env("MyEnv-v0", n_envs=n_cpus, vec_env_cls=SubprocVecEnv)
461```

CPU Core Utilization

import multiprocessing

# Use one less than total cores (leave one for Python main process)
n_cpus = max(multiprocessing.cpu_count() - 1, 1)
env = make_vec_env("MyEnv-v0", n_envs=n_cpus, vec_env_cls=SubprocVecEnv)
Raw lines 453 to 462
463### Memory Considerations
465```python
466# Large replay buffer + many environments = high memory usage
467# Reduce buffer size if memory constrained
468model = SAC(
469 "MlpPolicy",
470 env,
471 buffer_size=100_000, # Reduced from 1M
473```

Memory Considerations

# Large replay buffer + many environments = high memory usage
# Reduce buffer size if memory constrained
model = SAC(
    "MlpPolicy",
    env,
    buffer_size=100_000,  # Reduced from 1M
)
Raw lines 463 to 474
475## Common Issues
477### Issue: "Can't pickle local object"
479**Cause:** A captured environment resource may not serialize, or spawned workers
480cannot safely import the entry module. SubprocVecEnv uses cloudpickle for factories,
481so nested factories and lambdas are supported; they are not inherently the error.
482Create native handles inside each worker rather than capturing an already-open
483simulator. Each factory must return a distinct environment. Use a main guard.
484The released source selects forkserver when available, otherwise spawn.
486```python
487import gymnasium as gym
488from stable_baselines3.common.vec_env import SubprocVecEnv
490def make_env():
491 return gym.make("CartPole-v1")
493if __name__ == "__main__":
494 env = SubprocVecEnv([make_env for _ in range(4)])
495 try:
496 obs = env.reset()
497 finally:
498 env.close()
499```

Issue: "Can't pickle local object"

Cause: A captured environment resource may not serialize, or spawned workers cannot safely import the entry module. SubprocVecEnv uses cloudpickle for factories, so nested factories and lambdas are supported; they are not inherently the error. Create native handles inside each worker rather than capturing an already-open simulator. Each factory must return a distinct environment. Use a main guard. The released source selects forkserver when available, otherwise spawn.

import gymnasium as gym
from stable_baselines3.common.vec_env import SubprocVecEnv

def make_env():
    return gym.make("CartPole-v1")

if __name__ == "__main__":
    env = SubprocVecEnv([make_env for _ in range(4)])
    try:
        obs = env.reset()
    finally:
        env.close()
Raw lines 477 to 500
Starts an agent spawned :479
Starts an agent spawn :484
501### Issue: Different behavior between single and vectorized env
503**Cause:** Auto-reset in vectorized environments.
505**Solution:** Handle terminal observations correctly:
507```python
508obs, rewards, dones, infos = env.step(actions)
509for i, done in enumerate(dones):
510 if done:
511 terminal_obs = infos[i]["terminal_observation"]
512 # Process terminal_obs if needed
513```

Issue: Different behavior between single and vectorized env

Cause: Auto-reset in vectorized environments.

Solution: Handle terminal observations correctly:

obs, rewards, dones, infos = env.step(actions)
for i, done in enumerate(dones):
    if done:
        terminal_obs = infos[i]["terminal_observation"]
        # Process terminal_obs if needed
Raw lines 501 to 514
515### Issue: Slower with SubprocVecEnv than DummyVecEnv
517**Cause:** Environment too lightweight (multiprocessing overhead > computation).
519**Solution:** Use DummyVecEnv for simple environments:
521```python
522# For CartPole, use DummyVecEnv
523env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=DummyVecEnv)
524```

Issue: Slower with SubprocVecEnv than DummyVecEnv

Cause: Environment too lightweight (multiprocessing overhead > computation).

Solution: Use DummyVecEnv for simple environments:

# For CartPole, use DummyVecEnv
env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=DummyVecEnv)
Raw lines 515 to 525
526### Issue: Training crashes with SubprocVecEnv
528**Cause:** Environment not properly isolated or has shared state.
530**Solution:**
531- Ensure environment has no shared global state
532- Wrap code in `if __name__ == "__main__":`
533- Use DummyVecEnv for debugging

Issue: Training crashes with SubprocVecEnv

Cause: Environment not properly isolated or has shared state.

Solution:

  • Ensure environment has no shared global state
  • Wrap code in if __name__ == "__main__":
  • Use DummyVecEnv for debugging
Raw lines 526 to 534
535## Best Practices
5371. **Use appropriate VecEnv type:**
538 - DummyVecEnv: Simple environments (CartPole, basic grids)
539 - SubprocVecEnv: Complex environments (MuJoCo, Unity, 3D games)
5412. **Adjust hyperparameters for vectorization:**
542 - Use `max(freq // n_envs, 1)` for EvalCallback/CheckpointCallback call frequencies
543 - Maintain same `n_steps * n_envs` for on-policy algorithms
5453. **Save normalization statistics:**
546 - Always save VecNormalize stats with model
547 - Disable training during evaluation
5494. **Monitor memory usage:**
550 - More environments = more memory
551 - Reduce buffer size if needed
5535. **Test with DummyVecEnv first:**
554 - Easier debugging
555 - Ensure environment works before parallelizing

Best Practices

  1. Use appropriate VecEnv type:
  2. DummyVecEnv: Simple environments (CartPole, basic grids)
  3. SubprocVecEnv: Complex environments (MuJoCo, Unity, 3D games)
  1. Adjust hyperparameters for vectorization:
  2. Use max(freq // n_envs, 1) for EvalCallback/CheckpointCallback call frequencies
  3. Maintain same n_steps * n_envs for on-policy algorithms
  1. Save normalization statistics:
  2. Always save VecNormalize stats with model
  3. Disable training during evaluation
  1. Monitor memory usage:
  2. More environments = more memory
  3. Reduce buffer size if needed
  1. Test with DummyVecEnv first:
  2. Easier debugging
  3. Ensure environment works before parallelizing
Raw lines 535 to 556
557## Examples
559### Basic Training Loop
561```python
562from stable_baselines3 import PPO
563from stable_baselines3.common.env_util import make_vec_env
564from stable_baselines3.common.vec_env import SubprocVecEnv
566# Create vectorized environment
567env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)
569# Train
570model = PPO("MlpPolicy", env, verbose=1)
571model.learn(total_timesteps=100000)
573# Evaluate
574obs = env.reset()
575for _ in range(1000):
576 action, _states = model.predict(obs, deterministic=True)
577 obs, rewards, dones, infos = env.step(action)
578```

Basic Training Loop

from stable_baselines3 import PPO
from stable_baselines3.common.env_util import make_vec_env
from stable_baselines3.common.vec_env import SubprocVecEnv

# Create vectorized environment
env = make_vec_env("CartPole-v1", n_envs=8, vec_env_cls=SubprocVecEnv)

# Train
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=100000)

# Evaluate
obs = env.reset()
for _ in range(1000):
    action, _states = model.predict(obs, deterministic=True)
    obs, rewards, dones, infos = env.step(action)
Raw lines 559 to 579
580### With Normalization
582```python
583from stable_baselines3 import PPO
584from stable_baselines3.common.env_util import make_vec_env
585from stable_baselines3.common.vec_env import VecNormalize
587# Create and normalize
588env = make_vec_env("Pendulum-v1", n_envs=4)
589env = VecNormalize(env, norm_obs=True, norm_reward=True)
591# Train
592model = PPO("MlpPolicy", env)
593model.learn(total_timesteps=50000)
595# Save both
596model.save("model")
597env.save("vec_normalize.pkl")
599# Load for evaluation
600eval_env = make_vec_env("Pendulum-v1", n_envs=1)
601eval_env = VecNormalize.load("vec_normalize.pkl", eval_env)
602eval_env.training = False
603eval_env.norm_reward = False
605model = PPO.load("model", env=eval_env)
606```

With Normalization

from stable_baselines3 import PPO
from stable_baselines3.common.env_util import make_vec_env
from stable_baselines3.common.vec_env import VecNormalize

# Create and normalize
env = make_vec_env("Pendulum-v1", n_envs=4)
env = VecNormalize(env, norm_obs=True, norm_reward=True)

# Train
model = PPO("MlpPolicy", env)
model.learn(total_timesteps=50000)

# Save both
model.save("model")
env.save("vec_normalize.pkl")

# Load for evaluation
eval_env = make_vec_env("Pendulum-v1", n_envs=1)
eval_env = VecNormalize.load("vec_normalize.pkl", eval_env)
eval_env.training = False
eval_env.norm_reward = False

model = PPO.load("model", env=eval_env)
Raw lines 580 to 607
608## Additional Resources
610- Official SB3 VecEnv Guide: https://stable-baselines3.readthedocs.io/en/v2.9.0/guide/vec_envs.html
611- VecEnv API Reference: https://stable-baselines3.readthedocs.io/en/v2.9.0/guide/vec_envs.html#module-stable_baselines3.common.vec_env
612- Multiprocessing Best Practices: https://docs.python.org/3/library/multiprocessing.html
614Atari fragments are source-verified, not executed in this refresh. Install ALE
615and its supported ROM setup; keep action repeat, sticky actions, life-loss
616termination and reward clipping explicit. Use a separate evaluation environment
617with raw reward reporting and full-game episode semantics when comparing scores.
618[SB3 AtariWrapper source](https://github.com/DLR-RM/stable-baselines3/blob/v2.9.0/stable_baselines3/common/atari_wrappers.py).

Additional Resources

  • Official SB3 VecEnv Guide: https://stable-baselines3.readthedocs.io/en/v2.9.0/guide/vec_envs.html
  • VecEnv API Reference: https://stable-baselines3.readthedocs.io/en/v2.9.0/guide/vec_envs.html#module-stable_baselines3.common.vec_env
  • Multiprocessing Best Practices: https://docs.python.org/3/library/multiprocessing.html

Atari fragments are source-verified, not executed in this refresh. Install ALE and its supported ROM setup; keep action repeat, sticky actions, life-loss termination and reward clipping explicit. Use a separate evaluation environment with raw reward reporting and full-game episode semantics when comparing scores. SB3 AtariWrapper source.

Raw lines 608 to 618