Rate this Page

Source code for torchrl.modules.tensordict_module.world_models

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import annotations

from tensordict import TensorDictBase
from tensordict.nn import TensorDictModule, TensorDictModuleBase, TensorDictSequential


[docs] class WorldModel(TensorDictModuleBase): """A general, composable world model for model-based RL. ``WorldModel`` wraps an encoder, a dynamics model, a reward head, and optionally a done head and a decoder into a single TensorDict-native module. It owns *prediction and composition* — encoding observations, advancing latent state, predicting rewards and termination — and exposes named shortcuts (:meth:`encode`, :meth:`step`, :meth:`decode`) so each component can be invoked individually. Rollout semantics live elsewhere: wrap a ``WorldModel`` in :class:`~torchrl.envs.model_based.WorldModelEnv` (or another :class:`~torchrl.envs.model_based.ModelBasedEnvBase` subclass) and use :meth:`~torchrl.envs.EnvBase.rollout` to generate imagined trajectories. This keeps env-level concerns — reset/step contract, ``done`` handling, spec validation — out of the prediction module and avoids forking a second rollout implementation with subtly different semantics. The module is key-driven: each component communicates through named TensorDict keys defined by its ``in_keys`` / ``out_keys``. No specific latent representation, observation space, or dynamics architecture is assumed. Args: encoder (TensorDictModule): maps an observation to a latent representation, e.g. ``obs -> latent``. dynamics (TensorDictModule): advances the latent state given an action, e.g. ``(latent, action) -> ("next", latent)``. reward_head (TensorDictModule): predicts the reward from the next latent, e.g. ``("next", latent) -> ("next", "reward")``. done_head (TensorDictModule, optional): predicts the done flag, e.g. ``("next", latent) -> ("next", "done")``. When provided, :meth:`rollout` can terminate early when any trajectory is done. decoder (TensorDictModule, optional): reconstructs an observation from a latent, e.g. ``latent -> obs_recon``. Required to call :meth:`decode`. Examples: >>> import torch >>> from tensordict import TensorDict >>> from tensordict.nn import TensorDictModule >>> from torchrl.modules import WorldModel >>> obs_dim, latent_dim, action_dim = 8, 4, 2 >>> encoder = TensorDictModule( ... torch.nn.Linear(obs_dim, latent_dim), ... in_keys=["observation"], ... out_keys=["latent"], ... ) >>> dynamics = TensorDictModule( ... torch.nn.Linear(latent_dim + action_dim, latent_dim), ... in_keys=["latent", "action"], ... out_keys=[("next", "latent")], ... ) >>> reward_head = TensorDictModule( ... torch.nn.Linear(latent_dim, 1), ... in_keys=[("next", "latent")], ... out_keys=[("next", "reward")], ... ) >>> world_model = WorldModel(encoder, dynamics, reward_head) >>> td = TensorDict( ... {"observation": torch.randn(2, obs_dim), "action": torch.randn(2, action_dim)}, ... batch_size=[2], ... ) >>> out = world_model(td) >>> out.keys() dict_keys(['observation', 'action', 'latent', 'next']) """ def __init__( self, encoder: TensorDictModule, dynamics: TensorDictModule, reward_head: TensorDictModule, *, done_head: TensorDictModule | None = None, decoder: TensorDictModule | None = None, ) -> None: super().__init__() self.encoder = encoder self.dynamics = dynamics self.reward_head = reward_head self.done_head = done_head self.decoder = decoder # Full forward sequence (encode + step). full_modules = [encoder, dynamics, reward_head] if done_head is not None: full_modules.append(done_head) if decoder is not None: full_modules.append(decoder) self._full_seq = TensorDictSequential(*full_modules) self.in_keys = self._full_seq.in_keys self.out_keys = self._full_seq.out_keys # Step sequence (dynamics + heads, no encoder). step_modules = [dynamics, reward_head] if done_head is not None: step_modules.append(done_head) if decoder is not None: step_modules.append(decoder) self._step_seq = TensorDictSequential(*step_modules)
[docs] def forward(self, tensordict: TensorDictBase) -> TensorDictBase: """Run the full pipeline: encoder -> dynamics -> reward_head -> [done_head] -> [decoder].""" return self._full_seq(tensordict)
[docs] def encode(self, tensordict: TensorDictBase) -> TensorDictBase: """Encode an observation into the latent space.""" return self.encoder(tensordict)
[docs] def step(self, tensordict: TensorDictBase) -> TensorDictBase: """Take one imagined step: dynamics -> reward_head -> [done_head] -> [decoder]. The encoder is *not* called; the tensordict must already contain the current latent state as produced by :meth:`encode` or a previous call to :meth:`step`. """ return self._step_seq(tensordict)
[docs] def decode(self, tensordict: TensorDictBase) -> TensorDictBase: """Decode a latent back to observation space. Raises: RuntimeError: if no ``decoder`` was provided at construction. """ if self.decoder is None: raise RuntimeError( "WorldModel.decode requires a decoder module. Pass decoder= at construction." ) return self.decoder(tensordict)
@property def step_module(self) -> TensorDictSequential: """The step-only sequence (dynamics + heads, no encoder). Exposed as a public attribute so :class:`~torchrl.envs.model_based.WorldModelEnv` and other model-based env wrappers can drive the world model in latent space, one step at a time, without rerunning the encoder on every step. """ return self._step_seq
[docs] class WorldModelWrapper(TensorDictSequential): """World model wrapper. This module wraps together a transition model and a reward model. The transition model is used to predict an imaginary world state. The reward model is used to predict the reward of the imagined transition. Args: transition_model (TensorDictModule): a transition model that generates a new world states. reward_model (TensorDictModule): a reward model, that reads the world state and returns a reward. """ def __init__( self, transition_model: TensorDictModule, reward_model: TensorDictModule ): super().__init__(transition_model, reward_model)
[docs] def get_transition_model_operator(self) -> TensorDictModule: """Returns a transition operator that maps either an observation to a world state or a world state to the next world state.""" return self.module[0]
[docs] def get_reward_operator(self) -> TensorDictModule: """Returns a reward operator that maps a world state to a reward.""" return self.module[1]