Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
- Minecraft Diamond (
MinecraftDiamondEnv,env_backend="minecraft"): the DreamerV3 benchmark environment on MineRL 0.4.4, following the reference code: 25 flat actions with abstract crafting, +1 per milestone item once per episode, a health reward, 36000-step episodes, 100x block breaking, held jumps, a pitch limit, and image plus inventory, equipped-item and status vectors. A Malmo failure cuts the episode short (truncated) and the next reset restarts it. - BSuite benchmark protocol.
BSuiteImageEnvtakesobs_type="state"(the flat observation vector DreamerV3 learns from), accepts bare names ("catch"is"catch/0"), records BSuite's CSV logs for scoring withresults_dir=, and tracks the episode budget, at which Dreamer training now stops.list_bsuite_settingslists an environment's settings (468 in all). - DMLab-30 protocol.
DMLabEnvoffers the fixed PopArt/R2D2 (15) and IMPALA action sets by name, embeds the instruction oflanguage_*levels asobs["instr"]as in the DreamerV3 reference, and hastrain/evalmodes for*_train/held-out levels. DMLab-30 levels can be named without their folder (DMLAB30_LEVELS,resolve_dmlab_level), and Dreamer configs default to the PopArt actions and train mode, and count Lab frames. - Benchmark protocol in Dreamer's
make_env. DMLab, Minecraft and BSuite end their own episodes, sotime_limitdoes not apply to them, and they requireaction_repeat=1(DMLab repeats 4 frames inside Lab), as in the DreamerV3 reference.minecraft_lengthsets Minecraft's episode length. - DreamerV3 presets for BSuite, DMLab-30 and Minecraft Diamond. A
minecraftpreset follows Table 2 (100M steps, replay ratio 32); thebsuitepreset learns from the observation vector andbsuite_settingslists the 468 settings; thedmlabpreset uses the PopArt action set on training levels. docker/envsimage with DeepMind Lab (built from source), the patched MineRL 0.4.4 and BSuite on Python 3.11, for the benchmarks that cannot be pip-installed on every platform.- Proprioceptive DeepMind Control observations.
DeepMindControlEnvtakesobs_type="state"(Dreamer configs:dmc_obs_type="state"), which returns the task's state flattened intoobs["state"]and skips rendering, for the Proprio Control benchmark. Pixel observations now carryobs["state"]too.
Fixed
- DreamerV3 updates were bound by kernel launches (about 90 small kernels per
recurrent step): 2.4 s per update on an RTX 3050 laptop with the GPU mostly
idle. The observe rollout (forward and backward) and imagination now replay
as CUDA graphs (
cuda_graphs=True, falling back to eager on CPU, under AMP, or if capture fails), taking an update to 0.36 s from joint states and 0.80 s from pixels. LaProp and adaptive gradient clipping use foreach kernels, and training metrics reach the host in one copy per update. - DreamerV3's
actor_dist="auto"chose a Gaussian policy for DMLab, BSuite and Minecraft, whose discrete actions are one-hotBoxspaces; it now picks the categorical policy the paper uses. create_config("dreamer-v3", model_size=...)kept the 12M layer widths.- Training DreamerV3 through
agent.train()crashed at its first checkpoint, which expected DreamerV1/V2 update objects. BSuiteImageEnv.max_episode_stepsreturned the setting's episode count.- Plotting works with plotly 7, which removed
plotly.graph_objs.Line: importing it disabled all plotting, solineplotandplot_metrics(and sotrain_rssm) crashed. plotly and scikit-learn are now imported separately, so line plots no longer need scikit-learn, and a missing package raises anImportErrornaming thevizextra. @deprecatedon a class recursed forever on construction; it now calls the original__init__. Deprecation warnings name the function (not "function"), point at the caller, and have no stray space before the reason.- DeepMind Control episodes that end at the time limit (discount 1) are reported as truncations rather than terminations, so learners bootstrap the value of the last state. Every DMC task ends this way.
- The trained demo checkpoints for Dreamer, DreamerV2, PlaNet, IRIS, DiT and I-JEPA are on the Hugging Face Hub, in the Simulusion demo models collection. The README and the gallery link to them, and the gallery lists each repo with how to load it.
- DreamerV3 (
create_model("dreamer-v3")). Implements the paper's robustness techniques: categorical latents with 1% unimix and straight-through gradients, separate dynamics/representation KL terms with free bits, symexp two-hot reward and critic heads, percentile return normalization with a denominator limit, a critic EMA regularizer and replay loss, zero-initialized head outputs, and LaProp with adaptive gradient clipping. It trains throughDreamerAgenton the shared Trainer, and its per-term losses, gradient norms and return scale are logged with the other training metrics. DreamerV3Configwith the paper's Table 4 defaults and the 12M-400M model-size presets from Table 3; widths resolve frommodel_sizeandupdate_stepsderives fromreplay_ratio.CategoricalRSSM,DreamerV3Encoder/Decoder/Head/Actor,DreamerV3ReplayBuffer,BlockGRUCell/BlockLinear, and a newsimulusion.optimpackage exportingLaPropandadaptive_grad_clip_, all usable independently of the agent.- Discrete action support in the shared Dreamer
make_env: environments with a discrete action space are wrapped withOneHotActioninstead of failing inNormalizeActions. examples/dreamer_v3_example.pyand the DreamerV3: Mastering Diverse Domains with Fixed Hyperparameters documentation page.- DreamerV3 replay follows the paper's Methods section. Each record holds an
observation with the action and reward that led to it, and the final
observation of every episode gets its own record, so the reward and continue
predictors at
s_tlearnr_tandc_tas in Eq. 1, and imagined rewards are read off at the state reached. Model states are stored in replay, replayed sequences start from them, and training writes fresh states back. Each minibatch is filled from the online queue first. Collection continues the open episode across calls instead of resetting the environment. - DreamerV3 encodes several observations at once (
obs_keys), as in the paper: image inputs are stacked into one CNN, vector inputs into one symlog MLP, and the decoder reconstructs each of them. By default it encodes the image, or the state vector when there is no image (DMC Proprio Control), plus a DMLab language instruction when present. Replay and checkpoints keep every input. - DreamerV3's reward and continue predictors are 1-layer MLPs
(
reward_layers,cont_layers), the replay ratio counts policy steps (action repeat divided out, matching the paper's worked example), and replay capacity defaults to 5M (Table 4). - DreamerV3 benchmark presets (
benchmark_config,BENCHMARKS) follow Table 2 of the paper: task lists, step budget, action repeat, replay ratio and model size for DMC Proprio and Visual Control, Atari, Atari100k, ProcGen, DMLab-30 and BSuite, with each benchmark's environment settings (sticky actions and the full action set for Atari but not Atari100k, ProcGen hard mode with unlimited levels). Gym environments takegym_make_kwargs, which the Atari presets use to set ALE'sframeskip=1so the action repeat of 4 is not applied twice. demos/dreamer_v3_demo.pytrains DreamerV3 from a benchmark preset (resumable with--resume) and records the real-vs-imagined video, a policy video and the learning curve. The gallery and README show two runs on cartpole swing-up: from joint states with the paper's protocol (500k steps, final evaluation return 866), and from pixels (300k steps at replay ratio 128, return 740-840 at the end).simulusion play -m diamond: pressSin DREAM mode to save the last--dream-bufferdream frames (default 50) asdream_<timestamp>.mp4, with the actions and predicted rewards indream_<timestamp>.json. Before, the only way to keep a dream was--recordfrom the start.
Changed
DreamerAgentsubclasses can pick their configuration class (_config_cls) and build their own algorithm core (_build_core) alongsidecore_cls. Passing a baseDreamerConfigto a subclass agent carries over only the fields that were changed, so the subclass's tuned defaults are kept.
[1.1.0] — 2026-10-25
Security
- Raise the locked versions of dependencies with open Dependabot advisories:
Pillow 13.0.0, certifi 2026.10.23, fsspec 2026.9.0, GitPython 3.2.1,
nbconvert 7.17.2, filelock 4.2.4, virtualenv 21.14.13, setuptools 84.2.1,
opentelemetry-api 1.45.1 and Werkzeug 3.1.9.
constraint-dependenciesnow keeps each of them at or above its patched version.
Changed
simulusion train MODELruns registered tasks on the shared Trainer. Commands written for the older per-family entrypoints (a--configwithouttask:, or flat overrides such astotal_epochs=100) still reach them, as does--legacy.- The per-family training entrypoints run on the shared Trainer.
DreamerAgent.train(),IRISTrainer.train(),DiamondAgent.train(),Planet.train()/train_planet.main,GenieTrainer.train(),DiT.fit()/train_dit,JEPAAgent.train(),train_convae,train_mdn_rnnandtrain_controllerkeep their signatures, return values and the files they always wrote, through one helper (simulusion.training.legacy.fit_legacy). Their own loops are gone. They now also write resumable checkpoints andrun.json/run_config.yaml/train_metrics.jsonlinto the same folder. Small differences (checkpoint step names, IRIS/DIAMOND not evaluating after epoch 0, MDN-RNN latents sampled per batch, the controller scored in one process) are listed in the trainer guide. W&B sweeps ofJEPAAgent.trainstill usetrain_jepa. - The Trainer keeps the best evaluation's checkpoint (
best_metric,best_mode), lets a task end training early (TrainingTask.should_stop), and writes the last checkpoint after the final evaluation. - Episode folders read World Models rollout files (per-step
observations/actions/rewards/terminals) directly.
Fixed
ReduceLROnPlateau.state_dict()included the live optimizer, so any checkpoint holding it (train_convae's andtrain_mdn_rnn'sbest.tar) could not be loaded withweights_only=True, as the repo loads everything.- The World Models controller's real-environment rollouts unpacked
GymImageEnv.reset()as a Gymnasium(obs, info)pair; it returns the observation alone, so every rollout failed. - A resumed DIAMOND run kept a replay buffer only as large as it was at the
checkpoint:
save_checkpointrecorded the trimmed length as the capacity. Its replay and observation side files are now found next to the checkpoint even after the directory moves. - The World Models CMA-ES controller search minimised the episode return
instead of maximising it, and its rollouts called
Controllerwith two arguments whereforwardtakes one, so every rollout raised. GenieWorldModel.encodewithout actions failed for Genie's default mean- pooled latent actions, which infer one action per clip.train_world_model's rollouts saved each environment's raw observations, so for vector-observation environments (Pendulum, BipedalWalker) the VAE trained on three-number "frames". Rollouts are now recorded as the 64x64 frames the controller later acts on, rendered where needed, with actions as the controller emits them. The controller's environment is made withrender_mode="rgb_array", so such environments are no longer seen as grey bands.generate_rolloutsgenerates only the missing rollouts, no longer drops the remainder when splitting work across workers, and runs in-process with one worker. The action size of a discrete environment was read as 0.train_world_model.test_trained_modelfailed on its first step (it unpackedGymImageEnv.reset()as a pair and calledControllerwith two arguments) and assumed the default model sizes. It now plays the trained models through the controller's own rollout code, with sizes read from the checkpoints, and returns the episode returns.train_world_model_carracingwas an older copy of the same script; it is nowtrain_world_modelwith--env CarRacing-v3by default.CarRacing-v2, the default environment ofWMControllerConfigand the name in the docs, does not exist in the supported Gymnasium versions; it isCarRacing-v3.- The Atari 100k runner reported the mean of the two quartiles as the IQM,
always reported a standard deviation of 0 (it read a key IRIS's evaluation
does not return), trained every game into one folder so their runs wrote
over each other, and dropped failed games silently. The IQM is now the
interquartile mean, each game trains in its own folder, failures are listed
under
errorsin the results file, and unknown games raise instead of scoring against a made-up baseline.maintakesgames=[...],save_dirandeval_episodes. - The PlaNet encoder
viewed a convolution output that can be non-contiguous, sotrain_rssmfailed on the replay buffer's batches.train_rssm.maintakes its sizes and paths as flags instead of writing to the working directory, and no longer stops inpdb.
Renamed
- Synora is now Simulusion. The distribution is
simulusionon PyPI (pip install simulusion, extras assimulusion[gym]), the package isimport simulusion, the CLI commands aresimulusion/simulusion-train, and environment variables use theSIMULUSION_prefix. Nothing was published under the Synora name, so there is nosynoraimport alias. Checkpoints load unchanged: they are state dicts read withweights_only=True.
Added
- One trainer for every family (
simulusion.training.Trainer). Any training task runs to a step budget on one device (CPU, a GPU or MPS) with one precision setting (fp32/bf16/fp16/auto), gradient accumulation, logging tometrics.jsonl, periodic evaluation, early stopping, atomic checkpoints with resume (bit-exact; replay buffers included for online agents) and export of the trained world model. Training runs in one process; there is no data-parallel or multi-GPU training. Tasks:dreamer,dreamer-v1,dreamer-v2,planet,iris,diamond(agents, collecting online),dreamer-wm,iris-wm,diamond-wm,genie,vae,mdrnn(world models from data),controller(World Models' C by CMA-ES, in the real environment or inside the dream),ditandjepa. Write your own withTrainingTaskandregister_task. See the new "One Trainer for Every Model" guide. - Declarative run configs. One YAML names a task and fills
trainer:and the task's sections;key=valueoverrides reach any nested field and unknown keys fail with their dotted path. Every run writes its resolvedconfig.yaml(pass it back with--configto repeat the run) andrun.json(git commit and dirty flag, package versions, GPUs, command line). Shipped configs for every task are insimulusion/configs/runs/.simulusion trainruns them;simulusion.run_training()from Python. - World models train without their agent. The
-wmtasks fit Dreamer's, IRIS's or DIAMOND's world model to recorded episodes with no environment and no policy, andsimulusion.agents.PlanningAgent/CEMPlanneract through any world model with a reward head by planning in it. - One episode format for video, RL and robot data (
simulusion.data): an.npzper episode with frames, optional actions / rewards / episode ends between them, extra cameras, proprioception and a language instruction;EpisodeFolder/EpisodeWriter, andSequenceDatasetwindows that never cross an episode boundary. A reader for local LeRobot datasets (v2.x and v3.0;pip install simulusion[lerobot]), a converter for RLDS episodes,simulusion datasets import lerobotandsimulusion datasets info. MixedPrecisionandGradientUpdatereplace the hand-written GradScaler blocks in Dreamer, IRIS, DIAMOND, Genie, PlaNet, ConvVAE and MDN-RNN.use_ampstill selects what it did, so results are unchanged.- One
WorldModelprotocol for every family (simulusion.world_model):encode/step/rollout/decode/save, with the same frame, action and state conventions everywhere.as_world_model(model)wraps a trained Dreamer (V1/V2), PlaNet, IRIS, DIAMOND, Genie or (ConvVAE, MDN-RNN) model;load_world_model(path, family)reads each family's own training checkpoints, and any world model saves to one directory format.WorldModelSpecsays what a model reads and predicts (action space, context length, reward and termination heads). Steps go through each family's own code: IRIS matchesimagine_rollout, Dreamer its stepper, PlaNet its planner. A DIAMOND world model loads without the Atari emulator or replay buffer. JEPA and DiT have no adapter, since neither is action-conditioned. See the new "One interface for every world model" guide. - A demo per model in
demos/(Dreamer, DreamerV2, PlaNet, ModularRSSM, DIAMOND, IRIS, Genie, DiT, I-JEPA). Each trains from scratch on a 4 GB laptop GPU in one to two hours and records video of what it learned;demos/run_all.pyreproduces all of them. - A documentation gallery with the recorded clips, plus a "See it in action" section on every algorithm page and in the README.
- IRIS imagination replays each single-position Transformer step as a CUDA
graph on GPU (
GraphedDecodeStep). Outputs are identical; on an RTX 3050 a 20-step imagination rollout drops from 11.4 s to 3.2 s, because the eager path spent most of its time launching ~55k small kernels. Disable withagent.use_cuda_graphs = False. DreamerV1AgentandDreamerV2Agent, the agents behindcreate_model("dreamer-v1")/create_model("dreamer-v2").
Fixed
create_model("dreamer-v1")andcreate_model("dreamer-v2")raised aTypeError; they now build agents that train their own version.- DreamerV2 could not train: its two-hot heads exposed
meanas a method with an extra trailing axis, and its actor loss detached the lambda-returns, so the actor received no gradient. The actor also maximisedsymlogof the returns rather than the returns themselves (DreamerV2, Sec. 2.4); on DMC cartpole swing-up that pinned every action at -1 and stalled the return at ~75. - DIAMOND's imagination paired each conditioning frame with the action taken one step later, so actor-critic training rolled the diffusion model forward on shifted actions.
- The TinyWorlds loader read each game's flat
(N, H, W, 3)frame stream as grayscale clips, so every Genie sample was one scrambled frame; clip files also had channels interleaved across frames. - Genie's video tokenizer reshaped the quantizer's channel-first output instead of permuting it, scrambling embeddings across positions. The decoder learned that layout, so every frame Genie generated from tokens decoded to noise. Genie models trained before this fix need retraining.
- Genie checkpoints saved after warmup failed to load with
weights_only=True, and the trainer ignoredtokenizer_num_heads/action_num_heads. - DiT trained on CIFAR-10 in
[0, 1]instead of[-1, 1], a class-conditional run crashed at its final sampling step, itsconfig.yamlomitted the class settings needed to reload it, and periodic checkpoints were one epoch late.
[1.0.0] — 2026-10-04
First stable release, and the first under the name Synora (formerly
TorchWM). The public API surface documented in docs/source/public_api.md is
now covered by semantic versioning: breaking changes to it require a major
version bump and a deprecation cycle.
Because 0.5.0 below was never tagged or published, upgrading from TorchWM 0.4.2
— the last torchwm release on PyPI — also brings in every 0.5.0 change.
Renamed
- TorchWM is now Synora. The distribution is
synoraon PyPI, the package isimport synora, the CLI commands aresynora/synora-train, and environment variables use theSYNORA_prefix. There is notorchwmimport alias. Checkpoints load unchanged: they are saved as state dicts and read withweights_only=True, so they hold no module paths. Deployment bundle manifests recordsynora_versioninstead oftorchwm_version.
Known limitations
- Shared step-budget
train()covers the Dreamer family; other models use their own trainers orsynora train - On CPython 3.13 the
dmcextra installs everything exceptdm_controlitself; runpython -m synora.install_dmcto add it
Added
synora.inference, an efficient-inference and deployment toolkit (see the new Efficient Inference and Deployment guide):optimize_for_inference/InferenceModel: eval + no-grad + one precision policy (fp32/bf16/fp16/auto) + optionaltorch.compilewith CUDA graphs, channels-last and weight casting. Defaults are numerically identical to eager.- A stateful step interface (
make_stepper,DreamerStepper,IRISStepper) andDreamerStepModule, a pure observe+act step with sampling noise as an input, ready fortorch.export. benchmark_step(p50/p90/p99 latency, throughput, peak memory) androllout_drift(closed-loop divergence of an optimized step from a reference).quantize_weights: weight-only int8 fornn.Linear, skipping VQ codebooks, RSSM stochastic heads and small layers by default; optional torchao backend.save_bundle/load_bundle: deployment bundles with weights, config, exported artifacts, a manifest and per-artifact verification.
synora deploy inspectandsynora deploy benchCLI commands.- Export formats
exported_program(torch.export,.pt2) andaoti(AOTInductor), plusload_exportedandverify_export. - Temporal KV cache for the ST-transformer (
STKVCache,DynamicsModel.init_cache/forward_cached). Genie generation takesuse_cache=Trueto generate each frame in O(1) frames of compute instead of re-running the whole prefix. Off by default. RSSM.observe_step/imagine_stepaccept explicitnoise, andActionDecoder.mean_actiongives a deterministic single-pass action.python -m synora.install_dmc(alsomake install-dmc) installs the DeepMind Control backend on CPython 3.13, where dm-control'slabmazedependency has no wheel and builds with Bazel. It uses the pure-Pythonlabmaze-newand installs dm-control with--no-deps.--checkverifies an environment,--dry-runprints the commands. It keeps the installed mujoco and matches dm-control to it, because mujoco 3.13 removed an enum mujoco-mjx still uses and upgrading would break thebraxextra;--upgrade-mujocoopts out.[tool.uv] constraint-dependenciescapsmujoco<3.13for the same reason- CUDA, then Apple MPS, then CPU device selection
(
synora.utils.device) worldmodelsextra (albumentations, cma);hydra-core/omegaconfinml;h5pyinviz;ruffindev- Throughput instrumentation:
synora.ThroughputMeter,synora.measure_stepsandsynora.tensor_nbytesreport steps/sec, ms/step and bytes shipped to the device per step, synchronising on CUDA so the timer measures execution rather than queueing - Performance helpers:
synora.enable_performance_defaults(cuDNN autotuning and TF32, no-ops without CUDA),synora.maybe_compile(opt-intorch.compilewith an eager fallback) andsynora.to_channels_last GenieConfig.use_amp/GenieSmallConfig.use_amp— autocast for Genie training, preferring bfloat16 where supported so no gradient scaler is needed- Genie
VideoDatasetloads.npy/.npz/.ptclips, or video files when OpenCV (synora[viz]) is installed DreamerConfig.perf_defaultsandDreamerConfig.tf32RSSMPolicy(..., compile_rollout=True)compiles the CEM candidate-rollout step, which runsnum_iterations * planning_horizontiny kernels per env step- I-JEPA linear evaluation (
synora.training.eval_jepa, exported assynora.jepa_linear_probe/synora.load_jepa_encoder), implementing the paper's Appendix A.2 protocol: frozen target-encoder, average-pooled patch tokens, LARS-trained linear head, and the published sweep over learning rate, weight decay, batch-norm head, and last-layer vs last-four-layer features VisionTransformer.get_intermediate_layers(), needed for the last-four-layer probe representationsynora/configs/experiments/jepa_small_gpu.yaml— single-GPU preset that reduces only batch size, backbone, and epochs, leaving every method parameter at the paper's valuetests/models/test_jepa_paper_alignment.py, pinning the I-JEPA masking, architecture, loss, and schedule details against the papersynora eval --model jeparuns the linear probe from the CLI, alongside the existing--model diamondFID/FVD/LPIPS path. The two evaluations share only--checkpoint,--batch-size,--deviceand--output; passing an option that belongs to the other model is an error rather than a silent no-op
Changed
- TensorRT export compiles through Torch-TensorRT's
ir="dynamo"frontend by default (was the legacyir="ts"); passir="ts"to keep the old path. - Breaking: one package. The implementation moved from
world_models/intosynora/, and the alias layer that madesynora.<name>resolve toworld_models.<name>is gone.synorais now the only import path —import world_modelsraisesModuleNotFoundError. Replacefrom world_models.x import ywithfrom synora.x import y; the publicsynorasurface is unchanged - I-JEPA defaults now reproduce the paper. The shipped configuration
previously combined the two worst settings in the paper's own ablations:
enc_mask_scalewas(0.15, 0.2)where the context block calls for(0.85, 1.0)(Table 9), andnum_pred_maskswas1where the paper uses4(Table 10: 9.0 vs 54.2 low-shot top-1). View augmentations were also enabled by default, contradicting the paper's central claim;use_gaussian_blur,use_horizontal_flipanduse_color_distortionnow default toFalse.min_keepis10,crop_scaleis(0.3, 1.0), and the optimizer follows Appendix A: batch 2048, warmup 1e-4 -> 1e-3 over 15 epochs, cosine to 1e-6 - I-JEPA learning rates are quoted at the paper's batch size of 2048 and scaled
linearly to the effective batch size; set
lr_reference_batch_size = Noneto opt out - I-JEPA trains with the paper's L2 loss (
loss_type="l2") instead of the reference implementation's Smooth-L1, which remains available asloss_type="smooth_l1"alongside the literal per-block sum"l2_sum" JEPAConfig.pred_depthdefaults toNone, which selects the paper's predictor depth for the configured backbone (6 for ViT-B, 12 for ViT-L/H, 16 for ViT-G) instead of silently building a 6-layer predictor for every model- The multi-block mask sampler draws block scale and aspect ratio from independent uniforms, as Sec. 3 specifies, rather than sharing one draw
- No
GradScaleris created for bfloat16 training, which does not need loss scaling;torch.autocastreplaces the deprecatedtorch.cuda.ampentry points - Dreamer keeps replay observations in
uint8across the host-to-device copy. The buffer already storesuint8andpreprocess_obsalready casts on the device, so the oldtorch.tensor(obs, dtype=torch.float32)widened the batch on the host and moved four times the bytes for a cast that happened anyway — 123MB per step at stock config, now 30.7MB. Transfers usetorch.from_numpy(no extra host copy) and pinned, non-blocking staging on CUDA - The ViT/I-JEPA backbone uses
scaled_dot_product_attentioninstead of an explicitsoftmax(q @ k^T * scale) @ v, so the (B, heads, N, N) score matrix is never materialised. Every other attention block in the package already did. A customqk_scaleis passed through unchanged; outputs match the explicit form to 1.7e-16 in float64 - Target-network EMA updates (I-JEPA, DiT) use
torch._foreach_*: one fused multi-tensor op per step rather than two kernels per parameter tensor, andalpha=applies the(1 - m)scale inside the add instead of allocating a scaled copy of every source tensor. Results differ by at most one ulp, in the fused form's favour — it rounds once where the explicit form rounded twice - Dataloaders pin host memory when CUDA is present and keep workers alive across
epochs.
tinyworldshardcodedpin_memory=False, and the ImageNet loaders respawned every worker at each epoch boundary - FID/LPIPS/FVD share one frozen backbone per device instead of rebuilding (and re-downloading) Inception or VGG for every metric instance
- The PlaNet CEM planner runs under
torch.inference_moderather thanno_grad, and no longer clones state before broadcasting it over candidates - The Dreamer replay buffer's sequence sampler tests the episode-boundary rejection arithmetically instead of materialising the index window for every rejected draw. The RNG is consumed identically, so a given seed still yields exactly the same sequences
- uv no longer resolves the whole project through a CUDA-specific PyTorch index.
[tool.uv]sethttps://download.pytorch.org/whl/cu121as the default index withindex-strategy = "unsafe-best-match", which routed every package through it and pinned contributors to one CUDA build regardless of hardware — contradicting the README. Resolution now comes from PyPI; add the index for your platform explicitly if you need a specific wheel set
Removed
These were deprecated, or would have been frozen into the 1.x API, and go before 1.0 instead of being carried through it:
- The
.export()method TorchWM installed on everytorch.nn.Moduleat import, and theTORCHWM_NO_GLOBAL_EXPORTswitch for it. Synora no longer modifiestorch.nn.Module: everynn.Moduleclass in the top-levelsynoranamespace gets.export()fromExportableAgentMixin(a test enforces this), and any other module goes throughsynora.export_model.install_export_methodis gone with it. torchwm.utils.jit_utils(TorchScript helpers); usesynora.maybe_compilefor speed orsynora.export_model(..., format="exported_program")for deployment.- The
dreamer-v3/dreamerv3registry names and theDreamerV3export. They built the sameDreamerAgentasdreamer- there is no DreamerV3 implementation - so the name promised an algorithm the library does not have. - The
procgenextra, which could never install on Python >= 3.11 dm-controlfrom thedmcextra on CPython 3.13 only, sopip install synora[dmc]no longer fails there. Newdmc-uvextra keeps it unconditional for uv, which drops thelabmazepin through[tool.uv] override-dependenciestoolsis no longer installed as a top-level packagenginx.conf, which proxied a frontend that no longer exists- Breaking: the
synora.inferenceoperator package (get_operator,OperatorABC,TensorSpec,DreamerOperator,JEPAOperator,IrisOperator,PlaNetOperator). The operators only resized and normalized tensors, andJEPAOperatormasked uniformly at random, which is not I-JEPA's masking at all. Preprocess inputs directly, or usesynora.transforms.image.make_transformsandsynora.masks.MultiblockMaskCollator - Breaking: the unused
operator_state_dim/operator_action_dimfields onDiamondConfig - Breaking: the
minerlandminedojoextras, and theseleniumextra. Neither Minecraft extra could ever install — MineRL 1.x has no Python 3.11+ release and MineDojo pinsgym==0.21.0, whose sdist no longer builds — and between them they madeuv lockunresolvable for the whole project.synora.envs.minecraft_envis unchanged;docs/source/iris.mddocuments the manual Python 3.10 install
Fixed
pip install synora[gym]crashed the README quick start on gymnasium 1.3: thegymextra requiredpygamedirectly whilegymnasium[box2d]1.3 depends onpygame-ce, and the two install over each other, so rendering any classic-control environment died with an access violation. The extra no longer requires pygame itself; gymnasium brings the renderer it needs.- FID and FVD could stall for minutes:
scipy.linalg.sqrtmran a recursive Schur decomposition on the product of two 2048x2048 covariances, which is rank-deficient whenever there are fewer samples than feature dimensions. The Fréchet distance now takes only the trace it needs, from the eigenvalues of a symmetric PSD matrix (numpy.linalg.eigh), which is exact and no longer needs SciPy. - Constructing a
DreamerAgentcalledsetup_logging("synora"), which turned off propagation on the package logger, so everysynora.*record stopped reaching the application's own logging handlers (and pytest'scaplog).setup_loggingnow leaves propagation on and only adds its console handler when the root logger has none, so messages still print exactly once. - MP4 videos (
StreamingVideoWriter,save_video,combine_videos, Dreamer rollout videos and everydemos/record_*.py) were encoded with OpenCV'smp4vfourcc (MPEG-4 Part 2), which no browser plays. They now go through the newsynora.utils.utils.Mp4Writer, which writes H.264 viaimageio-ffmpeg(added to thevizandworldmodelsextras), falls back to OpenCVavc1, and only then tomp4vwith a warning. maybe_compileonly guarded thetorch.compilewrap, but compilation is lazy, so a missing backend (e.g. no Triton on Windows CUDA builds) raised on the first call instead of falling back. The first call is now guarded too, and compiling annn.Modulekeeps itsstate_dictkeys.- IRIS evaluation collected every raw frame and a per-step latent even with
render=False, the default during training, and then discarded them. At the defaulteval_episodes=100and the 27000-step episode cap that is tens of GB held until the evaluation returns, so the OS could kill the process outright (in a notebook, the kernel just restarts). Both are now render-only - IRIS evaluation reset the same environment
collect_experiencekeeps a partial episode on, so the next collection step paired the stale pre-eval observation with the post-eval environment and wrote a transition that never happened into the replay buffer. Evaluation now runs on its own environment; when the caller supplied the environment, the in-flight collection episode is dropped instead - Genie training on TinyWorlds could kill the process with no traceback (a
notebook kernel just restarts) whenever
num_workers > 0, the default:TinyWorldsDatasetkept one HDF5 handle open from__init__, and forked DataLoader workers all read through that inherited handle, which HDF5 does not support. Each process now opens its own handle on first read. The same handle made spawned workers (Windows, macOS) fail withTypeError: h5py objects cannot be pickled - System-metrics logging crashed training on GPU machines:
collect_system_statsguardedtorch.cuda.utilizationwithhasattr, which is always true, while calling it raisesModuleNotFoundErrorwithout NVML (nvidia-ml-py). The counter is now skipped with a single warning, andnvidia-ml-pyis part of themlextra - Dreamer acted on the RSSM prior instead of the posterior, so the current
observation was encoded and then ignored during collection and evaluation
(also in
play dreamerandscripts/benchmark_infer.py) - Dreamer checkpoints omitted the critic (
value_model); older checkpoints still load, keeping a fresh critic and skipping its optimizer state Dreamer.evaluate(render=True)raisedKeyError: 0use_disc_model=TrueraisedTypeErrorin the actor loss; the discount head output is now also scaled bydiscount, as in the reference implementation- Time-limit truncations were stored as terminal transitions; the replay buffer now keeps episode boundaries and true terminations separately
DreamerConfig.env_instancemade startup crash while writingconfig.yamlDreamer(restore=...)ignored its argumentDreamerAgent.train()now always writes a final checkpoint- PlaNet sampled latents with uniform instead of Gaussian noise;
rollout_priorreturned tuples;get_init_stateconditioned the posterior on the stale deterministic state - Genie's default 5120-wide dynamics model used 36 heads, which do not divide
the width (now 40); attention layers validate
dim % num_heads create_model("genie"/"genie-small")silently dropped tokenizer and latent-action widths/depths from the config;tokenizer_num_headsandaction_num_headsare now honoured (default 16, which is what was always built)- DIAMOND collection kept every frame of the current episode in memory
synora train --inprocre-ran training without arguments onTypeErrorand in a subprocess on any other errorsynora eval/synora playimported modules that are not in the wheel; they now live insynora.inference, and the metrics package moved from the top-levelevalstosynora.evalsregister_env_backendbackends were never used bymake_env; admcbackend was added- Every
torch.loadin scripts and demos now usesweights_only=True - IRIS play in
scripts/benchmark_infer.pyreset the policy LSTM every step - MP4 video logging wrapped uint8 frames around; GIFs are written with Pillow instead of the undeclared moviepy
MUJOCO_GL=eglis only defaulted on Linux (macOS has no EGL)- A broken W&B install no longer breaks importing JEPA training
- The multi-block mask sampler raised no error when
min_keepexceeded the patches a block can hold; it now fails with an explanatory message instead of looping forever - The test suite can run to completion in a single process again. Every
nn.Moduleholds reference cycles, so a model built in a test is reclaimable only by CPython's cyclic collector — which triggers on allocation counts, not bytes, so a handful of very large tensors never trips it. The suite retained ~4.4GB (1.6GB fromtests/models/test_genie.py, 1.4GB fromtests/evals/test_evals.py) and died partway through with eitherRuntimeError: can't start new threador a Windows access violation. A newtests/conftest.pycollects after each test; the suite now finishes, at about 15% more wall-clock
[0.5.0] — 2026-07-27 (never tagged or published)
Added
- Real PEP 561 typing stub:
torchwm/__init__.pyinow declares every public export instead of falling back toAny, generated from the export map bypython -m tools.gen_type_stuband verified in CI.torchwmships apy.typedmarker so the re-exports stay typed torchwm play -m dreamer— interactive REAL/DREAM playback for Dreamer checkpoints alongside DIAMOND (scripts/play_dreamer.py)CODE_OF_CONDUCT.md(Contributor Covenant 2.1)- CI job running the full test suite with every optional backend installed —
the configuration a contributor gets from
CONTRIBUTING.md, previously untested - Tests asserting every
__all__entry resolves, that the stub matches the export map, and that the README algorithms table matches the model registry - Complete
torchwmpublic import surface: every implementation submodule is now reachable through the friendly namespace (from torchwm.models import Dreamer,import torchwm.envs, ...), not just top-level factory helpers dmcoptional-dependency extra (pip install torchwm[dmc]) that installsdm-controlfor the default DeepMind Control backend- Actionable error from the DMC backend that names the missing
dm_controldependency and points totorchwm[dmc]or the gym backend
Changed
- README "Supported Algorithms" table now lists all 13 registered models
(previously 5), keyed by the name
create_model()accepts vizextra installs only what it uses (opencv, umap-learn, scikit-learn, plotly); the unused FastAPI/uvicorn/starlette/python-multipart entries and the duplicated docs dependencies are gone- CI runs the test matrix on
pushtomainonly, not on every push to a PR branch, which ran the whole matrix twice per commit - Documentation, examples, and scripts now import through the
torchwmpublic namespace - Quick-start examples (README, docs landing page, getting-started guide) now use
the base-installable
Pendulum-v1gym backend so they run onpip install torchwm[gym]out of the box; DMC usage is documented separately
Fixed
torchwm.DreamerRSSMandtorchwm.MujocoEnvraisedAttributeErroron access: the export map pointedDreamerRSSMat a module that names the classRSSM, andMujocoEnvhad no implementation behind it (it now resolves toMuJoCoImageEnv)- README advertised a FastAPI visualization feature that does not exist
create_model("dreamer", env="walker-walk")no longer raises a bareModuleNotFoundError; the DMC dependency is installable and the error is clear- Broken landing-page example that called
train(env_name=..., total_steps=...)(thetrainmethod only acceptstotal_steps) - Env-adapter tests patched non-existent module attributes
(
torchwm.models.dreamer.env_wrapper.*) and the wrong Gymnasium registry reference, causing 10 spurious failures - Removed stray build artifacts (
nul, emptytestsdata/directories) and added.gitignoreguards
[0.4.2] — 2026-06-20
Added
- Dreamer integration test for Pendulum-v1 wired into CI
- DIAMOND world model documentation
from_pretrainedandfrom_configclass methods for Dreamer, IRIS, JEPA, Genie agents- Gymnasium wrapper for world model environment
- ONNX export support for agents
py.typedmarker for PEP 561 type declarations- PSNR evaluation metric
Changed
- Restructured dreamer docs with separate V1/V2 theory and examples
- Stripped base deps to minimum, moved extras to optional groups
- Centralized version in
_version.pyas single source of truth - Improved block exports and testing utilities
Fixed
- Arbitrary code execution risk in pickle.load (hardened checkpoint/replay deserialization)
- Zero-element tensor reshape crash in ConvEncoder
- Empty sequence edge case in Dreamer training
- 25 GitHub Dependabot vulnerabilities (upgraded 8 packages)
- Dockerfile referencing removed
torchwm_uifolder - Replaced debug print calls with proper logging
- MyPy type errors across 40+ files
- Missing imports in
train_jepa.py(mp, F, DistributedDataParallel) - CI workflow and docs dependency config
- DreamerConfig documentation field sync
[0.4.1] — 2026-06-01
Added
- Modular RSSM with swappable LSTM/Transformer/MLP backbones
- Genie model support (video tokenizer, latent action model, dynamics model)
- Brax environment backend
- BSuite environment backend
- DMLab environment backend
- Procgen environment backend
- Robotics environment backend (gymnasium-robotics)
- Unity ML-Agents environment backend
- Sphinx documentation with auto-deploy to GitHub Pages
- Benchmark runners and reporting utilities
- CLI tools (
torchwm,torchwm-train)
Changed
- Migrated configs to dataclass style for consistency
- Improved lazy import architecture for faster CLI startup
Fixed
- Cross-platform memory detection (Windows ctypes + Linux /proc/meminfo + psutil fallback)
- Environment wrapper stack consistency across backends
[0.4.0] — 2026-05-15
Added
- Initial public release
- Dreamer (V1/V2) agent implementation
- PlaNet agent implementation
- JEPA self-supervised learning agent
- IRIS sample-efficient RL agent
- DiT (Diffusion Transformer) support
- DIAMOND diffusion world model for Atari
- Core environment backends (DMC, Gym, Atari, MuJoCo)
- Replay buffers (Dreamer, IRIS, PlaNet)
- VQ-VAE and ConvVAE vision components
- HuggingFace Hub checkpoint loading
- TensorBoard and WandB logging integration
Edit this page on GitHub
Updated Nov 4, 2026