Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/ray/rllib/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/__init__.py +38 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/algorithm.py +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/algorithm_config.py +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__init__.py +12 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_learner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_rl_module.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_tf_policy.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_torch_policy.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/default_appo_rl_module.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo.py +435 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_learner.py +147 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_rl_module.py +11 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_tf_policy.py +393 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_torch_policy.py +413 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/default_appo_rl_module.py +56 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__init__.py +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/appo_torch_learner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/appo_torch_rl_module.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/default_appo_torch_rl_module.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/appo_torch_learner.py +234 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/appo_torch_rl_module.py +13 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/default_appo_torch_rl_module.py +10 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/appo/utils.py +174 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/callbacks.py +7 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__init__.py +18 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil_learner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil_tf_policy.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil_torch_policy.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil.py +536 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil_learner.py +51 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil_tf_policy.py +251 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil_torch_policy.py +132 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/__init__.py +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/__pycache__/marwil_torch_learner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/marwil_torch_learner.py +138 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/mock.py +154 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__init__.py +12 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/default_ppo_rl_module.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/ppo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/ppo_catalog.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/ppo_learner.cpython-312.pyc +0 -0
lib/python3.12/site-packages/ray/rllib/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (2.16 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ray.rllib.algorithms.algorithm import Algorithm
|
| 2 |
+
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
|
| 3 |
+
from ray.rllib.algorithms.appo.appo import APPO, APPOConfig
|
| 4 |
+
from ray.rllib.algorithms.bc.bc import BC, BCConfig
|
| 5 |
+
from ray.rllib.algorithms.cql.cql import CQL, CQLConfig
|
| 6 |
+
from ray.rllib.algorithms.dqn.dqn import DQN, DQNConfig
|
| 7 |
+
from ray.rllib.algorithms.impala.impala import (
|
| 8 |
+
IMPALA,
|
| 9 |
+
Impala,
|
| 10 |
+
IMPALAConfig,
|
| 11 |
+
ImpalaConfig,
|
| 12 |
+
)
|
| 13 |
+
from ray.rllib.algorithms.marwil.marwil import MARWIL, MARWILConfig
|
| 14 |
+
from ray.rllib.algorithms.ppo.ppo import PPO, PPOConfig
|
| 15 |
+
from ray.rllib.algorithms.sac.sac import SAC, SACConfig
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
"Algorithm",
|
| 19 |
+
"AlgorithmConfig",
|
| 20 |
+
"APPO",
|
| 21 |
+
"APPOConfig",
|
| 22 |
+
"BC",
|
| 23 |
+
"BCConfig",
|
| 24 |
+
"CQL",
|
| 25 |
+
"CQLConfig",
|
| 26 |
+
"DQN",
|
| 27 |
+
"DQNConfig",
|
| 28 |
+
"IMPALA",
|
| 29 |
+
"IMPALAConfig",
|
| 30 |
+
"Impala",
|
| 31 |
+
"ImpalaConfig",
|
| 32 |
+
"MARWIL",
|
| 33 |
+
"MARWILConfig",
|
| 34 |
+
"PPO",
|
| 35 |
+
"PPOConfig",
|
| 36 |
+
"SAC",
|
| 37 |
+
"SACConfig",
|
| 38 |
+
]
|
lib/python3.12/site-packages/ray/rllib/algorithms/algorithm.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/algorithm_config.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ray.rllib.algorithms.appo.appo import APPO, APPOConfig
|
| 2 |
+
from ray.rllib.algorithms.appo.appo_tf_policy import APPOTF1Policy, APPOTF2Policy
|
| 3 |
+
from ray.rllib.algorithms.appo.appo_torch_policy import APPOTorchPolicy
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
"APPO",
|
| 7 |
+
"APPOConfig",
|
| 8 |
+
# @OldAPIStack
|
| 9 |
+
"APPOTF1Policy",
|
| 10 |
+
"APPOTF2Policy",
|
| 11 |
+
"APPOTorchPolicy",
|
| 12 |
+
]
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (525 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo.cpython-312.pyc
ADDED
|
Binary file (17.2 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_learner.cpython-312.pyc
ADDED
|
Binary file (7.36 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_rl_module.cpython-312.pyc
ADDED
|
Binary file (575 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_tf_policy.cpython-312.pyc
ADDED
|
Binary file (15.5 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/appo_torch_policy.cpython-312.pyc
ADDED
|
Binary file (17.3 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/default_appo_rl_module.cpython-312.pyc
ADDED
|
Binary file (3.35 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (8.12 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Asynchronous Proximal Policy Optimization (APPO)
|
| 2 |
+
|
| 3 |
+
The algorithm is described in [1] (under the name of "IMPACT"):
|
| 4 |
+
|
| 5 |
+
Detailed documentation:
|
| 6 |
+
https://docs.ray.io/en/master/rllib-algorithms.html#appo
|
| 7 |
+
|
| 8 |
+
[1] IMPACT: Importance Weighted Asynchronous Architectures with Clipped Target Networks.
|
| 9 |
+
Luo et al. 2020
|
| 10 |
+
https://arxiv.org/pdf/1912.00167
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import logging
|
| 14 |
+
from typing import Optional, Type
|
| 15 |
+
|
| 16 |
+
from typing_extensions import Self
|
| 17 |
+
|
| 18 |
+
from ray._common.deprecation import DEPRECATED_VALUE, deprecation_warning
|
| 19 |
+
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig, NotProvided
|
| 20 |
+
from ray.rllib.algorithms.impala.impala import IMPALA, IMPALAConfig
|
| 21 |
+
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
|
| 22 |
+
from ray.rllib.policy.policy import Policy
|
| 23 |
+
from ray.rllib.utils.annotations import override
|
| 24 |
+
from ray.rllib.utils.metrics import (
|
| 25 |
+
LAST_TARGET_UPDATE_TS,
|
| 26 |
+
LEARNER_STATS_KEY,
|
| 27 |
+
NUM_AGENT_STEPS_SAMPLED,
|
| 28 |
+
NUM_ENV_STEPS_SAMPLED,
|
| 29 |
+
NUM_TARGET_UPDATES,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
logger = logging.getLogger(__name__)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
LEARNER_RESULTS_KL_KEY = "mean_kl_loss"
|
| 36 |
+
LEARNER_RESULTS_CURR_KL_COEFF_KEY = "curr_kl_coeff"
|
| 37 |
+
OLD_ACTION_DIST_KEY = "old_action_dist"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class APPOConfig(IMPALAConfig):
|
| 41 |
+
"""Defines a configuration class from which an APPO Algorithm can be built.
|
| 42 |
+
|
| 43 |
+
.. testcode::
|
| 44 |
+
|
| 45 |
+
from ray.rllib.algorithms.appo import APPOConfig
|
| 46 |
+
config = (
|
| 47 |
+
APPOConfig()
|
| 48 |
+
.training(lr=0.01, grad_clip=30.0, train_batch_size_per_learner=50)
|
| 49 |
+
)
|
| 50 |
+
config = config.learners(num_learners=1)
|
| 51 |
+
config = config.env_runners(num_env_runners=1)
|
| 52 |
+
config = config.environment("CartPole-v1")
|
| 53 |
+
|
| 54 |
+
# Build an Algorithm object from the config and run 1 training iteration.
|
| 55 |
+
algo = config.build()
|
| 56 |
+
algo.train()
|
| 57 |
+
del algo
|
| 58 |
+
|
| 59 |
+
.. testcode::
|
| 60 |
+
|
| 61 |
+
from ray.rllib.algorithms.appo import APPOConfig
|
| 62 |
+
from ray import tune
|
| 63 |
+
|
| 64 |
+
config = APPOConfig()
|
| 65 |
+
# Update the config object.
|
| 66 |
+
config = config.training(lr=tune.grid_search([0.001,]))
|
| 67 |
+
# Set the config object's env.
|
| 68 |
+
config = config.environment(env="CartPole-v1")
|
| 69 |
+
# Use to_dict() to get the old-style python config dict when running with tune.
|
| 70 |
+
tune.Tuner(
|
| 71 |
+
"APPO",
|
| 72 |
+
run_config=tune.RunConfig(
|
| 73 |
+
stop={"training_iteration": 1},
|
| 74 |
+
verbose=0,
|
| 75 |
+
),
|
| 76 |
+
param_space=config.to_dict(),
|
| 77 |
+
|
| 78 |
+
).fit()
|
| 79 |
+
|
| 80 |
+
.. testoutput::
|
| 81 |
+
:hide:
|
| 82 |
+
|
| 83 |
+
...
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def __init__(self, algo_class=None):
|
| 87 |
+
"""Initializes a APPOConfig instance."""
|
| 88 |
+
self.exploration_config = {
|
| 89 |
+
# The Exploration class to use. In the simplest case, this is the name
|
| 90 |
+
# (str) of any class present in the `rllib.utils.exploration` package.
|
| 91 |
+
# You can also provide the python class directly or the full location
|
| 92 |
+
# of your class (e.g. "ray.rllib.utils.exploration.epsilon_greedy.
|
| 93 |
+
# EpsilonGreedy").
|
| 94 |
+
"type": "StochasticSampling",
|
| 95 |
+
# Add constructor kwargs here (if any).
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
super().__init__(algo_class=algo_class or APPO)
|
| 99 |
+
|
| 100 |
+
# fmt: off
|
| 101 |
+
# __sphinx_doc_begin__
|
| 102 |
+
# APPO specific settings:
|
| 103 |
+
self.vtrace = True
|
| 104 |
+
self.use_gae = True
|
| 105 |
+
self.lambda_ = 1.0
|
| 106 |
+
self.clip_param = 0.4
|
| 107 |
+
self.use_kl_loss = False
|
| 108 |
+
self.kl_coeff = 1.0
|
| 109 |
+
self.kl_target = 0.01
|
| 110 |
+
self.target_worker_clipping = 2.0
|
| 111 |
+
|
| 112 |
+
# Circular replay buffer settings.
|
| 113 |
+
# Used in [1] for discrete action tasks:
|
| 114 |
+
# `circular_buffer_num_batches=4` and `circular_buffer_iterations_per_batch=2`
|
| 115 |
+
# For cont. action tasks:
|
| 116 |
+
# `circular_buffer_num_batches=16` and `circular_buffer_iterations_per_batch=20`
|
| 117 |
+
self.circular_buffer_num_batches = 4
|
| 118 |
+
self.circular_buffer_iterations_per_batch = 2
|
| 119 |
+
|
| 120 |
+
# Override some of IMPALAConfig's default values with APPO-specific values.
|
| 121 |
+
self.num_env_runners = 2
|
| 122 |
+
self.target_network_update_freq = 2
|
| 123 |
+
self.broadcast_interval = 1
|
| 124 |
+
self.grad_clip = 40.0
|
| 125 |
+
# Note: Only when using enable_rl_module_and_learner=True can the clipping mode
|
| 126 |
+
# be configured by the user. On the old API stack, RLlib will always clip by
|
| 127 |
+
# global_norm, no matter the value of `grad_clip_by`.
|
| 128 |
+
self.grad_clip_by = "global_norm"
|
| 129 |
+
|
| 130 |
+
self.opt_type = "adam"
|
| 131 |
+
self.lr = 0.0005
|
| 132 |
+
self.decay = 0.99
|
| 133 |
+
self.momentum = 0.0
|
| 134 |
+
self.epsilon = 0.1
|
| 135 |
+
self.vf_loss_coeff = 0.5
|
| 136 |
+
self.entropy_coeff = 0.01
|
| 137 |
+
self.tau = 1.0
|
| 138 |
+
# __sphinx_doc_end__
|
| 139 |
+
# fmt: on
|
| 140 |
+
|
| 141 |
+
self.lr_schedule = None # @OldAPIStack
|
| 142 |
+
self.entropy_coeff_schedule = None # @OldAPIStack
|
| 143 |
+
self.num_gpus = 0 # @OldAPIStack
|
| 144 |
+
self.num_multi_gpu_tower_stacks = 1 # @OldAPIStack
|
| 145 |
+
self.minibatch_buffer_size = 1 # @OldAPIStack
|
| 146 |
+
self.replay_proportion = 0.0 # @OldAPIStack
|
| 147 |
+
self.replay_buffer_num_slots = 100 # @OldAPIStack
|
| 148 |
+
self.learner_queue_size = 16 # @OldAPIStack
|
| 149 |
+
self.learner_queue_timeout = 300 # @OldAPIStack
|
| 150 |
+
|
| 151 |
+
# Deprecated keys.
|
| 152 |
+
self.target_update_frequency = DEPRECATED_VALUE
|
| 153 |
+
self.use_critic = DEPRECATED_VALUE
|
| 154 |
+
|
| 155 |
+
@override(IMPALAConfig)
|
| 156 |
+
def training(
|
| 157 |
+
self,
|
| 158 |
+
*,
|
| 159 |
+
vtrace: Optional[bool] = NotProvided,
|
| 160 |
+
use_gae: Optional[bool] = NotProvided,
|
| 161 |
+
lambda_: Optional[float] = NotProvided,
|
| 162 |
+
clip_param: Optional[float] = NotProvided,
|
| 163 |
+
use_kl_loss: Optional[bool] = NotProvided,
|
| 164 |
+
kl_coeff: Optional[float] = NotProvided,
|
| 165 |
+
kl_target: Optional[float] = NotProvided,
|
| 166 |
+
target_network_update_freq: Optional[int] = NotProvided,
|
| 167 |
+
tau: Optional[float] = NotProvided,
|
| 168 |
+
target_worker_clipping: Optional[float] = NotProvided,
|
| 169 |
+
circular_buffer_num_batches: Optional[int] = NotProvided,
|
| 170 |
+
circular_buffer_iterations_per_batch: Optional[int] = NotProvided,
|
| 171 |
+
# Deprecated keys.
|
| 172 |
+
target_update_frequency=DEPRECATED_VALUE,
|
| 173 |
+
use_critic=DEPRECATED_VALUE,
|
| 174 |
+
**kwargs,
|
| 175 |
+
) -> Self:
|
| 176 |
+
"""Sets the training related configuration.
|
| 177 |
+
|
| 178 |
+
Args:
|
| 179 |
+
vtrace: Whether to use V-trace weighted advantages. If false, PPO GAE
|
| 180 |
+
advantages will be used instead.
|
| 181 |
+
use_gae: If true, use the Generalized Advantage Estimator (GAE)
|
| 182 |
+
with a value function, see https://arxiv.org/pdf/1506.02438.pdf.
|
| 183 |
+
Only applies if vtrace=False.
|
| 184 |
+
lambda_: GAE (lambda) parameter.
|
| 185 |
+
clip_param: PPO surrogate slipping parameter.
|
| 186 |
+
use_kl_loss: Whether to use the KL-term in the loss function.
|
| 187 |
+
kl_coeff: Coefficient for weighting the KL-loss term.
|
| 188 |
+
kl_target: Target term for the KL-term to reach (via adjusting the
|
| 189 |
+
`kl_coeff` automatically).
|
| 190 |
+
target_network_update_freq: NOTE: This parameter is only applicable on
|
| 191 |
+
the new API stack. The frequency with which to update the target
|
| 192 |
+
policy network from the main trained policy network. The metric
|
| 193 |
+
used is `NUM_ENV_STEPS_TRAINED_LIFETIME` and the unit is `n` (see [1]
|
| 194 |
+
4.1.1), where: `n = [circular_buffer_num_batches (N)] *
|
| 195 |
+
[circular_buffer_iterations_per_batch (K)] * [train batch size]`
|
| 196 |
+
For example, if you set `target_network_update_freq=2`, and N=4, K=2,
|
| 197 |
+
and `train_batch_size_per_learner=500`, then the target net is updated
|
| 198 |
+
every 2*4*2*500=8000 trained env steps (every 16 batch updates on each
|
| 199 |
+
learner).
|
| 200 |
+
The authors in [1] suggests that this setting is robust to a range of
|
| 201 |
+
choices (try values between 0.125 and 4).
|
| 202 |
+
target_network_update_freq: The frequency to update the target policy and
|
| 203 |
+
tune the kl loss coefficients that are used during training. After
|
| 204 |
+
setting this parameter, the algorithm waits for at least
|
| 205 |
+
`target_network_update_freq` number of environment samples to be trained
|
| 206 |
+
on before updating the target networks and tune the kl loss
|
| 207 |
+
coefficients. NOTE: This parameter is only applicable when using the
|
| 208 |
+
Learner API (enable_rl_module_and_learner=True).
|
| 209 |
+
tau: The factor by which to update the target policy network towards
|
| 210 |
+
the current policy network. Can range between 0 and 1.
|
| 211 |
+
e.g. updated_param = tau * current_param + (1 - tau) * target_param
|
| 212 |
+
target_worker_clipping: The maximum value for the target-worker-clipping
|
| 213 |
+
used for computing the IS ratio, described in [1]
|
| 214 |
+
IS = min(π(i) / π(target), ρ) * (π / π(i))
|
| 215 |
+
circular_buffer_num_batches: The number of train batches that fit
|
| 216 |
+
into the circular buffer. Each such train batch can be sampled for
|
| 217 |
+
training max. `circular_buffer_iterations_per_batch` times.
|
| 218 |
+
circular_buffer_iterations_per_batch: The number of times any train
|
| 219 |
+
batch in the circular buffer can be sampled for training. A batch gets
|
| 220 |
+
evicted from the buffer either if it's the oldest batch in the buffer
|
| 221 |
+
and a new batch is added OR if the batch reaches this max. number of
|
| 222 |
+
being sampled.
|
| 223 |
+
|
| 224 |
+
Returns:
|
| 225 |
+
This updated AlgorithmConfig object.
|
| 226 |
+
"""
|
| 227 |
+
if target_update_frequency != DEPRECATED_VALUE:
|
| 228 |
+
deprecation_warning(
|
| 229 |
+
old="target_update_frequency",
|
| 230 |
+
new="target_network_update_freq",
|
| 231 |
+
error=True,
|
| 232 |
+
)
|
| 233 |
+
if use_critic != DEPRECATED_VALUE:
|
| 234 |
+
deprecation_warning(
|
| 235 |
+
old="use_critic",
|
| 236 |
+
help="`use_critic` no longer supported! APPO always uses a value "
|
| 237 |
+
"function (critic).",
|
| 238 |
+
error=True,
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
# Pass kwargs onto super's `training()` method.
|
| 242 |
+
super().training(**kwargs)
|
| 243 |
+
|
| 244 |
+
if vtrace is not NotProvided:
|
| 245 |
+
self.vtrace = vtrace
|
| 246 |
+
if use_gae is not NotProvided:
|
| 247 |
+
self.use_gae = use_gae
|
| 248 |
+
if lambda_ is not NotProvided:
|
| 249 |
+
self.lambda_ = lambda_
|
| 250 |
+
if clip_param is not NotProvided:
|
| 251 |
+
self.clip_param = clip_param
|
| 252 |
+
if use_kl_loss is not NotProvided:
|
| 253 |
+
self.use_kl_loss = use_kl_loss
|
| 254 |
+
if kl_coeff is not NotProvided:
|
| 255 |
+
self.kl_coeff = kl_coeff
|
| 256 |
+
if kl_target is not NotProvided:
|
| 257 |
+
self.kl_target = kl_target
|
| 258 |
+
if target_network_update_freq is not NotProvided:
|
| 259 |
+
self.target_network_update_freq = target_network_update_freq
|
| 260 |
+
if tau is not NotProvided:
|
| 261 |
+
self.tau = tau
|
| 262 |
+
if target_worker_clipping is not NotProvided:
|
| 263 |
+
self.target_worker_clipping = target_worker_clipping
|
| 264 |
+
if circular_buffer_num_batches is not NotProvided:
|
| 265 |
+
self.circular_buffer_num_batches = circular_buffer_num_batches
|
| 266 |
+
if circular_buffer_iterations_per_batch is not NotProvided:
|
| 267 |
+
self.circular_buffer_iterations_per_batch = (
|
| 268 |
+
circular_buffer_iterations_per_batch
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
return self
|
| 272 |
+
|
| 273 |
+
@override(IMPALAConfig)
|
| 274 |
+
def validate(self) -> None:
|
| 275 |
+
super().validate()
|
| 276 |
+
|
| 277 |
+
# On new API stack, circular buffer should be used, not `minibatch_buffer_size`.
|
| 278 |
+
if self.enable_rl_module_and_learner:
|
| 279 |
+
if self.minibatch_buffer_size != 1 or self.replay_proportion != 0.0:
|
| 280 |
+
self._value_error(
|
| 281 |
+
"`minibatch_buffer_size/replay_proportion` not valid on new API "
|
| 282 |
+
"stack with APPO! "
|
| 283 |
+
"Use `circular_buffer_num_batches` for the number of train batches "
|
| 284 |
+
"in the circular buffer. To change the maximum number of times "
|
| 285 |
+
"any batch may be sampled, set "
|
| 286 |
+
"`circular_buffer_iterations_per_batch`."
|
| 287 |
+
)
|
| 288 |
+
if self.num_multi_gpu_tower_stacks != 1:
|
| 289 |
+
self._value_error(
|
| 290 |
+
"`num_multi_gpu_tower_stacks` not supported on new API stack with "
|
| 291 |
+
"APPO! In order to train on multi-GPU, use "
|
| 292 |
+
"`config.learners(num_learners=[number of GPUs], "
|
| 293 |
+
"num_gpus_per_learner=1)`. To scale the throughput of batch-to-GPU-"
|
| 294 |
+
"pre-loading on each of your `Learners`, set "
|
| 295 |
+
"`num_gpu_loader_threads` to a higher number (recommended values: "
|
| 296 |
+
"1-8)."
|
| 297 |
+
)
|
| 298 |
+
if self.learner_queue_size != 16:
|
| 299 |
+
self._value_error(
|
| 300 |
+
"`learner_queue_size` not supported on new API stack with "
|
| 301 |
+
"APPO! In order set the size of the circular buffer (which acts as "
|
| 302 |
+
"a 'learner queue'), use "
|
| 303 |
+
"`config.training(circular_buffer_num_batches=..)`. To change the "
|
| 304 |
+
"maximum number of times any batch may be sampled, set "
|
| 305 |
+
"`config.training(circular_buffer_iterations_per_batch=..)`."
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
@override(IMPALAConfig)
|
| 309 |
+
def get_default_learner_class(self):
|
| 310 |
+
if self.framework_str == "torch":
|
| 311 |
+
from ray.rllib.algorithms.appo.torch.appo_torch_learner import (
|
| 312 |
+
APPOTorchLearner,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
return APPOTorchLearner
|
| 316 |
+
elif self.framework_str in ["tf2", "tf"]:
|
| 317 |
+
raise ValueError(
|
| 318 |
+
"TensorFlow is no longer supported on the new API stack! "
|
| 319 |
+
"Use `framework='torch'`."
|
| 320 |
+
)
|
| 321 |
+
else:
|
| 322 |
+
raise ValueError(
|
| 323 |
+
f"The framework {self.framework_str} is not supported. "
|
| 324 |
+
"Use `framework='torch'`."
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
@override(IMPALAConfig)
|
| 328 |
+
def get_default_rl_module_spec(self) -> RLModuleSpec:
|
| 329 |
+
if self.framework_str == "torch":
|
| 330 |
+
from ray.rllib.algorithms.appo.torch.appo_torch_rl_module import (
|
| 331 |
+
APPOTorchRLModule as RLModule,
|
| 332 |
+
)
|
| 333 |
+
else:
|
| 334 |
+
raise ValueError(
|
| 335 |
+
f"The framework {self.framework_str} is not supported. "
|
| 336 |
+
"Use either 'torch' or 'tf2'."
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
return RLModuleSpec(module_class=RLModule)
|
| 340 |
+
|
| 341 |
+
@property
|
| 342 |
+
@override(AlgorithmConfig)
|
| 343 |
+
def _model_config_auto_includes(self):
|
| 344 |
+
return super()._model_config_auto_includes | {"vf_share_layers": False}
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
class APPO(IMPALA):
|
| 348 |
+
def __init__(self, config, *args, **kwargs):
|
| 349 |
+
"""Initializes an APPO instance."""
|
| 350 |
+
super().__init__(config, *args, **kwargs)
|
| 351 |
+
|
| 352 |
+
# After init: Initialize target net.
|
| 353 |
+
|
| 354 |
+
# TODO(avnishn): Does this need to happen in __init__? I think we can move it
|
| 355 |
+
# to setup()
|
| 356 |
+
if not self.config.enable_rl_module_and_learner:
|
| 357 |
+
self.env_runner.foreach_policy_to_train(lambda p, _: p.update_target())
|
| 358 |
+
|
| 359 |
+
@override(IMPALA)
|
| 360 |
+
def training_step(self) -> None:
|
| 361 |
+
if self.config.enable_rl_module_and_learner:
|
| 362 |
+
return super().training_step()
|
| 363 |
+
|
| 364 |
+
train_results = super().training_step()
|
| 365 |
+
# Update the target network and the KL coefficient for the APPO-loss.
|
| 366 |
+
# The target network update frequency is calculated automatically by the product
|
| 367 |
+
# of `num_epochs` setting (usually 1 for APPO) and `minibatch_buffer_size`.
|
| 368 |
+
last_update = self._counters[LAST_TARGET_UPDATE_TS]
|
| 369 |
+
cur_ts = self._counters[
|
| 370 |
+
(
|
| 371 |
+
NUM_AGENT_STEPS_SAMPLED
|
| 372 |
+
if self.config.count_steps_by == "agent_steps"
|
| 373 |
+
else NUM_ENV_STEPS_SAMPLED
|
| 374 |
+
)
|
| 375 |
+
]
|
| 376 |
+
target_update_freq = self.config.num_epochs * self.config.minibatch_buffer_size
|
| 377 |
+
if cur_ts - last_update > target_update_freq:
|
| 378 |
+
self._counters[NUM_TARGET_UPDATES] += 1
|
| 379 |
+
self._counters[LAST_TARGET_UPDATE_TS] = cur_ts
|
| 380 |
+
|
| 381 |
+
# Update our target network.
|
| 382 |
+
self.env_runner.foreach_policy_to_train(lambda p, _: p.update_target())
|
| 383 |
+
|
| 384 |
+
# Also update the KL-coefficient for the APPO loss, if necessary.
|
| 385 |
+
if self.config.use_kl_loss:
|
| 386 |
+
|
| 387 |
+
def update(pi, pi_id):
|
| 388 |
+
assert LEARNER_STATS_KEY not in train_results, (
|
| 389 |
+
"{} should be nested under policy id key".format(
|
| 390 |
+
LEARNER_STATS_KEY
|
| 391 |
+
),
|
| 392 |
+
train_results,
|
| 393 |
+
)
|
| 394 |
+
if pi_id in train_results:
|
| 395 |
+
kl = train_results[pi_id][LEARNER_STATS_KEY].get("kl")
|
| 396 |
+
assert kl is not None, (train_results, pi_id)
|
| 397 |
+
# Make the actual `Policy.update_kl()` call.
|
| 398 |
+
pi.update_kl(kl)
|
| 399 |
+
else:
|
| 400 |
+
logger.warning("No data for {}, not updating kl".format(pi_id))
|
| 401 |
+
|
| 402 |
+
# Update KL on all trainable policies within the local (trainer)
|
| 403 |
+
# Worker.
|
| 404 |
+
self.env_runner.foreach_policy_to_train(update)
|
| 405 |
+
|
| 406 |
+
return train_results
|
| 407 |
+
|
| 408 |
+
@classmethod
|
| 409 |
+
@override(IMPALA)
|
| 410 |
+
def get_default_config(cls) -> APPOConfig:
|
| 411 |
+
return APPOConfig()
|
| 412 |
+
|
| 413 |
+
@classmethod
|
| 414 |
+
@override(IMPALA)
|
| 415 |
+
def get_default_policy_class(
|
| 416 |
+
cls, config: AlgorithmConfig
|
| 417 |
+
) -> Optional[Type[Policy]]:
|
| 418 |
+
if config["framework"] == "torch":
|
| 419 |
+
from ray.rllib.algorithms.appo.appo_torch_policy import APPOTorchPolicy
|
| 420 |
+
|
| 421 |
+
return APPOTorchPolicy
|
| 422 |
+
elif config["framework"] == "tf":
|
| 423 |
+
if config.enable_rl_module_and_learner:
|
| 424 |
+
raise ValueError(
|
| 425 |
+
"RLlib's RLModule and Learner API is not supported for"
|
| 426 |
+
" tf1. Use "
|
| 427 |
+
"framework='tf2' instead."
|
| 428 |
+
)
|
| 429 |
+
from ray.rllib.algorithms.appo.appo_tf_policy import APPOTF1Policy
|
| 430 |
+
|
| 431 |
+
return APPOTF1Policy
|
| 432 |
+
else:
|
| 433 |
+
from ray.rllib.algorithms.appo.appo_tf_policy import APPOTF2Policy
|
| 434 |
+
|
| 435 |
+
return APPOTF2Policy
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_learner.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import abc
|
| 2 |
+
from typing import Any, Dict, Optional
|
| 3 |
+
|
| 4 |
+
from ray.rllib.algorithms.appo.appo import APPOConfig
|
| 5 |
+
from ray.rllib.algorithms.appo.utils import CircularBuffer
|
| 6 |
+
from ray.rllib.algorithms.impala.impala_learner import IMPALALearner
|
| 7 |
+
from ray.rllib.core.learner.learner import Learner
|
| 8 |
+
from ray.rllib.core.learner.utils import update_target_network
|
| 9 |
+
from ray.rllib.core.rl_module.apis import TargetNetworkAPI, ValueFunctionAPI
|
| 10 |
+
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
|
| 11 |
+
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
|
| 12 |
+
from ray.rllib.utils.annotations import override
|
| 13 |
+
from ray.rllib.utils.lambda_defaultdict import LambdaDefaultDict
|
| 14 |
+
from ray.rllib.utils.metrics import (
|
| 15 |
+
LAST_TARGET_UPDATE_TS,
|
| 16 |
+
NUM_ENV_STEPS_TRAINED_LIFETIME,
|
| 17 |
+
NUM_MODULE_STEPS_TRAINED,
|
| 18 |
+
NUM_TARGET_UPDATES,
|
| 19 |
+
)
|
| 20 |
+
from ray.rllib.utils.schedules.scheduler import Scheduler
|
| 21 |
+
from ray.rllib.utils.typing import ModuleID, ShouldModuleBeUpdatedFn
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class APPOLearner(IMPALALearner):
|
| 25 |
+
"""Adds KL coeff updates via `after_gradient_based_update()` to IMPALA logic.
|
| 26 |
+
|
| 27 |
+
Framework-specific subclasses must override `_update_module_kl_coeff()`.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
@override(IMPALALearner)
|
| 31 |
+
def build(self):
|
| 32 |
+
self._learner_thread_in_queue = CircularBuffer(
|
| 33 |
+
num_batches=self.config.circular_buffer_num_batches,
|
| 34 |
+
iterations_per_batch=self.config.circular_buffer_iterations_per_batch,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
super().build()
|
| 38 |
+
|
| 39 |
+
# Make target networks.
|
| 40 |
+
self.module.foreach_module(
|
| 41 |
+
lambda mid, mod: (
|
| 42 |
+
mod.make_target_networks()
|
| 43 |
+
if isinstance(mod, TargetNetworkAPI)
|
| 44 |
+
else None
|
| 45 |
+
)
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
# The current kl coefficients per module as (framework specific) tensor
|
| 49 |
+
# variables.
|
| 50 |
+
self.curr_kl_coeffs_per_module: LambdaDefaultDict[
|
| 51 |
+
ModuleID, Scheduler
|
| 52 |
+
] = LambdaDefaultDict(
|
| 53 |
+
lambda module_id: self._get_tensor_variable(
|
| 54 |
+
self.config.get_config_for_module(module_id).kl_coeff
|
| 55 |
+
)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
@override(Learner)
|
| 59 |
+
def add_module(
|
| 60 |
+
self,
|
| 61 |
+
*,
|
| 62 |
+
module_id: ModuleID,
|
| 63 |
+
module_spec: RLModuleSpec,
|
| 64 |
+
config_overrides: Optional[Dict] = None,
|
| 65 |
+
new_should_module_be_updated: Optional[ShouldModuleBeUpdatedFn] = None,
|
| 66 |
+
) -> MultiRLModuleSpec:
|
| 67 |
+
marl_spec = super().add_module(
|
| 68 |
+
module_id=module_id,
|
| 69 |
+
module_spec=module_spec,
|
| 70 |
+
config_overrides=config_overrides,
|
| 71 |
+
new_should_module_be_updated=new_should_module_be_updated,
|
| 72 |
+
)
|
| 73 |
+
# Create target networks for added Module, if applicable.
|
| 74 |
+
if isinstance(self.module[module_id].unwrapped(), TargetNetworkAPI):
|
| 75 |
+
self.module[module_id].unwrapped().make_target_networks()
|
| 76 |
+
return marl_spec
|
| 77 |
+
|
| 78 |
+
@override(IMPALALearner)
|
| 79 |
+
def remove_module(self, module_id: str) -> MultiRLModuleSpec:
|
| 80 |
+
marl_spec = super().remove_module(module_id)
|
| 81 |
+
self.curr_kl_coeffs_per_module.pop(module_id)
|
| 82 |
+
return marl_spec
|
| 83 |
+
|
| 84 |
+
@override(Learner)
|
| 85 |
+
def after_gradient_based_update(self, *, timesteps: Dict[str, Any]) -> None:
|
| 86 |
+
"""Updates the target Q Networks."""
|
| 87 |
+
super().after_gradient_based_update(timesteps=timesteps)
|
| 88 |
+
|
| 89 |
+
# TODO (sven): Maybe we should have a `after_gradient_based_update`
|
| 90 |
+
# method per module?
|
| 91 |
+
curr_timestep = timesteps.get(NUM_ENV_STEPS_TRAINED_LIFETIME, 0)
|
| 92 |
+
for module_id, module in self.module._rl_modules.items():
|
| 93 |
+
config = self.config.get_config_for_module(module_id)
|
| 94 |
+
|
| 95 |
+
last_update_ts_key = (module_id, LAST_TARGET_UPDATE_TS)
|
| 96 |
+
if isinstance(module.unwrapped(), TargetNetworkAPI) and (
|
| 97 |
+
curr_timestep - self.metrics.peek(last_update_ts_key, default=0)
|
| 98 |
+
>= (
|
| 99 |
+
config.target_network_update_freq
|
| 100 |
+
* config.circular_buffer_num_batches
|
| 101 |
+
* config.circular_buffer_iterations_per_batch
|
| 102 |
+
* config.train_batch_size_per_learner
|
| 103 |
+
)
|
| 104 |
+
):
|
| 105 |
+
for (
|
| 106 |
+
main_net,
|
| 107 |
+
target_net,
|
| 108 |
+
) in module.unwrapped().get_target_network_pairs():
|
| 109 |
+
update_target_network(
|
| 110 |
+
main_net=main_net,
|
| 111 |
+
target_net=target_net,
|
| 112 |
+
tau=config.tau,
|
| 113 |
+
)
|
| 114 |
+
# Increase lifetime target network update counter by one.
|
| 115 |
+
self.metrics.log_value((module_id, NUM_TARGET_UPDATES), 1, reduce="sum")
|
| 116 |
+
# Update the (single-value -> window=1) last updated timestep metric.
|
| 117 |
+
self.metrics.log_value(last_update_ts_key, curr_timestep, window=1)
|
| 118 |
+
|
| 119 |
+
if (
|
| 120 |
+
config.use_kl_loss
|
| 121 |
+
and self.metrics.peek((module_id, NUM_MODULE_STEPS_TRAINED), default=0)
|
| 122 |
+
> 0
|
| 123 |
+
):
|
| 124 |
+
self._update_module_kl_coeff(module_id=module_id, config=config)
|
| 125 |
+
|
| 126 |
+
@classmethod
|
| 127 |
+
@override(Learner)
|
| 128 |
+
def rl_module_required_apis(cls) -> list[type]:
|
| 129 |
+
# In order for a PPOLearner to update an RLModule, it must implement the
|
| 130 |
+
# following APIs:
|
| 131 |
+
return [TargetNetworkAPI, ValueFunctionAPI]
|
| 132 |
+
|
| 133 |
+
@abc.abstractmethod
|
| 134 |
+
def _update_module_kl_coeff(self, module_id: ModuleID, config: APPOConfig) -> None:
|
| 135 |
+
"""Dynamically update the KL loss coefficients of each module.
|
| 136 |
+
|
| 137 |
+
The update is completed using the mean KL divergence between the action
|
| 138 |
+
distributions current policy and old policy of each module. That action
|
| 139 |
+
distribution is computed during the most recent update/call to `compute_loss`.
|
| 140 |
+
|
| 141 |
+
Args:
|
| 142 |
+
module_id: The module whose KL loss coefficient to update.
|
| 143 |
+
config: The AlgorithmConfig specific to the given `module_id`.
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
AppoLearner = APPOLearner
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_rl_module.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Backward compat import.
|
| 2 |
+
from ray.rllib.algorithms.appo.default_appo_rl_module import ( # noqa
|
| 3 |
+
DefaultAPPORLModule as APPORLModule,
|
| 4 |
+
)
|
| 5 |
+
from ray._common.deprecation import deprecation_warning
|
| 6 |
+
|
| 7 |
+
deprecation_warning(
|
| 8 |
+
old="ray.rllib.algorithms.appo.appo_rl_module.APPORLModule",
|
| 9 |
+
new="ray.rllib.algorithms.appo.default_appo_rl_module.DefaultAPPORLModule",
|
| 10 |
+
error=False,
|
| 11 |
+
)
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_tf_policy.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
TensorFlow policy class used for APPO.
|
| 3 |
+
|
| 4 |
+
Adapted from VTraceTFPolicy to use the PPO surrogate loss.
|
| 5 |
+
Keep in sync with changes to VTraceTFPolicy.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import logging
|
| 9 |
+
from typing import Dict, List, Optional, Type, Union
|
| 10 |
+
|
| 11 |
+
import gymnasium as gym
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
from ray.rllib.algorithms.appo.utils import make_appo_models
|
| 15 |
+
from ray.rllib.algorithms.impala import vtrace_tf as vtrace
|
| 16 |
+
from ray.rllib.algorithms.impala.impala_tf_policy import (
|
| 17 |
+
VTraceClipGradients,
|
| 18 |
+
VTraceOptimizer,
|
| 19 |
+
_make_time_major,
|
| 20 |
+
)
|
| 21 |
+
from ray.rllib.evaluation.postprocessing import (
|
| 22 |
+
Postprocessing,
|
| 23 |
+
compute_bootstrap_value,
|
| 24 |
+
compute_gae_for_sample_batch,
|
| 25 |
+
)
|
| 26 |
+
from ray.rllib.models.modelv2 import ModelV2
|
| 27 |
+
from ray.rllib.models.tf.tf_action_dist import Categorical, TFActionDistribution
|
| 28 |
+
from ray.rllib.policy.dynamic_tf_policy_v2 import DynamicTFPolicyV2
|
| 29 |
+
from ray.rllib.policy.eager_tf_policy_v2 import EagerTFPolicyV2
|
| 30 |
+
from ray.rllib.policy.sample_batch import SampleBatch
|
| 31 |
+
from ray.rllib.policy.tf_mixins import (
|
| 32 |
+
EntropyCoeffSchedule,
|
| 33 |
+
GradStatsMixin,
|
| 34 |
+
KLCoeffMixin,
|
| 35 |
+
LearningRateSchedule,
|
| 36 |
+
TargetNetworkMixin,
|
| 37 |
+
ValueNetworkMixin,
|
| 38 |
+
)
|
| 39 |
+
from ray.rllib.utils.annotations import (
|
| 40 |
+
override,
|
| 41 |
+
)
|
| 42 |
+
from ray.rllib.utils.framework import try_import_tf
|
| 43 |
+
from ray.rllib.utils.tf_utils import explained_variance
|
| 44 |
+
from ray.rllib.utils.typing import TensorType
|
| 45 |
+
|
| 46 |
+
tf1, tf, tfv = try_import_tf()
|
| 47 |
+
|
| 48 |
+
logger = logging.getLogger(__name__)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# TODO (sven): Deprecate once APPO and IMPALA fully on RLModules/Learner APIs.
|
| 52 |
+
def get_appo_tf_policy(name: str, base: type) -> type:
|
| 53 |
+
"""Construct an APPOTFPolicy inheriting either dynamic or eager base policies.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
base: Base class for this policy. DynamicTFPolicyV2 or EagerTFPolicyV2.
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
A TF Policy to be used with Impala.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
class APPOTFPolicy(
|
| 63 |
+
VTraceClipGradients,
|
| 64 |
+
VTraceOptimizer,
|
| 65 |
+
LearningRateSchedule,
|
| 66 |
+
KLCoeffMixin,
|
| 67 |
+
EntropyCoeffSchedule,
|
| 68 |
+
ValueNetworkMixin,
|
| 69 |
+
TargetNetworkMixin,
|
| 70 |
+
GradStatsMixin,
|
| 71 |
+
base,
|
| 72 |
+
):
|
| 73 |
+
def __init__(
|
| 74 |
+
self,
|
| 75 |
+
observation_space,
|
| 76 |
+
action_space,
|
| 77 |
+
config,
|
| 78 |
+
existing_model=None,
|
| 79 |
+
existing_inputs=None,
|
| 80 |
+
):
|
| 81 |
+
# First thing first, enable eager execution if necessary.
|
| 82 |
+
base.enable_eager_execution_if_necessary()
|
| 83 |
+
|
| 84 |
+
# Although this is a no-op, we call __init__ here to make it clear
|
| 85 |
+
# that base.__init__ will use the make_model() call.
|
| 86 |
+
VTraceClipGradients.__init__(self)
|
| 87 |
+
VTraceOptimizer.__init__(self)
|
| 88 |
+
|
| 89 |
+
# Initialize base class.
|
| 90 |
+
base.__init__(
|
| 91 |
+
self,
|
| 92 |
+
observation_space,
|
| 93 |
+
action_space,
|
| 94 |
+
config,
|
| 95 |
+
existing_inputs=existing_inputs,
|
| 96 |
+
existing_model=existing_model,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# TF LearningRateSchedule depends on self.framework, so initialize
|
| 100 |
+
# after base.__init__() is called.
|
| 101 |
+
LearningRateSchedule.__init__(self, config["lr"], config["lr_schedule"])
|
| 102 |
+
EntropyCoeffSchedule.__init__(
|
| 103 |
+
self, config["entropy_coeff"], config["entropy_coeff_schedule"]
|
| 104 |
+
)
|
| 105 |
+
ValueNetworkMixin.__init__(self, config)
|
| 106 |
+
KLCoeffMixin.__init__(self, config)
|
| 107 |
+
|
| 108 |
+
GradStatsMixin.__init__(self)
|
| 109 |
+
|
| 110 |
+
# Note: this is a bit ugly, but loss and optimizer initialization must
|
| 111 |
+
# happen after all the MixIns are initialized.
|
| 112 |
+
self.maybe_initialize_optimizer_and_loss()
|
| 113 |
+
|
| 114 |
+
# Initiate TargetNetwork ops after loss initialization.
|
| 115 |
+
TargetNetworkMixin.__init__(self)
|
| 116 |
+
|
| 117 |
+
@override(base)
|
| 118 |
+
def make_model(self) -> ModelV2:
|
| 119 |
+
return make_appo_models(self)
|
| 120 |
+
|
| 121 |
+
@override(base)
|
| 122 |
+
def loss(
|
| 123 |
+
self,
|
| 124 |
+
model: Union[ModelV2, "tf.keras.Model"],
|
| 125 |
+
dist_class: Type[TFActionDistribution],
|
| 126 |
+
train_batch: SampleBatch,
|
| 127 |
+
) -> Union[TensorType, List[TensorType]]:
|
| 128 |
+
model_out, _ = model(train_batch)
|
| 129 |
+
action_dist = dist_class(model_out, model)
|
| 130 |
+
|
| 131 |
+
if isinstance(self.action_space, gym.spaces.Discrete):
|
| 132 |
+
is_multidiscrete = False
|
| 133 |
+
output_hidden_shape = [self.action_space.n]
|
| 134 |
+
elif isinstance(self.action_space, gym.spaces.multi_discrete.MultiDiscrete):
|
| 135 |
+
is_multidiscrete = True
|
| 136 |
+
output_hidden_shape = self.action_space.nvec.astype(np.int32)
|
| 137 |
+
else:
|
| 138 |
+
is_multidiscrete = False
|
| 139 |
+
output_hidden_shape = 1
|
| 140 |
+
|
| 141 |
+
def make_time_major(*args, **kw):
|
| 142 |
+
return _make_time_major(
|
| 143 |
+
self, train_batch.get(SampleBatch.SEQ_LENS), *args, **kw
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
actions = train_batch[SampleBatch.ACTIONS]
|
| 147 |
+
dones = train_batch[SampleBatch.TERMINATEDS]
|
| 148 |
+
rewards = train_batch[SampleBatch.REWARDS]
|
| 149 |
+
behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS]
|
| 150 |
+
|
| 151 |
+
target_model_out, _ = self.target_model(train_batch)
|
| 152 |
+
prev_action_dist = dist_class(behaviour_logits, self.model)
|
| 153 |
+
values = self.model.value_function()
|
| 154 |
+
values_time_major = make_time_major(values)
|
| 155 |
+
bootstrap_values_time_major = make_time_major(
|
| 156 |
+
train_batch[SampleBatch.VALUES_BOOTSTRAPPED]
|
| 157 |
+
)
|
| 158 |
+
bootstrap_value = bootstrap_values_time_major[-1]
|
| 159 |
+
|
| 160 |
+
if self.is_recurrent():
|
| 161 |
+
max_seq_len = tf.reduce_max(train_batch[SampleBatch.SEQ_LENS])
|
| 162 |
+
mask = tf.sequence_mask(train_batch[SampleBatch.SEQ_LENS], max_seq_len)
|
| 163 |
+
mask = tf.reshape(mask, [-1])
|
| 164 |
+
mask = make_time_major(mask)
|
| 165 |
+
|
| 166 |
+
def reduce_mean_valid(t):
|
| 167 |
+
return tf.reduce_mean(tf.boolean_mask(t, mask))
|
| 168 |
+
|
| 169 |
+
else:
|
| 170 |
+
reduce_mean_valid = tf.reduce_mean
|
| 171 |
+
|
| 172 |
+
if self.config["vtrace"]:
|
| 173 |
+
logger.debug("Using V-Trace surrogate loss (vtrace=True)")
|
| 174 |
+
|
| 175 |
+
# Prepare actions for loss.
|
| 176 |
+
loss_actions = (
|
| 177 |
+
actions if is_multidiscrete else tf.expand_dims(actions, axis=1)
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
old_policy_behaviour_logits = tf.stop_gradient(target_model_out)
|
| 181 |
+
old_policy_action_dist = dist_class(old_policy_behaviour_logits, model)
|
| 182 |
+
|
| 183 |
+
# Prepare KL for Loss
|
| 184 |
+
mean_kl = make_time_major(old_policy_action_dist.multi_kl(action_dist))
|
| 185 |
+
|
| 186 |
+
unpacked_behaviour_logits = tf.split(
|
| 187 |
+
behaviour_logits, output_hidden_shape, axis=1
|
| 188 |
+
)
|
| 189 |
+
unpacked_old_policy_behaviour_logits = tf.split(
|
| 190 |
+
old_policy_behaviour_logits, output_hidden_shape, axis=1
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
# Compute vtrace on the CPU for better perf.
|
| 194 |
+
with tf.device("/cpu:0"):
|
| 195 |
+
vtrace_returns = vtrace.multi_from_logits(
|
| 196 |
+
behaviour_policy_logits=make_time_major(
|
| 197 |
+
unpacked_behaviour_logits
|
| 198 |
+
),
|
| 199 |
+
target_policy_logits=make_time_major(
|
| 200 |
+
unpacked_old_policy_behaviour_logits
|
| 201 |
+
),
|
| 202 |
+
actions=tf.unstack(make_time_major(loss_actions), axis=2),
|
| 203 |
+
discounts=tf.cast(
|
| 204 |
+
~make_time_major(tf.cast(dones, tf.bool)),
|
| 205 |
+
tf.float32,
|
| 206 |
+
)
|
| 207 |
+
* self.config["gamma"],
|
| 208 |
+
rewards=make_time_major(rewards),
|
| 209 |
+
values=values_time_major,
|
| 210 |
+
bootstrap_value=bootstrap_value,
|
| 211 |
+
dist_class=Categorical if is_multidiscrete else dist_class,
|
| 212 |
+
model=model,
|
| 213 |
+
clip_rho_threshold=tf.cast(
|
| 214 |
+
self.config["vtrace_clip_rho_threshold"], tf.float32
|
| 215 |
+
),
|
| 216 |
+
clip_pg_rho_threshold=tf.cast(
|
| 217 |
+
self.config["vtrace_clip_pg_rho_threshold"], tf.float32
|
| 218 |
+
),
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
actions_logp = make_time_major(action_dist.logp(actions))
|
| 222 |
+
prev_actions_logp = make_time_major(prev_action_dist.logp(actions))
|
| 223 |
+
old_policy_actions_logp = make_time_major(
|
| 224 |
+
old_policy_action_dist.logp(actions)
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
is_ratio = tf.clip_by_value(
|
| 228 |
+
tf.math.exp(prev_actions_logp - old_policy_actions_logp), 0.0, 2.0
|
| 229 |
+
)
|
| 230 |
+
logp_ratio = is_ratio * tf.exp(actions_logp - prev_actions_logp)
|
| 231 |
+
self._is_ratio = is_ratio
|
| 232 |
+
|
| 233 |
+
advantages = vtrace_returns.pg_advantages
|
| 234 |
+
surrogate_loss = tf.minimum(
|
| 235 |
+
advantages * logp_ratio,
|
| 236 |
+
advantages
|
| 237 |
+
* tf.clip_by_value(
|
| 238 |
+
logp_ratio,
|
| 239 |
+
1 - self.config["clip_param"],
|
| 240 |
+
1 + self.config["clip_param"],
|
| 241 |
+
),
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
action_kl = (
|
| 245 |
+
tf.reduce_mean(mean_kl, axis=0) if is_multidiscrete else mean_kl
|
| 246 |
+
)
|
| 247 |
+
mean_kl_loss = reduce_mean_valid(action_kl)
|
| 248 |
+
mean_policy_loss = -reduce_mean_valid(surrogate_loss)
|
| 249 |
+
|
| 250 |
+
# The value function loss.
|
| 251 |
+
value_targets = vtrace_returns.vs
|
| 252 |
+
delta = values_time_major - value_targets
|
| 253 |
+
mean_vf_loss = 0.5 * reduce_mean_valid(tf.math.square(delta))
|
| 254 |
+
|
| 255 |
+
# The entropy loss.
|
| 256 |
+
actions_entropy = make_time_major(action_dist.multi_entropy())
|
| 257 |
+
mean_entropy = reduce_mean_valid(actions_entropy)
|
| 258 |
+
|
| 259 |
+
else:
|
| 260 |
+
logger.debug("Using PPO surrogate loss (vtrace=False)")
|
| 261 |
+
|
| 262 |
+
# Prepare KL for Loss
|
| 263 |
+
mean_kl = make_time_major(prev_action_dist.multi_kl(action_dist))
|
| 264 |
+
|
| 265 |
+
logp_ratio = tf.math.exp(
|
| 266 |
+
make_time_major(action_dist.logp(actions))
|
| 267 |
+
- make_time_major(prev_action_dist.logp(actions))
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
advantages = make_time_major(train_batch[Postprocessing.ADVANTAGES])
|
| 271 |
+
surrogate_loss = tf.minimum(
|
| 272 |
+
advantages * logp_ratio,
|
| 273 |
+
advantages
|
| 274 |
+
* tf.clip_by_value(
|
| 275 |
+
logp_ratio,
|
| 276 |
+
1 - self.config["clip_param"],
|
| 277 |
+
1 + self.config["clip_param"],
|
| 278 |
+
),
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
action_kl = (
|
| 282 |
+
tf.reduce_mean(mean_kl, axis=0) if is_multidiscrete else mean_kl
|
| 283 |
+
)
|
| 284 |
+
mean_kl_loss = reduce_mean_valid(action_kl)
|
| 285 |
+
mean_policy_loss = -reduce_mean_valid(surrogate_loss)
|
| 286 |
+
|
| 287 |
+
# The value function loss.
|
| 288 |
+
value_targets = make_time_major(
|
| 289 |
+
train_batch[Postprocessing.VALUE_TARGETS]
|
| 290 |
+
)
|
| 291 |
+
delta = values_time_major - value_targets
|
| 292 |
+
mean_vf_loss = 0.5 * reduce_mean_valid(tf.math.square(delta))
|
| 293 |
+
|
| 294 |
+
# The entropy loss.
|
| 295 |
+
mean_entropy = reduce_mean_valid(
|
| 296 |
+
make_time_major(action_dist.multi_entropy())
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
# The summed weighted loss.
|
| 300 |
+
total_loss = mean_policy_loss - mean_entropy * self.entropy_coeff
|
| 301 |
+
# Optional KL loss.
|
| 302 |
+
if self.config["use_kl_loss"]:
|
| 303 |
+
total_loss += self.kl_coeff * mean_kl_loss
|
| 304 |
+
# Optional vf loss (or in a separate term due to separate
|
| 305 |
+
# optimizers/networks).
|
| 306 |
+
loss_wo_vf = total_loss
|
| 307 |
+
if not self.config["_separate_vf_optimizer"]:
|
| 308 |
+
total_loss += mean_vf_loss * self.config["vf_loss_coeff"]
|
| 309 |
+
|
| 310 |
+
# Store stats in policy for stats_fn.
|
| 311 |
+
self._total_loss = total_loss
|
| 312 |
+
self._loss_wo_vf = loss_wo_vf
|
| 313 |
+
self._mean_policy_loss = mean_policy_loss
|
| 314 |
+
# Backward compatibility: Deprecate policy._mean_kl.
|
| 315 |
+
self._mean_kl_loss = self._mean_kl = mean_kl_loss
|
| 316 |
+
self._mean_vf_loss = mean_vf_loss
|
| 317 |
+
self._mean_entropy = mean_entropy
|
| 318 |
+
self._value_targets = value_targets
|
| 319 |
+
|
| 320 |
+
# Return one total loss or two losses: vf vs rest (policy + kl).
|
| 321 |
+
if self.config["_separate_vf_optimizer"]:
|
| 322 |
+
return loss_wo_vf, mean_vf_loss
|
| 323 |
+
else:
|
| 324 |
+
return total_loss
|
| 325 |
+
|
| 326 |
+
@override(base)
|
| 327 |
+
def stats_fn(self, train_batch: SampleBatch) -> Dict[str, TensorType]:
|
| 328 |
+
values_batched = _make_time_major(
|
| 329 |
+
self,
|
| 330 |
+
train_batch.get(SampleBatch.SEQ_LENS),
|
| 331 |
+
self.model.value_function(),
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
stats_dict = {
|
| 335 |
+
"cur_lr": tf.cast(self.cur_lr, tf.float64),
|
| 336 |
+
"total_loss": self._total_loss,
|
| 337 |
+
"policy_loss": self._mean_policy_loss,
|
| 338 |
+
"entropy": self._mean_entropy,
|
| 339 |
+
"var_gnorm": tf.linalg.global_norm(self.model.trainable_variables()),
|
| 340 |
+
"vf_loss": self._mean_vf_loss,
|
| 341 |
+
"vf_explained_var": explained_variance(
|
| 342 |
+
tf.reshape(self._value_targets, [-1]),
|
| 343 |
+
tf.reshape(values_batched, [-1]),
|
| 344 |
+
),
|
| 345 |
+
"entropy_coeff": tf.cast(self.entropy_coeff, tf.float64),
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
if self.config["vtrace"]:
|
| 349 |
+
is_stat_mean, is_stat_var = tf.nn.moments(self._is_ratio, [0, 1])
|
| 350 |
+
stats_dict["mean_IS"] = is_stat_mean
|
| 351 |
+
stats_dict["var_IS"] = is_stat_var
|
| 352 |
+
|
| 353 |
+
if self.config["use_kl_loss"]:
|
| 354 |
+
stats_dict["kl"] = self._mean_kl_loss
|
| 355 |
+
stats_dict["KL_Coeff"] = self.kl_coeff
|
| 356 |
+
|
| 357 |
+
return stats_dict
|
| 358 |
+
|
| 359 |
+
@override(base)
|
| 360 |
+
def postprocess_trajectory(
|
| 361 |
+
self,
|
| 362 |
+
sample_batch: SampleBatch,
|
| 363 |
+
other_agent_batches: Optional[SampleBatch] = None,
|
| 364 |
+
episode=None,
|
| 365 |
+
):
|
| 366 |
+
# Call super's postprocess_trajectory first.
|
| 367 |
+
# sample_batch = super().postprocess_trajectory(
|
| 368 |
+
# sample_batch, other_agent_batches, episode
|
| 369 |
+
# )
|
| 370 |
+
|
| 371 |
+
if not self.config["vtrace"]:
|
| 372 |
+
sample_batch = compute_gae_for_sample_batch(
|
| 373 |
+
self, sample_batch, other_agent_batches, episode
|
| 374 |
+
)
|
| 375 |
+
else:
|
| 376 |
+
# Add the Columns.VALUES_BOOTSTRAPPED column, which we'll need
|
| 377 |
+
# inside the loss for vtrace calculations.
|
| 378 |
+
sample_batch = compute_bootstrap_value(sample_batch, self)
|
| 379 |
+
|
| 380 |
+
return sample_batch
|
| 381 |
+
|
| 382 |
+
@override(base)
|
| 383 |
+
def get_batch_divisibility_req(self) -> int:
|
| 384 |
+
return self.config["rollout_fragment_length"]
|
| 385 |
+
|
| 386 |
+
APPOTFPolicy.__name__ = name
|
| 387 |
+
APPOTFPolicy.__qualname__ = name
|
| 388 |
+
|
| 389 |
+
return APPOTFPolicy
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
APPOTF1Policy = get_appo_tf_policy("APPOTF1Policy", DynamicTFPolicyV2)
|
| 393 |
+
APPOTF2Policy = get_appo_tf_policy("APPOTF2Policy", EagerTFPolicyV2)
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/appo_torch_policy.py
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PyTorch policy class used for APPO.
|
| 3 |
+
|
| 4 |
+
Adapted from VTraceTFPolicy to use the PPO surrogate loss.
|
| 5 |
+
Keep in sync with changes to VTraceTFPolicy.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import logging
|
| 9 |
+
from typing import Any, Dict, List, Optional, Type, Union
|
| 10 |
+
|
| 11 |
+
import gymnasium as gym
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
import ray
|
| 15 |
+
import ray.rllib.algorithms.impala.vtrace_torch as vtrace
|
| 16 |
+
from ray.rllib.algorithms.appo.utils import make_appo_models
|
| 17 |
+
from ray.rllib.algorithms.impala.impala_torch_policy import (
|
| 18 |
+
VTraceOptimizer,
|
| 19 |
+
make_time_major,
|
| 20 |
+
)
|
| 21 |
+
from ray.rllib.evaluation.postprocessing import (
|
| 22 |
+
Postprocessing,
|
| 23 |
+
compute_bootstrap_value,
|
| 24 |
+
compute_gae_for_sample_batch,
|
| 25 |
+
)
|
| 26 |
+
from ray.rllib.models.action_dist import ActionDistribution
|
| 27 |
+
from ray.rllib.models.modelv2 import ModelV2
|
| 28 |
+
from ray.rllib.models.torch.torch_action_dist import (
|
| 29 |
+
TorchCategorical,
|
| 30 |
+
TorchDistributionWrapper,
|
| 31 |
+
)
|
| 32 |
+
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
| 33 |
+
from ray.rllib.policy.sample_batch import SampleBatch
|
| 34 |
+
from ray.rllib.policy.torch_mixins import (
|
| 35 |
+
EntropyCoeffSchedule,
|
| 36 |
+
KLCoeffMixin,
|
| 37 |
+
LearningRateSchedule,
|
| 38 |
+
TargetNetworkMixin,
|
| 39 |
+
ValueNetworkMixin,
|
| 40 |
+
)
|
| 41 |
+
from ray.rllib.policy.torch_policy_v2 import TorchPolicyV2
|
| 42 |
+
from ray.rllib.utils.annotations import override
|
| 43 |
+
from ray.rllib.utils.framework import try_import_torch
|
| 44 |
+
from ray.rllib.utils.numpy import convert_to_numpy
|
| 45 |
+
from ray.rllib.utils.torch_utils import (
|
| 46 |
+
apply_grad_clipping,
|
| 47 |
+
explained_variance,
|
| 48 |
+
global_norm,
|
| 49 |
+
sequence_mask,
|
| 50 |
+
)
|
| 51 |
+
from ray.rllib.utils.typing import TensorType
|
| 52 |
+
|
| 53 |
+
torch, nn = try_import_torch()
|
| 54 |
+
|
| 55 |
+
logger = logging.getLogger(__name__)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# TODO (sven): Deprecate once APPO and IMPALA fully on RLModules/Learner APIs.
|
| 59 |
+
class APPOTorchPolicy(
|
| 60 |
+
VTraceOptimizer,
|
| 61 |
+
LearningRateSchedule,
|
| 62 |
+
EntropyCoeffSchedule,
|
| 63 |
+
KLCoeffMixin,
|
| 64 |
+
ValueNetworkMixin,
|
| 65 |
+
TargetNetworkMixin,
|
| 66 |
+
TorchPolicyV2,
|
| 67 |
+
):
|
| 68 |
+
"""PyTorch policy class used with APPO."""
|
| 69 |
+
|
| 70 |
+
def __init__(self, observation_space, action_space, config):
|
| 71 |
+
config = dict(ray.rllib.algorithms.appo.appo.APPOConfig().to_dict(), **config)
|
| 72 |
+
config["enable_rl_module_and_learner"] = False
|
| 73 |
+
config["enable_env_runner_and_connector_v2"] = False
|
| 74 |
+
|
| 75 |
+
# Although this is a no-op, we call __init__ here to make it clear
|
| 76 |
+
# that base.__init__ will use the make_model() call.
|
| 77 |
+
VTraceOptimizer.__init__(self)
|
| 78 |
+
|
| 79 |
+
lr_schedule_additional_args = []
|
| 80 |
+
if config.get("_separate_vf_optimizer"):
|
| 81 |
+
lr_schedule_additional_args = (
|
| 82 |
+
[config["_lr_vf"][0][1], config["_lr_vf"]]
|
| 83 |
+
if isinstance(config["_lr_vf"], (list, tuple))
|
| 84 |
+
else [config["_lr_vf"], None]
|
| 85 |
+
)
|
| 86 |
+
LearningRateSchedule.__init__(
|
| 87 |
+
self, config["lr"], config["lr_schedule"], *lr_schedule_additional_args
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
TorchPolicyV2.__init__(
|
| 91 |
+
self,
|
| 92 |
+
observation_space,
|
| 93 |
+
action_space,
|
| 94 |
+
config,
|
| 95 |
+
max_seq_len=config["model"]["max_seq_len"],
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
EntropyCoeffSchedule.__init__(
|
| 99 |
+
self, config["entropy_coeff"], config["entropy_coeff_schedule"]
|
| 100 |
+
)
|
| 101 |
+
ValueNetworkMixin.__init__(self, config)
|
| 102 |
+
KLCoeffMixin.__init__(self, config)
|
| 103 |
+
|
| 104 |
+
self._initialize_loss_from_dummy_batch()
|
| 105 |
+
|
| 106 |
+
# Initiate TargetNetwork ops after loss initialization.
|
| 107 |
+
TargetNetworkMixin.__init__(self)
|
| 108 |
+
|
| 109 |
+
@override(TorchPolicyV2)
|
| 110 |
+
def init_view_requirements(self):
|
| 111 |
+
self.view_requirements = self._get_default_view_requirements()
|
| 112 |
+
|
| 113 |
+
@override(TorchPolicyV2)
|
| 114 |
+
def make_model(self) -> ModelV2:
|
| 115 |
+
return make_appo_models(self)
|
| 116 |
+
|
| 117 |
+
@override(TorchPolicyV2)
|
| 118 |
+
def loss(
|
| 119 |
+
self,
|
| 120 |
+
model: ModelV2,
|
| 121 |
+
dist_class: Type[ActionDistribution],
|
| 122 |
+
train_batch: SampleBatch,
|
| 123 |
+
) -> Union[TensorType, List[TensorType]]:
|
| 124 |
+
"""Constructs the loss for APPO.
|
| 125 |
+
|
| 126 |
+
With IS modifications and V-trace for Advantage Estimation.
|
| 127 |
+
|
| 128 |
+
Args:
|
| 129 |
+
model (ModelV2): The Model to calculate the loss for.
|
| 130 |
+
dist_class (Type[ActionDistribution]): The action distr. class.
|
| 131 |
+
train_batch: The training data.
|
| 132 |
+
|
| 133 |
+
Returns:
|
| 134 |
+
Union[TensorType, List[TensorType]]: A single loss tensor or a list
|
| 135 |
+
of loss tensors.
|
| 136 |
+
"""
|
| 137 |
+
target_model = self.target_models[model]
|
| 138 |
+
|
| 139 |
+
model_out, _ = model(train_batch)
|
| 140 |
+
action_dist = dist_class(model_out, model)
|
| 141 |
+
|
| 142 |
+
if isinstance(self.action_space, gym.spaces.Discrete):
|
| 143 |
+
is_multidiscrete = False
|
| 144 |
+
output_hidden_shape = [self.action_space.n]
|
| 145 |
+
elif isinstance(self.action_space, gym.spaces.multi_discrete.MultiDiscrete):
|
| 146 |
+
is_multidiscrete = True
|
| 147 |
+
output_hidden_shape = self.action_space.nvec.astype(np.int32)
|
| 148 |
+
else:
|
| 149 |
+
is_multidiscrete = False
|
| 150 |
+
output_hidden_shape = 1
|
| 151 |
+
|
| 152 |
+
def _make_time_major(*args, **kwargs):
|
| 153 |
+
return make_time_major(
|
| 154 |
+
self, train_batch.get(SampleBatch.SEQ_LENS), *args, **kwargs
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
actions = train_batch[SampleBatch.ACTIONS]
|
| 158 |
+
dones = train_batch[SampleBatch.TERMINATEDS]
|
| 159 |
+
rewards = train_batch[SampleBatch.REWARDS]
|
| 160 |
+
behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS]
|
| 161 |
+
|
| 162 |
+
target_model_out, _ = target_model(train_batch)
|
| 163 |
+
|
| 164 |
+
prev_action_dist = dist_class(behaviour_logits, model)
|
| 165 |
+
values = model.value_function()
|
| 166 |
+
values_time_major = _make_time_major(values)
|
| 167 |
+
bootstrap_values_time_major = _make_time_major(
|
| 168 |
+
train_batch[SampleBatch.VALUES_BOOTSTRAPPED]
|
| 169 |
+
)
|
| 170 |
+
bootstrap_value = bootstrap_values_time_major[-1]
|
| 171 |
+
|
| 172 |
+
if self.is_recurrent():
|
| 173 |
+
max_seq_len = torch.max(train_batch[SampleBatch.SEQ_LENS])
|
| 174 |
+
mask = sequence_mask(train_batch[SampleBatch.SEQ_LENS], max_seq_len)
|
| 175 |
+
mask = torch.reshape(mask, [-1])
|
| 176 |
+
mask = _make_time_major(mask)
|
| 177 |
+
num_valid = torch.sum(mask)
|
| 178 |
+
|
| 179 |
+
def reduce_mean_valid(t):
|
| 180 |
+
return torch.sum(t[mask]) / num_valid
|
| 181 |
+
|
| 182 |
+
else:
|
| 183 |
+
reduce_mean_valid = torch.mean
|
| 184 |
+
|
| 185 |
+
if self.config["vtrace"]:
|
| 186 |
+
logger.debug("Using V-Trace surrogate loss (vtrace=True)")
|
| 187 |
+
|
| 188 |
+
old_policy_behaviour_logits = target_model_out.detach()
|
| 189 |
+
old_policy_action_dist = dist_class(old_policy_behaviour_logits, model)
|
| 190 |
+
|
| 191 |
+
if isinstance(output_hidden_shape, (list, tuple, np.ndarray)):
|
| 192 |
+
unpacked_behaviour_logits = torch.split(
|
| 193 |
+
behaviour_logits, list(output_hidden_shape), dim=1
|
| 194 |
+
)
|
| 195 |
+
unpacked_old_policy_behaviour_logits = torch.split(
|
| 196 |
+
old_policy_behaviour_logits, list(output_hidden_shape), dim=1
|
| 197 |
+
)
|
| 198 |
+
else:
|
| 199 |
+
unpacked_behaviour_logits = torch.chunk(
|
| 200 |
+
behaviour_logits, output_hidden_shape, dim=1
|
| 201 |
+
)
|
| 202 |
+
unpacked_old_policy_behaviour_logits = torch.chunk(
|
| 203 |
+
old_policy_behaviour_logits, output_hidden_shape, dim=1
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
# Prepare actions for loss.
|
| 207 |
+
loss_actions = (
|
| 208 |
+
actions if is_multidiscrete else torch.unsqueeze(actions, dim=1)
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
# Prepare KL for loss.
|
| 212 |
+
action_kl = _make_time_major(old_policy_action_dist.kl(action_dist))
|
| 213 |
+
|
| 214 |
+
# Compute vtrace on the CPU for better perf.
|
| 215 |
+
vtrace_returns = vtrace.multi_from_logits(
|
| 216 |
+
behaviour_policy_logits=_make_time_major(unpacked_behaviour_logits),
|
| 217 |
+
target_policy_logits=_make_time_major(
|
| 218 |
+
unpacked_old_policy_behaviour_logits
|
| 219 |
+
),
|
| 220 |
+
actions=torch.unbind(_make_time_major(loss_actions), dim=2),
|
| 221 |
+
discounts=(1.0 - _make_time_major(dones).float())
|
| 222 |
+
* self.config["gamma"],
|
| 223 |
+
rewards=_make_time_major(rewards),
|
| 224 |
+
values=values_time_major,
|
| 225 |
+
bootstrap_value=bootstrap_value,
|
| 226 |
+
dist_class=TorchCategorical if is_multidiscrete else dist_class,
|
| 227 |
+
model=model,
|
| 228 |
+
clip_rho_threshold=self.config["vtrace_clip_rho_threshold"],
|
| 229 |
+
clip_pg_rho_threshold=self.config["vtrace_clip_pg_rho_threshold"],
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
actions_logp = _make_time_major(action_dist.logp(actions))
|
| 233 |
+
prev_actions_logp = _make_time_major(prev_action_dist.logp(actions))
|
| 234 |
+
old_policy_actions_logp = _make_time_major(
|
| 235 |
+
old_policy_action_dist.logp(actions)
|
| 236 |
+
)
|
| 237 |
+
is_ratio = torch.clamp(
|
| 238 |
+
torch.exp(prev_actions_logp - old_policy_actions_logp), 0.0, 2.0
|
| 239 |
+
)
|
| 240 |
+
logp_ratio = is_ratio * torch.exp(actions_logp - prev_actions_logp)
|
| 241 |
+
self._is_ratio = is_ratio
|
| 242 |
+
|
| 243 |
+
advantages = vtrace_returns.pg_advantages.to(logp_ratio.device)
|
| 244 |
+
surrogate_loss = torch.min(
|
| 245 |
+
advantages * logp_ratio,
|
| 246 |
+
advantages
|
| 247 |
+
* torch.clamp(
|
| 248 |
+
logp_ratio,
|
| 249 |
+
1 - self.config["clip_param"],
|
| 250 |
+
1 + self.config["clip_param"],
|
| 251 |
+
),
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
mean_kl_loss = reduce_mean_valid(action_kl)
|
| 255 |
+
mean_policy_loss = -reduce_mean_valid(surrogate_loss)
|
| 256 |
+
|
| 257 |
+
# The value function loss.
|
| 258 |
+
value_targets = vtrace_returns.vs.to(values_time_major.device)
|
| 259 |
+
delta = values_time_major - value_targets
|
| 260 |
+
mean_vf_loss = 0.5 * reduce_mean_valid(torch.pow(delta, 2.0))
|
| 261 |
+
|
| 262 |
+
# The entropy loss.
|
| 263 |
+
mean_entropy = reduce_mean_valid(_make_time_major(action_dist.entropy()))
|
| 264 |
+
|
| 265 |
+
else:
|
| 266 |
+
logger.debug("Using PPO surrogate loss (vtrace=False)")
|
| 267 |
+
|
| 268 |
+
# Prepare KL for Loss
|
| 269 |
+
action_kl = _make_time_major(prev_action_dist.kl(action_dist))
|
| 270 |
+
|
| 271 |
+
actions_logp = _make_time_major(action_dist.logp(actions))
|
| 272 |
+
prev_actions_logp = _make_time_major(prev_action_dist.logp(actions))
|
| 273 |
+
logp_ratio = torch.exp(actions_logp - prev_actions_logp)
|
| 274 |
+
|
| 275 |
+
advantages = _make_time_major(train_batch[Postprocessing.ADVANTAGES])
|
| 276 |
+
surrogate_loss = torch.min(
|
| 277 |
+
advantages * logp_ratio,
|
| 278 |
+
advantages
|
| 279 |
+
* torch.clamp(
|
| 280 |
+
logp_ratio,
|
| 281 |
+
1 - self.config["clip_param"],
|
| 282 |
+
1 + self.config["clip_param"],
|
| 283 |
+
),
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
mean_kl_loss = reduce_mean_valid(action_kl)
|
| 287 |
+
mean_policy_loss = -reduce_mean_valid(surrogate_loss)
|
| 288 |
+
|
| 289 |
+
# The value function loss.
|
| 290 |
+
value_targets = _make_time_major(train_batch[Postprocessing.VALUE_TARGETS])
|
| 291 |
+
delta = values_time_major - value_targets
|
| 292 |
+
mean_vf_loss = 0.5 * reduce_mean_valid(torch.pow(delta, 2.0))
|
| 293 |
+
|
| 294 |
+
# The entropy loss.
|
| 295 |
+
mean_entropy = reduce_mean_valid(_make_time_major(action_dist.entropy()))
|
| 296 |
+
|
| 297 |
+
# The summed weighted loss.
|
| 298 |
+
total_loss = mean_policy_loss - mean_entropy * self.entropy_coeff
|
| 299 |
+
# Optional additional KL Loss
|
| 300 |
+
if self.config["use_kl_loss"]:
|
| 301 |
+
total_loss += self.kl_coeff * mean_kl_loss
|
| 302 |
+
|
| 303 |
+
# Optional vf loss (or in a separate term due to separate
|
| 304 |
+
# optimizers/networks).
|
| 305 |
+
loss_wo_vf = total_loss
|
| 306 |
+
if not self.config["_separate_vf_optimizer"]:
|
| 307 |
+
total_loss += mean_vf_loss * self.config["vf_loss_coeff"]
|
| 308 |
+
|
| 309 |
+
# Store values for stats function in model (tower), such that for
|
| 310 |
+
# multi-GPU, we do not override them during the parallel loss phase.
|
| 311 |
+
model.tower_stats["total_loss"] = total_loss
|
| 312 |
+
model.tower_stats["mean_policy_loss"] = mean_policy_loss
|
| 313 |
+
model.tower_stats["mean_kl_loss"] = mean_kl_loss
|
| 314 |
+
model.tower_stats["mean_vf_loss"] = mean_vf_loss
|
| 315 |
+
model.tower_stats["mean_entropy"] = mean_entropy
|
| 316 |
+
model.tower_stats["value_targets"] = value_targets
|
| 317 |
+
model.tower_stats["vf_explained_var"] = explained_variance(
|
| 318 |
+
torch.reshape(value_targets, [-1]),
|
| 319 |
+
torch.reshape(values_time_major, [-1]),
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
# Return one total loss or two losses: vf vs rest (policy + kl).
|
| 323 |
+
if self.config["_separate_vf_optimizer"]:
|
| 324 |
+
return loss_wo_vf, mean_vf_loss
|
| 325 |
+
else:
|
| 326 |
+
return total_loss
|
| 327 |
+
|
| 328 |
+
@override(TorchPolicyV2)
|
| 329 |
+
def stats_fn(self, train_batch: SampleBatch) -> Dict[str, TensorType]:
|
| 330 |
+
"""Stats function for APPO. Returns a dict with important loss stats.
|
| 331 |
+
|
| 332 |
+
Args:
|
| 333 |
+
policy: The Policy to generate stats for.
|
| 334 |
+
train_batch: The SampleBatch (already) used for training.
|
| 335 |
+
|
| 336 |
+
Returns:
|
| 337 |
+
Dict[str, TensorType]: The stats dict.
|
| 338 |
+
"""
|
| 339 |
+
stats_dict = {
|
| 340 |
+
"cur_lr": self.cur_lr,
|
| 341 |
+
"total_loss": torch.mean(torch.stack(self.get_tower_stats("total_loss"))),
|
| 342 |
+
"policy_loss": torch.mean(
|
| 343 |
+
torch.stack(self.get_tower_stats("mean_policy_loss"))
|
| 344 |
+
),
|
| 345 |
+
"entropy": torch.mean(torch.stack(self.get_tower_stats("mean_entropy"))),
|
| 346 |
+
"entropy_coeff": self.entropy_coeff,
|
| 347 |
+
"var_gnorm": global_norm(self.model.trainable_variables()),
|
| 348 |
+
"vf_loss": torch.mean(torch.stack(self.get_tower_stats("mean_vf_loss"))),
|
| 349 |
+
"vf_explained_var": torch.mean(
|
| 350 |
+
torch.stack(self.get_tower_stats("vf_explained_var"))
|
| 351 |
+
),
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
if self.config["vtrace"]:
|
| 355 |
+
is_stat_mean = torch.mean(self._is_ratio, [0, 1])
|
| 356 |
+
is_stat_var = torch.var(self._is_ratio, [0, 1])
|
| 357 |
+
stats_dict["mean_IS"] = is_stat_mean
|
| 358 |
+
stats_dict["var_IS"] = is_stat_var
|
| 359 |
+
|
| 360 |
+
if self.config["use_kl_loss"]:
|
| 361 |
+
stats_dict["kl"] = torch.mean(
|
| 362 |
+
torch.stack(self.get_tower_stats("mean_kl_loss"))
|
| 363 |
+
)
|
| 364 |
+
stats_dict["KL_Coeff"] = self.kl_coeff
|
| 365 |
+
|
| 366 |
+
return convert_to_numpy(stats_dict)
|
| 367 |
+
|
| 368 |
+
@override(TorchPolicyV2)
|
| 369 |
+
def extra_action_out(
|
| 370 |
+
self,
|
| 371 |
+
input_dict: Dict[str, TensorType],
|
| 372 |
+
state_batches: List[TensorType],
|
| 373 |
+
model: TorchModelV2,
|
| 374 |
+
action_dist: TorchDistributionWrapper,
|
| 375 |
+
) -> Dict[str, TensorType]:
|
| 376 |
+
return {SampleBatch.VF_PREDS: model.value_function()}
|
| 377 |
+
|
| 378 |
+
@override(TorchPolicyV2)
|
| 379 |
+
def postprocess_trajectory(
|
| 380 |
+
self,
|
| 381 |
+
sample_batch: SampleBatch,
|
| 382 |
+
other_agent_batches: Optional[Dict[Any, SampleBatch]] = None,
|
| 383 |
+
episode=None,
|
| 384 |
+
):
|
| 385 |
+
# Call super's postprocess_trajectory first.
|
| 386 |
+
# sample_batch = super().postprocess_trajectory(
|
| 387 |
+
# sample_batch, other_agent_batches, episode
|
| 388 |
+
# )
|
| 389 |
+
|
| 390 |
+
# Do all post-processing always with no_grad().
|
| 391 |
+
# Not using this here will introduce a memory leak
|
| 392 |
+
# in torch (issue #6962).
|
| 393 |
+
with torch.no_grad():
|
| 394 |
+
if not self.config["vtrace"]:
|
| 395 |
+
sample_batch = compute_gae_for_sample_batch(
|
| 396 |
+
self, sample_batch, other_agent_batches, episode
|
| 397 |
+
)
|
| 398 |
+
else:
|
| 399 |
+
# Add the SampleBatch.VALUES_BOOTSTRAPPED column, which we'll need
|
| 400 |
+
# inside the loss for vtrace calculations.
|
| 401 |
+
sample_batch = compute_bootstrap_value(sample_batch, self)
|
| 402 |
+
|
| 403 |
+
return sample_batch
|
| 404 |
+
|
| 405 |
+
@override(TorchPolicyV2)
|
| 406 |
+
def extra_grad_process(
|
| 407 |
+
self, optimizer: "torch.optim.Optimizer", loss: TensorType
|
| 408 |
+
) -> Dict[str, TensorType]:
|
| 409 |
+
return apply_grad_clipping(self, optimizer, loss)
|
| 410 |
+
|
| 411 |
+
@override(TorchPolicyV2)
|
| 412 |
+
def get_batch_divisibility_req(self) -> int:
|
| 413 |
+
return self.config["rollout_fragment_length"]
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/default_appo_rl_module.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import abc
|
| 2 |
+
from typing import Any, Dict, List, Tuple
|
| 3 |
+
|
| 4 |
+
from ray.rllib.algorithms.ppo.default_ppo_rl_module import DefaultPPORLModule
|
| 5 |
+
from ray.rllib.core.learner.utils import make_target_network
|
| 6 |
+
from ray.rllib.core.models.base import ACTOR, ENCODER_OUT
|
| 7 |
+
from ray.rllib.core.rl_module.apis import (
|
| 8 |
+
TARGET_NETWORK_ACTION_DIST_INPUTS,
|
| 9 |
+
TargetNetworkAPI,
|
| 10 |
+
)
|
| 11 |
+
from ray.rllib.utils.annotations import (
|
| 12 |
+
OverrideToImplementCustomLogic_CallToSuperRecommended,
|
| 13 |
+
override,
|
| 14 |
+
)
|
| 15 |
+
from ray.rllib.utils.typing import NetworkType
|
| 16 |
+
from ray.util.annotations import DeveloperAPI
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@DeveloperAPI
|
| 20 |
+
class DefaultAPPORLModule(DefaultPPORLModule, TargetNetworkAPI, abc.ABC):
|
| 21 |
+
"""Default RLModule used by APPO, if user does not specify a custom RLModule.
|
| 22 |
+
|
| 23 |
+
Users who want to train their RLModules with APPO may implement any RLModule
|
| 24 |
+
(or TorchRLModule) subclass as long as the custom class also implements the
|
| 25 |
+
`ValueFunctionAPI` (see ray.rllib.core.rl_module.apis.value_function_api.py)
|
| 26 |
+
and the `TargetNetworkAPI` (see
|
| 27 |
+
ray.rllib.core.rl_module.apis.target_network_api.py).
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
@override(TargetNetworkAPI)
|
| 31 |
+
def make_target_networks(self):
|
| 32 |
+
self._old_encoder = make_target_network(self.encoder)
|
| 33 |
+
self._old_pi = make_target_network(self.pi)
|
| 34 |
+
|
| 35 |
+
@override(TargetNetworkAPI)
|
| 36 |
+
def get_target_network_pairs(self) -> List[Tuple[NetworkType, NetworkType]]:
|
| 37 |
+
return [
|
| 38 |
+
(self.encoder, self._old_encoder),
|
| 39 |
+
(self.pi, self._old_pi),
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
@override(TargetNetworkAPI)
|
| 43 |
+
def forward_target(self, batch: Dict[str, Any]) -> Dict[str, Any]:
|
| 44 |
+
old_pi_inputs_encoded = self._old_encoder(batch)[ENCODER_OUT][ACTOR]
|
| 45 |
+
old_action_dist_logits = self._old_pi(old_pi_inputs_encoded)
|
| 46 |
+
return {TARGET_NETWORK_ACTION_DIST_INPUTS: old_action_dist_logits}
|
| 47 |
+
|
| 48 |
+
@OverrideToImplementCustomLogic_CallToSuperRecommended
|
| 49 |
+
@override(DefaultPPORLModule)
|
| 50 |
+
def get_non_inference_attributes(self) -> List[str]:
|
| 51 |
+
# Get the NON inference-only attributes from the parent class.
|
| 52 |
+
ret = super().get_non_inference_attributes()
|
| 53 |
+
# Add the two (APPO) target networks to it (NOT needed in
|
| 54 |
+
# inference-only mode).
|
| 55 |
+
ret += ["_old_encoder", "_old_pi"]
|
| 56 |
+
return ret
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (210 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/appo_torch_learner.cpython-312.pyc
ADDED
|
Binary file (8.16 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/appo_torch_rl_module.cpython-312.pyc
ADDED
|
Binary file (647 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/__pycache__/default_appo_torch_rl_module.cpython-312.pyc
ADDED
|
Binary file (718 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/appo_torch_learner.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Asynchronous Proximal Policy Optimization (APPO)
|
| 2 |
+
|
| 3 |
+
The algorithm is described in [1] (under the name of "IMPACT"):
|
| 4 |
+
|
| 5 |
+
Detailed documentation:
|
| 6 |
+
https://docs.ray.io/en/master/rllib-algorithms.html#appo
|
| 7 |
+
|
| 8 |
+
[1] IMPACT: Importance Weighted Asynchronous Architectures with Clipped Target Networks.
|
| 9 |
+
Luo et al. 2020
|
| 10 |
+
https://arxiv.org/pdf/1912.00167
|
| 11 |
+
"""
|
| 12 |
+
from typing import Dict
|
| 13 |
+
|
| 14 |
+
from ray.rllib.algorithms.appo.appo import (
|
| 15 |
+
LEARNER_RESULTS_CURR_KL_COEFF_KEY,
|
| 16 |
+
LEARNER_RESULTS_KL_KEY,
|
| 17 |
+
APPOConfig,
|
| 18 |
+
)
|
| 19 |
+
from ray.rllib.algorithms.appo.appo_learner import APPOLearner
|
| 20 |
+
from ray.rllib.algorithms.impala.torch.impala_torch_learner import IMPALATorchLearner
|
| 21 |
+
from ray.rllib.algorithms.impala.torch.vtrace_torch_v2 import (
|
| 22 |
+
make_time_major,
|
| 23 |
+
vtrace_torch,
|
| 24 |
+
)
|
| 25 |
+
from ray.rllib.core.columns import Columns
|
| 26 |
+
from ray.rllib.core.learner.learner import ENTROPY_KEY, POLICY_LOSS_KEY, VF_LOSS_KEY
|
| 27 |
+
from ray.rllib.core.rl_module.apis import (
|
| 28 |
+
TARGET_NETWORK_ACTION_DIST_INPUTS,
|
| 29 |
+
TargetNetworkAPI,
|
| 30 |
+
ValueFunctionAPI,
|
| 31 |
+
)
|
| 32 |
+
from ray.rllib.utils.annotations import override
|
| 33 |
+
from ray.rllib.utils.framework import try_import_torch
|
| 34 |
+
from ray.rllib.utils.numpy import convert_to_numpy
|
| 35 |
+
from ray.rllib.utils.typing import ModuleID, TensorType
|
| 36 |
+
|
| 37 |
+
torch, nn = try_import_torch()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class APPOTorchLearner(APPOLearner, IMPALATorchLearner):
|
| 41 |
+
"""Implements APPO loss / update logic on top of IMPALATorchLearner."""
|
| 42 |
+
|
| 43 |
+
@override(IMPALATorchLearner)
|
| 44 |
+
def compute_loss_for_module(
|
| 45 |
+
self,
|
| 46 |
+
*,
|
| 47 |
+
module_id: ModuleID,
|
| 48 |
+
config: APPOConfig,
|
| 49 |
+
batch: Dict,
|
| 50 |
+
fwd_out: Dict[str, TensorType],
|
| 51 |
+
) -> TensorType:
|
| 52 |
+
module = self.module[module_id].unwrapped()
|
| 53 |
+
assert isinstance(module, TargetNetworkAPI)
|
| 54 |
+
assert isinstance(module, ValueFunctionAPI)
|
| 55 |
+
|
| 56 |
+
# TODO (sven): Now that we do the +1ts trick to be less vulnerable about
|
| 57 |
+
# bootstrap values at the end of rollouts in the new stack, we might make
|
| 58 |
+
# this a more flexible, configurable parameter for users, e.g.
|
| 59 |
+
# `v_trace_seq_len` (independent of `rollout_fragment_length`). Separation
|
| 60 |
+
# of concerns (sampling vs learning).
|
| 61 |
+
rollout_frag_or_episode_len = config.get_rollout_fragment_length()
|
| 62 |
+
recurrent_seq_len = batch.get("seq_lens")
|
| 63 |
+
|
| 64 |
+
loss_mask = batch[Columns.LOSS_MASK].float()
|
| 65 |
+
loss_mask_time_major = make_time_major(
|
| 66 |
+
loss_mask,
|
| 67 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 68 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 69 |
+
)
|
| 70 |
+
size_loss_mask = torch.sum(loss_mask)
|
| 71 |
+
|
| 72 |
+
values = module.compute_values(
|
| 73 |
+
batch, embeddings=fwd_out.get(Columns.EMBEDDINGS)
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
action_dist_cls_train = module.get_train_action_dist_cls()
|
| 77 |
+
target_policy_dist = action_dist_cls_train.from_logits(
|
| 78 |
+
fwd_out[Columns.ACTION_DIST_INPUTS]
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
old_target_policy_dist = action_dist_cls_train.from_logits(
|
| 82 |
+
module.forward_target(batch)[TARGET_NETWORK_ACTION_DIST_INPUTS]
|
| 83 |
+
)
|
| 84 |
+
old_target_policy_actions_logp = old_target_policy_dist.logp(
|
| 85 |
+
batch[Columns.ACTIONS]
|
| 86 |
+
)
|
| 87 |
+
behaviour_actions_logp = batch[Columns.ACTION_LOGP]
|
| 88 |
+
target_actions_logp = target_policy_dist.logp(batch[Columns.ACTIONS])
|
| 89 |
+
|
| 90 |
+
behaviour_actions_logp_time_major = make_time_major(
|
| 91 |
+
behaviour_actions_logp,
|
| 92 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 93 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 94 |
+
)
|
| 95 |
+
target_actions_logp_time_major = make_time_major(
|
| 96 |
+
target_actions_logp,
|
| 97 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 98 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 99 |
+
)
|
| 100 |
+
old_actions_logp_time_major = make_time_major(
|
| 101 |
+
old_target_policy_actions_logp,
|
| 102 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 103 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 104 |
+
)
|
| 105 |
+
rewards_time_major = make_time_major(
|
| 106 |
+
batch[Columns.REWARDS],
|
| 107 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 108 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 109 |
+
)
|
| 110 |
+
values_time_major = make_time_major(
|
| 111 |
+
values,
|
| 112 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 113 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 114 |
+
)
|
| 115 |
+
assert Columns.VALUES_BOOTSTRAPPED not in batch
|
| 116 |
+
# Use as bootstrap values the vf-preds in the next "batch row", except
|
| 117 |
+
# for the very last row (which doesn't have a next row), for which the
|
| 118 |
+
# bootstrap value does not matter b/c it has a +1ts value at its end
|
| 119 |
+
# anyways. So we chose an arbitrary item (for simplicity of not having to
|
| 120 |
+
# move new data to the device).
|
| 121 |
+
bootstrap_values = torch.cat(
|
| 122 |
+
[
|
| 123 |
+
values_time_major[0][1:], # 0th ts values from "next row"
|
| 124 |
+
values_time_major[0][0:1], # <- can use any arbitrary value here
|
| 125 |
+
],
|
| 126 |
+
dim=0,
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
# The discount factor that is used should be gamma except for timesteps where
|
| 130 |
+
# the episode is terminated. In that case, the discount factor should be 0.
|
| 131 |
+
discounts_time_major = (
|
| 132 |
+
1.0
|
| 133 |
+
- make_time_major(
|
| 134 |
+
batch[Columns.TERMINATEDS],
|
| 135 |
+
trajectory_len=rollout_frag_or_episode_len,
|
| 136 |
+
recurrent_seq_len=recurrent_seq_len,
|
| 137 |
+
).float()
|
| 138 |
+
) * config.gamma
|
| 139 |
+
|
| 140 |
+
# Note that vtrace will compute the main loop on the CPU for better performance.
|
| 141 |
+
vtrace_adjusted_target_values, pg_advantages = vtrace_torch(
|
| 142 |
+
target_action_log_probs=old_actions_logp_time_major,
|
| 143 |
+
behaviour_action_log_probs=behaviour_actions_logp_time_major,
|
| 144 |
+
discounts=discounts_time_major,
|
| 145 |
+
rewards=rewards_time_major,
|
| 146 |
+
values=values_time_major,
|
| 147 |
+
bootstrap_values=bootstrap_values,
|
| 148 |
+
clip_pg_rho_threshold=config.vtrace_clip_pg_rho_threshold,
|
| 149 |
+
clip_rho_threshold=config.vtrace_clip_rho_threshold,
|
| 150 |
+
)
|
| 151 |
+
pg_advantages = pg_advantages * loss_mask_time_major
|
| 152 |
+
|
| 153 |
+
# The policy gradients loss.
|
| 154 |
+
is_ratio = torch.clip(
|
| 155 |
+
torch.exp(behaviour_actions_logp_time_major - old_actions_logp_time_major),
|
| 156 |
+
0.0,
|
| 157 |
+
2.0,
|
| 158 |
+
)
|
| 159 |
+
logp_ratio = is_ratio * torch.exp(
|
| 160 |
+
target_actions_logp_time_major - behaviour_actions_logp_time_major
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
surrogate_loss = torch.minimum(
|
| 164 |
+
pg_advantages * logp_ratio,
|
| 165 |
+
pg_advantages
|
| 166 |
+
* torch.clip(logp_ratio, 1 - config.clip_param, 1 + config.clip_param),
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
if config.use_kl_loss:
|
| 170 |
+
action_kl = old_target_policy_dist.kl(target_policy_dist) * loss_mask
|
| 171 |
+
mean_kl_loss = torch.sum(action_kl) / size_loss_mask
|
| 172 |
+
else:
|
| 173 |
+
mean_kl_loss = 0.0
|
| 174 |
+
mean_pi_loss = -(torch.sum(surrogate_loss) / size_loss_mask)
|
| 175 |
+
|
| 176 |
+
# The baseline loss.
|
| 177 |
+
delta = values_time_major - vtrace_adjusted_target_values
|
| 178 |
+
vf_loss = 0.5 * torch.sum(torch.pow(delta, 2.0) * loss_mask_time_major)
|
| 179 |
+
mean_vf_loss = vf_loss / size_loss_mask
|
| 180 |
+
|
| 181 |
+
# The entropy loss.
|
| 182 |
+
mean_entropy_loss = (
|
| 183 |
+
-torch.sum(target_policy_dist.entropy() * loss_mask) / size_loss_mask
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
# The summed weighted loss.
|
| 187 |
+
total_loss = (
|
| 188 |
+
mean_pi_loss
|
| 189 |
+
+ (mean_vf_loss * config.vf_loss_coeff)
|
| 190 |
+
+ (
|
| 191 |
+
mean_entropy_loss
|
| 192 |
+
* self.entropy_coeff_schedulers_per_module[
|
| 193 |
+
module_id
|
| 194 |
+
].get_current_value()
|
| 195 |
+
)
|
| 196 |
+
+ (mean_kl_loss * self.curr_kl_coeffs_per_module[module_id])
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# Log important loss stats.
|
| 200 |
+
self.metrics.log_dict(
|
| 201 |
+
{
|
| 202 |
+
POLICY_LOSS_KEY: mean_pi_loss,
|
| 203 |
+
VF_LOSS_KEY: mean_vf_loss,
|
| 204 |
+
ENTROPY_KEY: -mean_entropy_loss,
|
| 205 |
+
LEARNER_RESULTS_KL_KEY: mean_kl_loss,
|
| 206 |
+
LEARNER_RESULTS_CURR_KL_COEFF_KEY: (
|
| 207 |
+
self.curr_kl_coeffs_per_module[module_id]
|
| 208 |
+
),
|
| 209 |
+
},
|
| 210 |
+
key=module_id,
|
| 211 |
+
window=1, # <- single items (should not be mean/ema-reduced over time).
|
| 212 |
+
)
|
| 213 |
+
# Return the total loss.
|
| 214 |
+
return total_loss
|
| 215 |
+
|
| 216 |
+
@override(APPOLearner)
|
| 217 |
+
def _update_module_kl_coeff(self, module_id: ModuleID, config: APPOConfig) -> None:
|
| 218 |
+
# Update the current KL value based on the recently measured value.
|
| 219 |
+
# Increase.
|
| 220 |
+
kl = convert_to_numpy(self.metrics.peek((module_id, LEARNER_RESULTS_KL_KEY)))
|
| 221 |
+
kl_coeff_var = self.curr_kl_coeffs_per_module[module_id]
|
| 222 |
+
|
| 223 |
+
if kl > 2.0 * config.kl_target:
|
| 224 |
+
# TODO (Kourosh) why not *2.0?
|
| 225 |
+
kl_coeff_var.data *= 1.5
|
| 226 |
+
# Decrease.
|
| 227 |
+
elif kl < 0.5 * config.kl_target:
|
| 228 |
+
kl_coeff_var.data *= 0.5
|
| 229 |
+
|
| 230 |
+
self.metrics.log_value(
|
| 231 |
+
(module_id, LEARNER_RESULTS_CURR_KL_COEFF_KEY),
|
| 232 |
+
kl_coeff_var.item(),
|
| 233 |
+
window=1,
|
| 234 |
+
)
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/appo_torch_rl_module.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Backward compat import.
|
| 2 |
+
from ray.rllib.algorithms.appo.torch.default_appo_torch_rl_module import ( # noqa
|
| 3 |
+
DefaultAPPOTorchRLModule as APPOTorchRLModule,
|
| 4 |
+
)
|
| 5 |
+
from ray._common.deprecation import deprecation_warning
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
deprecation_warning(
|
| 9 |
+
old="ray.rllib.algorithms.appo.torch.appo_torch_rl_module.APPOTorchRLModule",
|
| 10 |
+
new="ray.rllib.algorithms.appo.torch.default_appo_torch_rl_module."
|
| 11 |
+
"DefaultAPPOTorchRLModule",
|
| 12 |
+
error=False,
|
| 13 |
+
)
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/torch/default_appo_torch_rl_module.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ray.rllib.algorithms.appo.default_appo_rl_module import DefaultAPPORLModule
|
| 2 |
+
from ray.rllib.algorithms.ppo.torch.default_ppo_torch_rl_module import (
|
| 3 |
+
DefaultPPOTorchRLModule,
|
| 4 |
+
)
|
| 5 |
+
from ray.util.annotations import DeveloperAPI
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@DeveloperAPI
|
| 9 |
+
class DefaultAPPOTorchRLModule(DefaultPPOTorchRLModule, DefaultAPPORLModule):
|
| 10 |
+
pass
|
lib/python3.12/site-packages/ray/rllib/algorithms/appo/utils.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
[1] IMPACT: Importance Weighted Asynchronous Architectures with Clipped Target Networks.
|
| 3 |
+
Luo et al. 2020
|
| 4 |
+
https://arxiv.org/pdf/1912.00167
|
| 5 |
+
"""
|
| 6 |
+
import threading
|
| 7 |
+
import time
|
| 8 |
+
from collections import deque
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
|
| 12 |
+
from ray.rllib.models.catalog import ModelCatalog
|
| 13 |
+
from ray.rllib.models.modelv2 import ModelV2
|
| 14 |
+
from ray.rllib.utils.annotations import OldAPIStack
|
| 15 |
+
from ray.rllib.utils.metrics.ray_metrics import (
|
| 16 |
+
DEFAULT_HISTOGRAM_BOUNDARIES_SHORT_EVENTS,
|
| 17 |
+
TimerAndPrometheusLogger,
|
| 18 |
+
)
|
| 19 |
+
from ray.util.metrics import Counter, Histogram
|
| 20 |
+
|
| 21 |
+
POLICY_SCOPE = "func"
|
| 22 |
+
TARGET_POLICY_SCOPE = "target_func"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class CircularBuffer:
|
| 26 |
+
"""A circular batch-wise buffer as described in [1] for APPO.
|
| 27 |
+
|
| 28 |
+
The buffer holds at most N batches, which are sampled at random (uniformly).
|
| 29 |
+
If full and a new batch is added, the oldest batch is discarded. Also, each batch
|
| 30 |
+
currently in the buffer can be sampled at most K times (after which it is also
|
| 31 |
+
discarded).
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self, num_batches: int, iterations_per_batch: int):
|
| 35 |
+
# N from the paper (buffer size).
|
| 36 |
+
self.num_batches = num_batches
|
| 37 |
+
# K ("replay coefficient") from the paper.
|
| 38 |
+
self.iterations_per_batch = iterations_per_batch
|
| 39 |
+
|
| 40 |
+
self._NxK = self.num_batches * self.iterations_per_batch
|
| 41 |
+
self._num_added = 0
|
| 42 |
+
|
| 43 |
+
self._buffer = deque([None for _ in range(self._NxK)], maxlen=self._NxK)
|
| 44 |
+
self._indices = set()
|
| 45 |
+
self._offset = self._NxK
|
| 46 |
+
self._lock = threading.Lock()
|
| 47 |
+
|
| 48 |
+
self._rng = np.random.default_rng()
|
| 49 |
+
|
| 50 |
+
# Ray metrics
|
| 51 |
+
self._metrics_circular_buffer_add_time = Histogram(
|
| 52 |
+
name="rllib_utils_circular_buffer_add_time",
|
| 53 |
+
description="Time spent in CircularBuffer.add()",
|
| 54 |
+
boundaries=DEFAULT_HISTOGRAM_BOUNDARIES_SHORT_EVENTS,
|
| 55 |
+
tag_keys=("rllib",),
|
| 56 |
+
)
|
| 57 |
+
self._metrics_circular_buffer_add_time.set_default_tags(
|
| 58 |
+
{"rllib": self.__class__.__name__}
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
self._metrics_circular_buffer_add_ts_dropped = Counter(
|
| 62 |
+
name="rllib_utils_circular_buffer_add_ts_dropped_counter",
|
| 63 |
+
description="Total number of env steps dropped by the CircularBuffer.",
|
| 64 |
+
tag_keys=("rllib",),
|
| 65 |
+
)
|
| 66 |
+
self._metrics_circular_buffer_add_ts_dropped.set_default_tags(
|
| 67 |
+
{"rllib": self.__class__.__name__}
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
self._metrics_circular_buffer_sample_time = Histogram(
|
| 71 |
+
name="rllib_utils_circular_buffer_sample_time",
|
| 72 |
+
description="Time spent in CircularBuffer.sample()",
|
| 73 |
+
boundaries=DEFAULT_HISTOGRAM_BOUNDARIES_SHORT_EVENTS,
|
| 74 |
+
tag_keys=("rllib",),
|
| 75 |
+
)
|
| 76 |
+
self._metrics_circular_buffer_sample_time.set_default_tags(
|
| 77 |
+
{"rllib": self.__class__.__name__}
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
def add(self, batch):
|
| 81 |
+
# Add buffer and k=0 information to the deque.
|
| 82 |
+
with TimerAndPrometheusLogger(self._metrics_circular_buffer_add_time):
|
| 83 |
+
with self._lock:
|
| 84 |
+
dropped_entry = self._buffer[0]
|
| 85 |
+
for _ in range(self.iterations_per_batch):
|
| 86 |
+
self._buffer.append(batch)
|
| 87 |
+
self._indices.add(self._offset)
|
| 88 |
+
self._indices.discard(self._offset - self._NxK)
|
| 89 |
+
self._offset += 1
|
| 90 |
+
self._num_added += 1
|
| 91 |
+
|
| 92 |
+
# A valid entry (w/ a batch whose k has not been reach K yet) was dropped.
|
| 93 |
+
dropped_ts = 0
|
| 94 |
+
if dropped_entry is not None:
|
| 95 |
+
dropped_ts = dropped_entry.env_steps()
|
| 96 |
+
if dropped_ts > 0:
|
| 97 |
+
self._metrics_circular_buffer_add_ts_dropped.inc(value=dropped_ts)
|
| 98 |
+
|
| 99 |
+
return dropped_ts
|
| 100 |
+
|
| 101 |
+
def sample(self):
|
| 102 |
+
# Only initially, the buffer may be empty -> Just wait for some time.
|
| 103 |
+
with TimerAndPrometheusLogger(self._metrics_circular_buffer_sample_time):
|
| 104 |
+
while len(self) == 0:
|
| 105 |
+
time.sleep(0.0001)
|
| 106 |
+
|
| 107 |
+
# Sample a random buffer index.
|
| 108 |
+
with self._lock:
|
| 109 |
+
idx = self._rng.choice(list(self._indices))
|
| 110 |
+
actual_buffer_idx = idx - self._offset + self._NxK
|
| 111 |
+
batch = self._buffer[actual_buffer_idx]
|
| 112 |
+
assert batch is not None, (
|
| 113 |
+
idx,
|
| 114 |
+
actual_buffer_idx,
|
| 115 |
+
self._offset,
|
| 116 |
+
self._indices,
|
| 117 |
+
[b is None for b in self._buffer],
|
| 118 |
+
)
|
| 119 |
+
self._buffer[actual_buffer_idx] = None
|
| 120 |
+
self._indices.discard(idx)
|
| 121 |
+
|
| 122 |
+
# Return the sampled batch.
|
| 123 |
+
return batch
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def filled(self):
|
| 127 |
+
"""Whether the buffer has been filled once with at least `self.num_batches`."""
|
| 128 |
+
with self._lock:
|
| 129 |
+
return self._num_added >= self.num_batches
|
| 130 |
+
|
| 131 |
+
def __len__(self) -> int:
|
| 132 |
+
"""Returns the number of actually valid (non-expired) batches in the buffer."""
|
| 133 |
+
with self._lock:
|
| 134 |
+
return len(self._indices)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
@OldAPIStack
|
| 138 |
+
def make_appo_models(policy) -> ModelV2:
|
| 139 |
+
"""Builds model and target model for APPO.
|
| 140 |
+
|
| 141 |
+
Returns:
|
| 142 |
+
ModelV2: The Model for the Policy to use.
|
| 143 |
+
Note: The target model will not be returned, just assigned to
|
| 144 |
+
`policy.target_model`.
|
| 145 |
+
"""
|
| 146 |
+
# Get the num_outputs for the following model construction calls.
|
| 147 |
+
_, logit_dim = ModelCatalog.get_action_dist(
|
| 148 |
+
policy.action_space, policy.config["model"]
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Construct the (main) model.
|
| 152 |
+
policy.model = ModelCatalog.get_model_v2(
|
| 153 |
+
policy.observation_space,
|
| 154 |
+
policy.action_space,
|
| 155 |
+
logit_dim,
|
| 156 |
+
policy.config["model"],
|
| 157 |
+
name=POLICY_SCOPE,
|
| 158 |
+
framework=policy.framework,
|
| 159 |
+
)
|
| 160 |
+
policy.model_variables = policy.model.variables()
|
| 161 |
+
|
| 162 |
+
# Construct the target model.
|
| 163 |
+
policy.target_model = ModelCatalog.get_model_v2(
|
| 164 |
+
policy.observation_space,
|
| 165 |
+
policy.action_space,
|
| 166 |
+
logit_dim,
|
| 167 |
+
policy.config["model"],
|
| 168 |
+
name=TARGET_POLICY_SCOPE,
|
| 169 |
+
framework=policy.framework,
|
| 170 |
+
)
|
| 171 |
+
policy.target_model_variables = policy.target_model.variables()
|
| 172 |
+
|
| 173 |
+
# Return only the model (not the target model).
|
| 174 |
+
return policy.model
|
lib/python3.12/site-packages/ray/rllib/algorithms/callbacks.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @OldAPIStack
|
| 2 |
+
from ray.rllib.callbacks.callbacks import RLlibCallback
|
| 3 |
+
from ray.rllib.callbacks.utils import _make_multi_callbacks
|
| 4 |
+
|
| 5 |
+
# Backward compatibility
|
| 6 |
+
DefaultCallbacks = RLlibCallback
|
| 7 |
+
make_multi_callbacks = _make_multi_callbacks
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ray.rllib.algorithms.marwil.marwil import (
|
| 2 |
+
MARWIL,
|
| 3 |
+
MARWILConfig,
|
| 4 |
+
)
|
| 5 |
+
from ray.rllib.algorithms.marwil.marwil_tf_policy import (
|
| 6 |
+
MARWILTF1Policy,
|
| 7 |
+
MARWILTF2Policy,
|
| 8 |
+
)
|
| 9 |
+
from ray.rllib.algorithms.marwil.marwil_torch_policy import MARWILTorchPolicy
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
"MARWIL",
|
| 13 |
+
"MARWILConfig",
|
| 14 |
+
# @OldAPIStack
|
| 15 |
+
"MARWILTF1Policy",
|
| 16 |
+
"MARWILTF2Policy",
|
| 17 |
+
"MARWILTorchPolicy",
|
| 18 |
+
]
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (556 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil.cpython-312.pyc
ADDED
|
Binary file (19.2 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil_learner.cpython-312.pyc
ADDED
|
Binary file (2.51 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil_tf_policy.cpython-312.pyc
ADDED
|
Binary file (9.55 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/__pycache__/marwil_torch_policy.cpython-312.pyc
ADDED
|
Binary file (6.21 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil.py
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Callable, Optional, Type, Union
|
| 2 |
+
|
| 3 |
+
from typing_extensions import Self
|
| 4 |
+
|
| 5 |
+
from ray._common.deprecation import deprecation_warning
|
| 6 |
+
from ray.rllib.algorithms.algorithm import Algorithm
|
| 7 |
+
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig, NotProvided
|
| 8 |
+
from ray.rllib.connectors.learner import (
|
| 9 |
+
AddNextObservationsFromEpisodesToTrainBatch,
|
| 10 |
+
AddObservationsFromEpisodesToBatch,
|
| 11 |
+
AddOneTsToEpisodesAndTruncate,
|
| 12 |
+
GeneralAdvantageEstimation,
|
| 13 |
+
)
|
| 14 |
+
from ray.rllib.core.learner.learner import Learner
|
| 15 |
+
from ray.rllib.core.learner.training_data import TrainingData
|
| 16 |
+
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
|
| 17 |
+
from ray.rllib.execution.rollout_ops import (
|
| 18 |
+
synchronous_parallel_sample,
|
| 19 |
+
)
|
| 20 |
+
from ray.rllib.execution.train_ops import (
|
| 21 |
+
multi_gpu_train_one_step,
|
| 22 |
+
train_one_step,
|
| 23 |
+
)
|
| 24 |
+
from ray.rllib.policy.policy import Policy
|
| 25 |
+
from ray.rllib.utils.annotations import OldAPIStack, override
|
| 26 |
+
from ray.rllib.utils.metrics import (
|
| 27 |
+
LEARNER_RESULTS,
|
| 28 |
+
LEARNER_UPDATE_TIMER,
|
| 29 |
+
NUM_AGENT_STEPS_SAMPLED,
|
| 30 |
+
NUM_ENV_STEPS_SAMPLED,
|
| 31 |
+
OFFLINE_SAMPLING_TIMER,
|
| 32 |
+
SAMPLE_TIMER,
|
| 33 |
+
SYNCH_WORKER_WEIGHTS_TIMER,
|
| 34 |
+
TIMERS,
|
| 35 |
+
)
|
| 36 |
+
from ray.rllib.utils.typing import (
|
| 37 |
+
EnvType,
|
| 38 |
+
ResultDict,
|
| 39 |
+
RLModuleSpecType,
|
| 40 |
+
)
|
| 41 |
+
from ray.tune.logger import Logger
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class MARWILConfig(AlgorithmConfig):
|
| 45 |
+
"""Defines a configuration class from which a MARWIL Algorithm can be built.
|
| 46 |
+
|
| 47 |
+
.. testcode::
|
| 48 |
+
|
| 49 |
+
import gymnasium as gym
|
| 50 |
+
import numpy as np
|
| 51 |
+
|
| 52 |
+
from pathlib import Path
|
| 53 |
+
from ray.rllib.algorithms.marwil import MARWILConfig
|
| 54 |
+
|
| 55 |
+
# Get the base path (to ray/rllib)
|
| 56 |
+
base_path = Path(__file__).parents[2]
|
| 57 |
+
# Get the path to the data in rllib folder.
|
| 58 |
+
data_path = base_path / "tests/data/cartpole/cartpole-v1_large"
|
| 59 |
+
|
| 60 |
+
config = MARWILConfig()
|
| 61 |
+
# Enable the new API stack.
|
| 62 |
+
config.api_stack(
|
| 63 |
+
enable_rl_module_and_learner=True,
|
| 64 |
+
enable_env_runner_and_connector_v2=True,
|
| 65 |
+
)
|
| 66 |
+
# Define the environment for which to learn a policy
|
| 67 |
+
# from offline data.
|
| 68 |
+
config.environment(
|
| 69 |
+
observation_space=gym.spaces.Box(
|
| 70 |
+
np.array([-4.8, -np.inf, -0.41887903, -np.inf]),
|
| 71 |
+
np.array([4.8, np.inf, 0.41887903, np.inf]),
|
| 72 |
+
shape=(4,),
|
| 73 |
+
dtype=np.float32,
|
| 74 |
+
),
|
| 75 |
+
action_space=gym.spaces.Discrete(2),
|
| 76 |
+
)
|
| 77 |
+
# Set the training parameters.
|
| 78 |
+
config.training(
|
| 79 |
+
beta=1.0,
|
| 80 |
+
lr=1e-5,
|
| 81 |
+
gamma=0.99,
|
| 82 |
+
# We must define a train batch size for each
|
| 83 |
+
# learner (here 1 local learner).
|
| 84 |
+
train_batch_size_per_learner=2000,
|
| 85 |
+
)
|
| 86 |
+
# Define the data source for offline data.
|
| 87 |
+
config.offline_data(
|
| 88 |
+
input_=[data_path.as_posix()],
|
| 89 |
+
# Run exactly one update per training iteration.
|
| 90 |
+
dataset_num_iters_per_learner=1,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# Build an `Algorithm` object from the config and run 1 training
|
| 94 |
+
# iteration.
|
| 95 |
+
algo = config.build()
|
| 96 |
+
algo.train()
|
| 97 |
+
|
| 98 |
+
.. testcode::
|
| 99 |
+
|
| 100 |
+
import gymnasium as gym
|
| 101 |
+
import numpy as np
|
| 102 |
+
|
| 103 |
+
from pathlib import Path
|
| 104 |
+
from ray.rllib.algorithms.marwil import MARWILConfig
|
| 105 |
+
from ray import tune
|
| 106 |
+
|
| 107 |
+
# Get the base path (to ray/rllib)
|
| 108 |
+
base_path = Path(__file__).parents[2]
|
| 109 |
+
# Get the path to the data in rllib folder.
|
| 110 |
+
data_path = base_path / "tests/data/cartpole/cartpole-v1_large"
|
| 111 |
+
|
| 112 |
+
config = MARWILConfig()
|
| 113 |
+
# Enable the new API stack.
|
| 114 |
+
config.api_stack(
|
| 115 |
+
enable_rl_module_and_learner=True,
|
| 116 |
+
enable_env_runner_and_connector_v2=True,
|
| 117 |
+
)
|
| 118 |
+
# Print out some default values
|
| 119 |
+
print(f"beta: {config.beta}")
|
| 120 |
+
# Update the config object.
|
| 121 |
+
config.training(
|
| 122 |
+
lr=tune.grid_search([1e-3, 1e-4]),
|
| 123 |
+
beta=0.75,
|
| 124 |
+
# We must define a train batch size for each
|
| 125 |
+
# learner (here 1 local learner).
|
| 126 |
+
train_batch_size_per_learner=2000,
|
| 127 |
+
)
|
| 128 |
+
# Set the config's data path.
|
| 129 |
+
config.offline_data(
|
| 130 |
+
input_=[data_path.as_posix()],
|
| 131 |
+
# Set the number of updates to be run per learner
|
| 132 |
+
# per training step.
|
| 133 |
+
dataset_num_iters_per_learner=1,
|
| 134 |
+
)
|
| 135 |
+
# Set the config's environment for evalaution.
|
| 136 |
+
config.environment(
|
| 137 |
+
observation_space=gym.spaces.Box(
|
| 138 |
+
np.array([-4.8, -np.inf, -0.41887903, -np.inf]),
|
| 139 |
+
np.array([4.8, np.inf, 0.41887903, np.inf]),
|
| 140 |
+
shape=(4,),
|
| 141 |
+
dtype=np.float32,
|
| 142 |
+
),
|
| 143 |
+
action_space=gym.spaces.Discrete(2),
|
| 144 |
+
)
|
| 145 |
+
# Set up a tuner to run the experiment.
|
| 146 |
+
tuner = tune.Tuner(
|
| 147 |
+
"MARWIL",
|
| 148 |
+
param_space=config,
|
| 149 |
+
run_config=tune.RunConfig(
|
| 150 |
+
stop={"training_iteration": 1},
|
| 151 |
+
),
|
| 152 |
+
)
|
| 153 |
+
# Run the experiment.
|
| 154 |
+
tuner.fit()
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
def __init__(self, algo_class=None):
|
| 158 |
+
"""Initializes a MARWILConfig instance."""
|
| 159 |
+
self.exploration_config = {
|
| 160 |
+
# The Exploration class to use. In the simplest case, this is the name
|
| 161 |
+
# (str) of any class present in the `rllib.utils.exploration` package.
|
| 162 |
+
# You can also provide the python class directly or the full location
|
| 163 |
+
# of your class (e.g. "ray.rllib.utils.exploration.epsilon_greedy.
|
| 164 |
+
# EpsilonGreedy").
|
| 165 |
+
"type": "StochasticSampling",
|
| 166 |
+
# Add constructor kwargs here (if any).
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
super().__init__(algo_class=algo_class or MARWIL)
|
| 170 |
+
self._is_online = False
|
| 171 |
+
# fmt: off
|
| 172 |
+
# __sphinx_doc_begin__
|
| 173 |
+
# MARWIL specific settings:
|
| 174 |
+
self.beta = 1.0
|
| 175 |
+
self.bc_logstd_coeff = 0.0
|
| 176 |
+
self.moving_average_sqd_adv_norm_update_rate = 1e-8
|
| 177 |
+
self.moving_average_sqd_adv_norm_start = 100.0
|
| 178 |
+
self.vf_coeff = 1.0
|
| 179 |
+
self.model["vf_share_layers"] = False
|
| 180 |
+
self.grad_clip = None
|
| 181 |
+
|
| 182 |
+
# Override some of AlgorithmConfig's default values with MARWIL-specific values.
|
| 183 |
+
|
| 184 |
+
# You should override input_ to point to an offline dataset
|
| 185 |
+
# (see algorithm.py and algorithm_config.py).
|
| 186 |
+
# The dataset may have an arbitrary number of timesteps
|
| 187 |
+
# (and even episodes) per line.
|
| 188 |
+
# However, each line must only contain consecutive timesteps in
|
| 189 |
+
# order for MARWIL to be able to calculate accumulated
|
| 190 |
+
# discounted returns. It is ok, though, to have multiple episodes in
|
| 191 |
+
# the same line.
|
| 192 |
+
self.input_ = "sampler"
|
| 193 |
+
self.postprocess_inputs = True
|
| 194 |
+
self.lr = 1e-4
|
| 195 |
+
self.lambda_ = 1.0
|
| 196 |
+
self.train_batch_size = 2000
|
| 197 |
+
|
| 198 |
+
# Materialize only the data in raw format, but not the mapped data b/c
|
| 199 |
+
# MARWIL uses a connector to calculate values and therefore the module
|
| 200 |
+
# needs to be updated frequently. This updating would not work if we
|
| 201 |
+
# map the data once at the beginning.
|
| 202 |
+
# TODO (simon, sven): The module is only updated when the OfflinePreLearner
|
| 203 |
+
# gets reinitiated, i.e. when the iterator gets reinitiated. This happens
|
| 204 |
+
# frequently enough with a small dataset, but with a big one this does not
|
| 205 |
+
# update often enough. We might need to put model weigths every couple of
|
| 206 |
+
# iterations into the object storage (maybe also connector states).
|
| 207 |
+
self.materialize_data = True
|
| 208 |
+
self.materialize_mapped_data = False
|
| 209 |
+
# __sphinx_doc_end__
|
| 210 |
+
# fmt: on
|
| 211 |
+
self._set_off_policy_estimation_methods = False
|
| 212 |
+
|
| 213 |
+
@override(AlgorithmConfig)
|
| 214 |
+
def training(
|
| 215 |
+
self,
|
| 216 |
+
*,
|
| 217 |
+
beta: Optional[float] = NotProvided,
|
| 218 |
+
bc_logstd_coeff: Optional[float] = NotProvided,
|
| 219 |
+
moving_average_sqd_adv_norm_update_rate: Optional[float] = NotProvided,
|
| 220 |
+
moving_average_sqd_adv_norm_start: Optional[float] = NotProvided,
|
| 221 |
+
vf_coeff: Optional[float] = NotProvided,
|
| 222 |
+
grad_clip: Optional[float] = NotProvided,
|
| 223 |
+
**kwargs,
|
| 224 |
+
) -> Self:
|
| 225 |
+
"""Sets the training related configuration.
|
| 226 |
+
|
| 227 |
+
Args:
|
| 228 |
+
beta: Scaling of advantages in exponential terms. When beta is 0.0,
|
| 229 |
+
MARWIL is reduced to behavior cloning (imitation learning);
|
| 230 |
+
see bc.py algorithm in this same directory.
|
| 231 |
+
bc_logstd_coeff: A coefficient to encourage higher action distribution
|
| 232 |
+
entropy for exploration.
|
| 233 |
+
moving_average_sqd_adv_norm_update_rate: The rate for updating the
|
| 234 |
+
squared moving average advantage norm (c^2). A higher rate leads
|
| 235 |
+
to faster updates of this moving avergage.
|
| 236 |
+
moving_average_sqd_adv_norm_start: Starting value for the
|
| 237 |
+
squared moving average advantage norm (c^2).
|
| 238 |
+
vf_coeff: Balancing value estimation loss and policy optimization loss.
|
| 239 |
+
grad_clip: If specified, clip the global norm of gradients by this amount.
|
| 240 |
+
|
| 241 |
+
Returns:
|
| 242 |
+
This updated AlgorithmConfig object.
|
| 243 |
+
"""
|
| 244 |
+
# Pass kwargs onto super's `training()` method.
|
| 245 |
+
super().training(**kwargs)
|
| 246 |
+
if beta is not NotProvided:
|
| 247 |
+
self.beta = beta
|
| 248 |
+
if bc_logstd_coeff is not NotProvided:
|
| 249 |
+
self.bc_logstd_coeff = bc_logstd_coeff
|
| 250 |
+
if moving_average_sqd_adv_norm_update_rate is not NotProvided:
|
| 251 |
+
self.moving_average_sqd_adv_norm_update_rate = (
|
| 252 |
+
moving_average_sqd_adv_norm_update_rate
|
| 253 |
+
)
|
| 254 |
+
if moving_average_sqd_adv_norm_start is not NotProvided:
|
| 255 |
+
self.moving_average_sqd_adv_norm_start = moving_average_sqd_adv_norm_start
|
| 256 |
+
if vf_coeff is not NotProvided:
|
| 257 |
+
self.vf_coeff = vf_coeff
|
| 258 |
+
if grad_clip is not NotProvided:
|
| 259 |
+
self.grad_clip = grad_clip
|
| 260 |
+
return self
|
| 261 |
+
|
| 262 |
+
@override(AlgorithmConfig)
|
| 263 |
+
def get_default_rl_module_spec(self) -> RLModuleSpecType:
|
| 264 |
+
if self.framework_str == "torch":
|
| 265 |
+
from ray.rllib.algorithms.ppo.torch.default_ppo_torch_rl_module import (
|
| 266 |
+
DefaultPPOTorchRLModule,
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
return RLModuleSpec(module_class=DefaultPPOTorchRLModule)
|
| 270 |
+
else:
|
| 271 |
+
raise ValueError(
|
| 272 |
+
f"The framework {self.framework_str} is not supported. "
|
| 273 |
+
"Use 'torch' instead."
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
@override(AlgorithmConfig)
|
| 277 |
+
def get_default_learner_class(self) -> Union[Type["Learner"], str]:
|
| 278 |
+
if self.framework_str == "torch":
|
| 279 |
+
from ray.rllib.algorithms.marwil.torch.marwil_torch_learner import (
|
| 280 |
+
MARWILTorchLearner,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
return MARWILTorchLearner
|
| 284 |
+
else:
|
| 285 |
+
raise ValueError(
|
| 286 |
+
f"The framework {self.framework_str} is not supported. "
|
| 287 |
+
"Use 'torch' instead."
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
@override(AlgorithmConfig)
|
| 291 |
+
def evaluation(
|
| 292 |
+
self,
|
| 293 |
+
**kwargs,
|
| 294 |
+
) -> Self:
|
| 295 |
+
"""Sets the evaluation related configuration.
|
| 296 |
+
Returns:
|
| 297 |
+
This updated AlgorithmConfig object.
|
| 298 |
+
"""
|
| 299 |
+
# Pass kwargs onto super's `evaluation()` method.
|
| 300 |
+
super().evaluation(**kwargs)
|
| 301 |
+
|
| 302 |
+
if "off_policy_estimation_methods" in kwargs:
|
| 303 |
+
# User specified their OPE methods.
|
| 304 |
+
self._set_off_policy_estimation_methods = True
|
| 305 |
+
|
| 306 |
+
return self
|
| 307 |
+
|
| 308 |
+
@override(AlgorithmConfig)
|
| 309 |
+
def offline_data(self, **kwargs) -> Self:
|
| 310 |
+
|
| 311 |
+
super().offline_data(**kwargs)
|
| 312 |
+
|
| 313 |
+
# Check, if the passed in class incorporates the `OfflinePreLearner`
|
| 314 |
+
# interface.
|
| 315 |
+
if "prelearner_class" in kwargs:
|
| 316 |
+
from ray.rllib.offline.offline_data import OfflinePreLearner
|
| 317 |
+
|
| 318 |
+
if not issubclass(kwargs.get("prelearner_class"), OfflinePreLearner):
|
| 319 |
+
raise ValueError(
|
| 320 |
+
f"`prelearner_class` {kwargs.get('prelearner_class')} is not a "
|
| 321 |
+
"subclass of `OfflinePreLearner`. Any class passed to "
|
| 322 |
+
"`prelearner_class` needs to implement the interface given by "
|
| 323 |
+
"`OfflinePreLearner`."
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
return self
|
| 327 |
+
|
| 328 |
+
@override(AlgorithmConfig)
|
| 329 |
+
def build(
|
| 330 |
+
self,
|
| 331 |
+
env: Optional[Union[str, EnvType]] = None,
|
| 332 |
+
logger_creator: Optional[Callable[[], Logger]] = None,
|
| 333 |
+
) -> "Algorithm":
|
| 334 |
+
if not self._set_off_policy_estimation_methods:
|
| 335 |
+
deprecation_warning(
|
| 336 |
+
old=r"MARWIL used to have off_policy_estimation_methods "
|
| 337 |
+
"is and wis by default. This has"
|
| 338 |
+
r"changed to off_policy_estimation_methods: \{\}."
|
| 339 |
+
"If you want to use an off-policy estimator, specify it in"
|
| 340 |
+
".evaluation(off_policy_estimation_methods=...)",
|
| 341 |
+
error=False,
|
| 342 |
+
)
|
| 343 |
+
return super().build(env, logger_creator)
|
| 344 |
+
|
| 345 |
+
@override(AlgorithmConfig)
|
| 346 |
+
def build_learner_connector(
|
| 347 |
+
self,
|
| 348 |
+
input_observation_space,
|
| 349 |
+
input_action_space,
|
| 350 |
+
device=None,
|
| 351 |
+
):
|
| 352 |
+
pipeline = super().build_learner_connector(
|
| 353 |
+
input_observation_space=input_observation_space,
|
| 354 |
+
input_action_space=input_action_space,
|
| 355 |
+
device=device,
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
# Before anything, add one ts to each episode (and record this in the loss
|
| 359 |
+
# mask, so that the computations at this extra ts are not used to compute
|
| 360 |
+
# the loss).
|
| 361 |
+
pipeline.prepend(AddOneTsToEpisodesAndTruncate())
|
| 362 |
+
|
| 363 |
+
# Prepend the "add-NEXT_OBS-from-episodes-to-train-batch" connector piece (right
|
| 364 |
+
# after the corresponding "add-OBS-..." default piece).
|
| 365 |
+
pipeline.insert_after(
|
| 366 |
+
AddObservationsFromEpisodesToBatch,
|
| 367 |
+
AddNextObservationsFromEpisodesToTrainBatch(),
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
# At the end of the pipeline (when the batch is already completed), add the
|
| 371 |
+
# GAE connector, which performs a vf forward pass, then computes the GAE
|
| 372 |
+
# computations, and puts the results of this (advantages, value targets)
|
| 373 |
+
# directly back in the batch. This is then the batch used for
|
| 374 |
+
# `forward_train` and `compute_losses`.
|
| 375 |
+
pipeline.append(
|
| 376 |
+
GeneralAdvantageEstimation(gamma=self.gamma, lambda_=self.lambda_)
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
return pipeline
|
| 380 |
+
|
| 381 |
+
@override(AlgorithmConfig)
|
| 382 |
+
def validate(self) -> None:
|
| 383 |
+
# Call super's validation method.
|
| 384 |
+
super().validate()
|
| 385 |
+
|
| 386 |
+
if self.beta < 0.0 or self.beta > 1.0:
|
| 387 |
+
self._value_error("`beta` must be within 0.0 and 1.0!")
|
| 388 |
+
|
| 389 |
+
if self.postprocess_inputs is False and self.beta > 0.0:
|
| 390 |
+
self._value_error(
|
| 391 |
+
"`postprocess_inputs` must be True for MARWIL (to "
|
| 392 |
+
"calculate accum., discounted returns)! Try setting "
|
| 393 |
+
"`config.offline_data(postprocess_inputs=True)`."
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
# Assert that for a local learner the number of iterations is 1. Note,
|
| 397 |
+
# this is needed because we have no iterators, but instead a single
|
| 398 |
+
# batch returned directly from the `OfflineData.sample` method.
|
| 399 |
+
if (
|
| 400 |
+
self.num_learners == 0
|
| 401 |
+
and not self.dataset_num_iters_per_learner
|
| 402 |
+
and self.enable_rl_module_and_learner
|
| 403 |
+
):
|
| 404 |
+
self._value_error(
|
| 405 |
+
"When using a local Learner (`config.num_learners=0`), the number of "
|
| 406 |
+
"iterations per learner (`dataset_num_iters_per_learner`) has to be "
|
| 407 |
+
"defined! Set this hyperparameter through `config.offline_data("
|
| 408 |
+
"dataset_num_iters_per_learner=...)`."
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
@property
|
| 412 |
+
def _model_auto_keys(self):
|
| 413 |
+
return super()._model_auto_keys | {"beta": self.beta, "vf_share_layers": False}
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
class MARWIL(Algorithm):
|
| 417 |
+
@classmethod
|
| 418 |
+
@override(Algorithm)
|
| 419 |
+
def get_default_config(cls) -> MARWILConfig:
|
| 420 |
+
return MARWILConfig()
|
| 421 |
+
|
| 422 |
+
@classmethod
|
| 423 |
+
@override(Algorithm)
|
| 424 |
+
def get_default_policy_class(
|
| 425 |
+
cls, config: AlgorithmConfig
|
| 426 |
+
) -> Optional[Type[Policy]]:
|
| 427 |
+
if config["framework"] == "torch":
|
| 428 |
+
from ray.rllib.algorithms.marwil.marwil_torch_policy import (
|
| 429 |
+
MARWILTorchPolicy,
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
return MARWILTorchPolicy
|
| 433 |
+
elif config["framework"] == "tf":
|
| 434 |
+
from ray.rllib.algorithms.marwil.marwil_tf_policy import (
|
| 435 |
+
MARWILTF1Policy,
|
| 436 |
+
)
|
| 437 |
+
|
| 438 |
+
return MARWILTF1Policy
|
| 439 |
+
else:
|
| 440 |
+
from ray.rllib.algorithms.marwil.marwil_tf_policy import MARWILTF2Policy
|
| 441 |
+
|
| 442 |
+
return MARWILTF2Policy
|
| 443 |
+
|
| 444 |
+
@override(Algorithm)
|
| 445 |
+
def training_step(self) -> None:
|
| 446 |
+
"""Implements training logic for the new stack
|
| 447 |
+
|
| 448 |
+
Note, this includes so far training with the `OfflineData`
|
| 449 |
+
class (multi-/single-learner setup) and evaluation on
|
| 450 |
+
`EnvRunner`s. Note further, evaluation on the dataset itself
|
| 451 |
+
using estimators is not implemented, yet.
|
| 452 |
+
"""
|
| 453 |
+
# Old API stack (Policy, RolloutWorker, Connector).
|
| 454 |
+
if not self.config.enable_env_runner_and_connector_v2:
|
| 455 |
+
return self._training_step_old_api_stack()
|
| 456 |
+
|
| 457 |
+
# TODO (simon): Take care of sampler metrics: right
|
| 458 |
+
# now all rewards are `nan`, which possibly confuses
|
| 459 |
+
# the user that sth. is not right, although it is as
|
| 460 |
+
# we do not step the env.
|
| 461 |
+
with self.metrics.log_time((TIMERS, OFFLINE_SAMPLING_TIMER)):
|
| 462 |
+
# If we should use an iterator in the learner(s). Note, in case of
|
| 463 |
+
# multiple learners we must always return a list of iterators.
|
| 464 |
+
return_iterator = (
|
| 465 |
+
self.config.num_learners > 0
|
| 466 |
+
or self.config.dataset_num_iters_per_learner != 1
|
| 467 |
+
)
|
| 468 |
+
|
| 469 |
+
# Sampling from offline data.
|
| 470 |
+
batch_or_iterator = self.offline_data.sample(
|
| 471 |
+
num_samples=self.config.train_batch_size_per_learner,
|
| 472 |
+
num_shards=self.config.num_learners,
|
| 473 |
+
# Return an iterator, if a `Learner` should update
|
| 474 |
+
# multiple times per RLlib iteration.
|
| 475 |
+
return_iterator=return_iterator,
|
| 476 |
+
)
|
| 477 |
+
if return_iterator:
|
| 478 |
+
training_data = TrainingData(data_iterators=batch_or_iterator)
|
| 479 |
+
else:
|
| 480 |
+
training_data = TrainingData(batch=batch_or_iterator)
|
| 481 |
+
|
| 482 |
+
with self.metrics.log_time((TIMERS, LEARNER_UPDATE_TIMER)):
|
| 483 |
+
# Updating the policy.
|
| 484 |
+
learner_results = self.learner_group.update(
|
| 485 |
+
training_data=training_data,
|
| 486 |
+
minibatch_size=self.config.train_batch_size_per_learner,
|
| 487 |
+
num_iters=self.config.dataset_num_iters_per_learner,
|
| 488 |
+
**self.offline_data.iter_batches_kwargs,
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
# Log training results.
|
| 492 |
+
self.metrics.aggregate(learner_results, key=LEARNER_RESULTS)
|
| 493 |
+
|
| 494 |
+
@OldAPIStack
|
| 495 |
+
def _training_step_old_api_stack(self) -> ResultDict:
|
| 496 |
+
"""Implements training step for the old stack.
|
| 497 |
+
|
| 498 |
+
Note, there is no hybrid stack anymore. If you need to use `RLModule`s,
|
| 499 |
+
use the new api stack.
|
| 500 |
+
"""
|
| 501 |
+
# Collect SampleBatches from sample workers.
|
| 502 |
+
with self._timers[SAMPLE_TIMER]:
|
| 503 |
+
train_batch = synchronous_parallel_sample(worker_set=self.env_runner_group)
|
| 504 |
+
train_batch = train_batch.as_multi_agent(
|
| 505 |
+
module_id=list(self.config.policies)[0]
|
| 506 |
+
)
|
| 507 |
+
self._counters[NUM_AGENT_STEPS_SAMPLED] += train_batch.agent_steps()
|
| 508 |
+
self._counters[NUM_ENV_STEPS_SAMPLED] += train_batch.env_steps()
|
| 509 |
+
|
| 510 |
+
# Train.
|
| 511 |
+
if self.config.simple_optimizer:
|
| 512 |
+
train_results = train_one_step(self, train_batch)
|
| 513 |
+
else:
|
| 514 |
+
train_results = multi_gpu_train_one_step(self, train_batch)
|
| 515 |
+
|
| 516 |
+
# TODO: Move training steps counter update outside of `train_one_step()` method.
|
| 517 |
+
# # Update train step counters.
|
| 518 |
+
# self._counters[NUM_ENV_STEPS_TRAINED] += train_batch.env_steps()
|
| 519 |
+
# self._counters[NUM_AGENT_STEPS_TRAINED] += train_batch.agent_steps()
|
| 520 |
+
|
| 521 |
+
global_vars = {
|
| 522 |
+
"timestep": self._counters[NUM_AGENT_STEPS_SAMPLED],
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
# Update weights - after learning on the local worker - on all remote
|
| 526 |
+
# workers (only those policies that were actually trained).
|
| 527 |
+
if self.env_runner_group.num_remote_env_runners() > 0:
|
| 528 |
+
with self._timers[SYNCH_WORKER_WEIGHTS_TIMER]:
|
| 529 |
+
self.env_runner_group.sync_weights(
|
| 530 |
+
policies=list(train_results.keys()), global_vars=global_vars
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
# Update global vars on local worker as well.
|
| 534 |
+
self.env_runner.set_global_vars(global_vars)
|
| 535 |
+
|
| 536 |
+
return train_results
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil_learner.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, Optional
|
| 2 |
+
|
| 3 |
+
from ray.rllib.core.learner.learner import Learner
|
| 4 |
+
from ray.rllib.core.rl_module.apis import ValueFunctionAPI
|
| 5 |
+
from ray.rllib.utils.annotations import override
|
| 6 |
+
from ray.rllib.utils.lambda_defaultdict import LambdaDefaultDict
|
| 7 |
+
from ray.rllib.utils.typing import ModuleID, ShouldModuleBeUpdatedFn, TensorType
|
| 8 |
+
|
| 9 |
+
LEARNER_RESULTS_MOVING_AVG_SQD_ADV_NORM_KEY = "moving_avg_sqd_adv_norm"
|
| 10 |
+
LEARNER_RESULTS_VF_EXPLAINED_VAR_KEY = "vf_explained_variance"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# TODO (simon): Check, if the norm update should be done inside
|
| 14 |
+
# the Learner.
|
| 15 |
+
class MARWILLearner(Learner):
|
| 16 |
+
@override(Learner)
|
| 17 |
+
def build(self) -> None:
|
| 18 |
+
super().build()
|
| 19 |
+
|
| 20 |
+
# Dict mapping module IDs to the respective moving averages of squared
|
| 21 |
+
# advantages.
|
| 22 |
+
self.moving_avg_sqd_adv_norms_per_module: Dict[
|
| 23 |
+
ModuleID, TensorType
|
| 24 |
+
] = LambdaDefaultDict(
|
| 25 |
+
lambda module_id: self._get_tensor_variable(
|
| 26 |
+
self.config.get_config_for_module(
|
| 27 |
+
module_id
|
| 28 |
+
).moving_average_sqd_adv_norm_start
|
| 29 |
+
)
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
@override(Learner)
|
| 33 |
+
def remove_module(
|
| 34 |
+
self,
|
| 35 |
+
module_id: ModuleID,
|
| 36 |
+
*,
|
| 37 |
+
new_should_module_be_updated: Optional[ShouldModuleBeUpdatedFn] = None,
|
| 38 |
+
) -> None:
|
| 39 |
+
super().remove_module(
|
| 40 |
+
module_id,
|
| 41 |
+
new_should_module_be_updated=new_should_module_be_updated,
|
| 42 |
+
)
|
| 43 |
+
# In case of BC (beta==0.0 and this property never being used),
|
| 44 |
+
self.moving_avg_sqd_adv_norms_per_module.pop(module_id, None)
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
@override(Learner)
|
| 48 |
+
def rl_module_required_apis(cls) -> list[type]:
|
| 49 |
+
# In order for a PPOLearner to update an RLModule, it must implement the
|
| 50 |
+
# following APIs:
|
| 51 |
+
return [ValueFunctionAPI]
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil_tf_policy.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import Any, Dict, List, Optional, Type, Union
|
| 3 |
+
|
| 4 |
+
from ray.rllib.evaluation.postprocessing import Postprocessing, compute_advantages
|
| 5 |
+
from ray.rllib.models.action_dist import ActionDistribution
|
| 6 |
+
from ray.rllib.models.modelv2 import ModelV2
|
| 7 |
+
from ray.rllib.models.tf.tf_action_dist import TFActionDistribution
|
| 8 |
+
from ray.rllib.policy.dynamic_tf_policy_v2 import DynamicTFPolicyV2
|
| 9 |
+
from ray.rllib.policy.eager_tf_policy_v2 import EagerTFPolicyV2
|
| 10 |
+
from ray.rllib.policy.policy import Policy
|
| 11 |
+
from ray.rllib.policy.sample_batch import SampleBatch
|
| 12 |
+
from ray.rllib.policy.tf_mixins import (
|
| 13 |
+
ValueNetworkMixin,
|
| 14 |
+
compute_gradients,
|
| 15 |
+
)
|
| 16 |
+
from ray.rllib.utils.annotations import override
|
| 17 |
+
from ray.rllib.utils.framework import get_variable, try_import_tf
|
| 18 |
+
from ray.rllib.utils.tf_utils import explained_variance
|
| 19 |
+
from ray.rllib.utils.typing import (
|
| 20 |
+
LocalOptimizer,
|
| 21 |
+
ModelGradients,
|
| 22 |
+
TensorType,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
tf1, tf, tfv = try_import_tf()
|
| 26 |
+
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class PostprocessAdvantages:
|
| 31 |
+
"""Marwil's custom trajectory post-processing mixin."""
|
| 32 |
+
|
| 33 |
+
def __init__(self):
|
| 34 |
+
pass
|
| 35 |
+
|
| 36 |
+
def postprocess_trajectory(
|
| 37 |
+
self,
|
| 38 |
+
sample_batch: SampleBatch,
|
| 39 |
+
other_agent_batches: Optional[Dict[Any, SampleBatch]] = None,
|
| 40 |
+
episode=None,
|
| 41 |
+
):
|
| 42 |
+
sample_batch = super().postprocess_trajectory(
|
| 43 |
+
sample_batch, other_agent_batches, episode
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# Trajectory is actually complete -> last r=0.0.
|
| 47 |
+
if sample_batch[SampleBatch.TERMINATEDS][-1]:
|
| 48 |
+
last_r = 0.0
|
| 49 |
+
# Trajectory has been truncated -> last r=VF estimate of last obs.
|
| 50 |
+
else:
|
| 51 |
+
# Input dict is provided to us automatically via the Model's
|
| 52 |
+
# requirements. It's a single-timestep (last one in trajectory)
|
| 53 |
+
# input_dict.
|
| 54 |
+
# Create an input dict according to the Model's requirements.
|
| 55 |
+
index = "last" if SampleBatch.NEXT_OBS in sample_batch else -1
|
| 56 |
+
input_dict = sample_batch.get_single_step_input_dict(
|
| 57 |
+
self.view_requirements, index=index
|
| 58 |
+
)
|
| 59 |
+
last_r = self._value(**input_dict)
|
| 60 |
+
|
| 61 |
+
# Adds the "advantages" (which in the case of MARWIL are simply the
|
| 62 |
+
# discounted cumulative rewards) to the SampleBatch.
|
| 63 |
+
return compute_advantages(
|
| 64 |
+
sample_batch,
|
| 65 |
+
last_r,
|
| 66 |
+
self.config["gamma"],
|
| 67 |
+
# We just want the discounted cumulative rewards, so we won't need
|
| 68 |
+
# GAE nor critic (use_critic=True: Subtract vf-estimates from returns).
|
| 69 |
+
use_gae=False,
|
| 70 |
+
use_critic=False,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class MARWILLoss:
|
| 75 |
+
def __init__(
|
| 76 |
+
self,
|
| 77 |
+
policy: Policy,
|
| 78 |
+
value_estimates: TensorType,
|
| 79 |
+
action_dist: ActionDistribution,
|
| 80 |
+
train_batch: SampleBatch,
|
| 81 |
+
vf_loss_coeff: float,
|
| 82 |
+
beta: float,
|
| 83 |
+
):
|
| 84 |
+
# L = - A * log\pi_\theta(a|s)
|
| 85 |
+
logprobs = action_dist.logp(train_batch[SampleBatch.ACTIONS])
|
| 86 |
+
if beta != 0.0:
|
| 87 |
+
cumulative_rewards = train_batch[Postprocessing.ADVANTAGES]
|
| 88 |
+
# Advantage Estimation.
|
| 89 |
+
adv = cumulative_rewards - value_estimates
|
| 90 |
+
adv_squared = tf.reduce_mean(tf.math.square(adv))
|
| 91 |
+
# Value function's loss term (MSE).
|
| 92 |
+
self.v_loss = 0.5 * adv_squared
|
| 93 |
+
|
| 94 |
+
# Perform moving averaging of advantage^2.
|
| 95 |
+
rate = policy.config["moving_average_sqd_adv_norm_update_rate"]
|
| 96 |
+
|
| 97 |
+
# Update averaged advantage norm.
|
| 98 |
+
# Eager.
|
| 99 |
+
if policy.config["framework"] == "tf2":
|
| 100 |
+
update_term = adv_squared - policy._moving_average_sqd_adv_norm
|
| 101 |
+
policy._moving_average_sqd_adv_norm.assign_add(rate * update_term)
|
| 102 |
+
|
| 103 |
+
# Exponentially weighted advantages.
|
| 104 |
+
c = tf.math.sqrt(policy._moving_average_sqd_adv_norm)
|
| 105 |
+
exp_advs = tf.math.exp(beta * (adv / (1e-8 + c)))
|
| 106 |
+
# Static graph.
|
| 107 |
+
else:
|
| 108 |
+
update_adv_norm = tf1.assign_add(
|
| 109 |
+
ref=policy._moving_average_sqd_adv_norm,
|
| 110 |
+
value=rate * (adv_squared - policy._moving_average_sqd_adv_norm),
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# Exponentially weighted advantages.
|
| 114 |
+
with tf1.control_dependencies([update_adv_norm]):
|
| 115 |
+
exp_advs = tf.math.exp(
|
| 116 |
+
beta
|
| 117 |
+
* tf.math.divide(
|
| 118 |
+
adv,
|
| 119 |
+
1e-8 + tf.math.sqrt(policy._moving_average_sqd_adv_norm),
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
+
exp_advs = tf.stop_gradient(exp_advs)
|
| 123 |
+
|
| 124 |
+
self.explained_variance = tf.reduce_mean(
|
| 125 |
+
explained_variance(cumulative_rewards, value_estimates)
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
else:
|
| 129 |
+
# Value function's loss term (MSE).
|
| 130 |
+
self.v_loss = tf.constant(0.0)
|
| 131 |
+
exp_advs = 1.0
|
| 132 |
+
|
| 133 |
+
# logprob loss alone tends to push action distributions to
|
| 134 |
+
# have very low entropy, resulting in worse performance for
|
| 135 |
+
# unfamiliar situations.
|
| 136 |
+
# A scaled logstd loss term encourages stochasticity, thus
|
| 137 |
+
# alleviate the problem to some extent.
|
| 138 |
+
logstd_coeff = policy.config["bc_logstd_coeff"]
|
| 139 |
+
if logstd_coeff > 0.0:
|
| 140 |
+
logstds = tf.reduce_sum(action_dist.log_std, axis=1)
|
| 141 |
+
else:
|
| 142 |
+
logstds = 0.0
|
| 143 |
+
|
| 144 |
+
self.p_loss = -1.0 * tf.reduce_mean(
|
| 145 |
+
exp_advs * (logprobs + logstd_coeff * logstds)
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
self.total_loss = self.p_loss + vf_loss_coeff * self.v_loss
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
# We need this builder function because we want to share the same
|
| 152 |
+
# custom logics between TF1 dynamic and TF2 eager policies.
|
| 153 |
+
def get_marwil_tf_policy(name: str, base: type) -> type:
|
| 154 |
+
"""Construct a MARWILTFPolicy inheriting either dynamic or eager base policies.
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
base: Base class for this policy. DynamicTFPolicyV2 or EagerTFPolicyV2.
|
| 158 |
+
|
| 159 |
+
Returns:
|
| 160 |
+
A TF Policy to be used with MAML.
|
| 161 |
+
"""
|
| 162 |
+
|
| 163 |
+
class MARWILTFPolicy(ValueNetworkMixin, PostprocessAdvantages, base):
|
| 164 |
+
def __init__(
|
| 165 |
+
self,
|
| 166 |
+
observation_space,
|
| 167 |
+
action_space,
|
| 168 |
+
config,
|
| 169 |
+
existing_model=None,
|
| 170 |
+
existing_inputs=None,
|
| 171 |
+
):
|
| 172 |
+
# First thing first, enable eager execution if necessary.
|
| 173 |
+
base.enable_eager_execution_if_necessary()
|
| 174 |
+
|
| 175 |
+
# Initialize base class.
|
| 176 |
+
base.__init__(
|
| 177 |
+
self,
|
| 178 |
+
observation_space,
|
| 179 |
+
action_space,
|
| 180 |
+
config,
|
| 181 |
+
existing_inputs=existing_inputs,
|
| 182 |
+
existing_model=existing_model,
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
ValueNetworkMixin.__init__(self, config)
|
| 186 |
+
PostprocessAdvantages.__init__(self)
|
| 187 |
+
|
| 188 |
+
# Not needed for pure BC.
|
| 189 |
+
if config["beta"] != 0.0:
|
| 190 |
+
# Set up a tf-var for the moving avg (do this here to make it work
|
| 191 |
+
# with eager mode); "c^2" in the paper.
|
| 192 |
+
self._moving_average_sqd_adv_norm = get_variable(
|
| 193 |
+
config["moving_average_sqd_adv_norm_start"],
|
| 194 |
+
framework="tf",
|
| 195 |
+
tf_name="moving_average_of_advantage_norm",
|
| 196 |
+
trainable=False,
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# Note: this is a bit ugly, but loss and optimizer initialization must
|
| 200 |
+
# happen after all the MixIns are initialized.
|
| 201 |
+
self.maybe_initialize_optimizer_and_loss()
|
| 202 |
+
|
| 203 |
+
@override(base)
|
| 204 |
+
def loss(
|
| 205 |
+
self,
|
| 206 |
+
model: Union[ModelV2, "tf.keras.Model"],
|
| 207 |
+
dist_class: Type[TFActionDistribution],
|
| 208 |
+
train_batch: SampleBatch,
|
| 209 |
+
) -> Union[TensorType, List[TensorType]]:
|
| 210 |
+
model_out, _ = model(train_batch)
|
| 211 |
+
action_dist = dist_class(model_out, model)
|
| 212 |
+
value_estimates = model.value_function()
|
| 213 |
+
|
| 214 |
+
self._marwil_loss = MARWILLoss(
|
| 215 |
+
self,
|
| 216 |
+
value_estimates,
|
| 217 |
+
action_dist,
|
| 218 |
+
train_batch,
|
| 219 |
+
self.config["vf_coeff"],
|
| 220 |
+
self.config["beta"],
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
return self._marwil_loss.total_loss
|
| 224 |
+
|
| 225 |
+
@override(base)
|
| 226 |
+
def stats_fn(self, train_batch: SampleBatch) -> Dict[str, TensorType]:
|
| 227 |
+
stats = {
|
| 228 |
+
"policy_loss": self._marwil_loss.p_loss,
|
| 229 |
+
"total_loss": self._marwil_loss.total_loss,
|
| 230 |
+
}
|
| 231 |
+
if self.config["beta"] != 0.0:
|
| 232 |
+
stats["moving_average_sqd_adv_norm"] = self._moving_average_sqd_adv_norm
|
| 233 |
+
stats["vf_explained_var"] = self._marwil_loss.explained_variance
|
| 234 |
+
stats["vf_loss"] = self._marwil_loss.v_loss
|
| 235 |
+
|
| 236 |
+
return stats
|
| 237 |
+
|
| 238 |
+
@override(base)
|
| 239 |
+
def compute_gradients_fn(
|
| 240 |
+
self, optimizer: LocalOptimizer, loss: TensorType
|
| 241 |
+
) -> ModelGradients:
|
| 242 |
+
return compute_gradients(self, optimizer, loss)
|
| 243 |
+
|
| 244 |
+
MARWILTFPolicy.__name__ = name
|
| 245 |
+
MARWILTFPolicy.__qualname__ = name
|
| 246 |
+
|
| 247 |
+
return MARWILTFPolicy
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
MARWILTF1Policy = get_marwil_tf_policy("MARWILTF1Policy", DynamicTFPolicyV2)
|
| 251 |
+
MARWILTF2Policy = get_marwil_tf_policy("MARWILTF2Policy", EagerTFPolicyV2)
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/marwil_torch_policy.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, List, Type, Union
|
| 2 |
+
|
| 3 |
+
from ray.rllib.algorithms.marwil.marwil_tf_policy import PostprocessAdvantages
|
| 4 |
+
from ray.rllib.evaluation.postprocessing import Postprocessing
|
| 5 |
+
from ray.rllib.models.modelv2 import ModelV2
|
| 6 |
+
from ray.rllib.models.torch.torch_action_dist import TorchDistributionWrapper
|
| 7 |
+
from ray.rllib.policy.sample_batch import SampleBatch
|
| 8 |
+
from ray.rllib.policy.torch_mixins import ValueNetworkMixin
|
| 9 |
+
from ray.rllib.policy.torch_policy_v2 import TorchPolicyV2
|
| 10 |
+
from ray.rllib.utils.annotations import override
|
| 11 |
+
from ray.rllib.utils.framework import try_import_torch
|
| 12 |
+
from ray.rllib.utils.numpy import convert_to_numpy
|
| 13 |
+
from ray.rllib.utils.torch_utils import apply_grad_clipping, explained_variance
|
| 14 |
+
from ray.rllib.utils.typing import TensorType
|
| 15 |
+
|
| 16 |
+
torch, _ = try_import_torch()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class MARWILTorchPolicy(ValueNetworkMixin, PostprocessAdvantages, TorchPolicyV2):
|
| 20 |
+
"""PyTorch policy class used with Marwil."""
|
| 21 |
+
|
| 22 |
+
def __init__(self, observation_space, action_space, config):
|
| 23 |
+
TorchPolicyV2.__init__(
|
| 24 |
+
self,
|
| 25 |
+
observation_space,
|
| 26 |
+
action_space,
|
| 27 |
+
config,
|
| 28 |
+
max_seq_len=config["model"]["max_seq_len"],
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
ValueNetworkMixin.__init__(self, config)
|
| 32 |
+
PostprocessAdvantages.__init__(self)
|
| 33 |
+
|
| 34 |
+
# Not needed for pure BC.
|
| 35 |
+
if config["beta"] != 0.0:
|
| 36 |
+
# Set up a torch-var for the squared moving avg. advantage norm.
|
| 37 |
+
self._moving_average_sqd_adv_norm = torch.tensor(
|
| 38 |
+
[config["moving_average_sqd_adv_norm_start"]],
|
| 39 |
+
dtype=torch.float32,
|
| 40 |
+
requires_grad=False,
|
| 41 |
+
).to(self.device)
|
| 42 |
+
|
| 43 |
+
# TODO: Don't require users to call this manually.
|
| 44 |
+
self._initialize_loss_from_dummy_batch()
|
| 45 |
+
|
| 46 |
+
@override(TorchPolicyV2)
|
| 47 |
+
def loss(
|
| 48 |
+
self,
|
| 49 |
+
model: ModelV2,
|
| 50 |
+
dist_class: Type[TorchDistributionWrapper],
|
| 51 |
+
train_batch: SampleBatch,
|
| 52 |
+
) -> Union[TensorType, List[TensorType]]:
|
| 53 |
+
model_out, _ = model(train_batch)
|
| 54 |
+
action_dist = dist_class(model_out, model)
|
| 55 |
+
actions = train_batch[SampleBatch.ACTIONS]
|
| 56 |
+
# log\pi_\theta(a|s)
|
| 57 |
+
logprobs = action_dist.logp(actions)
|
| 58 |
+
|
| 59 |
+
# Advantage estimation.
|
| 60 |
+
if self.config["beta"] != 0.0:
|
| 61 |
+
cumulative_rewards = train_batch[Postprocessing.ADVANTAGES]
|
| 62 |
+
state_values = model.value_function()
|
| 63 |
+
adv = cumulative_rewards - state_values
|
| 64 |
+
adv_squared_mean = torch.mean(torch.pow(adv, 2.0))
|
| 65 |
+
|
| 66 |
+
explained_var = explained_variance(cumulative_rewards, state_values)
|
| 67 |
+
ev = torch.mean(explained_var)
|
| 68 |
+
model.tower_stats["explained_variance"] = ev
|
| 69 |
+
|
| 70 |
+
# Policy loss.
|
| 71 |
+
# Update averaged advantage norm.
|
| 72 |
+
rate = self.config["moving_average_sqd_adv_norm_update_rate"]
|
| 73 |
+
self._moving_average_sqd_adv_norm = (
|
| 74 |
+
rate * (adv_squared_mean.detach() - self._moving_average_sqd_adv_norm)
|
| 75 |
+
+ self._moving_average_sqd_adv_norm
|
| 76 |
+
)
|
| 77 |
+
model.tower_stats[
|
| 78 |
+
"_moving_average_sqd_adv_norm"
|
| 79 |
+
] = self._moving_average_sqd_adv_norm
|
| 80 |
+
# Exponentially weighted advantages.
|
| 81 |
+
exp_advs = torch.exp(
|
| 82 |
+
self.config["beta"]
|
| 83 |
+
* (adv / (1e-8 + torch.pow(self._moving_average_sqd_adv_norm, 0.5)))
|
| 84 |
+
).detach()
|
| 85 |
+
# Value loss.
|
| 86 |
+
v_loss = 0.5 * adv_squared_mean
|
| 87 |
+
else:
|
| 88 |
+
# Policy loss (simple BC loss term).
|
| 89 |
+
exp_advs = 1.0
|
| 90 |
+
# Value loss.
|
| 91 |
+
v_loss = 0.0
|
| 92 |
+
model.tower_stats["v_loss"] = v_loss
|
| 93 |
+
# logprob loss alone tends to push action distributions to
|
| 94 |
+
# have very low entropy, resulting in worse performance for
|
| 95 |
+
# unfamiliar situations.
|
| 96 |
+
# A scaled logstd loss term encourages stochasticity, thus
|
| 97 |
+
# alleviate the problem to some extent.
|
| 98 |
+
logstd_coeff = self.config["bc_logstd_coeff"]
|
| 99 |
+
if logstd_coeff > 0.0:
|
| 100 |
+
logstds = torch.mean(action_dist.log_std, dim=1)
|
| 101 |
+
else:
|
| 102 |
+
logstds = 0.0
|
| 103 |
+
|
| 104 |
+
p_loss = -torch.mean(exp_advs * (logprobs + logstd_coeff * logstds))
|
| 105 |
+
model.tower_stats["p_loss"] = p_loss
|
| 106 |
+
# Combine both losses.
|
| 107 |
+
self.v_loss = v_loss
|
| 108 |
+
self.p_loss = p_loss
|
| 109 |
+
total_loss = p_loss + self.config["vf_coeff"] * v_loss
|
| 110 |
+
model.tower_stats["total_loss"] = total_loss
|
| 111 |
+
return total_loss
|
| 112 |
+
|
| 113 |
+
@override(TorchPolicyV2)
|
| 114 |
+
def stats_fn(self, train_batch: SampleBatch) -> Dict[str, TensorType]:
|
| 115 |
+
stats = {
|
| 116 |
+
"policy_loss": self.get_tower_stats("p_loss")[0].item(),
|
| 117 |
+
"total_loss": self.get_tower_stats("total_loss")[0].item(),
|
| 118 |
+
}
|
| 119 |
+
if self.config["beta"] != 0.0:
|
| 120 |
+
stats["moving_average_sqd_adv_norm"] = self.get_tower_stats(
|
| 121 |
+
"_moving_average_sqd_adv_norm"
|
| 122 |
+
)[0].item()
|
| 123 |
+
stats["vf_explained_var"] = self.get_tower_stats("explained_variance")[
|
| 124 |
+
0
|
| 125 |
+
].item()
|
| 126 |
+
stats["vf_loss"] = self.get_tower_stats("v_loss")[0].item()
|
| 127 |
+
return convert_to_numpy(stats)
|
| 128 |
+
|
| 129 |
+
def extra_grad_process(
|
| 130 |
+
self, optimizer: "torch.optim.Optimizer", loss: TensorType
|
| 131 |
+
) -> Dict[str, TensorType]:
|
| 132 |
+
return apply_grad_clipping(self, optimizer, loss)
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (212 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/__pycache__/marwil_torch_learner.cpython-312.pyc
ADDED
|
Binary file (5.03 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/marwil/torch/marwil_torch_learner.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Dict, Optional
|
| 2 |
+
|
| 3 |
+
from ray.rllib.algorithms.marwil.marwil import MARWILConfig
|
| 4 |
+
from ray.rllib.algorithms.marwil.marwil_learner import (
|
| 5 |
+
LEARNER_RESULTS_MOVING_AVG_SQD_ADV_NORM_KEY,
|
| 6 |
+
LEARNER_RESULTS_VF_EXPLAINED_VAR_KEY,
|
| 7 |
+
MARWILLearner,
|
| 8 |
+
)
|
| 9 |
+
from ray.rllib.core.columns import Columns
|
| 10 |
+
from ray.rllib.core.learner.learner import POLICY_LOSS_KEY, VF_LOSS_KEY
|
| 11 |
+
from ray.rllib.core.learner.torch.torch_learner import TorchLearner
|
| 12 |
+
from ray.rllib.evaluation.postprocessing import Postprocessing
|
| 13 |
+
from ray.rllib.utils.framework import try_import_torch
|
| 14 |
+
from ray.rllib.utils.torch_utils import explained_variance
|
| 15 |
+
from ray.rllib.utils.typing import TensorType
|
| 16 |
+
|
| 17 |
+
torch, nn = try_import_torch()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class MARWILTorchLearner(MARWILLearner, TorchLearner):
|
| 21 |
+
"""Implements torch-specific MARWIL loss on top of MARWILLearner.
|
| 22 |
+
|
| 23 |
+
This class implements the MARWIL loss under `self.compute_loss_for_module()`.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
def compute_loss_for_module(
|
| 27 |
+
self,
|
| 28 |
+
*,
|
| 29 |
+
module_id: str,
|
| 30 |
+
config: Optional[MARWILConfig] = None,
|
| 31 |
+
batch: Dict[str, Any],
|
| 32 |
+
fwd_out: Dict[str, TensorType]
|
| 33 |
+
) -> TensorType:
|
| 34 |
+
module = self.module[module_id].unwrapped()
|
| 35 |
+
|
| 36 |
+
# Possibly apply masking to some sub loss terms and to the total loss term
|
| 37 |
+
# at the end. Masking could be used for RNN-based model (zero padded `batch`)
|
| 38 |
+
# and for PPO's batched value function (and bootstrap value) computations,
|
| 39 |
+
# for which we add an additional (artificial) timestep to each episode to
|
| 40 |
+
# simplify the actual computation.
|
| 41 |
+
if Columns.LOSS_MASK in batch:
|
| 42 |
+
num_valid = torch.sum(batch[Columns.LOSS_MASK])
|
| 43 |
+
|
| 44 |
+
def possibly_masked_mean(data_):
|
| 45 |
+
return torch.sum(data_[batch[Columns.LOSS_MASK]]) / num_valid
|
| 46 |
+
|
| 47 |
+
else:
|
| 48 |
+
possibly_masked_mean = torch.mean
|
| 49 |
+
|
| 50 |
+
action_dist_class_train = module.get_train_action_dist_cls()
|
| 51 |
+
curr_action_dist = action_dist_class_train.from_logits(
|
| 52 |
+
fwd_out[Columns.ACTION_DIST_INPUTS]
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
log_probs = curr_action_dist.logp(batch[Columns.ACTIONS])
|
| 56 |
+
|
| 57 |
+
# If beta is zero, we fall back to BC.
|
| 58 |
+
if config.beta == 0.0:
|
| 59 |
+
# Value function's loss term.
|
| 60 |
+
mean_vf_loss = 0.0
|
| 61 |
+
# Policy's loss term.
|
| 62 |
+
exp_weighted_advantages = 1.0
|
| 63 |
+
# Otherwise, compute advantages.
|
| 64 |
+
else:
|
| 65 |
+
# cumulative_rewards = batch[Columns.ADVANTAGES]
|
| 66 |
+
value_fn_out = module.compute_values(
|
| 67 |
+
batch, embeddings=fwd_out.get(Columns.EMBEDDINGS)
|
| 68 |
+
)
|
| 69 |
+
advantages = batch[Columns.VALUE_TARGETS] - value_fn_out
|
| 70 |
+
advantages_squared_mean = possibly_masked_mean(torch.pow(advantages, 2.0))
|
| 71 |
+
|
| 72 |
+
# Compute the value loss.
|
| 73 |
+
mean_vf_loss = 0.5 * advantages_squared_mean
|
| 74 |
+
|
| 75 |
+
# Compute the policy loss.
|
| 76 |
+
self.moving_avg_sqd_adv_norms_per_module[module_id] = (
|
| 77 |
+
config.moving_average_sqd_adv_norm_update_rate
|
| 78 |
+
* (
|
| 79 |
+
advantages_squared_mean.detach()
|
| 80 |
+
- self.moving_avg_sqd_adv_norms_per_module[module_id]
|
| 81 |
+
)
|
| 82 |
+
+ self.moving_avg_sqd_adv_norms_per_module[module_id]
|
| 83 |
+
)
|
| 84 |
+
# Exponentially weighted advantages.
|
| 85 |
+
# TODO (simon): Check, if we need the mask here.
|
| 86 |
+
exp_weighted_advantages = torch.exp(
|
| 87 |
+
config.beta
|
| 88 |
+
* (
|
| 89 |
+
advantages
|
| 90 |
+
/ (
|
| 91 |
+
1e-8
|
| 92 |
+
+ torch.pow(
|
| 93 |
+
self.moving_avg_sqd_adv_norms_per_module[module_id], 0.5
|
| 94 |
+
)
|
| 95 |
+
)
|
| 96 |
+
)
|
| 97 |
+
).detach()
|
| 98 |
+
|
| 99 |
+
# Note, using solely a log-probability loss term tends to push the action
|
| 100 |
+
# distributions to have very low entropy, which results in worse performance
|
| 101 |
+
# specifically in unknown situations.
|
| 102 |
+
# Scaling the loss term with the logarithm of the action distribution's
|
| 103 |
+
# standard deviation encourages stochasticity in the policy.
|
| 104 |
+
if config.bc_logstd_coeff > 0.0:
|
| 105 |
+
log_stds = possibly_masked_mean(curr_action_dist.log_std, dim=1)
|
| 106 |
+
else:
|
| 107 |
+
log_stds = 0.0
|
| 108 |
+
|
| 109 |
+
# Compute the policy loss.
|
| 110 |
+
policy_loss = -possibly_masked_mean(
|
| 111 |
+
exp_weighted_advantages * (log_probs + config.bc_logstd_coeff * log_stds)
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
# Compute the total loss.
|
| 115 |
+
total_loss = policy_loss + config.vf_coeff * mean_vf_loss
|
| 116 |
+
|
| 117 |
+
# Log import loss stats. In case of the BC loss this is simply
|
| 118 |
+
# the policy loss.
|
| 119 |
+
if config.beta == 0.0:
|
| 120 |
+
self.metrics.log_value((module_id, POLICY_LOSS_KEY), policy_loss, window=1)
|
| 121 |
+
# Log more stats, if using the MARWIL loss.
|
| 122 |
+
else:
|
| 123 |
+
ma_sqd_adv_norms = self.moving_avg_sqd_adv_norms_per_module[module_id]
|
| 124 |
+
self.metrics.log_dict(
|
| 125 |
+
{
|
| 126 |
+
POLICY_LOSS_KEY: policy_loss,
|
| 127 |
+
VF_LOSS_KEY: mean_vf_loss,
|
| 128 |
+
LEARNER_RESULTS_VF_EXPLAINED_VAR_KEY: explained_variance(
|
| 129 |
+
batch[Postprocessing.VALUE_TARGETS], value_fn_out
|
| 130 |
+
),
|
| 131 |
+
LEARNER_RESULTS_MOVING_AVG_SQD_ADV_NORM_KEY: ma_sqd_adv_norms,
|
| 132 |
+
},
|
| 133 |
+
key=module_id,
|
| 134 |
+
window=1, # <- single items (should not be mean/ema-reduced over time).
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
# Return the total loss.
|
| 138 |
+
return total_loss
|
lib/python3.12/site-packages/ray/rllib/algorithms/mock.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pickle
|
| 3 |
+
import time
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
from ray.rllib.algorithms.algorithm import Algorithm, AlgorithmConfig
|
| 8 |
+
from ray.rllib.utils.annotations import override
|
| 9 |
+
from ray.tune import result as tune_result
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class _MockTrainer(Algorithm):
|
| 13 |
+
"""Mock Algorithm for use in tests."""
|
| 14 |
+
|
| 15 |
+
@classmethod
|
| 16 |
+
@override(Algorithm)
|
| 17 |
+
def get_default_config(cls) -> AlgorithmConfig:
|
| 18 |
+
return (
|
| 19 |
+
AlgorithmConfig()
|
| 20 |
+
.framework("tf")
|
| 21 |
+
.update_from_dict(
|
| 22 |
+
{
|
| 23 |
+
"mock_error": False,
|
| 24 |
+
"persistent_error": False,
|
| 25 |
+
"test_variable": 1,
|
| 26 |
+
"user_checkpoint_freq": 0,
|
| 27 |
+
"sleep": 0,
|
| 28 |
+
}
|
| 29 |
+
)
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
@classmethod
|
| 33 |
+
def default_resource_request(cls, config: AlgorithmConfig):
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
+
@override(Algorithm)
|
| 37 |
+
def setup(self, config):
|
| 38 |
+
self.callbacks = self.config.callbacks_class()
|
| 39 |
+
|
| 40 |
+
# Add needed properties.
|
| 41 |
+
self.info = None
|
| 42 |
+
self.restored = False
|
| 43 |
+
|
| 44 |
+
@override(Algorithm)
|
| 45 |
+
def step(self):
|
| 46 |
+
if (
|
| 47 |
+
self.config.mock_error
|
| 48 |
+
and self.iteration == 1
|
| 49 |
+
and (self.config.persistent_error or not self.restored)
|
| 50 |
+
):
|
| 51 |
+
raise Exception("mock error")
|
| 52 |
+
if self.config.sleep:
|
| 53 |
+
time.sleep(self.config.sleep)
|
| 54 |
+
result = dict(
|
| 55 |
+
episode_reward_mean=10, episode_len_mean=10, timesteps_this_iter=10, info={}
|
| 56 |
+
)
|
| 57 |
+
if self.config.user_checkpoint_freq > 0 and self.iteration > 0:
|
| 58 |
+
if self.iteration % self.config.user_checkpoint_freq == 0:
|
| 59 |
+
result.update({tune_result.SHOULD_CHECKPOINT: True})
|
| 60 |
+
return result
|
| 61 |
+
|
| 62 |
+
@override(Algorithm)
|
| 63 |
+
def save_checkpoint(self, checkpoint_dir):
|
| 64 |
+
path = os.path.join(checkpoint_dir, "mock_agent.pkl")
|
| 65 |
+
with open(path, "wb") as f:
|
| 66 |
+
pickle.dump(self.info, f)
|
| 67 |
+
|
| 68 |
+
@override(Algorithm)
|
| 69 |
+
def load_checkpoint(self, checkpoint_dir):
|
| 70 |
+
path = os.path.join(checkpoint_dir, "mock_agent.pkl")
|
| 71 |
+
with open(path, "rb") as f:
|
| 72 |
+
info = pickle.load(f)
|
| 73 |
+
self.info = info
|
| 74 |
+
self.restored = True
|
| 75 |
+
|
| 76 |
+
@staticmethod
|
| 77 |
+
@override(Algorithm)
|
| 78 |
+
def _get_env_id_and_creator(env_specifier, config):
|
| 79 |
+
# No env to register.
|
| 80 |
+
return None, None
|
| 81 |
+
|
| 82 |
+
def set_info(self, info):
|
| 83 |
+
self.info = info
|
| 84 |
+
return info
|
| 85 |
+
|
| 86 |
+
def get_info(self, sess=None):
|
| 87 |
+
return self.info
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class _SigmoidFakeData(_MockTrainer):
|
| 91 |
+
"""Algorithm that returns sigmoid learning curves.
|
| 92 |
+
|
| 93 |
+
This can be helpful for evaluating early stopping algorithms."""
|
| 94 |
+
|
| 95 |
+
@classmethod
|
| 96 |
+
@override(Algorithm)
|
| 97 |
+
def get_default_config(cls) -> AlgorithmConfig:
|
| 98 |
+
return AlgorithmConfig().update_from_dict(
|
| 99 |
+
{
|
| 100 |
+
"width": 100,
|
| 101 |
+
"height": 100,
|
| 102 |
+
"offset": 0,
|
| 103 |
+
"iter_time": 10,
|
| 104 |
+
"iter_timesteps": 1,
|
| 105 |
+
}
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
def step(self):
|
| 109 |
+
i = max(0, self.iteration - self.config.offset)
|
| 110 |
+
v = np.tanh(float(i) / self.config.width)
|
| 111 |
+
v *= self.config.height
|
| 112 |
+
return dict(
|
| 113 |
+
episode_reward_mean=v,
|
| 114 |
+
episode_len_mean=v,
|
| 115 |
+
timesteps_this_iter=self.config.iter_timesteps,
|
| 116 |
+
time_this_iter_s=self.config.iter_time,
|
| 117 |
+
info={},
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class _ParameterTuningTrainer(_MockTrainer):
|
| 122 |
+
@classmethod
|
| 123 |
+
@override(Algorithm)
|
| 124 |
+
def get_default_config(cls) -> AlgorithmConfig:
|
| 125 |
+
return AlgorithmConfig().update_from_dict(
|
| 126 |
+
{
|
| 127 |
+
"reward_amt": 10,
|
| 128 |
+
"dummy_param": 10,
|
| 129 |
+
"dummy_param2": 15,
|
| 130 |
+
"iter_time": 10,
|
| 131 |
+
"iter_timesteps": 1,
|
| 132 |
+
}
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
def step(self):
|
| 136 |
+
return dict(
|
| 137 |
+
episode_reward_mean=self.config.reward_amt * self.iteration,
|
| 138 |
+
episode_len_mean=self.config.reward_amt,
|
| 139 |
+
timesteps_this_iter=self.config.iter_timesteps,
|
| 140 |
+
time_this_iter_s=self.config.iter_time,
|
| 141 |
+
info={},
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def _algorithm_import_failed(trace):
|
| 146 |
+
"""Returns dummy Algorithm class for if PyTorch etc. is not installed."""
|
| 147 |
+
|
| 148 |
+
class _AlgorithmImportFailed(Algorithm):
|
| 149 |
+
_name = "AlgorithmImportFailed"
|
| 150 |
+
|
| 151 |
+
def setup(self, config):
|
| 152 |
+
raise ImportError(trace)
|
| 153 |
+
|
| 154 |
+
return _AlgorithmImportFailed
|
lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ray.rllib.algorithms.ppo.ppo import PPO, PPOConfig
|
| 2 |
+
from ray.rllib.algorithms.ppo.ppo_tf_policy import PPOTF1Policy, PPOTF2Policy
|
| 3 |
+
from ray.rllib.algorithms.ppo.ppo_torch_policy import PPOTorchPolicy
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
"PPO",
|
| 7 |
+
"PPOConfig",
|
| 8 |
+
# @OldAPIStack
|
| 9 |
+
"PPOTF1Policy",
|
| 10 |
+
"PPOTF2Policy",
|
| 11 |
+
"PPOTorchPolicy",
|
| 12 |
+
]
|
lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (513 Bytes). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/default_ppo_rl_module.cpython-312.pyc
ADDED
|
Binary file (3.24 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/ppo.cpython-312.pyc
ADDED
|
Binary file (21.6 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/ppo_catalog.cpython-312.pyc
ADDED
|
Binary file (7.88 kB). View file
|
|
|
lib/python3.12/site-packages/ray/rllib/algorithms/ppo/__pycache__/ppo_learner.cpython-312.pyc
ADDED
|
Binary file (6.29 kB). View file
|
|
|