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

Dynamic shapes

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.

08 · Dynamic Shapes and the ORT Format

Introduction

A model may accept batches from 1 to 64 while keeping feature width 256. Dynamic dimensions improve flexibility, but accelerators such as TensorRT still need bounded profiles to prepare efficient engines.

Learning goal

Represent symbolic tensor dimensions, configure bounded accelerator shape profiles, and distinguish ONNX from optimized ORT artifacts.

Before you start

Tensor shapes, exported ONNX models, execution providers, and fixed versus variable dimensions.

Lesson plan

  1. Mark selected axes dynamic while keeping each dimension's meaning and constraints clear.
  2. Use symbolic inference and TensorRT minimum, preferred, and maximum shape profiles.
  3. Convert to ORT format and understand its practical portability-versus-startup trade-off.

Dynamic means a dimension may change between runs; it does not mean every dimension is unconstrained. A model can accept batch sizes 1 and 8 while requiring feature width 768. Name which axes vary before configuring profiles or testing exports.

Weights are fixed at export; shapes are a deployment decision. A dimension is either fixed ([4, 512] — fastest to plan, breaks on other sizes) or symbolic (["batch", 512] — flexible, re-plans per shape).

Shape-specialized EPs turn this into a contract. TensorRT builds an engine per shape range, and the profile you give it decides which batch sizes get optimized kernels:

("TensorrtExecutionProvider", {
    "trt_fp16_enable": True,
    "trt_engine_cache_enable": True,          # rebuilds happen once, not per process
    "trt_engine_cache_path": "./trt_cache",
    "trt_profile_min_shapes": "input:1x256",
    "trt_profile_opt_shapes": "input:16x256", # optimize for your MEDIAN batch
    "trt_profile_max_shapes": "input:64x256", # required — never leave unbounded
})
Shape out of profile

A shape outside the profile either falls back to CPU or triggers an engine rebuild. With engine caching, the rebuild happens once; without it, every new shape pays build cost. Production traffic that drifts past your max shape will show up as a latency cliff, not an error.

The .ort format: freeze the optimization

python -m onnxruntime.tools.convert_onnx_models_to_ort model.onnx     # writes model.ort
python -m onnxruntime.tools.symbolic_shape_infer --input m.onnx --output m_shaped.onnx --auto_merge

The conversion runs the optimizer and freezes the result into ORT's flatbuffer format — faster loads, no dead graph. But it is a build artifact, not a portable model: it is tied to the runtime build and EP set that produced it. Generate it in CI for a known target.

▶ Run notebook 08 in Colab 08-dynamic-shapes-ort-format.ipynb CPU runtime · one session, batches 1→256, then .ort conversion