From 7f05707d0b6eb2399246ab955bc7bbe58dc28d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=91=EC=A2=85=EC=9B=90?= Date: Mon, 9 Sep 2024 16:51:28 +0900 Subject: [PATCH] [WIP][luci/service] Support dynamic shape inference for reshape This commit supports dynamic shape inference for reshape operation ONE-DCO-1.0-Signed-off-by: Jongwon Yang --- .../luci/service/src/Nodes/CircleReshape.cpp | 143 ++++++++++-------- .../service/src/Nodes/CircleReshape.test.cpp | 19 +++ 2 files changed, 95 insertions(+), 67 deletions(-) diff --git a/compiler/luci/service/src/Nodes/CircleReshape.cpp b/compiler/luci/service/src/Nodes/CircleReshape.cpp index 080115bf2fe..9c6d473b37a 100644 --- a/compiler/luci/service/src/Nodes/CircleReshape.cpp +++ b/compiler/luci/service/src/Nodes/CircleReshape.cpp @@ -21,6 +21,7 @@ #include "CircleCloneNode.h" #include +#include namespace { @@ -66,11 +67,11 @@ namespace sinf { /** - * @note CircleReshape has new shape info in two places: 2nd input and attribute. - * This shape inference uses shape from input 'shape' node when it's constant. - * If not, shape will be from node itself. shape from attribute is not used. - * - * TODO Change this policy when not appropriate + * @note CircleReshape always has two inputs: tensor and shape. + * The shape input can be CircleOutputDummy, CircleConst, or CircleNode. + * If the shape input is CircleOutputDummy, the shape is inferred from the attribute. + * If the shape input is CircleConst, the shape is inferred from the constant. + * If the shape input is CircleNode, the shape is not inferred. */ loco::TensorShape Algorithm::visit(const luci::CircleReshape *node) { @@ -78,88 +79,96 @@ loco::TensorShape Algorithm::visit(const luci::CircleReshape *node) const loco::DataType S32 = loco::DataType::S32; - loco::TensorShape shape_by_input; + // CircleReshape node must have reshape/shape + if (node->shape() == nullptr) { - LUCI_ASSERT(node->shape(), "2nd input shape() should not be nullptr"); + INTERNAL_EXN("2nd input shape() should not be nullptr"); + } - // Only support node's shape() is CircleConst with S32 - // TODO support other node with other types - auto const_shape_node = dynamic_cast(node->shape()); - if (const_shape_node != nullptr) + bool should_infer = true; + loco::TensorShape output_shape; + { + // Check if shape() is CircleOutputDummy + auto dummy_input = dynamic_cast(node->shape()); + if (dummy_input != nullptr) { - LUCI_ASSERT(const_shape_node->dtype() == S32, "Only support int32 CircleConst"); - - shape_by_input.rank(const_shape_node->size()); + // Try to get shape from attribute + if (node->newShape()) + { + output_shape.rank(node->newShape()->rank()); - for (uint32_t axis = 0; axis < shape_by_input.rank(); ++axis) + for (uint32_t axis = 0; axis < output_shape.rank(); ++axis) + { + output_shape.dim(axis) = node->newShape()->dim(axis); + } + } + else { - shape_by_input.dim(axis) = const_shape_node->at(axis); + // Or, use shape from the node itself + output_shape = circle_shape(node); } } else { - // We use shape from the node itself - loco::TensorShape shape; - shape.rank(node->rank()); - for (uint32_t r = 0; r < node->rank(); ++r) + // If shape() is not CircleOutputDummy, it should be CircleConst or CircleNode + // Check if shape() is CircleConst + auto const_input = dynamic_cast(node->shape()); + if (const_input != nullptr) { - // Shape inference rules in this file did not consider unknown dimension. - // If some node has unknown dimension, 0 is inserted and wrong shape - // inference was done as a result. - // To fix this, new shape inference algorithm is being implemented. - // Until new inference algorithm is fully implemented, unknown dimension - // would be represented as 1 along with TFLite expression. - shape.dim(r) = node->dim(r).known() ? node->dim(r).value() : 1; - } - shape_by_input = shape; - } - } - - loco::TensorShape shape_by_attr; - { - shape_by_attr.rank(node->newShape()->rank()); + output_shape.rank(const_input->size()); - for (uint32_t axis = 0; axis < shape_by_attr.rank(); ++axis) - { - shape_by_attr.dim(axis) = node->newShape()->dim(axis); + for (uint32_t axis = 0; axis < output_shape.rank(); ++axis) + { + output_shape.dim(axis) = const_input->at(axis); + } + } + else + { + // If shape() is not CircleConst, it should be CircleNode + auto node_input = dynamic_cast(node->shape()); + if (node_input != nullptr) + { + output_shape.rank(node_input->dim(0).value()); + + for (uint32_t axis = 0; axis < output_shape.rank(); ++axis) + { + output_shape.dim(axis).unset(); + } + + should_infer = false; + } + } } } - if (!(shape_by_input == shape_by_attr)) + if (should_infer) { - INFO(l) << "CircleReshape: Two new shape information mismatched : " << std::endl; - INFO(l) << " shape_by_input : " << shape_by_input << std::endl; - INFO(l) << " shape_by_attr : " << shape_by_attr << std::endl; - } - - loco::TensorShape output_shape = shape_by_input; - - // One of the dimensions can have special value -1, meaning its actual value should be inferred. - const auto input = loco::must_cast(node->tensor()); - const auto input_shape = circle_shape(input); - uint32_t input_element_count = 1; - uint32_t output_element_count = 1; - uint32_t unknown_dim_index = UINT32_MAX; - for (uint32_t i = 0; i < input_shape.rank(); ++i) - input_element_count *= (input_shape.dim(i).known() ? input_shape.dim(i).value() : 1); - for (uint32_t dim_index = 0; dim_index < output_shape.rank(); ++dim_index) - { - const uint32_t dim_value = output_shape.dim(dim_index).value(); - if (static_cast(dim_value) == -1) + const auto input = loco::must_cast(node->tensor()); + const auto input_shape = circle_shape(input); + uint32_t input_element_count = 1; + uint32_t output_element_count = 1; + uint32_t unknown_dim_index = UINT32_MAX; + for (uint32_t i = 0; i < input_shape.rank(); ++i) + input_element_count *= (input_shape.dim(i).known() ? input_shape.dim(i).value() : 1); + for (uint32_t dim_index = 0; dim_index < output_shape.rank(); ++dim_index) { - LUCI_ASSERT(unknown_dim_index == UINT32_MAX, "More than one unknown dimension"); - unknown_dim_index = dim_index; + const uint32_t dim_value = output_shape.dim(dim_index).value(); + if (static_cast(dim_value) == -1) + { + LUCI_ASSERT(unknown_dim_index == UINT32_MAX, "More than one unknown dimension"); + unknown_dim_index = dim_index; + } + else + { + output_element_count *= dim_value; + } } - else + if (unknown_dim_index != UINT32_MAX) { - output_element_count *= dim_value; + output_shape.dim(unknown_dim_index) = input_element_count / output_element_count; } } - if (unknown_dim_index != UINT32_MAX) - { - output_shape.dim(unknown_dim_index) = input_element_count / output_element_count; - } - + return output_shape; } diff --git a/compiler/luci/service/src/Nodes/CircleReshape.test.cpp b/compiler/luci/service/src/Nodes/CircleReshape.test.cpp index 39ed4b049d8..1108acfabd2 100644 --- a/compiler/luci/service/src/Nodes/CircleReshape.test.cpp +++ b/compiler/luci/service/src/Nodes/CircleReshape.test.cpp @@ -154,3 +154,22 @@ TEST(ShapeRuleTest, reshape_input_shape_undefined_NEG) ASSERT_FALSE(shape_inf_rule.infer(node_reshape, output_shape)); } + +TEST(ShapeRuleTest, reshape_no_2nd_input_NEG) +{ + auto g = loco::make_graph(); + auto node_reshape = g->nodes()->create(); + auto tensor_input = g->nodes()->create(); + + tensor_input->dtype(loco::DataType::S32); + tensor_input->shape({2, 3, 4}); + tensor_input->shape_status(luci::ShapeStatus::VALID); + + node_reshape->tensor(tensor_input); + node_reshape->shape(nullptr); + + loco::TensorShape output_shape; + luci::sinf::Rule shape_inf_rule; + + ASSERT_ANY_THROW(shape_inf_rule.infer(node_reshape, output_shape)); +} \ No newline at end of file