Files
2026-07-13 13:17:40 +08:00

127 lines
4.6 KiB
Python

from typing import TYPE_CHECKING, Any, Dict, List, Optional
import gymnasium as gym
from ray.rllib.connectors.connector_v2 import ConnectorV2
from ray.rllib.core import DEFAULT_MODULE_ID
from ray.rllib.core.columns import Columns
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModule
from ray.rllib.core.rl_module.rl_module import RLModule
from ray.rllib.utils.annotations import override
from ray.rllib.utils.torch_utils import convert_to_torch_tensor
from ray.rllib.utils.typing import EpisodeType
from ray.util.annotations import PublicAPI
if TYPE_CHECKING:
from ray.rllib.utils.typing import DeviceType
@PublicAPI(stability="alpha")
class NumpyToTensor(ConnectorV2):
"""Converts numpy arrays across the entire input data into (framework) tensors.
The framework information is received via the provided `rl_module` arg in the
`__call__()` method.
Note: This is one of the default env-to-module or Learner ConnectorV2 pieces that
are added automatically by RLlib into every env-to-module/Learner connector
pipeline, unless `config.add_default_connectors_to_env_to_module_pipeline` or
`config.add_default_connectors_to_learner_pipeline ` are set to
False.
The default env-to-module connector pipeline is:
[
[0 or more user defined ConnectorV2 pieces],
AddObservationsFromEpisodesToBatch,
AddTimeDimToBatchAndZeroPad,
AddStatesFromEpisodesToBatch,
AgentToModuleMapping, # only in multi-agent setups!
BatchIndividualItems,
NumpyToTensor,
]
The default Learner connector pipeline is:
[
[0 or more user defined ConnectorV2 pieces],
AddObservationsFromEpisodesToBatch,
AddColumnsFromEpisodesToTrainBatch,
AddTimeDimToBatchAndZeroPad,
AddStatesFromEpisodesToBatch,
AgentToModuleMapping, # only in multi-agent setups!
BatchIndividualItems,
NumpyToTensor,
]
This ConnectorV2:
- Loops through the input `data` and converts all found numpy arrays into
framework-specific tensors (possibly on a GPU).
"""
def __init__(
self,
input_observation_space: Optional[gym.Space] = None,
input_action_space: Optional[gym.Space] = None,
*,
pin_memory: bool = False,
device: Optional["DeviceType"] = None,
**kwargs,
):
"""Initializes a NumpyToTensor instance.
Args:
pin_memory: Whether to pin memory when creating (torch) tensors.
If None (default), pins memory if `as_learner_connector` is True,
otherwise doesn't pin memory.
device: An optional device to move the resulting tensors to. If not
provided, all data will be left on the CPU.
**kwargs:
"""
super().__init__(
input_observation_space=input_observation_space,
input_action_space=input_action_space,
**kwargs,
)
self._pin_memory = pin_memory
self._device = device
@override(ConnectorV2)
def __call__(
self,
*,
rl_module: RLModule,
batch: Dict[str, Any],
episodes: List[EpisodeType],
explore: Optional[bool] = None,
shared_data: Optional[dict] = None,
**kwargs,
) -> Any:
is_single_agent = False
is_multi_rl_module = isinstance(rl_module, MultiRLModule)
# `data` already a ModuleID to batch mapping format.
if not (is_multi_rl_module and all(c in rl_module._rl_modules for c in batch)):
is_single_agent = True
batch = {DEFAULT_MODULE_ID: batch}
for module_id, module_data in batch.copy().items():
# If `rl_module` is None, leave data in numpy format.
if rl_module is not None:
infos = module_data.pop(Columns.INFOS, None)
if rl_module.framework == "torch":
module_data = convert_to_torch_tensor(
module_data, pin_memory=self._pin_memory, device=self._device
)
else:
raise ValueError(
"`NumpyToTensor`does NOT support frameworks other than torch! "
f"Your current framework is {rl_module.framework}"
)
if infos is not None:
module_data[Columns.INFOS] = infos
# Early out with data under(!) `DEFAULT_MODULE_ID`, b/c we are in plain
# single-agent mode.
if is_single_agent:
return module_data
batch[module_id] = module_data
return batch