chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,54 @@
from typing import Any, Dict
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.algorithms.ppo.torch.ppo_torch_learner import PPOTorchLearner
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
from ray.rllib.utils.typing import ModuleID, TensorType
torch, _ = try_import_torch()
class PPOTorchLearnerWithWeightRegularizerLoss(PPOTorchLearner):
"""A custom PPO torch learner adding a weight regularizer term to the loss.
We compute a naive regularizer term averaging over all parameters of the RLModule
and add this mean value (multiplied by the regularizer coefficient) to the base PPO
loss.
The experiment shows that even with a large learning rate, our custom Learner is
still able to learn properly as it's forced to keep the weights small.
"""
@override(PPOTorchLearner)
def compute_loss_for_module(
self,
*,
module_id: ModuleID,
config: PPOConfig,
batch: Dict[str, Any],
fwd_out: Dict[str, TensorType],
) -> TensorType:
base_total_loss = super().compute_loss_for_module(
module_id=module_id,
config=config,
batch=batch,
fwd_out=fwd_out,
)
# Compute the mean of all the RLModule's weights.
parameters = self.get_parameters(self.module[module_id])
mean_weight = torch.mean(torch.stack([w.mean() for w in parameters]))
self.metrics.log_value(
key=(module_id, "mean_weight"),
value=mean_weight,
window=1,
)
total_loss = (
base_total_loss
+ config.learner_config_dict["regularizer_coeff"] * mean_weight
)
return total_loss
@@ -0,0 +1,162 @@
from typing import Any, List, Optional
import gymnasium as gym
import torch
from ray.rllib.algorithms.dqn.torch.dqn_torch_learner import DQNTorchLearner
from ray.rllib.algorithms.ppo.torch.ppo_torch_learner import PPOTorchLearner
from ray.rllib.connectors.common.add_observations_from_episodes_to_batch import (
AddObservationsFromEpisodesToBatch,
)
from ray.rllib.connectors.common.numpy_to_tensor import NumpyToTensor
from ray.rllib.connectors.connector_v2 import ConnectorV2
from ray.rllib.connectors.learner.add_next_observations_from_episodes_to_train_batch import ( # noqa
AddNextObservationsFromEpisodesToTrainBatch,
)
from ray.rllib.core import DEFAULT_MODULE_ID, Columns
from ray.rllib.core.learner.torch.torch_learner import TorchLearner
from ray.rllib.core.rl_module.rl_module import RLModule
from ray.rllib.utils.typing import EpisodeType
ICM_MODULE_ID = "_intrinsic_curiosity_model"
class DQNTorchLearnerWithCuriosity(DQNTorchLearner):
def build(self) -> None:
super().build()
add_intrinsic_curiosity_connectors(self)
class PPOTorchLearnerWithCuriosity(PPOTorchLearner):
def build(self) -> None:
super().build()
add_intrinsic_curiosity_connectors(self)
def add_intrinsic_curiosity_connectors(torch_learner: TorchLearner) -> None:
"""Adds two connector pieces to the Learner pipeline, needed for ICM training.
- The `AddNextObservationsFromEpisodesToTrainBatch` connector makes sure the train
batch contains the NEXT_OBS for ICM's forward- and inverse dynamics net training.
- The `IntrinsicCuriosityModelConnector` piece computes intrinsic rewards from the
ICM and adds the results to the extrinsic reward of the main module's train batch.
Args:
torch_learner: The TorchLearner, to whose Learner pipeline the two ICM connector
pieces should be added.
"""
learner_config_dict = torch_learner.config.learner_config_dict
# Assert, we are only training one policy (RLModule) and we have the ICM
# in our MultiRLModule.
assert (
len(torch_learner.module) == 2
and DEFAULT_MODULE_ID in torch_learner.module
and ICM_MODULE_ID in torch_learner.module
)
# Make sure both curiosity loss settings are explicitly set in the
# `learner_config_dict`.
if (
"forward_loss_weight" not in learner_config_dict
or "intrinsic_reward_coeff" not in learner_config_dict
):
raise KeyError(
"When using the IntrinsicCuriosityTorchLearner, both `forward_loss_weight` "
" and `intrinsic_reward_coeff` must be part of your config's "
"`learner_config_dict`! Add these values through: `config.training("
"learner_config_dict={'forward_loss_weight': .., 'intrinsic_reward_coeff': "
"..})`."
)
if torch_learner.config.add_default_connectors_to_learner_pipeline:
# Prepend a "add-NEXT_OBS-from-episodes-to-train-batch" connector piece
# (right after the corresponding "add-OBS-..." default piece).
torch_learner._learner_connector.insert_after(
AddObservationsFromEpisodesToBatch,
AddNextObservationsFromEpisodesToTrainBatch(),
)
# Append the ICM connector, computing intrinsic rewards and adding these to
# the main model's extrinsic rewards.
torch_learner._learner_connector.insert_after(
NumpyToTensor,
IntrinsicCuriosityModelConnector(
intrinsic_reward_coeff=(
torch_learner.config.learner_config_dict["intrinsic_reward_coeff"]
)
),
)
class IntrinsicCuriosityModelConnector(ConnectorV2):
"""Learner ConnectorV2 piece to compute intrinsic rewards based on an ICM.
For more details, see here:
[1] Curiosity-driven Exploration by Self-supervised Prediction
Pathak, Agrawal, Efros, and Darrell - UC Berkeley - ICML 2017.
https://arxiv.org/pdf/1705.05363.pdf
This connector piece:
- requires two RLModules to be present in the MultiRLModule:
DEFAULT_MODULE_ID (the policy model to be trained) and ICM_MODULE_ID (the instrinsic
curiosity architecture).
- must be located toward the end of to your Learner pipeline (after the
`NumpyToTensor` piece) in order to perform a forward pass on the ICM model with the
readily compiled batch and a following forward-loss computation to get the intrinsi
rewards.
- these intrinsic rewards will then be added to the (extrinsic) rewards in the main
model's train batch.
"""
def __init__(
self,
input_observation_space: Optional[gym.Space] = None,
input_action_space: Optional[gym.Space] = None,
*,
intrinsic_reward_coeff: float,
**kwargs,
):
"""Initializes a CountBasedCuriosity instance.
Args:
intrinsic_reward_coeff: The weight with which to multiply the intrinsic
reward before adding it to the extrinsic rewards of the main model.
"""
super().__init__(input_observation_space, input_action_space)
self.intrinsic_reward_coeff = intrinsic_reward_coeff
def __call__(
self,
*,
rl_module: RLModule,
batch: Any,
episodes: List[EpisodeType],
explore: Optional[bool] = None,
shared_data: Optional[dict] = None,
**kwargs,
) -> Any:
# Assert that the batch is ready.
assert DEFAULT_MODULE_ID in batch and ICM_MODULE_ID not in batch
assert (
Columns.OBS in batch[DEFAULT_MODULE_ID]
and Columns.NEXT_OBS in batch[DEFAULT_MODULE_ID]
)
# TODO (sven): We are performing two forward passes per update right now.
# Once here in the connector (w/o grad) to just get the intrinsic rewards
# and once in the learner to actually compute the ICM loss and update the ICM.
# Maybe we can save one of these, but this would currently harm the DDP-setup
# for multi-GPU training.
with torch.no_grad():
# Perform ICM forward pass.
fwd_out = rl_module[ICM_MODULE_ID].forward_train(batch[DEFAULT_MODULE_ID])
# Add the intrinsic rewards to the main module's extrinsic rewards.
batch[DEFAULT_MODULE_ID][Columns.REWARDS] += (
self.intrinsic_reward_coeff * fwd_out[Columns.INTRINSIC_REWARDS]
)
# Duplicate the batch such that the ICM also has data to learn on.
batch[ICM_MODULE_ID] = batch[DEFAULT_MODULE_ID]
return batch
@@ -0,0 +1,83 @@
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
from ray.rllib.algorithms.ppo.torch.ppo_torch_learner import PPOTorchLearner
from ray.rllib.core.learner.torch.torch_learner import TorchLearner
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
from ray.rllib.utils.typing import ModuleID
torch, _ = try_import_torch()
class PPOTorchLearnerWithSeparateVfOptimizer(PPOTorchLearner):
"""A custom PPO torch learner with 2 optimizers, for policy and value function.
Overrides the Learner's standard `configure_optimizers_for_module()` method to
register the additional vf optimizer.
The standard PPOLearner only uses a single optimizer (and single learning rate) to
update the model, regardless of whether the value function network
is separate from the policy network or whether they have shared components.
We may leave the loss function of PPO completely untouched. It already returns a
sum of policy loss and vf loss (and entropy loss), and thus - given that the neural
networks used to compute each of these terms are separate and don't share any
components - gradients are computed separately per neural network (policy vs vf)
and applied separately through the two optimizers.
"""
@override(TorchLearner)
def configure_optimizers_for_module(
self,
module_id: ModuleID,
config: "AlgorithmConfig" = None,
) -> None:
"""Registers 2 optimizers for the given ModuleID with this Learner."""
# Make sure the RLModule has the correct properties.
module = self.module[module_id]
# TODO (sven): We should move this into a new `ValueFunction` API, which
# should have a `get_value_function_params` method. This way, any custom
# RLModule that implements this API can be used here, not just the standard
# PPO one.
assert (
hasattr(module, "pi")
and hasattr(module, "vf")
and hasattr(module, "encoder")
and hasattr(module.encoder, "actor_encoder")
and hasattr(module.encoder, "critic_encoder")
)
assert config.model_config["vf_share_layers"] is False
# Get all policy-related parameters from the RLModule.
pi_params = (
# Actor encoder and policy head.
self.get_parameters(self.module[module_id].encoder.actor_encoder)
+ self.get_parameters(self.module[module_id].pi)
)
# Register the policy optimizer.
self.register_optimizer(
module_id=module_id,
optimizer_name="optim_for_pi",
optimizer=torch.optim.Adam(params=pi_params),
params=pi_params,
# For the policy learning rate, we use the "main" lr in the AlgorithmConfig.
lr_or_lr_schedule=config.lr,
)
# Get all value function-related parameters from the RLModule.
vf_params = (
# Critic encoder and value head.
self.get_parameters(self.module[module_id].encoder.critic_encoder)
+ self.get_parameters(self.module[module_id].vf)
)
# Register the value function optimizer.
self.register_optimizer(
module_id=module_id,
optimizer_name="optim_for_vf",
optimizer=torch.optim.Adam(params=vf_params),
params=vf_params,
# For the value function learning rate, we use a user-provided custom
# setting in the `learner_config_dict` in the AlgorithmConfig. If this
# is not provided, use the same lr as for the policy optimizer.
lr_or_lr_schedule=config.learner_config_dict.get("lr_vf", config.lr),
)
@@ -0,0 +1,71 @@
from typing import TYPE_CHECKING, Any, Dict
import numpy as np
import torch
from ray.rllib.connectors.learner import ComputeReturnsToGo
from ray.rllib.core.columns import Columns
from ray.rllib.core.learner.learner import Learner
from ray.rllib.core.learner.torch.torch_learner import TorchLearner
from ray.rllib.utils.annotations import override
from ray.rllib.utils.numpy import convert_to_numpy
from ray.rllib.utils.typing import ModuleID, TensorType
if TYPE_CHECKING:
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
class VPGTorchLearner(TorchLearner):
@override(TorchLearner)
def build(self) -> None:
super().build()
# Prepend the returns-to-go connector piece to have that information
# available in the train batch.
if self.config.add_default_connectors_to_learner_pipeline:
self._learner_connector.prepend(ComputeReturnsToGo(gamma=self.config.gamma))
@override(TorchLearner)
def compute_loss_for_module(
self,
*,
module_id: ModuleID,
config: "AlgorithmConfig",
batch: Dict[str, Any],
fwd_out: Dict[str, TensorType],
) -> TensorType:
rl_module = self.module[module_id]
# Create the action distribution from the parameters output by the RLModule.
action_dist_inputs = fwd_out[Columns.ACTION_DIST_INPUTS]
action_dist_class = rl_module.get_train_action_dist_cls()
action_dist = action_dist_class.from_logits(action_dist_inputs)
# Compute log probabilities of the actions taken during sampling.
log_probs = action_dist.logp(batch[Columns.ACTIONS])
# Compute the policy gradient loss.
# Since we're not using a baseline, we use returns to go directly.
loss = -torch.mean(log_probs * batch[Columns.RETURNS_TO_GO])
# Just for exercise, log the average return to go per discrete action.
for act, ret_to_go in zip(batch[Columns.ACTIONS], batch[Columns.RETURNS_TO_GO]):
self.metrics.log_value(
key=(module_id, f"action_{act}_return_to_go_mean"),
value=ret_to_go,
reduce="mean",
)
return loss
@override(Learner)
def after_gradient_based_update(self, *, timesteps):
# This is to check if in the multi-gpu case, the weights across workers are
# the same. Only for testing purposes.
if self.config.report_mean_weights:
for module_id in self.module.keys():
parameters = convert_to_numpy(
self.get_parameters(self.module[module_id])
)
mean_ws = np.mean([w.mean() for w in parameters])
self.metrics.log_value((module_id, "mean_weight"), mean_ws, window=1)
@@ -0,0 +1,32 @@
from ray.rllib.core.learner.torch.torch_learner import TorchLearner
from ray.rllib.examples.learners.classes.vpg_torch_learner import VPGTorchLearner
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
torch, _ = try_import_torch()
class VPGTorchLearnerSharedOptimizer(VPGTorchLearner):
"""
In order for a shared module to learn properly, a special, multi-agent Learner
has been set up. There is only one optimizer (used to train all submodules, e.g.
a shared encoder and n policy nets), in order to not destabilize learning. The
latter may happen if more than one optimizer would try to alternatingly optimize
the same shared submodule.
"""
@override(TorchLearner)
def configure_optimizers(self) -> None:
# Get and aggregate parameters for every module
param_list = []
for m in self.module.values():
if self.rl_module_is_compatible(m):
param_list.extend(m.parameters())
self.register_optimizer(
optimizer_name="shared_optimizer",
optimizer=torch.optim.Adam(params=param_list),
params=param_list,
# For the policy learning rate, we use the "main" lr in the AlgorithmConfig.
lr_or_lr_schedule=self.config.lr,
)