CPP implementation of L2Norm and LRN ops#1157
Conversation
|
@sxjscience can you please do a round of code review? |
7ceb221 to
d351033
Compare
|
I have added nnvm frontend support for lrn and l2norm ops. Please review |
2070956 to
eca71e4
Compare
|
@tqchen, I am facing a build error only in the i386 environment with an unrelated source code. Could you please help for a clean build to see whether it is an environment issue? |
|
@kazum @merrymercy can you help review this PR? |
eca71e4 to
2e11f32
Compare
| DMLC_DECLARE_FIELD(axis) | ||
| .describe("input data layout channel axis"); | ||
| DMLC_DECLARE_FIELD(alpha) | ||
| .describe("alpha constant."); |
| DMLC_DECLARE_FIELD(alpha) | ||
| .describe("alpha constant."); | ||
| DMLC_DECLARE_FIELD(beta) | ||
| .describe("beta constant."); |
| DMLC_DECLARE_FIELD(beta) | ||
| .describe("beta constant."); | ||
| DMLC_DECLARE_FIELD(bias) | ||
| .describe("bias constant."); |
|
|
||
| import topi | ||
| import tvm | ||
| import topi |
There was a problem hiding this comment.
Lint is suggesting this change.
| offset to avoid dividing by 0. constant value | ||
|
|
||
| alpha : float | ||
| contant valie |
| auto lrn = outs[0]; | ||
| auto sqr_sum_up = lrn->op->InputTensors()[1]; | ||
| auto sqr_sum = sqr_sum_up->op->InputTensors()[0]; | ||
| auto set_pad = sqr_sum->op->InputTensors()[0]; |
There was a problem hiding this comment.
I think it's better to use concrete types:
https://github.com/dmlc/tvm/blob/master/docs/contribute/code_guide.rst#c-code-styles
| * Copyright (c) 2017 by Contributors | ||
| * \brief NN op constructions | ||
| * \file topi/nn.h | ||
| * \file |
| using namespace tvm; | ||
|
|
||
| /*! | ||
| * \brief L2 normalization inference operator |
| std::string tag = kBroadcast) { | ||
| CHECK_EQ(data->shape.size(), 4) << "LRN requires 4-D input"; | ||
| assert(size % 2 == 1); | ||
| assert(axis == 1 || axis == 3); |
There was a problem hiding this comment.
I think we should use CHECK_* macros here because assert() can be compiled out when we define NDEBUG.
2e11f32 to
776989a
Compare
|
@tqchen I am facing the same error again |
| } | ||
| }; | ||
|
|
||
| struct LrnParam : public dmlc::Parameter<LrnParam> { |
| DMLC_REGISTER_PARAMETER(L2normParam); | ||
|
|
||
| inline bool L2normInferShape(const nnvm::NodeAttrs& attrs, | ||
| std::vector<TShape>* in_shape, |
| return true; | ||
| } | ||
|
|
||
| NNVM_REGISTER_OP(l2norm) |
There was a problem hiding this comment.
l2norm not a typical operator, a typical version os numpy.lingalg.norm, so I would recommend we do not add registration and support proper norm in a separate PR
There was a problem hiding this comment.
Okay, it is good idea to have generalized norm operation. I will remove L2Norm from this PR and will raise another PR to have generalized norm operation.
There was a problem hiding this comment.
Sorry, I was confused by the name of the API, the current API is l2_normalize which performs the normalization, instead of calculating the norm.
Let us make the name clear in both TOPI and nnvm.
c.f. related tensorflow API https://www.tensorflow.org/api_docs/python/tf/nn/l2_normalize
There was a problem hiding this comment.
L2 norm operation can be used to perform l2 normalization. If we are planning to add generalized norm op, we can use the same to compute l2 normalization.
| sqr_sum[i, j, k, l] = sum(a_np[i, j, k, sum_start:sum_end] * \ | ||
| a_np[i, j, k, sum_start:sum_end]) | ||
|
|
||
| for i in range(axis0): |
There was a problem hiding this comment.
@tqchen , I have tried using broadcast operations further on this changes, but here the sum is doing based on window size and the window move across the axis. could you please elaborate your suggestion
There was a problem hiding this comment.
OK, never mind, it is fine to keep this one as for loop
| * | ||
| * \return A schedule for the given ops. | ||
| */ | ||
| inline Schedule schedule_l2norm(const Target &target, const Array<Tensor>& outs) { |
There was a problem hiding this comment.
remove L2 norm for now and can do that in separate PR, support norm later.
| inputs = [('x', (1, 3, 28, 28), x)] | ||
| helper(y, inputs, dtype, forward) | ||
|
|
||
| def verify_lrn(n, c, h, w, size, axis, bias, alpha, beta): |
There was a problem hiding this comment.
def verify_lrn(ishape, size, axis, bias, alpha, beta) and using ishape instead of dshape looks simpler.
| offset to avoid dividing by 0. constant value | ||
|
|
||
| alpha : float | ||
| contant value |
| radius = size // 2 | ||
| sqr_sum = np.zeros(shape=a_np.shape).astype(a_np.dtype) | ||
| sqr_sum_up = np.zeros(shape=a_np.shape).astype(a_np.dtype) | ||
| lrn_out = np.zeros(shape=a_np.shape).astype(a_np.dtype) |
| out_np = lrn_python(x_np, size, axis, bias, alpha, beta) | ||
| np.testing.assert_allclose(out.asnumpy(), out_np, atol=1e-5, rtol=1e-5) | ||
|
|
||
| def verify_l2norm(batch, channel, height, width, eps, axis): |
There was a problem hiding this comment.
def verify_l2norm(ishape, eps, axis) looks simpler.
| std::string tag = kBroadcast) { | ||
| CHECK_EQ(data->shape.size(), 4) << "LRN requires 4-D input"; | ||
| CHECK_EQ(size % 2, 1) << "size should be odd number"; | ||
| CHECK_EQ((axis - 1) && (axis - 3), 0) << "axis should be 1 or 3 for NCHW and NHWC"; |
There was a problem hiding this comment.
CHECK(axis == 1 || axis == 3)
| l2norm_out : np.ndarray | ||
| 4-D with shape [batch, out_channel, out_height, out_width] | ||
| """ | ||
| batch, axis1, axis2, axis3 = a_np.shape |
There was a problem hiding this comment.
batch = a_np.shape[0] looks simpler? We don't need axis[1-3].
| batch, axis1, axis2, axis3 = a_np.shape | ||
| sqr_sum = np.zeros(shape=(batch,)).astype(a_np.dtype) | ||
| sqrt_sum = np.zeros(shape=(batch,)).astype(a_np.dtype) | ||
| l2norm_out = np.zeros(shape=a_np.shape).astype(a_np.dtype) |
| sqrt_sum = np.sqrt(np.maximum(np.broadcast_to(sqr_sum, a_np.shape), eps)) | ||
| return np.divide(a_np, sqrt_sum) | ||
|
|
||
| def verify_l2norm(n, c, h, w, eps, axis=None): |
There was a problem hiding this comment.
I'd suggest def verify_l2norm(shape, eps, axis=None).
| @@ -0,0 +1,101 @@ | |||
| """Test code for LRN""" | |||
| import os | |||
| radius = size // 2 | ||
| sqr_sum = np.zeros(shape=a_np.shape).astype(a_np.dtype) | ||
| sqr_sum_up = np.zeros(shape=a_np.shape).astype(a_np.dtype) | ||
| lrn_out = np.zeros(shape=a_np.shape).astype(a_np.dtype) |
| return true; | ||
| } | ||
|
|
||
| NNVM_REGISTER_OP(l2norm) |
There was a problem hiding this comment.
Sorry, I was confused by the name of the API, the current API is l2_normalize which performs the normalization, instead of calculating the norm.
Let us make the name clear in both TOPI and nnvm.
c.f. related tensorflow API https://www.tensorflow.org/api_docs/python/tf/nn/l2_normalize
|
OK, please fix the comments, remove l2norm or add l2_normalize to this PR and let us aim to prioritize and bring this in. This code review has been hanging for a bit long |
|
@PariksheetPinjari909 can you act on the comments? Rebase is needed |
776989a to
fa22f79
Compare
|
@tqchen Thanks, i was about to commit, then i saw rebase is needed. I have made the l2normalize naming to avoid future confusion. Pls review. |
|
@kazum Pls review |
| .set_num_outputs(1) | ||
| .set_attr<FInferShape>("FInferShape", L2normalizeInferShape) | ||
| .set_attr<FInferType>("FInferType", ElemwiseType<1, 1>) | ||
| .set_support_level(1); |
There was a problem hiding this comment.
We need FCorrectLayout attribute to for correct layout pass.
|
|
||
| reg.register_pattern("lrn", OpPattern.OUT_ELEMWISE_FUSABLE) | ||
|
|
||
| @reg.register_compute("l2normalize") |
There was a problem hiding this comment.
use l2_normalize(with underscore), to be consistent with tensorflow API
| with tvm.target.create(target): | ||
| return topi.generic.schedule_lrn(outs) | ||
|
|
||
| reg.register_pattern("lrn", OpPattern.OUT_ELEMWISE_FUSABLE) |
There was a problem hiding this comment.
can we confirm if lrn is OUT_ELEMWISE_FUSABLE. We need to add a testcase, lrn followed by relu, and confirm if the test pass on GPU
| * | ||
| * \return A Tensor whose op member is the l2 normalization operation | ||
| */ | ||
| inline Tensor l2normalize_instance(const Tensor& data, |
There was a problem hiding this comment.
what does instance mean in here?
There was a problem hiding this comment.
Instance name was given with respect to mxnet l2_normalize function, but now we are supporting l2_normalize in all axes so no need to keep the instance name. I will remove it. Thanks for pointing out.
| with tvm.target.create(target): | ||
| return topi.generic.schedule_l2normalize(outs) | ||
|
|
||
| reg.register_pattern("l2normalize", OpPattern.OUT_ELEMWISE_FUSABLE) |
There was a problem hiding this comment.
if we want to mark it as OUT_ELEMWISE_FUSABLE, confirm with a testcase of op + elemwise operator so that it generate testcase for used ops
| l2normalize_out : np.ndarray | ||
| 4-D with shape [batch, out_channel, out_height, out_width] | ||
| """ | ||
| batch = a_np.shape[0] |
| sqr_sum = np.zeros(shape=(batch,)).astype(a_np.dtype) | ||
| sqrt_sum = np.zeros(shape=(batch,)).astype(a_np.dtype) | ||
| l2norm_out = np.zeros(shape=a_np.shape).astype(a_np.dtype) | ||
| batch = a_np.shape[0] |
| l2normalize_out : np.ndarray | ||
| 4-D with shape [batch, out_channel, out_height, out_width] | ||
| """ | ||
| batch = a_np.shape[0] |
f60cb88 to
91b6025
Compare
|
|
||
| NNVM_REGISTER_OP(lrn) | ||
| .describe(R"code(LRN layer)code" NNVM_ADD_FILELINE) | ||
| .add_argument("data", "4D Tesndor", "Input data.") |
|
|
||
| NNVM_REGISTER_OP(l2_normalize) | ||
| .describe(R"code(L2NORMALIZE layer)code" NNVM_ADD_FILELINE) | ||
| .add_argument("data", "4D Tesndor", "Input data.") |
| axis0, axis1, axis2, axis3 = a_np.shape | ||
| radius = size // 2 | ||
| sqr_sum = np.zeros(shape=a_np.shape).astype(a_np.dtype) | ||
| def sum_dot_values(i, j, k, l): |
There was a problem hiding this comment.
from itertools import product
for i, j, k, l in product(*[range(_axis) for _axis in a_np.shape]):
and we can remove the nested loop below. I think this cleanup is matter of taste, though.
There was a problem hiding this comment.
Yes, it looks nicer now. Thanks
91b6025 to
e91e6d1
Compare
| dtype = "float32" | ||
| x_np = np.random.uniform(size=ishape).astype(dtype) | ||
|
|
||
| def l2_normalize_python(a_np, eps, axis=None): |
There was a problem hiding this comment.
move this function to topi.testing.
| dtype = "float32" | ||
| x_np = np.random.uniform(size=ishape).astype(dtype) | ||
|
|
||
| def lrn_python(a_np, size, axis, bias, alpha, beta): |
There was a problem hiding this comment.
move this function to topi.testing
|
Some final comments and should be approved from my side, await @kazum 's comment |
kazum
left a comment
There was a problem hiding this comment.
Looks good from my side, thanks!
|
@PariksheetPinjari909 please act on my final comments |
e91e6d1 to
c2ca9c2
Compare
|
@tqchen all reviews are handled now. Pls check. |
|
Thanks! This is merged! |
This PR has CPP implementation of LRN and L2Norm. It also redirect LRN and L2Norm python ops to CPP ops.