chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import abc
|
||||
from typing import Any, Dict
|
||||
|
||||
from ray.rllib.algorithms.ppo.ppo import (
|
||||
LEARNER_RESULTS_CURR_ENTROPY_COEFF_KEY,
|
||||
LEARNER_RESULTS_KL_KEY,
|
||||
PPOConfig,
|
||||
)
|
||||
from ray.rllib.connectors.learner import (
|
||||
AddOneTsToEpisodesAndTruncate,
|
||||
GeneralAdvantageEstimation,
|
||||
)
|
||||
from ray.rllib.core.learner.learner import Learner
|
||||
from ray.rllib.core.rl_module.apis.value_function_api import ValueFunctionAPI
|
||||
from ray.rllib.utils.annotations import (
|
||||
OverrideToImplementCustomLogic_CallToSuperRecommended,
|
||||
override,
|
||||
)
|
||||
from ray.rllib.utils.lambda_defaultdict import LambdaDefaultDict
|
||||
from ray.rllib.utils.metrics import (
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.rllib.utils.numpy import convert_to_numpy
|
||||
from ray.rllib.utils.schedules.scheduler import Scheduler
|
||||
from ray.rllib.utils.typing import ModuleID, TensorType
|
||||
|
||||
|
||||
class PPOLearner(Learner):
|
||||
@override(Learner)
|
||||
def build(self) -> None:
|
||||
super().build()
|
||||
|
||||
# Dict mapping module IDs to the respective entropy Scheduler instance.
|
||||
self.entropy_coeff_schedulers_per_module: Dict[
|
||||
ModuleID, Scheduler
|
||||
] = LambdaDefaultDict(
|
||||
lambda module_id: Scheduler(
|
||||
fixed_value_or_schedule=(
|
||||
self.config.get_config_for_module(module_id).entropy_coeff
|
||||
),
|
||||
framework=self.framework,
|
||||
device=self._device,
|
||||
)
|
||||
)
|
||||
|
||||
# Set up KL coefficient variables (per module).
|
||||
# Note that the KL coeff is not controlled by a Scheduler, but seeks
|
||||
# to stay close to a given kl_target value.
|
||||
self.curr_kl_coeffs_per_module: Dict[ModuleID, TensorType] = LambdaDefaultDict(
|
||||
lambda module_id: self._get_tensor_variable(
|
||||
self.config.get_config_for_module(module_id).kl_coeff
|
||||
)
|
||||
)
|
||||
|
||||
# Extend all episodes by one artificial timestep to allow the value function net
|
||||
# to compute the bootstrap values (and add a mask to the batch to know, which
|
||||
# slots to mask out).
|
||||
if (
|
||||
self._learner_connector is not None
|
||||
and self.config.add_default_connectors_to_learner_pipeline
|
||||
):
|
||||
# Before anything, add one ts to each episode (and record this in the loss
|
||||
# mask, so that the computations at this extra ts are not used to compute
|
||||
# the loss).
|
||||
self._learner_connector.prepend(AddOneTsToEpisodesAndTruncate())
|
||||
# At the end of the pipeline (when the batch is already completed), add the
|
||||
# GAE connector, which performs a vf forward pass, then computes the GAE
|
||||
# computations, and puts the results of this (advantages, value targets)
|
||||
# directly back in the batch. This is then the batch used for
|
||||
# `forward_train` and `compute_losses`.
|
||||
self._learner_connector.append(
|
||||
GeneralAdvantageEstimation(
|
||||
gamma=self.config.gamma, lambda_=self.config.lambda_
|
||||
)
|
||||
)
|
||||
|
||||
@override(Learner)
|
||||
def remove_module(self, module_id: ModuleID, **kwargs):
|
||||
marl_spec = super().remove_module(module_id, **kwargs)
|
||||
|
||||
self.entropy_coeff_schedulers_per_module.pop(module_id, None)
|
||||
self.curr_kl_coeffs_per_module.pop(module_id, None)
|
||||
|
||||
return marl_spec
|
||||
|
||||
@OverrideToImplementCustomLogic_CallToSuperRecommended
|
||||
@override(Learner)
|
||||
def after_gradient_based_update(
|
||||
self,
|
||||
*,
|
||||
timesteps: Dict[str, Any],
|
||||
) -> None:
|
||||
super().after_gradient_based_update(timesteps=timesteps)
|
||||
|
||||
for module_id, module in self.module._rl_modules.items():
|
||||
config = self.config.get_config_for_module(module_id)
|
||||
|
||||
# Update entropy coefficient via our Scheduler.
|
||||
new_entropy_coeff = self.entropy_coeff_schedulers_per_module[
|
||||
module_id
|
||||
].update(timestep=timesteps.get(NUM_ENV_STEPS_SAMPLED_LIFETIME, 0))
|
||||
self.metrics.log_value(
|
||||
(module_id, LEARNER_RESULTS_CURR_ENTROPY_COEFF_KEY),
|
||||
new_entropy_coeff,
|
||||
window=1,
|
||||
)
|
||||
if (
|
||||
config.use_kl_loss
|
||||
and (module_id, LEARNER_RESULTS_KL_KEY) in self.metrics
|
||||
):
|
||||
kl_loss = convert_to_numpy(
|
||||
self.metrics.peek((module_id, LEARNER_RESULTS_KL_KEY))
|
||||
)
|
||||
self._update_module_kl_coeff(
|
||||
module_id=module_id,
|
||||
config=config,
|
||||
kl_loss=kl_loss,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@override(Learner)
|
||||
def rl_module_required_apis(cls) -> list[type]:
|
||||
# In order for a PPOLearner to update an RLModule, it must implement the
|
||||
# following APIs:
|
||||
return [ValueFunctionAPI]
|
||||
|
||||
@abc.abstractmethod
|
||||
def _update_module_kl_coeff(
|
||||
self,
|
||||
*,
|
||||
module_id: ModuleID,
|
||||
config: PPOConfig,
|
||||
kl_loss: float,
|
||||
) -> None:
|
||||
"""Dynamically update the KL loss coefficients of each module.
|
||||
|
||||
The update is completed using the mean KL divergence between the action
|
||||
distributions current policy and old policy of each module. That action
|
||||
distribution is computed during the most recent update/call to `compute_loss`.
|
||||
|
||||
Args:
|
||||
module_id: The module whose KL loss coefficient to update.
|
||||
config: The AlgorithmConfig specific to the given `module_id`.
|
||||
kl_loss: The mean KL loss of the module, computed inside
|
||||
`compute_loss_for_module()`.
|
||||
"""
|
||||
Reference in New Issue
Block a user