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,88 @@
from typing import List
import ray
from ray.rllib.algorithms import AlgorithmConfig
from ray.rllib.algorithms.appo import APPO
from ray.rllib.env.env_runner_group import EnvRunnerGroup
@ray.remote
class SharedDataActor:
"""Simple example of an actor that's accessible from all other actors of an algo.
Exposes remote APIs `put` and `get` to other actors for storing and retrieving
arbitrary data.
"""
def __init__(self):
self.storage = {}
def get(self, key, delete: bool = False):
value = self.storage.get(key)
if delete and key in self.storage:
del self.storage[key]
return value
def put(self, key, value):
self.storage[key] = value
def get_state(self):
return self.storage
def set_state(self, state):
self.storage = state
class APPOWithSharedDataActor(APPO):
def setup(self, config: AlgorithmConfig):
# Call to parent `setup`.
super().setup(config)
# Create shared data actor.
self.shared_data_actor = SharedDataActor.remote()
# Share the actor with all other relevant actors.
def _share(actor, shared_act=self.shared_data_actor):
actor._shared_data_actor = shared_act
# Also add shared actor reference to all the learner connector pieces,
# if applicable.
if hasattr(actor, "_learner_connector") and actor._learner_connector:
for conn in actor._learner_connector:
conn._shared_data_actor = shared_act
self.env_runner_group.foreach_env_runner(func=_share)
if self.eval_env_runner_group:
self.eval_env_runner_group.foreach_env_runner(func=_share)
self.learner_group.foreach_learner(func=_share)
if self._aggregator_actor_manager:
self._aggregator_actor_manager.foreach_actor(func=_share)
def get_state(self, *args, **kwargs):
state = super().get_state(*args, **kwargs)
# Add shared actor's state.
state["shared_data_actor"] = ray.get(self.shared_data_actor.get_state.remote())
return state
def set_state(self, state, *args, **kwargs):
super().set_state(state, *args, **kwargs)
# Set shared actor's state.
if "shared_data_actor" in state:
self.shared_data_actor.set_state.remote(state["shared_data_actor"])
def restore_env_runners(self, env_runner_group: EnvRunnerGroup) -> List[int]:
restored = super().restore_env_runners(env_runner_group)
# For the restored EnvRunners, send them the latest shared, global state
# from the `SharedDataActor`.
for restored_idx in restored:
state_ref = self.shared_data_actor.get.remote(
key=f"EnvRunner_{restored_idx}"
)
env_runner_group.foreach_env_runner(
lambda env_runner, state=state_ref: env_runner._global_state,
remote_worker_ids=[restored_idx],
timeout_seconds=0.0,
)
return restored
@@ -0,0 +1,35 @@
from typing import TYPE_CHECKING, Any, Dict
from ray.rllib.core.learner.torch.torch_differentiable_learner import (
TorchDifferentiableLearner,
)
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
from ray.rllib.utils.typing import ModuleID, TensorType
if TYPE_CHECKING:
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
torch, nn = try_import_torch()
class MAMLTorchDifferentiableLearner(TorchDifferentiableLearner):
"""A `TorchDifferentiableLearner` to perform MAML learning.
This `TorchDifferentiableLearner`
- defines a funcitonal MSE loss for learning simple (here non-linear)
prediction.
"""
@override(TorchDifferentiableLearner)
def compute_loss_for_module(
self,
*,
module_id: ModuleID,
config: "AlgorithmConfig",
batch: Dict[str, Any],
fwd_out: Dict[str, TensorType],
) -> TensorType:
"""Defines a simple MSE prediction loss for continuous task."""
return nn.functional.mse_loss(fwd_out["y_pred"], batch["y"])
@@ -0,0 +1,39 @@
from ray.rllib.core.columns import Columns
from ray.rllib.core.rl_module.torch.torch_rl_module import TorchRLModule
from ray.rllib.utils.framework import try_import_torch
torch, nn = try_import_torch()
class DifferentiableTorchRLModule(TorchRLModule):
"""Differentiable neural network to learn sinusoid curves.
This `TorchRLModule`:
- defines a simple neural network to learn sinusoid curves with two
feed forward layern and ReLU activations,
- defines a differentiable `forward` call by overriding the `_forward`
method (which is implicitly used by the module's `forward` method); this
enables `torch.func.functional_call?` to work.
"""
def setup(self):
"""Sets up a simple neural network
The network contains two hidden layers and ReLU activations. Note,
input and output are single dimensional b/c the sinusoid curve is.
"""
self.net = nn.Sequential(
nn.Linear(1, 40), nn.ReLU(), nn.Linear(40, 40), nn.ReLU(), nn.Linear(40, 1)
)
def _forward(self, batch, **kwargs):
"""Defines method to be called for general forward path.
Note, it is important that the `RLModule.forward` method contains the
logic to be used for training forward pass b/c otherwise the functional
call via `torch.func.functional_call` will not work. See for reference
https://pytorch.org/docs/stable/generated/torch.func.functional_call.html.
"""
outs = {}
outs["y_pred"] = self.net(batch[Columns.OBS])
return outs
@@ -0,0 +1,38 @@
from typing import TYPE_CHECKING, Any, Dict, List
from ray.rllib.core.learner.torch.torch_meta_learner import TorchMetaLearner
from ray.rllib.utils.annotations import override
from ray.rllib.utils.framework import try_import_torch
from ray.rllib.utils.typing import ModuleID, TensorType
if TYPE_CHECKING:
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig
torch, nn = try_import_torch()
class MAMLTorchMetaLearner(TorchMetaLearner):
"""A `TorchMetaLearner` to perform MAML learning.
This `TorchMetaLearner`
- defines a MSE loss for learning simple (here non-linear) prediction.
"""
@override(TorchMetaLearner)
def compute_loss_for_module(
self,
*,
module_id: ModuleID,
config: "AlgorithmConfig",
batch: Dict[str, Any],
fwd_out: Dict[str, TensorType],
others_loss_per_module: List[Dict[ModuleID, TensorType]] = None,
) -> TensorType:
"""Defines a simple MSE prediction loss for continuous task.
Note, MAML does not need the losses from the registered differentiable
learners (contained in `others_loss_per_module`) b/c it computes a test
loss on an unseen data batch.
"""
# Use a simple MSE loss for the meta learning task.
return torch.nn.functional.mse_loss(fwd_out["y_pred"], batch["y"])
+175
View File
@@ -0,0 +1,175 @@
import tree # pip install dm_tree
from typing_extensions import Self
from ray.rllib.algorithms import Algorithm
from ray.rllib.algorithms.algorithm_config import AlgorithmConfig, NotProvided
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.utils.annotations import override
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
ENV_RUNNER_SAMPLING_TIMER,
LEARNER_RESULTS,
LEARNER_UPDATE_TIMER,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
SYNCH_WORKER_WEIGHTS_TIMER,
TIMERS,
)
class VPGConfig(AlgorithmConfig):
"""A simple VPG (vanilla policy gradient) algorithm w/o value function support.
Use for testing purposes only!
This Algorithm should use the VPGTorchLearner and VPGTorchRLModule
"""
# A test setting to activate metrics on mean weights.
report_mean_weights: bool = True
def __init__(self, algo_class=None):
super().__init__(algo_class=algo_class or VPG)
# VPG specific settings.
self.num_episodes_per_train_batch = 10
# Note that we don't have to set this here, because we tell the EnvRunners
# explicitly to sample entire episodes. However, for good measure, we change
# this setting here either way.
self.batch_mode = "complete_episodes"
# VPG specific defaults (from AlgorithmConfig).
self.num_env_runners = 1
@override(AlgorithmConfig)
def training(self, *, num_episodes_per_train_batch=NotProvided, **kwargs) -> Self:
"""Sets the training related configuration.
Args:
num_episodes_per_train_batch: The number of complete episodes per train
batch. VPG requires entire episodes to be sampled from the EnvRunners.
For environments with varying episode lengths, this leads to varying
batch sizes (in timesteps) as well possibly causing slight learning
instabilities. However, for simplicity reasons, we stick to collecting
always exactly n episodes per training update.
Returns:
This updated AlgorithmConfig object.
"""
# Pass kwargs onto super's `training()` method.
super().training(**kwargs)
if num_episodes_per_train_batch is not NotProvided:
self.num_episodes_per_train_batch = num_episodes_per_train_batch
return self
@override(AlgorithmConfig)
def get_default_rl_module_spec(self):
if self.framework_str == "torch":
from ray.rllib.examples.rl_modules.classes.vpg_torch_rlm import (
VPGTorchRLModule,
)
spec = RLModuleSpec(
module_class=VPGTorchRLModule,
model_config={"hidden_dim": 64},
)
else:
raise ValueError(f"Unsupported framework: {self.framework_str}")
return spec
@override(AlgorithmConfig)
def get_default_learner_class(self):
if self.framework_str == "torch":
from ray.rllib.examples.learners.classes.vpg_torch_learner import (
VPGTorchLearner,
)
return VPGTorchLearner
else:
raise ValueError(f"Unsupported framework: {self.framework_str}")
class VPG(Algorithm):
@classmethod
@override(Algorithm)
def get_default_config(cls) -> VPGConfig:
return VPGConfig()
@override(Algorithm)
def training_step(self) -> None:
"""Override of the training_step method of `Algorithm`.
Runs the following steps per call:
- Sample B timesteps (B=train batch size). Note that we don't sample complete
episodes due to simplicity. For an actual VPG algo, due to the loss computation,
you should always sample only completed episodes.
- Send the collected episodes to the VPG LearnerGroup for model updating.
- Sync the weights from LearnerGroup to all EnvRunners.
"""
# Sample.
with self.metrics.log_time((TIMERS, ENV_RUNNER_SAMPLING_TIMER)):
episodes, env_runner_results = self._sample_episodes()
# Merge results from n parallel sample calls into self's metrics logger.
self.metrics.aggregate(env_runner_results, key=ENV_RUNNER_RESULTS)
# Just for demonstration purposes, log the number of time steps sampled in this
# `training_step` round.
# Mean over a window of 100:
self.metrics.log_value(
"episode_timesteps_sampled_mean_win100",
sum(map(len, episodes)),
reduce="mean",
window=100,
)
# Exponential Moving Average (EMA) with coeff=0.1:
self.metrics.log_value(
"episode_timesteps_sampled_ema",
sum(map(len, episodes)),
ema_coeff=0.1, # <- weight of new value; weight of old avg=1.0-ema_coeff
)
# Update model.
with self.metrics.log_time((TIMERS, LEARNER_UPDATE_TIMER)):
learner_results = self.learner_group.update(
episodes=episodes,
timesteps={
NUM_ENV_STEPS_SAMPLED_LIFETIME: (
self.metrics.peek(
(ENV_RUNNER_RESULTS, NUM_ENV_STEPS_SAMPLED_LIFETIME)
)
),
},
)
# Merge results from m parallel update calls into self's metrics logger.
self.metrics.aggregate(learner_results, key=LEARNER_RESULTS)
# Sync weights.
with self.metrics.log_time((TIMERS, SYNCH_WORKER_WEIGHTS_TIMER)):
self.env_runner_group.sync_weights(
from_worker_or_learner_group=self.learner_group,
inference_only=True,
)
def _sample_episodes(self):
# How many episodes to sample from each EnvRunner?
num_episodes_per_env_runner = self.config.num_episodes_per_train_batch // (
self.config.num_env_runners or 1
)
# Send parallel remote requests to sample and get the metrics.
sampled_data = self.env_runner_group.foreach_env_runner(
# Return tuple of [episodes], [metrics] from each EnvRunner.
lambda env_runner: (
env_runner.sample(num_episodes=num_episodes_per_env_runner),
env_runner.get_metrics(),
),
# Loop over remote EnvRunners' `sample()` method in parallel or use the
# local EnvRunner if there aren't any remote ones.
local_env_runner=self.env_runner_group.num_remote_workers() <= 0,
)
# Return one list of episodes and a list of metrics dicts (one per EnvRunner).
episodes = tree.flatten([s[0] for s in sampled_data])
stats_dicts = [s[1] for s in sampled_data]
return episodes, stats_dicts