Skip to content
Snippets Groups Projects
Commit c30ca760 authored by Maxence Naud's avatar Maxence Naud
Browse files

Merge branch 'dev' into 'main'

version 0.1.1

See merge request !14
parents f96cc7ee a999c222
No related branches found
No related tags found
1 merge request!14version 0.1.1
......@@ -7,7 +7,7 @@ from typing import List, Union
from jinja2 import Environment, FileSystemLoader
import aidge_core
from aidge_core.export.code_generation import *
from aidge_core.export_utils.code_generation import *
from aidge_export_cpp.utils import (ROOT, OPERATORS_REGISTRY, supported_operators)
from aidge_export_cpp.utils.converter import aidge_datatype2ctype, numpy_dtype2ctype
import aidge_export_cpp.operators
......@@ -18,7 +18,7 @@ from aidge_export_cpp.memory import *
def generate_input_file(export_folder:str,
array_name:str,
array: np.ndarray):
# If directory doesn't exist, create it
if not os.path.exists(export_folder):
os.makedirs(export_folder)
......@@ -50,7 +50,7 @@ def export(export_folder_name, graphview, scheduler):
for node in list_forward_nodes:
if node.type() in supported_operators():
op = OPERATORS_REGISTRY[node.type()](node)
# For configuration files
list_configs = op.export(dnn_folder, list_configs)
......@@ -77,7 +77,7 @@ def export(export_folder_name, graphview, scheduler):
list_inputs_name = []
for node in graphview.get_nodes():
if node.type() == "Producer":
if not node.get_operator().get_attr("Constant"):
if not node.get_operator().attr.constant:
export_type = aidge_datatype2ctype(node.get_operator().get_output(0).dtype())
list_inputs_name.append((export_type, node.name()))
......@@ -110,4 +110,4 @@ def export(export_folder_name, graphview, scheduler):
# Copy all static files in the export
shutil.copy(str(ROOT / "static" / "main.cpp"), str(export_folder))
shutil.copy(str(ROOT / "static" / "Makefile"), str(export_folder))
shutil.copytree(str(ROOT / "static" / "include"), str(dnn_folder / "include"), dirs_exist_ok=True)
\ No newline at end of file
shutil.copytree(str(ROOT / "static" / "include"), str(dnn_folder / "include"), dirs_exist_ok=True)
......@@ -5,7 +5,7 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader
from aidge_core import ExportNode
from aidge_core.export.code_generation import *
from aidge_core.export_utils.code_generation import *
from aidge_export_cpp.utils import ROOT, operator_register
from aidge_export_cpp.utils.converter import numpy_dtype2ctype
from aidge_export_cpp.utils.generation import *
......@@ -71,7 +71,7 @@ class ProducerCPP(ExportNode):
def __init__(self, node):
super().__init__(node)
self.constant = self.operator.get_attr("Constant")
self.constant = self.operator.attr.constant
self.values = np.array(self.operator.get_output(0))
if len(self.values.shape) == 4:
......@@ -130,7 +130,7 @@ class ReLUCPP(ExportNode):
list_actions.append(generate_str(
str(ROOT / "templates" / "kernel_forward" / "activation_forward.jinja"),
name=self.name,
input_name=self.inputs[0].name(),
input_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input",
output_name=self.name
))
return list_actions
......@@ -141,16 +141,16 @@ class ConvCPP(ExportNode):
def __init__(self, node):
super().__init__(node)
self.kernel = node.get_operator().get_attr("KernelDims")
self.stride = node.get_operator().get_attr("StrideDims")
self.dilation = node.get_operator().get_attr("DilationDims")
self.kernel = node.get_operator().attr.kernel_dims
self.stride = node.get_operator().attr.stride_dims
self.dilation = node.get_operator().attr.dilation_dims
# No padding with Conv
# Use PaddedConv to add padding attribute
self.padding = [0, 0]
self.nb_channels = node.get_operator().get_attr("InChannels")
self.nb_outputs = node.get_operator().get_attr("OutChannels")
self.nb_channels = node.get_operator().in_channels()
self.nb_outputs = node.get_operator().out_channels()
if len(self.inputs_dims[0]) == 4:
# if dims == [batch, nb_channels, height, width]
......@@ -196,7 +196,7 @@ class ConvCPP(ExportNode):
list_actions.append(generate_str(
str(ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja"),
name=self.name,
input_name=self.inputs[0].name(),
input_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input",
output_name=self.name,
weights_name=self.inputs[1].name(),
biases_name=self.inputs[2].name()
......@@ -211,11 +211,11 @@ class PaddedConvCPP(ConvCPP):
for n in self.operator.get_micro_graph().get_nodes():
if n.type() == "Pad":
self.padding = n.get_operator().get_attr("BeginEndBorders")
self.padding = n.get_operator().attr.begin_end_borders
if n.type() == "Conv":
self.kernel = n.get_operator().get_attr("KernelDims")
self.stride = n.get_operator().get_attr("StrideDims")
self.dilation = n.get_operator().get_attr("DilationDims")
self.kernel = n.get_operator().attr.kernel_dims
self.stride = n.get_operator().attr.stride_dims
self.dilation = n.get_operator().attr.dilation_dims
if len(self.inputs_dims[0]) == 4:
# if dims == [batch, nb_channels, height, width]
......@@ -253,11 +253,11 @@ class AddCPP(ExportNode):
def forward(self, list_actions:list):
list_actions.append(set_up_output(self.name, "float"))
list_actions.append(generate_action(
list_actions.append(generate_str(
str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja"),
name=self.name,
inputs1_name=self.parents[0].name(),
inputs2_name=self.parents[1].name(),
inputs1_name=self.parents[0].name() if self.parents[0] else self.name + "_input1",
inputs2_name=self.parents[1].name() if self.parents[1] else self.name + "_input2",
output_name=self.name
))
return list_actions
......@@ -286,11 +286,11 @@ class SubCPP(ExportNode):
def forward(self, list_actions:list):
list_actions.append(set_up_output(self.name, "float"))
list_actions.append(generate_action(
list_actions.append(generate_str(
str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja"),
name=self.name,
inputs1_name=self.inputs[0].name(),
inputs2_name=self.inputs[1].name(),
inputs1_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input1",
inputs2_name=self.inputs[1].name() if self.inputs[1] else self.name + "_input2",
output_name=self.name
))
return list_actions
......@@ -300,8 +300,8 @@ class MaxPoolCPP(ExportNode):
def __init__(self, node):
super().__init__(node)
self.kernel = node.get_operator().get_attr("KernelDims")
self.stride = node.get_operator().get_attr("StrideDims")
self.kernel = node.get_operator().attr.kernel_dims
self.stride = node.get_operator().attr.stride_dims
# No padding with MaxPooling
# Use PaddedMaxPooling to add padding attribute
......@@ -347,7 +347,7 @@ class MaxPoolCPP(ExportNode):
list_actions.append(generate_str(
str(ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja"),
name=self.name,
input_name=self.inputs[0].name(),
input_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input",
output_name=self.name
))
return list_actions
......@@ -400,11 +400,10 @@ class FcCPP(ExportNode):
def forward(self, list_actions:list):
if not self.is_last:
list_actions.append(set_up_output(self.name, "float"))
list_actions.append(generate_str(
str(ROOT / "templates" / "kernel_forward" / "fullyconnected_forward.jinja"),
name=self.name,
inputs_name=self.inputs[0].name(),
inputs_name= self.inputs[0].name() if (self.inputs[0] is not None) else self.name + '_input',
weights_name=self.inputs[1].name(),
biases_name=self.inputs[2].name(),
outputs_name=self.name
......
......@@ -17,18 +17,18 @@ def numpy_dtype2ctype(dtype):
# Add more dtype mappings as needed
else:
raise ValueError(f"Unsupported {dtype} dtype")
def aidge_datatype2ctype(datatype):
if datatype == aidge_core.DataType.Int8:
if datatype == aidge_core.dtype.int8:
return "int8_t"
elif datatype == aidge_core.DataType.Int32:
elif datatype == aidge_core.dtype.int32:
return "int32_t"
elif datatype == aidge_core.DataType.Int64:
elif datatype == aidge_core.dtype.int64:
return "int64_t"
elif datatype == aidge_core.DataType.Float32:
elif datatype == aidge_core.dtype.float32:
return "float"
elif datatype == aidge_core.DataType.Float64:
elif datatype == aidge_core.dtype.float64:
return "double"
# Add more dtype mappings as needed
else:
......
%% Cell type:markdown id: tags:
# Add a custom operator in the CPP export
%% Cell type:markdown id: tags:
The main objective of this tutorial is to demonstrate the toolchain to **detect unsupported operators** and **add them** in an export module. <br>
For this tutorial, we use the CPP export module ``aidge_export_cpp`` to demonstrate the toolchain.
%% Cell type:markdown id: tags:
## Import Aidge
%% Cell type:code id: tags:
``` python
import aidge_core
import aidge_backend_cpu
import aidge_onnx
import numpy as np
import os
import requests
```
%% Cell type:markdown id: tags:
## Load ONNX model
%% Cell type:code id: tags:
``` python
# Download onnx file if it has not been done before
if not os.path.isfile("./lenet_mnist.onnx"):
response = requests.get("https://huggingface.co/vtemplier/LeNet_MNIST/resolve/main/lenet_mnist.onnx?download=true")
if response.status_code == 200:
with open("lenet_mnist.onnx", 'wb') as f:
f.write(response.content)
print("ONNX model downloaded successfully.")
else:
print("Failed to download ONNX model. Status code:", response.status_code)
```
%% Cell type:code id: tags:
``` python
model = aidge_onnx.load_onnx("lenet_mnist.onnx")
```
%% Cell type:code id: tags:
``` python
# Remove Flatten node, useless in the CPP export
aidge_core.remove_flatten(model)
# Freeze the model by setting constant to parameters producers
for node in model.get_nodes():
if node.type() == "Producer":
node.get_operator().set_attr("Constant", True)
# Create Producer Node for the Graph
input_node = aidge_core.Producer([1, 1, 28, 28], "input")
input_node.add_child(model)
model.add(input_node)
# Configuration for the model + forward dimensions
model.compile("cpu", aidge_core.DataType.Float32)
model.compile("cpu", aidge_core.dtype.float32)
```
%% Cell type:code id: tags:
``` python
# Generate scheduling of the model
scheduler = aidge_core.SequentialScheduler(model)
scheduler.generate_scheduling()
```
%% Cell type:markdown id: tags:
## Replace ReLU operators by Swish operators
Let's say you want to replace ReLU with another activation like Switch.
%% Cell type:code id: tags:
``` python
switch_id = 0 # ID for naming newly created Swich Operators
for node in scheduler.get_static_scheduling():
if node.type() == "ReLU":
print(f"{node.name()} will be replaced")
# Swich is not implemented by default in Aidge
# It is inserted in the current model as a GenericOperator that we will custom
node_swish = aidge_core.GenericOperator("Swish", nb_data=1, nb_param=0, nb_out=1, name=f"swish_{switch_id}")
node_swish.get_operator().add_attr("betas", [1.0]*node.get_operator().get_input(0).dims()[1])
aidge_core.GraphView.replace(set([node]), set([node_swish]))
switch_id+=1
```
%% Cell type:code id: tags:
``` python
import base64
from IPython.display import Image, display
import matplotlib.pyplot as plt
# function to vizualize .mmd files
def visualize_mmd(path_to_mmd):
with open(path_to_mmd, "r") as file_mmd:
graph_mmd = file_mmd.read()
graphbytes = graph_mmd.encode("utf-8")
base64_bytes = base64.b64encode(graphbytes)
base64_string = base64_bytes.decode("utf-8")
display(Image(url=f"https://mermaid.ink/img/{base64_string}"))
model.save("myModel")
visualize_mmd("myModel.mmd")
```
%% Cell type:markdown id: tags:
## Schedule the graph
%% Cell type:markdown id: tags:
Add an implementation for Swish. <br>
The implementation is required to perform a sequential scheduling.
%% Cell type:code id: tags:
``` python
class SwishImpl(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
node.get_operator().set_impl(SwishImpl(node.get_operator())) # Setting implementation
```
%% Cell type:code id: tags:
``` python
scheduler = aidge_core.SequentialScheduler(model)
model.forward_dims()
scheduler.generate_scheduling()
```
%% Cell type:markdown id: tags:
## Add Swish to the CPP export support
%% Cell type:code id: tags:
``` python
import aidge_export_cpp as cpp
cpp.supported_operators()
```
%% Cell type:code id: tags:
``` python
from aidge_export_cpp.operators import *
# To complete
@operator_register("Swish")
class SwishCPP(ExportNode):
def __init__(self, node):
super().__init__(node)
self.betas = self.operator.get_attr("betas")
self.betas: float = self.operator.get_attr("betas")
def export(self, export_folder:str, list_configs:list):
def export(self, export_folder: str, list_configs: list[str]) -> list[str]:
copyfile("for_export/swish_kernel.hpp",
f"{export_folder}/include/kernels/")
list_configs.append(f"layers/{self.name}.h")
generate_file(
f"{export_folder}/layers/{self.name}.h",
"for_export/swish_config.jinja",
name=self.name,
output_dims=self.outputs_dims[0]
)
return list_configs
def forward(self, list_actions:list):
def forward(self, list_actions:list[str]) -> list[str]:
if not self.is_last:
list_actions.append(set_up_output(self.name, "float"))
list_actions.append(generate_str(
"for_export/swish_forward.jinja",
name=self.name,
input_name=self.input.name(),
output_name=self.name
))
return list_actions
```
%% Cell type:code id: tags:
``` python
print(cpp.supported_operators())
```
%% Cell type:code id: tags:
``` python
cpp.export("myexport", model, scheduler)
```
%% Cell type:code id: tags:
``` python
!tree myexport
```
%% Cell type:code id: tags:
``` python
digit = np.load("digit.npy")
cpp.generate_input_file("inputs", digit.reshape(-1), "myexport/inputs.h")
cpp.generate_input_file(array_name="inputs", array=digit.reshape(-1), folder_path="myexport")
```
%% Cell type:code id: tags:
``` python
!cd myexport && make
```
%% Cell type:code id: tags:
``` python
!./myexport/bin/run_export
```
......
%% Cell type:markdown id: tags:
# Export a MNIST model to a CPP standalone project
%% Cell type:code id: tags:
``` python
%pip install requests numpy ipywidgets ipycanvas
```
%% Cell type:markdown id: tags:
## Download the model
%% Cell type:code id: tags:
``` python
import os
import requests
```
%% Cell type:code id: tags:
``` python
# Download onnx file if it has not been done before
if not os.path.isfile("./lenet_mnist.onnx"):
response = requests.get("https://huggingface.co/vtemplier/LeNet_MNIST/resolve/main/lenet_mnist.onnx?download=true")
if response.status_code == 200:
with open("lenet_mnist.onnx", 'wb') as f:
f.write(response.content)
print("ONNX model downloaded successfully.")
else:
print("Failed to download ONNX model. Status code:", response.status_code)
```
%% Cell type:markdown id: tags:
## Load the model in Aidge and manipulate it
%% Cell type:code id: tags:
``` python
import aidge_core
import aidge_backend_cpu
import aidge_onnx
import aidge_export_cpp
```
%% Cell type:code id: tags:
``` python
model = aidge_onnx.load_onnx("lenet_mnist.onnx")
```
%% Cell type:code id: tags:
``` python
# Remove Flatten node, useless in the CPP export
aidge_core.remove_flatten(model)
# Freeze the model by setting constant to parameters producers
for node in model.get_nodes():
if node.type() == "Producer":
node.get_operator().set_attr("Constant", True)
# Create Producer Node for the Graph
input_node = aidge_core.Producer([1, 1, 28, 28], "input")
input_node.add_child(model)
model.add(input_node)
# Configuration for the model + forward dimensions
model.compile("cpu", aidge_core.DataType.Float32)
model.compile("cpu", aidge_core.dtype.float32)
```
%% Cell type:code id: tags:
``` python
# Generate scheduling of the model
scheduler = aidge_core.SequentialScheduler(model)
scheduler.generate_scheduling()
```
%% Cell type:code id: tags:
``` python
model.save("test")
```
%% Cell type:markdown id: tags:
## Export the model
%% Cell type:code id: tags:
``` python
aidge_export_cpp.export("lenet_export_fp32", model, scheduler)
```
%% Cell type:markdown id: tags:
### Draw your own number
%% Cell type:code id: tags:
``` python
from ipywidgets import HBox, VBox, Button, Layout
from ipycanvas import RoughCanvas, hold_canvas
img_name = "my_number.png"
canvas = RoughCanvas(width=28, height=28, sync_image_data=True)
button_gen = Button(description="Generate PNG")
button_clear = Button(description="Clear")
drawing = False
position = None
shape = []
def on_erase_button_clicked(b):
canvas.clear()
def on_generate_button_clicked(b):
try:
canvas.to_file(img_name)
print(f"Image generated to {img_name} !")
except:
print("Draw a number before generating the image.")
button_clear.on_click(on_erase_button_clicked)
button_gen.on_click(on_generate_button_clicked)
def on_mouse_down(x, y):
global drawing
global position
global shape
drawing = True
position = (x, y)
shape = [position]
def on_mouse_move(x, y):
global drawing
global position
global shape
if not drawing:
return
with hold_canvas():
canvas.stroke_line(position[0], position[1], x, y)
position = (x, y)
shape.append(position)
def on_mouse_up(x, y):
global drawing
global position
global shape
drawing = False
with hold_canvas():
canvas.stroke_line(position[0], position[1], x, y)
shape = []
canvas.on_mouse_down(on_mouse_down)
canvas.on_mouse_move(on_mouse_move)
canvas.on_mouse_up(on_mouse_up)
canvas.stroke_style = "#000000"
VBox((canvas, HBox((button_gen, button_clear))),
layout=Layout(height='auto', width="300px"))
```
%% Cell type:markdown id: tags:
### Generate inputs for testing the model from your drawing
%% Cell type:code id: tags:
``` python
try:
number_np = canvas.get_image_data()
# We got a numpy array with the shape of (28,28,4)
# Transform it to (28,28)
x = number_np[:, :, 3].astype("float32")
# Convert from [0, 255] to [0, 1] and export it
aidge_export_cpp.generate_input_file(export_folder="lenet_export_fp32",
array_name="inputs",
array=x / 255)
except:
print("Please draw a number in the previous cell before running this one.")
```
%% Cell type:markdown id: tags:
### Compile the export and test it
%% Cell type:code id: tags:
``` python
!cd lenet_export_fp32 && make
```
%% Cell type:code id: tags:
``` python
!./lenet_export_fp32/bin/run_export
```
......
0.1.0
0.1.1
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment