Unit 10 — Ship Your Brain
Your policy is trained. Now get it out of Python and into Godot — running at full speed without a Python process, as a self-contained game. Learn the ONNX export pipeline, load and resume checkpoints, and optionally publish a playable HTML5 demo.
Prerequisites
- Any trained agent from Units 2–9 — you need a
.zipSB3 checkpoint to export - Unit 2 §11 already showed the first ONNX export — this unit makes it production-shaped
- Comfort with Godot export presets (desktop and/or HTML5)
- No PyTorch internals needed; no ONNX background required
Time
Reading: ~20 min · Training: ~10 min GPU / ~30 min CPU
Three ways to see your AI
Godot (inference mode — no Python running, agent plays live) · ONNX inspector (Netron — visualise the exported graph) · HTML5 export (share a URL, let anyone play against your agent)
1 · The shipping pipeline
ONNX (Open Neural Network Exchange) is a standard model format. Godot RL Agents ships a GDScript ONNX runtime — no Python required at inference time.
2 · Save and resume checkpoints
Before exporting, make sure you have a final saved model. You can also resume training from any checkpoint if you interrupted a run.
# Save with checkpoints every 100k steps and export ONNX at the end
gdrl --env_path=./BallChase.x86_64 \
--experiment_name=ballchase_final \
--timesteps=1_000_000 \
--save_model_path=ballchase_final \
--save_checkpoint_frequency=100000 \
--onnx_export_path=ballchase_final.onnx \
--n_parallel=8 \
--speedup=20
# Resume from a checkpoint if training was interrupted
gdrl --env_path=./BallChase.x86_64 \
--resume_model_path=ballchase_final.zip \
--experiment_name=ballchase_resumed \
--timesteps=500_000 \
--onnx_export_path=ballchase_final.onnx
The --resume_model_path flag loads weights, optimizer state, and step count — training continues exactly where it left off.
3 · Export ONNX from a saved model
If you already have a .zip model and want to export ONNX separately:
from stable_baselines3 import PPO
from godot_rl.wrappers.stable_baselines_wrapper import StableBaselinesGodotEnv
env = StableBaselinesGodotEnv(env_path="./BallChase.x86_64", n_parallel=1, speedup=1)
model = PPO.load("logs/sb3/ballchase_final/best_model", env=env)
# Export to ONNX
model.policy.to("cpu")
import torch
obs = torch.zeros(1, *env.observation_space.shape)
torch.onnx.export(
model.policy,
obs,
"ballchase_final.onnx",
input_names=["obs"],
output_names=["action"],
opset_version=15,
)
print("Exported: ballchase_final.onnx")
env.close()
Alternatively, use the built-in gdrl export:
gdrl --env_path=./BallChase.x86_64 \
--resume_model_path=ballchase_final.zip \
--onnx_export_path=ballchase_final.onnx \
--timesteps=0
Setting --timesteps=0 skips training and just exports.
4 · Inspect the ONNX graph (optional)
Install Netron — a browser-based ONNX viewer — and drag your .onnx file into it. You'll see:
- Input node: observation shape
- MLP layers: weight matrices and activation functions
- Output node: action distribution parameters
This is useful for debugging shape mismatches when switching between SB3 versions.
5 · Load ONNX in Godot
- Copy
ballchase_final.onnxinto your Godot project folder (e.g.,res://models/) - Select the
Syncnode in your scene - Set
Control ModetoONNX_INFERENCE - Set
Onnx Model Pathtores://models/ballchase_final.onnx - Run the scene — the agent plays without any Python process
Sync node properties:
Control Mode: ONNX_INFERENCE
Onnx Model Path: res://models/ballchase_final.onnx
Speed Up: 1 ← real-time, not accelerated
The agent runs at game speed. You can add human-controlled characters, obstacles, or UI around the AI agent — it's now just another Godot node.
Done when
The scene runs in ONNX_INFERENCE mode and the agent plays competently with no Python process alive — no gdrl in your process list, no conda environment activated, nothing to Ctrl-C. At the Section 9 viz checkpoint the agent shows the same competence it had at the end of training — don't expect frame-identical runs: training sampled actions stochastically, while the exported policy acts deterministically, so slightly smoother, less jittery behavior is normal. An agent that jitters randomly or freezes points to a wrong model path or an observation-shape mismatch (Section 4), not a broken runtime.
6 · Export for desktop
Export a standard desktop binary with the AI baked in:
- Project → Export → Add preset (Linux / Windows / macOS)
- Resources tab: make sure
*.onnxfiles are included in the export filter - Export → Export Project (not PCK)
The result is a standalone executable — no Python, no conda environment needed.
7 · HTML5 / WASM export (optional)
Godot can export to WebAssembly, making the game playable in a browser. The ONNX runtime works in WASM.
Requirements:
- Godot 4.x with the HTML5 export template installed
- A web server (GitHub Pages, itch.io, Netlify — all work)
Steps:
- Project → Export → Add preset → Web
- Enable
Export Type: Release - Resources: include
*.onnx - Export → Export Project → choose
index.htmloutput path - Upload the output folder to your web server
SharedArrayBuffer
HTML5 Godot exports require SharedArrayBuffer, which needs specific HTTP headers (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp). GitHub Pages supports this; basic file hosting does not. Check your host's documentation.
Publish on itch.io:
- Create a project on itch.io
- Kind: HTML
- Upload the exported folder as a zip
- Check "This file will be played in the browser"
- Share the URL
Anyone with a browser can now watch — and interact with — your trained agent.
8 · Swap agents at runtime (advanced)
You can load different ONNX models at runtime using GDScript:
# In a scene script — swap agent brain on button press
@onready var sync_node = $Sync
func _on_swap_pressed():
sync_node.onnx_model_path = "res://models/alternative_brain.onnx"
sync_node.reload_model()
This lets you build "replay demos" that switch between a random policy, a BC clone, and a fine-tuned PPO agent — all in one scene, no Python restarts.
9 · Viz checkpoint
Run your exported binary or HTML5 build for 5 minutes:
- Is the agent's behavior identical to what you saw during training? (It should be — ONNX is deterministic)
- Frame rate stable? ONNX inference adds < 1ms per step for MLP policies
- For HTML5: test on mobile and a low-end laptop — the WASM runtime is leaner than desktop Godot
10 · Stretch goals
- A/B test in-browser — export two models (PPO vs BC fine-tune from Unit 9); build a Godot UI that lets users switch between them and vote on which looks more natural
- Quantize the ONNX model — use
onnxruntimetools to reduce the model to INT8; measure size and speed difference - Continuous deployment — set up a GitHub Actions workflow: push new training results → auto-export ONNX → deploy HTML5 to GitHub Pages
What's next
You've completed the course. Here's what you've built:
| Unit | Skill |
|---|---|
| 0 | Run a Godot RL environment |
| 1 | Understand the agent–environment loop |
| 2 | Build a custom Godot RL env from scratch |
| 3 | DQN for sparse discrete tasks |
| 4 | PPO hyperparameter tuning |
| 5 | Parallel training at scale |
| 6 | Continuous action spaces + normalization |
| 7 | Multi-agent: cooperative and competitive |
| 8 | Memory and POMDPs with RecurrentPPO |
| 9 | Imitation learning: BC and GAIL |
| 10 | ONNX export + Godot inference + HTML5 |
What comes next: Put everything together in the capstone — pick your own environment, design your own reward, train and ship it.
Self-check before you move on
Can you answer these in your own words?
- Why does the shipping path go through ONNX instead of loading the SB3
.zipdirectly in Godot? - What three things must match between training and inference so the policy doesn't silently produce garbage actions?
- What's the difference between a resume-training checkpoint and an export-for-inference model — what state does each contain?
- When would you choose HTML5/WASM export over a desktop binary, and what does that cost the agent?
- After export, how would you sanity-check that the Godot-side inference matches the Python-side training rollouts?
If you can answer all five — you're ready for the capstone.
Self-check answers
- The SB3
.zipis a PyTorch artifact — loading it requires a live Python process. ONNX is a standard interchange format, and Godot RL Agents ships a GDScript ONNX runtime, so the shipped game runs the policy with no Python at all (Section 1). - The observation shape, the action space, and the meaning/order of the values your Godot env feeds the model. If any of these drift between training and the shipped scene, the graph still runs — it just maps garbage in to garbage out. Netron (Section 4) catches the shape part.
- A resume-training checkpoint (
--resume_model_path) contains weights, optimizer state, and step count, so training continues exactly where it stopped (Section 2). The exported ONNX contains only the policy's forward pass — enough to act, not enough to keep learning. - Choose HTML5/WASM when you want anyone to play via a URL without installing anything. It costs hosting constraints — the host must serve the
SharedArrayBuffer(COOP/COEP) headers, which itch.io offers as an opt-in — and performance headroom, which is why the viz checkpoint tells you to test on mobile and a low-end laptop. - Run the Section 9 viz checkpoint: play the exported build for several minutes. ONNX inference is deterministic — the same observation always produces the same action — so behavior is repeatable across runs of the build; expect it slightly smoother than training rollouts, which sampled actions stochastically. Gross misbehavior (jitter, freezing, wandering) signals an export problem, not randomness.