# Loading ONNX Networks in the VERONA Verification Pipeline: A Complete Guide

> Learn to load ONNX networks in the VERONA verification pipeline. VERONA efficiently handles ONNX models, streamlining your neural network verification workflows. Get the complete guide.

- Repository: [ADA research/verona](https://github.com/ada-research/verona)
- Tags: how-to-guide
- Published: 2026-02-23

---

**VERONA treats ONNX models as first-class `ONNXNetwork` objects that handle lazy protobuf loading, input shape extraction, PyTorch conversion, and JSON serialization to streamline neural network verification workflows.**

The VERONA framework (ada-research/verona) standardizes how verification pipelines ingest machine learning models. When loading ONNX networks in VERONA verification pipeline implementations, the system wraps each ONNX file in an `ONNXNetwork` instance located in [`ada_verona/database/machine_learning_model/onnx_network.py`](https://github.com/ada-research/verona/blob/main/ada_verona/database/machine_learning_model/onnx_network.py). This class abstracts format-specific complexities while exposing the metadata and interfaces required by both formal verifiers and gradient-based attack estimators.

## Core Architecture of the ONNXNetwork Class

The `ONNXNetwork` class serves as the concrete implementation for machine learning models within VERONA's database layer. It encapsulates five critical responsibilities that enable seamless integration with the verification ecosystem.

### Storage and Path Management

The constructor (`__init__`) receives a `Path` object and stores it in the private attribute `_path`, exposing it via the `path` property. This design ensures that downstream components—particularly the `AutoVerifyModule`—can always locate the original ONNX file for external verifier invocation.

### Lazy Loading of ONNX Protobufs

The `load_onnx_model()` method implements lazy initialization by calling `onnx.load(str(self.path))` only when first accessed. The resulting protobuf object is cached in `self.onnx_model`, preventing redundant disk I/O during repeated verification attempts.

### Input Shape Extraction

Dynamic neural network inputs are handled by `get_input_shape()`, which reads the first input tensor from the ONNX protobuf. The method converts dynamic dimensions (represented as `0` in ONNX) to `-1` and returns a list of integers, providing the static shape metadata required by property generators.

### PyTorch Conversion for Attack Estimation

To support gradient-based attacks, `load_pytorch_model()` leverages the **onnx-to-torch** library via `convert(self.path)`. This returns a `torch.nn.Module` wrapped in a `TorchModelWrapper` that also stores the input shape, enabling seamless use by PGD and FGSM implementations in `ada_verona/verification_module/attacks/`.

### Serialization Support

The class implements `to_dict()`, `from_dict()`, and `from_file()` methods to make networks JSON-serializable. This allows complete verification runs to be persisted and reconstructed, with `VerificationContext.from_dict()` invoking `ONNXNetwork.from_dict()` to restore model objects from saved state.

## Integration Flow Through the Verification Pipeline

Understanding how `ONNXNetwork` instances flow through VERONA explains why the abstraction matters for robustness verification.

### 1. Verification Context Creation

When configuring a verification job, the system constructs a `VerificationContext` (defined in [`ada_verona/database/verification_context.py`](https://github.com/ada-research/verona/blob/main/ada_verona/database/verification_context.py)). This context stores the `network` attribute as an `ONNXNetwork` instance, alongside the data point and property generator. During deserialization, the context uses `ONNXNetwork.from_dict()` to reconstruct the model object from JSON.

### 2. Property Generation

The `PropertyGenerator` (such as `One2OnePropertyGenerator` in [`ada_verona/verification_module/property_generator/one2one_property_generator.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/property_generator/one2one_property_generator.py)) queries `verification_context.network.get_input_shape()` to determine the tensor dimensions. This shape information shapes the VNNLib constraints that model the robustness property, ensuring the specification matches the network's input layer.

### 3. Verification Execution

The `AutoVerifyModule` (located in [`ada_verona/verification_module/auto_verify_module.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/auto_verify_module.py)) receives the context and writes the VNNLib file based on the property generator's output. It then invokes the selected external verifier using `verification_context.network.path`, passing the original ONNX file path rather than an intermediate representation.

### 4. Attack Estimation (Optional)

When the pipeline includes adversarial robustness estimation, the `attack_estimation_module` calls `verification_context.network.load_pytorch_model()`. This returns a `TorchModelWrapper` compatible with the PGD and FGSM wrappers in [`ada_verona/verification_module/attacks/pgd_attack.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/attacks/pgd_attack.py), enabling gradient-based perturbation generation without manual model conversion.

## Practical Code Examples

### Instantiating and Inspecting an ONNXNetwork

```python
from pathlib import Path
from ada_verona.database.machine_learning_model.onnx_network import ONNXNetwork

# Path to an exported ONNX model

onnx_path = Path("examples/example_experiment/data/networks/mnist-net_256x2.onnx")

# Create the network object

network = ONNXNetwork(onnx_path)

# Load the raw ONNX protobuf (cached on subsequent calls)

onnx_proto = network.load_onnx_model()
print("ONNX opset:", onnx_proto.opset_import[0].version)

# Query the expected input shape (e.g. [1, 1, 28, 28] for MNIST)

print("Input shape:", network.get_input_shape())

```

### Converting ONNX to PyTorch for Attack Estimators

```python
import torch

# Convert ONNX → PyTorch and obtain a wrapper that knows the input shape

torch_wrapper = network.load_pytorch_model()

# The underlying torch.nn.Module can be used like any regular model

torch_model = torch_wrapper.model
torch_model.eval()

# Run a forward pass on a dummy tensor with the correct shape

dummy = torch.randn(network.get_input_shape())
logits = torch_model(dummy)
print("Logits shape:", logits.shape)

```

### Building a VerificationContext for a Single Image

```python
import pandas as pd
from ada_verona.database.verification_context import VerificationContext
from ada_verona.database.dataset.data_point import DataPoint
from ada_verona.verification_module.property_generator.one2one_property_generator import One2OnePropertyGenerator

# Example data point (replace with a real torch tensor)

image_tensor = torch.randn(*network.get_input_shape())
data_point = DataPoint(id="img_001", label=3, data=image_tensor)

# Property generator that creates a VNNLib file for a given epsilon

prop_gen = One2OnePropertyGenerator()

# Temporary folder where VERONA will write VNNLib & CSV files

tmp_dir = Path("./tmp_verification")
tmp_dir.mkdir(parents=True, exist_ok=True)

# Assemble the context

ctx = VerificationContext(
    network=network,
    data_point=data_point,
    tmp_path=tmp_dir,
    property_generator=prop_gen,
)

# The context now holds everything needed for the verifier:

print("Network name:", ctx.network.name)
print("Temporary folder:", ctx.tmp_path)

```

### Running the Automatic Verifier

```python
from autoverify.verifier.verifier import Verifier
from ada_verona.verification_module.auto_verify_module import AutoVerifyModule

# Assume an installed verifier (e.g., "sdpcrown")

verifier = Verifier(name="sdpcrown")          # placeholder – see autoverify docs

module = AutoVerifyModule(verifier=verifier, timeout=300.0)

epsilon = 0.03
result = module.verify(ctx, epsilon)

if isinstance(result, str):
    print("Verification failed:", result)
else:
    print("Verification outcome:", result.result)   # "SAT" or "UNSAT"

```

## Key Implementation Files

Several source files collaborate to enable ONNX support in VERONA:

- **[`ada_verona/database/machine_learning_model/onnx_network.py`](https://github.com/ada-research/verona/blob/main/ada_verona/database/machine_learning_model/onnx_network.py)**: Contains the core `ONNXNetwork` implementation, including lazy loading, shape extraction, and ONNX-to-PyTorch conversion.

- **[`ada_verona/database/verification_context.py`](https://github.com/ada-research/verona/blob/main/ada_verona/database/verification_context.py)**: Defines the `VerificationContext` class that stores the network alongside data points and property generators, handling reconstruction via `from_dict()`.

- **[`ada_verona/verification_module/auto_verify_module.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/auto_verify_module.py)**: Consumes the context and invokes external verifiers using the ONNX file path stored in the network object.

- **[`ada_verona/verification_module/attack_estimation_module.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/attack_estimation_module.py)**: Demonstrates consumption of the PyTorch wrapper for gradient-based attacks.

- **[`ada_verona/verification_module/attacks/pgd_attack.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/attacks/pgd_attack.py)**: Uses the `TorchModelWrapper` returned by `load_pytorch_model()` to craft adversarial examples.

- **[`ada_verona/verification_module/property_generator/one2one_property_generator.py`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/property_generator/one2one_property_generator.py)**: Generates VNNLib constraints based on the network's input shape obtained via `get_input_shape()`.

## Summary

- **ONNXNetwork** encapsulates ONNX models in VERONA, providing lazy loading via `load_onnx_model()` and shape introspection via `get_input_shape()`.

- The class converts ONNX to PyTorch on demand using **onnx-to-torch**, wrapped in a `TorchModelWrapper` for attack estimation modules.

- **VerificationContext** stores the network and handles serialization through `to_dict()` and `from_dict()`, enabling reproducible verification runs.

- The `AutoVerifyModule` accesses `network.path` to pass the original ONNX file to external verifiers, while property generators use `network.get_input_shape()` to construct valid VNNLib specifications.

## Frequently Asked Questions

### How does VERONA handle dynamic input dimensions in ONNX models?

The `get_input_shape()` method in `ONNXNetwork` automatically converts dynamic dimensions (represented as `0` in the ONNX protobuf) to `-1`. This ensures that property generators receive a consistent list of integers representing the input tensor shape, even when the original model supports variable batch sizes or spatial dimensions.

### Can VERONA serialize verification runs that use ONNX models?

Yes. The `ONNXNetwork` class implements `to_dict()` and `from_dict()` methods that serialize the model's file path and metadata to JSON. When a `VerificationContext` is saved and later reconstructed, it calls `ONNXNetwork.from_dict()` to restore the network object, enabling full reproducibility of verification experiments without reloading raw model data.

### What conversion library does VERONA use for ONNX to PyTorch translation?

VERONA uses the **onnx-to-torch** library via the `convert()` function inside `load_pytorch_model()`. This conversion creates a `torch.nn.Module` that is wrapped in a `TorchModelWrapper` along with the network's input shape, making it available to gradient-based attack implementations like PGD and FGSM.

### How does the verification context pass ONNX models to external verifiers?

The `AutoVerifyModule` extracts the file path via `verification_context.network.path` and passes this path directly to the external verification tool. This approach avoids intermediate representation issues by ensuring the verifier receives the original ONNX file, while VERONA manages all preprocessing and property specification generation internally.