Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[luci/service] Migrate shape inferences for Conv2D ops #14125

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ class Algorithm final : public luci::CircleNodeVisitor<loco::TensorShape>
// loco::TensorShape visit(const luci::CircleCeil *node) final;
loco::TensorShape visit(const luci::CircleConcatenation *node) final;
// loco::TensorShape visit(const luci::CircleConst *node) final;
// loco::TensorShape visit(const luci::CircleConv2D *node) final;
loco::TensorShape visit(const luci::CircleConv2D *node) final;
// loco::TensorShape visit(const luci::CircleCos *node) final;
// loco::TensorShape visit(const luci::CircleCustom *node) final;
// loco::TensorShape visit(const luci::CircleDepthToSpace *node) final;
// loco::TensorShape visit(const luci::CircleDepthwiseConv2D *node) final;
loco::TensorShape visit(const luci::CircleDepthwiseConv2D *node) final;
zetwhite marked this conversation as resolved.
Show resolved Hide resolved
// loco::TensorShape visit(const luci::CircleDequantize *node) final;
loco::TensorShape visit(const luci::CircleDiv *node) final;
// loco::TensorShape visit(const luci::CircleElu *node) final;
Expand Down
108 changes: 0 additions & 108 deletions compiler/luci/service/src/CircleShapeInferenceRule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,57 +411,6 @@ loco::NodeShape infer_batch_to_space_nd(const luci::CircleBatchToSpaceND *node)
return loco::NodeShape{shape_output};
}

struct OutputSize
{
uint32_t height = 0;
uint32_t width = 0;
};

template <class Conv2DType> OutputSize infer_conv2d_type(const Conv2DType *node)
{
auto ifm_shape = luci::shape_get(node->input()).template as<loco::TensorShape>();
auto ker_shape = luci::shape_get(node->filter()).template as<loco::TensorShape>();
assert(ifm_shape.rank() == 4);
assert(ker_shape.rank() == 4);
assert(ifm_shape.dim(1).known());
assert(ifm_shape.dim(2).known());
assert(ker_shape.dim(1).known());
assert(ker_shape.dim(2).known());

uint32_t input_height = ifm_shape.dim(1).value();
uint32_t input_width = ifm_shape.dim(2).value();
uint32_t stride_height = node->stride()->h();
uint32_t stride_width = node->stride()->w();
uint32_t ker_height = ker_shape.dim(1).value();
uint32_t ker_width = ker_shape.dim(2).value();
uint32_t dilation_height = node->dilation()->h();
uint32_t dilation_width = node->dilation()->w();
uint32_t effective_ker_height = dilation_height * (ker_height - 1) + 1;
uint32_t effective_ker_width = dilation_width * (ker_width - 1) + 1;

uint32_t output_height = 0;
uint32_t output_width = 0;

if (node->padding() == luci::Padding::VALID)
{
LUCI_ASSERT(input_height + stride_height > effective_ker_height, "Invalid shape");
LUCI_ASSERT(input_width + stride_width > effective_ker_width, "Invalid shape");
output_height = (input_height + stride_height - effective_ker_height) / stride_height;
output_width = (input_width + stride_width - effective_ker_width) / stride_width;
}
else if (node->padding() == luci::Padding::SAME)
{
output_height = (input_height + stride_height - 1) / stride_height;
output_width = (input_width + stride_width - 1) / stride_width;
}
else
LUCI_ASSERT(false, "Wrong padding type");

OutputSize os{output_height, output_width};

return os;
}

loco::NodeShape infer_broadcast_to(const luci::CircleBroadcastTo *node)
{
const loco::DataType S32 = loco::DataType::S32;
Expand Down Expand Up @@ -492,34 +441,6 @@ loco::NodeShape infer_broadcast_to(const luci::CircleBroadcastTo *node)
return loco::NodeShape{shape_by_input};
}

loco::NodeShape infer_conv2d(const luci::CircleConv2D *node)
{
LOGGER(l);

auto ifm_shape = luci::shape_get(node->input()).as<loco::TensorShape>(); // in NHWC
auto ker_shape = luci::shape_get(node->filter()).as<loco::TensorShape>(); // in OHWI

assert(ifm_shape.rank() == 4);
assert(ker_shape.rank() == 4);
assert(ifm_shape.dim(3) == ker_shape.dim(3));

auto os = infer_conv2d_type(node);

loco::TensorShape ofm_shape;
ofm_shape.rank(4);
ofm_shape.dim(0) = ifm_shape.dim(0);
ofm_shape.dim(1) = os.height;
ofm_shape.dim(2) = os.width;
ofm_shape.dim(3) = ker_shape.dim(0);

INFO(l) << "[luci] CircleConv2D ShapeInf ifm(" << ifm_shape.rank() << ") ker(" << ker_shape.rank()
<< ") output(" << ofm_shape.dim(0).value() << "," << ofm_shape.dim(1).value() << ","
<< ofm_shape.dim(2).value() << "," << ofm_shape.dim(3).value() << ") " << node->name()
<< std::endl;

return loco::NodeShape{ofm_shape};
}

loco::NodeShape infer_depth_to_space(const luci::CircleDepthToSpace *node)
{
auto input_shape = luci::shape_get(node->input()).as<loco::TensorShape>();
Expand Down Expand Up @@ -552,28 +473,6 @@ loco::NodeShape infer_depth_to_space(const luci::CircleDepthToSpace *node)
return loco::NodeShape{output_shape};
}

loco::NodeShape infer_depthwise_conv2d(const luci::CircleDepthwiseConv2D *node)
{
auto ifm_shape = luci::shape_get(node->input()).as<loco::TensorShape>(); // in NHWC
auto ker_shape = luci::shape_get(node->filter()).as<loco::TensorShape>(); // in 1 H W CM

assert(ifm_shape.rank() == 4);
assert(ker_shape.rank() == 4);
assert(ker_shape.dim(0).value() == 1);
assert(ifm_shape.dim(3).value() * node->depthMultiplier() == ker_shape.dim(3).value());

auto os = infer_conv2d_type(node);

loco::TensorShape ofm_shape;
ofm_shape.rank(4);
ofm_shape.dim(0) = ifm_shape.dim(0);
ofm_shape.dim(1) = os.height;
ofm_shape.dim(2) = os.width;
ofm_shape.dim(3) = ker_shape.dim(3);

return loco::NodeShape{ofm_shape};
}

loco::NodeShape infer_expand_dims(const luci::CircleExpandDims *node)
{
const loco::DataType S32 = loco::DataType::S32;
Expand Down Expand Up @@ -1815,8 +1714,6 @@ class ShapeInferenceAlgorithm final : public luci::CircleNodeVisitor<loco::NodeS

loco::NodeShape visit(const luci::CircleConst *node) final { return use_own(node); }

loco::NodeShape visit(const luci::CircleConv2D *node) final { return infer_conv2d(node); }

loco::NodeShape visit(const luci::CircleCos *node) final { return use_x(node); }

loco::NodeShape visit(const luci::CircleCumSum *node) final { return use_input(node); }
Expand All @@ -1830,11 +1727,6 @@ class ShapeInferenceAlgorithm final : public luci::CircleNodeVisitor<loco::NodeS
return infer_depth_to_space(node);
}

loco::NodeShape visit(const luci::CircleDepthwiseConv2D *node) final
{
return infer_depthwise_conv2d(node);
}

loco::NodeShape visit(const luci::CircleDequantize *node) final
{
const auto input_shape = luci::shape_get(node->input()).as<loco::TensorShape>();
Expand Down
79 changes: 79 additions & 0 deletions compiler/luci/service/src/HelperConv2Ds.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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 "Check.h"
#include "CircleShapeInferenceHelper.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "CircleShapeInferenceHelper.h"

Is this header used..?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed that.
Since I just migrated them, it doesn't need to be included this time.
It should be included in the next PR.
Thank you for checking.


namespace luci
{

namespace sinf
{

struct OutputSize
{
uint32_t height = 0;
uint32_t width = 0;
};

template <class Conv2DType> OutputSize infer_conv2d_type(const Conv2DType *node)
{
auto ifm_shape = luci::shape_get(node->input()).template as<loco::TensorShape>();
auto ker_shape = luci::shape_get(node->filter()).template as<loco::TensorShape>();
assert(ifm_shape.rank() == 4);
assert(ker_shape.rank() == 4);
assert(ifm_shape.dim(1).known());
assert(ifm_shape.dim(2).known());
assert(ker_shape.dim(1).known());
assert(ker_shape.dim(2).known());

uint32_t input_height = ifm_shape.dim(1).value();
uint32_t input_width = ifm_shape.dim(2).value();
uint32_t stride_height = node->stride()->h();
uint32_t stride_width = node->stride()->w();
uint32_t ker_height = ker_shape.dim(1).value();
uint32_t ker_width = ker_shape.dim(2).value();
uint32_t dilation_height = node->dilation()->h();
uint32_t dilation_width = node->dilation()->w();
uint32_t effective_ker_height = dilation_height * (ker_height - 1) + 1;
uint32_t effective_ker_width = dilation_width * (ker_width - 1) + 1;

uint32_t output_height = 0;
uint32_t output_width = 0;

if (node->padding() == luci::Padding::VALID)
{
LUCI_ASSERT(input_height + stride_height > effective_ker_height, "Invalid shape");
LUCI_ASSERT(input_width + stride_width > effective_ker_width, "Invalid shape");
output_height = (input_height + stride_height - effective_ker_height) / stride_height;
output_width = (input_width + stride_width - effective_ker_width) / stride_width;
}
else if (node->padding() == luci::Padding::SAME)
{
output_height = (input_height + stride_height - 1) / stride_height;
output_width = (input_width + stride_width - 1) / stride_width;
}
else
LUCI_ASSERT(false, "Wrong padding type");

OutputSize os{output_height, output_width};

return os;
}

} // namespace sinf

} // namespace luci
39 changes: 39 additions & 0 deletions compiler/luci/service/src/Nodes/CircleConv2D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
* limitations under the License.
*/

#include "HelperConv2Ds.h"
#include "luci/Service/CircleShapeInference.h"

#include "CircleCloneNode.h"
#include "CircleShapeInferenceHelper.h"

#include <luci/Log.h>
Comment on lines +17 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought there was a rule for header including order.
I'm not sure if it follows the rule or not. 🤔

I guess there was a comment about the rule, but I couldn't remember where it is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+) Ah, here it is - #13784 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header including order have to be changed like this :

Suggested change
#include "HelperConv2Ds.h"
#include "luci/Service/CircleShapeInference.h"
#include "CircleCloneNode.h"
#include "CircleShapeInferenceHelper.h"
#include <luci/Log.h>
#include "luci/Service/CircleShapeInference.h"
#include "CircleCloneNode.h"
#include "CircleShapeInferenceHelper.h"
#include "HelperConv2Ds.h"
#include <luci/Log.h>


namespace luci
{
Expand All @@ -39,4 +45,37 @@ luci::CircleNode *CloneNodeLet<CN::ABC>::visit(const luci::CircleConv2D *node)
return cloned;
}

namespace sinf
{

loco::TensorShape Algorithm::visit(const luci::CircleConv2D *node)
{
LOGGER(l);

auto ifm_shape = luci::shape_get(node->input()).as<loco::TensorShape>(); // in NHWC
auto ker_shape = luci::shape_get(node->filter()).as<loco::TensorShape>(); // in OHWI

assert(ifm_shape.rank() == 4);
assert(ker_shape.rank() == 4);
assert(ifm_shape.dim(3) == ker_shape.dim(3));

auto os = infer_conv2d_type(node);

loco::TensorShape ofm_shape;
ofm_shape.rank(4);
ofm_shape.dim(0) = ifm_shape.dim(0);
ofm_shape.dim(1) = os.height;
ofm_shape.dim(2) = os.width;
ofm_shape.dim(3) = ker_shape.dim(0);

INFO(l) << "[luci] CircleConv2D ShapeInf ifm(" << ifm_shape.rank() << ") ker(" << ker_shape.rank()
<< ") output(" << ofm_shape.dim(0).value() << "," << ofm_shape.dim(1).value() << ","
<< ofm_shape.dim(2).value() << "," << ofm_shape.dim(3).value() << ") " << node->name()
<< std::endl;

return ofm_shape;
}

} // namespace sinf

} // namespace luci
31 changes: 31 additions & 0 deletions compiler/luci/service/src/Nodes/CircleDepthwiseConv2D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
* limitations under the License.
*/

#include "HelperConv2Ds.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional)

How about renaming a header file to Conv2DHelepr.h
I found out that there are more headers named *.Helper.h than Helper*.h.
So I thought the name *Helper.h might be good for consistency.

ONE/compiler [$?] …
➜ find -name '*Helper.h' | wc -l
36

ONE/compiler [$?] …
➜ find -name 'Helper*.h' | wc -l 
10

But still, it is a personal preference, So I don't want to force it at all.
Please feel free to make your own decision.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional 2)

It would be also good to move the infer_conv2d_type helper function to the CircleShapeInferenceHelper.h header file directly, for the reusability of helper functions.

As I know, because it is a template function, it is okay that its definition can be located in the header file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I know, because it is a template function, it is okay that its definition can be located in the header file.

Ah, there was a history about HelperPads introduced. If so, your suggestion looks good for me :)

#include "luci/Service/CircleShapeInference.h"

#include "CircleCloneNode.h"
#include "CircleShapeInferenceHelper.h"

namespace luci
{
Expand All @@ -40,4 +44,31 @@ luci::CircleNode *CloneNodeLet<CN::DEF>::visit(const luci::CircleDepthwiseConv2D
return cloned;
}

namespace sinf
{

loco::TensorShape Algorithm::visit(const luci::CircleDepthwiseConv2D *node)
{
auto ifm_shape = luci::shape_get(node->input()).as<loco::TensorShape>(); // in NHWC
auto ker_shape = luci::shape_get(node->filter()).as<loco::TensorShape>(); // in 1 H W CM

assert(ifm_shape.rank() == 4);
assert(ker_shape.rank() == 4);
assert(ker_shape.dim(0).value() == 1);
assert(ifm_shape.dim(3).value() * node->depthMultiplier() == ker_shape.dim(3).value());

auto os = infer_conv2d_type(node);

loco::TensorShape ofm_shape;
ofm_shape.rank(4);
ofm_shape.dim(0) = ifm_shape.dim(0);
ofm_shape.dim(1) = os.height;
ofm_shape.dim(2) = os.width;
ofm_shape.dim(3) = ker_shape.dim(3);

return ofm_shape;
}

} // namespace sinf

} // namespace luci