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

Add a padding operator. #95

Merged
merged 11 commits into from
Jul 17, 2021
Merged

Add a padding operator. #95

merged 11 commits into from
Jul 17, 2021

Conversation

mrbeann
Copy link
Contributor

@mrbeann mrbeann commented Jul 12, 2021

The implemented operator supports padding in all dimensions and asymmetric padding. And it is implemented in software only, no accelerated version is available. The code is tested in both CPP and python API. In the CPP test, I test it with different input sizes (2D, 4D) and different padding patterns (asymmetric, symmetric). The python API testing is mainly focused on it can work in a graph.

Example Python code.

      # consider input_tensor is a 4D tensor, we pad the last 2 dimension with 1
      out = array_ops.padding(input_tensor, [0, 0, 0, 0, 1, 1, 1, 1], "padding")
      # consider input_tensor is a 4D tensor, we pad the 2nd dimension with symmetric 
      # padding with size 1 and the 3rd dimension with asymmetric padiing of size 1 and 2.
      out = array_ops.padding(input_tensor, [0, 0, 0, 0, 1, 1, 1, 2], "padding")

Fixes issue #94.

@yaoyuannnn
Copy link
Member

@mrbeann Is this PR ready for code review? You can add either Sam or me (or both) as reviewers.

@mrbeann
Copy link
Contributor Author

mrbeann commented Jul 13, 2021

I think this is ready for review. But it seems I do not have permission. Can you or Sam review it? @xyzsam @yaoyuannnn

@yaoyuannnn
Copy link
Member

I think this is ready for review. But it seems I do not have permission. Can you or Sam review it? @xyzsam @yaoyuannnn

You should be able to add reviewers since you already created the PR.

@yaoyuannnn
Copy link
Member

Also can you resolve the issue in the CI build?

@mrbeann
Copy link
Contributor Author

mrbeann commented Jul 13, 2021

@yaoyuannnn Hi Yao, I resolve the CI problem. But add a reviewer can only be done for the owner of this repo according to the following answer,
https://stackoverflow.com/questions/32262295/adding-a-reviewer-to-a-github-pull-request

@yaoyuannnn
Copy link
Member

@yaoyuannnn Hi Yao, I resolve the CI problem. But add a reviewer can only be done for the owner of this repo according to the following answer,
https://stackoverflow.com/questions/32262295/adding-a-reviewer-to-a-github-pull-request

Thanks! That's interesting. In GitLab, the author can select reviewers so I thought GitHub would be the same. Maybe assignee should work.

I will review this PR tonight. Thanks for contributing to the SMAUG project :)

.gitignore Outdated
@@ -0,0 +1,3 @@
build/
experiments/
smaug/operators/padding_op_test.h
Copy link
Member

Choose a reason for hiding this comment

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

Why putting this file here?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, please remove this file from this PR. You are welcome to send a separate PR with a .gitignore if you like, but if so, it should only contain build products, not other code (like padding_op_test.h).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mainly to facilitate my development. But I think adding the initial two lines would be beneficial to this repo (the third line is meaningless now). But I can delete the file if you think it doesn't make sense.

Copy link
Member

Choose a reason for hiding this comment

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

See #96

@@ -1,38 +1,39 @@
#include "smaug/core/backend.h"
#include "smaug/operators/batch_norm_op.h"
#include "smaug/operators/concat_op.h"
Copy link
Member

Choose a reason for hiding this comment

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

Code rearrangement generally is confusing :) Moving forward, please move this to a separate PR.

Copy link
Member

Choose a reason for hiding this comment

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

I definitely appreciate you sorting all the includes in alphabetical order! It's certainly cleaner than before. But yes, refactoring and code cleanups should be done separately if it's not related to the main PR purpose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll fix this

const unsigned kEltwiseOpHw = 0x0003;
const unsigned kBatchNormHw = 0x0004;
const unsigned kPoolingHw = 0x0005;
const unsigned kConvolutionHw = 0x0001; // 0x0001;
Copy link
Member

Choose a reason for hiding this comment

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

I can't remember exactly, but we do want these accelerator blocks to be traced with different accelerator IDs. @xyzsam for his perspective.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, leave this unchanged here; there is an argument for both sides but regardless, it should not be done in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is based on the advice @xyzsam gives for another problem. But it is not relevant to this PR. I'll delete it.

@@ -1,56 +1,57 @@
#include <iostream>
Copy link
Member

Choose a reason for hiding this comment

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

Again, it'd better to put the code rearrangement into a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll fix this.

}

/** Set the number of padders of the Tensor along each dimension. */
void setPadder(const int& val) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not make this support different padding size on different dimensions like TensorFlow (https://www.tensorflow.org/api_docs/python/tf/keras/layers/ZeroPadding2D)? Judging from the implementation, I think this is for 2D input tensor? If so, it's better to rename it to Padding2DOp.

Copy link
Member

Choose a reason for hiding this comment

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

I think it would be relatively straightforward to make this support tensors with any number of dimensions and arbitrary padding size along all of them, so we should do that instead. The copyTensorRegion API should work regardless.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. I'll rename the operation.

namespace smaug {

/** \ingroup Operators
* \brief Pad a given tensor in different dimension.
Copy link
Member

Choose a reason for hiding this comment

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

I think this is meant to pad on H and W dimensions. Let's make this more explicit.

Copy link
Member

Choose a reason for hiding this comment

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

Also, mention that the padding on either side of all dimensions is assumed to be equal (e.g. 1 element on both left and right sides).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay!

int getPadder() { return padder; }

void run() override {
Tensor* input = getInput(0);
Copy link
Member

Choose a reason for hiding this comment

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

clang-format?

std::vector<int> destOrigin;
if (input->getShape().getLayout() == DataLayout::NCHW){
destOrigin = std::vector<int>({0, 0, padder, padder});
}
Copy link
Member

Choose a reason for hiding this comment

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

clang-format

}
std::vector<int> srcOrigin = std::vector<int>({0, 0, 0, 0});
std::vector<int> regionSize = inputDims;
copyTensorRegion(output, input, destOrigin, srcOrigin, regionSize);
Copy link
Member

Choose a reason for hiding this comment

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

Glad this API gets used here :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, thanks for your efforts. Hope I use it correctly. :)

// Optional but recommended function to verify operator parameters.
bool validate() override {
if (padder < 0){
return false;
Copy link
Member

Choose a reason for hiding this comment

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

clang-format.

Copy link
Member

@xyzsam xyzsam left a comment

Choose a reason for hiding this comment

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

Thank you for your PR! A few high level comments:

  1. Run clang-format on your code. For all the files you changed, run clang-format -i -style=file filename.cpp.
  2. Move unrelated code out of this PR (refactoring, re-ordering includes, depthwise conv). You can put them in a separate PR.

I will add some more information about contributing code to the repo for future users. As you can see, you are the first external user to contribute to smaug. We really appreciate it :)

.gitignore Outdated
@@ -0,0 +1,3 @@
build/
experiments/
smaug/operators/padding_op_test.h
Copy link
Member

Choose a reason for hiding this comment

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

Yes, please remove this file from this PR. You are welcome to send a separate PR with a .gitignore if you like, but if so, it should only contain build products, not other code (like padding_op_test.h).

@@ -1,38 +1,39 @@
#include "smaug/core/backend.h"
#include "smaug/operators/batch_norm_op.h"
#include "smaug/operators/concat_op.h"
Copy link
Member

Choose a reason for hiding this comment

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

I definitely appreciate you sorting all the includes in alphabetical order! It's certainly cleaner than before. But yes, refactoring and code cleanups should be done separately if it's not related to the main PR purpose.

#include "smaug/operators/smv/smv_less_op.h"
#include "smaug/operators/smv/smv_greater_op.h"
#include "smaug/operators/smv/smv_tanh_op.h"
#include "smaug/operators/softmax_op.h"
Copy link
Member

Choose a reason for hiding this comment

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

(Also, if this was meant to sort the header includes, these three should not be at the bottom :] )

const unsigned kEltwiseOpHw = 0x0003;
const unsigned kBatchNormHw = 0x0004;
const unsigned kPoolingHw = 0x0005;
const unsigned kConvolutionHw = 0x0001; // 0x0001;
Copy link
Member

Choose a reason for hiding this comment

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

Yes, leave this unchanged here; there is an argument for both sides but regardless, it should not be done in this PR.

@@ -3,79 +3,80 @@ syntax = "proto3";
package smaug;

enum DataType {
UnknownDataType = 0;
Copy link
Member

Choose a reason for hiding this comment

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

+1

// set output size?
}

auto getPadder() { return padder; }
Copy link
Member

Choose a reason for hiding this comment

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

Two issues here:

  1. Avoid auto return types; it makes the API unnecessarily hard to read and understand. The reader should not need to look for where padder is defined to know what this returns.
  2. The name "padder" suggests that it is an object doing padding, when in fact it is just your padding size. So this API (and the class member) should be named "padding_size".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your advice.

}

/** Set the number of padders of the Tensor along each dimension. */
void setPadder(const int& val) {
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be relatively straightforward to make this support tensors with any number of dimensions and arbitrary padding size along all of them, so we should do that instead. The copyTensorRegion API should work regardless.

Tensor* input = getInput(0);
Tensor* output = getOutput(0);
int ndims = input->ndims();
std::vector<int> inputDims = input->getShape().dims();
Copy link
Member

Choose a reason for hiding this comment

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

All these vectors should be const ref since you don't need to modify them.

namespace smaug {

/** \ingroup Operators
* \brief Pad a given tensor in different dimension.
Copy link
Member

Choose a reason for hiding this comment

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

Also, mention that the padding on either side of all dimensions is assumed to be equal (e.g. 1 element on both left and right sides).

name: Name of the operator.

Returns:
A paded version of the input tensor.
Copy link
Member

Choose a reason for hiding this comment

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

padded

@mrbeann
Copy link
Contributor Author

mrbeann commented Jul 14, 2021

Hi, I Just fix all the problems. BTW some includes are sorted after using the clang-format. Please check if everything is okay now.

* ,dimk_backward>
*/
void setPaddingSize(RepeatedField<google::protobuf::int32> const& val) {
std::vector<double> paddingSize(val.begin(), val.end());
Copy link
Member

Choose a reason for hiding this comment

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

Why converting to double here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I change it to int.


void setPaddingSize(std::vector<int> const& val) { paddingSize = val; }

std::vector<int> getPaddingSize() { return paddingSize; }
Copy link
Member

Choose a reason for hiding this comment

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

This should be a const member function.

auto paddingOp =
new PaddingOp<ReferenceBackend>("padding", workspace());
paddingOp->setInput(input, 0);
paddingOp->setPaddingSize({ 0, 0, 0, 0, 1, 1, 1, 1 });
Copy link
Member

Choose a reason for hiding this comment

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

Can we have a test for asymmetric padding like padding size of 1 on the left and 2 on the right since it's supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I add it now.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks!


Args:
input_tensor: Input tensor.
padding_size: A list that contains number of values padded to each dimension.
Copy link
Member

Choose a reason for hiding this comment

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

I think this docstring needs to be more explicit: padding_size is in the format of {dim0_begin, dim0_end, dim1_begin, dim1_end, ...}.

* ,dimk_backward>
*/
void setPaddingSize(RepeatedField<google::protobuf::int32> const& val) {
std::vector<int> paddingSize(val.begin(), val.end());
Copy link
Member

Choose a reason for hiding this comment

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

Sorry I didn't make it clear in my comment. This defines a new variable. Can you copy from the repeated field like:
paddingSize.assign(val.begin(), val.end());


void setPaddingSize(std::vector<int> const& val) { paddingSize = val; }

const std::vector<int> getPaddingSize() { return paddingSize; }
Copy link
Member

Choose a reason for hiding this comment

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

I meant to make this a const member function, not a const return type:
std::vector<int> getPaddingSize() const { return paddingSize; }

auto paddingOp =
new PaddingOp<ReferenceBackend>("padding", workspace());
paddingOp->setInput(input, 0);
paddingOp->setPaddingSize({ 0, 0, 0, 0, 1, 1, 1, 1 });
Copy link
Member

Choose a reason for hiding this comment

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

Thanks!

Copy link
Member

@yaoyuannnn yaoyuannnn left a comment

Choose a reason for hiding this comment

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

Thanks!

Copy link
Member

@xyzsam xyzsam left a comment

Choose a reason for hiding this comment

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

Please update the PR description to include more detail about this operator. For example:

  1. Mention that the operator supports padding in all dimensions and asymmetric padding.
  2. Implemented in software only, no accelerated version available.
  3. Example Python code.
  4. How this was tested.

* The paddingSize is orgainized as <dim1_forward, dim1_backward, ...
* ,dimk_backward>
*/
void setPaddingSize(RepeatedField<google::protobuf::int32> const& val) {
Copy link
Member

Choose a reason for hiding this comment

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

This should be const RepeatedField&, not RepeatedField const&.

Also, what does "forward" and "backward" mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll update this.


int getPadder() { return padder; }
std::vector<int> getPaddingSize() const { return paddingSize; }
Copy link
Member

Choose a reason for hiding this comment

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

return const std::vector& to avoid making a copy of paddingSize.

Tensor* input = getInput(0);
Tensor* output = getOutput(0);
int ndims = input->ndims();
const std::vector<int> inputDims = input->getShape().dims();
Copy link
Member

Choose a reason for hiding this comment

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

both inputDims and outputDims should be const-ref.

output->fillData(vf.data(), vf.size());
std::vector<int> destOrigin, paddingBegin, srcOrigin;
for (int i = 0; i < ndims; i++) {
paddingBegin.push_back(paddingSize[2 * i]);
Copy link
Member

Choose a reason for hiding this comment

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

use paddingSize.at(2*i) instead; this will throw an exception if you go out of bounds, whereas paddingSize[] will attempt to add an element at that position.

paddingBegin.push_back(paddingSize[2 * i]);
srcOrigin.push_back(0);
}
destOrigin = std::vector<int>(paddingBegin);
Copy link
Member

Choose a reason for hiding this comment

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

There's no need to declare destOrigin earlier and then re-initialize it here - that causes the vector to be constructed twice. Just declare it here directly: std::vector<int> destOrigin = ....

dims[1] += 2*padder;
dims[2] += 2*padder;
for (int i = 0; i < ndims; i++) {
dims[i] += (paddingSize[2 * i] + paddingSize[2 * i + 1]);
}
TensorShape shape(
dims, input->getShape().getLayout(), Backend::Alignment);
Tensor* output = new Tensor(name, shape);
workspace->addTensor(output);
outputs.at(0) = output;
Copy link
Member

Choose a reason for hiding this comment

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

Same here - use enums instead of hardcoded constants.

return Operator::validate();
}

enum { kInputs, kNumInputs };
Copy link
Member

Choose a reason for hiding this comment

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

nit: kInput instead of kInputs (you only have one), and likewise for outputs.

std::vector<float> expected_output{
0, 0, 0, // input 0, chan 0, row -1
0, 0, 0, // input 0, chan 0, row 0
0, 0, 0, // input 0, chan 1, row 3
Copy link
Member

Choose a reason for hiding this comment

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

Do you mean row 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I modified it.

"""Construct a tensor by padding a given tensor.

Args:
input_tensor: Input tensor.
padder: A int value that represents the padding dimension
padding_size: A list in the format of {dim0_begin, dim0_end, dim1_begin, dim1_end, ...} that
Copy link
Member

Choose a reason for hiding this comment

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

Add a note that the order of dimensions must align with the data layout of input_tensor.

Also, ensure that this line doesn't exceed 80 characters in length.

else:
raise ValueError("Only support layout as NHWC or NCHW")
if len(padding_size) != 2 * len(src_dims):
raise ValueError("The padding_size's dimension must be two times as the input_tensor")
Copy link
Member

Choose a reason for hiding this comment

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

nit: "len(padding_size) should be 2x input_tensor.shape.dims" is clearer.

@mrbeann
Copy link
Contributor Author

mrbeann commented Jul 16, 2021

I just update the PR description. Please check if it is okay now. Thanks!

Copy link
Member

@xyzsam xyzsam left a comment

Choose a reason for hiding this comment

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

Almost done, just two more small fixes, then we can merge. Thank you for your patience and your contributions!!!

srcOrigin.push_back(0);
}
destOrigin = std::vector<int>(paddingBegin);
std::vector<int> destOrigin = std::vector<int>(paddingBegin);
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I should have caught this earlier. Why build paddingBegin and then copy it into destOrigin? Why not just use paddingBegin directly?

srcOrigin.push_back(0);
}
destOrigin = std::vector<int>(paddingBegin);
std::vector<int> destOrigin = std::vector<int>(paddingBegin);
std::vector<int> regionSize = inputDims;
Copy link
Member

Choose a reason for hiding this comment

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

Same here - no need to make a copy of inputDims, just use it directly. I think you're trying to make it more clear what each vector is representing in the copyTensorRegion call but there's no need since the API documents it very clearly already.

@mrbeann
Copy link
Contributor Author

mrbeann commented Jul 16, 2021

Thanks for your efforts! I just update the code.

@xyzsam xyzsam changed the title Padding Add a padding operator. Jul 16, 2021
@xyzsam xyzsam merged commit e54f53d into harvard-acc:master Jul 17, 2021
@mrbeann mrbeann deleted the padding branch July 17, 2021 05:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants