Skip to content
AuvrynStart building

Developers

How to check and validate an ONNX model before deployment

Validating an ONNX model means answering four questions before it reaches production: is the file structurally valid (the ONNX checker with full_check), what does it expect and produce (IR version, opsets, input and output names, types and shapes), does it actually load and run in the runtime you will use (a test session in ONNX Runtime), and does it give the same answers as the model it was exported from (a numerical comparison with tolerances). Record the file’s SHA-256 alongside the result, so you know later exactly which file passed.

Auvryn is provider-neutral execution infrastructure for AI models. It validates ONNX models, runs them as durable ExecutionJobs on infrastructure it does not own, and returns verified results with provenance and telemetry.

Why a model that exported cleanly can still fail

An export that finishes without errors only proves that a file was written. The model can still declare an opset your runtime does not support, use an operator your target lacks, carry dynamic dimensions your code does not fill, store weights in external data files that were not shipped, or produce different numbers from the original framework. Each of the checks below catches a different one of these problems; none of them alone is enough.

1. Run the ONNX checker with full_check

The checker in the onnx package verifies the model’s structure: the graph is well formed and topologically sorted, every node uses an operator that exists for the declared opset with valid attributes, and names are consistent. With full_check=True it also runs shape inference, which catches many type and shape mismatches between connected nodes.

Pass a file path instead of a loaded model when the model is larger than 2 GB: the in-memory check is limited by the protobuf size.

  • It does not execute the model, so it cannot tell you whether a runtime will load it.
  • It checks operators against the ONNX specification, not against a specific runtime or accelerator.
  • It does not compare numbers with the original framework.
import onnx

model = onnx.load("model.onnx")
onnx.checker.check_model(model, full_check=True)  # raises on failure

# For models over 2 GB, check by path:
# onnx.checker.check_model("model.onnx", full_check=True)
Structural check (onnx package)

2. Read what the model declares: IR version, opsets, inputs, outputs

Before integrating a model, read its contract. The IR version is the version of the ONNX file format; the opset imports say which version of each operator set the graph was built against (the default domain is ai.onnx, often written as an empty string). Inputs and outputs have a name, an element type and a shape, where each dimension is either fixed (a number) or symbolic (a name such as “batch”), meaning the caller decides it at run time.

import onnx
from onnx import TensorProto

model = onnx.load("model.onnx")
print("IR version:", model.ir_version)
for opset in model.opset_import:
    print("opset:", opset.domain or "ai.onnx", opset.version)
print("producer:", model.producer_name, model.producer_version)

def describe(value):
    t = value.type.tensor_type
    dims = [d.dim_value if d.HasField("dim_value") else (d.dim_param or "?")
            for d in t.shape.dim]
    return value.name, TensorProto.DataType.Name(t.elem_type), dims

initializers = {i.name for i in model.graph.initializer}
for value in model.graph.input:
    if value.name not in initializers:  # older exports list weights as inputs
        print("input ", *describe(value))
for value in model.graph.output:
    print("output", *describe(value))
print("operators:", sorted({n.op_type for n in model.graph.node}))
Inspect the contract (onnx package)
Fixed dimension
A number, such as 3 channels or 224 pixels. The caller must match it exactly.
Symbolic dimension
A name, such as “batch” or “sequence”. The caller chooses the size; the model must support it.
Operator list
Compare it with what your target runtime and execution provider support for that opset: an operator valid in ONNX can still be unsupported on a given accelerator.
External data
Large models may keep weights in separate files next to the .onnx file. Ship them together, or the model will not load elsewhere.

3. Load it and run it once in ONNX Runtime

The checker cannot tell you whether the runtime you will deploy accepts the model. Create an inference session with the execution provider you plan to use, feed inputs of the declared types and shapes, and run it once. Zero-filled inputs are enough to prove that the model loads and executes; they say nothing about accuracy.

  • Run it with the same ONNX Runtime version and execution provider you will deploy: support differs between versions and providers.
  • If the target is not a CPU, repeat the smoke test there; a model that runs on CPU can still fail on a GPU or NPU provider.
import numpy as np
import onnxruntime as ort

DTYPES = {"tensor(float)": np.float32, "tensor(float16)": np.float16,
          "tensor(double)": np.float64, "tensor(int64)": np.int64,
          "tensor(int32)": np.int32, "tensor(uint8)": np.uint8,
          "tensor(bool)": np.bool_}

session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
feeds = {}
for i in session.get_inputs():
    shape = [d if isinstance(d, int) else 1 for d in i.shape]  # symbolic -> 1
    feeds[i.name] = np.zeros(shape, dtype=DTYPES[i.type])

outputs = session.run(None, feeds)
print("onnxruntime", ort.__version__, [o.shape for o in outputs])
Smoke test in ONNX Runtime (CPU)

4. Compare its outputs with the original model

Run the same, already preprocessed input through the source framework and through ONNX Runtime and compare with tolerances, not exact equality. Small differences are normal; large ones usually mean an export or preprocessing problem.

import numpy as np

x = np.load("input.npy")                  # the exact same, preprocessed input
reference = run_original_model(x)         # e.g. PyTorch in eval() mode
candidate = session.run(None, {session.get_inputs()[0].name: x})[0]

print("max abs diff:", np.abs(reference - candidate).max())
np.testing.assert_allclose(reference, candidate, rtol=1e-3, atol=1e-5)
Numerical parity with tolerances

5. Record what passed

Compute the file’s SHA-256 and keep it with the validation result, the runtime version and the execution provider you tested. Names and version labels get reused; the hash identifies the exact bytes that passed, so later you can tell whether the model running in production is the one you validated.

import hashlib

with open("model.onnx", "rb") as f:
    print(hashlib.sha256(f.read()).hexdigest())

# or: sha256sum model.onnx
Identity of the file that passed

Common errors and what they mean

Real messages, reproduced with onnx 1.23.2 and onnxruntime 1.31.0 on small test models. The first two come from the checker; the rest appear only when the runtime loads or runs the model — which is why the checker alone is not enough.

ValidationError: No Op registered for NotAnOp with domain_version of 17
The graph uses an operator that does not exist in the declared opset or domain: a custom operator, a typo from a graph edit, or an operator added in a later opset. Export with the right opset, or register the custom operator’s domain.
InferenceError: [ShapeInferenceError] (op_type:Add): B has inconsistent type tensor(int64)
Shape and type inference found two connected tensors with incompatible types. Usually a missing cast in the exported graph; fix it in the source model and export again.
FAIL : … ONNX Runtime only *guarantees* support for models stamped with official released onnx opset versions
The model declares an opset newer than this ONNX Runtime release supports. Upgrade the runtime, or export with an older opset.
NOT_IMPLEMENTED : Could not find an implementation for Abs(13) node
The operator and type are valid ONNX — the checker passes — but this runtime and execution provider have no kernel for them (here Abs on bfloat16 on the CPU provider). Check the provider’s operator support, or change the type.
INVALID_ARGUMENT : Unexpected input data type. Actual: (tensor(double)) , expected: (tensor(float))
The input array has the wrong dtype; numpy defaults to float64. Cast to the declared element type (float32 here).
INVALID_ARGUMENT : Invalid rank for input / Got invalid dimensions for input
The input shape does not match a fixed dimension or the number of dimensions the model declares. Read the contract (step 2) and reshape.
Required inputs (['input']) are missing from input feed
The feed uses a different input name. Use the names from session.get_inputs().

Pre-deployment checklist

  • onnx.checker.check_model(..., full_check=True) passes.
  • IR version and opsets are supported by the runtime version you deploy.
  • Every operator is supported by the execution provider you deploy.
  • Input and output names, types and shapes match the calling code; symbolic dimensions are filled.
  • External data files, if any, ship with the model.
  • The model loads and runs once in the target runtime and provider.
  • Outputs match the source model within stated tolerances on representative inputs.
  • The SHA-256 of the file, the runtime version and the provider are recorded with the result.

Validate an ONNX model with Auvryn

Auvryn runs the first steps of this procedure for every ONNX model you upload, without installing anything. Uploading, validating and testing a model is self-service after sign-up.

  • The upload is identified by its SHA-256.
  • Validation runs onnx.checker.check_model with full_check=True, without executing the model. Models that use external data files are not supported.
  • The result lists the IR version, opset imports, producer, every input and output with its element type and shape (fixed, symbolic or undeclared), node and initializer counts and the operator types.
  • A sandbox test loads and runs the model once on ONNX Runtime (CPU) in an isolated container without network, with zero-filled inputs and symbolic dimensions set to 1, and reports the runtime version, output shapes and first values, load and execution time and peak memory. Only numeric and boolean inputs are supported.

What this does not tell you

Passing these checks means the file is valid, loads and runs, and matches its source on the inputs you tried. It does not prove accuracy on your data, performance on your hardware, or behaviour on execution providers you did not test. Auvryn’s sandbox tests on CPU only; it is not a benchmark and not execution on production infrastructure.

The code on this page was run on 2026-10-16 with onnx 1.23.2, onnxruntime 1.31.0 and numpy 2.4.6; the PyTorch line in the parity step is illustrative.

Primary sources

Try it

Frequently asked questions

How do I check if an ONNX model is valid?

Load it with the onnx package and run onnx.checker.check_model(model, full_check=True). It raises an error if the structure, operators or inferred shapes are invalid. Then load it once in ONNX Runtime to make sure your runtime accepts it.

How do I find the opset version of an ONNX model?

Read model.opset_import after onnx.load: each entry has a domain (empty means the default ai.onnx domain) and a version.

How do I get the input and output names and shapes?

Iterate over model.graph.input and model.graph.output; each dimension is either dim_value (fixed) or dim_param (symbolic). In ONNX Runtime, session.get_inputs() and get_outputs() return the same information.

Does passing the ONNX checker mean the model will run?

No. The checker validates the model against the ONNX specification; a runtime or execution provider can still reject an operator, an opset or a data type. Test-load it in the runtime you will deploy.

Can I validate an ONNX model online?

Viewers such as Netron show the graph in a browser. Auvryn validates an uploaded model with the ONNX checker, reports its contract and test-runs it in an isolated sandbox, after sign-up.

Facts reviewed 2026-10-08.