Add a custom operator to the C++ export#

The main objective of this tutorial is to demonstrate the toolchain to detect unsupported operators and add them in an export module. For this tutorial, we use the CPP export module aidge_export_cpp to demonstrate the toolchain.

[1]:
# First import some utility methods used in the tutorial:
import sys, os
sys.path.append(os.path.abspath(os.path.join('../../..')))
import tuto_utils
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 4
      2 import sys, os
      3 sys.path.append(os.path.abspath(os.path.join('../../..')))
----> 4 import tuto_utils

ModuleNotFoundError: No module named 'tuto_utils'

Import Aidge#

[2]:
import aidge_core
import aidge_backend_cpu
import aidge_export_cpp
import aidge_onnx
import numpy as np
import os
import requests

Load ONNX model#

[3]:
file_url = "https://huggingface.co/EclipseAidge/LeNet/resolve/main/lenet_mnist.onnx?download=true"
file_path = "lenet_mnist.onnx"
aidge_core.utils.download_file(file_path, file_url)
[4]:
model = aidge_onnx.load_onnx("lenet_mnist.onnx")
[5]:
# Remove Flatten node, useless in the CPP export
aidge_core.remove_flatten(model)

digit = np.load("digit.npy")

# Create Producer Node for the Graph
# Note: This means the graph will have no input!
input_node = aidge_core.Producer(aidge_core.Tensor(digit), "input")
input_node.add_child(model)
model.add(input_node)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[5], line 4
      1 # Remove Flatten node, useless in the CPP export
      2 aidge_core.remove_flatten(model)
----> 4 digit = np.load("digit.npy")
      6 # Create Producer Node for the Graph
      7 # Note: This means the graph will have no input!
      8 input_node = aidge_core.Producer(aidge_core.Tensor(digit), "input")

File /builds/eclipse/aidge/aidge/venv/lib/python3.10/site-packages/numpy/lib/_npyio_impl.py:451, in load(file, mmap_mode, allow_pickle, fix_imports, encoding, max_header_size)
    449     own_fid = False
    450 else:
--> 451     fid = stack.enter_context(open(os.fspath(file), "rb"))
    452     own_fid = True
    454 # Code to distinguish from NumPy binary files and pickles.

FileNotFoundError: [Errno 2] No such file or directory: 'digit.npy'

Replace ReLU operators by Swish operators#

Let’s say you want to replace ReLU with another activation like Switch.

[6]:
# Forward the dimensions in the graph in order to get the size for the beta vector of the Swish
model.forward_dims()

# Use GraphMatching to replace ReLU with Swish
matches = aidge_core.SinglePassGraphMatching(model).match("ReLU")
print('Number of match : ', len(matches))

switch_id = 0
for i, match in enumerate(matches):
    print('Match ', i)
    node_ReLU = match.graph.root_node()

    print('Replacing ', node_ReLU.type(), ' : ', node_ReLU.name())

    # We instanciate Swish as a generic operator
    node_swish = aidge_core.GenericOperator("Swish", nb_data=1, nb_param=0, nb_out=1, name=f"swish_{switch_id}")
    node_swish.get_operator().attr.betas = [1.0]*node_ReLU.get_operator().get_input(0).dims()[1]

    print('Replacing ', node_ReLU.type(), ' : ', node_ReLU.name(), ' with ' , node_swish.name())
    aidge_core.GraphView.replace(set([node_ReLU]), set([node_swish]))

    switch_id+=1
Number of match :  4
Match  0
Replacing  ReLU  :  data_1_Relu
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[6], line 17
     15 # We instanciate Swish as a generic operator
     16 node_swish = aidge_core.GenericOperator("Swish", nb_data=1, nb_param=0, nb_out=1, name=f"swish_{switch_id}")
---> 17 node_swish.get_operator().attr.betas = [1.0]*node_ReLU.get_operator().get_input(0).dims()[1]
     19 print('Replacing ', node_ReLU.type(), ' : ', node_ReLU.name(), ' with ' , node_swish.name())
     20 aidge_core.GraphView.replace(set([node_ReLU]), set([node_swish]))

IndexError: list index out of range
[7]:
model.save("myModel")
tuto_utils.visualize_mmd("myModel.mmd")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 model.save("myModel")
----> 2 tuto_utils.visualize_mmd("myModel.mmd")

NameError: name 'tuto_utils' is not defined

Schedule the graph#

Add the function to specify how Swish activation transforms the dimensions. This forward_dims function is required to perform a sequential scheduling.

[8]:
class GenericImpl(aidge_core.OperatorImpl): # Inherit OperatorImpl to interface with Aidge !
    def __init__(self, op: aidge_core.Operator):
        aidge_core.OperatorImpl.__init__(self, op, 'cpu')
    # no need to define forward() function in python as we do not intend to run a scheduler on the model

for node in model.get_nodes():
    if node.type() == "Swish":
        node.get_operator().set_forward_dims(lambda x: x) # to propagate dimensions in the model

for node in model.get_nodes():
    node.get_operator().set_impl(GenericImpl(node.get_operator())) # Setting implementation
[9]:
scheduler = aidge_core.SequentialScheduler(model)
model.compile("cpu", aidge_core.dtype.float32, dims=[[1, 1, 28, 28]])
scheduler.generate_scheduling()
s = scheduler.get_sequential_static_scheduling()

Add Swish to the CPP export support#

[10]:
# Note: we register a GenericOperator so we need to use ``register_generic``.
# For registering an existing operator use ``register``
# For registering a MetaOperator use ``register_metaop``
@aidge_export_cpp.ExportLibCpp.register_generic("Swish", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
class SwishCPP(aidge_core.export_utils.ExportNodeCpp):
    def __init__(self, node, mem_info):
        super().__init__(node, mem_info)
        self.config_template = "swish_export_files/swish_config.jinja"

        self.forward_template = "swish_export_files/swish_forward.jinja"
        self.include_list = []
        self.kernels_to_copy = [
            "swish_export_files/swish_kernel.hpp",
        ]

[11]:
aidge_core.export_utils.scheduler_export(
        scheduler,
        "myexport",
        aidge_export_cpp.ExportLibCpp,
        memory_manager=aidge_core.mem_info.generate_optimized_memory_info,
        memory_manager_args={"stats_folder": "myexport/stats", "wrapping": False }
)

aidge_core.export_utils.generate_main_cpp("myexport", model)
gen : myexport/data/data_0_Conv_input_0.h
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[11], line 9
      1 aidge_core.export_utils.scheduler_export(
      2         scheduler,
      3         "myexport",
   (...)
      6         memory_manager_args={"stats_folder": "myexport/stats", "wrapping": False }
      7 )
----> 9 aidge_core.export_utils.generate_main_cpp("myexport", model)

File /builds/eclipse/aidge/aidge/venv/lib/python3.10/site-packages/aidge_core/export_utils/generate_main.py:54, in generate_main_cpp(export_folder, graph_view, inputs_tensor, labels)
     52     else:
     53         inputs_name.append(in_name)
---> 54         generate_input_file(
     55             export_folder=str(Path(export_folder) / "data"),
     56             array_name=in_name,
     57             tensor=inputs_tensor)
     59 # Generate labels file
     60 if labels is not None:

File /builds/eclipse/aidge/aidge/venv/lib/python3.10/site-packages/aidge_core/export_utils/tensor_export.py:38, in generate_input_file(export_folder, array_name, tensor)
     30 print(f"gen : {export_folder}/{array_name}.h")
     31 ROOT = Path(__file__).resolve().parents[0]
     32 generate_file(
     33     file_path=f"{export_folder}/{array_name}.h",
     34     template_path=str(ROOT / "templates" / "c_data.jinja"),
     35     dims = tensor.dims(),
     36     data_t = aidge2c(tensor.dtype()),
     37     name = array_name,
---> 38     values = list(tensor)
     39 )

RuntimeError: Tensor::get<>(0): can only be used for backends providing a valid host pointer.
[12]:
!tree myexport
/usr/bin/sh: 1: tree: not found
[13]:
!cd myexport && make
make[1]: Entering directory '/builds/eclipse/aidge/aidge/docs/source/Tutorial/myexport'
g++  -O2 -Wall -Wextra -MMD -fopenmp -I. -I./dnn -I./dnn/include -I./dnn/layers -I./dnn/parameters -c dnn/src/forward.cpp -o build/./dnn/src/forward.o
In file included from ./dnn/include/layers/data_4_Relu.h:3,
                 from dnn/src/forward.cpp:19:
./dnn/include/network/rescaling_utils.hpp:11:16: warning: int64_t smlal(int32_t, int32_t, uint32_t, uint32_t)’ defined but not used []8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wunused-function-Wunused-function]8;;]
   11 | static int64_t smlal(int32_t lhs, int32_t rhs,
      |                ^~~~~
g++  build/./dnn/src/forward.o  -fopenmp -o bin/run_export
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status
make[1]: *** [Makefile:19: build] Error 1
make[1]: Leaving directory '/builds/eclipse/aidge/aidge/docs/source/Tutorial/myexport'
[14]:
!./myexport/bin/run_export
/usr/bin/sh: 1: ./myexport/bin/run_export: not found