LogoLogo
Orion
Orion
  • 👋Welcome
    • Orion
    • Why Validity ML?
  • 🧱Framework
    • Get Started
    • Contribute
    • Compatibility
    • Numbers
      • Fixed Point
        • fp.new
        • fp.new_unscaled
        • fp.from_felt
        • fp.abs
        • fp.ceil
        • fp.floor
        • fp.exp
        • fp.exp2
        • fp.log
        • fp.log2
        • fp.log10
        • fp.pow
        • fp.round
        • fp.sqrt
        • fp.sin
        • fp.atan
        • fp.sign
      • Complex Number
        • complex.acos
        • complex.acosh
        • complex.arg
        • complex.asin
        • complex.asinh
        • complex.atan
        • complex.atanh
        • complex.conjugate
        • complex.cos
        • complex.cosh
        • complex.exp
        • complex.exp2
        • complex.from_polar
        • complex.img
        • complex.ln
        • complex.log2
        • complex.log10
        • complex.mag
        • complex.new
        • complex.one
        • complex.pow
        • complex.real
        • complex.reciprocal
        • complex.sin
        • complex.sinh
        • complex.sqrt
        • complex.tan
        • complex.tanh
        • complex.to_polar
        • complex.zero
    • Operators
      • Tensor
        • tensor.new
        • tensor.at
        • tensor.min_in_tensor
        • tensor.min
        • tensor.max_in_tensor
        • tensor.max
        • tensor.stride
        • tensor.ravel_index
        • tensor.unravel_index
        • tensor.reshape
        • tensor.transpose
        • tensor.reduce_sum
        • tensor.argmax
        • tensor.argmin
        • tensor.matmul
        • tensor.exp
        • tensor.log
        • tensor.equal
        • tensor.greater
        • tensor.greater_equal
        • tensor.less
        • tensor.less_equal
        • tensor.abs
        • tensor.neg
        • tensor.ceil
        • tensor.cumsum
        • tensor.sin
        • tensor.cos
        • tensor.asin
        • tensor.flatten
        • tensor.sinh
        • tensor.asinh
        • tensor.cosh
        • tensor.acosh
        • tensor.tanh
        • tensor.atan
        • tensor.acos
        • tensor.sqrt
        • tensor.or
        • tensor.xor
        • tensor.onehot
        • tensor.slice
        • tensor.concat
        • tensor.gather
        • tensor.quantize_linear
        • tensor.dequantize_linear
        • tensor.qlinear_add
        • tensor.qlinear_mul
        • tensor.qlinear_matmul
        • tensor.qlinear_concat
        • tensor.qlinear_leakyrelu
        • tensor.qlinear_conv
        • tensor.nonzero
        • tensor.squeeze
        • tensor.unsqueeze
        • tensor.sign
        • tensor.clip
        • tensor.identity
        • tensor.and
        • tensor.where
        • tensor.bitwise_and
        • tensor.bitwise_xor
        • tensor.bitwise_or
        • tensor.resize
        • tensor.round
        • tensor.scatter
        • tensor.array_feature_extractor
        • tensor.binarizer
        • tensor.reduce_sum_square
        • tensor.reduce_l2
        • tensor.reduce_l1
        • tensor.reduce_prod
        • tensor.gather_elements
        • tensor.gather_nd
        • tensor.reduce_min
        • tensor.shrink
        • tensor.reduce_mean
        • tensor.pow
        • tensor.is_nan
        • tensor.is_inf
        • tensor.not
        • tensor.erf
        • tensor.reduce_log_sum
        • tensor.reduce_log_sum_exp
        • tensor.unique
        • tensor.compress
        • tensor.layer_normalization
        • tensor.scatter_nd
        • tensor.dynamic_quantize_linear
        • tensor.optional
        • tensor.reverse_sequence
        • tensor.split_to_sequence
        • tensor.range
        • tensor.hann_window
        • tensor.hamming_window
        • tensor.blackman_window
        • tensor.random_uniform_like
        • tensor.label_encoder
      • Neural Network
        • nn.relu
        • nn.leaky_relu
        • nn.sigmoid
        • nn.softmax
        • nn.softmax_zero
        • nn.logsoftmax
        • nn.softsign
        • nn.softplus
        • nn.linear
        • nn.hard_sigmoid
        • nn.thresholded_relu
        • nn.gemm
        • nn.grid_sample
        • nn.col2im
        • nn.conv_transpose
        • nn.conv
        • nn.conv_integer
        • nn.depth_to_space
        • nn.space_to_depth
        • nn.max_pool
        • nn.deform_conv
      • Machine Learning
        • Tree Ensemble Classifier
          • tree_ensemble_classifier.predict
        • Tree Ensemble Regressor
          • tree_ensemble_regressor.predict
        • Linear Classifier
          • linear_classifier.predict
        • Linear Regressor
          • linear_regressor.predict
        • SVM Regressor
          • svm_regressor.predict
        • SVM Classifier
          • svm_classifier.predict
        • Sequence
          • sequence.sequence_construct
          • sequence.sequence_empty
          • sequence.sequence_length
          • sequence.sequence_at
          • sequence.sequence_empty
          • sequence.sequence_erase
          • sequence.sequence_insert
          • sequence.concat_from_sequence
        • Normalizer
          • normalize.predict
  • 🏛️Hub
    • Models
    • Spaces
  • 🧑‍🎓Academy
    • Tutorials
      • MNIST Classification with Orion
      • Implement new operators in Orion
      • Verifiable Linear Regression Model
      • Verifiable Support Vector Machine
      • Verifiable Principal Components Analysis
      • Provable MLR: Forecasting AAVE's Lifetime Repayments
Powered by GitBook
On this page
  • Code Structure
  • What are Orion Operators?
  • How to contribute?
  • How to implement new Orion Operators?
  • What is Softmax?
  • Best practices before implementing an operator
  • Start coding!
  • How to test the Orion Operator?
Edit on GitHub
  1. Academy
  2. Tutorials

Implement new operators in Orion

PreviousMNIST Classification with OrionNextVerifiable Linear Regression Model

Last updated 1 year ago

The Orion Framework offers an open-source ONNX runtime implementation for Validity and ZK Machine Learning. Are you interested in contributing? We sincerely appreciate your interest. This is exactly how we'll build a robust and transparent AI ecosystem! In this tutorial, you'll learn how to contribute to the by implementing from scratch a new operator.

Throughout this tutorial, any concept that is directly explained in the official documentation will be met with a reference guiding you to the respective source. Feel free to dive in.

Code Structure

The repository is structured as follows:

.
├── LICENSE
├── README.md
├── Scarb.toml
├── book.json
├── cairo_project.toml
├── docgen
├── docs
├── nodegen
├── src
│   ├── lib.cairo
│   ├── numbers
│   ├── numbers.cairo
│   ├── operators
│   ├── operators.cairo
│   ├── tests
│   ├── tests.cairo
│   └── utils.cairo
└── target

In the src directory, you'll find four distinct folders:

  • tests: This is the location where we'll test our code.

In this tutorial we will focus on operators directory, as we will implement a new operator from scratch.

What are Orion Operators?

Orion operators serve as the foundational components of machine learning models compliant with ONNX ops. ONNX is an open format for representing machine learning models that allows interoperability between various deep learning frameworks. It enables models to be trained in one framework and deployed in another without the need for extensive model conversion.

Orion operators represent specific computations or operations performed by machine learning models. Each operator defines a specific functionality, such as convolution, pooling, activation functions, matrix operations, and more. These operators are defined using a set of attributes and inputs that describe their behaviour and dependencies.

Ensuring compatibility with ONNX operators facilitates integration into the ONNX ecosystem. This enables researchers and developers to pre-train models using their preferred framework, before executing verifiable inferences with Orion.

We implemented two different types of operators, each having their own trait:

Use Resources:

How to contribute?

How to implement new Orion Operators?

In this section, I will guide you through the process of adding new operators to the Orion repository. To illustrate this, we will build the Softmax operator from scratch.

What is Softmax?

It is a non-linear activation function that takes a vector of real numbers as input and transforms them into a probability distribution over multiple classes. It's defined as follows:

softmax(xi)=exi∑j=1nexj\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^n e^{x_j}}softmax(xi​)=∑j=1n​exj​exi​​

In other words, the softmax function exponentiates each element of the input vector and divides it by the sum of exponentiated values across all elements. This normalization ensures that the output values lie between 0 and 1, and their sum adds up to 1, resembling a probability distribution.

Best practices before implementing an operator

Before implementing an operator in Orion, I recommend that you:

  1. Consider whether the operator should be implemented in NNTrait or TensorTrait.

Start coding!

Step 1: Add softmax to NNTrait

Since Softmax is a neural network operator, it needs to be implemented in NNTrait. It accepts an input tensor of a generic type 'T' and an axis along which the softmax computation will occur. Given that the resulting values must range between 0 and 1, it should return a tensor of fixed-point numbers, retaining the same shape as the input tensor.

trait NNTrait<T> {
    //...
    fn softmax(tensor: @Tensor<T>, axis: usize) -> Tensor<FixedType>;
}

Step 2: Add the business logic

In the src/operators/nn/functional directory, create a new file named softmax.cairo.

The functional folder is where all the business logic resides. All functions should be implemented with generic type.

A softmax function can be implemented as follows:

Softmax(input, axis) = Exp(input) / ReduceSum(Exp(input), axis=axis)

Here's the implementation in softmax.cairo:

use orion::operators::tensor::core::{Tensor, TensorTrait};

/// Cf: NNTrait::softmax docstring
fn softmax<
    T,
    impl TTensor: TensorTrait<T>,
    impl TTensor: TensorTrait<T>,
    impl TTensorDiv: Div<Tensor<T>>,
    impl TCopy: Copy<T>,
    impl TDrop: Drop<T>,
>(
    z: @Tensor<T>, axis: usize
) -> Tensor<T> {
    let exp_tensor = z.exp();
    let sum = exp_tensor.reduce_sum(axis, true);
    let softmax = exp_tensor / sum;

    return softmax;
}

Step 3: Add softmax to the implementations

Now, we need to add the softmax function into the different representations. In nn/implementations/nn_fp8x23.cairo, import the business logic and add the softmax implementation.

// In nn_fp8x23.cairo

impl FP8x23NN of NNTrait<FP8x23> {
    // [...]
    fn softmax(tensor: @Tensor<FP8x23>, axis: usize) -> Tensor<FP8x23> {
        functional::softmax::softmax(tensor, axis)
    }
}

Do the same for all other fixed point implementations (FP16x16NN, FP32x32NN, FP64x64NN). As softmax only support fixed point tensors, it should panic for other implementations. Below an example with U32NN.

// In nn_u32.cairo

impl U32NN of NNTrait<u32> {
    // [...]
    fn softmax(tensor: @Tensor<u32>, axis: usize) -> Tensor<u32> {
        panic(array!['not supported!'])
    }
}

Step 4: Write the docstring

/// Trait
///
/// [...]
/// softmax - Computes softmax activations.
trait NNTrait<T> {
    /// [...]
    /// # NNTrait::softmax
    ///
    /// ```rust 
    ///    fn softmax(tensor: @Tensor<T>, axis: usize) -> Tensor<T>;
    /// ```
    ///
    /// Applies the Softmax function to an n-dimensional input Tensor rescaling them so that the elements of the n-dimensional output Tensor lie in the range \[0,1] and sum to 1.
    /// 
    /// $$
    /// \text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^n e^{x_j}}
    /// $$
    /// 
    /// ## Args
    ///
    /// * `tensor`(`@Tensor<T>`) - The input tensor.
    /// * `axis`(`usize`) - The axis along which to compute the softmax.
    ///
    /// ## Returns
    ///
    /// A Tensor of fixed point numbers with the same shape than the input Tensor.
    ///
    /// ## Type Constraints
    ///
    /// Constrain input and output types to fixed point tensors.
    ///
    /// ## Examples
    /// 
    /// ```rust
    /// use core::array::{ArrayTrait, SpanTrait};
    /// 
    /// use orion::operators::tensor::{TensorTrait, Tensor, FP8x23};
    /// use orion::operators::nn::{NNTrait, FP8x23NN};
    /// use orion::numbers::{FP8x23, FixedTrait};
    /// 
    /// fn softmax_example() -> Tensor<FP8x23> {
    ///     let tensor = TensorTrait::<FP8x23>::new(
    ///         shape: array![2, 2].span(),
    ///         data: array![
    ///             NNTrait::new(0, false),
    ///             NNTrait::new(1, false),
    ///             NNTrait::new(2, false),
    ///             NNTrait::new(3, false),
    ///         ]
    ///             .span(),
    ///     );
    /// 
    ///     return NNTrait::softmax(@tensor, 1);
    /// }
    /// >>> [[2255697,6132911],[2255697,6132911]]
    ///     // The fixed point representation of
    ///     // [[0.2689, 0.7311],[0.2689, 0.7311]]
    /// ```
    ///
    fn softmax(tensor: @Tensor<T>, axis: usize) -> Tensor<T>;
}

Voilà! We have successfully implemented the softmax function in NNTrait!

How to test the Orion Operator?

Now, let's proceed to testing the softmax operator we've just implemented. When testing an operator in Orion, you should ensure to test across all types of implementation.

To simplify the task of writing tests, and get closer to ONNX tests, we've designed Nodegen! It lets you write your test in Python/Numpy, then generate the following Cairo code:

  • Input data

  • Expected output data

  • Your tests

import numpy as np

def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray:
    x_max = np.max(x, axis=axis, keepdims=True)
    tmp = np.exp(x - x_max)
    s = np.sum(tmp, axis=axis, keepdims=True)
    return tmp / s

Finally, we create a Softmax class, containing tests for each dtypes.

import numpy as np
from nodegen.node import RunAll
from ..helpers import make_test, to_fp, Tensor, Dtype, FixedImpl, Trait

def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray:
    x_max = np.max(x, axis=axis, keepdims=True)
    tmp = np.exp(x - x_max)
    s = np.sum(tmp, axis=axis, keepdims=True)
    return tmp / s

class Softmax(RunAll):
     
    @staticmethod
    # We test here with fp8x23 implementation.
    def fp8x23():
        # Create a random numpy array:
        x = np.random.randint(-3, 3, (2, 2)).astype(np.float64)
        # Ddefine the expected result:
        y = softmax(x, 0)
        
        # Convert the input and output to Tensor class, similar to Orion's Tensor struct:
        x = Tensor(Dtype.FP8x23, x.shape, to_fp(x.flatten(), FixedImpl.FP8x23))
        # Convert the floats values in `y` to fixed points with `to_fp` method:
        y = Tensor(Dtype.FP8x23, y.shape, to_fp(y.flatten(), FixedImpl.FP8x23))
        
        # Define the name of the generated folder. 
        name = "softmax_fp8x23"
        # Invoke `make_test` method to generate corresponding Cairo tests:
        make_test(
            [x], # List of input tensors.
            y, # The expected output result.
            "NNTrait::softmax(@input_0, 0)", # The code signature.
            name, # The name of the generated folder.
            Trait.NN # The trait, if the function is present in either the TensorTrait or NNTrait.
        )
        
    # We test here with fp16x16 implementation.    
    @staticmethod
    def fp16x16():
        x = np.random.uniform(-3, 3, (2, 2)).astype(np.float64)
        y = softmax(x, 1)

        x = Tensor(Dtype.FP16x16, x.shape, to_fp(
            x.flatten(), FixedImpl.FP16x16))
        y = Tensor(Dtype.FP16x16, y.shape, to_fp(
            y.flatten(), FixedImpl.FP16x16))

        name = "softmax_fp16x16"
        make_test([x], y, "NNTrait::softmax(@input_0, 1)",
                    name, Trait.NN)

Once set up, you can generate tests and data by executing scarb run nodegen softmax.

The above code will generate 6 test files located in tests/src/nodes. As an example, here's the content of the softmax_fp8x23.cairo generated file:

// softmax_fp8x23.cairo
mod input_0; 
mod output_0; 

use orion::operators::nn::NNTrait;
use orion::numbers::FixedTrait;
use orion::operators::nn::FP8x23NN;
use orion::operators::tensor::FP8x23TensorPartialEq;
use orion::utils::assert_eq;

#[test]
#[available_gas(2000000000)]
fn test_softmax_fp8x23() {
    let input_0 = input_0::input_0();
    let z = output_0::output_0();

    let y = NNTrait::softmax(@input_0, 0);

    assert_eq(y, z);
}

If you'd like to expand the tests with additional cases, feel free to edit the generated Cairo file.

Orion leverages Cairo to guarantee the reliability of inference, providing developers with a user-friendly framework to build complex and verifiable machine learning models. We invite the community to join us in shaping a future where trustworthy AI becomes a reliable resource for all.

Orion repo uses Scarb, a Cairo package manager. You can find all information about Scarb and Cairo installation .

: This folder contains a complete implementation of Signed Integer and Fixed Point.

: This directory includes a set of functions and operations used in calculating neural network models.

: This represents a full implementation of multi-dimensional arrays.

- These are operators designed for building neural networks.

.

This tutorial will focus specifically on implementing a new Operator in the Orion repository, and will not cover the entirety of the contribution guidelines. If you intend to contribute to Orion, we kindly ask that you read carefully the .

Read the corresponding ONNX operator .

Understand how the has implemented it. It's essential to maintain the operator's interface consistent with the one in ONNX.

In we add the softmax to NNTrait .

So we can leverage the and operators from TensorTrait to implement softmax.

Navigate back to operators/nn/core.cairo and prior to the declaration of the softmax function, write the docstring and list it preceding the trait as shown below. This step is useful for generating the documentation during the preparation of your Pull Request, which can be achieved with scarb run docgen command. We use a docstring style similar to, with a few variations.

Since softmax employs fixed points for intermediate calculations and returns a tensor of FixedType, it is essential to test it across all fixed point implementations. As of now, Orion supports two fixed point implementations: and .

First, we'll create a softmax.py file in the nodegen/node directory. Next, we'll define a softmax function in python. You can find the python function in

You're now ready to prepare your Pull Request. Please ensure you thoroughly read the before making your first PR. Your contribution is greatly appreciated, and we sincerely value your interest 🫶.

🧑‍🎓
numbers
operators
tensor (TensorTrait)
nn (NNTrait)
Full list of ONNX Operators
Current list of operators supported by Orion.
Contribution Guidelines
documentation
ONNX backend
src/operators/nn/core.cairo
exp
reduce_sum
Rust's docstring
ONNX implementation directory.
Contribution Guidelines
FP16x16
FP8x23
Orion repository
here