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

Graph optimization

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.

04 · Graph Optimizations

Introduction

A graph with separate convolution, normalization, and activation nodes may launch more kernels than necessary. Runtime optimization can fold constants, remove dead work, and fuse supported patterns while preserving model outputs.

Learning goal

Compare optimization levels, inspect a saved optimized graph, and verify that transformed outputs remain correct.

Before you start

ONNX graph nodes, inference sessions, numerical output comparison, and basic performance terminology.

Lesson plan

  1. Identify constant folding, dead-node removal, and operator fusion in a small graph.
  2. Change runtime optimization levels and inspect which transformations are provider-dependent.
  3. Save the optimized model and compare outputs before trusting performance improvements.

Consider y = relu(xW+b). A runtime may fuse these operations or precompute constants while preserving the output. Verify equivalence before and after optimization. A smaller node count is useful only when the model still answers correctly.

At session creation ORT rewrites your graph. The trigger to see it: point optimized_model_filepath at a file and the session writes the post-optimization graph to disk.

LevelWhat it enables
ORT_DISABLE_ALLNothing. Graph runs as written (kernel impls still optimized)
ORT_ENABLE_BASICConstant folding, redundant-node removal, identity elimination
ORT_ENABLE_EXTENDEDBASIC + node fusion: Conv+BN, Conv+ReLU, MatMul+Add, …
ORT_ENABLE_ALLEXTENDED + provider-tuned layout transforms (e.g. NCHWc on CPU)
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
so.optimized_model_filepath = "opt_all.onnx"     # audit what the runtime actually runs
sess = ort.InferenceSession("convnet.onnx", so, providers=["CPUExecutionProvider"])
Interactive · Fusion playground (illustrative)
Conv, BatchNorm, ReLU, Flatten, Gemm run as separate kernels. Frontier nodes (Input, Output) are data, not compute.

The notebook proves this on a real model: node lists at each optimization level, a constant-folding demo, and a latency comparison. Expect the fusion levels to collapse Conv + BatchNormalization + Relu — the exact resulting op names are runtime- and EP-dependent, which is why you dump the graph instead of trusting folklore.

▶ Run notebook 04 in Colab 04-graph-optimization.ipynb CPU runtime · ~30 seconds
Pin your runtime in deployment

Optimization passes change between ORT versions. A graph that fused here may split there. Version-pin the runtime, or snapshot the optimized graph and diff it in CI.