SO-101 World Model v1

Visionary is an action-conditioned video world model for SO-100/SO-101 and WidowX robot arms. It predicts future video in a learned latent space.

Source code: github.com/james0248/visionary

Compatible code revision: 64a2395c

This is a world model. It is not a robot control policy.

Components

The release contains two coupled components:

Component Parameters Purpose
Tokenizer about 137M Convert 240 x 320 video frames to and from latent tokens
Dynamics about 312M Predict latent video from prior video and robot actions

Both components use the step-100,000 inference exports. The dynamics weights are the exponential moving average parameters used by the evaluation code.

The model runs at 5 Hz. It has a 48-frame dynamics context window. This is 9.6 seconds at the training rate.

Install

git clone https://github.com/james0248/visionary.git
cd visionary
git checkout 64a2395c86298b0c0a35997e5696d510a6d78e94
uv sync
uv pip install "safetensors>=0.5.3"

Download and load

import json
from pathlib import Path

from flax.core import freeze
from flax.traverse_util import unflatten_dict
from huggingface_hub import snapshot_download
from hydra.utils import instantiate
from omegaconf import OmegaConf
from safetensors.flax import load_file

from visionary.models.dreamer4.tokenizer_preprocessor import TokenizerPreprocessor


def load_component(directory):
    config = OmegaConf.create(json.loads((directory / "config.json").read_text()))
    flat = load_file(str(directory / "model.safetensors"))
    variables = freeze(unflatten_dict({tuple(name.split("/")): value for name, value in flat.items()}))
    return config, variables


root = Path(snapshot_download("hyeonsjung/so101-world-model-v1"))

tokenizer_config, tokenizer_variables = load_component(root / "tokenizer")
dynamics_config, dynamics_variables = load_component(root / "dynamics")

tokenizer = instantiate(tokenizer_config)
dynamics = instantiate(dynamics_config)
preprocessor = TokenizerPreprocessor.from_config(
    json.loads((root / "tokenizer" / "preprocessor_config.json").read_text())
)

Actions must be normalized before they enter the dynamics model:

import json
import numpy as np

stats = json.loads((root / "normalizers" / "so101_action_stats.json").read_text())
q01 = np.asarray(stats["q01"], dtype=np.float32)
q99 = np.asarray(stats["q99"], dtype=np.float32)
normalized_actions = np.clip(2.0 * (actions - q01) / (q99 - q01) - 1.0, -1.0, 1.0)

Use embodiment ID 0 for SO-101 and 1 for WidowX. See embodiments.json for the action schema.

Training data

The tokenizer was trained on fixed-camera video from 733 public SO-100/SO-101 LeRobot repositories. The source corpus contains 36,320 episodes and 174.8 hours before final stream filtering.

The dynamics model was co-trained with equal source weights:

  • SO-101 absolute joint pose actions: 29,687 episodes and 2,243,913 frames.
  • WidowX end-effector delta actions from BridgeData V2 and SOAR-Data: 131,413 episodes and 6,741,197 frames.

The original SO-101 downloads did not record Hugging Face commit IDs. Five SO-101 source repositories were no longer public when this release was prepared. These are reproducibility and provenance limits. BridgeData V2 uses OpenMDW-1.1. SOAR-Data uses MIT. The source code has the data preparation details.

License

The Visionary code and model files are available under Apache-2.0. Third-party dataset terms still apply to the source data. This license grants only rights that the Visionary copyright holder can grant. See NOTICE.

Citation

@software{jung2026visionary,
  author = {Hyeonseok Jung},
  title = {Visionary: Action-Conditioned Robot World Models},
  year = {2026},
  url = {https://github.com/james0248/visionary}
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Video Preview
loading