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