chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,31 @@
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=450.0,
default_timesteps=2000000,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
APPOConfig()
.environment("CartPole-v1")
.training(
circular_buffer_iterations_per_batch=2,
vf_loss_coeff=0.05,
entropy_coeff=0.0,
)
.rl_module(
model_config=DefaultModelConfig(vf_share_layers=True),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,49 @@
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(default_timesteps=2000000)
parser.set_defaults(
num_agents=2,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env("env", lambda cfg: MultiAgentCartPole(config=cfg))
config = (
APPOConfig()
.environment("env", env_config={"num_agents": args.num_agents})
.training(
vf_loss_coeff=0.005,
entropy_coeff=0.0,
)
.rl_module(
model_config=DefaultModelConfig(vf_share_layers=True),
)
.multi_agent(
policy_mapping_fn=(lambda agent_id, episode, **kwargs: f"p{agent_id}"),
policies={f"p{i}" for i in range(args.num_agents)},
)
)
stop = {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 350.0 * args.num_agents,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": args.stop_timesteps,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,254 @@
"""
Multi-agent RLlib Footsies Example (APPO)
About:
- Example is based on the Footsies environment (https://github.com/chasemcd/FootsiesGym).
- Footsies is a two-player fighting game where each player controls a character and tries to hit the opponent while avoiding being hit.
- Footsies is a zero-sum game, when one player wins (+1 reward) the other loses (-1 reward).
Summary:
- Main policy is an LSTM-based policy.
- Training algorithm is APPO.
Training:
- Training is governed by adding new, more complex opponents to the mix as the main policy reaches a certain win rate threshold against the current opponent.
- Current opponent is always the newest opponent added to the mix.
- Training starts with a very simple opponent: "noop" (does nothing), then progresses to "back" (only moves backwards). These are the fixed (very simple) policies that are used to kick off the training.
- New opponents are frozen copies of the main policy at different training stages. They will be added to the mix as "lstm_v0", "lstm_v1", etc.
- In this way - after kick-starting the training with fixed simple opponents - the main policy will play against a version of itself from an earlier training stage.
- The main policy has to achieve the win rate threshold against the current opponent to add a new opponent to the mix.
- Training concludes when the target mix size is reached.
Evaluation:
- Evaluation is performed against the current (newest) opponent.
- Evaluation runs for a fixed number of episodes at the end of each training iteration.
"""
import functools
from pathlib import Path
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.core.rl_module import MultiRLModuleSpec, RLModuleSpec
from ray.rllib.env.multi_agent_env_runner import MultiAgentEnvRunner
from ray.rllib.examples.envs.classes.multi_agent.footsies.fixed_rlmodules import (
BackFixedRLModule,
NoopFixedRLModule,
)
from ray.rllib.examples.envs.classes.multi_agent.footsies.footsies_env import (
env_creator,
)
from ray.rllib.examples.envs.classes.multi_agent.footsies.utils import (
Matchmaker,
Matchup,
MetricsLoggerCallback,
MixManagerCallback,
platform_for_binary_to_download,
)
from ray.rllib.examples.rl_modules.classes.lstm_containing_rlm import (
LSTMContainingRLModuleWithTargetNetwork,
)
from ray.rllib.utils.metrics import NUM_ENV_STEPS_SAMPLED_LIFETIME
from ray.rllib.utils.test_utils import (
add_rllib_example_script_args,
)
from ray.tune.registry import register_env
from ray.tune.result import TRAINING_ITERATION
parser = add_rllib_example_script_args(
default_iters=500,
default_timesteps=5_000_000,
)
parser.add_argument(
"--train-start-port",
type=int,
default=45001,
help="First port number for the Footsies training environment server (default: 45001). Each server gets its own port.",
)
parser.add_argument(
"--eval-start-port",
type=int,
default=55001,
help="First port number for the Footsies evaluation environment server (default: 55001) Each server gets its own port.",
)
parser.add_argument(
"--binary-download-dir",
type=Path,
default="/tmp/ray/binaries/footsies",
help="Directory to download Footsies binaries (default: /tmp/ray/binaries/footsies)",
)
parser.add_argument(
"--binary-extract-dir",
type=Path,
default="/tmp/ray/binaries/footsies",
help="Directory to extract Footsies binaries (default: /tmp/ray/binaries/footsies)",
)
parser.add_argument(
"--win-rate-threshold",
type=float,
default=0.8,
help="The main policy should have at least 'win-rate-threshold' win rate against the "
"other policy to advance to the next level. Moving to the next level "
"means adding a new policy to the mix.",
)
parser.add_argument(
"--target-mix-size",
type=int,
default=5,
help="Target number of policies (RLModules) in the mix to consider the test passed. "
"The initial mix size is 2: 'main policy' vs. 'other'. "
"`--target-mix-size=5` means that 3 new policies will be added to the mix. "
"Whether to add new policy is decided by checking the '--win-rate-threshold' condition. ",
)
parser.add_argument(
"--rollout-fragment-length",
type=int,
default=256,
help="The length of each rollout fragment to be collected by the EnvRunners when sampling.",
)
parser.add_argument(
"--log-unity-output",
action="store_true",
help="Whether to log Unity output (from the game engine). Default is False.",
default=False,
)
parser.add_argument(
"--render",
action="store_true",
default=False,
help="Whether to render the Footsies environment. Default is False.",
)
main_policy = "lstm"
args = parser.parse_args()
register_env(name="FootsiesEnv", env_creator=env_creator)
# Detect platform and choose appropriate binary
binary_to_download = platform_for_binary_to_download(args.render)
config = (
APPOConfig()
.reporting(
min_time_s_per_iteration=30,
)
.environment(
env="FootsiesEnv",
env_config={
"max_t": 1000,
"frame_skip": 4,
"observation_delay": 16,
"train_start_port": args.train_start_port,
"eval_start_port": args.eval_start_port,
"host": "localhost",
"binary_download_dir": args.binary_download_dir,
"binary_extract_dir": args.binary_extract_dir,
"binary_to_download": binary_to_download,
"log_unity_output": args.log_unity_output,
},
)
.learners(
num_learners=1,
num_cpus_per_learner=1,
num_gpus_per_learner=0,
num_aggregator_actors_per_learner=2,
)
.env_runners(
env_runner_cls=MultiAgentEnvRunner,
num_env_runners=args.num_env_runners or 1,
num_cpus_per_env_runner=1,
num_envs_per_env_runner=1,
batch_mode="truncate_episodes",
rollout_fragment_length=args.rollout_fragment_length,
episodes_to_numpy=True,
create_env_on_local_worker=False,
)
.training(
train_batch_size_per_learner=4096 * (args.num_env_runners or 1),
lr=1e-4,
entropy_coeff=0.01,
)
.multi_agent(
policies={
main_policy,
"noop",
"back",
},
# this is a starting policy_mapping_fn
# It will be updated by the MixManagerCallback during training.
policy_mapping_fn=Matchmaker(
[Matchup(main_policy, "noop", 1.0)]
).agent_to_module_mapping_fn,
# we only train the main policy, this doesn't change during training.
policies_to_train=[main_policy],
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
main_policy: RLModuleSpec(
module_class=LSTMContainingRLModuleWithTargetNetwork,
model_config={
"lstm_cell_size": 128,
"dense_layers": [128, 128],
"max_seq_len": 64,
},
),
# for simplicity, all fixed RLModules are added to the config at the start.
# However, only "noop" is used at the start of training,
# the others are added to the mix later by the MixManagerCallback.
"noop": RLModuleSpec(module_class=NoopFixedRLModule),
"back": RLModuleSpec(module_class=BackFixedRLModule),
},
)
)
.evaluation(
evaluation_num_env_runners=args.evaluation_num_env_runners or 1,
evaluation_sample_timeout_s=120,
evaluation_interval=1,
evaluation_duration=10, # 10 episodes is enough to get a good win rate estimate
evaluation_duration_unit="episodes",
evaluation_parallel_to_training=False,
# we may add new RLModules to the mix at the end of the evaluation stage.
# Running evaluation in parallel may result in training for one more iteration on the old mix.
evaluation_force_reset_envs_before_iteration=True,
evaluation_config={
"env_config": {"env-for-evaluation": True},
}, # evaluation_config is used to add an argument to the env creator.
)
.callbacks(
[
functools.partial(
MetricsLoggerCallback,
main_policy=main_policy,
),
functools.partial(
MixManagerCallback,
win_rate_threshold=args.win_rate_threshold,
main_policy=main_policy,
target_mix_size=args.target_mix_size,
starting_modules=[main_policy, "noop"],
fixed_modules_progression_sequence=(
"noop",
"back",
),
),
]
)
)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
TRAINING_ITERATION: args.stop_iters,
"mix_size": args.target_mix_size,
}
if __name__ == "__main__":
from ray.rllib.utils.test_utils import run_rllib_example_script_experiment
results = run_rllib_example_script_experiment(
base_config=config,
args=args,
stop=stop,
success_metric={
"mix_size": args.target_mix_size
}, # pass the success metric for RLlib's testing framework
)
@@ -0,0 +1,110 @@
import random
import gymnasium as gym
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.connectors.env_to_module.frame_stacking import FrameStackingEnvToModule
from ray.rllib.connectors.learner.frame_stacking import FrameStackingLearner
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.multi_agent_env import make_multi_agent
from ray.rllib.env.wrappers.atari_wrappers import wrap_atari_for_new_api_stack
from ray.rllib.examples.rl_modules.classes.random_rlm import RandomRLModule
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=0.0,
default_timesteps=20000000,
default_iters=400,
)
parser.set_defaults(
env="ale_py:ALE/Pong-v5",
num_agents=2,
)
args = parser.parse_args()
def _make_env_to_module_connector(env, spaces, device):
return FrameStackingEnvToModule(num_frames=4, multi_agent=True)
def _make_learner_connector(input_observation_space, input_action_space):
return FrameStackingLearner(num_frames=4, multi_agent=True)
def _env_creator(cfg):
return wrap_atari_for_new_api_stack(
gym.make(args.env, **cfg, **{"render_mode": "rgb_array"}),
dim=64,
framestack=None,
)
MultiAgentPong = make_multi_agent(_env_creator)
NUM_POLICIES = 5
main_spec = RLModuleSpec(
model_config=DefaultModelConfig(
vf_share_layers=True,
conv_filters=[(16, 4, 2), (32, 4, 2), (64, 4, 2), (128, 4, 2)],
conv_activation="relu",
head_fcnet_hiddens=[256],
),
)
config = (
APPOConfig()
.environment(
MultiAgentPong,
env_config={
"num_agents": args.num_agents,
# Make analogous to old v4 + NoFrameskip.
"frameskip": 1,
"full_action_space": False,
"repeat_action_probability": 0.0,
},
clip_rewards=True,
)
.env_runners(
env_to_module_connector=_make_env_to_module_connector,
)
.learners(
num_aggregator_actors_per_learner=2,
)
.training(
learner_connector=_make_learner_connector,
train_batch_size_per_learner=500,
target_network_update_freq=2,
lr=0.0005 * ((args.num_learners or 1) ** 0.5),
vf_loss_coeff=1.0,
entropy_coeff=[[0, 0.01], [3000000, 0.0]], # <- crucial parameter to finetune
# Only update connector states and model weights every n training_step calls.
broadcast_interval=5,
# learner_queue_size=1,
circular_buffer_num_batches=4,
circular_buffer_iterations_per_batch=2,
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs=(
{f"p{i}": main_spec for i in range(NUM_POLICIES)}
| {"random": RLModuleSpec(module_class=RandomRLModule)}
),
),
)
.multi_agent(
policies={f"p{i}" for i in range(NUM_POLICIES)} | {"random"},
policy_mapping_fn=lambda aid, eps, **kw: (
random.choice([f"p{i}" for i in range(NUM_POLICIES)] + ["random"])
),
policies_to_train=[f"p{i}" for i in range(NUM_POLICIES)],
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,61 @@
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentStatelessCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(default_timesteps=2000000)
parser.set_defaults(
num_agents=2,
num_env_runners=6,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env("env", lambda cfg: MultiAgentStatelessCartPole(config=cfg))
config = (
APPOConfig()
.environment("env", env_config={"num_agents": args.num_agents})
# TODO (sven): Need to fix the MeanStdFilter(). It seems to cause NaNs when
# training.
# .env_runners(
# env_to_module_connector=lambda env, spaces, device: MeanStdFilter(multi_agent=True),
# )
.training(
train_batch_size_per_learner=600,
lr=0.0005 * ((args.num_learners or 1) ** 0.5),
num_epochs=1,
vf_loss_coeff=0.05,
entropy_coeff=0.005,
)
.rl_module(
model_config=DefaultModelConfig(
vf_share_layers=True,
use_lstm=True,
max_seq_len=20,
),
)
.multi_agent(
policy_mapping_fn=(lambda agent_id, episode, **kwargs: f"p{agent_id}"),
policies={f"p{i}" for i in range(args.num_agents)},
)
)
stop = {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 150.0 * args.num_agents,
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,48 @@
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=-300.0,
default_timesteps=100000000,
)
parser.set_defaults(
num_env_runners=4,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
APPOConfig()
.environment("Pendulum-v1")
.env_runners(
num_envs_per_env_runner=20,
)
.learners(num_learners=1)
.training(
train_batch_size_per_learner=500,
circular_buffer_num_batches=16,
circular_buffer_iterations_per_batch=10,
target_network_update_freq=2,
clip_param=0.4,
lr=0.0003,
gamma=0.95,
lambda_=0.5,
entropy_coeff=0.0,
use_kl_loss=True,
kl_coeff=1.0,
kl_target=0.04,
)
.rl_module(
model_config=DefaultModelConfig(fcnet_activation="relu"),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,87 @@
import gymnasium as gym
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.connectors.env_to_module.frame_stacking import FrameStackingEnvToModule
from ray.rllib.connectors.learner.frame_stacking import FrameStackingLearner
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.env.wrappers.atari_wrappers import wrap_atari_for_new_api_stack
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(
default_reward=20.0,
default_timesteps=10_000_000,
)
parser.set_defaults(
env="ale_py:ALE/Pong-v5",
)
args = parser.parse_args()
def _make_env_to_module_connector(env, spaces, device):
return FrameStackingEnvToModule(num_frames=4)
def _make_learner_connector(input_observation_space, input_action_space):
return FrameStackingLearner(num_frames=4)
def _env_creator(cfg):
return wrap_atari_for_new_api_stack(
gym.make(args.env, **cfg, **{"render_mode": "rgb_array"}),
dim=64,
framestack=None,
)
register_env("env", _env_creator)
config = (
APPOConfig()
.environment(
"env",
env_config={
# Make analogous to old v4 + NoFrameskip.
"frameskip": 1,
"full_action_space": False,
"repeat_action_probability": 0.0,
},
clip_rewards=True,
)
.env_runners(
env_to_module_connector=_make_env_to_module_connector,
num_envs_per_env_runner=2,
)
.learners(
num_aggregator_actors_per_learner=2,
)
.training(
learner_connector=_make_learner_connector,
train_batch_size_per_learner=500,
target_network_update_freq=2,
lr=0.0005 * ((args.num_learners or 1) ** 0.5),
vf_loss_coeff=1.0,
entropy_coeff=[[0, 0.01], [3000000, 0.0]], # <- crucial parameter to finetune
# Only update connector states and model weights every n training_step calls.
broadcast_interval=5,
# learner_queue_size=1,
circular_buffer_num_batches=4,
circular_buffer_iterations_per_batch=2,
)
.rl_module(
model_config=DefaultModelConfig(
vf_share_layers=True,
conv_filters=[(16, 4, 2), (32, 4, 2), (64, 4, 2), (128, 4, 2)],
conv_activation="relu",
head_fcnet_hiddens=[256],
)
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,47 @@
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.connectors.env_to_module.mean_std_filter import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.stateless_cartpole import StatelessCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=2000000,
default_reward=300.0,
)
parser.set_defaults(
num_env_runners=3,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
APPOConfig()
.environment(StatelessCartPole)
.env_runners(
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(),
)
.training(
lr=0.0005 * ((args.num_learners or 1) ** 0.5),
num_epochs=1,
vf_loss_coeff=0.05,
entropy_coeff=0.005,
use_circular_buffer=False,
broadcast_interval=10,
)
.rl_module(
model_config=DefaultModelConfig(
vf_share_layers=True,
use_lstm=True,
max_seq_len=20,
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,183 @@
"""Example of how to write a custom APPO that uses a global shared data actor.
The actor is custom code and its remote APIs can be designed as the user requires.
It is created inside the Algorithm's `setup` method and then shared through its
reference with all of the Algorithm's other actors, like EnvRunners, Learners, and
aggregator actors.
During sampling and through using callbacks, each EnvRunner assigns a unique ID
to each sampled episode chunk, then sends manipulated reward data for each sampled
episode chunk to the shared data actor. In particular, the manipulation consists of
each individual reward being multiplied by the EnvRunner's index (from 1 to ...).
Note that the actual reward in the episode is not altered and thus the metrics
reporting continues to show the original reward.
In the learner connector, which creates the train batch from episode data, a custom
connector piece then gets the manipulated rewards from the shared data actor using
the episode chunk's unique ID (see above) and uses the manipulated reward for training.
Note that because of this, different EnvRunners provide different reward signals, which
should make it slightly harder for the value function to learn consistently.
Nevertheless, because the default config here only uses 2 EnvRunners, each multiplying
their rewards by 1 and 2, respectively, this effect is negligible here and the example
should learn how to solve the CartPole-1 env either way.
This example shows:
- how to write a custom, global shared data actor class with a custom remote API.
- how an instance of this shared data actor is created upon algorithm
initialization.
- how to distribute the actor reference of the shared actor to all other actors
in the Algorithm, for example EnvRunners, AggregatorActors, and Learners
- how to subclass an existing algorithm class (APPO) to implement a custom
Algorithm, overriding the `setup` method to control, which additional actors
should be created (and shared) by the algo, the `get_state/set_state` methods
to include the state of the new actor.
- how - through custom callbacks - the new actor can be written to and queried
from anywhere within the algorithm, for example its EnvRunner actors or Learners.
How to run this script
----------------------
`python [script file name].py`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
The experiment should work regardless of whether you are using aggregator
actors or not. By default, the experiment provides one agg. actor per Learner,
but you can set `--num-aggregator-actors-per-learner=0` to have the learner
connector pipeline work directly inside the Learner actor(s).
+-------------------------------------------------+------------+--------+
| Trial name | status | iter |
| | | |
|-------------------------------------------------+------------+--------+
| APPOWithSharedDataActor_CartPole-v1_4e860_00000 | TERMINATED | 7 |
+-------------------------------------------------+------------+--------+
+------------------+------------------------+
| total time (s) | episode_return_mean |
| | |
|------------------+------------------------+
| 70.0315 | 468.42 |
+------------------+------------------------+
"""
import uuid
import ray
from ray.rllib.algorithms.appo import APPOConfig
from ray.rllib.connectors.connector_v2 import ConnectorV2
from ray.rllib.core import Columns
from ray.rllib.env.single_agent_episode import SingleAgentEpisode
from ray.rllib.examples.algorithms.classes.appo_w_shared_data_actor import (
APPOWithSharedDataActor,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=450.0,
default_iters=200,
default_timesteps=2000000,
)
parser.set_defaults(
num_aggregator_actors_per_learner=1,
)
SPECIAL_REWARDS_KEY = "special_(double)_rewards"
ENV_RUNNER_IDX_KEY = "env_runner_index"
UNIQUE_EPISODE_CHUNK_KEY = "unique_eps_chunk"
# Define 2 simple EnvRunner-based callbacks:
def on_episode_step(*, episode, env_runner, **kwargs):
# Multiplies the received reward by the env runner index.
if SPECIAL_REWARDS_KEY not in episode.custom_data:
episode.custom_data[SPECIAL_REWARDS_KEY] = []
episode.custom_data[SPECIAL_REWARDS_KEY].append(
episode.get_rewards(-1) * env_runner.worker_index
)
def on_sample_end(*, samples, env_runner, **kwargs):
# Sends the (manipulated) reward sequence to the shared data actor for "pickup" by
# a Learner. Alternatively, one could also just store the information in the
# `custom_data` property.
for episode in samples:
# Provide a unique key for both episode AND record in the shared
# data actor.
unique_key = str(uuid.uuid4())
# Store the EnvRunner index and unique key in the episode.
episode.custom_data[ENV_RUNNER_IDX_KEY] = env_runner.worker_index
episode.custom_data[UNIQUE_EPISODE_CHUNK_KEY] = unique_key
# Get the manipulated rewards from the episode ..
special_rewards = episode.custom_data.pop(SPECIAL_REWARDS_KEY)
# .. and send them under the unique key to the shared data actor.
env_runner._shared_data_actor.put.remote(
key=unique_key,
value=special_rewards,
)
class ManipulatedRewardConnector(ConnectorV2):
def __call__(self, *, episodes, batch, metrics, **kwargs):
if not isinstance(episodes[0], SingleAgentEpisode):
raise ValueError("This connector only works on `SingleAgentEpisodes`.")
# Get the manipulated rewards from the shared actor and add them to the train
# batch.
for sa_episode in self.single_agent_episode_iterator(episodes):
unique_key = sa_episode.custom_data[UNIQUE_EPISODE_CHUNK_KEY]
special_rewards = ray.get(
self._shared_data_actor.get.remote(unique_key, delete=True)
)
if special_rewards is None:
continue
assert int(special_rewards[0]) == sa_episode.custom_data[ENV_RUNNER_IDX_KEY]
# Add one more fake reward, b/c all episodes will be extended
# (in PPO-style algos) by one artificial timestep for GAE/v-trace
# computation purposes.
special_rewards += [0.0]
self.add_n_batch_items(
batch=batch,
column=Columns.REWARDS,
items_to_add=special_rewards[-len(sa_episode) :],
num_items=len(sa_episode),
single_agent_episode=sa_episode,
)
return batch
if __name__ == "__main__":
args = parser.parse_args()
base_config = (
APPOConfig(algo_class=APPOWithSharedDataActor)
.environment("CartPole-v1")
.callbacks(
on_episode_step=on_episode_step,
on_sample_end=on_sample_end,
)
.training(
learner_connector=(lambda obs_sp, act_sp: ManipulatedRewardConnector()),
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,356 @@
"""
schema={
a_t: int64,
r_t: float,
episode_return: float,
o_tp1: list<item: binary>,
episode_id: int64,
a_tp1: int64,
o_t: list<item: binary>,
d_t: float
}
"""
import os
from typing import Optional
import cv2
import gymnasium as gym
import numpy as np
import wandb
from ray import tune
from ray.rllib.algorithms.bc import BCConfig
from ray.rllib.connectors.connector_v2 import ConnectorV2
from ray.rllib.core import ALL_MODULES
from ray.rllib.core.columns import Columns
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.env.wrappers.atari_wrappers import wrap_atari_for_new_api_stack
from ray.rllib.examples.utils import add_rllib_example_script_args
from ray.rllib.utils.annotations import override
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
LEARNER_RESULTS,
NUM_ENV_STEPS_TRAINED_LIFETIME,
)
from ray.rllib.utils.test_utils import should_stop
# Define a `ConnectorV2` to decode stacked encoded Atari frames.
class DecodeObservations(ConnectorV2):
def __init__(
self,
input_observation_space: Optional[gym.Space] = None,
input_action_space: Optional[gym.Space] = None,
*,
multi_agent: bool = False,
as_learner_connector: bool = True,
**kwargs,
):
"""Decodes observation from PNG to numpy array.
Note, `rl_unplugged`'s stored observations are framestacked with
four frames per observation. This connector returns therefore
decoded observations of shape `(64, 64, 4)`.
Args:
multi_agent: Whether this is a connector operating on a multi-agent
observation space mapping AgentIDs to individual agents' observations.
as_learner_connector: Whether this connector is part of a Learner connector
pipeline, as opposed to an env-to-module pipeline.
"""
super().__init__(
input_observation_space=input_observation_space,
input_action_space=input_action_space,
**kwargs,
)
self._multi_agent = multi_agent
self._as_learner_connector = as_learner_connector
@override(ConnectorV2)
def recompute_output_observation_space(
self, input_observation_space, input_action_space
):
return gym.spaces.Box(
-1.0, 1.0, (64, 64, 4), float
) # <- to keep it simple hardcoded to a fixed space
@override(ConnectorV2)
def __call__(
self,
*,
rl_module,
data,
episodes,
explore=None,
shared_data=None,
**kwargs,
):
for sa_episode in self.single_agent_episode_iterator(
episodes, agents_that_stepped_only=False
):
# Map encoded PNGs into arrays of shape (64, 64, 4).
def _map_fn(s):
# Preallocate the result array with shape (64, 64, 4)
result = np.empty((64, 64, 4), dtype=np.uint8)
for i in range(4):
# Convert byte data to a numpy array of uint8
nparr = np.frombuffer(s[i], np.uint8)
# Decode the image as grayscale
img = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
# Resize the image to 64x64 using an efficient interpolation method
resized = cv2.resize(img, (64, 64), interpolation=cv2.INTER_AREA)
result[:, :, i] = resized
return (result.astype(np.float32) / 128.0) - 1.0
# Add the observations for t.
self.add_n_batch_items(
batch=data,
column=Columns.OBS,
# Ensure, we pass in a list, otherwise it is considered
# an already batched array.
items_to_add=[
_map_fn(
sa_episode.get_observations(slice(0, len(sa_episode)))[0],
)
],
num_items=len(sa_episode),
single_agent_episode=sa_episode,
)
# Add the observations for t+1.
self.add_n_batch_items(
batch=data,
column=Columns.NEXT_OBS,
items_to_add=[
_map_fn(
sa_episode.get_observations(slice(1, len(sa_episode) + 1))[0],
)
],
num_items=len(sa_episode),
single_agent_episode=sa_episode,
)
return data
# Make the learner connector.
def _make_learner_connector(observation_space, action_space):
return DecodeObservations()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values toset up `config` below.
parser = add_rllib_example_script_args(
default_reward=21.0,
default_timesteps=3000000000,
default_iters=100000000000,
)
args = parser.parse_args()
# If multiple learners are requested define a scheduling
# strategy with best data locality.
if args.num_learners and args.num_learners > 1:
import ray
ray.init()
# Check, if we have a multi-node cluster.
nodes = ray.nodes()
ray.shutdown()
print(f"Number of nodes in cluster: {len(nodes)}")
# If we have a multi-node cluster spread learners.
if len(nodes) > 1:
os.environ["TRAIN_ENABLE_WORKER_SPREAD_ENV"] = "1"
print(
"Multi-node cluster and multi-learner setup. "
"Using a 'SPREAD' scheduling strategy for learners"
"to support data locality."
)
# Otherwise pack the learners on the single node.
else:
print(
"Single-node cluster and multi-learner setup. "
"Using a 'PACK' scheduling strategy for learners"
"to support data locality."
)
# Wrap the environment used in evalaution into `RLlib`'s Atari Wrapper
# that automatically stacks frames and converts to the dimension used
# in the collection of the `rl_unplugged` data.
def _env_creator(cfg):
return wrap_atari_for_new_api_stack(
gym.make("ale_py:ALE/Pong-v5", **cfg),
framestack=4,
dim=64,
)
# Register the wrapped environment to `tune`. Note, environment registration
# to Ray Tune must happen after checking the number of nodes, otherwise the
# registration is removed.
tune.register_env("WrappedALE/Pong-v5", _env_creator)
# Anyscale RLUnplugged storage bucket. The bucket contains from the
# original `RLUnplugged` bucket only the first `atari/Pong` run.
# TODO (simon, artur): Create an extra bucket for the data and do not
# use the `ANYSCALE_ARTIFACT_STORAGE`.
anyscale_storage_bucket = os.environ["ANYSCALE_ARTIFACT_STORAGE"]
anyscale_rlunplugged_atari_path = anyscale_storage_bucket + "/rllib/rl_unplugged/atari"
# We only use the Atari game `Pong` here. Users can choose other Atari
# games and set here the name.
game = "Pong"
# Path to the directory with all runs from Atari Pong.
anyscale_rlunplugged_atari_pong_path = anyscale_rlunplugged_atari_path + f"/{game}"
print(
"Streaming RLUnplugged Atari Pong data from path: "
f"{anyscale_rlunplugged_atari_pong_path}"
)
# Define the config for Behavior Cloning.
config = (
BCConfig()
.environment(
env="WrappedALE/Pong-v5",
clip_rewards=True,
env_config={
# Make analogous to old v4 + NoFrameskip.
"frameskip": 4,
"full_action_space": False,
"repeat_action_probability": 0.0,
},
)
# Use the new API stack that makes directly use of `ray.data`.
.api_stack(
enable_rl_module_and_learner=True,
enable_env_runner_and_connector_v2=True,
)
# Evaluate in the actual environment online.
.evaluation(
evaluation_interval=1,
evaluation_num_env_runners=1,
evaluation_duration=5,
evaluation_parallel_to_training=True,
evaluation_config=BCConfig.overrides(exploration=False),
)
.learners(
num_learners=args.num_learners,
num_gpus_per_learner=args.num_gpus_per_learner,
)
# Note, the `input_` argument is the major argument for the
# new offline API. Via the `input_read_method_kwargs` the
# arguments for the `ray.data.Dataset` read method can be
# configured. The read method needs at least as many blocks
# as remote learners.
.offline_data(
input_=[anyscale_rlunplugged_atari_pong_path],
# `rl_unplugged`'s data schema is different from the one used
# internally in `RLlib`. Define the schema here so it can be used
# when transforming column data to episodes.
input_read_schema={
Columns.EPS_ID: "episode_id",
Columns.OBS: "o_t",
Columns.ACTIONS: "a_t",
Columns.REWARDS: "r_t",
Columns.NEXT_OBS: "o_tp1",
Columns.TERMINATEDS: "d_t",
},
# Do not materialize data, instead stream the data from Anyscale's
# S3 bucket (note, streaming data is an Anyscale-platform-only feature).
materialize_data=False,
materialize_mapped_data=False,
# Increase the parallelism in transforming batches, such that while
# training, new batches are transformed while others are used in updating.
map_batches_kwargs={
"concurrency": 40 * (max(args.num_learners, 1) or 1),
"num_cpus": 1,
},
# When iterating over batches in the dataset, prefetch at least 4
# batches per learner.
iter_batches_kwargs={
"prefetch_batches": 10,
},
# Iterate over 200 batches per RLlib iteration if multiple learners
# are used.
dataset_num_iters_per_learner=200,
)
.training(
# To increase learning speed with multiple learners,
# increase the learning rate correspondingly.
lr=0.0001
* max(
1,
(args.num_learners if args.num_learners and args.num_learners > 1 else 1)
** 0.5,
),
train_batch_size_per_learner=2048,
# Use the defined learner connector above, to decode observations.
learner_connector=_make_learner_connector,
)
.rl_module(
model_config=DefaultModelConfig(
conv_filters=[[16, 4, 2], [32, 4, 2], [64, 4, 2], [128, 4, 2]],
conv_activation="relu",
head_fcnet_hiddens=[256],
),
)
.debugging(
log_level="ERROR",
)
)
# Stop, if either the maximum point in Pong is reached (21.0) or 10 million steps
# were trained.
stop = {
f"{EVALUATION_RESULTS} / {ENV_RUNNER_RESULTS} / {EPISODE_RETURN_MEAN}": args.stop_reward,
f"{LEARNER_RESULTS} / {ALL_MODULES} / {NUM_ENV_STEPS_TRAINED_LIFETIME}": args.stop_timesteps,
}
# Build the algorithm.
algo = config.build()
# Shall we use wandb for logging results?
if args.wandb_key:
# Login to wandb.
wandb.login(
key=args.wandb_key,
verify=True,
relogin=True,
force=True,
)
# Initialize wandb.
wandb.init(project=args.wandb_project)
# Clean results to log seemlessly to wandb.
from ray.air.integrations.wandb import _clean_log
i = 0
while True:
print("---------------------------------------------------------------")
print(f"Iteration {i + 1}")
results = algo.train()
print(results)
if args.wandb_key:
# Log results to wandb.
wandb.log(data=_clean_log(results), step=i)
if stop:
if should_stop(stop, results):
algo.cleanup()
break
i += 1
print("------------------------------------------------")
print()
print("Training finished:\n")
print(
f"Mean Episode Return in Evaluation: {results[EVALUATION_RESULTS][ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN]}"
)
print(
f"Number of Environment Steps trained: {results[LEARNER_RESULTS][ALL_MODULES][NUM_ENV_STEPS_TRAINED_LIFETIME]}"
)
print("================================================")
@@ -0,0 +1,91 @@
import warnings
from pathlib import Path
from ray.rllib.algorithms.bc import BCConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
)
from ray.tune.result import TRAINING_ITERATION
parser = add_rllib_example_script_args()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
assert (
args.env == "CartPole-v1" or args.env is None
), "This tuned example works only with `CartPole-v1`."
# Define the data paths.
data_path = "offline/tests/data/cartpole/cartpole-v1_large"
base_path = Path(__file__).parents[3]
data_path = "local://" / base_path / data_path
print(f"data_path={data_path}")
# Define the BC config.
config = (
BCConfig()
.environment("CartPole-v1")
# Note, the `input_` argument is the major argument for the
# new offline API. Via the `input_read_method_kwargs` the
# arguments for the `ray.data.Dataset` read method can be
# configured. The read method needs at least as many blocks
# as remote learners.
.offline_data(
input_=[data_path.as_posix()],
# Concurrency defines the number of processes that run the
# `map_batches` transformations. This should be aligned with the
# 'prefetch_batches' argument in 'iter_batches_kwargs'.
map_batches_kwargs={"concurrency": 2, "num_cpus": 1},
# This data set is small so do not prefetch too many batches and use no
# local shuffle.
iter_batches_kwargs={"prefetch_batches": 1},
# The number of iterations to be run per learner when in multi-learner
# mode in a single RLlib training iteration. Leave this to `None` to
# run an entire epoch on the dataset during a single RLlib training
# iteration.
dataset_num_iters_per_learner=5,
)
.training(
train_batch_size_per_learner=1024,
# To increase learning speed with multiple learners,
# increase the learning rate correspondingly.
lr=0.0008 * (args.num_learners or 1) ** 0.5,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
),
)
.evaluation(
evaluation_interval=3,
evaluation_num_env_runners=1,
evaluation_duration=5,
evaluation_parallel_to_training=True,
evaluation_config=BCConfig.overrides(explore=False),
)
)
if not args.no_tune:
warnings.warn(
"You are running the example with Ray Tune. Offline RL uses "
"Ray Data, which doesn't interact seamlessly with Ray Tune. "
"If you encounter difficulties try to run the example without "
"Ray Tune using `--no-tune`."
)
stop = {
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 350.0,
TRAINING_ITERATION: 350,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,123 @@
import warnings
from pathlib import Path
from ray.rllib.algorithms.bc import BCConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
)
from ray.tune.result import TRAINING_ITERATION
parser = add_rllib_example_script_args()
parser.add_argument(
"--offline-evaluation-interval",
type=int,
default=1,
help=(
"The interval in which offline evaluation should run in relation "
"to training iterations, e.g. if 1 offline evaluation runs in each "
"iteration, if 3 it runs each 3rd training iteration."
),
)
parser.add_argument(
"--num-offline-eval-runners",
type=int,
default=2,
help=("The number of offline evaluation runners to be used in offline evaluation."),
)
parser.add_argument(
"--num-gpus-per-offline-eval-runner",
type=float,
default=0.0,
help=(
"The number of GPUs to be used in offline evaluation per offline "
"evaluation runner. Can be fractional."
),
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
assert (
args.env == "CartPole-v1" or args.env is None
), "This tuned example works only with `CartPole-v1`."
# Define the data paths.
data_path = "offline/tests/data/cartpole/cartpole-v1_large"
base_path = Path(__file__).parents[3]
print(f"base_path={base_path}")
data_path = "local://" / base_path / data_path
print(f"data_path={data_path}")
# Define the BC config.
config = (
BCConfig()
.environment(
"CartPole-v1",
)
# Note, the `input_` argument is the major argument for the
# new offline API. Via the `input_read_method_kwargs` the
# arguments for the `ray.data.Dataset` read method can be
# configured. The read method needs at least as many blocks
# as remote learners.
.offline_data(
input_=[data_path.as_posix()],
# Concurrency defines the number of processes that run the
# `map_batches` transformations. This should be aligned with the
# 'prefetch_batches' argument in 'iter_batches_kwargs'.
map_batches_kwargs={"concurrency": 2, "num_cpus": 1},
# This data set is small so do not prefetch too many batches and use no
# local shuffle.
iter_batches_kwargs={"prefetch_batches": 1},
# The number of iterations to be run per learner when in multi-learner
# mode in a single RLlib training iteration. Leave this to `None` to
# run an entire epoch on the dataset during a single RLlib training
# iteration.
dataset_num_iters_per_learner=5,
)
.training(
train_batch_size_per_learner=1024,
# To increase learning speed with multiple learners,
# increase the learning rate correspondingly.
lr=0.0008 * (args.num_learners or 1) ** 0.5,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
),
)
.evaluation(
evaluation_interval=1,
evaluation_parallel_to_training=False,
evaluation_config=BCConfig.overrides(explore=False),
offline_evaluation_interval=1,
offline_evaluation_type="eval_loss",
num_offline_eval_runners=args.num_offline_eval_runners,
num_gpus_per_offline_eval_runner=args.num_gpus_per_offline_eval_runner,
offline_eval_batch_size_per_runner=128,
)
)
if not args.no_tune:
warnings.warn(
"You are running the example with Ray Tune. Offline RL uses "
"Ray Data, which doesn't interact seamlessly with Ray Tune. "
"If you encounter difficulties try to run the example without "
"Ray Tune using `--no-tune`."
)
stop = {
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 350.0,
TRAINING_ITERATION: 350,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,88 @@
from pathlib import Path
from ray.rllib.algorithms.bc import BCConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
)
from ray.tune.result import TRAINING_ITERATION
parser = add_rllib_example_script_args()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
assert (
args.env == "Pendulum-v1" or args.env is None
), "This tuned example works only with `Pendulum-v1`."
# Define the data paths.
data_path = "offline/tests/data/pendulum/pendulum-v1_large"
base_path = Path(__file__).parents[3]
print(f"base_path={base_path}")
data_path = "local://" / base_path / data_path
print(f"data_path={data_path}")
# Define the BC config.
config = (
BCConfig()
.environment(env="Pendulum-v1")
.api_stack(
enable_rl_module_and_learner=True,
enable_env_runner_and_connector_v2=True,
)
.evaluation(
evaluation_interval=3,
evaluation_num_env_runners=1,
evaluation_duration=5,
evaluation_parallel_to_training=True,
evaluation_config=BCConfig.overrides(explore=False),
)
# Note, the `input_` argument is the major argument for the
# new offline API. Via the `input_read_method_kwargs` the
# arguments for the `ray.data.Dataset` read method can be
# configured. The read method needs at least as many blocks
# as remote learners.
.offline_data(
input_=[data_path.as_posix()],
# Concurrency defines the number of processes that run the
# `map_batches` transformations. This should be aligned with the
# 'prefetch_batches' argument in 'iter_batches_kwargs'.
map_batches_kwargs={"concurrency": 2, "num_cpus": 2},
# This data set is small so do not prefetch too many batches and use no
# local shuffle.
iter_batches_kwargs={
"prefetch_batches": 1,
},
# The number of iterations to be run per learner when in multi-learner
# mode in a single RLlib training iteration. Leave this to `None` to
# run an entire epoch on the dataset during a single RLlib training
# iteration. For single-learner mode, 1 is the only option.
dataset_num_iters_per_learner=1 if not args.num_learners else None,
)
.training(
# To increase learning speed with multiple learners,
# increase the learning rate correspondingly.
lr=0.0008 * (args.num_learners or 1) ** 0.5,
train_batch_size_per_learner=1024,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
),
)
)
stop = {
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -200.0,
TRAINING_ITERATION: 350,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,88 @@
from typing import List
import ray
from ray.rllib.algorithms import AlgorithmConfig
from ray.rllib.algorithms.appo import APPO
from ray.rllib.env.env_runner_group import EnvRunnerGroup
@ray.remote
class SharedDataActor:
"""Simple example of an actor that's accessible from all other actors of an algo.
Exposes remote APIs `put` and `get` to other actors for storing and retrieving
arbitrary data.
"""
def __init__(self):
self.storage = {}
def get(self, key, delete: bool = False):
value = self.storage.get(key)
if delete and key in self.storage:
del self.storage[key]
return value
def put(self, key, value):
self.storage[key] = value
def get_state(self):
return self.storage
def set_state(self, state):
self.storage = state
class APPOWithSharedDataActor(APPO):
def setup(self, config: AlgorithmConfig):
# Call to parent `setup`.
super().setup(config)
# Create shared data actor.
self.shared_data_actor = SharedDataActor.remote()
# Share the actor with all other relevant actors.
def _share(actor, shared_act=self.shared_data_actor):
actor._shared_data_actor = shared_act
# Also add shared actor reference to all the learner connector pieces,
# if applicable.
if hasattr(actor, "_learner_connector") and actor._learner_connector:
for conn in actor._learner_connector:
conn._shared_data_actor = shared_act
self.env_runner_group.foreach_env_runner(func=_share)
if self.eval_env_runner_group:
self.eval_env_runner_group.foreach_env_runner(func=_share)
self.learner_group.foreach_learner(func=_share)
if self._aggregator_actor_manager:
self._aggregator_actor_manager.foreach_actor(func=_share)
def get_state(self, *args, **kwargs):
state = super().get_state(*args, **kwargs)
# Add shared actor's state.
state["shared_data_actor"] = ray.get(self.shared_data_actor.get_state.remote())
return state
def set_state(self, state, *args, **kwargs):
super().set_state(state, *args, **kwargs)
# Set shared actor's state.
if "shared_data_actor" in state:
self.shared_data_actor.set_state.remote(state["shared_data_actor"])
def restore_env_runners(self, env_runner_group: EnvRunnerGroup) -> List[int]:
restored = super().restore_env_runners(env_runner_group)
# For the restored EnvRunners, send them the latest shared, global state
# from the `SharedDataActor`.
for restored_idx in restored:
state_ref = self.shared_data_actor.get.remote(
key=f"EnvRunner_{restored_idx}"
)
env_runner_group.foreach_env_runner(
lambda env_runner, state=state_ref: env_runner._global_state,
remote_worker_ids=[restored_idx],
timeout_seconds=0.0,
)
return restored
@@ -0,0 +1,35 @@
from typing import TYPE_CHECKING, Any, Dict
from ray.rllib.core.learner.torch.torch_differentiable_learner import (
TorchDifferentiableLearner,
)
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
from ray.rllib.utils.typing import ModuleID, TensorType
if TYPE_CHECKING:
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
torch, nn = try_import_torch()
class MAMLTorchDifferentiableLearner(TorchDifferentiableLearner):
"""A `TorchDifferentiableLearner` to perform MAML learning.
This `TorchDifferentiableLearner`
- defines a funcitonal MSE loss for learning simple (here non-linear)
prediction.
"""
@override(TorchDifferentiableLearner)
def compute_loss_for_module(
self,
*,
module_id: ModuleID,
config: "AlgorithmConfig",
batch: Dict[str, Any],
fwd_out: Dict[str, TensorType],
) -> TensorType:
"""Defines a simple MSE prediction loss for continuous task."""
return nn.functional.mse_loss(fwd_out["y_pred"], batch["y"])
@@ -0,0 +1,39 @@
from ray.rllib.core.columns import Columns
from ray.rllib.core.rl_module.torch.torch_rl_module import TorchRLModule
from ray.rllib.utils.framework import try_import_torch
torch, nn = try_import_torch()
class DifferentiableTorchRLModule(TorchRLModule):
"""Differentiable neural network to learn sinusoid curves.
This `TorchRLModule`:
- defines a simple neural network to learn sinusoid curves with two
feed forward layern and ReLU activations,
- defines a differentiable `forward` call by overriding the `_forward`
method (which is implicitly used by the module's `forward` method); this
enables `torch.func.functional_call?` to work.
"""
def setup(self):
"""Sets up a simple neural network
The network contains two hidden layers and ReLU activations. Note,
input and output are single dimensional b/c the sinusoid curve is.
"""
self.net = nn.Sequential(
nn.Linear(1, 40), nn.ReLU(), nn.Linear(40, 40), nn.ReLU(), nn.Linear(40, 1)
)
def _forward(self, batch, **kwargs):
"""Defines method to be called for general forward path.
Note, it is important that the `RLModule.forward` method contains the
logic to be used for training forward pass b/c otherwise the functional
call via `torch.func.functional_call` will not work. See for reference
https://pytorch.org/docs/stable/generated/torch.func.functional_call.html.
"""
outs = {}
outs["y_pred"] = self.net(batch[Columns.OBS])
return outs
@@ -0,0 +1,38 @@
from typing import TYPE_CHECKING, Any, Dict, List
from ray.rllib.core.learner.torch.torch_meta_learner import TorchMetaLearner
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
from ray.rllib.utils.typing import ModuleID, TensorType
if TYPE_CHECKING:
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
torch, nn = try_import_torch()
class MAMLTorchMetaLearner(TorchMetaLearner):
"""A `TorchMetaLearner` to perform MAML learning.
This `TorchMetaLearner`
- defines a MSE loss for learning simple (here non-linear) prediction.
"""
@override(TorchMetaLearner)
def compute_loss_for_module(
self,
*,
module_id: ModuleID,
config: "AlgorithmConfig",
batch: Dict[str, Any],
fwd_out: Dict[str, TensorType],
others_loss_per_module: List[Dict[ModuleID, TensorType]] = None,
) -> TensorType:
"""Defines a simple MSE prediction loss for continuous task.
Note, MAML does not need the losses from the registered differentiable
learners (contained in `others_loss_per_module`) b/c it computes a test
loss on an unseen data batch.
"""
# Use a simple MSE loss for the meta learning task.
return torch.nn.functional.mse_loss(fwd_out["y_pred"], batch["y"])
+175
View File
@@ -0,0 +1,175 @@
import tree # pip install dm_tree
from typing_extensions import Self
from ray.rllib.algorithms import Algorithm
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig, NotProvided
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.utils.annotations import override
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
ENV_RUNNER_SAMPLING_TIMER,
LEARNER_RESULTS,
LEARNER_UPDATE_TIMER,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
SYNCH_WORKER_WEIGHTS_TIMER,
TIMERS,
)
class VPGConfig(AlgorithmConfig):
"""A simple VPG (vanilla policy gradient) algorithm w/o value function support.
Use for testing purposes only!
This Algorithm should use the VPGTorchLearner and VPGTorchRLModule
"""
# A test setting to activate metrics on mean weights.
report_mean_weights: bool = True
def __init__(self, algo_class=None):
super().__init__(algo_class=algo_class or VPG)
# VPG specific settings.
self.num_episodes_per_train_batch = 10
# Note that we don't have to set this here, because we tell the EnvRunners
# explicitly to sample entire episodes. However, for good measure, we change
# this setting here either way.
self.batch_mode = "complete_episodes"
# VPG specific defaults (from AlgorithmConfig).
self.num_env_runners = 1
@override(AlgorithmConfig)
def training(self, *, num_episodes_per_train_batch=NotProvided, **kwargs) -> Self:
"""Sets the training related configuration.
Args:
num_episodes_per_train_batch: The number of complete episodes per train
batch. VPG requires entire episodes to be sampled from the EnvRunners.
For environments with varying episode lengths, this leads to varying
batch sizes (in timesteps) as well possibly causing slight learning
instabilities. However, for simplicity reasons, we stick to collecting
always exactly n episodes per training update.
Returns:
This updated AlgorithmConfig object.
"""
# Pass kwargs onto super's `training()` method.
super().training(**kwargs)
if num_episodes_per_train_batch is not NotProvided:
self.num_episodes_per_train_batch = num_episodes_per_train_batch
return self
@override(AlgorithmConfig)
def get_default_rl_module_spec(self):
if self.framework_str == "torch":
from ray.rllib.examples.rl_modules.classes.vpg_torch_rlm import (
VPGTorchRLModule,
)
spec = RLModuleSpec(
module_class=VPGTorchRLModule,
model_config={"hidden_dim": 64},
)
else:
raise ValueError(f"Unsupported framework: {self.framework_str}")
return spec
@override(AlgorithmConfig)
def get_default_learner_class(self):
if self.framework_str == "torch":
from ray.rllib.examples.learners.classes.vpg_torch_learner import (
VPGTorchLearner,
)
return VPGTorchLearner
else:
raise ValueError(f"Unsupported framework: {self.framework_str}")
class VPG(Algorithm):
@classmethod
@override(Algorithm)
def get_default_config(cls) -> VPGConfig:
return VPGConfig()
@override(Algorithm)
def training_step(self) -> None:
"""Override of the training_step method of `Algorithm`.
Runs the following steps per call:
- Sample B timesteps (B=train batch size). Note that we don't sample complete
episodes due to simplicity. For an actual VPG algo, due to the loss computation,
you should always sample only completed episodes.
- Send the collected episodes to the VPG LearnerGroup for model updating.
- Sync the weights from LearnerGroup to all EnvRunners.
"""
# Sample.
with self.metrics.log_time((TIMERS, ENV_RUNNER_SAMPLING_TIMER)):
episodes, env_runner_results = self._sample_episodes()
# Merge results from n parallel sample calls into self's metrics logger.
self.metrics.aggregate(env_runner_results, key=ENV_RUNNER_RESULTS)
# Just for demonstration purposes, log the number of time steps sampled in this
# `training_step` round.
# Mean over a window of 100:
self.metrics.log_value(
"episode_timesteps_sampled_mean_win100",
sum(map(len, episodes)),
reduce="mean",
window=100,
)
# Exponential Moving Average (EMA) with coeff=0.1:
self.metrics.log_value(
"episode_timesteps_sampled_ema",
sum(map(len, episodes)),
ema_coeff=0.1, # <- weight of new value; weight of old avg=1.0-ema_coeff
)
# Update model.
with self.metrics.log_time((TIMERS, LEARNER_UPDATE_TIMER)):
learner_results = self.learner_group.update(
episodes=episodes,
timesteps={
NUM_ENV_STEPS_SAMPLED_LIFETIME: (
self.metrics.peek(
(ENV_RUNNER_RESULTS, NUM_ENV_STEPS_SAMPLED_LIFETIME)
)
),
},
)
# Merge results from m parallel update calls into self's metrics logger.
self.metrics.aggregate(learner_results, key=LEARNER_RESULTS)
# Sync weights.
with self.metrics.log_time((TIMERS, SYNCH_WORKER_WEIGHTS_TIMER)):
self.env_runner_group.sync_weights(
from_worker_or_learner_group=self.learner_group,
inference_only=True,
)
def _sample_episodes(self):
# How many episodes to sample from each EnvRunner?
num_episodes_per_env_runner = self.config.num_episodes_per_train_batch // (
self.config.num_env_runners or 1
)
# Send parallel remote requests to sample and get the metrics.
sampled_data = self.env_runner_group.foreach_env_runner(
# Return tuple of [episodes], [metrics] from each EnvRunner.
lambda env_runner: (
env_runner.sample(num_episodes=num_episodes_per_env_runner),
env_runner.get_metrics(),
),
# Loop over remote EnvRunners' `sample()` method in parallel or use the
# local EnvRunner if there aren't any remote ones.
local_env_runner=self.env_runner_group.num_remote_workers() <= 0,
)
# Return one list of episodes and a list of metrics dicts (one per EnvRunner).
episodes = tree.flatten([s[0] for s in sampled_data])
stats_dicts = [s[1] for s in sampled_data]
return episodes, stats_dicts
@@ -0,0 +1,103 @@
import warnings
from pathlib import Path
from ray.rllib.algorithms.cql.cql import CQLConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
parser = add_rllib_example_script_args()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
assert (
args.env == "Pendulum-v1" or args.env is None
), "This tuned example works only with `Pendulum-v1`."
# Define the base path relative to this file.
base_path = Path(__file__).parents[3]
# Use the larger data set of Pendulum we have. Note, these are
# parquet data, the default in `AlgorithmConfig.offline_data`.
data_path = base_path / "offline/tests/data/pendulum/pendulum-v1_enormous"
data_path_uri = f"local://{data_path.as_posix()}"
print(f"data_path_uri={data_path_uri}")
# Define the configuration.
config = (
CQLConfig()
.environment("Pendulum-v1")
.offline_data(
input_=[data_path_uri],
# The `kwargs` for the `map_batches` method in which our
# `OfflinePreLearner` is run. 2 data workers should be run
# concurrently.
map_batches_kwargs={"concurrency": 2, "num_cpus": 1},
# The `kwargs` for the `iter_batches` method. Due to the small
# dataset we choose only a single batch to prefetch.
iter_batches_kwargs={"prefetch_batches": 1},
# The number of iterations to be run per learner when in multi-learner
# mode in a single RLlib training iteration. Leave this to `None` to
# run an entire epoch on the dataset during a single RLlib training
# iteration.
dataset_num_iters_per_learner=5,
# TODO (sven): Has this any influence in the connectors?
actions_in_input_normalized=True,
)
.training(
bc_iters=200,
tau=9.5e-3,
min_q_weight=5.0,
train_batch_size_per_learner=1024,
twin_q=True,
actor_lr=1.7e-3 * (args.num_learners or 1) ** 0.5,
critic_lr=2.5e-3 * (args.num_learners or 1) ** 0.5,
alpha_lr=1e-3 * (args.num_learners or 1) ** 0.5,
# Set this to `None` for all `SAC`-like algorithms. These
# algorithms use learning rates for each optimizer.
lr=None,
)
.reporting(
min_time_s_per_iteration=10,
metrics_num_episodes_for_smoothing=5,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
)
)
.evaluation(
evaluation_interval=3,
evaluation_num_env_runners=1,
evaluation_duration=5,
evaluation_config={
"explore": False,
},
)
)
if not args.no_tune:
warnings.warn(
"You are running the example with Ray Tune. Offline RL uses "
"Ray Data, which doesn't does not interact seamlessly with Ray Tune. "
"If you encounter difficulties try to run the example without "
"Ray Tune using `--no-tune`."
)
stop = {
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -700.0,
NUM_ENV_STEPS_SAMPLED_LIFETIME: 800000,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,364 @@
import gymnasium as gym
from gymnasium.wrappers import AtariPreprocessing
from ray import tune
from ray.rllib.algorithms.dqn.dqn import DQNConfig
from ray.rllib.connectors.env_to_module.frame_stacking import FrameStackingEnvToModule
from ray.rllib.connectors.learner.frame_stacking import FrameStackingLearner
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune import Stopper
# Might need `gymnasium[atari, other]` to be installed.
# See the following links for becnhmark results of other libraries:
# Original paper: https://arxiv.org/abs/1812.05905
# CleanRL: https://wandb.ai/cleanrl/cleanrl.benchmark/reports/Mujoco--VmlldzoxODE0NjE
# AgileRL: https://github.com/AgileRL/AgileRL?tab=readme-ov-file#benchmarks
benchmark_envs = {
"AlienNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 6022.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AmidarNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 202.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AssaultNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 14491.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AsterixNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 280114.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AsteroidsNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2249.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AtlantisNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 814684.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BankHeistNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 826.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BattleZoneNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 52040.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BeamRiderNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 21768.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BerzerkNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 1793.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BowlingNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 39.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BoxingNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 54.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BreakoutNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 379.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"CentipedeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 7160.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"ChopperCommandNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 10916.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"CrazyClimberNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 143962.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"DefenderNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 47671.3,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"DemonAttackNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 109670.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"DoubleDunkNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -0.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"EnduroNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2061.1,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"FishingDerbyNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 22.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"FreewayNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 29.1,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"FrostbiteNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 4141.1,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"GopherNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 72595.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"GravitarNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 567.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"HeroNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 50496.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"IceHockeyNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -11685.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"KangarooNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 10841.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"KrullNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 6715.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"KungFuMasterNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 28999.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"MontezumaRevengeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 154.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"MsPacmanNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2570.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"NameThisGameNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 11686.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PhoenixNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 103061.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PitfallNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -37.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PongNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 19.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PrivateEyeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 1704.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"QbertNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 18397.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"RoadRunnerNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 54261.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"RobotankNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 55.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SeaquestNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 19176.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SkiingNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -11685.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SolarisNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2860.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SpaceInvadersNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 12629.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"StarGunnerNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 123853.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SurroundNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 7.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"TennisNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -2.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"TimePilotNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 11190.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"TutankhamNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 126.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"VentureNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 45.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"VideoPinballNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 506817.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"WizardOfWorNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 14631.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"YarsRevengeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 93007.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"ZaxxonNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 19658.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
}
for env in benchmark_envs.keys():
tune.register_env(
env,
lambda ctx, e=env: AtariPreprocessing(
gym.make(e, **ctx), grayscale_newaxis=True, screen_size=84, noop_max=0
),
)
def _make_env_to_module_connector(env, spaces, device):
return FrameStackingEnvToModule(num_frames=4)
def _make_learner_connector(input_observation_space, input_action_space):
return FrameStackingLearner(num_frames=4)
# Define a `tune.Stopper` that stops the training if the benchmark is reached
# or the maximum number of timesteps is exceeded.
class BenchmarkStopper(Stopper):
def __init__(self, benchmark_envs):
self.benchmark_envs = benchmark_envs
def __call__(self, trial_id, result):
# Stop training if the mean reward is reached.
if (
result[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN]
>= self.benchmark_envs[result["env"]][
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}"
]
):
return True
# Otherwise check, if the total number of timesteps is exceeded.
elif (
result[f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
>= self.benchmark_envs[result["env"]][f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
):
return True
# Otherwise continue training.
else:
return False
# Note, this needs to implemented b/c the parent class is abstract.
def stop_all(self):
return False
# See Table 1 in the Rainbow paper for the hyperparameters.
config = (
DQNConfig()
.environment(
env=tune.grid_search(list(benchmark_envs.keys())),
env_config={
"max_episode_steps": 108000,
"obs_type": "grayscale",
# The authors actually use an action repetition of 4.
"repeat_action_probability": 0.25,
},
clip_rewards=True,
)
.env_runners(
# Every 4 agent steps a training update is performed.
rollout_fragment_length=4,
num_env_runners=1,
env_to_module_connector=_make_env_to_module_connector,
)
# TODO (simon): Adjust to new model_config_dict.
.training(
# Note, the paper uses also an Adam epsilon of 0.00015.
lr=0.0000625,
n_step=3,
tau=1.0,
train_batch_size=32,
target_network_update_freq=32000,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 1000000,
"alpha": 0.5,
# Note the paper used a linear schedule for beta.
"beta": 0.4,
},
# Note, these are frames.
num_steps_sampled_before_learning_starts=80000,
noisy=True,
num_atoms=51,
v_min=-10.0,
v_max=10.0,
double_q=True,
dueling=True,
model={
"cnn_filter_specifiers": [[32, 8, 4], [64, 4, 2], [64, 3, 1]],
"fcnet_activation": "tanh",
"post_fcnet_hiddens": [512],
"post_fcnet_activation": "relu",
"post_fcnet_weights_initializer": "orthogonal_",
"post_fcnet_weights_initializer_config": {"gain": 0.01},
},
learner_connector=_make_learner_connector,
)
.reporting(
metrics_num_episodes_for_smoothing=10,
min_sample_timesteps_per_iteration=1000,
)
.evaluation(
evaluation_duration="auto",
evaluation_interval=1,
evaluation_num_env_runners=1,
evaluation_parallel_to_training=True,
evaluation_config={
"explore": False,
},
)
)
tuner = tune.Tuner(
"DQN",
param_space=config,
run_config=tune.RunConfig(
stop=BenchmarkStopper(benchmark_envs=benchmark_envs),
name="benchmark_dqn_atari",
),
)
tuner.fit()
@@ -0,0 +1,366 @@
import gymnasium as gym
from ray import tune
from ray.rllib.algorithms.dqn.dqn import DQNConfig
from ray.rllib.env.wrappers.atari_wrappers import wrap_atari_for_new_api_stack
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune import Stopper
# Might need `gymnasium[atari, other]` to be installed.
# See the following links for becnhmark results of other libraries:
# Original paper: https://arxiv.org/abs/1812.05905
# CleanRL: https://wandb.ai/cleanrl/cleanrl.benchmark/reports/Mujoco--VmlldzoxODE0NjE
# AgileRL: https://github.com/AgileRL/AgileRL?tab=readme-ov-file#benchmarks
benchmark_envs = {
"AlienNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 6022.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AmidarNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 202.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AssaultNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 14491.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AsterixNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 280114.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AsteroidsNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2249.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"AtlantisNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 814684.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BankHeistNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 826.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BattleZoneNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 52040.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BeamRiderNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 21768.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BerzerkNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 1793.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BowlingNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 39.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BoxingNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 54.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"BreakoutNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 379.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"CentipedeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 7160.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"ChopperCommandNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 10916.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"CrazyClimberNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 143962.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"DefenderNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 47671.3,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"DemonAttackNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 109670.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"DoubleDunkNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -0.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"EnduroNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2061.1,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"FishingDerbyNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 22.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"FreewayNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 29.1,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"FrostbiteNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 4141.1,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"GopherNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 72595.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"GravitarNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 567.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"HeroNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 50496.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"IceHockeyNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -11685.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"KangarooNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 10841.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"KrullNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 6715.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"KungFuMasterNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 28999.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"MontezumaRevengeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 154.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"MsPacmanNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2570.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"NameThisGameNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 11686.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PhoenixNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 103061.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PitfallNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -37.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PongNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 19.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"PrivateEyeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 1704.4,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"QbertNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 18397.6,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"RoadRunnerNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 54261.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"RobotankNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 55.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SeaquestNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 19176.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SkiingNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -11685.8,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SolarisNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2860.7,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SpaceInvadersNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 12629.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"StarGunnerNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 123853.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"SurroundNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 7.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"TennisNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -2.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"TimePilotNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 11190.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"TutankhamNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 126.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"VentureNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 45.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"VideoPinballNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 506817.2,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"WizardOfWorNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 14631.5,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"YarsRevengeNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 93007.9,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
"ZaxxonNoFrameskip-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 19658.0,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000000,
},
}
for env in benchmark_envs:
tune.register_env(
env,
# Use the RLlib atari wrapper to squeeze images to 84x84.
# Note, the default of this wrapper is `framestack=4`.
lambda ctx, e=env: wrap_atari_for_new_api_stack(gym.make(e, **ctx), dim=84),
)
# Define a `tune.Stopper` that stops the training if the benchmark is reached
# or the maximum number of timesteps is exceeded.
class BenchmarkStopper(Stopper):
def __init__(self, benchmark_envs):
self.benchmark_envs = benchmark_envs
def __call__(self, trial_id, result):
# Stop training if the mean reward is reached.
if (
result[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN]
>= self.benchmark_envs[result["env"]][
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}"
]
):
return True
# Otherwise check, if the total number of timesteps is exceeded.
elif (
result[f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
>= self.benchmark_envs[result["env"]][f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
):
return True
# Otherwise continue training.
else:
return False
# Note, this needs to implemented b/c the parent class is abstract.
def stop_all(self):
return False
# See Table 1 in the Rainbow paper for the hyperparameters.
config = (
DQNConfig()
.environment(
env=tune.grid_search(list(benchmark_envs.keys())),
env_config={
# "sticky actions" but not according to Danijar's 100k configs.
"repeat_action_probability": 0.0,
# "full action space" but not according to Danijar's 100k configs.
"full_action_space": False,
# Already done by MaxAndSkip wrapper: "action repeat" == 4.
"frameskip": 1,
# NOTE, because we use the atari wrapper of RLlib, we also have
# framestack: 4,
# dim: 84,
# NOTE, we do not use grayscale here, so this run will need
# more memory on GPU and CPU (buffer).
},
clip_rewards=True,
)
.env_runners(
# Every 4 agent steps a training update is performed.
rollout_fragment_length=4,
num_env_runners=1,
)
.learners(
# We have a train/sample ratio of 1:1 and a batch of 32.
num_learners=1,
num_gpus_per_learner=1,
)
# TODO (simon): Adjust to new model_config_dict.
.training(
# Note, the paper uses also an Adam epsilon of 0.00015.
lr=0.0000625,
n_step=1,
tau=1.0,
# TODO (simon): Activate when new model_config_dict is available.
# epsilon=0.01,
train_batch_size=32,
target_network_update_freq=8000,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 1000000,
"alpha": 0.5,
# Note the paper used a linear schedule for beta.
"beta": 0.4,
},
# Note, these are frames.
num_steps_sampled_before_learning_starts=20000,
noisy=True,
num_atoms=51,
v_min=-10.0,
v_max=10.0,
double_q=True,
dueling=True,
model={
"cnn_filter_specifiers": [[32, 8, 4], [64, 4, 2], [64, 3, 1]],
"fcnet_activation": "tanh",
"post_fcnet_hiddens": [512],
"post_fcnet_activation": "relu",
"post_fcnet_weights_initializer": "orthogonal_",
"post_fcnet_weights_initializer_config": {"gain": 0.01},
},
)
.reporting(
metrics_num_episodes_for_smoothing=10,
min_sample_timesteps_per_iteration=1000,
)
.evaluation(
evaluation_duration="auto",
evaluation_interval=1,
evaluation_num_env_runners=1,
evaluation_parallel_to_training=True,
evaluation_config={
"explore": False,
},
)
)
tuner = tune.Tuner(
"DQN",
param_space=config,
run_config=tune.RunConfig(
stop=BenchmarkStopper(benchmark_envs=benchmark_envs),
name="benchmark_dqn_atari_rllib_preprocessing",
),
)
tuner.fit()
@@ -0,0 +1,47 @@
from ray.rllib.algorithms.dqn import DQNConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=450.0,
default_timesteps=200000,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
DQNConfig()
.environment(env="CartPole-v1")
.training(
lr=0.0005 * (args.num_learners or 1) ** 0.5,
train_batch_size_per_learner=32,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 50000,
"alpha": 0.6,
"beta": 0.4,
},
n_step=(2, 5),
double_q=True,
dueling=True,
epsilon=[(0, 1.0), (10000, 0.02)],
)
.rl_module(
# Settings identical to old stack.
model_config=DefaultModelConfig(
fcnet_hiddens=[256],
fcnet_activation="tanh",
fcnet_bias_initializer="zeros_",
head_fcnet_bias_initializer="zeros_",
head_fcnet_hiddens=[256],
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,74 @@
from ray.rllib.algorithms.dqn import DQNConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(
default_timesteps=500000,
)
parser.set_defaults(
num_agents=2,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env("multi_agent_cartpole", lambda cfg: MultiAgentCartPole(config=cfg))
config = (
DQNConfig()
.environment(env="multi_agent_cartpole", env_config={"num_agents": args.num_agents})
.training(
lr=0.00065 * (args.num_learners or 1) ** 0.5,
train_batch_size_per_learner=48,
replay_buffer_config={
"type": "MultiAgentPrioritizedEpisodeReplayBuffer",
"capacity": 50000,
"alpha": 0.6,
"beta": 0.4,
},
n_step=(2, 5),
double_q=True,
num_atoms=1,
dueling=True,
epsilon=[(0, 1.0), (20000, 0.02)],
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="tanh",
fcnet_bias_initializer="zeros_",
head_fcnet_bias_initializer="zeros_",
head_fcnet_hiddens=[256],
),
)
)
if args.num_agents:
config.multi_agent(
policy_mapping_fn=lambda aid, *arg, **kw: f"p{aid}",
policies={f"p{i}" for i in range(args.num_agents)},
)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
# `episode_return_mean` is the sum of all agents/policies' returns.
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 150.0 * args.num_agents,
}
if __name__ == "__main__":
assert (
args.num_agents > 0
), "The `--num-agents` arg must be > 0 for this script to work."
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,58 @@
from ray.rllib.algorithms.dqn import DQNConfig
from ray.rllib.connectors.env_to_module import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.stateless_cartpole import StatelessCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=2000000,
default_reward=350.0,
)
parser.set_defaults(
num_env_runners=3,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
DQNConfig()
.environment(StatelessCartPole)
.env_runners(
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(),
)
.training(
lr=0.0005,
train_batch_size_per_learner=32,
replay_buffer_config={
"type": "EpisodeReplayBuffer",
"capacity": 100000,
},
n_step=1,
double_q=True,
dueling=True,
num_atoms=1,
epsilon=[(0, 1.0), (20000, 0.02)],
burn_in_len=8,
)
.rl_module(
# Settings identical to old stack.
model_config=DefaultModelConfig(
fcnet_hiddens=[256],
fcnet_activation="tanh",
fcnet_bias_initializer="zeros_",
head_fcnet_bias_initializer="zeros_",
head_fcnet_hiddens=[256],
head_fcnet_activation="tanh",
lstm_kernel_initializer="xavier_uniform_",
use_lstm=True,
max_seq_len=20,
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,100 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
# Run with:
# python [this script name].py --env ale_py:ALE/[gym ID e.g. Pong-v5]
# To see all available options:
# python [this script name].py --help
import gymnasium as gym
from ray import tune
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
from ray.rllib.env.wrappers.atari_wrappers import wrap_atari_for_new_api_stack
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_iters=1000000,
default_reward=20.0,
default_timesteps=100000,
)
parser.set_defaults(env="ale_py:ALE/Pong-v5")
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
if args.num_envs_per_env_runner is None:
args.num_envs_per_env_runner = args.num_learners or 1
# Create the DreamerV3-typical Atari setup.
def _env_creator(cfg):
return wrap_atari_for_new_api_stack(
gym.make(args.env, **cfg, render_mode="rgb_array"),
# No framestacking necessary for Dreamer.
framestack=None,
# No grayscaling necessary for Dreamer.
grayscale=False,
)
tune.register_env("env", _env_creator)
default_config = DreamerV3Config()
lr_multiplier = args.num_learners or 1
config = (
DreamerV3Config()
.environment(
env="env",
# [2]: "We follow the evaluation protocol of Machado et al. (2018) with 200M
# environment steps, action repeat of 4, a time limit of 108,000 steps per
# episode that correspond to 30 minutes of game play, no access to life
# information, full action space, and sticky actions. Because the world model
# integrates information over time, DreamerV2 does not use frame stacking.
# The experiments use a single-task setup where a separate agent is trained
# for each game. Moreover, each agent uses only a single environment instance.
env_config={
# "sticky actions" but not according to Danijar's 100k configs.
"repeat_action_probability": 0.0,
# "full action space" but not according to Danijar's 100k configs.
"full_action_space": False,
# Already done by MaxAndSkip wrapper: "action repeat" == 4.
"frameskip": 1,
},
)
.env_runners(
remote_worker_envs=(args.num_learners and args.num_learners > 1),
)
.reporting(
metrics_num_episodes_for_smoothing=(args.num_learners or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="S",
training_ratio=1024,
batch_size_B=16 * (args.num_learners or 1),
world_model_lr=default_config.world_model_lr * lr_multiplier,
actor_lr=default_config.actor_lr * lr_multiplier,
critic_lr=default_config.critic_lr * lr_multiplier,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,88 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
# Run with:
# python [this script name].py --env ale_py:ALE/[gym ID e.g. Pong-v5]
# To see all available options:
# python [this script name].py --help
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_iters=1000000,
default_reward=20.0,
default_timesteps=1000000,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
if args.num_envs_per_env_runner is None:
args.num_envs_per_env_runner = 8 * (args.num_learners or 1)
default_config = DreamerV3Config()
lr_multiplier = (args.num_learners or 1) ** 0.5
config = (
DreamerV3Config()
.resources(
# For each (parallelized) env, we should provide a CPU. Lower this number
# if you don't have enough CPUs.
num_cpus_for_main_process=8
* (args.num_learners or 1),
)
.environment(
env=args.env,
# [2]: "We follow the evaluation protocol of Machado et al. (2018) with 200M
# environment steps, action repeat of 4, a time limit of 108,000 steps per
# episode that correspond to 30 minutes of game play, no access to life
# information, full action space, and sticky actions. Because the world model
# integrates information over time, DreamerV2 does not use frame stacking.
# The experiments use a single-task setup where a separate agent is trained
# for each game. Moreover, each agent uses only a single environment instance.
env_config={
# "sticky actions" but not according to Danijar's 100k configs.
"repeat_action_probability": 0.0,
# "full action space" but not according to Danijar's 100k configs.
"full_action_space": False,
# Already done by MaxAndSkip wrapper: "action repeat" == 4.
"frameskip": 1,
},
)
.env_runners(
remote_worker_envs=True,
)
.reporting(
metrics_num_episodes_for_smoothing=(args.num_learners or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="XL",
training_ratio=64,
batch_size_B=16 * (args.num_learners or 1),
world_model_lr=default_config.world_model_lr * lr_multiplier,
actor_lr=default_config.actor_lr * lr_multiplier,
critic_lr=default_config.critic_lr * lr_multiplier,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,22 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
# Run with:
# python [this script name].py
config = (
DreamerV3Config()
.environment("CartPole-v1")
.training(
model_size="XS",
training_ratio=1024,
)
)
@@ -0,0 +1,88 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
# Run with:
# python [this script name].py --env DMC/[task]/[domain] (e.g. DMC/cartpole/swingup)
# To see all available options:
# python [this script name].py --help
from ray import tune
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
from ray.rllib.env.wrappers.dm_control_wrapper import ActionClip, DMCEnv
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_iters=1000000,
default_reward=800.0,
default_timesteps=1000000,
)
parser.set_defaults(env="DMC/cartpole/swingup")
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
if args.num_envs_per_env_runner is None:
args.num_envs_per_env_runner = 4 * (args.num_learners or 1)
parts = args.env.split("/")
assert len(parts) == 3, (
"ERROR: DMC env must be formatted as 'DMC/[task]/[domain]', e.g. "
f"'DMC/cartpole/swingup'! You provided '{args.env}'."
)
def env_creator(cfg):
return ActionClip(
DMCEnv(
parts[1],
parts[2],
from_pixels=True,
channels_first=False,
)
)
tune.register_env("env", env_creator)
default_config = DreamerV3Config()
lr_multiplier = (args.num_learners or 1) ** 0.5
config = (
DreamerV3Config()
# Use image observations.
.environment(env="env")
.env_runners(
remote_worker_envs=True,
)
.reporting(
metrics_num_episodes_for_smoothing=(args.num_learners or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="S",
training_ratio=512,
batch_size_B=16 * (args.num_learners or 1),
world_model_lr=default_config.world_model_lr * lr_multiplier,
actor_lr=default_config.actor_lr * lr_multiplier,
critic_lr=default_config.critic_lr * lr_multiplier,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,80 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
# Run with:
# python [this script name].py
# To see all available options:
# python [this script name].py --help
from ray import tune
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
# Number of GPUs to run on.
num_gpus = 0
# DreamerV3 config and default (1 GPU) learning rates.
config = DreamerV3Config()
w = config.world_model_lr
c = config.critic_lr
def _env_creator(ctx):
import flappy_bird_gymnasium # noqa doctest: +SKIP
import gymnasium as gym
from supersuit.generic_wrappers import resize_v1
from ray.rllib.env.wrappers.atari_wrappers import NormalizedImageEnv
return NormalizedImageEnv(
resize_v1( # resize to 64x64 and normalize images
gym.make("FlappyBird-rgb-v0", audio_on=False), x_size=64, y_size=64
)
)
# Register the FlappyBird-rgb-v0 env including necessary wrappers via the
# `tune.register_env()` API.
tune.register_env("flappy-bird", _env_creator)
# Further specify the DreamerV3 config object to use.
(
config.environment("flappy-bird")
.resources(
num_cpus_for_main_process=1,
)
.learners(
num_learners=0 if num_gpus == 1 else num_gpus,
num_gpus_per_learner=1 if num_gpus else 0,
)
.env_runners(
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
num_envs_per_env_runner=8 * (num_gpus or 1),
remote_worker_envs=True,
)
.reporting(
metrics_num_episodes_for_smoothing=(num_gpus or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="M",
training_ratio=64,
batch_size_B=16 * (num_gpus or 1),
# Use a well established 4-GPU lr scheduling recipe:
# ~ 1000 training updates with 0.4x[default rates], then over a few hundred
# steps, increase to 4x[default rates].
world_model_lr=[[0, 0.4 * w], [8000, 0.4 * w], [10000, 3 * w]],
critic_lr=[[0, 0.4 * c], [8000, 0.4 * c], [10000, 3 * c]],
actor_lr=[[0, 0.4 * c], [8000, 0.4 * c], [10000, 3 * c]],
)
)
@@ -0,0 +1,34 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
# Run with:
# python [this script name].py
# To see all available options:
# python [this script name].py --help
config = (
DreamerV3Config()
.environment(
"FrozenLake-v1",
env_config={
"desc": [
"SF",
"HG",
],
"is_slippery": False,
},
)
.training(
model_size="XS",
training_ratio=1024,
)
)
@@ -0,0 +1,31 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
# Run with:
# python [this script name].py
# To see all available options:
# python [this script name].py --help
config = (
DreamerV3Config()
.environment(
"FrozenLake-v1",
env_config={
"map_name": "4x4",
"is_slippery": False,
},
)
.training(
model_size="nano",
training_ratio=1024,
)
)
@@ -0,0 +1,68 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
# Run with:
# python [this script name].py
# To see all available options:
# python [this script name].py --help
try:
import gymnasium_robotics # noqa
except (ImportError, ModuleNotFoundError):
print("You have to `pip install gymnasium_robotics` in order to run this example!")
import gymnasium as gym
from ray import tune
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
# Number of GPUs to run on.
num_gpus = 4
# Register the gymnasium robotics env (including necessary wrappers and options) via the
# `tune.register_env()` API.
# Create the specific gymnasium robotics env.
# e.g. AdroitHandHammerSparse-v1 or FrankaKitchen-v1.
# return gym.make("FrankaKitchen-v1", tasks_to_complete=["microwave", "kettle"])
tune.register_env("flappy-bird", lambda ctx: gym.make("AdroitHandHammer-v1"))
# Define the DreamerV3 config object to use.
config = DreamerV3Config()
w = config.world_model_lr
c = config.critic_lr
# Further specify the details of our config object.
(
config.resources(
num_cpus_for_main_process=8 * (num_gpus or 1),
)
.learners(
num_learners=0 if num_gpus == 1 else num_gpus,
num_gpus_per_learner=1 if num_gpus else 0,
)
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
.env_runners(num_envs_per_env_runner=8 * (num_gpus or 1), remote_worker_envs=True)
.reporting(
metrics_num_episodes_for_smoothing=(num_gpus or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="XL",
training_ratio=64,
batch_size_B=16 * (num_gpus or 1),
world_model_lr=[[0, 0.4 * w], [50000, 0.4 * w], [100000, 3 * w]],
critic_lr=[[0, 0.4 * c], [50000, 0.4 * c], [100000, 3 * c]],
actor_lr=[[0, 0.4 * c], [50000, 0.4 * c], [100000, 3 * c]],
)
)
@@ -0,0 +1,73 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
# Run with:
# python [this script name].py
# To see all available options:
# python [this script name].py --help
try:
import highway_env # noqa
except (ImportError, ModuleNotFoundError):
print("You have to `pip install highway_env` in order to run this example!")
import gymnasium as gym
from ray import tune
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
# Number of GPUs to run on.
num_gpus = 4
# Register the highway env (including necessary wrappers and options) via the
# `tune.register_env()` API.
# Create the specific env.
# e.g. roundabout-v0 or racetrack-v0
tune.register_env("flappy-bird", lambda ctx: gym.make("intersection-v0", policy_freq=5))
# Define the DreamerV3 config object to use.
config = DreamerV3Config()
w = config.world_model_lr
c = config.critic_lr
(
config.resources(
num_cpus_for_main_process=1,
)
.learners(
num_learners=0 if num_gpus == 1 else num_gpus,
num_gpus_per_learner=1 if num_gpus else 0,
)
.env_runners(
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
num_envs_per_env_runner=8 * (num_gpus or 1),
remote_worker_envs=True,
)
.reporting(
metrics_num_episodes_for_smoothing=(num_gpus or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="M",
training_ratio=64,
batch_size_B=16 * (num_gpus or 1),
# Use a well established 4-GPU lr scheduling recipe:
# ~ 1000 training updates with 0.4x[default rates], then over a few hundred
# steps, increase to 4x[default rates].
world_model_lr=[[0, 0.4 * w], [8000, 0.4 * w], [10000, 3 * w]],
critic_lr=[[0, 0.4 * c], [8000, 0.4 * c], [10000, 3 * c]],
actor_lr=[[0, 0.4 * c], [8000, 0.4 * c], [10000, 3 * c]],
)
)
@@ -0,0 +1,64 @@
"""
[1] Mastering Diverse Domains through World Models - 2023
D. Hafner, J. Pasukonis, J. Ba, T. Lillicrap
https://arxiv.org/pdf/2301.04104v1.pdf
[2] Mastering Atari with Discrete World Models - 2021
D. Hafner, T. Lillicrap, M. Norouzi, J. Ba
https://arxiv.org/pdf/2010.02193.pdf
"""
from ray.rllib.algorithms.dreamerv3.dreamerv3 import DreamerV3Config
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_iters=10000,
default_reward=-200.0,
default_timesteps=100000,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
# If we use >1 GPU and increase the batch size accordingly, we should also
# increase the number of envs per worker.
if args.num_envs_per_env_runner is None:
args.num_envs_per_env_runner = args.num_learners or 1
# Run with:
# python [this script name].py
# To see all available options:
# python [this script name].py --help
default_config = DreamerV3Config()
lr_multiplier = args.num_learners or 1
config = (
DreamerV3Config()
.environment("Pendulum-v1")
.env_runners(
remote_worker_envs=(args.num_learners and args.num_learners > 1),
)
.reporting(
metrics_num_episodes_for_smoothing=(args.num_learners or 1),
report_images_and_videos=False,
report_dream_data=False,
report_individual_batch_item_stats=False,
)
# See Appendix A.
.training(
model_size="S",
training_ratio=1024,
batch_size_B=16 * (args.num_learners or 1),
world_model_lr=default_config.world_model_lr * lr_multiplier,
actor_lr=default_config.actor_lr * lr_multiplier,
critic_lr=default_config.critic_lr * lr_multiplier,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,90 @@
"""Example showing how to run IMPALA on the CartPole environment.
IMPALA (Importance Weighted Actor-Learner Architecture) is a distributed
reinforcement learning algorithm that decouples acting from learning. It uses
V-trace for off-policy correction, enabling efficient training across many
distributed actors while maintaining stable learning.
This example:
- trains on the classic CartPole-v1 control task
- uses gradient clipping by global norm (40.0) for training stability
- scales the learning rate with the square root of the number of learners
- shares value function layers with the policy network for parameter efficiency
- targets a reward of 450 (near-optimal for CartPole-v1's max of 500)
How to run this script
----------------------
`python cartpole_impala.py [options]`
To run with default settings:
`python cartpole_impala.py`
To scale up with distributed learning using multiple learners and env-runners:
`python cartpole_impala.py --num-learners=2 --num-env-runners=8`
To use a GPU-based learner add the number of GPUs per learner:
`python cartpole_impala.py --num-learners=1 --num-gpus-per-learner=1`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0 --num-learners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
By setting `--num-learners=0` and `--num-env-runners=0` will make them run locally
instead of as remote Ray Actors where breakpoints aren't possible.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key]
--wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
The algorithm should reach the reward threshold of 450 on CartPole-v1
within 2 million timesteps (see: `default_timesteps` in the code).
CartPole-v1 has a maximum episode reward of 500, and IMPALA should
consistently achieve near-optimal performance on this task.
"""
from ray.rllib.algorithms.impala import IMPALAConfig
from ray.rllib.connectors.env_to_module.mean_std_filter import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=450.0,
default_timesteps=2_000_000,
)
parser.set_defaults(
num_env_runners=4,
num_envs_per_env_runner=16,
num_learners=1,
)
args = parser.parse_args()
config = (
IMPALAConfig()
.environment("CartPole-v1")
.env_runners(
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(),
)
.training(
train_batch_size_per_learner=500,
grad_clip=40.0,
grad_clip_by="global_norm",
lr=0.0005 * ((args.num_learners or 1) ** 0.5),
vf_loss_coeff=0.05,
entropy_coeff=0.0,
)
.rl_module(
model_config=DefaultModelConfig(
vf_share_layers=True,
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,134 @@
"""Example showing how to run multi-agent IMPALA on TicTacToe with self-play.
This example demonstrates multi-agent reinforcement learning using IMPALA on a
TicTacToe environment. The setup includes trainable policies that learn to play
against each other and a frozen random policy that provides diverse opponents.
This self-play with random opponents approach helps prevent overfitting to a
single opponent strategy.
This example:
- trains multiple policies on the TicTacToe multi-agent environment
- uses a RandomRLModule as a frozen opponent that is not trained
- randomly maps agents to policies (including the random policy) each episode
- demonstrates MultiRLModuleSpec for configuring multiple policies
- uses 4 env runners by default for parallel experience collection
How to run this script
----------------------
`python tictactoe_impala.py [options]`
To run with default settings (5 trainable agents):
`python tictactoe_impala.py`
To run with a different number of trainable agents:
`python tictactoe_impala.py --num-agents=4`
To scale up with distributed learning using multiple learners and env-runners:
`python tictactoe_impala.py --num-learners=2 --num-env-runners=8`
To use a GPU-based learner add the number of GPUs per learner:
`python tictactoe_impala.py --num-learners=1 --num-gpus-per-learner=1`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0 --num-learners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
By setting `--num-learners=0` and `--num-env-runners=0` will make them run locally
instead of as remote Ray Actors where breakpoints aren't possible.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key]
--wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
Four policies are trained plus a fifth random policy are randomly paired against
each other. Training is stopped when policy 0 achieves a return of < -0.3 within
2 million timesteps. A reward close to 0 or positive indicates
the policies are learning to win or draw more often than they lose.
"""
import random
from ray.air.constants import TRAINING_ITERATION
from ray.rllib.algorithms.impala import IMPALAConfig
from ray.rllib.core.rl_module import MultiRLModuleSpec, RLModuleSpec
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent.tic_tac_toe import TicTacToe
from ray.rllib.examples.rl_modules.classes.random_rlm import RandomRLModule
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_MODULE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
parser = add_rllib_example_script_args(
default_reward=-0.5,
default_timesteps=2_000_000,
)
parser.set_defaults(
num_env_runners=4,
num_envs_per_env_runner=3,
num_learners=1,
num_agents=5,
)
args = parser.parse_args()
config = (
IMPALAConfig()
.environment(TicTacToe)
.env_runners(
num_env_runners=args.num_env_runners,
num_envs_per_env_runner=args.num_envs_per_env_runner,
)
.learners(
num_learners=args.num_learners,
)
.training(
train_batch_size_per_learner=1000,
grad_clip=30.0,
grad_clip_by="global_norm",
lr=0.0005,
vf_loss_coeff=0.01,
entropy_coeff=0.0,
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs=(
{
f"p{i}": RLModuleSpec(
model_config=DefaultModelConfig(vf_share_layers=True),
)
for i in range(args.num_agents)
}
| {"random": RLModuleSpec(module_class=RandomRLModule)}
),
),
)
.multi_agent(
policies={f"p{i}" for i in range(args.num_agents)} | {"random"},
policy_mapping_fn=lambda aid, eps, **kw: (
random.choice([f"p{i}" for i in range(args.num_agents)] + ["random"])
),
policies_to_train=[f"p{i}" for i in range(args.num_agents)],
)
)
stop = {
f"{ENV_RUNNER_RESULTS}/{EPISODE_MODULE_RETURN_MEAN}/p0": args.stop_reward,
f"{ENV_RUNNER_RESULTS}/{NUM_ENV_STEPS_SAMPLED_LIFETIME}": args.stop_timesteps,
TRAINING_ITERATION: args.stop_iters,
}
success_metric = {
f"{ENV_RUNNER_RESULTS}/{EPISODE_MODULE_RETURN_MEAN}/p0": args.stop_reward
}
if __name__ == "__main__":
run_rllib_example_script_experiment(
config, args, stop=stop, success_metric=success_metric
)
@@ -0,0 +1,92 @@
from pathlib import Path
from ray.rllib.algorithms.iql.iql import IQLConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
)
from ray.tune.result import TRAINING_ITERATION
parser = add_rllib_example_script_args()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
assert (
args.env == "Pendulum-v1" or args.env is None
), "This tuned example works only with `Pendulum-v1`."
# Define the data paths.
data_path = "offline/tests/data/pendulum/pendulum-v1_enormous"
base_path = Path(__file__).parents[3]
print(f"base_path={base_path}")
data_path = "local://" / base_path / data_path
print(f"data_path={data_path}")
# Define the IQL config.
config = (
IQLConfig()
.environment(env="Pendulum-v1")
.evaluation(
evaluation_interval=3,
evaluation_num_env_runners=1,
evaluation_duration=5,
evaluation_parallel_to_training=True,
)
# Note, the `input_` argument is the major argument for the
# new offline API. Via the `input_read_method_kwargs` the
# arguments for the `ray.data.Dataset` read method can be
# configured. The read method needs at least as many blocks
# as remote learners.
.offline_data(
input_=[data_path.as_posix()],
# Concurrency defines the number of processes that run the
# `map_batches` transformations. This should be aligned with the
# 'prefetch_batches' argument in 'iter_batches_kwargs'.
map_batches_kwargs={"concurrency": 2, "num_cpus": 2},
# This data set is small so do not prefetch too many batches and use no
# local shuffle.
iter_batches_kwargs={
"prefetch_batches": 1,
},
# The number of iterations to be run per learner when in multi-learner
# mode in a single RLlib training iteration. Leave this to `None` to
# run an entire epoch on the dataset during a single RLlib training
# iteration.
dataset_num_iters_per_learner=5,
)
.training(
# To increase learning speed with multiple learners,
# increase the learning rates correspondingly.
actor_lr=2.59e-4 * (args.num_learners or 1) ** 0.5,
critic_lr=2.14e-4 * (args.num_learners or 1) ** 0.5,
value_lr=3.7e-5 * (args.num_learners or 1) ** 0.5,
# Smooth Polyak-averaging for the target network.
tau=6e-4,
# Update the target network each training iteration.
target_network_update_freq=1,
train_batch_size_per_learner=1024,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
)
)
)
stop = {
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -200.0,
TRAINING_ITERATION: 1250,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,443 @@
"""Example of how to use `TorchMetaLearner` and `DifferentiableLearner` for MAML.
Meta-learning, or “learning to learn,” trains models to quickly adapt to new tasks
using only a few examples. One prominent method is Model-Agnostic Meta-Learning
(MAML), which is compatible with any model trained via gradient descent. MAML has
been successfully applied across domains such as classification, regression, and
reinforcement learning.
In this MAML example, the goal is to train a model that can adapt to an infinite
number of tasks, where each task corresponds to a sinusoidal function with randomly
sampled amplitude and phase. Because each new task introduces a shift in data
distribution, traditional learning algorithms would fail to generalize — theyd
overfit to the training task and struggle on unseen ones. Meta-learning addresses
this by optimizing the model parameters such that they can be fine-tuned rapidly
for any new task.
During training, a DifferentiableLearner performs an inner-loop update using the
training error for each task. The outer-loop TorchMetaLearner then evaluates the
models performance on held-out data (the task's test set) and updates the meta-
parameters so that they lead to better generalization across all tasks. This bi-
level optimization ensures that gradients across tasks remain close, enabling
fast adaptation.
At inference time, the trained model can adapt to a new task using just a small
batch of examples — performing few-shot learning to adjust quickly and accurately.
This example shows:
- how to implement MAML with RLlib in just a few lines of code.
- how to define a `TorchDifferentiableLearner` to register a custom train loss
function.
- how to define a `TorchMetaLearner` class to implement a custom meta (test) train
loss function.
- how to configure both learners top be used with each others via the
`DifferentiableAlgorithmConfig` and `DifferentiableLearnerConfig`.
- how to update the `RLModule` in a meta-learning fashion.
- how to fine-tune an `RLModule` with gradient descent within a few iterations with
only using the meta (test) loss.
See :py:class:`~ray.rllib.examples.learners.classes.lr_meta_learner.LRTorchMetaLearner` # noqa
class for details on how to override the main `TorchMetaLearner`. And see
:py:class:`~ray.rllib.examples.learners.classes.lr_differentiable_learner.LRTorchDifferentiableLearner` # noqa
class for an example of how to override the main `TorchDifferentiableLearner`.
Note, the meta-learner needs a long-enough training (`default_iters`=~70,000) to learn
to adapt quickly to new tasks.
How to run this script
----------------------
`python [script file name].py --iters=70000 --meta-train-batch-size=5 --fine-tune-batch-size=5`
Use the `--meta-train-batch-size` to set the training/testing batch size in meta-learning and
the `--fine-tune-batch-size` to adjust the number of samples used in all updates during
few-shot learning.
To suppress plotting (plotting is the default) use `--no-plot` and for taking a longer
look at the plot increase the seconds for which plotting is paused at the end of the
script by `--pause-plot-secs`.
Results to expect
-----------------
You should expect to see sometimes alternating test losses ("Total Loss") due to new
(unseen) tasks during meta learning. In few-shot learning after the meta-learning the
(few shot) loss should decrease almost monotonically. In the plot you can expect to see
a decent adaption to the new task after fine-tuning updates of the `RLModule` weights.
With `--iters=70_000`, `--meta-train-batch-size=5`, `--fine-tune-batch-size=5`,
`--fine-tune-lr=0.01`, `--fine-tune-iters=10`, `--meta-lr=0.001`, `--noise-std=0.0`,
and no seed defined.
-------------------------
Iteration: 68000
Total loss: 0.013758559711277485
-------------------------
Iteration: 69000
Total loss: 0.7246640920639038
-------------------------
Iteration: 70000
Total loss: 3.091259002685547
Few shot loss: 2.754437208175659
Few shot loss: 2.7399725914001465
Few shot loss: 2.499554395675659
Few shot loss: 2.1763901710510254
Few shot loss: 1.793503999710083
Few shot loss: 1.4362313747406006
Few shot loss: 1.083552598953247
Few shot loss: 0.7845061421394348
Few shot loss: 0.5579453110694885
Few shot loss: 0.4087105393409729
"""
import gymnasium as gym
import matplotlib.pyplot as plt
import numpy as np
from ray.rllib.algorithms.algorithm_config import DifferentiableAlgorithmConfig
from ray.rllib.core import DEFAULT_MODULE_ID
from ray.rllib.core.columns import Columns
from ray.rllib.core.learner.differentiable_learner_config import (
DifferentiableLearnerConfig,
)
from ray.rllib.core.learner.training_data import TrainingData
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.examples.algorithms.classes.maml_lr_differentiable_learner import (
MAMLTorchDifferentiableLearner,
)
from ray.rllib.examples.algorithms.classes.maml_lr_differentiable_rlm import (
DifferentiableTorchRLModule,
)
from ray.rllib.examples.algorithms.classes.maml_lr_meta_learner import (
MAMLTorchMetaLearner,
)
from ray.rllib.examples.utils import add_rllib_example_script_args
from ray.rllib.policy.sample_batch import MultiAgentBatch, SampleBatch
from ray.rllib.utils.framework import try_import_torch
# Import torch.
torch, _ = try_import_torch()
# Implement generation of data from sinusoid curves.
def generate_sinusoid_task(batch_size, noise_std=0.1, return_params=False):
"""Generate a sinusoid task with random amplitude and phase.
Args:
batch_size: The number of data points to be generated.
noise_std: An optional standard deviation to be used in the sinusoid
data generation. Defines a linear error term added to the sine
curve.
return_params: If the sampled amplitude and phase should be returned.
Returns:
Torch tensors with the support data and the labels of a sinusoid
curve.
"""
# Sample the amplitude and the phase for a task.
amplitude = np.random.uniform(0.1, 5.0)
phase = np.random.uniform(0.0, np.pi)
# Sample the support.
x = np.random.uniform(-5.0, 5.0, (batch_size, 1))
# Generate the labels.
y = amplitude * np.sin(x - phase)
# Add noise.
y += noise_std * np.random.random((batch_size, 1))
# If sampled parameters should be returned.
if return_params:
# Return torch tensors.
return (
torch.tensor(x, dtype=torch.float32),
torch.tensor(y, dtype=torch.float32),
amplitude,
phase,
)
# Otherwise, return only the sampled data.
else:
return (
torch.tensor(x, dtype=torch.float32),
torch.tensor(y, dtype=torch.float32),
)
def sample_task(batch_size=10, noise_std=0.1, training_data=False, return_params=False):
"""Samples training batches for meta learner and differentiable learner.
Args:
batch_size: The batch size for both meta learning and task learning.
noise_std: An optional standard deviation to be used in the sinusoid
data generation. Defines a linear error term added to the sine
curve.
training_data: Whether data should be returned as `TrainingData`.
Otherwise, a `MultiAgentBatch` is returned. Default is `False`.
return_params: If the sampled amplitude and phase should be returned.
Returns:
A tuple with training batches for the meta learner and the differentiable
learner. If `training_data` is `True`, the data is wrapped into
`TrainingData`, otherwise both batches are `MultiAgentBatch`es.
"""
# Generate training data for meta learner and differentiable learner.
train_batch = {}
generated_data = generate_sinusoid_task(
batch_size * 2, noise_std=noise_std, return_params=return_params
)
train_batch[Columns.OBS], train_batch["y"] = generated_data[:2]
# Convert to `MultiAgentBatch`.
meta_train_batch = MultiAgentBatch(
env_steps=batch_size,
policy_batches={
DEFAULT_MODULE_ID: SampleBatch(
{k: train_batch[k][:batch_size] for k in train_batch}
)
},
)
task_train_batch = MultiAgentBatch(
env_steps=batch_size,
policy_batches={
DEFAULT_MODULE_ID: SampleBatch(
{k: train_batch[k][batch_size:] for k in train_batch}
)
},
)
# If necessary convert to `TrainingData`.
if training_data:
meta_train_batch = TrainingData(
batch=meta_train_batch,
)
task_train_batch = TrainingData(
batch=task_train_batch,
)
# If amplitude and phase should be returned add them to the return tuple.
if return_params:
return meta_train_batch, task_train_batch, *generated_data[2:]
# Otherwise return solely train data.
else:
return meta_train_batch, task_train_batch
# Define arguments.
parser = add_rllib_example_script_args(default_iters=70_000)
parser.add_argument(
"--meta-train-batch-size",
type=int,
default=5,
help="The number of samples per train and test update (meta-learning).",
)
parser.add_argument(
"--meta-lr",
type=float,
default=0.001,
help="The learning rate to be used for meta learning (in the `MetaLearner`).",
)
parser.add_argument(
"--fine-tune-batch-size",
type=int,
default=10,
help="The number of samples for the fine-tuning updates.",
)
parser.add_argument(
"--noise-std",
type=float,
default=0.0,
help="The standard deviation for noise added to the single tasks.",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="An optional random seed. If not set, the experiment is not reproducable.",
)
parser.add_argument(
"--fine-tune-iters",
type=int,
default=10,
help="The number of updates in fine-tuning.",
)
parser.add_argument(
"--fine-tune-lr",
type=float,
default=0.01,
help="The learning rate to be used in fine-tuning the model in the test phase.",
)
parser.add_argument(
"--no-plot",
action="store_true",
help=(
"If plotting should suppressed. Otherwise user action is needed to close "
"the plot early."
),
)
parser.add_argument(
"--pause-plot-secs",
type=int,
default=1000,
help=(
"The number of seconds to keep the plot open. Note the plot can always be "
"closed by the user when open."
),
)
# Parse the arguments.
args = parser.parse_args()
# If a random seed is provided set it for torch and numpy.
if args.seed:
torch.random.manual_seed(args.seed)
np.random.seed(args.seed)
if __name__ == "__main__":
# Define the `RLModule`.
module_spec = RLModuleSpec(
module_class=DifferentiableTorchRLModule,
# Note, the spaces are needed by default but are not used.
observation_space=gym.spaces.Box(-np.inf, np.inf, (1,), dtype=np.float32),
action_space=gym.spaces.Box(-np.inf, np.inf, (1,), dtype=np.float32),
)
# `Learner`s work on `MultiRLModule`s.
multi_module_spec = MultiRLModuleSpec(
rl_module_specs={DEFAULT_MODULE_ID: module_spec}
)
# Build the `MultiRLModule`.
module = multi_module_spec.build()
# Configure the `DifferentiableLearner`.
diff_learner_config = DifferentiableLearnerConfig(
learner_class=MAMLTorchDifferentiableLearner,
minibatch_size=args.meta_train_batch_size,
lr=0.01,
)
# Configure the `TorchMetaLearner` via the `DifferentiableAlgorithmConfig`.
config = (
DifferentiableAlgorithmConfig()
.learners(
# Add the `DifferentiableLearnerConfig`s.
differentiable_learner_configs=[diff_learner_config],
num_gpus_per_learner=args.num_gpus_per_learner or 0,
)
.training(
lr=args.meta_lr,
train_batch_size=args.meta_train_batch_size,
# Use the full batch in a single update.
minibatch_size=args.meta_train_batch_size,
)
)
# Initialize the `TorchMetaLearner`.
meta_learner = MAMLTorchMetaLearner(config=config, module_spec=module_spec)
# Build the `TorchMetaLearner`.
meta_learner.build()
for i in range(args.stop_iters):
# Sample the training data.
meta_training_data, task_training_data = sample_task(
args.meta_train_batch_size, noise_std=args.noise_std, training_data=True
)
# Update the module.
outs = meta_learner.update(
training_data=meta_training_data,
num_epochs=1,
others_training_data=[task_training_data],
)
iter = i + 1
if iter % 1000 == 0:
total_loss = outs["default_policy"]["total_loss"].peek()
print("-------------------------\n")
print(f"Iteration: {iter}")
print(f"Total loss: {total_loss}")
# Generate test data.
test_batch, _, amplitude, phase = sample_task(
batch_size=args.fine_tune_batch_size,
noise_std=args.noise_std,
return_params=True,
)
if config.num_gpus_per_learner > 0:
test_batch = meta_learner._convert_batch_type(test_batch)
# Run inference and plot results.
with torch.no_grad():
# Generate a grid for the support.
x_grid = torch.tensor(
np.arange(-5.0, 5.0, 0.02), dtype=torch.float32, device=meta_learner._device
).view(-1, 1)
# Get label prediction from the model trained by MAML.
y_pred = meta_learner.module[DEFAULT_MODULE_ID]({Columns.OBS: x_grid})["y_pred"]
# Plot the results if requested.
if not args.no_plot:
# Sort the data by the support.
x_order = np.argsort(test_batch[DEFAULT_MODULE_ID][Columns.OBS].numpy()[:, 0])
x_sorted = test_batch[DEFAULT_MODULE_ID][Columns.OBS].numpy()[:, 0][x_order]
y_sorted = test_batch[DEFAULT_MODULE_ID]["y"][:, 0][x_order]
# Plot the data.
def sinusoid(t):
return amplitude * np.sin(t - phase)
plt.ion()
plt.figure(figsize=(5, 3))
# Plot the true sinusoid curve.
plt.plot(x_grid, sinusoid(x_grid), "r", label="Ground Truth")
# Add the sampled support values.
plt.plot(x_sorted, y_sorted, "^", color="purple")
# Add the prediction made by the model after MAML training.
plt.plot(x_grid, y_pred, ":", label="Prediction", color="#90EE90")
plt.title(f"MAML Results from {args.fine_tune_iters} fine-tuning steps.")
# Fine-tune with the meta loss for just a few steps.
optim = meta_learner.get_optimizers_for_module(DEFAULT_MODULE_ID)[0][1]
# Set the learning rate to a larger value.
for g in optim.param_groups:
g["lr"] = args.fine_tune_lr
# Now run the fine-tune iterations and update the model via the meta-learner loss.
for i in range(args.fine_tune_iters):
# Forward pass.
fwd_out = {
DEFAULT_MODULE_ID: meta_learner.module[DEFAULT_MODULE_ID](
test_batch[DEFAULT_MODULE_ID]
)
}
# Compute the MSE prediction loss.
loss_per_module = meta_learner.compute_losses(fwd_out=fwd_out, batch=test_batch)
# Optimize parameters.
optim.zero_grad(set_to_none=True)
loss_per_module[DEFAULT_MODULE_ID].backward()
optim.step()
# Show the loss for few-shot learning (fine-tuning).
print(f"Few shot loss: {loss_per_module[DEFAULT_MODULE_ID].item()}")
# Run the model again after fine-tuning.
with torch.no_grad():
y_pred_fine_tuned = meta_learner.module[DEFAULT_MODULE_ID](
{Columns.OBS: x_grid}
)["y_pred"]
if not args.no_plot:
# Plot the predictions of the fine-tuned model.
plt.plot(
x_grid,
y_pred_fine_tuned,
"-.",
label="Tuned Prediction",
color="green",
mfc="gray",
)
plt.legend()
plt.show()
# Pause the plot until the user closes it.
plt.pause(args.pause_plot_secs)
@@ -0,0 +1,87 @@
import warnings
from pathlib import Path
from ray.rllib.algorithms.marwil import MARWILConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
parser = add_rllib_example_script_args()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
assert (
args.env == "CartPole-v1" or args.env is None
), "This tuned example works only with `CartPole-v1`."
# Define the data paths.
data_path = "offline/tests/data/cartpole/cartpole-v1_large"
base_path = Path(__file__).parents[3]
print(f"base_path={base_path}")
data_path = "local://" / base_path / data_path
print(f"data_path={data_path}")
# Define the MARWIL config.
config = (
MARWILConfig()
.environment(env="CartPole-v1")
# Evaluate every 3 training iterations.
.evaluation(
evaluation_interval=3,
evaluation_num_env_runners=1,
evaluation_duration=5,
evaluation_parallel_to_training=True,
evaluation_config=MARWILConfig.overrides(explore=False),
)
# Note, the `input_` argument is the major argument for the
# new offline API. Via the `input_read_method_kwargs` the
# arguments for the `ray.data.Dataset` read method can be
# configured. The read method needs at least as many blocks
# as remote learners.
.offline_data(
input_=[data_path.as_posix()],
# The `kwargs` for the `map_batches` method in which our
# `OfflinePreLearner` is run. 2 data workers should be run
# concurrently.
map_batches_kwargs={"concurrency": 2, "num_cpus": 1},
# The `kwargs` for the `iter_batches` method. Due to the small
# dataset we choose only a single batch to prefetch.
iter_batches_kwargs={"prefetch_batches": 1},
# The number of iterations to be run per learner when in multi-learner
# mode in a single RLlib training iteration. Leave this to `None` to
# run an entire epoch on the dataset during a single RLlib training
# iteration.
dataset_num_iters_per_learner=5,
)
.training(
beta=1.0,
# To increase learning speed with multiple learners,
# increase the learning rate correspondingly.
lr=0.0008 * (args.num_learners or 1) ** 0.5,
train_batch_size_per_learner=1024,
)
)
if not args.no_tune:
warnings.warn(
"You are running the example with Ray Tune. Offline RL uses "
"Ray Data, which doesn't does not interact seamlessly with Ray Tune. "
"If you encounter difficulties try to run the example without "
"Ray Tune using `--no-tune`."
)
stop = {
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 250.0,
NUM_ENV_STEPS_SAMPLED_LIFETIME: 500000,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,97 @@
# These tags allow extracting portions of this script on Anyscale.
# ws-template-imports-start
import gymnasium as gym
from ray import tune
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.connectors.env_to_module.frame_stacking import FrameStackingEnvToModule
from ray.rllib.connectors.learner.frame_stacking import FrameStackingLearner
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.env.wrappers.atari_wrappers import wrap_atari_for_new_api_stack
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
# ws-template-imports-end
parser = add_rllib_example_script_args(
default_reward=float("inf"),
default_timesteps=3000000,
default_iters=100000000000,
)
parser.set_defaults(
env="ale_py:ALE/Pong-v5",
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
NUM_LEARNERS = args.num_learners or 1
ENV = args.env
# These tags allow extracting portions of this script on Anyscale.
# ws-template-code-start
def _make_env_to_module_connector(env, spaces, device):
return FrameStackingEnvToModule(num_frames=4)
def _make_learner_connector(input_observation_space, input_action_space):
return FrameStackingLearner(num_frames=4)
# Create a custom Atari setup (w/o the usual RLlib-hard-coded framestacking in it).
# We would like our frame stacking connector to do this job.
def _env_creator(cfg):
return wrap_atari_for_new_api_stack(
gym.make(ENV, **cfg, render_mode="rgb_array"),
# Perform frame-stacking through ConnectorV2 API.
framestack=None,
)
tune.register_env("env", _env_creator)
config = (
PPOConfig()
.environment(
"env",
env_config={
# Make analogous to old v4 + NoFrameskip.
"frameskip": 1,
"full_action_space": False,
"repeat_action_probability": 0.0,
},
clip_rewards=True,
)
.env_runners(
env_to_module_connector=_make_env_to_module_connector,
)
.training(
learner_connector=_make_learner_connector,
train_batch_size_per_learner=4000,
minibatch_size=128,
lambda_=0.95,
kl_coeff=0.5,
clip_param=0.1,
vf_clip_param=10.0,
entropy_coeff=0.01,
num_epochs=10,
lr=0.00015 * NUM_LEARNERS,
grad_clip=100.0,
grad_clip_by="global_norm",
)
.rl_module(
model_config=DefaultModelConfig(
conv_filters=[[16, 4, 2], [32, 4, 2], [64, 4, 2], [128, 4, 2]],
conv_activation="relu",
head_fcnet_hiddens=[256],
vf_share_layers=True,
),
)
)
# ws-template-code-end
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args=args)
@@ -0,0 +1,138 @@
from ray import tune
from ray.rllib.algorithms.ppo.ppo import PPOConfig
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune import Stopper
# Needs the following packages to be installed on Ubuntu:
# sudo apt-get libosmesa-dev
# sudo apt-get install patchelf
# python -m pip install "gymnasium[mujoco]"
# Might need to be added to bashsrc:
# export MUJOCO_GL=osmesa"
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.mujoco/mujoco200/bin"
# See the following links for becnhmark results of other libraries:
# Original paper: https://arxiv.org/pdf/1707.06347
# CleanRL: https://wandb.ai/openrlbenchmark/openrlbenchmark/reports"
# /MuJoCo-CleanRL-s-PPO--VmlldzoxODAwNjkw
# AgileRL: https://github.com/AgileRL/AgileRL?tab=readme-ov-file#benchmarks
benchmark_envs = {
"HalfCheetah-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2000,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"Hopper-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 2250,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"InvertedPendulum-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 1000,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"InvertedDoublePendulum-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 8000,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"Reacher-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -15,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"Swimmer-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 120,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"Walker2d-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 3500,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
}
# Define a `tune.Stopper` that stops the training if the benchmark is reached
# or the maximum number of timesteps is exceeded.
class BenchmarkStopper(Stopper):
def __init__(self, benchmark_envs):
self.benchmark_envs = benchmark_envs
def __call__(self, trial_id, result):
# Stop training if the mean reward is reached.
if (
result[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN]
>= self.benchmark_envs[result["env"]][
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}"
]
):
return True
# Otherwise check, if the total number of timesteps is exceeded.
elif (
result[f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
>= self.benchmark_envs[result["env"]][f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
):
return True
# Otherwise continue training.
else:
return False
# Note, this needs to implemented b/c the parent class is abstract.
def stop_all(self):
return False
config = (
PPOConfig()
.environment(env=tune.grid_search(list(benchmark_envs.keys())))
.env_runners(
# Following the paper.
num_env_runners=32,
rollout_fragment_length=512,
)
.learners(
# Let's start with a small number of learner workers and
# add later a tune grid search for these resources.
num_learners=1,
num_gpus_per_learner=1,
)
# TODO (simon): Adjust to new model_config_dict.
.training(
# Following the paper.
lambda_=0.95,
lr=0.0003,
num_epochs=15,
train_batch_size=32 * 512,
minibatch_size=4096,
vf_loss_coeff=0.01,
model={
"fcnet_hiddens": [64, 64],
"fcnet_activation": "tanh",
"vf_share_layers": True,
},
)
.reporting(
metrics_num_episodes_for_smoothing=5,
min_sample_timesteps_per_iteration=1000,
)
.evaluation(
evaluation_duration="auto",
evaluation_interval=1,
evaluation_num_env_runners=1,
evaluation_parallel_to_training=True,
evaluation_config={
"explore": True,
},
)
)
tuner = tune.Tuner(
"PPO",
param_space=config,
run_config=tune.RunConfig(
stop=BenchmarkStopper(benchmark_envs=benchmark_envs),
name="benchmark_ppo_mujoco",
),
)
tuner.fit()
@@ -0,0 +1,38 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.connectors.env_to_module import FlattenObservations
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.cartpole_with_large_observation_space import (
CartPoleWithLargeObservationSpace,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(default_reward=450.0, default_timesteps=300000)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
PPOConfig()
.environment(CartPoleWithLargeObservationSpace)
.env_runners(
env_to_module_connector=lambda env, spaces, device: FlattenObservations(),
episodes_to_numpy=False,
)
.training(
lr=0.0003,
num_epochs=6,
vf_loss_coeff=0.01,
)
.rl_module(
model_config=DefaultModelConfig(
use_lstm=True,
lstm_cell_size=1024,
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,32 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(default_reward=450.0, default_timesteps=300000)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
PPOConfig()
.environment("CartPole-v1")
.training(
lr=0.0003,
num_epochs=6,
vf_loss_coeff=0.01,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[32],
fcnet_activation="linear",
vf_share_layers=True,
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,55 @@
import gymnasium as gym
from gymnasium.wrappers import TimeLimit
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
EVALUATION_RESULTS,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args()
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
# For training, use a time-truncated (max. 50 timestep) version of CartPole-v1.
register_env(
"cartpole_truncated",
lambda _: TimeLimit(gym.make("CartPole-v1"), max_episode_steps=50),
)
config = (
PPOConfig()
.environment("cartpole_truncated")
.env_runners(num_envs_per_env_runner=10)
.training(
lr=0.0003,
num_epochs=6,
vf_loss_coeff=0.01,
)
# For evaluation, use the "real" CartPole-v1 env (up to 500 steps).
.evaluation(
evaluation_config=PPOConfig.overrides(
env="CartPole-v1",
explore=False,
),
evaluation_interval=1,
evaluation_num_env_runners=1,
)
)
stop = {
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 500000,
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 80.0,
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,13 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.examples.envs.classes.random_env import RandomLargeObsSpaceEnv
config = (
PPOConfig()
# Switch off np.random, which is known to have memory leaks.
.environment(RandomLargeObsSpaceEnv, env_config={"static_samples": True})
.env_runners(
num_env_runners=4,
num_envs_per_env_runner=5,
)
.training(train_batch_size=500, minibatch_size=256, num_epochs=5)
)
@@ -0,0 +1,57 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args()
parser.set_defaults(
num_agents=2,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env("multi_agent_cartpole", lambda cfg: MultiAgentCartPole(config=cfg))
config = (
PPOConfig()
.environment("multi_agent_cartpole", env_config={"num_agents": args.num_agents})
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[32],
fcnet_activation="linear",
vf_share_layers=True,
),
)
.env_runners(
num_envs_per_env_runner=2,
)
.training(
lr=0.0003,
num_epochs=6,
vf_loss_coeff=0.01,
)
.multi_agent(
policy_mapping_fn=lambda aid, *arg, **kw: f"p{aid}",
policies={f"p{i}" for i in range(args.num_agents)},
)
)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: 400000,
# Divide by num_agents to get actual return per agent.
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 450.0 * (args.num_agents or 1),
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,264 @@
"""
Multi-agent RLlib Footsies Example (PPO)
About:
- Example is based on the Footsies environment (https://github.com/chasemcd/FootsiesGym).
- Footsies is a two-player fighting game where each player controls a character and tries to hit the opponent while avoiding being hit.
- Footsies is a zero-sum game, when one player wins (+1 reward) the other loses (-1 reward).
Summary:
- Main policy is an LSTM-based policy.
- Training algorithm is PPO.
Training:
- Training is governed by adding new, more complex opponents to the mix as the main policy reaches a certain win rate threshold against the current opponent.
- Current opponent is always the newest opponent added to the mix.
- Training starts with a very simple opponent: "noop" (does nothing), then progresses to "back" (only moves backwards). These are the fixed (very simple) policies that are used to kick off the training.
- After "random", new opponents are frozen copies of the main policy at different training stages. They will be added to the mix as "lstm_v0", "lstm_v1", etc.
- In this way - after kick-starting the training with fixed simple opponents - the main policy will play against a version of itself from an earlier training stage.
- The main policy has to achieve the win rate threshold against the current opponent to add a new opponent to the mix.
- Training concludes when the target mix size is reached.
Evaluation:
- Evaluation is performed against the current (newest) opponent.
- Evaluation runs for a fixed number of episodes at the end of each training iteration.
"""
import functools
from pathlib import Path
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.core.rl_module import MultiRLModuleSpec, RLModuleSpec
from ray.rllib.env.multi_agent_env_runner import MultiAgentEnvRunner
from ray.rllib.examples.envs.classes.multi_agent.footsies.fixed_rlmodules import (
BackFixedRLModule,
NoopFixedRLModule,
)
from ray.rllib.examples.envs.classes.multi_agent.footsies.footsies_env import (
env_creator,
)
from ray.rllib.examples.envs.classes.multi_agent.footsies.utils import (
Matchmaker,
Matchup,
MetricsLoggerCallback,
MixManagerCallback,
platform_for_binary_to_download,
)
from ray.rllib.examples.rl_modules.classes.lstm_containing_rlm import (
LSTMContainingRLModule,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import NUM_ENV_STEPS_SAMPLED_LIFETIME
from ray.tune.registry import register_env
from ray.tune.result import TRAINING_ITERATION
# setting two default stopping criteria:
# 1. training_iteration (via "stop_iters")
# 2. num_env_steps_sampled_lifetime (via "default_timesteps")
# ...values very high to make sure that the test passes by adding
# all required policies to the mix, not by hitting the iteration limit.
# Our main stopping criterion is "target_mix_size" (see an argument below).
parser = add_rllib_example_script_args(
default_iters=500,
default_timesteps=5_000_000,
)
parser.add_argument(
"--train-start-port",
type=int,
default=45001,
help="First port number for the Footsies training environment server (default: 45001). Each server gets its own port.",
)
parser.add_argument(
"--eval-start-port",
type=int,
default=55001,
help="First port number for the Footsies evaluation environment server (default: 55001) Each server gets its own port.",
)
parser.add_argument(
"--binary-download-dir",
type=Path,
default="/tmp/ray/binaries/footsies",
help="Directory to download Footsies binaries (default: /tmp/ray/binaries/footsies)",
)
parser.add_argument(
"--binary-extract-dir",
type=Path,
default="/tmp/ray/binaries/footsies",
help="Directory to extract Footsies binaries (default: /tmp/ray/binaries/footsies)",
)
parser.add_argument(
"--win-rate-threshold",
type=float,
default=0.8,
help="The main policy should have at least 'win-rate-threshold' win rate against the "
"other policy to advance to the next level. Moving to the next level "
"means adding a new policy to the mix.",
)
parser.add_argument(
"--target-mix-size",
type=int,
default=5,
help="Target number of policies (RLModules) in the mix to consider the test passed. "
"The initial mix size is 2: 'main policy' vs. 'other'. "
"`--target-mix-size=5` means that 3 new policies will be added to the mix. "
"Whether to add new policy is decided by checking the '--win-rate-threshold' condition. ",
)
parser.add_argument(
"--rollout-fragment-length",
type=int,
default=256,
help="The length of each rollout fragment to be collected by the EnvRunners when sampling.",
)
parser.add_argument(
"--log-unity-output",
action="store_true",
help="Whether to log Unity output (from the game engine). Default is False.",
default=False,
)
parser.add_argument(
"--render",
action="store_true",
default=False,
help="Whether to render the Footsies environment. Default is False.",
)
main_policy = "lstm"
args = parser.parse_args()
register_env(name="FootsiesEnv", env_creator=env_creator)
# Detect platform and choose appropriate binary
binary_to_download = platform_for_binary_to_download(args.render)
config = (
PPOConfig()
.reporting(
min_time_s_per_iteration=30,
)
.environment(
env="FootsiesEnv",
env_config={
"max_t": 1000,
"frame_skip": 4,
"observation_delay": 16,
"train_start_port": args.train_start_port,
"eval_start_port": args.eval_start_port,
"host": "localhost",
"binary_download_dir": args.binary_download_dir,
"binary_extract_dir": args.binary_extract_dir,
"binary_to_download": binary_to_download,
"log_unity_output": args.log_unity_output,
},
)
.learners(
num_learners=1,
num_cpus_per_learner=1,
num_gpus_per_learner=0,
num_aggregator_actors_per_learner=0,
)
.env_runners(
env_runner_cls=MultiAgentEnvRunner,
num_env_runners=args.num_env_runners or 1,
num_cpus_per_env_runner=0.5,
num_envs_per_env_runner=1,
batch_mode="truncate_episodes",
rollout_fragment_length=args.rollout_fragment_length,
episodes_to_numpy=False,
create_env_on_local_worker=True,
)
.training(
train_batch_size_per_learner=args.rollout_fragment_length
* (args.num_env_runners or 1),
lr=1e-4,
entropy_coeff=0.01,
num_epochs=10,
minibatch_size=128,
)
.multi_agent(
policies={
main_policy,
"noop",
"back",
},
# this is a starting policy_mapping_fn
# It will be updated by the MixManagerCallback during training.
policy_mapping_fn=Matchmaker(
[Matchup(main_policy, "noop", 1.0)]
).agent_to_module_mapping_fn,
# we only train the main policy, this doesn't change during training.
policies_to_train=[main_policy],
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
main_policy: RLModuleSpec(
module_class=LSTMContainingRLModule,
model_config={
"lstm_cell_size": 128,
"dense_layers": [128, 128],
"max_seq_len": 64,
},
),
# for simplicity, all fixed RLModules are added to the config at the start.
# However, only "noop" is used at the start of training,
# the others are added to the mix later by the MixManagerCallback.
"noop": RLModuleSpec(module_class=NoopFixedRLModule),
"back": RLModuleSpec(module_class=BackFixedRLModule),
},
)
)
.evaluation(
evaluation_num_env_runners=args.evaluation_num_env_runners or 1,
evaluation_sample_timeout_s=120,
evaluation_interval=1,
evaluation_duration=10, # 10 episodes is enough to get a good win rate estimate
evaluation_duration_unit="episodes",
evaluation_parallel_to_training=False,
# we may add new RLModules to the mix at the end of the evaluation stage.
# Running evaluation in parallel may result in training for one more iteration on the old mix.
evaluation_force_reset_envs_before_iteration=True,
evaluation_config={
"env_config": {"env-for-evaluation": True},
}, # evaluation_config is used to add an argument to the env creator.
)
.callbacks(
[
functools.partial(
MetricsLoggerCallback,
main_policy=main_policy,
),
functools.partial(
MixManagerCallback,
win_rate_threshold=args.win_rate_threshold,
main_policy=main_policy,
target_mix_size=args.target_mix_size,
starting_modules=[main_policy, "noop"],
fixed_modules_progression_sequence=(
"noop",
"back",
),
),
]
)
)
# stopping criteria to be passed to Ray Tune. The main stopping criterion is "mix_size".
# "mix_size" is reported at the end of each training iteration by the MixManagerCallback.
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
TRAINING_ITERATION: args.stop_iters,
"mix_size": args.target_mix_size,
}
if __name__ == "__main__":
results = run_rllib_example_script_experiment(
base_config=config,
args=args,
stop=stop,
success_metric={
"mix_size": args.target_mix_size
}, # pass the success metric for RLlib's testing framework
)
@@ -0,0 +1,58 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.connectors.env_to_module import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentPendulum
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(default_timesteps=500000)
parser.set_defaults(
num_agents=2,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env("multi_agent_pendulum", lambda cfg: MultiAgentPendulum(config=cfg))
config = (
PPOConfig()
.environment("multi_agent_pendulum", env_config={"num_agents": args.num_agents})
.env_runners(
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(
multi_agent=True
),
)
.training(
train_batch_size_per_learner=1024,
minibatch_size=128,
lr=0.0002 * (args.num_learners or 1) ** 0.5,
gamma=0.95,
lambda_=0.5,
)
.rl_module(
model_config=DefaultModelConfig(fcnet_activation="relu"),
)
.multi_agent(
policy_mapping_fn=lambda aid, *arg, **kw: f"p{aid}",
policies={f"p{i}" for i in range(args.num_agents)},
)
)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
# Divide by num_agents to get actual return per agent.
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -300.0 * (args.num_agents or 1),
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,63 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.connectors.env_to_module import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentStatelessCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune import register_env
parser = add_rllib_example_script_args(default_timesteps=4000000)
parser.set_defaults(
num_agents=2,
num_env_runners=3,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env(
"multi_stateless_cart",
lambda _: MultiAgentStatelessCartPole({"num_agents": args.num_agents}),
)
config = (
PPOConfig()
.environment("multi_stateless_cart")
.env_runners(
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(
multi_agent=True
),
)
.training(
lr=0.0003 * ((args.num_learners or 1) ** 0.5),
num_epochs=6,
vf_loss_coeff=0.05,
)
.rl_module(
model_config=DefaultModelConfig(
use_lstm=True,
max_seq_len=20,
),
)
.multi_agent(
policy_mapping_fn=lambda aid, *arg, **kw: f"p{aid}",
policies={f"p{i}" for i in range(args.num_agents)},
)
)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
# Divide by num_agents to get actual return per agent.
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 300.0 * (args.num_agents or 1),
}
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,37 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.connectors.env_to_module import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(default_timesteps=400000, default_reward=-300)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
PPOConfig()
.environment("Pendulum-v1")
.env_runners(
num_env_runners=2,
num_envs_per_env_runner=20,
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(),
)
.training(
train_batch_size_per_learner=1024,
minibatch_size=128,
lr=0.0002 * (args.num_learners or 1) ** 0.5,
gamma=0.95,
lambda_=0.5,
# num_epochs=8,
)
.rl_module(
model_config=DefaultModelConfig(fcnet_activation="relu"),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,43 @@
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.connectors.env_to_module import MeanStdFilter
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.stateless_cartpole import StatelessCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=2000000,
default_reward=350.0,
)
parser.set_defaults(
num_env_runners=3,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
PPOConfig()
.environment(StatelessCartPole)
.env_runners(
env_to_module_connector=lambda env, spaces, device: MeanStdFilter(),
)
.training(
lr=0.0003 * ((args.num_learners or 1) ** 0.5),
num_epochs=6,
vf_loss_coeff=0.05,
)
.rl_module(
model_config=DefaultModelConfig(
vf_share_layers=False,
use_lstm=True,
max_seq_len=20,
),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,143 @@
from ray import tune
from ray.rllib.algorithms.sac.sac import SACConfig
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune import Stopper
# Needs the following packages to be installed on Ubuntu:
# sudo apt-get libosmesa-dev
# sudo apt-get install patchelf
# python -m pip install "gymnasium[mujoco]"
# Might need to be added to bashsrc:
# export MUJOCO_GL=osmesa"
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.mujoco/mujoco200/bin"
# See the following links for becnhmark results of other libraries:
# Original paper: https://arxiv.org/abs/1812.05905
# CleanRL: https://wandb.ai/cleanrl/cleanrl.benchmark/reports/Mujoco--VmlldzoxODE0NjE
# AgileRL: https://github.com/AgileRL/AgileRL?tab=readme-ov-file#benchmarks
benchmark_envs = {
"HalfCheetah-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 15000,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 3000000,
},
"Hopper-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 3500,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 1000000,
},
"Humanoid-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 8000,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 10000000,
},
"Ant-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 5500,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 3000000,
},
"Walker2d-v4": {
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 6000,
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 3000000,
},
}
# Define a `tune.Stopper` that stops the training if the benchmark is reached
# or the maximum number of timesteps is exceeded.
class BenchmarkStopper(Stopper):
def __init__(self, benchmark_envs):
self.benchmark_envs = benchmark_envs
def __call__(self, trial_id, result):
# Stop training if the mean reward is reached.
if (
result[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN]
>= self.benchmark_envs[result["env"]][
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}"
]
):
return True
# Otherwise check, if the total number of timesteps is exceeded.
elif (
result[f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
>= self.benchmark_envs[result["env"]][f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}"]
):
return True
# Otherwise continue training.
else:
return False
# Note, this needs to implemented b/c the parent class is abstract.
def stop_all(self):
return False
config = (
SACConfig()
.environment(env=tune.grid_search(list(benchmark_envs.keys())))
.env_runners(
rollout_fragment_length=1,
num_env_runners=0,
)
.learners(
# Note, we have a sample/train ratio of 1:1 and a small train
# batch, so 1 learner with a single GPU should suffice.
num_learners=1,
num_gpus_per_learner=1,
)
# TODO (simon): Adjust to new model_config_dict.
.training(
initial_alpha=1.001,
# Choose a smaller learning rate for the actor (policy).
actor_lr=3e-5,
critic_lr=3e-4,
alpha_lr=1e-4,
target_entropy="auto",
n_step=1,
tau=0.005,
train_batch_size=256,
target_network_update_freq=1,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 1000000,
"alpha": 0.6,
"beta": 0.4,
},
num_steps_sampled_before_learning_starts=256,
model={
"fcnet_hiddens": [256, 256],
"fcnet_activation": "relu",
"post_fcnet_hiddens": [],
"post_fcnet_activation": None,
"post_fcnet_weights_initializer": "orthogonal_",
"post_fcnet_weights_initializer_config": {"gain": 0.01},
"fusionnet_hiddens": [256, 256, 256],
"fusionnet_activation": "relu",
},
)
.reporting(
metrics_num_episodes_for_smoothing=5,
min_sample_timesteps_per_iteration=1000,
)
.evaluation(
evaluation_duration="auto",
evaluation_interval=1,
evaluation_num_env_runners=1,
evaluation_parallel_to_training=True,
evaluation_config={
"explore": False,
},
)
)
tuner = tune.Tuner(
"SAC",
param_space=config,
run_config=tune.RunConfig(
stop=BenchmarkStopper(benchmark_envs=benchmark_envs),
name="benchmark_sac_mujoco",
),
)
tuner.fit()
@@ -0,0 +1,64 @@
from torch import nn
from ray.rllib.algorithms.sac.sac import SACConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=1000000,
default_reward=12000.0,
default_iters=2000,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
SACConfig()
.environment("HalfCheetah-v4")
.training(
initial_alpha=1.001,
# lr=0.0006 is very high, w/ 4 GPUs -> 0.0012
# Might want to lower it for better stability, but it does learn well.
actor_lr=2e-4 * (args.num_learners or 1) ** 0.5,
critic_lr=8e-4 * (args.num_learners or 1) ** 0.5,
alpha_lr=9e-4 * (args.num_learners or 1) ** 0.5,
lr=None,
target_entropy="auto",
n_step=(1, 5), # 1?
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 100000,
"alpha": 0.6,
"beta": 0.4,
},
num_steps_sampled_before_learning_starts=10000,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer="orthogonal_",
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
),
)
.reporting(
metrics_num_episodes_for_smoothing=5,
min_sample_timesteps_per_iteration=1000,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,71 @@
"""This is WIP.
On a single-GPU machine, with the `--num-gpus-per-learner=1` command line option, this
example should learn a episode return of >1000 in ~10h, which is still very basic, but
does somewhat prove SAC's capabilities. Some more hyperparameter fine tuning, longer
runs, and more scale (`--num-learners > 0` and `--num-env-runners > 0`) should help push
this up.
"""
from torch import nn
from ray.rllib.algorithms.sac.sac import SACConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=1000000,
default_reward=12000.0,
default_iters=2000,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
SACConfig()
.environment("Humanoid-v4")
.training(
initial_alpha=1.001,
actor_lr=0.00005,
critic_lr=0.00005,
alpha_lr=0.00005,
target_entropy="auto",
n_step=(1, 3),
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 1000000,
"alpha": 0.6,
"beta": 0.4,
},
num_steps_sampled_before_learning_starts=10000,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[1024, 1024],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer="orthogonal_",
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
)
)
.reporting(
metrics_num_episodes_for_smoothing=5,
min_sample_timesteps_per_iteration=1000,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,61 @@
from torch import nn
from ray.rllib.algorithms.sac.sac import SACConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=20000,
default_reward=-250.0,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
SACConfig()
.environment("MountainCar-v0")
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer="orthogonal_",
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
),
)
.reporting(
metrics_num_episodes_for_smoothing=5,
)
.training(
initial_alpha=1.001,
# Use a smaller learning rate for the policy.
actor_lr=2e-4 * (args.num_learners or 1) ** 0.5,
critic_lr=8e-4 * (args.num_learners or 1) ** 0.5,
alpha_lr=9e-4 * (args.num_learners or 1) ** 0.5,
lr=None,
target_entropy="auto",
n_step=(2, 5),
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 100000,
"alpha": 1.0,
"beta": 0.0,
},
num_steps_sampled_before_learning_starts=256 * (args.num_learners or 1),
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,87 @@
from torch import nn
from ray.rllib.algorithms.sac import SACConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentPendulum
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
EPISODE_RETURN_MEAN,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(
default_timesteps=500000,
)
parser.set_defaults(
num_agents=2,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
register_env("multi_agent_pendulum", lambda cfg: MultiAgentPendulum(config=cfg))
config = (
SACConfig()
.environment("multi_agent_pendulum", env_config={"num_agents": args.num_agents})
.training(
initial_alpha=1.001,
# Use a smaller learning rate for the policy.
actor_lr=2e-4 * (args.num_learners or 1) ** 0.5,
critic_lr=8e-4 * (args.num_learners or 1) ** 0.5,
alpha_lr=9e-4 * (args.num_learners or 1) ** 0.5,
lr=None,
target_entropy="auto",
n_step=(2, 5),
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
replay_buffer_config={
"type": "MultiAgentPrioritizedEpisodeReplayBuffer",
"capacity": 100000,
"alpha": 1.0,
"beta": 0.0,
},
num_steps_sampled_before_learning_starts=256,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer=nn.init.orthogonal_,
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
),
)
.reporting(
metrics_num_episodes_for_smoothing=5,
)
)
if args.num_agents > 0:
config.multi_agent(
policy_mapping_fn=lambda aid, *arg, **kw: f"p{aid}",
policies={f"p{i}" for i in range(args.num_agents)},
)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
# `episode_return_mean` is the sum of all agents/policies' returns.
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -450.0 * args.num_agents,
}
if __name__ == "__main__":
assert (
args.num_agents > 0
), "The `--num-agents` arg must be > 0 for this script to work."
run_rllib_example_script_experiment(config, args, stop=stop)
@@ -0,0 +1,61 @@
from torch import nn
from ray.rllib.algorithms.sac.sac import SACConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=20000,
default_reward=-250.0,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
SACConfig()
.environment("Pendulum-v1")
.training(
initial_alpha=1.001,
# Use a smaller learning rate for the policy.
actor_lr=2e-4 * (args.num_learners or 1) ** 0.5,
critic_lr=8e-4 * (args.num_learners or 1) ** 0.5,
alpha_lr=9e-4 * (args.num_learners or 1) ** 0.5,
# TODO (sven): Maybe go back to making this a dict of the sub-learning rates?
lr=None,
target_entropy="auto",
n_step=(2, 5),
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 100000,
"alpha": 1.0,
"beta": 0.0,
},
num_steps_sampled_before_learning_starts=256 * (args.num_learners or 1),
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer="orthogonal_",
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
),
)
.reporting(
metrics_num_episodes_for_smoothing=5,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,122 @@
"""Example showing how to train TQC on the Humanoid-v4 MuJoCo environment.
TQC (Truncated Quantile Critics) is an extension of SAC that uses distributional
critics with quantile regression. By truncating the upper quantiles when computing
target values, TQC reduces overestimation bias that can plague actor-critic methods,
leading to more stable and efficient learning on complex continuous control tasks.
This example:
- Trains on Humanoid-v4, a challenging 17-DoF locomotion task
- Uses truncated quantile critics with 25 quantiles and 2 critics
- Drops the top 2 quantiles per network to reduce overestimation bias
- Employs prioritized experience replay with capacity of 1M transitions
- Uses a large network architecture (1024x1024) suitable for high-dimensional control
- Applies mixed n-step returns (1 to 3 steps) for variance reduction
- Expects to achieve episode returns >12000 with sufficient training
How to run this script
----------------------
`python humanoid_tqc.py --num-env-runners=4`
For faster training, use GPU acceleration and more parallelism:
`python humanoid_tqc.py --num-learners=1 --num-gpus-per-learner=1 --num-env-runners=8`
To scale up with distributed learning using multiple learners and env-runners:
`python humanoid_tqc.py --num-learners=2 --num-env-runners=16`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
On a single-GPU machine with --num-gpus-per-learner=1, this example should learn
an episode return of >1000 within approximately 10 hours. With more hyperparameter
tuning, longer runs, and additional scale, returns of >12000 are achievable.
"""
from torch import nn
from ray.rllib.algorithms.tqc.tqc import TQCConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=1_000_000,
default_reward=12_000.0,
default_iters=2_000,
)
parser.set_defaults(
num_env_runners=4,
num_envs_per_env_runner=8,
num_learners=1,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
TQCConfig()
.environment("Humanoid-v4")
.env_runners(
num_env_runners=args.num_env_runners,
num_envs_per_env_runner=args.num_envs_per_env_runner,
)
.learners(
num_learners=args.num_learners,
num_gpus_per_learner=1,
num_aggregator_actors_per_learner=2,
)
.training(
initial_alpha=1.001,
actor_lr=0.00005,
critic_lr=0.00005,
alpha_lr=0.00005,
target_entropy="auto",
n_step=(1, 3),
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
# TQC-specific parameters
n_quantiles=25,
n_critics=2,
top_quantiles_to_drop_per_net=2,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 1000000,
"alpha": 0.6,
"beta": 0.4,
},
num_steps_sampled_before_learning_starts=10000,
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[1024, 1024],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer="orthogonal_",
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
)
)
.reporting(
metrics_num_episodes_for_smoothing=5,
min_sample_timesteps_per_iteration=1000,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,123 @@
"""Example showing how to train TQC on the Pendulum-v1 classic control environment.
TQC (Truncated Quantile Critics) is an extension of SAC that uses distributional
critics with quantile regression to reduce overestimation bias. This example
demonstrates TQC on a simple continuous control task suitable for quick experiments.
This example:
- Trains on Pendulum-v1, a classic swing-up control task with continuous actions
- Uses truncated quantile critics with 25 quantiles and 2 critics
- Drops the top 2 quantiles per network to reduce overestimation bias
- Employs prioritized experience replay with 100K capacity
- Scales learning rates based on the number of learners for distributed training
- Uses mixed n-step returns (2 to 5 steps) for improved sample efficiency
- Expects to achieve episode returns of approximately -250 within 20K timesteps
How to run this script
----------------------
`python pendulum_tqc.py`
To run with different configuration:
`python pendulum_tqc.py --num-env-runners=2`
To scale up with distributed learning using multiple learners and env-runners:
`python pendulum_tqc.py --num-learners=2 --num-env-runners=8`
To use a GPU-based learner add the number of GPUs per learners:
`python pendulum_tqc.py --num-learners=1 --num-gpus-per-learner=1`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
With default settings, this example should achieve an episode return of around -250
within 20,000 timesteps. The Pendulum environment has a maximum possible return of 0
(perfect balancing), with typical good performance in the -200 to -300 range.
"""
from torch import nn
from ray.rllib.algorithms.tqc.tqc import TQCConfig
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_timesteps=20000,
default_reward=-250.0,
)
parser.set_defaults(
num_env_runners=4,
num_envs_per_env_runner=8,
num_learners=1,
)
# Use `parser` to add your own custom command line options to this script
# and (if needed) use their values to set up `config` below.
args = parser.parse_args()
config = (
TQCConfig()
.environment("Pendulum-v1")
.env_runners(
num_env_runners=args.num_env_runners,
num_envs_per_env_runner=args.num_envs_per_env_runner,
)
.learners(
num_learners=args.num_learners,
num_gpus_per_learner=1,
num_aggregator_actors_per_learner=2,
)
.training(
initial_alpha=1.001,
# Use a smaller learning rate for the policy.
actor_lr=2e-4 * (args.num_learners or 1) ** 0.5,
critic_lr=8e-4 * (args.num_learners or 1) ** 0.5,
alpha_lr=9e-4 * (args.num_learners or 1) ** 0.5,
lr=None,
target_entropy="auto",
n_step=(2, 5),
tau=0.005,
train_batch_size_per_learner=256,
target_network_update_freq=1,
# TQC-specific parameters
n_quantiles=25,
n_critics=2,
top_quantiles_to_drop_per_net=2,
replay_buffer_config={
"type": "PrioritizedEpisodeReplayBuffer",
"capacity": 100000,
"alpha": 1.0,
"beta": 0.0,
},
num_steps_sampled_before_learning_starts=256 * (args.num_learners or 1),
)
.rl_module(
model_config=DefaultModelConfig(
fcnet_hiddens=[256, 256],
fcnet_activation="relu",
fcnet_kernel_initializer=nn.init.xavier_uniform_,
head_fcnet_hiddens=[],
head_fcnet_activation=None,
head_fcnet_kernel_initializer="orthogonal_",
head_fcnet_kernel_initializer_kwargs={"gain": 0.01},
fusionnet_hiddens=[256, 256, 256],
fusionnet_activation="relu",
),
)
.reporting(
metrics_num_episodes_for_smoothing=5,
)
)
if __name__ == "__main__":
run_rllib_example_script_experiment(config, args)
@@ -0,0 +1,115 @@
"""Example of how to write a custom Algorithm.
This is an end-to-end example for how to implement a custom Algorithm, including
a matching AlgorithmConfig class and Learner class. There is no particular RLModule API
needed for this algorithm, which means that any TorchRLModule returning actions
or action distribution parameters suffices.
The RK algorithm implemented here is "vanilla policy gradient" (VPG) in its simplest
form, without a value function baseline.
See the actual VPG algorithm class here:
https://github.com/ray-project/ray/blob/master/rllib/examples/algorithms/classes/vpg.py
The Learner class the algorithm uses by default (if the user doesn't specify a custom
Learner):
https://github.com/ray-project/ray/blob/master/rllib/examples/learners/classes/vpg_torch_learner.py # noqa
And the RLModule class the algorithm uses by default (if the user doesn't specify a
custom RLModule):
https://github.com/ray-project/ray/blob/master/rllib/examples/rl_modules/classes/vpg_torch_rlm.py # noqa
This example shows:
- how to subclass the AlgorithmConfig base class to implement a custom algorithm's.
config class.
- how to subclass the Algorithm base class to implement a custom Algorithm,
including its `training_step` method.
- how to subclass the TorchLearner base class to implement a custom Learner with
loss function, overriding `compute_loss_for_module` and
`after_gradient_based_update`.
- how to define a default RLModule used by the algorithm in case the user
doesn't bring their own custom RLModule. The VPG algorithm doesn't require any
specific RLModule APIs, so any RLModule returning actions or action distribution
inputs suffices.
We compute a plain policy gradient loss without value function baseline.
The experiment shows that even with such a simple setup, our custom algorithm is still
able to successfully learn CartPole-v1.
How to run this script
----------------------
`python [script file name].py`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
With some fine-tuning of the learning rate, the batch size, and maybe the
number of env runners and number of envs per env runner, you should see decent
learning behavior on the CartPole-v1 environment:
+-----------------------------+------------+--------+------------------+
| Trial name | status | iter | total time (s) |
| | | | |
|-----------------------------+------------+--------+------------------+
| VPG_CartPole-v1_2973e_00000 | TERMINATED | 451 | 59.5184 |
+-----------------------------+------------+--------+------------------+
+-----------------------+------------------------+------------------------+
| episode_return_mean | num_env_steps_sample | ...env_steps_sampled |
| | d_lifetime | _lifetime_throughput |
|-----------------------+------------------------+------------------------|
| 250.52 | 415787 | 7428.98 |
+-----------------------+------------------------+------------------------+
"""
from ray.rllib.examples.algorithms.classes.vpg import VPGConfig
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
parser = add_rllib_example_script_args(
default_reward=250.0,
default_iters=1000,
default_timesteps=1_000_000,
)
if __name__ == "__main__":
args = parser.parse_args()
base_config = (
VPGConfig()
.environment("CartPole-v1")
.training(
# The only VPG-specific setting. How many episodes per train batch?
num_episodes_per_train_batch=10,
# Set other config parameters.
lr=0.0005,
# Note that you don't have to set any specific Learner class, because
# our custom Algorithm already defines the default Learner class to use
# through its `get_default_learner_class` method, which returns
# `VPGTorchLearner`.
# learner_class=VPGTorchLearner,
)
# Increase the number of EnvRunners (default is 1 for VPG)
# or the number of envs per EnvRunner.
.env_runners(num_env_runners=2, num_envs_per_env_runner=1)
# Plug in your own RLModule class. VPG doesn't require any specific
# RLModule APIs, so any RLModule returning `actions` or `action_dist_inputs`
# from the forward methods works ok.
# .rl_module(
# rl_module_spec=RLModuleSpec(module_class=...),
# )
)
run_rllib_example_script_experiment(base_config, args)