diff --git a/compiler/circle-interpreter-cffi-test/CMakeLists.txt b/compiler/circle-interpreter-cffi-test/CMakeLists.txt new file mode 100644 index 00000000000..f2704baaeb5 --- /dev/null +++ b/compiler/circle-interpreter-cffi-test/CMakeLists.txt @@ -0,0 +1,17 @@ +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +set(VIRTUALENV "${NNCC_OVERLAY_DIR}/venv_2_12_1") +set(TEST_LIST_FILE "test.lst") + +get_target_property(ARTIFACTS_PATH testDataGenerator BINARY_DIR) + +add_test( + NAME circle_interpreter_cffi_test + COMMAND ${VIRTUALENV}/bin/python infer.py + --lib_path $ + --test_list ${TEST_LIST_FILE} + --artifact_dir ${ARTIFACTS_PATH} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/compiler/circle-interpreter-cffi-test/README.md b/compiler/circle-interpreter-cffi-test/README.md new file mode 100644 index 00000000000..2d5dac56ca6 --- /dev/null +++ b/compiler/circle-interpreter-cffi-test/README.md @@ -0,0 +1,15 @@ +# circle-interpreter-cffi-test + +The `circle_interpereter_cffi` library wrapped with CFFI is designed to expose +an existing `luci-interpreter` class to Python. It simplifies the integration of +the class by creating a Python-compatible interface using CFFI. + +`circle-interpreter-cffi-test` ensures that the Python bindings for the C++ +library correctly. Specifically, it verifies that: + +1. The CFFI-wrapped library can succesfully load the circle model. +2. Inputs passed from Python are correctly interpreted by the `luci-interpreter`. +3. The output generated by the interpreter matches the expected results. + +This test provides confidence that `luci-interpter`, when accessed through +the Python interface, produces the same results as the original implementation. diff --git a/compiler/circle-interpreter-cffi-test/infer.py b/compiler/circle-interpreter-cffi-test/infer.py new file mode 100644 index 00000000000..caa244ff140 --- /dev/null +++ b/compiler/circle-interpreter-cffi-test/infer.py @@ -0,0 +1,112 @@ +import argparse +import h5py +import numpy as np +from pathlib import Path +import re +import sys + +############ Managing paths for the artifacts required by the test. + + +def extract_test_args(s): + p = re.compile('eval\\((.*)\\)') + result = p.search(s) + return result.group(1) + + +parser = argparse.ArgumentParser() +parser.add_argument('--lib_path', type=str, required=True) +parser.add_argument('--test_list', type=str, required=True) +parser.add_argument('--artifact_dir', type=str, required=True) +args = parser.parse_args() + +with open(args.test_list) as f: + contents = [line.rstrip() for line in f] +# remove newline and comments. +eval_lines = [line for line in contents if line.startswith('eval(')] +test_args = [extract_test_args(line) for line in eval_lines] +test_models = [Path(args.artifact_dir) / f'{arg}.circle' for arg in test_args] +input_data = [ + Path(args.artifact_dir) / f'{arg}.opt/metadata/tc/input.h5' for arg in test_args +] +expected_output_data = [ + Path(args.artifact_dir) / f'{arg}.opt/metadata/tc/expected.h5' for arg in test_args +] + +############ CFFI test + +from cffi import FFI + +ffi = FFI() +ffi.cdef(""" + typedef struct InterpreterWrapper InterpreterWrapper; + + const char *get_last_error(void); + void clear_last_error(void); + InterpreterWrapper *Interpreter_new(const uint8_t *data, const size_t data_size); + void Interpreter_delete(InterpreterWrapper *intp); + void Interpreter_interpret(InterpreterWrapper *intp); + void Interpreter_writeInputTensor(InterpreterWrapper *intp, const int input_idx, const void *data, size_t input_size); + void Interpreter_readOutputTensor(InterpreterWrapper *intp, const int output_idx, void *output, size_t output_size); +""") +C = ffi.dlopen(args.lib_path) + + +def check_for_errors(): + error_message = ffi.string(C.get_last_error()).decode('utf-8') + if error_message: + C.clear_last_error() + raise RuntimeError(f'C++ Exception: {error_message}') + + +def error_checked(func): + """ + Decorator to wrap functions with error checking. + """ + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + check_for_errors() + return result + + return wrapper + + +Interpreter_new = error_checked(C.Interpreter_new) +Interpreter_delete = error_checked(C.Interpreter_delete) +Interpreter_interpret = error_checked(C.Interpreter_interpret) +Interpreter_writeInputTensor = error_checked(C.Interpreter_writeInputTensor) +Interpreter_readOutputTensor = error_checked(C.Interpreter_readOutputTensor) + +for idx, model_path in enumerate(test_models): + with open(model_path, "rb") as f: + model_data = ffi.from_buffer(bytearray(f.read())) + + try: + intp = Interpreter_new(model_data, len(model_data)) + + # Set inputs + h5 = h5py.File(input_data[idx]) + input_values = h5.get('value') + input_num = len(input_values) + for input_idx in range(input_num): + arr = np.array(input_values.get(str(input_idx))) + c_arr = ffi.from_buffer(arr) + Interpreter_writeInputTensor(intp, input_idx, c_arr, arr.nbytes) + # Do inference + Interpreter_interpret(intp) + # Check outputs + h5 = h5py.File(expected_output_data[idx]) + output_values = h5.get('value') + output_num = len(output_values) + for output_idx in range(output_num): + arr = np.array(output_values.get(str(output_idx))) + result = np.empty(arr.shape, dtype=arr.dtype) + Interpreter_readOutputTensor(intp, output_idx, ffi.from_buffer(result), + arr.nbytes) + if not np.allclose(result, arr): + raise RuntimeError("Wrong outputs") + + Interpreter_delete(intp) + except RuntimeError as e: + print(e) + sys.exit(-1) diff --git a/compiler/circle-interpreter-cffi-test/requires.cmake b/compiler/circle-interpreter-cffi-test/requires.cmake new file mode 100644 index 00000000000..8d8585b470d --- /dev/null +++ b/compiler/circle-interpreter-cffi-test/requires.cmake @@ -0,0 +1,2 @@ +require("common-artifacts") +require("circle-interpreter") diff --git a/compiler/circle-interpreter-cffi-test/test.lst b/compiler/circle-interpreter-cffi-test/test.lst new file mode 100644 index 00000000000..97ec610ad5f --- /dev/null +++ b/compiler/circle-interpreter-cffi-test/test.lst @@ -0,0 +1,17 @@ +eval(Add_000) +eval(Add_U8_000) +eval(AveragePool2D_000) +eval(Concatenation_000) +eval(Conv2D_000) +eval(Conv2D_001) +eval(Conv2D_002) +eval(DepthwiseConv2D_000) +eval(FullyConnected_000) +eval(FullyConnected_001) +eval(MaxPool2D_000) +eval(Mul_000) +eval(Pad_000) +eval(Reshape_000) +eval(Reshape_001) +eval(Reshape_002) +eval(Softmax_000) diff --git a/tests/nnfw_api/lib/CircleGen.cc b/tests/nnfw_api/lib/CircleGen.cc index 80d1fcfc870..83ab3487761 100644 --- a/tests/nnfw_api/lib/CircleGen.cc +++ b/tests/nnfw_api/lib/CircleGen.cc @@ -582,6 +582,15 @@ uint32_t CircleGen::addOperatorSquare(const OperatorParams ¶ms) circle::BuiltinOptions_SquareOptions, options); } +uint32_t CircleGen::addOperatorSqueeze(const OperatorParams ¶ms, + const std::vector &squeeze_dims) +{ + auto squeeze_dims_vec = _fbb.CreateVector(squeeze_dims.data(), squeeze_dims.size()); + auto options = circle::CreateSqueezeOptions(_fbb, squeeze_dims_vec).Union(); + return addOperatorWithOptions(params, circle::BuiltinOperator_SQUEEZE, + circle::BuiltinOptions_SqueezeOptions, options); +} + uint32_t CircleGen::addOperatorBatchToSpaceND(const OperatorParams ¶ms) { auto options = circle::CreateBatchToSpaceNDOptions(_fbb).Union(); diff --git a/tests/nnfw_api/lib/CircleGen.h b/tests/nnfw_api/lib/CircleGen.h index 6297f415c69..ac95ba9b2e1 100644 --- a/tests/nnfw_api/lib/CircleGen.h +++ b/tests/nnfw_api/lib/CircleGen.h @@ -223,6 +223,8 @@ class CircleGen uint32_t addOperatorSplit(const OperatorParams ¶ms, int32_t num_split); uint32_t addOperatorSqrt(const OperatorParams ¶ms); uint32_t addOperatorSquare(const OperatorParams ¶ms); + uint32_t addOperatorSqueeze(const OperatorParams ¶ms, + const std::vector &squeeze_dims); uint32_t addOperatorStridedSlice(const OperatorParams ¶ms, int32_t begin_mask = 0, int32_t end_mask = 0, int32_t ellipsis_mask = 0, int32_t new_axis_mask = 0, int32_t shrink_axis_mask = 0); diff --git a/tests/nnfw_api/src/GenModelTests/ModelTensorMemorySharing.test.cc b/tests/nnfw_api/src/GenModelTests/ModelTensorMemorySharing.test.cc new file mode 100644 index 00000000000..590b0df2e73 --- /dev/null +++ b/tests/nnfw_api/src/GenModelTests/ModelTensorMemorySharing.test.cc @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "CircleGen.h" +#include "GenModelTest.h" + +namespace +{ +// Add node other than Reshape/ExpandDims/Squeeze. +// It is used for cases where Reshape input/output is not input/output on the whole model. +uint32_t addNotOptimizedNode(CircleGen &cgen, const CircleGen::OperatorParams ¶ms) +{ + return cgen.addOperatorCos(params); +} +} // namespace + +TEST_F(GenModelTest, reshape_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape_out}, {cos2_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, expand_dims_inference) +{ + CircleGen cgen; + std::vector axes_data{0, 1}; + uint32_t axes_buf = cgen.addBuffer(axes_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int axes = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, axes_buf}); + int expand_dims_out = cgen.addTensor({{1, 1, 2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{1, 1, 2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorExpandDims({{cos1_out, axes}, {expand_dims_out}}); + addNotOptimizedNode(cgen, {{expand_dims_out}, {cos2_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, squeeze_inference) +{ + CircleGen cgen; + const std::vector squeeze_dims{0, 2}; + int input = cgen.addTensor({{1, 2, 1, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{1, 2, 1, 2}, circle::TensorType::TensorType_FLOAT32}); + int squeeze_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorSqueeze({{cos1_out}, {squeeze_out}}, squeeze_dims); + addNotOptimizedNode(cgen, {{squeeze_out}, {cos2_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_const_input_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + std::vector reshape_in_data{1, 2, 3, 4}; + uint32_t reshape_in_buf = cgen.addBuffer(reshape_in_data); + int reshape_input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32, reshape_in_buf}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + cgen.addOperatorReshape({{reshape_input, new_shape}, {reshape_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape_out}, {cos_out}}); + cgen.setInputsAndOutputs({}, {cos_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({}, {{0.54030231, -0.41614684, -0.9899925, -0.65364362}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_dynamic_output) +{ + CircleGen cgen; + int cast_in = cgen.addTensor({{4}, circle::TensorType::TensorType_INT32}); + int cast_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32}); + int reshape_out = cgen.addTensor({{}, circle::TensorType::TensorType_FLOAT32}); + int cast2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_INT32}); + + cgen.addOperatorCast({{cast_in}, {cast_out}}, circle::TensorType::TensorType_INT32, + circle::TensorType::TensorType_FLOAT32); + cgen.addOperatorReshape({{cast_out, new_shape}, {reshape_out}}); + cgen.addOperatorCast({{reshape_out}, {cast2_out}}, circle::TensorType::TensorType_FLOAT32, + circle::TensorType::TensorType_INT32); + cgen.setInputsAndOutputs({cast_in, new_shape}, {cast2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}, {2, 2}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_input_used_in_many_places_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos3_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape_out}, {cos2_out}}); + addNotOptimizedNode(cgen, {{cos1_out}, {cos3_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out, cos3_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}, + {0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_output_used_in_many_places_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos3_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape_out}, {cos2_out}}); + addNotOptimizedNode(cgen, {{reshape_out}, {cos3_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out, cos3_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}, + {0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_reshape_chain_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape1_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int reshape2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape1_out}}, &new_shape_data); + cgen.addOperatorReshape({{reshape1_out, new_shape}, {reshape2_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape2_out}, {cos2_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_reshape_reshape_chain_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape1_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int reshape2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int reshape3_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape1_out}}, &new_shape_data); + cgen.addOperatorReshape({{reshape1_out, new_shape}, {reshape2_out}}, &new_shape_data); + cgen.addOperatorReshape({{reshape2_out, new_shape}, {reshape3_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape3_out}, {cos2_out}}); + cgen.setInputsAndOutputs({input}, {cos2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_input_model_input_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int output = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + cgen.addOperatorReshape({{input, new_shape}, {output}}, &new_shape_data); + cgen.setInputsAndOutputs({input}, {output}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_input_model_output_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + int cos2_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape_out}}, &new_shape_data); + addNotOptimizedNode(cgen, {{reshape_out}, {cos2_out}}); + cgen.setInputsAndOutputs({input}, {cos1_out, cos2_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.54030231, -0.41614684, -0.9899925, -0.65364362}, + {0.85755322, 0.91465333, 0.54869613, 0.79387345}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, reshape_output_model_output_inference) +{ + CircleGen cgen; + std::vector new_shape_data{2, 2}; + uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + int input = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int cos1_out = cgen.addTensor({{4}, circle::TensorType::TensorType_FLOAT32}); + int new_shape = cgen.addTensor({{2}, circle::TensorType::TensorType_INT32, new_shape_buf}); + int reshape_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + addNotOptimizedNode(cgen, {{input}, {cos1_out}}); + cgen.addOperatorReshape({{cos1_out, new_shape}, {reshape_out}}, &new_shape_data); + cgen.setInputsAndOutputs({input}, {reshape_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + uniformTCD({{1, 2, 3, 4}}, {{0.54030231, -0.41614684, -0.9899925, -0.65364362}})); + _context->setBackends({"cpu"}); + + SUCCEED(); +} diff --git a/tests/nnfw_api/src/GenModelTests/one_op_tests/ExpandDims.test.cc b/tests/nnfw_api/src/GenModelTests/one_op_tests/ExpandDims.test.cc index 280cf7344f0..9c88a77f8be 100644 --- a/tests/nnfw_api/src/GenModelTests/one_op_tests/ExpandDims.test.cc +++ b/tests/nnfw_api/src/GenModelTests/one_op_tests/ExpandDims.test.cc @@ -56,7 +56,7 @@ TEST_F(GenModelTest, OneOp_ExpandDims_Int64AxisNeg) SUCCEED(); } -TEST_F(GenModelTest, OneOp_neg_ExpandDims_Axis) +TEST_F(GenModelTest, neg_OneOp_ExpandDims_Axis) { CircleGen cgen; @@ -75,7 +75,26 @@ TEST_F(GenModelTest, OneOp_neg_ExpandDims_Axis) SUCCEED(); } -TEST_F(GenModelTest, OneOp_neg_ExpandDims_AxisNegInput) +TEST_F(GenModelTest, neg_OneOp_ExpandDims_NegAxis) +{ + CircleGen cgen; + + std::vector axis_data{-5}; + uint32_t axis_buf = cgen.addBuffer(axis_data); + int in = cgen.addTensor({{1, 4, 1}, circle::TensorType::TensorType_FLOAT32}); + int axis = cgen.addTensor({{1}, circle::TensorType::TensorType_INT32, axis_buf}); + int out = cgen.addTensor({{1, 1, 4, 1}, circle::TensorType::TensorType_FLOAT32}); + cgen.addOperatorExpandDims({{in, axis}, {out}}); + cgen.setInputsAndOutputs({in}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->setBackends({"cpu"}); + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_ExpandDims_AxisNegInput) { CircleGen cgen; diff --git a/tests/nnfw_api/src/GenModelTests/one_op_tests/Reshape.test.cc b/tests/nnfw_api/src/GenModelTests/one_op_tests/Reshape.test.cc new file mode 100644 index 00000000000..314fe914580 --- /dev/null +++ b/tests/nnfw_api/src/GenModelTests/one_op_tests/Reshape.test.cc @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GenModelTest.h" + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_shape) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + + const std::vector new_shape_data{1, 5}; + const uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + const int new_shape = cgen.addTensor({{2}, f32, new_shape_buf}); + const int input = cgen.addTensor({{4}, f32}); + const int out = cgen.addTensor({{1, 5}, f32}); + + cgen.addOperatorReshape({{input, new_shape}, {out}}, &new_shape_data); + cgen.setInputsAndOutputs({input}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_dyn_shape) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + const auto i32 = circle::TensorType::TensorType_INT32; + + const std::vector in_data{1.f, 2.f, 3.f, 4.f}; + const uint32_t input_buf = cgen.addBuffer(in_data); + const int input = cgen.addTensor({{4}, f32, input_buf}); + const int new_shape = cgen.addTensor({{2}, i32}); + const int out = cgen.addTensor({{}, f32}); // unspecified shape + + const CircleGen::Shape empty_new_shape; + cgen.addOperatorReshape({{input, new_shape}, {out}}, &empty_new_shape); + cgen.setInputsAndOutputs({new_shape}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + TestCaseData{}.addInput(std::vector{1, 5}).addOutput(in_data).expectFailRun()); + _context->output_sizes(0, sizeof(float) * in_data.size()); + _context->setBackends({"cpu", "gpu_cl"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_dyn_type) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + + const std::vector in_data{1.f, 2.f, 3.f, 4.f}; + const uint32_t input_buf = cgen.addBuffer(in_data); + const int input = cgen.addTensor({{4}, f32, input_buf}); + const int new_shape = cgen.addTensor({{2}, f32}); + const int out = cgen.addTensor({{}, f32}); // unspecified shape + + const CircleGen::Shape empty_new_shape; + cgen.addOperatorReshape({{input, new_shape}, {out}}, &empty_new_shape); + cgen.setInputsAndOutputs({new_shape}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + TestCaseData{}.addInput(std::vector{2, 2}).addOutput(in_data).expectFailRun()); + _context->output_sizes(0, sizeof(float) * in_data.size()); + _context->setBackends({"cpu", "gpu_cl"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_shape_with_neg_dim) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + + const std::vector new_shape_data{5, -1}; + const uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + const int new_shape = cgen.addTensor({{2}, f32, new_shape_buf}); + const int input = cgen.addTensor({{4}, f32}); + const int out = cgen.addTensor({{1, 5}, f32}); + + cgen.addOperatorReshape({{input, new_shape}, {out}}, &new_shape_data); + cgen.setInputsAndOutputs({input}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_dyn_shape_with_neg_dim) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + const auto i32 = circle::TensorType::TensorType_INT32; + + const std::vector in_data{1.f, 2.f, 3.f, 4.f}; + const uint32_t input_buf = cgen.addBuffer(in_data); + const int input = cgen.addTensor({{4}, f32, input_buf}); + const int new_shape = cgen.addTensor({{2}, i32}); + const int out = cgen.addTensor({{}, f32}); // unspecified shape + + const CircleGen::Shape empty_new_shape; + cgen.addOperatorReshape({{input, new_shape}, {out}}, &empty_new_shape); + cgen.setInputsAndOutputs({new_shape}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + TestCaseData{}.addInput(std::vector{-1, 5}).addOutput(in_data).expectFailRun()); + _context->output_sizes(0, sizeof(float) * in_data.size()); + _context->setBackends({"cpu", "gpu_cl"}); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_shape_with_zero_dim) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + + const std::vector new_shape_data{5, 0}; + const uint32_t new_shape_buf = cgen.addBuffer(new_shape_data); + const int new_shape = cgen.addTensor({{2}, f32, new_shape_buf}); + const int input = cgen.addTensor({{4}, f32}); + const int out = cgen.addTensor({{1, 5}, f32}); + + cgen.addOperatorReshape({{input, new_shape}, {out}}, &new_shape_data); + cgen.setInputsAndOutputs({input}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Reshape_invalid_target_dyn_shape_with_zero_dim) +{ + CircleGen cgen; + const auto f32 = circle::TensorType::TensorType_FLOAT32; + const auto i32 = circle::TensorType::TensorType_INT32; + + const std::vector in_data{1.f, 2.f, 3.f, 4.f}; + const uint32_t input_buf = cgen.addBuffer(in_data); + const int input = cgen.addTensor({{4}, f32, input_buf}); + const int new_shape = cgen.addTensor({{2}, i32}); + const int out = cgen.addTensor({{}, f32}); // unspecified shape + + const CircleGen::Shape empty_new_shape; + cgen.addOperatorReshape({{input, new_shape}, {out}}, &empty_new_shape); + cgen.setInputsAndOutputs({new_shape}, {out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase( + TestCaseData{}.addInput(std::vector{-1, 5}).addOutput(in_data).expectFailRun()); + _context->output_sizes(0, sizeof(float) * in_data.size()); + _context->setBackends({"cpu", "gpu_cl"}); + + SUCCEED(); +} diff --git a/tests/nnfw_api/src/GenModelTests/one_op_tests/Squeeze.test.cc b/tests/nnfw_api/src/GenModelTests/one_op_tests/Squeeze.test.cc new file mode 100644 index 00000000000..73021e39f96 --- /dev/null +++ b/tests/nnfw_api/src/GenModelTests/one_op_tests/Squeeze.test.cc @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GenModelTest.h" + +TEST_F(GenModelTest, neg_OneOp_Squeeze_invalid_dims) +{ + CircleGen cgen; + const std::vector squeeze_dims{0, 1}; // 1 dim here is incorrect + int input = cgen.addTensor({{1, 2, 1, 2}, circle::TensorType::TensorType_FLOAT32}); + int squeeze_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + cgen.addOperatorSqueeze({{input}, {squeeze_out}}, squeeze_dims); + cgen.setInputsAndOutputs({input}, {squeeze_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Squeeze_out_of_rank_dims) +{ + CircleGen cgen; + const std::vector squeeze_dims{0, 4}; // 4 dim here is incorrect + int input = cgen.addTensor({{1, 2, 1, 2}, circle::TensorType::TensorType_FLOAT32}); + int squeeze_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + cgen.addOperatorSqueeze({{input}, {squeeze_out}}, squeeze_dims); + cgen.setInputsAndOutputs({input}, {squeeze_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Squeeze_neg_invalid_dims) +{ + CircleGen cgen; + const std::vector squeeze_dims{0, -3}; // -3 dim here is incorrect + int input = cgen.addTensor({{1, 2, 1, 2}, circle::TensorType::TensorType_FLOAT32}); + int squeeze_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + cgen.addOperatorSqueeze({{input}, {squeeze_out}}, squeeze_dims); + cgen.setInputsAndOutputs({input}, {squeeze_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} + +TEST_F(GenModelTest, neg_OneOp_Squeeze_neg_out_of_rank_dims) +{ + CircleGen cgen; + const std::vector squeeze_dims{0, -5}; // -5 dim here is incorrect + int input = cgen.addTensor({{1, 2, 1, 2}, circle::TensorType::TensorType_FLOAT32}); + int squeeze_out = cgen.addTensor({{2, 2}, circle::TensorType::TensorType_FLOAT32}); + + cgen.addOperatorSqueeze({{input}, {squeeze_out}}, squeeze_dims); + cgen.setInputsAndOutputs({input}, {squeeze_out}); + + _context = std::make_unique(cgen.finish()); + _context->addTestCase(uniformTCD({{1, 2, 3, 4}}, {{1, 2, 3, 4}})); + _context->setBackends({"cpu", "gpu_cl"}); + + _context->expectFailCompile(); + + SUCCEED(); +} diff --git a/tools/model_explorer_circle/src/model_explorer_circle/main.py b/tools/model_explorer_circle/src/model_explorer_circle/main.py index 6d0681728d3..31f8442b55a 100644 --- a/tools/model_explorer_circle/src/model_explorer_circle/main.py +++ b/tools/model_explorer_circle/src/model_explorer_circle/main.py @@ -40,6 +40,8 @@ def __init__(self): v: k for k, v in circle_schema.TensorType.__dict__.items() } + # Number of elements to show in tensor values (default: 16) + self.const_element_count_limit = 16 def load_model(self, model_path: str) -> None: """Load the model from the given path.""" @@ -179,6 +181,9 @@ def convert(self, model_path: str, settings: Dict) -> ModelExplorerGraphs: """Convert the model to a set of graphs.""" graph = graph_builder.Graph(id='main') + if settings.get('const_element_count_limit'): + self.const_element_count_limit = settings['const_element_count_limit'] + self.load_model(model_path) self.build_graph(graph)