Crates.io | runnx |
lib.rs | runnx |
version | 0.2.1 |
created_at | 2025-07-21 22:53:41.048823+00 |
updated_at | 2025-09-01 14:23:54.663143+00 |
description | A minimal, verifiable ONNX runtime implementation in Rust |
homepage | |
repository | https://github.com/JGalego/runnx |
max_upload_size | |
id | 1762781 |
size | 600,730 |
A minimal, mathematically verifiable ONNX runtime implementation in Rust.
Fast, fearless, and formally verified ONNX in Rust.
This project provides a minimal, educational ONNX runtime implementation focused on:
--graph
and --dot
optionsAdd
, Mul
, MatMul
, Conv
, Relu
, Sigmoid
, Reshape
, Transpose
Concat
, Slice
, Upsample
, MaxPool
, Softmax
, NonMaxSuppression
RunNX requires the Protocol Buffers compiler (protoc
) to build:
# Ubuntu/Debian
sudo apt-get install protobuf-compiler
# macOS
brew install protobuf
# Windows
choco install protoc
Add this to your Cargo.toml
:
[dependencies]
runnx = "0.2.0"
use runnx::{Model, Tensor};
// Load a model (supports both JSON and ONNX binary formats)
let model = Model::from_file("model.onnx")?; // Auto-detects format
// Or explicitly:
// let model = Model::from_onnx_file("model.onnx")?; // Binary ONNX
// let model = Model::from_json_file("model.json")?; // JSON format
// Create input tensor
let input = Tensor::from_array(ndarray::array![[1.0, 2.0, 3.0]]);
// Run inference
let outputs = model.run(&[("input", input)])?;
// Get results
let result = outputs.get("output").unwrap();
println!("Result: {:?}", result.data());
RunNX supports various computer vision models. Here's an example with object detection:
use runnx::Model;
// Load any compatible ONNX model (e.g., classification, detection, segmentation)
let model = Model::from_file("vision_model.onnx")?;
// For object detection models like YOLOv8, RCNN, etc.
// The runtime handles various operator types automatically
cargo run --example yolov8_detect_and_draw // YOLOv8 detection example
use runnx::Model;
let model = /* ... create or load model ... */;
// Save in different formats
model.to_file("output.onnx")?; // Auto-detects format from extension
model.to_onnx_file("binary.onnx")?; // Explicit binary ONNX format
model.to_json_file("readable.json")?; // Explicit JSON format
# Run inference on a model (supports both .onnx and .json files)
cargo run --bin runnx-runner -- --model model.onnx --input input.json
cargo run --bin runnx-runner -- --model model.json --input input.json
# Show model summary and graph visualization
cargo run --bin runnx-runner -- --model model.onnx --summary --graph
# Generate Graphviz DOT file for professional diagrams
cargo run --bin runnx-runner -- --model model.onnx --dot graph.dot
# Run specialized examples (computer vision, object detection, etc.)
cargo run --example yolov8_detect_and_draw # Object detection example
# Run async inference (requires --features async)
cargo run --features async --bin runnx-runner -- --model model.onnx --input input.json
RunNX provides comprehensive graph visualization capabilities to help you understand and debug ONNX model structures. You can visualize models both in the terminal and as publication-quality graphics.
Display beautiful ASCII art representations of your model directly in the terminal:
# Show visual graph representation
./target/debug/runnx-runner --model model.onnx --graph
# Show both model summary and graph
./target/debug/runnx-runner --model model.onnx --summary --graph
Here's what the terminal visualization looks like for a complex neural network:
┌────────────────────────────────────────┐
│ GRAPH: neural_network_demo │
└────────────────────────────────────────┘
📥 INPUTS:
┌─ image_input [1 × 3 × 224 × 224] (float32)
┌─ mask_input [1 × 1 × 224 × 224] (float32)
⚙️ INITIALIZERS:
┌─ conv1_weight [64 × 3 × 7 × 7]
┌─ conv1_bias [64]
┌─ fc_weight [1000 × 512]
┌─ fc_bias [1000]
🔄 COMPUTATION FLOW:
│
├─ Step 1: conv1
│ ┌─ Operation: Conv
│ ├─ Inputs:
│ │ └─ image_input
│ │ └─ conv1_weight
│ │ └─ conv1_bias
│ ├─ Outputs:
│ │ └─ conv1_output
│ └─ Attributes:
│ └─ kernel_shape: [7, 7]
│ └─ strides: [2, 2]
│ └─ pads: [3, 3, 3, 3]
│
├─ Step 2: relu1
│ ┌─ Operation: Relu
│ ├─ Inputs:
│ │ └─ conv1_output
│ ├─ Outputs:
│ │ └─ relu1_output
│ └─ (no attributes)
[... more steps ...]
📤 OUTPUTS:
└─ classification [1 × 1000] (float32)
└─ segmentation [1 × 21 × 224 × 224] (float32)
📊 STATISTICS:
├─ Total nodes: 10
├─ Input tensors: 2
├─ Output tensors: 2
└─ Initializers: 4
🎯 OPERATION SUMMARY:
├─ Add: 1
├─ Conv: 2
├─ Flatten: 1
├─ GlobalAveragePool: 1
├─ MatMul: 1
├─ MaxPool: 1
├─ Mul: 1
├─ Relu: 1
└─ Upsample: 1
Generate professional diagrams using DOT format for Graphviz:
# Generate DOT file for Graphviz
./target/debug/runnx-runner --model model.onnx --dot graph.dot
# Convert to PNG (requires Graphviz installation)
dot -Tpng graph.dot -o graph.png
# Convert to SVG for vector graphics
dot -Tsvg graph.dot -o graph.svg
# Convert to PDF for documents
dot -Tpdf graph.dot -o graph.pdf
The DOT format generates clean, professional diagrams with:
Example: Multi-task neural network with classification and segmentation branches
The generated DOT file contains structured graph data that Graphviz uses to create the visualizations. Here's an excerpt of the DOT format:
digraph G {
rankdir=TB;
node [shape=box, style=rounded];
"image_input" [shape=ellipse, color=green, label="image_input"];
"mask_input" [shape=ellipse, color=green, label="mask_input"];
"conv1_weight" [shape=diamond, color=blue, label="conv1_weight"];
"conv1_bias" [shape=diamond, color=blue, label="conv1_bias"];
"conv1" [label="conv1\n(Conv)"];
"relu1" [label="relu1\n(Relu)"];
"classification" [shape=ellipse, color=red, label="classification"];
"segmentation" [shape=ellipse, color=red, label="segmentation"];
"image_input" -> "conv1";
"conv1_weight" -> "conv1";
"conv1_bias" -> "conv1";
"conv1" -> "relu1";
"relu1" -> "classification";
// ... additional connections
}
The DOT format uses:
->
arrowsFor the complete DOT file example, see assets/complex_graph.dot
.
You can also generate visualizations programmatically:
use runnx::Model;
let model = Model::from_file("model.onnx")?;
// Print graph to terminal
model.print_graph();
// Generate DOT format
let dot_content = model.to_dot();
std::fs::write("graph.dot", dot_content)?;
// The graph name box automatically adjusts to any length
// Works with short names like "CNN" or very long names like
// "SuperLongComplexNeuralNetworkGraphName"
The runtime is organized into several key components:
RunNX supports both JSON and binary ONNX protobuf formats:
.json
.onnx
The Model::from_file()
method automatically detects the format based on file extension:
.onnx
files → Binary ONNX protobuf format.json
files → JSON formatFor explicit control, use:
Model::from_onnx_file()
for binary ONNX filesModel::from_json_file()
for JSON filesOperator | Status | Notes |
---|---|---|
Add |
✅ | Element-wise addition |
Mul |
✅ | Element-wise multiplication |
MatMul |
✅ | Matrix multiplication |
Conv |
✅ | 2D Convolution |
Relu |
✅ | Rectified Linear Unit |
Sigmoid |
✅ | Sigmoid activation |
Reshape |
✅ | Tensor reshaping |
Transpose |
✅ | Tensor transposition |
Operator | Status | Notes |
---|---|---|
Concat |
✅ | Tensor concatenation |
Slice |
✅ | Tensor slicing operations |
Upsample |
✅ | Feature map upsampling |
MaxPool |
✅ | Max pooling operations |
Softmax |
✅ | Softmax normalization |
NonMaxSuppression |
✅ | Non-maximum suppression |
Legend: ✅ = Fully implemented, 🚧 = In development, ❌ = Not implemented
Model Compatibility: These operators enable support for various model architectures including:
use runnx::{Model, Tensor};
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load model from file
let model = Model::from_file("path/to/model.onnx")?;
// Print model information
println!("Model: {}", model.name());
println!("Inputs: {:?}", model.input_names());
println!("Outputs: {:?}", model.output_names());
// Prepare inputs
let mut inputs = HashMap::new();
inputs.insert("input", Tensor::zeros(&[1, 3, 224, 224]));
// Run inference
let outputs = model.run(&inputs)?;
// Process outputs
for (name, tensor) in outputs {
println!("Output '{}': shape {:?}", name, tensor.shape());
}
Ok(())
}
RunNX supports various computer vision models including object detection:
# Object detection example (YOLOv8)
cargo run --example yolov8_detect_and_draw
# Expected workflow:
# 1. Model loading and validation
# 2. Image preprocessing (resize, normalize)
# 3. Inference execution
# 4. Post-processing (NMS, confidence filtering)
# 5. Visualization (bounding boxes, labels)
use runnx::*;
fn main() -> runnx::Result<()> {
// Create a simple model
let mut graph = graph::Graph::new("demo_graph".to_string());
// Add input/output specifications
let input_spec = graph::TensorSpec::new("input".to_string(), vec![Some(1), Some(4)]);
let output_spec = graph::TensorSpec::new("output".to_string(), vec![Some(1), Some(4)]);
graph.add_input(input_spec);
graph.add_output(output_spec);
// Add a ReLU node
let relu_node = graph::Node::new(
"relu_1".to_string(),
"Relu".to_string(),
vec!["input".to_string()],
vec!["output".to_string()],
);
graph.add_node(relu_node);
let model = model::Model::with_metadata(
model::ModelMetadata {
name: "demo_model".to_string(),
version: "1.0".to_string(),
description: "A simple ReLU demo model".to_string(),
producer: "RunNX Demo".to_string(),
onnx_version: "1.9.0".to_string(),
domain: "".to_string(),
},
graph,
);
// Save in both formats
model.to_json_file("demo_model.json")?;
model.to_onnx_file("demo_model.onnx")?;
// Load from both formats
let json_model = model::Model::from_json_file("demo_model.json")?;
let onnx_model = model::Model::from_onnx_file("demo_model.onnx")?;
// Auto-detection also works
let auto_json = model::Model::from_file("demo_model.json")?;
let auto_onnx = model::Model::from_file("demo_model.onnx")?;
println!("✅ All formats loaded successfully!");
println!("Original: {}", model.name());
println!("JSON: {}", json_model.name());
println!("ONNX: {}", onnx_model.name());
Ok(())
}
use runnx::{Model, Tensor};
use ndarray::Array2;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
env_logger::init();
// Create a simple linear transformation: y = x * w + b
let weights = Array2::from_shape_vec((3, 2), vec![0.5, 0.3, 0.2, 0.4, 0.1, 0.6])?;
let bias = Array2::from_shape_vec((1, 2), vec![0.1, 0.2])?;
let input = Tensor::from_array(Array2::from_shape_vec((1, 3), vec![1.0, 2.0, 3.0])?);
let w_tensor = Tensor::from_array(weights);
let b_tensor = Tensor::from_array(bias);
// Manual computation for verification
let result1 = input.matmul(&w_tensor)?;
let result2 = result1.add(&b_tensor)?;
println!("Linear transformation result: {:?}", result2.data());
Ok(())
}
# Basic model operations and format compatibility
cargo run --example onnx_demo
cargo run --example simple_model
cargo run --example format_conversion
# Computer vision applications
cargo run --example yolov8_detect_and_draw # Object detection example
cargo run --example yolov8_object_detection # Detection with post-processing
cargo run --example yolov8n_compat_demo # Model compatibility testing
# Core functionality
cargo run --example tensor_ops # Tensor operations
cargo run --example formal_verification # Mathematical verification
cargo run --example test_onnx_support # Operator support testing
use runnx::*;
fn create_simple_model() -> runnx::Result<()> {
// Create a simple neural network model
let mut graph = graph::Graph::new("custom_model".to_string());
// Define inputs and outputs
let input_spec = graph::TensorSpec::new("input".to_string(), vec![Some(1), Some(4)]);
let output_spec = graph::TensorSpec::new("output".to_string(), vec![Some(1), Some(4)]);
graph.add_input(input_spec);
graph.add_output(output_spec);
// Add operations
let relu_node = graph::Node::new(
"activation".to_string(),
"Relu".to_string(),
vec!["input".to_string()],
vec!["output".to_string()],
);
graph.add_node(relu_node);
// Create model with metadata
let model = model::Model::with_metadata(
model::ModelMetadata {
name: "custom_neural_network".to_string(),
version: "1.0".to_string(),
description: "Custom model example".to_string(),
producer: "RunNX".to_string(),
onnx_version: "1.9.0".to_string(),
domain: "".to_string(),
},
graph,
);
// Save in multiple formats
model.to_json_file("custom_model.json")?;
model.to_onnx_file("custom_model.onnx")?;
println!("✅ Custom model created and saved!");
Ok(())
}
graph.add_node(silu_sigmoid);
graph.add_node(silu_mul);
// Multi-scale feature processing
let upsample = graph::Node::new(
"upsample".to_string(),
"Upsample".to_string(),
vec!["silu_out".to_string()],
vec!["upsampled".to_string()],
);
let concat = graph::Node::new(
"concat".to_string(),
"Concat".to_string(),
vec!["upsampled".to_string(), "silu_out".to_string()],
vec!["concat_out".to_string()],
);
graph.add_node(upsample);
graph.add_node(concat);
// Detection head with Softmax
let head_conv = graph::Node::new(
"head_conv".to_string(),
"Conv".to_string(),
vec!["concat_out".to_string()],
vec!["raw_detections".to_string()],
);
let softmax = graph::Node::new(
"softmax".to_string(),
"Softmax".to_string(),
vec!["raw_detections".to_string()],
vec!["detections".to_string()],
);
graph.add_node(head_conv);
graph.add_node(softmax);
let model = model::Model::with_metadata(
model::ModelMetadata {
name: "yolo_demo_v1".to_string(),
version: "1.0".to_string(),
description: "YOLO-like object detection model".to_string(),
producer: "RunNX YOLO Demo".to_string(),
onnx_version: "1.9.0".to_string(),
domain: "".to_string(),
},
graph,
);
println!("🎯 YOLO Model Created!");
println!(" Inputs: {} ({})", model.graph.inputs.len(), model.graph.inputs[0].name);
println!(" Outputs: {} ({})", model.graph.outputs.len(), model.graph.outputs[0].name);
println!(" Nodes: {} (Conv, SiLU, Upsample, Concat, Softmax)", model.graph.nodes.len());
Ok(())
}
### Basic Model Loading
```rust
use runnx::{Model, Tensor};
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load model from file
let model = Model::from_file("path/to/model.onnx")?;
// Print model information
println!("Model: {}", model.name());
println!("Inputs: {:?}", model.input_names());
println!("Outputs: {:?}", model.output_names());
// Prepare inputs
let mut inputs = HashMap::new();
inputs.insert("input", Tensor::zeros(&[1, 3, 224, 224]));
// Run inference
let outputs = model.run(&inputs)?;
// Process outputs
for (name, tensor) in outputs {
println!("Output '{}': shape {:?}", name, tensor.shape());
}
Ok(())
}
The runtime includes benchmarking capabilities:
# Run benchmarks
cargo bench
# Generate HTML reports
cargo bench -- --output-format html
Example benchmark results:
RunNX includes comprehensive formal verification capabilities to ensure mathematical correctness:
The runtime includes formal specifications for all tensor operations using Why3:
(** Addition operation specification *)
function add_spec (a b: tensor) : tensor
requires { valid_tensor a /\ valid_tensor b }
requires { a.shape = b.shape }
ensures { valid_tensor result }
ensures { result.shape = a.shape }
ensures { forall i. 0 <= i < length result.data ->
result.data[i] = a.data[i] + b.data[i] }
Automatic verification of mathematical properties:
use runnx::formal::contracts::{AdditionContracts, ActivationContracts, YoloOperatorContracts};
// Test addition commutativity: a + b = b + a
let result1 = tensor_a.add_with_contracts(&tensor_b)?;
let result2 = tensor_b.add_with_contracts(&tensor_a)?;
assert_eq!(result1.data(), result2.data());
// Test ReLU idempotency: ReLU(ReLU(x)) = ReLU(x)
let relu_once = tensor.relu_with_contracts()?;
let relu_twice = relu_once.relu_with_contracts()?;
assert_eq!(relu_once.data(), relu_twice.data());
// Test Softmax probability distribution: sum = 1.0
let softmax_result = tensor.softmax_with_contracts()?;
let sum: f32 = softmax_result.data().iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
Dynamic checking of invariants during execution:
use runnx::formal::runtime_verification::InvariantMonitor;
let monitor = InvariantMonitor::new();
let result = tensor.add(&other)?;
// Verify numerical stability and bounds
assert!(monitor.verify_operation(&[&tensor, &other], &[&result]));
The formal verification system proves:
# Install Why3 (optional, for complete formal proofs)
make -C formal install-why3
# Run all verification (tests + proofs)
make -C formal all
# Run only property-based tests (no Why3 required)
cargo test formal --lib
# Run verification example
cargo run --example formal_verification
# Generate verification report
make -C formal report
RunNX includes a Justfile with convenient shortcuts for common development tasks:
# Install just command runner (one time setup)
cargo install just
# Show all available commands
just --list
# Quick development cycle
just dev # Format, lint, and test
just test # Run all tests
just build # Build the project
just examples # Run all examples
# Code quality
just format # Format code
just lint # Run clippy
just quality # Run quality check script
# Documentation
just docs-open # Build and open docs
# Benchmarks
just bench # Run benchmarks
# Formal verification
just formal-test # Test formal verification setup
# CI simulation
just ci # Simulate CI checks locally
Alternatively, if you don't have just
installed, use the included shell script:
# Show all available commands
./dev.sh help
# Quick development cycle
./dev.sh dev # Format, lint, and test
./dev.sh test # Run all tests
./dev.sh examples # Run all examples
# Run all tests
cargo test
# or with just
just test
# Run tests with logging
RUST_LOG=debug cargo test
# Run specific test
cargo test test_tensor_operations
# Build and open documentation
cargo doc --open
# or with just
just docs-open
# Build with private items
cargo doc --document-private-items
We welcome contributions! Please follow our development quality standards:
./scripts/quality-check.sh
RunNX uses automated quality assurance tools to maintain code quality:
rustfmt
clippy
(warnings treated as errors)For detailed information, see Development QA Guidelines.
To run quality checks manually:
# Run all quality checks with auto-fixes
./scripts/quality-check.sh
# Or run individual checks
cargo fmt # Format code
cargo clippy # Run linting
cargo test # Run all tests
This project is licensed under
Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
ndarray
library