Build your own model¶
A new model is a subclass of JEPA or WorldModel built from the shared modules. This page writes a world model in about 30 lines, then trains it and plans with it without further code. The custom model notebook runs it end to end.
The contract¶
For a WorldModel, implement three methods:
| method | maps |
|---|---|
encode(pixels, ...) |
observations (B, T, ...) to latents (B, T, ...) |
predict(z, actions, ...) |
z[:, t] and actions[:, t] to a prediction of z[:, t + 1], for every t |
forward(...) |
a batch to {"loss": ..., **components} |
and set history, the number of past latents predict should see during rollouts. rollout, cost and CEM planning then work as is.
A convolutional JEPA¶
A convolutional encoder, residual MLP dynamics and SIGReg against collapse: a minimal LeWM-style model.
import torch
from torch import nn
from torch_world.losses import SIGReg
from torch_world.models import WorldModel
from torch_world.nn import MLP
class ConvJEPA(WorldModel):
history = 1
def __init__(self, action_dim: int, dim: int = 32, lamb: float = 0.01):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv2d(3, 32, 4, 2, 1), nn.GELU(),
nn.Conv2d(32, 64, 4, 2, 1), nn.GELU(),
nn.Conv2d(64, 64, 4, 2, 1), nn.GELU(),
nn.Flatten(), nn.Linear(64 * 4 * 4, dim),
)
self.dynamics = MLP([dim + action_dim, 256, 256, dim])
self.sigreg = SIGReg(256)
self.lamb = lamb
def encode(self, pixels):
return self.encoder(pixels.flatten(0, 1)).unflatten(0, pixels.shape[:2])
def predict(self, z, actions):
return z + self.dynamics(torch.cat([z, actions], dim=-1))
def forward(self, pixels, actions):
z = self.encode(pixels)
pred = (self.predict(z[:, :-1], actions[:, :-1]) - z[:, 1:]).square().mean()
sigreg = self.sigreg(z.transpose(0, 1))
return {"loss": pred + self.lamb * sigreg, "prediction": pred, "SIGReg": sigreg}
Train it like any other model (Training), and plan with CEM:
planner = CEM(horizon=5, action_dim=2, num_samples=512, num_elites=48, iterations=10, bounds=(-1, 1))
actions = planner(model, model.encode(obs[:, None]), goal)
Parts to reach for¶
| need | use |
|---|---|
| ready image encoders | nn.CLSEncoder (one embedding per image), nn.PixelPatches (raw patches), nn.PatchTokens.from_hub (frozen DINOv2) |
| image or video encoder | nn.VisionTransformer (from_size("small"), num_frames, use_rope, cls_token) |
| text | nn.TextEncoder, nn.WordTokenizer |
| evaluation | evaluation.goal_reaching, evaluation.vqa; the metrics of the papers in metrics (see Metrics) |
| predictor over masked positions | nn.MaskedPredictor |
| action-conditioned transformer | nn.Transformer(conditional=True) (adaLN-zero blocks) |
| causal over frames | masks.frame_causal_mask, or is_causal=True with one token per frame |
| anti-collapse | losses.SIGReg, or an EMA target via JEPA.make_target_encoder |
| contrastive alignment | losses.info_nce |
| initialization as in the JEPA code | nn.init_weights, nn.rescale_blocks |
Adding it to the library¶
Models live in torch_world/models/jepa/, one file each, exported from torch_world.models. Each comes with tests in tests/models/ and a notebook in examples/. See Contributing.