Skip to content

Commit

Permalink
[compiler/circle-input-names] Introduce a new tool displaying input n…
Browse files Browse the repository at this point in the history
…ames (#14472)

The new tool 'circle_input_names' displays the all input names of
each operator in JSON format.

ONE-DCO-1.0-Signed-off-by: Jonghwa Lee <[email protected]>
  • Loading branch information
batcheu authored Dec 18, 2024
1 parent 17081b4 commit 5887d4c
Show file tree
Hide file tree
Showing 5 changed files with 386 additions and 0 deletions.
24 changes: 24 additions & 0 deletions compiler/circle-input-names/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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}")
18 changes: 18 additions & 0 deletions compiler/circle-input-names/README.md
Original file line number Diff line number Diff line change
@@ -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" ]
}
```
2 changes: 2 additions & 0 deletions compiler/circle-input-names/requires.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require("crew")
require("luci")
194 changes: 194 additions & 0 deletions compiler/circle-input-names/src/circle-input-names.cpp
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <crew/PConfigJson.h>
#include <luci/CircleNodeSummaryBuilders.h>

using namespace luci;

// clang-format off
// For Variadic Arity Nodes which have multiple inputs.
template <typename T> struct is_VariadicArity : std::false_type {};
template <> struct is_VariadicArity<CircleConcatenation> : std::true_type {};
template <> struct is_VariadicArity<CircleCustom> : std::true_type {};
template <> struct is_VariadicArity<CirclePack> : std::true_type {};
template <> struct is_VariadicArity<CircleAddN> : std::true_type {};
template <> struct is_VariadicArity<CircleIf> : std::true_type {};
template <> struct is_VariadicArity<CircleWhile> : std::true_type {};

// For Variadic Outputs Nodes which have multiple outputs.
template <typename T> struct is_VariadicOut : std::false_type {};
template <> struct is_VariadicOut<CircleCustom> : std::true_type{};
template <> struct is_VariadicOut<CircleIf> : std::true_type {};
template <> struct is_VariadicOut<CircleWhile> : std::true_type {};
// clang-format on

// For Circle Nodes which have variadic arity and variadic outputs
template <typename CircleOp, typename std::enable_if_t<is_VariadicArity<CircleOp>::value &&
is_VariadicOut<CircleOp>::value> * = nullptr>
auto CircleNodeCreator()
{
return CircleOp(1, 1);
}

// For Circle Nodes which have variadic arity but single output
template <typename CircleOp,
typename std::enable_if_t<is_VariadicArity<CircleOp>::value &&
!is_VariadicOut<CircleOp>::value> * = nullptr>
auto CircleNodeCreator()
{
return CircleOp(1);
}

// For Circle Nodes which have fixed arity
template <typename CircleOp,
typename std::enable_if_t<!is_VariadicArity<CircleOp>::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<CircleNodeMixin<CircleNodeTrait::FusedActFunc> *>(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<CLASS *>(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<CLASS *>(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<std::string> 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<CIRCLE_OP>(); \
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 <luci/IR/CircleNodes.lst>
#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;
}
148 changes: 148 additions & 0 deletions compiler/circle-input-names/src/circle-input-names.test.cpp
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

#include <cstdlib>
#include <cstring>
#include <fstream>
#include <vector>
#include <regex>

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<std::string> 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<std::string> circle_input_names_test::get_input_names_of(std::string op)
{
std::vector<std::string> 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));
}

0 comments on commit 5887d4c

Please sign in to comment.