Skip to content

Export a policy to ONNX

simulo models gives you the trained checkpoint. This gives you something you can put on a robot: an ONNX bundle that runs with Simulo uninstalled, plus the evidence that it reproduces Simulo’s own inference.

Terminal window
simulo export

No arguments. Simulo resolves your latest training job, selects its best checkpoint, converts it, and downloads the result — it does not ask you which tensor names to use, which normalization to apply, which observations to test with, or what numerical tolerance is appropriate, because it already knows all of them.

Exporting best.pt from job 8b52…9f
PyTorch → ONNX … done
Validating 100 golden vectors … passed
Maximum action difference: 2.4e-07
Deployment bundle downloaded:
./cartpole-policy-best/
Simulo export validation: PASSED
Verify it on this machine:
cd cartpole-policy-best
python -m pip install -r requirements.txt
python verify.py

Every flag overrides a default that was already correct: --kind latest to export the last checkpoint instead of the best, --model <id> for a specific one, -o for somewhere else, --json for machine output.

Conversion completing is not success. There are two separate checks, and they answer different questions.

Simulo-side. Before publishing anything, Simulo runs the same observations through the source PyTorch policy and through the exported ONNX model, and compares the actions. A disagreement beyond tolerance fails the export rather than handing you a bundle that loads and misbehaves. That answers: did Simulo convert the policy faithfully?

Your machine. The bundle ships its own verifier. That answers: does this model actually run, correctly, here?

Terminal window
cd cartpole-policy-best
python3 -m venv .venv && source .venv/bin/activate
python -m pip install -r requirements.txt # onnxruntime + numpy, nothing else
python verify.py

It needs no Simulo SDK, no simulator, no CUDA, no GPU, no credentials, and no access to the training project. It does not need PyTorch either.

A bare model.onnx is not enough to use a policy correctly, so the bundle is a contract:

File What it carries
model.onnx the portable model
model.pt the source checkpoint it came from
manifest.json identity, provenance, control period, format and schema versions
observation_schema.json ordered inputs — names, shapes, dtypes, units, frames
action_schema.json ordered outputs — names, units, bounds
normalization.json scaling parameters, or an explicit “none”
test_vectors.npz golden inputs and the actions Simulo computed for them
verify.py the standalone check above
requirements.txt numpy and onnxruntime
README.md the same instructions, inside the bundle

The golden vectors come from the trusted export, so verify.py compares the downloaded model against known-good results rather than merely confirming ONNX Runtime returned something.

import json, numpy as np, 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]
period = manifest["control_period_s"] # seconds, or the string "unknown"
print(action, f"every {period}s" if period != "unknown" else "(control rate not recorded)")

observation_schema.json tells you what belongs in each slot of obs and in what order; action_schema.json tells you what the outputs mean and what bounds they respect.

A policy trained before Simulo captured control period and action bounds exports fine — those fields read unknown in the manifest and in verify.py, and they stay unknown rather than becoming a plausible default. Branch on that rather than formatting it, as the snippet above does. A number you can act on and an honest “never recorded” are both useful; a guess is the only outcome that can hurt you on hardware.

Four failures are reported differently, because they need different responses:

  • Conversion failed — a transient or model-specific conversion fault. Retrying is reasonable.
  • Unsupported model — the policy uses a structure or operator the exporter cannot represent faithfully. Retrying reaches the identical refusal; the model has to change.
  • Validation mismatch — the ONNX model disagreed with the source policy beyond tolerance. Simulo publishes nothing rather than ship a bundle that would mislead you.
  • Local validation failedverify.py did not reproduce the expected outputs on your machine. Simulo already proved the conversion, so start with the download and your local ONNX Runtime install.

Passing local verification means the exported policy executes independently and reproduces Simulo’s inference results on that machine. It says nothing about whether the policy is safe or effective on real hardware — sim-to-real transfer, robot I/O, actuator limits and physical safety are separate problems. This is the prerequisite for tackling them, not a substitute.

Score a policy before exporting it: Evaluate & roll out. List and download the raw checkpoints instead: Retrieve & verify models.