From 5f07987ab197a718a51cc82daa93d1f8b318dbd6 Mon Sep 17 00:00:00 2001 From: Jun Gong Date: Sun, 7 Aug 2022 17:48:09 -0700 Subject: [PATCH] [RLlib] Fix connector examples (#27583) --- rllib/BUILD | 26 +++++++++--------- .../connectors/adapt_connector_policy.py | 22 ++++++++++++--- .../connectors/run_connector_policy.py | 12 +++++--- .../self_play_with_policy_checkpoint.py | 13 +++++++-- ...=> APPO_CartPole-v0_checkpoint-6-07092022} | Bin .../APPO_CartPole-v0_checkpoint-6-08062022 | Bin 0 -> 17334 bytes rllib/utils/policy.py | 2 +- 7 files changed, 51 insertions(+), 24 deletions(-) rename rllib/tests/data/checkpoints/{APPO_CartPole-v0_checkpoint-6 => APPO_CartPole-v0_checkpoint-6-07092022} (100%) create mode 100644 rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-08062022 diff --git a/rllib/BUILD b/rllib/BUILD index 85b511567..0f14c2ccd 100644 --- a/rllib/BUILD +++ b/rllib/BUILD @@ -3343,36 +3343,36 @@ py_test( py_test( name = "examples/connectors/run_connector_policy", main = "examples/connectors/run_connector_policy.py", - tags = ["team:rllib", "exclusive", "examples", ], + tags = ["team:rllib", "exclusive", "examples", "examples_C", "examples_C_AtoT"], size = "small", srcs = ["examples/connectors/run_connector_policy.py"], - data = [ - "tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6", - ], - args = ["--checkpoint_file=tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6"] + data = glob([ + "tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6.new", + ]), + args = ["--checkpoint_file=tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-08062022"] ) py_test( name = "examples/connectors/adapt_connector_policy", main = "examples/connectors/adapt_connector_policy.py", - tags = ["team:rllib", "exclusive", "examples", ], + tags = ["team:rllib", "exclusive", "examples", "examples_C", "examples_C_AtoT"], size = "small", srcs = ["examples/connectors/adapt_connector_policy.py"], - data = [ - "tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6", - ], - args = ["--checkpoint_file=tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6"] + data = glob([ + "tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6.old", + ]), + args = ["--checkpoint_file=tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-07092022"] ) py_test( name = "examples/connectors/self_play_with_policy_checkpoint", main = "examples/connectors/self_play_with_policy_checkpoint.py", - tags = ["team:rllib", "exclusive", "examples", ], + tags = ["team:rllib", "exclusive", "examples", "examples_C", "examples_C_AtoT"], size = "small", srcs = ["examples/connectors/self_play_with_policy_checkpoint.py"], - data = [ + data = glob([ "tests/data/checkpoints/PPO_open_spiel_checkpoint-6", - ], + ]), args = [ "--checkpoint_file=tests/data/checkpoints/PPO_open_spiel_checkpoint-6", "--train_iteration=1" # Smoke test. diff --git a/rllib/examples/connectors/adapt_connector_policy.py b/rllib/examples/connectors/adapt_connector_policy.py index 2f845cbe5..9f1ed39d4 100644 --- a/rllib/examples/connectors/adapt_connector_policy.py +++ b/rllib/examples/connectors/adapt_connector_policy.py @@ -5,6 +5,7 @@ and adapt/use it with a different version of the environment. import argparse import gym import numpy as np +from pathlib import Path from typing import Dict from ray.rllib.utils.policy import ( @@ -23,9 +24,11 @@ from ray.rllib.utils.typing import ( parser = argparse.ArgumentParser() +# A policy checkpoint that works with this example script can be found at: +# rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-07092022 parser.add_argument( "--checkpoint_file", - help="Path to an RLlib checkpoint file.", + help="Path to an RLlib checkpoint file, relative to //ray/rllib/ folder.", ) parser.add_argument( "--policy_id", @@ -87,13 +90,21 @@ V1ToV2ActionConnector = register_lambda_action_connector( ) -def run(): +def run(checkpoint_path): # Restore policy. - policies = load_policies_from_checkpoint(args.checkpoint_file, [args.policy_id]) + policies = load_policies_from_checkpoint(checkpoint_path, [args.policy_id]) policy = policies[args.policy_id] # Adapt policy trained for standard CartPole to the new env. ctx: ConnectorContext = ConnectorContext.from_policy(policy) + + # When this policy was trained, it relied on FlattenDataAgentConnector + # to add a batch dimension to single observations. + # This is not necessary anymore, so we first remove the previously used + # FlattenDataAgentConnector. + policy.agent_connectors.remove("FlattenDataAgentConnector") + + # We then add the two adapter connectors. policy.agent_connectors.prepend(V2ToV1ObsAgentConnector(ctx)) policy.action_connectors.append(V1ToV2ActionConnector(ctx)) @@ -115,4 +126,7 @@ def run(): if __name__ == "__main__": - run() + checkpoint_path = str( + Path(__file__).parent.parent.parent.absolute().joinpath(args.checkpoint_file) + ) + run(checkpoint_path) diff --git a/rllib/examples/connectors/run_connector_policy.py b/rllib/examples/connectors/run_connector_policy.py index 4ab1fcd91..0576a9d1e 100644 --- a/rllib/examples/connectors/run_connector_policy.py +++ b/rllib/examples/connectors/run_connector_policy.py @@ -4,6 +4,7 @@ and use it in a serving/inference setting. import argparse import gym +from pathlib import Path from ray.rllib.utils.policy import ( load_policies_from_checkpoint, @@ -15,7 +16,7 @@ parser = argparse.ArgumentParser() # This should a checkpoint created with connectors enabled. parser.add_argument( "--checkpoint_file", - help="Path to an RLlib checkpoint file.", + help="Path to an RLlib checkpoint file, relative to //ray/rllib/ folder.", ) parser.add_argument( "--policy_id", @@ -27,9 +28,9 @@ args = parser.parse_args() assert args.checkpoint_file, "Must specify flag --checkpoint_file." -def run(): +def run(checkpoint_path): # Restore policy. - policies = load_policies_from_checkpoint(args.checkpoint_file, [args.policy_id]) + policies = load_policies_from_checkpoint(checkpoint_path, [args.policy_id]) policy = policies[args.policy_id] # Run CartPole. @@ -52,4 +53,7 @@ def run(): if __name__ == "__main__": - run() + checkpoint_path = str( + Path(__file__).parent.parent.parent.absolute().joinpath(args.checkpoint_file) + ) + run(checkpoint_path) diff --git a/rllib/examples/connectors/self_play_with_policy_checkpoint.py b/rllib/examples/connectors/self_play_with_policy_checkpoint.py index f7d7fe158..52033ad7f 100644 --- a/rllib/examples/connectors/self_play_with_policy_checkpoint.py +++ b/rllib/examples/connectors/self_play_with_policy_checkpoint.py @@ -4,6 +4,7 @@ The checkpointed policy may be trained with a different algorithm too. """ import argparse +from pathlib import Path import pyspiel import ray @@ -23,7 +24,10 @@ parser.add_argument( "--checkpoint_file", type=str, default="", - help="Path to a connector enabled checkpoint file for restoring.", + help=( + "Path to a connector enabled checkpoint file for restoring," + "relative to //ray/rllib/ folder." + ), ) parser.add_argument( "--policy_id", @@ -46,8 +50,13 @@ class AddPolicyCallback(DefaultCallbacks): super().__init__() def on_algorithm_init(self, *, algorithm, **kwargs): + checkpoint_path = str( + Path(__file__) + .parent.parent.parent.absolute() + .joinpath(args.checkpoint_file) + ) policy_config, policy_specs, policy_states = parse_policy_specs_from_checkpoint( - args.checkpoint_file + checkpoint_path ) assert args.policy_id in policy_specs, ( diff --git a/rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6 b/rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-07092022 similarity index 100% rename from rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6 rename to rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-07092022 diff --git a/rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-08062022 b/rllib/tests/data/checkpoints/APPO_CartPole-v0_checkpoint-6-08062022 new file mode 100644 index 0000000000000000000000000000000000000000..ef16d42ae5b3f5a41b99aa38e97da468a1724d40 GIT binary patch literal 17334 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 gfBgVIQV#