Aidge tiling demonstration#
This tutorial aims at demonstrating how tiling can splite computation on several devices.
[1]:
import aidge_core
import aidge_backend_cpu
import aidge_onnx
import numpy as np
Define mermaid visualizer function#
Aidge save graph using the mermaid format, in order to visualize the graph live in the notebook, we will setup the following function:
[2]:
import base64
from IPython.display import Image, display
import matplotlib.pyplot as plt
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}"))
Let’s create a small neural network with four layers.
The GraphView generating function sequential
is used. You should at least name the layers of most interest to ease access to them if required.
[3]:
model = aidge_core.sequential([
aidge_core.LeakyReLU(1, name="leakyrelu0"),
aidge_core.Conv2D(3, 32, [3, 3], name="conv0"),
aidge_core.BatchNorm2D(32, name="bn0"),
aidge_core.ReLU(name="relu0")
])
model.save("initial_graph")
[4]:
visualize_mmd("initial_graph.mmd")
Let’s create an input to link to the model.
[5]:
# Create an input
input_tensor = aidge_core.Tensor(np.random.rand(4, 3, 66, 66).astype(np.float32))
Let’s generate random values for each parameter
[6]:
convW = aidge_core.Tensor(np.random.rand(32, 3, 3, 3).astype(np.float32))
convB = aidge_core.Tensor(np.random.rand(32).astype(np.float32))
BNscale = aidge_core.Tensor(np.random.rand(32).astype(np.float32))
BNshift = aidge_core.Tensor(np.random.rand(32).astype(np.float32))
BNmean = aidge_core.Tensor(np.random.rand(32).astype(np.float32))
BNvar = aidge_core.Tensor(np.random.rand(32).astype(np.float32))
[7]:
model.get_node("conv0").get_operator().set_input(1, convW)
model.get_node("conv0").get_operator().set_input(2, convB)
model.get_node("bn0").get_operator().set_input(1, BNscale)
model.get_node("bn0").get_operator().set_input(2, BNshift)
model.get_node("bn0").get_operator().set_input(3, BNmean)
model.get_node("bn0").get_operator().set_input(4, BNvar)
Select an implementation and compute input/output dimensions.
[8]:
model.compile("cpu", aidge_core.dtype.float32, dims=[[4,3,66,66]])
Run the model
[9]:
# Create SCHEDULER
scheduler = aidge_core.SequentialScheduler(model)
# Run inference !
scheduler.forward(data=[input_tensor])
# keep result in memory
res1 = np.array(model.get_node("relu0").get_operator().get_output(0))
Thanks to tiling, the convolution computation can be divided in the desired number of stripes.
Here, we choose 4 stripes on the second axis (the horizontl axis).
[10]:
tiled_conv = aidge_core.get_conv_horizontal_tiling(model.get_node("conv0"), 2, 4)
node_to_replace = {model.get_node("conv0"),
model.get_node("conv0").get_parent(1),
model.get_node("conv0").get_parent(2)}
aidge_core.GraphView.replace(node_to_replace, tiled_conv)
[10]:
True
The replace
function returned True
: the replacement was successful. We can visualize the new model.
The convolution has been divided in 4 smaller convolutions preceeded by a Slice operator to extract the right sub-tensor. All 4 results are concatenated back to a single Tensor that serves as an input for the following layer.
[11]:
model.save("model_after_tiling")
visualize_mmd("model_after_tiling.mmd")
Now we run the transformed model and compare it’s ouput value to the previous one.
[12]:
model.compile("cpu", aidge_core.dtype.float32)
scheduler.resetScheduling()
scheduler.forward(data=[input_tensor])
res2 = np.array(model.get_node("relu0").get_operator().get_output(0))
[13]:
(res1 == res2).all()
[13]:
True
Both outputs are the same !