Conv2D layer gives incorrect result on tiny example
Note this is related to issue aidge_backend_cpu#54 (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
. The exporter returns1
which should is a not a valid weighted sum for those values. -
output[1] = 1*3 + 2*4 = 11
. The exporter returns3
.
Using the entry point generator for comparing backends, the CPP exporter should provide the Predicted
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,
)