Training Lab · Export
Take the skill home.
One file, no training stack.
Everything the robot learned in simulation ends up in a single ONNX file: the policy network, the observation normalisation and the action limits. On the robot you need onnxruntime and numpy, a loop that reads sensors at the right rate, and a healthy respect for the gap between simulation and reality.
17
ONNX opset
16
random inputs in the verification
10⁻³
max allowed difference vs the original
2
libraries on the robot
1The artifact
A deterministic policy with its normalisation inside
Export traces the trained actor network into ONNX, the open format for neural networks. The critic, the optimiser state and the exploration noise are left behind; what remains is a pure function from observation to action.
obs
float32 [batch, obs_dim] · raw values, training order
normalise
(obs − mean) / √(var + 1e-8), clip ±10
actor MLP
64 → 64, tanh · the trained policy
mean action
no sampling: deterministic
clip
to the action-space bounds
action
float32 [batch, act_dim]
Deterministic
For PPO the output is the mean of the policy’s action distribution: the same action model.predict(obs, deterministic=True) returns. Same observation in, same command out.
Normalisation baked in
The frozen running mean and variance from training become the first layer, with the same ±10 clip. You feed raw sensor values, never pre-scaled ones.
Clipped actions
The output is clipped to the action-space bounds inside the graph, so it is always a valid command vector for the task’s action space.
Any completed run can be exported; the Export page features the best one (highest mean return). Downloads are named <task>_run<id>_PPO.onnx, and a policy of this size is well under a megabyte.
2Trust, but verify
Checked against the original, described by its metadata
An export that silently differs from the trained policy would be worse than none. So every export of a completed run is checked before you can download it.
Verification
- The model must pass the official
onnx.checker. - 16 random observations are run through both the ONNX file (with onnxruntime) and the original Stable-Baselines3 policy (with the same normalisation).
- If any action differs by more than 0.001, the export is rejected. The largest difference is recorded as
max_abs_diff_vs_sb3; in practice it is floating-point rounding. - The file’s size and SHA-256 checksum are computed, so you can confirm the file on the robot is the one you exported.
| Field | Meaning |
|---|---|
input · output | Tensor names, dtypes and shapes: obs [1, obs_dim] and action [1, act_dim]. |
action_space | Low and high bound of each action value. |
obs_normalization · obs_clip | vecnormalize and 10.0 when statistics are baked in. |
robot_model · env_* · run_id | Which robot, task, task version and run the policy came from. |
verified · max_abs_diff_vs_sb3 | Result of the comparison with the original policy. |
size_bytes · sha256 | Integrity of the file. |
browser_sim | Whether the browser can run it live, with the observation recipe or the reason it can’t. |
The metadata is embedded in the ONNX file itself (metadata_props, one JSON value per key), saved as JSON next to it on the server, and shown on the Export page. On the robot, read it back with sess.get_modelmeta().custom_metadata_map.
3Deployment
Running it on the robot
The integration is a fixed-rate loop: build the observation vector, run the policy, send the commands, wait for the next tick. The hard part is the first line, making the observation match simulation exactly.
Match the observation
- Same order as in training: joint positions, then joint velocities, then world objects relative to the robot’s base.
- Same units: metres, radians, rad/s.
- Same conventions: the base’s world x/y is not in the observation; joint velocities were clipped to ±50 and object velocities to ±20.
- Objects mean you need to sense them on the real robot too, for example a ball position from a camera.
4Actuation
From policy output to actuator command
On kualia’s runtime every actuator is normalised to [−1, 1] during training and mapped linearly onto its control range. Do the same on the robot and the command means what it meant in simulation.
On the realistic Menagerie robots (and uploaded models that ship a keyframe) each position actuator’s range is shifted to centre on the standing keyframe, width unchanged, so an action of 0 holds the standing pose. Use those shifted ranges for CTRL_LOW / CTRL_HIGH; when the task supports browser simulation, the metadata’s browser_sim recipe lists them exactly.
Gymnasium-route tasks on the classic robots use the robot’s own action space instead; the bounds are in the metadata’s action_space. For a position servo the value is a joint target, for a motor a torque, so check which one your real actuator expects.
5Reality check
Mind the sim-to-real gap
A policy is only as good as the simulation it trained in. Real motors have latency and backlash, friction differs, batteries sag and sensors are noisy. Expect differences, and design the first tests so they are harmless.
Start at low power
Low gains, low speed limits, reduced torque. Raise them only once the behaviour looks right.
Emergency stop in reach
A physical e-stop or a kill switch that cuts motor power, and people out of the robot’s reach.
Suspend or tether first
Run a legged robot lifted or on a harness before it bears its own weight.
Watch the timing
If a tick is late or a sensor read fails, hold a safe pose instead of acting on stale data.
Clamp to your own joint limits and rate-limit changes while you test. A guard changes the dynamics the policy learned, so remove or loosen it once the robot behaves.
What varies in training today
- Small random noise on joint positions and velocities at every reset.
- Random placement (jitter) of movable world objects.
- Task-level variation alia writes, such as a randomised target or command picked in
on_reset().
The robot’s physical parameters (masses, friction, motor strength) stay fixed: kualia does not randomise dynamics today. Plan real-world tests with that in mind.
Control rate matters
If your robot’s loop runs at a different rate than the task, retrain withCONTROL_HZ set to the real rate rather than resampling actions.6Before the hardware
The same file runs in your browser
The Export page loads the exact ONNX file you download into the browser simulator: MuJoCo in WebAssembly plus onnxruntime-web, driving the robot in real time. Push it, watch it recover, and only then move to hardware.
- Try the exported policy on the Export page: the policy acts in the same composed world it trained in, objects included.
- During training, the newest checkpoint from 25 % on is exported to ONNX on request and runs live on the Training screen, refreshing as new checkpoints are recorded.
- The Export page’s Browser simulation line says whether the task can run live, with the reason when it can’t (custom observation features, action remapping, a different control period).
- ONNX is a standard format, so the policy runs in your own web or desktop tools too.


7The handoff
What you can take with you
The Export page packages everything a robot-side integration needs, and the Plan page shows the task the policy learned.
The ONNX policy
Download File on the Export page. Deterministic, normalisation inside, verified.
Metadata & checksum
Input and output schema, control rate, normalisation, and the SHA-256 hash to copy.
Runtime snippet
Copy Runtime Instructions gives a minimal onnxruntime loop for your file’s shapes and rate.
The task code
The generated Gymnasium task is plain Python; read and copy it on the Plan page.
The file is the handoff
Direct push to a robot runtime is on the roadmap. Today you copy the ONNX file to the robot (scp, USB, your robot’s tooling) and run it with onnxruntime.Questions
Do I need PyTorch or Stable-Baselines3 on the robot?
Do I have to normalise the observation myself?
Why is the exported policy deterministic?
My robot’s control loop runs at a different rate. Can I resample?
CONTROL_HZ set to your robot’s real rate instead.Can I export a run that is still training?
Can kualia push the policy to my robot directly?
Train it here. Run it there.
A first 10K-step try costs $1, and a failed run costs nothing.