Skip to content

Export a policy to ONNX

An export turns one policy checkpoint into an ONNX bundle: a folder with the model, its input and output contract, and a script that proves the model works on your machine. The bundle needs no Simulo account, no Simulo client, no PyTorch and no GPU.

Name the policy and the checkpoint kind:

Terminal window
simulo export policy_brisk-heron-4f8k2m:best
EXPORT job_quiet-cedar-7k3m4q
Checkpoint: policy_brisk-heron-4f8k2m:best
ONNX bundle downloaded:
./train-policy-best/
Verify it on this machine:
cd train-policy-best
python -m pip install -r requirements.txt
python verify.py

Simulo runs the export as a job of type export, which you can see in simulo jobs. The job converts the checkpoint to ONNX, then runs 100 test observations through both the original policy and the ONNX model. If any action differs by more than 1e-4, the export fails and no bundle is published. When the check passes, the CLI downloads the bundle, checks its digest, and unpacks it.

The policy must be sealed, which means its training job has ended. The kind is required, and a job ID gets the same hint as simulo policy get.

The folder is named {job name}-policy-{kind}. The job name is the name of the training job’s function, so the starter’s train job exports train-policy-best or train-policy-latest. Use the path the CLI prints.

Useful options:

  • -o DIR unpacks the bundle into DIR instead of the current folder.
  • --no-download waits for the export to pass and leaves the bundle on the platform.
  • --json prints the export record and the bundle path as JSON.

Exporting again replaces an earlier bundle folder with the same name. Two policies trained by jobs with the same function name share a folder name, so pass -o DIR to keep both.

If you press Ctrl-C while the export runs, it keeps running on Simulo. Run the same simulo export command to pick it up again.

In Simulo Console, open Jobs, select the training job, and use its Export panel to create and download an ONNX bundle.

Use a clean virtual environment, so nothing else on your machine helps the model run:

Terminal window
cd train-policy-best
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python verify.py

requirements.txt lists only ONNX Runtime and NumPy. verify.py loads model.onnx, runs the bundle’s 100 test observations, and compares each action with the one Simulo computed during the export, using the same tolerance. It also reports the average time per action and the control period. A passing run ends with:

LOCAL VALIDATION: PASSED
This ONNX bundle runs independently of Simulo on this machine.

Passing means the ONNX model reproduces Simulo’s own results on this machine. It does not show that the policy is safe or effective on a physical robot.

File What it holds
model.onnx The policy as an ONNX model.
model.pt The source checkpoint, when the export includes it.
manifest.json Identity, provenance, control period and versions.
observation_schema.json The ordered inputs: names, shapes, types and units.
action_schema.json The ordered outputs, with units and bounds.
normalization.json Input scaling parameters, or an explicit none.
test_vectors.npz The test observations and the actions Simulo computed for them.
verify.py The standalone check you just ran.
requirements.txt The check’s two dependencies.
README.md These instructions, inside the bundle.

The model takes one input named observations and returns one output named actions. For cartpole, an observation is 4 numbers:

import json
import numpy as np
import onnxruntime as ort
manifest = json.load(open("manifest.json"))
session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
obs = np.zeros((1, 4), dtype=np.float32) # your robot's real observation
action = session.run(["actions"], {"observations": obs})[0]
print(action)

observation_schema.json says what goes in each slot of the observation and in what order. action_schema.json says what each output value means and its bounds. manifest.json records control_period_s in seconds, or the string "unknown" when training didn’t record it, so check for that before you use it as a number.