diff --git a/compiler/circle-input-names/CMakeLists.txt b/compiler/circle-input-names/CMakeLists.txt new file mode 100644 index 00000000000..2446136c3e7 --- /dev/null +++ b/compiler/circle-input-names/CMakeLists.txt @@ -0,0 +1,24 @@ +set(SOURCE "src/circle-input-names.cpp") + +add_executable(circle_input_names ${SOURCE}) +target_link_libraries(circle_input_names PRIVATE luci_logex) +target_link_libraries(circle_input_names PRIVATE luci_lang) +target_link_libraries(circle_input_names PRIVATE crew) +install(TARGETS circle_input_names DESTINATION bin) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +get_target_property(CIRCLE_INPUT_NAMES_PATH circle_input_names BINARY_DIR) +set(CIRCLE_INPUT_NAMES_PATH "${CIRCLE_INPUT_NAMES_PATH}/circle_input_names") + +nnas_find_package(GTest REQUIRED) + +set(TEST_SOURCE "src/circle-input-names.test.cpp") + +GTest_AddTest(circle-input-names-test ${TEST_SOURCE}) + +set_tests_properties(circle-input-names-test + PROPERTIES + ENVIRONMENT "CIRCLE_INPUT_NAMES_PATH=${CIRCLE_INPUT_NAMES_PATH}") diff --git a/compiler/circle-input-names/README.md b/compiler/circle-input-names/README.md new file mode 100644 index 00000000000..9f65b0b95b9 --- /dev/null +++ b/compiler/circle-input-names/README.md @@ -0,0 +1,18 @@ +# circle-input-names + +`circle-input-names` is a tool to generate input names of the Circle model's operators in JSON format. + +* Example result of `circle-input-names` + ``` + { + "ABS" : [ "x" ], + "ADD" : [ "x", "y" ], + "ADD_N" : [ "inputs" ], + "ARG_MAX" : [ "input", "dimension" ], + "ARG_MIN" : [ "input", "dimension" ], + ... + "INSTANCE_NORM" : [ "input", "gamma", "beta" ], + "RMS_NORM" : [ "input", "gamma" ], + "ROPE" : [ "input", "sin_table", "cos_table" ] + } + ``` diff --git a/compiler/circle-input-names/requires.cmake b/compiler/circle-input-names/requires.cmake new file mode 100644 index 00000000000..2a4b6463a87 --- /dev/null +++ b/compiler/circle-input-names/requires.cmake @@ -0,0 +1,2 @@ +require("crew") +require("luci") diff --git a/compiler/circle-input-names/src/circle-input-names.cpp b/compiler/circle-input-names/src/circle-input-names.cpp new file mode 100644 index 00000000000..9bbce24b058 --- /dev/null +++ b/compiler/circle-input-names/src/circle-input-names.cpp @@ -0,0 +1,194 @@ +/* + * 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 + +using namespace luci; + +// clang-format off +// For Variadic Arity Nodes which have multiple inputs. +template struct is_VariadicArity : std::false_type {}; +template <> struct is_VariadicArity : std::true_type {}; +template <> struct is_VariadicArity : std::true_type {}; +template <> struct is_VariadicArity : std::true_type {}; +template <> struct is_VariadicArity : std::true_type {}; +template <> struct is_VariadicArity : std::true_type {}; +template <> struct is_VariadicArity : std::true_type {}; + +// For Variadic Outputs Nodes which have multiple outputs. +template struct is_VariadicOut : std::false_type {}; +template <> struct is_VariadicOut : std::true_type{}; +template <> struct is_VariadicOut : std::true_type {}; +template <> struct is_VariadicOut : std::true_type {}; +// clang-format on + +// For Circle Nodes which have variadic arity and variadic outputs +template ::value && + is_VariadicOut::value> * = nullptr> +auto CircleNodeCreator() +{ + return CircleOp(1, 1); +} + +// For Circle Nodes which have variadic arity but single output +template ::value && + !is_VariadicOut::value> * = nullptr> +auto CircleNodeCreator() +{ + return CircleOp(1); +} + +// For Circle Nodes which have fixed arity +template ::value> * = nullptr> +auto CircleNodeCreator() +{ + return CircleOp(); +} + +// Add fused activation function option to CircleNode if it supports FusedActFunc traits +void add_fused_actfn_option(CircleNode *node) +{ + auto node_ = dynamic_cast *>(node); + if (node_) + { + node_->fusedActivationFunction(luci::FusedActFunc::RELU); + } +} + +// Add padding option to AVERAGE_POOL_2D, CONV_2D, DEPTHWISE_CONV_2D, L2_POOL_2D, MAX_POOL_2D, +// TRANSPOSE_CONV nodes +void add_padding_option(CircleNode *node) +{ + switch (node->opcode()) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + case luci::CircleOpcode::OPCODE: \ + { \ + auto node_ = dynamic_cast(node); \ + node_->padding(Padding::SAME); \ + break; \ + } + CIRCLE_NODE(AVERAGE_POOL_2D, CircleAveragePool2D) + CIRCLE_NODE(CONV_2D, CircleConv2D) + CIRCLE_NODE(DEPTHWISE_CONV_2D, CircleDepthwiseConv2D) + CIRCLE_NODE(L2_POOL_2D, CircleL2Pool2D) + CIRCLE_NODE(MAX_POOL_2D, CircleMaxPool2D) + CIRCLE_NODE(TRANSPOSE_CONV, CircleTransposeConv) +#undef CIRCLE_NODE + default: + { + break; + } + } + return; +} + +// Add mode option to MIRROR_PAD, ROPE nodes +void add_mode_option(CircleNode *node) +{ + switch (node->opcode()) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + case luci::CircleOpcode::OPCODE: \ + { \ + auto node_ = dynamic_cast(node); \ + auto mode_ = node_->mode(); \ + node_->mode(decltype(mode_)(1)); \ + break; \ + } + CIRCLE_NODE(MIRROR_PAD, CircleMirrorPad) + CIRCLE_NODE(ROPE, CircleRoPE) +#undef CIRCLE_NODE + default: + { + break; + } + } + return; +} + +// Fill dummy values to CircleNode for creating NodeSummary +void fill_dummies_for_summary_creation(CircleNode *node) +{ + add_fused_actfn_option(node); + add_padding_option(node); + add_mode_option(node); +} + +// Mock Symbol Table for CircleNodeSummaryBuilder +class MockSymbolTable : public locop::SymbolTable +{ + std::string lookup(const loco::Node *) const override { return ""; } +}; + +// Create NodeSummary using CircleNodeSummaryBuilder and MockSymbolTable +locop::NodeSummary create_circle_node_summary(CircleNode *node) +{ + locop::NodeSummary s; + MockSymbolTable tbl; + CircleNodeSummaryBuilder builder; + + builder.build(node, &tbl, s); + return s; +} + +// Get input names of CircleNode and export as JSON format +void get_input_names_from_summary(CircleNode *node, locop::NodeSummary &s, + crew::JsonExport &json_export) +{ + std::vector arg_names; + for (int i = 0; i < node->arity(); i++) + { + auto args = s.args().at(i); + // args : pair(name, value) + arg_names.emplace_back(args.first); + } + + // s.opname() : "Circle.Opname"" + auto opname = s.opname().substr(7); + // "Opname" : ["arg1", "arg2",...]," + json_export.key_val(opname, arg_names, true); +} + +int main(void) +{ + std::stringstream ss; + crew::JsonExport json_export(ss); + // "{" + json_export.open_brace(); +#define CIRCLE_NODE(OP, CIRCLE_OP) \ + { \ + auto node = CircleNodeCreator(); \ + fill_dummies_for_summary_creation(&node); \ + auto summary = create_circle_node_summary(&node); \ + get_input_names_from_summary(&node, summary, json_export); \ + } +#define CIRCLE_VNODE(_1, _2) +#include +#undef CIRCLE_NODE +#undef CIRCLE_VNODE + // Remove last comma from stringstream 'ss' + ss.seekp(-2, std::ios_base::end) << '\n'; + // "}" + json_export.close_brace(false); + std::cout << ss.str(); + + return 0; +} diff --git a/compiler/circle-input-names/src/circle-input-names.test.cpp b/compiler/circle-input-names/src/circle-input-names.test.cpp new file mode 100644 index 00000000000..1d4c99593c4 --- /dev/null +++ b/compiler/circle-input-names/src/circle-input-names.test.cpp @@ -0,0 +1,148 @@ +/* + * 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 +#include +#include +#include + +class circle_input_names_test : public ::testing::Test +{ +protected: + // Override Test::SetUp method to run before each test starts + void SetUp(void) override; + +protected: + // Helper functions for tests + std::vector get_input_names_of(std::string op); + +protected: + // Dictionary string containing all input names of each op in JSON format + std::string _input_names_dict_str; +}; + +void circle_input_names_test::SetUp(void) +{ + std::string cmd = std::getenv("CIRCLE_INPUT_NAMES_PATH"); + if (cmd.empty()) + { + throw std::runtime_error("CIRCLE_INPUT_NAMES_PATH is not found"); + } + + FILE *fp = popen(cmd.c_str(), "r"); + if (!fp) + { + throw std::runtime_error("popen() failed"); + } + + char buff[1024]; + std::string result = ""; + try + { + while (fgets(buff, sizeof(buff), fp) != NULL) + { + result += buff; + } + } + catch (...) + { + pclose(fp); + throw; + } + + _input_names_dict_str = result; + pclose(fp); + + return; +} + +std::vector circle_input_names_test::get_input_names_of(std::string op) +{ + std::vector input_names{}; + + // Find op string key from _input_names_dict_str and parse the values input input_names vector + size_t pos = _input_names_dict_str.find(op); + if (pos == std::string::npos) + { + return input_names; + } + else + { + std::string substr = _input_names_dict_str.substr(pos); + size_t start_pos = substr.find("["); + size_t end_pos = substr.find("]"); + if (start_pos != std::string::npos && end_pos != std::string::npos) + { + std::string names = substr.substr(start_pos + 1, end_pos - start_pos - 1); + std::stringstream ss(names); + std::string name; + while (std::getline(ss, name, ',')) + { + std::smatch match; + std::regex pattern = std::regex(R"(^\s*\"([^\"]+)\"\s*$)"); + if (std::regex_match(name, match, pattern)) + { + input_names.push_back(match[1].str()); + } + } + } + } + return std::move(input_names); +} + +TEST_F(circle_input_names_test, valid_command) { ASSERT_FALSE(_input_names_dict_str.empty()); } + +TEST_F(circle_input_names_test, valid_names_softmax) +{ + // "SOFTMAX" should have single input: "logits" + auto names = get_input_names_of("SOFTMAX"); + ASSERT_EQ(names.size(), 1); + ASSERT_EQ(names[0], "logits"); +} + +TEST_F(circle_input_names_test, valid_names_conv2d) +{ + // "CONV_2D" should have three inputs: "input", "filter", "bias" + auto names = get_input_names_of("CONV_2D"); + ASSERT_EQ(names.size(), 3); + ASSERT_EQ(names[0], "input"); + ASSERT_EQ(names[1], "filter"); + ASSERT_EQ(names[2], "bias"); +} + +TEST_F(circle_input_names_test, not_exist_opname_NEG) +{ + // names of "NOT_EXIST_OP" should be empty + auto names = get_input_names_of("NOT_EXIST_OP"); + ASSERT_EQ(names.size(), 0); +} + +TEST_F(circle_input_names_test, lower_case_opname_NEG) +{ + // Upper case opname should be used + auto names = get_input_names_of("conv_2d"); + ASSERT_EQ(names.size(), 0); +} + +TEST_F(circle_input_names_test, out_of_bounds_NEG) +{ + auto names = get_input_names_of("CONV_2D"); + // names[3] should throw exception since it's out of bounds + EXPECT_ANY_THROW(names.at(3)); +}