§ ONNX Runtime for Engineers
9 lessons · every lesson has a Colab notebook

Export from PyTorch

Nine lessons on running trained models with ONNX Runtime. Each lesson links to a Colab notebook for hands-on practice. The core exercises use a CPU; GPU work is optional.

Before you begin

You should be comfortable with Python functions and arrays. Inference means using a trained model to calculate an answer. A graph describes the operations used in that calculation. An operator is one kind of operation, such as addition or matrix multiplication.

Read the plain-language introduction, try its question, then open the notebook. Keep your measured results separate from the illustrative simulations on this page.

Technical references: ONNX Runtime Python API and quantization guidance.

03 · Exporting from PyTorch

Introduction

Export success does not prove that the deployed model is correct. A reliable path runs one fixed input through PyTorch and ONNX Runtime, checks the artifact, and compares both shapes and numeric values.

Learning goal

Export a PyTorch model and verify its structure and numerical output for fixed and dynamic inputs.

Before you start

PyTorch modules, tensors and shapes, evaluation mode, NumPy arrays, and inference outputs.

Lesson plan

  1. Prepare a deterministic reference input and place the PyTorch model in evaluation mode.
  2. Choose names, dynamic axes, and exporter settings while writing the ONNX artifact.
  3. Run checker and runtime comparisons, then review alternative framework export paths.

Predict the acceptance check: for the same fixed input, PyTorch and ONNX Runtime should return the same shape and numerically close values. A successful export call only proves that a file was written.

The pipeline is three steps, and only the middle one is magic:

nn.Module (eval)  ──torch.onnx.export──▶  model.onnx  ──onnx.checker + ORT──▶  verified artifact

The export knobs that matter

torch.onnx.export(
    model,                      # .eval() — training layers behave differently
    (dummy_input,),             # shapes get traced through this path
    "model.onnx",
    input_names=["input"],      # NAME YOUR TENSORS
    output_names=["logits"],
    opset_version=17,           # pin it; verify the runtime supports it
    do_constant_folding=True,   # fold constant subgraphs at export time
    dynamic_axes={"input": {0: "batch"}, "logits": {0: "batch"}},
)
Two exporters, one API

Modern PyTorch has a classic TorchScript exporter (dynamo=False, where dynamic_axes lives) and a torch.export-based exporter (dynamo=True, which uses dynamic_shapes instead). The notebook detects which exists and stays on the classic path — and tells you when the deprecation warning appears. Exporters will keep changing; the verification step never does.

The step everyone skips
with torch.no_grad():
    ref = model(x).numpy()
got = sess.run(None, {"input": x.numpy()})[0]
assert np.allclose(ref, got, atol=1e-5)      # never trust an unchecked export

What a clean export looks like

LinearGemm, ReLURelu, no aten:: remnants, no Python control flow. If you see chains of Slice/Squeeze/Unsqueeze/Concat around what should be one op, your module is fragmented — fix the module, not the runtime.

Static shapes bite

A model traced with batch 1 has batch 1 baked in. It runs in dev and fails on real traffic. The notebook demonstrates the failure, then fixes it with dynamic_axes and runs batches 1, 5, and 32 on one session.

▶ Run notebook 03 in Colab 03-export-from-pytorch.ipynb CPU runtime · ~40 seconds (torch preinstalled)

Other paths in

FrameworkExporter
TensorFlow / Kerastf2onnx
scikit-learnskl2onnx
Hugging Face modelsoptimum (wraps export with correct axes/opsets)
JAX / Flaxjax2tf then tf2onnx