Files
ray-project--ray/rllib/core/learner/differentiable_learner_config.py
2026-07-13 13:17:40 +08:00

149 lines
6.1 KiB
Python

from dataclasses import dataclass, fields
from typing import Callable, List, Optional, Union
import gymnasium as gym
from ray.rllib.connectors.connector_v2 import ConnectorV2
from ray.rllib.core.learner.differentiable_learner import DifferentiableLearner
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModule
from ray.rllib.utils.typing import DeviceType, ModuleID
@dataclass
class DifferentiableLearnerConfig:
"""Configures a `DifferentiableLearner`."""
# TODO (simon): We implement only for `PyTorch`, so maybe we use here directly
# TorchDifferentiableLearner` and check for this?
# The `DifferentiableLearner` class. Must be derived from `DifferentiableLearner`.
learner_class: Callable
learner_connector: Optional[
Callable[["RLModule"], Union["ConnectorV2", List["ConnectorV2"]]]
] = None
add_default_connectors_to_learner_pipeline: bool = True
is_multi_agent: bool = False
policies_to_update: List[ModuleID] = None
# The learning rate to use for the nested update. Note, in the default case this
# learning rate is only used to update parameters in a functional form, i.e. the
# `RLModule`'s stateful parameters are only updated in the `MetaLearner`. Different
# logic can be implemented in customized `DifferentiableLearner`s.
lr: float = 3e-5
# TODO (simon): Add further hps like clip_grad, ...
# The total number of minibatches to be formed from the batch per learner, e.g.
# setting `train_batch_size_per_learner=10` and `num_total_minibatches` to 2
# runs 2 SGD minibatch updates with a batch of 5 per training iteration.
num_total_minibatches: int = 0
# The number of epochs per training iteration.
num_epochs: int = 1
# The minibatch size per SGD minibatch update, e.g. with a `train_batch_size_per_learner=10`
# and a `minibatch_size=2` the training step runs 5 SGD minibatch updates with minibatches
# of 2.
minibatch_size: int = None
# If the batch should be shuffled between epochs.
shuffle_batch_per_epoch: bool = False
def __post_init__(self):
"""Additional initialization processes."""
# Ensure we have a `DifferentiableLearner` class.
if not issubclass(self.learner_class, DifferentiableLearner):
raise ValueError(
"`learner_class` must be a subclass of `DifferentiableLearner "
f"but is {self.learner_class}."
)
def build_learner_connector(
self,
input_observation_space: Optional[gym.spaces.Space],
input_action_space: Optional[gym.spaces.Space],
device: Optional[DeviceType] = None,
):
from ray.rllib.connectors.learner import (
AddColumnsFromEpisodesToTrainBatch,
AddObservationsFromEpisodesToBatch,
AddStatesFromEpisodesToBatch,
AddTimeDimToBatchAndZeroPad,
AgentToModuleMapping,
BatchIndividualItems,
LearnerConnectorPipeline,
NumpyToTensor,
)
custom_connectors = []
# Create a learner connector pipeline (including RLlib's default
# learner connector piece) and return it.
if self.learner_connector is not None:
val_ = self.learner_connector(
input_observation_space,
input_action_space,
# device, # TODO (sven): Also pass device into custom builder.
)
from ray.rllib.connectors.connector_v2 import ConnectorV2
# ConnectorV2 (piece or pipeline).
if isinstance(val_, ConnectorV2):
custom_connectors = [val_]
# Sequence of individual ConnectorV2 pieces.
elif isinstance(val_, (list, tuple)):
custom_connectors = list(val_)
# Unsupported return value.
else:
raise ValueError(
"`AlgorithmConfig.training(learner_connector=..)` must return "
"a ConnectorV2 object or a list thereof (to be added to a "
f"pipeline)! Your function returned {val_}."
)
pipeline = LearnerConnectorPipeline(
connectors=custom_connectors,
input_observation_space=input_observation_space,
input_action_space=input_action_space,
)
if self.add_default_connectors_to_learner_pipeline:
# Append OBS handling.
pipeline.append(
AddObservationsFromEpisodesToBatch(as_learner_connector=True)
)
# Append all other columns handling.
pipeline.append(AddColumnsFromEpisodesToTrainBatch())
# Append time-rank handler.
pipeline.append(AddTimeDimToBatchAndZeroPad(as_learner_connector=True))
# Append STATE_IN/STATE_OUT handler.
pipeline.append(AddStatesFromEpisodesToBatch(as_learner_connector=True))
# If multi-agent -> Map from AgentID-based data to ModuleID based data.
if self.is_multi_agent:
pipeline.append(
AgentToModuleMapping(
rl_module_specs=(
self.rl_module_spec.rl_module_specs
if isinstance(self.rl_module_spec, MultiRLModuleSpec)
else set(self.policies)
),
agent_to_module_mapping_fn=self.policy_mapping_fn,
)
)
# Batch all data.
pipeline.append(BatchIndividualItems(multi_agent=self.is_multi_agent))
# Convert to Tensors.
pipeline.append(NumpyToTensor(as_learner_connector=True, device=device))
return pipeline
def update_from_kwargs(self, **kwargs):
"""Sets all slots with values defined in `kwargs`."""
# Get all field names (i.e., slot names).
field_names = {f.name for f in fields(self)}
for key, value in kwargs.items():
if key in field_names:
setattr(self, key, value)