From f02e2c5ce5018df02c49dcfa69c1cb4ac1add0eb Mon Sep 17 00:00:00 2001 From: cmoineau <cyril.moineau@cea.fr> Date: Mon, 31 Mar 2025 13:35:13 +0000 Subject: [PATCH] Add a first instance of unit test for Cpp export. --- aidge_export_cpp/unit_tests/.gitignore | 2 + aidge_export_cpp/unit_tests/test_export.py | 124 ++++++++++++++++++++- 2 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 aidge_export_cpp/unit_tests/.gitignore diff --git a/aidge_export_cpp/unit_tests/.gitignore b/aidge_export_cpp/unit_tests/.gitignore new file mode 100644 index 0000000..2194471 --- /dev/null +++ b/aidge_export_cpp/unit_tests/.gitignore @@ -0,0 +1,2 @@ +# Ignore +/*_test/ \ No newline at end of file diff --git a/aidge_export_cpp/unit_tests/test_export.py b/aidge_export_cpp/unit_tests/test_export.py index 449eebe..d900df8 100644 --- a/aidge_export_cpp/unit_tests/test_export.py +++ b/aidge_export_cpp/unit_tests/test_export.py @@ -4,18 +4,130 @@ import aidge_backend_cpu import aidge_export_cpp import numpy as np +import subprocess +import re +from aidge_core.utils import run_command + +def initFiller(model): + # Initialize parameters (weights and biases) + for node in model.get_nodes(): + if node.type() == "Producer": + prod_op = node.get_operator() + value = prod_op.get_output(0) + value.set_backend("cpu") + tuple_out = node.output(0)[0] + # No conv in current network + if tuple_out[0].type() == "Conv" and tuple_out[1] == 1: + # Conv weight + aidge_core.xavier_uniform_filler(value) + elif tuple_out[0].type() == "Conv" and tuple_out[1] == 2: + # Conv bias + aidge_core.constant_filler(value, 0.01) + elif tuple_out[0].type() == "FC" and tuple_out[1] == 1: + # FC weight + aidge_core.normal_filler(value) + elif tuple_out[0].type() == "FC" and tuple_out[1] == 2: + # FC bias + aidge_core.constant_filler(value, 0.01) + else: + pass + + +class test_operator_export(unittest.TestCase): -class test_export(unittest.TestCase): - """Test tensor binding - """ def setUp(self): - pass + # TODO change seed at each test ? + RNG_SEED = 1234 + np.random.seed(RNG_SEED) + aidge_core.random.Generator.set_seed(RNG_SEED) def tearDown(self): pass - def test_export_cpp(self): - print("Export test to do") + def unit_test_export(self, graph_view, op_name, in_dims): + """ + TODO: + * Handle multiple dataformat + * Handle multiple datatype (currently only float32) + + Here are the following steps of this test: + 1- Generate random inputs + 2- Forward random inputs to the graph + 3- Generate Cpp export with a main that compare the result of the inference with the result obtained at step 2. + 4- Retrieve standard output and using regex to now if the results are the same + """ + graph_view.compile("cpu", aidge_core.dtype.float32, dims=in_dims) + scheduler = aidge_core.SequentialScheduler(graph_view) + + in_tensor = [aidge_core.Tensor(np.random.random(in_dim).astype(np.float32)) for in_dim in in_dims] + scheduler.forward(data=in_tensor) + + # Note the convention ``<op_name>_test`` is useful for gitignore to avoid pushing generated export by accident. + export_folder = op_name + "_test" + + # Export the model in C++ standalone + aidge_core.export_utils.scheduler_export( + scheduler, + export_folder, + aidge_export_cpp.ExportLibCpp, + memory_manager=aidge_core.mem_info.generate_optimized_memory_info, + memory_manager_args={"stats_folder": f"{export_folder}/stats", "wrapping": False } + ) + + aidge_core.export_utils.generate_main_compare_cpp(export_folder, graph_view) + print("COMPILATION") + + try: + for std_line in run_command(["make"], cwd=export_folder): + print(std_line, end="") + except subprocess.CalledProcessError as e: + self.assertTrue(1, f"An error occurred: {e}\nFailed to generate export.") + + print("RUN EXPORT") + pattern = r"Number of equal outputs: (\d+) / (\d+)" + comparison_matched = False + result = False + try: + for std_line in run_command(["./bin/run_export"], cwd=export_folder): + print(std_line, end="") + matches = re.findall(pattern, std_line) + if matches: + self.assertFalse(comparison_matched, "Two comparison matched found!") + expected, infered = map(int, matches[0]) + result = (expected == infered) + comparison_matched = True + except subprocess.CalledProcessError as e: + self.assertTrue(1, f"An error occurred: {e}\nFailed to run export for comparison.") + + self.assertTrue(comparison_matched, "No comparison matched found!") + self.assertTrue(result, "Export result are different than backend ones.") + + + def test_export_FC_flatten_in(self): + """Test exporting a FC operator with a flattened input. + """ + model = aidge_core.sequential([ + aidge_core.FC(in_channels=6, out_channels=6, name="InputNode") + ]) + initFiller(model) + + self.unit_test_export(model, "FC_flat", [[1, 6, 1, 1]]) + + @unittest.skip("Currently this test is failing") + def test_export_FC_image_in(self): + """Test exporting a FC operator with a HWC input. + """ + model = aidge_core.sequential([ + aidge_core.FC(in_channels=12, out_channels=6, name="InputNode") + ]) + initFiller(model) + self.unit_test_export(model, "FC_img", [[1, 3, 2, 2]]) + def test_export_Conv(self): + model = aidge_core.sequential([ + aidge_core.Conv2D(1, 1, [3, 3], name="InputNode") + ]) + initFiller(model) + self.unit_test_export(model, "Conv", [[1, 1, 9, 9]]) if __name__ == '__main__': unittest.main() -- GitLab