diff --git a/compiler/circle-resizer/include/CircleModel.h b/compiler/circle-resizer/include/CircleModel.h new file mode 100644 index 00000000000..20ed294e79a --- /dev/null +++ b/compiler/circle-resizer/include/CircleModel.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025 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. + */ + +#ifndef __CIRCLE_RESIZER_CIRCLE_MODEL_H__ +#define __CIRCLE_RESIZER_CIRCLE_MODEL_H__ + +#include "Shape.h" + +#include +#include +#include + +namespace luci +{ +class Module; +} + +namespace circle_resizer +{ + +/** + * The representation of Circle Model. + */ +class CircleModel +{ +public: + /** + * @brief Initialize the model with buffer representation. + * + * Exceptions: + * - std::runtime_error if interpretation of provided buffer as a circle model failed. + */ + explicit CircleModel(const std::vector &buffer); + + /** + * @brief Initialize the model with buffer representation. + * + * Exceptions: + * - std::runtime_error if reading a model from provided path failed. + */ + explicit CircleModel(const std::string &model_path); + + /** + * @brief Dtor of CircleModel. Note that explicit declaration is needed to satisfy forward + * declaration + unique_ptr. + */ + ~CircleModel(); + + /** + * @brief Get the loaded model in luci::Module representation. + */ + luci::Module *module(); + + /** + * @brief Get input shapes of the loaded model. + */ + std::vector input_shapes() const; + + /** + * @brief Get output shapes of the loaded model. + * + */ + std::vector output_shapes() const; + + /** + * @brief Save the model to the output stream. + * + * Exceptions: + * - std::runtime_error if saving the model the given stream failed. + */ + void save(std::ostream &stream); + + /** + * @brief Save the model to the location indicated by output_path. + * + * Exceptions: + * - std::runtime_error if saving the model the given path failed. + */ + void save(const std::string &output_path); + +private: + std::unique_ptr _module; +}; + +} // namespace circle_resizer + +#endif // __CIRCLE_RESIZER_CIRCLE_MODEL_H__ diff --git a/compiler/circle-resizer/requires.cmake b/compiler/circle-resizer/requires.cmake index 8e48764f58f..7c0ade30d58 100644 --- a/compiler/circle-resizer/requires.cmake +++ b/compiler/circle-resizer/requires.cmake @@ -1,3 +1,5 @@ require("arser") +require("common-artifacts") +require("mio-circle08") require("safemain") require("vconone") diff --git a/compiler/circle-resizer/src/CMakeLists.txt b/compiler/circle-resizer/src/CMakeLists.txt index 03f5a2dba14..85e0380b40e 100644 --- a/compiler/circle-resizer/src/CMakeLists.txt +++ b/compiler/circle-resizer/src/CMakeLists.txt @@ -1,9 +1,15 @@ -list(APPEND CIRCLE_RESIZER_CORE_SOURCES Dim.cpp) -list(APPEND CIRCLE_RESIZER_CORE_SOURCES Shape.cpp) -list(APPEND CIRCLE_RESIZER_CORE_SOURCES ShapeParser.cpp) +list(APPEND CIRCLE_RESIZER_SOURCES Dim.cpp) +list(APPEND CIRCLE_RESIZER_SOURCES Shape.cpp) +list(APPEND CIRCLE_RESIZER_SOURCES ShapeParser.cpp) +list(APPEND CIRCLE_RESIZER_SOURCES CircleModel.cpp) -add_library(circle_resizer_core STATIC "${CIRCLE_RESIZER_CORE_SOURCES}") +add_library(circle_resizer_core SHARED "${CIRCLE_RESIZER_SOURCES}") target_include_directories(circle_resizer_core PUBLIC ../include) +target_link_libraries(circle_resizer_core PRIVATE luci_export) +target_link_libraries(circle_resizer_core PRIVATE luci_import) +target_link_libraries(circle_resizer_core PRIVATE luci_lang) +target_link_libraries(circle_resizer_core PRIVATE mio_circle08) + install(TARGETS circle_resizer_core DESTINATION lib) diff --git a/compiler/circle-resizer/src/CircleModel.cpp b/compiler/circle-resizer/src/CircleModel.cpp new file mode 100644 index 00000000000..69c5040474d --- /dev/null +++ b/compiler/circle-resizer/src/CircleModel.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2025 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 "CircleModel.h" + +#include + +#include +#include +#include + +#include + +using namespace circle_resizer; + +namespace +{ + +std::vector read_model(const std::string &model_path) +{ + std::ifstream file_stream(model_path, std::ios::in | std::ios::binary | std::ifstream::ate); + if (!file_stream.is_open()) + { + throw std::runtime_error("Failed to open file: " + model_path); + } + + std::streamsize size = file_stream.tellg(); + file_stream.seekg(0, std::ios::beg); + + std::vector buffer(size); + if (!file_stream.read(reinterpret_cast(buffer.data()), size)) + { + throw std::runtime_error("Failed to read file: " + model_path); + } + + return buffer; +} + +std::unique_ptr load_module(const std::vector &model_buffer) +{ + flatbuffers::Verifier verifier{model_buffer.data(), model_buffer.size()}; + if (!circle::VerifyModelBuffer(verifier)) + { + throw std::runtime_error("Verification of the model failed"); + } + + const luci::GraphBuilderSource *source_ptr = &luci::GraphBuilderRegistry::get(); + luci::Importer importer(source_ptr); + return importer.importModule(model_buffer.data(), model_buffer.size()); +} + +class BufferModelContract : public luci::CircleExporter::Contract +{ +public: + BufferModelContract(luci::Module *module) + : _module(module), _buffer{std::make_unique>()} + { + assert(_module); // FIX_CALLER_UNLESS + } + + luci::Module *module() const override { return _module; } + + bool store(const char *ptr, const size_t size) const override + { + _buffer->resize(size); + std::copy(ptr, ptr + size, _buffer->begin()); + return true; + } + + std::vector get_buffer() { return *_buffer; } + +private: + luci::Module *_module; + std::unique_ptr> _buffer; // note that the store method has to be const +}; + +template +std::vector extract_shapes(const std::vector &nodes) +{ + std::vector shapes; + for (const auto &loco_node : nodes) + { + std::vector dims; + const auto circle_node = loco::must_cast(loco_node); + for (uint32_t dim_idx = 0; dim_idx < circle_node->rank(); dim_idx++) + { + if (circle_node->dim(dim_idx).known()) + { + const int32_t dim_val = circle_node->dim(dim_idx).value(); + dims.push_back(Dim{dim_val}); + } + else + { + dims.push_back(Dim{-1}); + } + } + shapes.push_back(Shape{dims}); + } + return shapes; +} + +} // namespace + +CircleModel::CircleModel(const std::vector &buffer) : _module{load_module(buffer)} {} + +CircleModel::CircleModel(const std::string &model_path) : CircleModel(read_model(model_path)) {} + +luci::Module *CircleModel::module() { return _module.get(); } + +void CircleModel::save(std::ostream &stream) +{ + BufferModelContract contract(module()); + luci::CircleExporter exporter; + if (!exporter.invoke(&contract)) + { + throw std::runtime_error("Exporting buffer from the model failed"); + } + + auto model_buffer = contract.get_buffer(); + stream.write(reinterpret_cast(model_buffer.data()), model_buffer.size()); + if (!stream.good()) + { + throw std::runtime_error("Failed to write to output stream"); + } +} + +void CircleModel::save(const std::string &output_path) +{ + std::ofstream out_stream(output_path, std::ios::out | std::ios::binary); + save(out_stream); +} + +std::vector CircleModel::input_shapes() const +{ + return extract_shapes(loco::input_nodes(_module->graph())); +} + +std::vector CircleModel::output_shapes() const +{ + return extract_shapes(loco::output_nodes(_module->graph())); +} + +CircleModel::~CircleModel() = default; diff --git a/compiler/circle-resizer/tests/CMakeLists.txt b/compiler/circle-resizer/tests/CMakeLists.txt index 6f450467537..eeebdfe973f 100644 --- a/compiler/circle-resizer/tests/CMakeLists.txt +++ b/compiler/circle-resizer/tests/CMakeLists.txt @@ -4,7 +4,13 @@ endif(NOT ENABLE_TEST) list(APPEND CIRCLE_RESIZER_TEST_SOURCES Shape.test.cpp) list(APPEND CIRCLE_RESIZER_TEST_SOURCES ShapeParser.test.cpp) +list(APPEND CIRCLE_RESIZER_TEST_SOURCES CircleModel.test.cpp) nnas_find_package(GTest REQUIRED) GTest_AddTest(circle_resizer_unit_test ${CIRCLE_RESIZER_TEST_SOURCES}) target_link_libraries(circle_resizer_unit_test circle_resizer_core) + +get_target_property(ARTIFACTS_PATH testDataGenerator BINARY_DIR) +set_tests_properties(circle_resizer_unit_test + PROPERTIES + ENVIRONMENT "ARTIFACTS_PATH=${ARTIFACTS_PATH}") diff --git a/compiler/circle-resizer/tests/CircleModel.test.cpp b/compiler/circle-resizer/tests/CircleModel.test.cpp new file mode 100644 index 00000000000..e803de31fb9 --- /dev/null +++ b/compiler/circle-resizer/tests/CircleModel.test.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025 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 "CircleModel.h" + +#include +#include + +#include + +using namespace circle_resizer; +using ::testing::HasSubstr; + +namespace +{ + +bool compare_shapes(const std::vector ¤t, const std::vector &expected) +{ + if (current.size() != expected.size()) + { + return false; + } + for (size_t i = 0; i < current.size(); ++i) + { + if (!(current[i] == expected[i])) + { + return false; + } + } + return true; +} + +} // namespace + +class CircleModelTest : public ::testing::Test +{ +protected: + void SetUp() override + { + char *path = std::getenv("ARTIFACTS_PATH"); + if (path == nullptr) + { + throw std::runtime_error("environmental variable ARTIFACTS_PATH required for circle-resizer " + "tests was not not provided"); + } + _test_models_dir = path; + } + +protected: + std::string _test_models_dir; +}; + +TEST_F(CircleModelTest, proper_input_output_shapes) +{ + CircleModel circle_model(_test_models_dir + "/Add_000.circle"); + EXPECT_TRUE(compare_shapes(circle_model.input_shapes(), + std::vector{Shape{1, 4, 4, 3}, Shape{1, 4, 4, 3}})); + EXPECT_TRUE(compare_shapes(circle_model.output_shapes(), std::vector{Shape{1, 4, 4, 3}})); +} + +TEST_F(CircleModelTest, proper_output_stream) +{ + CircleModel circle_model(_test_models_dir + "/Add_000.circle"); + std::stringstream out_stream; + circle_model.save(out_stream); + out_stream.seekg(0, std::ios::end); + EXPECT_TRUE(out_stream.tellg() > 0); +} + +TEST_F(CircleModelTest, model_file_not_exist_NEG) +{ + auto file_name = "/not_existed.circle"; + try + { + CircleModel circle_model(file_name); + FAIL() << "Expected std::runtime_error"; + } + catch (const std::runtime_error &err) + { + EXPECT_THAT(err.what(), HasSubstr("Failed to open file")); + EXPECT_THAT(err.what(), HasSubstr(file_name)); + } + catch (...) + { + FAIL() << "Expected std::runtime_error, other exception thrown"; + } +} + +TEST_F(CircleModelTest, invalid_model_NEG) +{ + try + { + CircleModel(std::vector{1, 2, 3, 4, 5}); + FAIL() << "Expected std::runtime_error"; + } + catch (const std::runtime_error &err) + { + EXPECT_THAT(err.what(), HasSubstr("Verification of the model failed")); + } + catch (...) + { + FAIL() << "Expected std::runtime_error, other exception thrown"; + } +} + +TEST_F(CircleModelTest, incorrect_output_stream_NEG) +{ + auto circle_model = std::make_shared(_test_models_dir + "/Add_000.circle"); + std::ofstream out_stream; + try + { + circle_model->save(out_stream); + } + catch (const std::runtime_error &err) + { + EXPECT_THAT(err.what(), HasSubstr("Failed to write to output stream")); + } + catch (...) + { + FAIL() << "Expected std::runtime_error, other exception thrown"; + } +}