Conv2D layer gives incorrect result on tiny example
Related to aidge_export_cpp#28 (closed)
What commit version of aidge do you use
aidge_backend_cpu==0.5.0
aidge_core==0.5.1
aidge_export_cpp==0.2.1
aidge_onnx==0.4.1
Problem description
Defining an ad-hoc Conv2D
layer with small input and weight matrices results in improbable results using the CPU backend. The test matrices have a number of dimensions set to 1 (for testing purposes):
-
input: Tensor[1][1][2][1] = {{{{ 1.00000}, { 2.00000}}}}
is a matrix with 2 input row (single column, channel, and batch). -
weight: Tensor[2][1][2][1] = {{{{ 1.00000}, { 2.00000}}}, {{{ 3.00000}, { 4.00000}}}}
has 2 filters, matching the input size. - dilation, stride, and other convolution parameters should not matter has the weights match the input in size.
There are two expected outputs, one for each application of a filter to the input. Said application is a weighted sum of the input and corresponding filter. In other words:
-
output[0] = 1*1 + 2*2 = 3
. -
output[1] = 1*3 + 2*4 = 11
. - The backend returns
11
for both values, which should not be possible. Only one combination ofinput
andweight
can result in this value.
Using the entry point generator for comparing backends, the CPU backend should provide the Expected
values:
InputNode_output_0:
Expected 11.000000 <-> Predicted 1.000000
Expected 11.000000 <-> Predicted 3.000000
Number of equal outputs: 0 / 2
Reproducible example code
The code is a reduced version of the test used for the ACETONE C backend under development.
from functools import reduce
from operator import mul
import aidge_backend_cpu # noqa: F401
import aidge_core
import numpy as np
from aidge_core import Operator, Tensor
import aidge_export_cpp
def volume(shape: list[int]) -> int:
"""Count elements in shape."""
return reduce(mul, shape, 1)
def declare_conv_input(shape: list[int]) -> aidge_core.Tensor:
"""Declare tensor of specified shape, ignoring batch dimensions if null."""
n = np.arange(volume(shape), dtype=np.float32).reshape(shape)
n += 1
t = aidge_core.Tensor(n)
t.set_backend("cpu")
t.set_datatype(aidge_core.dtype.float32)
return t
if __name__ == "__main__":
# Convolution dimensions
# Input height/width
H, W = 17, 19
# Batch
B = 2
# Channels
C = 3
D = 1
# Kernel size
F, G = 5, 7
# Convolution dimensions
# Input height/width
H, W = 2, 1
# Batch
B = 1
# Channels
C = 1
D = 2
# Kernel size (H, W)
F, G = 2, 1
# FIXME There are many version of the convolution
# - Conv{1,2}D
# - Padded variants
# - Depthwise variants
conv_layer = aidge_core.Conv2D(
in_channels=C,
out_channels=D, # TODO Related to depthwise? Should be a divisor of C?
kernel_dims=[F, G],
stride_dims=[1, 1], # TODO Identify where those are used/available for template
dilation_dims=[
1,
1,
], # TODO Identify where those are used/available for template
name="InputNode",
no_bias=False,
)
model = aidge_core.sequential([conv_layer])
# Set backend and datatype
model.set_backend("cpu")
model.set_datatype(aidge_core.dtype.float32)
### GENERATING SCHEDULING
scheduler = aidge_core.SequentialScheduler(model)
### REFERENCE INFERENCE
data = declare_conv_input([B, C, H, W])
print(data, f"{B}, {C}, {H}, {W}")
# Initialise Conv2D data
for node in model.get_nodes():
if node.type() == "Producer":
assert node.get_nb_outputs() == 1
node_operator: Operator = node.get_operator()
assert node_operator.nb_outputs() == 1
node_target, node_target_id = node.output(0)[0]
if node_target.type() == "Conv2D":
if node_target_id == 1:
weights: Tensor = node_operator.get_output(0)
w = declare_conv_input(weights.dims())
print(w, weights.dims())
node_operator.set_output(0, w)
elif node_target_id == 2:
pass
node_value = node_operator.get_output(0)
scheduler.forward(
data=[
data,
]
)
### LOG OUTPUTS AND SCHEDULING
model.log_outputs("aidge_cpp_results")
print("Scheduling:")
print(scheduler.get_static_scheduling())
### GENERATE EXPORT
cpp_export = aidge_export_cpp.ExportLibCpp
for exporter, exporter_dir in [(cpp_export, "export_cpp")]:
aidge_core.export_utils.scheduler_export(
scheduler,
exporter_dir,
exporter,
memory_manager=aidge_core.mem_info.compute_default_mem_info,
)
aidge_core.export_utils.generate_input_file(
export_folder=exporter_dir,
array_name="InputNode_input_0",
tensor=data,
)
aidge_core.export_utils.generate_main_compare_cpp(
export_folder=exporter_dir,
graph_view=model,
)