Skip to content

Runtimes

Every job runs in a Simulo runtime: a prebuilt environment on the GPU machine that already has the simulator, the GPU drivers and libraries, PyTorch, NumPy, and the rest of the scientific Python stack. Simulo builds and maintains it; you never build or push an image.

A simulo.Runtime can add two things on top: public PyPI packages with pip_install(), and environment variables with env(). Most apps need neither.

An app that names no runtime runs on the default, simulo/gpu-rl:2026.06:

import simulo
app = simulo.App("cartpole") # the default runtime

To name a runtime explicitly, pick it from the ones Simulo offers:

app = simulo.App(
"cartpole",
runtime=simulo.Runtime.from_registry("simulo/gpu-rl:2026.06"),
)

Runtime.from_registry(name) chooses among Simulo’s runtimes. You can’t bring your own image or registry. If your job needs something the base itself must provide, such as a different CUDA version or a compiled system library, that is a request to the Simulo team.

A name Simulo doesn’t offer is normally refused before anything uploads, and the message lists the runtimes that exist. If the platform can’t tell yet, you get a warning instead and the job runs on the default runtime.

✗ unknown runtime 'simulo/python-cpu:3.11' — this platform does not offer it; available runtimes: simulo/gpu-rl:2026.06

simulo run makes this check automatically, and simulo prepare runs the same check on its own. An app that leaves out runtime= always gets the default and never hits it.

pip_install() adds public PyPI packages the base runtime doesn’t ship. Simulo installs them on the GPU machine before your job starts:

import simulo
runtime = simulo.Runtime.from_registry("simulo/gpu-rl:2026.06").pip_install("shapely")
app = simulo.App("zone-approach", runtime=runtime)
with app.runtime.imports():
import torch # from the base runtime
from shapely.geometry import Point, Polygon # added by pip_install()

Build a dedicated Runtime with Runtime.from_registry(...) and pass it to App(runtime=...). Calling pip_install() on app.runtime of an app that left out runtime= would change the default runtime object that every such app shares.

Each call adds one layer, and layers install in the order you declared them:

runtime = (
simulo.Runtime.from_registry("simulo/gpu-rl:2026.06")
.pip_install("shapely")
.pip_install("wandb==0.17.0", pre=False)
)

The rules:

  • Public PyPI only. Each argument is a plain requirement: a name, optionally with extras and a version, such as "shapely" or "transformers[torch]>=4.40". URLs, git+ references, local paths, and name @ url references are refused when you call pip_install(). pre=True allows pre-release versions for that layer.
  • No replacing base packages. Naming a well-known base package, such as torch, numpy, the CUDA packages, or the simulo packages, fails the submit with a list of the offenders. Any other package the base already provides fails the job on the GPU machine before your code runs. A second copy of a base package can break the runtime in ways your own traceback would never explain.
  • Your packages come last. They are added after the base environment, so the runtime’s own versions always win.
  • pip_install() with no arguments does nothing.

env() adds environment variables to the job. Later calls win when keys repeat, and it chains like pip_install():

runtime = simulo.Runtime.from_registry("simulo/gpu-rl:2026.06").env(
{"WANDB_PROJECT": "cartpole", "WANDB_MODE": "offline"}
)

A small set of names belongs to the platform: PATH, PYTHONPATH, HOME, PIP_* names, and specific SIMULO_* names and prefixes that carry mount paths, job identity, and operator settings. A reserved key is skipped with a warning, and your job still runs. Other SIMULO_* names are yours to set, including SIMULO_CHECKPOINT_EVERY, which changes how often a training job saves checkpoints. A checkpoint_every= passed to train() takes precedence over it.

simulo run imports your file on your own machine, where heavy packages such as torch usually aren’t installed. Write those imports once, at module level, inside app.runtime.imports():

with app.runtime.imports():
import torch # resolved only on the GPU machine
  • On your machine, the import is recorded and skipped, so submitting stays fast and needs nothing heavy installed.
  • On the GPU machine, it is an ordinary import.

A heavy import anywhere else at module level would break simulo run on a machine without that package. Imports inside a job body or a task method need no guard, since those never run on your machine.

A reward or observation function that benefits from torch.jit.script can be a module-level function decorated with @app.runtime.torch_jit:

@app.runtime.torch_jit
def _compute_rewards(pole_pos, pole_vel, cart_vel, reset_terminated):
...
return reward

On your machine the decorator returns the function unchanged; on the GPU machine it returns the compiled version. The function can live at module level, where it is easy to read and test.