Skip to content

Commit 80d5636

Browse files
committed
Modernize benchmark stack and switch procgen upstream
1 parent dd350f4 commit 80d5636

15 files changed

Lines changed: 192 additions & 93 deletions

benchmark/README.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,22 @@ The following results are generated from four types of machine:
77
3. TPU-VM: 96 core ``Intel(R) Xeon(R) CPU @ 2.00GHz``, 2 NUMA core, TPU v3-8
88
4. DGX-A100: 256 core ``AMD EPYC 7742 64-Core Processor``, 8 NUMA core, 8x A100
99

10-
We use `PongNoFrameskip-v4` (with environment wrappers from [OpenAI baselines](https://github.com/openai/baselines/blob/master/baselines/common/atari_wrappers.py)) and `Ant-v3` for Atari/Mujoco environment benchmark test with `envpool==0.6.1.post1`. Other packages' versions are all in `requirements.txt`:
10+
The historical numbers below were produced with `PongNoFrameskip-v4` and
11+
`Ant-v3` on `envpool==0.6.1.post1`. The current benchmark scripts in this
12+
directory use Gymnasium's `ALE/Pong-v5` and `Ant-v5`, and the baseline
13+
dependencies plus shared benchmark tooling are installed from
14+
`requirements.txt`:
1115

1216
```bash
1317
$ pip install -r requirements.txt
1418
```
1519

16-
To align with other baseline results, FPS is multiplied with `frame_skip` (4 for `PongNoFrameskip-v4` and 5 for `Ant-v3`).
20+
`test_gym.py` uses only the packages above. `test_envpool.py` additionally
21+
expects an installed EnvPool build with native modules, so install the
22+
EnvPool wheel you want to benchmark separately before running it.
23+
24+
To align with other baseline results, FPS is multiplied with `frame_skip` (4
25+
for Atari and 5 for Mujoco).
1726

1827
## Highest FPS Overview
1928

@@ -53,7 +62,7 @@ python3 test_gym.py --env atari --num-envs 12 --total-step 6000
5362
python3 test_gym.py --env mujoco --num-envs 12 --total-step 12000
5463
```
5564

56-
### Subprocess (gym.vector_env)
65+
### Subprocess (gymnasium.vector)
5766

5867
Command to run:
5968

@@ -66,18 +75,10 @@ python3 test_gym.py --env mujoco --async_ --num-envs 10 --total-step 50000
6675

6776
### Sample Factory
6877

69-
To run with Ant-v3 in Sample Factory, add one line in `sample_factory/envs/mujoco/mujoco_utils.py`:
70-
71-
```diff
72-
MUJOCO_ENVS = [
73-
+ MujocoSpec('mujoco_ant', 'Ant-v3'),
74-
MujocoSpec('mujoco_hopper', 'Hopper-v2'),
75-
MujocoSpec('mujoco_halfcheetah', 'HalfCheetah-v2'),
76-
MujocoSpec('mujoco_humanoid', 'Humanoid-v2'),
77-
]
78-
```
79-
80-
and finally use FPS \* 5 as the result.
78+
Sample Factory remains a historical reference for now. The latest upstream
79+
release still depends on `gymnasium<1.0`, `numpy<2`, and an older `ale-py`
80+
build that is not available in the current devbox package mirror, so it is not
81+
included in `requirements.txt`.
8182

8283
Command to run:
8384

@@ -108,6 +109,9 @@ for i in num_workers:
108109

109110
### EnvPool
110111

112+
Install an EnvPool wheel for the version you want to benchmark before running
113+
the commands below.
114+
111115
<!--
112116
113117
```bash

benchmark/atari_wrappers.py

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from collections import deque
2020

2121
import cv2
22-
import gym
22+
import gymnasium as gym
2323
import numpy as np
2424

2525

@@ -38,14 +38,14 @@ def __init__(self, env, noop_max=30):
3838
self.noop_action = 0
3939
assert env.unwrapped.get_action_meanings()[0] == "NOOP"
4040

41-
def reset(self):
42-
self.env.reset()
41+
def reset(self, *, seed=None, options=None):
42+
obs, info = self.env.reset(seed=seed, options=options)
4343
noops = self.unwrapped.np_random.integers(1, self.noop_max + 1)
4444
for _ in range(noops):
45-
obs, _, done, _ = self.env.step(self.noop_action)
46-
if done:
47-
obs = self.env.reset()
48-
return obs
45+
obs, _, terminated, truncated, info = self.env.step(self.noop_action)
46+
if terminated or truncated:
47+
obs, info = self.env.reset()
48+
return obs, info
4949

5050

5151
class MaxAndSkipEnv(gym.Wrapper):
@@ -65,15 +65,15 @@ def step(self, action):
6565
6666
Repeat action, sum reward, and max over last observations.
6767
"""
68-
obs_list, total_reward, done = [], 0., False
68+
obs_list, total_reward = [], 0.
6969
for _ in range(self._skip):
70-
obs, reward, done, info = self.env.step(action)
70+
obs, reward, terminated, truncated, info = self.env.step(action)
7171
obs_list.append(obs)
7272
total_reward += reward
73-
if done:
73+
if terminated or truncated:
7474
break
7575
max_frame = np.max(obs_list[-2:], axis=0)
76-
return max_frame, total_reward, done, info
76+
return max_frame, total_reward, terminated, truncated, info
7777

7878

7979
class EpisodicLifeEnv(gym.Wrapper):
@@ -90,32 +90,34 @@ def __init__(self, env):
9090
self.was_real_done = True
9191

9292
def step(self, action):
93-
obs, reward, done, info = self.env.step(action)
94-
self.was_real_done = done
93+
obs, reward, terminated, truncated, info = self.env.step(action)
94+
self.was_real_done = terminated or truncated
9595
# check current lives, make loss of life terminal, then update lives to
9696
# handle bonus lives
9797
lives = self.env.unwrapped.ale.lives()
9898
if 0 < lives < self.lives:
9999
# for Qbert sometimes we stay in lives == 0 condition for a few
100100
# frames, so its important to keep lives > 0, so that we only reset
101101
# once the environment is actually done.
102-
done = True
102+
terminated = True
103103
self.lives = lives
104-
return obs, reward, done, info
104+
return obs, reward, terminated, truncated, info
105105

106-
def reset(self):
106+
def reset(self, *, seed=None, options=None):
107107
"""Calls the Gym environment reset, only when lives are exhausted.
108108
109109
This way all states are still reachable even though lives are episodic,
110110
and the learner need not know about any of this behind-the-scenes.
111111
"""
112112
if self.was_real_done:
113-
obs = self.env.reset()
113+
obs, info = self.env.reset(seed=seed, options=options)
114114
else:
115115
# no-op step to advance from terminal/lost life state
116-
obs = self.env.step(0)[0]
116+
obs, _, terminated, truncated, info = self.env.step(0)
117+
if terminated or truncated:
118+
obs, info = self.env.reset(seed=seed, options=options)
117119
self.lives = self.env.unwrapped.ale.lives()
118-
return obs
120+
return obs, info
119121

120122

121123
class FireResetEnv(gym.Wrapper):
@@ -131,9 +133,12 @@ def __init__(self, env):
131133
assert env.unwrapped.get_action_meanings()[1] == "FIRE"
132134
assert len(env.unwrapped.get_action_meanings()) >= 3
133135

134-
def reset(self):
135-
self.env.reset()
136-
return self.env.step(1)[0]
136+
def reset(self, *, seed=None, options=None):
137+
self.env.reset(seed=seed, options=options)
138+
obs, _, terminated, truncated, info = self.env.step(1)
139+
if terminated or truncated:
140+
obs, info = self.env.reset()
141+
return obs, info
137142

138143

139144
class WarpFrame(gym.ObservationWrapper):
@@ -214,16 +219,16 @@ def __init__(self, env, n_frames):
214219
dtype=env.observation_space.dtype
215220
)
216221

217-
def reset(self):
218-
obs = self.env.reset()
222+
def reset(self, *, seed=None, options=None):
223+
obs, info = self.env.reset(seed=seed, options=options)
219224
for _ in range(self.n_frames):
220225
self.frames.append(obs)
221-
return self._get_ob()
226+
return self._get_ob(), info
222227

223228
def step(self, action):
224-
obs, reward, done, info = self.env.step(action)
229+
obs, reward, terminated, truncated, info = self.env.step(action)
225230
self.frames.append(obs)
226-
return self._get_ob(), reward, done, info
231+
return self._get_ob(), reward, terminated, truncated, info
227232

228233
def _get_ob(self):
229234
# the original wrapper use `LazyFrames` but since we use np buffer,
@@ -251,7 +256,11 @@ def wrap_deepmind(
251256
:param bool warp_frame: wrap the grayscale + resize observation wrapper.
252257
:return: the wrapped atari environment.
253258
"""
254-
assert "NoFrameskip" in env.spec.id
259+
assert env.spec is not None
260+
assert (
261+
"NoFrameskip" in env.spec.id or
262+
(env.spec.id.startswith("ALE/") and env.spec.id.endswith("-v5"))
263+
)
255264
env = NoopResetEnv(env, noop_max=30)
256265
env = MaxAndSkipEnv(env, skip=4)
257266
if episode_life:

benchmark/requirements.txt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
gym[accept-rom-license]==0.23.1
2-
ale-py==0.7.5
1+
gymnasium[atari,box2d,mujoco]==1.2.3
2+
gym>=0.26.2
3+
ale-py==0.11.2
34
mujoco==3.6.0
4-
envpool==0.6.1.post1
5-
sample-factory==1.123.0
6-
mujoco_py==2.1.2.14
75
tqdm
8-
opencv-python-headless
6+
opencv-python-headless>=4.11.0.86,<4.13
97
dm_control==1.0.38
108
packaging

benchmark/test_envpool.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
# limitations under the License.
1414
"""EnvPool python benchmark script.
1515
16+
This script expects an installed EnvPool build with native modules available.
17+
Baseline dependencies for the other benchmark scripts live in
18+
``benchmark/requirements.txt``; install the EnvPool wheel you want to
19+
benchmark separately before running this script.
20+
1621
Single Python Thread
1722
====================
1823
@@ -56,18 +61,22 @@
5661
choices=["atari", "mujoco", "vizdoom", "box2d"],
5762
)
5863
parser.add_argument("--num-envs", type=int, default=645)
59-
parser.add_argument("--batch-size", type=int, default=248)
64+
parser.add_argument("--batch-size", type=int, default=None)
6065
# num_threads == 0 means to let envpool itself determine
6166
parser.add_argument("--num-threads", type=int, default=0)
6267
# thread_affinity_offset == -1 means no thread affinity
6368
parser.add_argument("--thread-affinity-offset", type=int, default=0)
6469
parser.add_argument("--total-step", type=int, default=50000)
6570
parser.add_argument("--seed", type=int, default=0)
6671
args = parser.parse_args()
72+
if args.batch_size is None:
73+
args.batch_size = min(248, args.num_envs)
74+
elif args.batch_size > args.num_envs:
75+
raise ValueError("--batch-size must be less than or equal to --num-envs")
6776
print(args)
6877
task_id = {
6978
"atari": "Pong-v5",
70-
"mujoco": "Ant-v3",
79+
"mujoco": "Ant-v5",
7180
"vizdoom": "HealthGathering-v1",
7281
"box2d": "LunarLander-v2",
7382
}[args.env]

benchmark/test_gym.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,57 +15,74 @@
1515
import argparse
1616
import time
1717

18-
import gym
18+
import ale_py
19+
import gymnasium as gym
1920
import tqdm
2021
from atari_wrappers import wrap_deepmind
2122

2223

24+
def make_vector_env(num_envs, async_, make_env):
25+
if async_:
26+
vector_env_cls = gym.vector.AsyncVectorEnv
27+
else:
28+
vector_env_cls = gym.vector.SyncVectorEnv
29+
return vector_env_cls([make_env for _ in range(num_envs)])
30+
31+
2332
def run(env, num_envs, total_step, async_):
2433
if env == "atari":
25-
task_id = "PongNoFrameskip-v4"
34+
gym.register_envs(ale_py)
35+
task_id = "ALE/Pong-v5"
2636
frame_skip = 4
37+
make_kwargs = {"frameskip": 1}
2738
if num_envs == 1:
2839
env = wrap_deepmind(
29-
gym.make(task_id),
40+
gym.make(task_id, **make_kwargs),
3041
episode_life=False,
3142
clip_rewards=False,
3243
frame_stack=4,
3344
)
3445
else:
35-
env = gym.vector.make(
36-
task_id, num_envs, async_, lambda e:
37-
wrap_deepmind(e, episode_life=False, clip_rewards=False, frame_stack=4)
46+
env = make_vector_env(
47+
num_envs,
48+
async_,
49+
lambda: wrap_deepmind(
50+
gym.make(task_id, **make_kwargs),
51+
episode_life=False,
52+
clip_rewards=False,
53+
frame_stack=4,
54+
),
3855
)
3956
elif env == "mujoco":
40-
task_id = "Ant-v3"
57+
task_id = "Ant-v5"
4158
frame_skip = 5
4259
if num_envs == 1:
4360
env = gym.make(task_id)
4461
else:
45-
env = gym.vector.make(task_id, num_envs, async_)
62+
env = make_vector_env(num_envs, async_, lambda: gym.make(task_id))
4663
elif env == "box2d":
47-
task_id = "LunarLander-v2"
64+
task_id = "LunarLander-v3"
4865
frame_skip = 1
4966
if num_envs == 1:
5067
env = gym.make(task_id)
5168
else:
52-
env = gym.vector.make(task_id, num_envs, async_)
69+
env = make_vector_env(num_envs, async_, lambda: gym.make(task_id))
5370
else:
5471
raise NotImplementedError(f"Unknown env {env}")
55-
env.seed(0)
56-
env.reset()
72+
env.reset(seed=0)
5773
action = env.action_space.sample()
58-
done = False
74+
terminated = truncated = False
5975
t = time.time()
6076
for _ in tqdm.trange(total_step):
6177
if num_envs == 1:
62-
if done:
63-
done = False
78+
if terminated or truncated:
79+
terminated = truncated = False
6480
env.reset()
6581
else:
66-
done = env.step(action)[2]
82+
_, _, terminated, truncated, _ = env.step(action)
6783
else:
6884
env.step(action)
85+
env.close()
6986
print(f"FPS = {frame_skip * total_step * num_envs / (time.time() - t):.2f}")
7087

7188

0 commit comments

Comments
 (0)