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

Sessions and memory

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.

06 · Sessions, IOBinding and Memory

Introduction

Creating a session prepares an optimized graph, selected kernels, and reusable memory structures. Reusing that session avoids repeated setup, while IOBinding can also avoid unnecessary copies between host and device memory.

Learning goal

Configure and reuse inference sessions, reason about threading and memory arenas, and apply IOBinding correctly.

Before you start

Inference sessions, tensor shapes, CPU and device memory, and Python context managers.

Lesson plan

  1. Separate one-time session preparation from the work repeated for each request.
  2. Examine thread settings and memory arenas with their latency and throughput trade-offs.
  3. Bind inputs and outputs to chosen devices while preserving ownership and synchronization.

A session owns the loaded graph and execution plan. Reuse it across requests when practical. IOBinding controls where input and output buffers live; it helps only when it avoids real copies or allocations in the measured path.

A session owns three expensive things: the optimized graph, the kernels, and memory arenas. Per call, the costs are input copy-in, output allocation, and kernel launches. Two session options and one API control nearly all of it:

KnobDefaultWhat it doesServing advice
intra_op_num_threadsautoparallelism inside an op (matmul fan-out)Set to 1 and scale with request-level parallelism
inter_op_num_threadsautoparallelism across independent nodesUsually leave alone
enable_cpu_mem_arenaonreuse preallocated tensors instead of malloc per runLeave on; turn off only to debug memory
enable_mem_patternonreuse the allocation plan for repeated shapesLeave on

IOBinding: stop copying

Normal sess.run() copies inputs into runtime buffers and allocates fresh outputs. IOBinding hands over memory directly. On CPU the win is modest; the same API is how GPU inference avoids PCIe round-trips.

io = sess.io_binding()
io.bind_cpu_input("X", x)            # input stays where it is
io.bind_output("Y", "cpu")           # runtime-owned output buffer
sess.run_with_iobinding(io)
y = io.copy_outputs_to_cpu()[0]      # one copy, only when you ask

# fixed shapes: own the output buffer yourself
out = ort.OrtValue.ortvalue_from_numpy(np.empty((32, 128), dtype=np.float32))
io.bind_output(out)                  # runtime writes straight into your array
▶ Run notebook 06 in Colab 06-sessions-iobinding-memory.ipynb CPU runtime · thread sweep + IOBinding benchmark