Skip to content

Search guides, algorithms and the platform.

One interface for every world model

One interface for every world model

Each world model family in Simulusion predicts the future its own way: Dreamer and PlaNet roll a recurrent latent forward, IRIS and Genie generate tokens with a Transformer, DIAMOND samples pixels with a diffusion model, and World Models pairs a VAE with an MDN-RNN. The WorldModel protocol puts one interface over all of them, so code that evaluates, plans with, or plays a world model works with any family.

python
import torch
from simulusion.world_model import as_world_model, load_world_model

model = as_world_model(agent)  # a model you trained in this process
# or: model = load_world_model("runs/dreamer/ckpts/61000_ckpt.pt", "dreamer")

state = model.encode(frames, actions)  # real context -> model state
out = model.step(state, action)  # one imagined step
out.reward, out.terminated  # from the model's own heads
image = model.decode(out.state)  # (B, C, H, W) in [0, 1]

torch.manual_seed(0)
traj = model.rollout(state, planned_actions)  # many steps
traj.frames, traj.rewards, traj.terminated  # (B, H, ...)

The methods

Method Does
encode(frames, actions=None) Turns real context frames, and the actions taken between them, into a model state.
step(state, action) Imagines one step. Returns a Transition: the next state, plus reward and terminated where the model has those heads.
decode(state) The current frame of a state, float (B, C, H, W) in [0, 1].
rollout(state, actions, decode=True) H steps at once; returns a Rollout of frames, rewards and terminations, each (B, H, ...).
save(path) Writes the model to a directory that load_world_model reads back.
spec A WorldModelSpec: what the model reads and predicts (below).

Conventions

The same for every family, so you never need to know a model's internals:

  • Frames are RGB, channel-first and batch-first: (B, T, C, H, W), either float in [0, 1] or uint8 in [0, 255]. A 4-D input is one sequence and a 3-D input one frame. Channel-last input and other sizes are converted to the model's own (spec.obs_shape).
  • Actions between context frames: actions[:, t] is the action taken at frame t, which led to frame t + 1. T frames come with T - 1 actions. Discrete actions are integers (B, ...), continuous ones float (B, ..., A). A single action passed to step is used for the whole batch.
  • State is a dict[str, Tensor] with the batch on dim 0. It is plain data: keep a copy to branch a dream from the same point.
  • Randomness comes from PyTorch's global RNG. torch.manual_seed before encode and rollout makes a dream repeatable.
  • Inference only: methods run without autograd and put the model in eval mode. Training stays with each family's trainer.

What each family provides

model.spec says what a model can do, so generic code can check instead of special-casing families:

Family Actions Context Reward Termination Notes
dreamer (V1, V2) continuous in [-1, 1] 1+ frames, recurrent yes if trained with the discount head
planet continuous, unbounded 1+ frames, recurrent yes no frames are quantised to the trained bit depth
iris discrete up to transformer_timesteps frames yes, on the trained (sign) scale yes sample=False makes steps deterministic
diamond discrete exactly num_conditioning_frames yes, {-1, 0, 1} yes sampling_steps= trades quality for speed
genie discrete, latent (spec.action.latent) up to num_frames no no without actions, Genie labels the context itself
world-models continuous, unbounded 1+ frames, recurrent yes yes temperature= as in the paper

JEPA and DiT have no adapter: I-JEPA learns image representations without actions and DiT generates images from a class label, so neither predicts what an action does. as_world_model says so if you pass one.

Loading checkpoints

load_world_model(path) reads a directory written by save, which records its family in world_model.json. It also reads each family's own training checkpoints when you name the family:

python
load_world_model("runs/dreamer/ckpts/61000_ckpt.pt", "dreamer")  # V1 or V2
load_world_model("runs/planet/rssm.pth", "planet")  # bare RSSM weights
load_world_model("runs/iris/iris.pt", "iris")
load_world_model("runs/diamond/checkpoint_10.pt", "diamond")  # no emulator needed
load_world_model("runs/genie/genie_sonic_final.pt", "genie")

World Models trains its two halves separately, so it loads from both files:

python
from simulusion.world_model import MDRNNWorldModel

model = MDRNNWorldModel.from_checkpoints("vae/best.tar", "mdrnn/best.tar")

Constructor options such as a sampling temperature are saved with the model and can be overridden on load: load_world_model(path, temperature=0.5).

Models and agents

The protocol covers the model: what happens to the world under an action. The policy that chooses actions stays with each family's agent, and steppers remain the interface for acting in a real environment. A DIAMOND world model, for example, loads without the Atari emulator or the replay buffer its agent needs for training.

Your own model

Implement the methods above (and spec and device) and any code written against WorldModel accepts it; isinstance(obj, WorldModel) checks the shape at runtime. Subclassing simulusion.world_model.WorldModelBase gives you input conversion, rollout and the save format for free: implement _encode, _step, _decode, _save_weights and _load_weights.

Edit this page on GitHub

Updated Oct 18, 2026