-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Support aten::grid_sampler, aten::im2col #8443
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't reviewed the specific implementation, but it seems that there are some problems in the unit test.
def test_forward_im2col(): | ||
torch.set_grad_enabled(False) | ||
|
||
class Im2col3x3(Module): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im2col3x3
is not called in verify_script_model?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we don't need two separate modules, but use parameters to pass the kernel_size into one Module, so that we can also support more kernel_size in the unit test. @delldu
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im2col3x3 for trace mode, Im2col5x5 for script mode.
ONLY for KISS in unit test. Please feel free comments ^_^.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modified. Only one class Im2col with constructor, remove script test.
auto kernel_h = tvm::cast(tvm::DataType::Int(32), param->kernel_size[0]); | ||
auto kernel_w = tvm::cast(tvm::DataType::Int(32), param->kernel_size[1]); | ||
auto dilation_h = tvm::cast(tvm::DataType::Int(32), param->dilation[0]); | ||
auto dilation_w = tvm::cast(tvm::DataType::Int(32), param->dilation[1]); | ||
auto padding_h = tvm::cast(tvm::DataType::Int(32), param->padding[0]); | ||
auto padding_w = tvm::cast(tvm::DataType::Int(32), param->padding[1]); | ||
auto stride_h = tvm::cast(tvm::DataType::Int(32), param->stride[0]); | ||
auto stride_w = tvm::cast(tvm::DataType::Int(32), param->stride[1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that we can use tir::as_const_int
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We checked source code and found that tvm::cast could deal with more complex situation than tir::as_const_int.
Not sure, I am new comer for TVM development, please feel free to figure out my mistake ^-^, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why you need all these casts. kernel_size etc should be already int. Supporting script mode is not an acceptable excuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just copy from PadCompute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, let me paste the source code.
bool Im2colRel(const Array<Type>& types, int num_inputs, const Attrs& attrs,
const TypeReporter& reporter) {
// types: [input, output]
ICHECK_EQ(types.size(), 2) << "Expects two types, one for the input and another for the output";
const auto* input = types[0].as<TensorTypeNode>();
if (input == nullptr) return false;
if (input->shape.size() != 4) {
reporter->GetDiagCtx().EmitFatal(Diagnostic::Error(reporter->GetSpan())
<< "Im2lossRel: input data should be 4 dimensions, NxCxHxW.");
return false;
}
const Im2colAttrs* param = attrs.as<Im2colAttrs>();
if (param == nullptr) return false;
ICHECK_EQ(param->kernel_size.size(), 2) << "Expects two parameters for kernel height and width";
ICHECK_EQ(param->dilation.size(), 2) << "Expects two parameters for dilation height and width";
ICHECK_EQ(param->padding.size(), 2) << "Expects two parameters for padding height and width";
ICHECK_EQ(param->stride.size(), 2) << "Expects two parameters for stride height and width";
// Calculate output shape
auto kernel_h = tvm::cast(tvm::DataType::Int(32), param->kernel_size[0]);
auto kernel_w = tvm::cast(tvm::DataType::Int(32), param->kernel_size[1]);
auto dilation_h = tvm::cast(tvm::DataType::Int(32), param->dilation[0]);
auto dilation_w = tvm::cast(tvm::DataType::Int(32), param->dilation[1]);
auto padding_h = tvm::cast(tvm::DataType::Int(32), param->padding[0]);
auto padding_w = tvm::cast(tvm::DataType::Int(32), param->padding[1]);
auto stride_h = tvm::cast(tvm::DataType::Int(32), param->stride[0]);
auto stride_w = tvm::cast(tvm::DataType::Int(32), param->stride[1]);
auto dilated_kernel_h = (kernel_h - 1) * dilation_h + 1;
auto dilated_kernel_w = (kernel_w - 1) * dilation_w + 1;
// Output size after padding
auto output_h = (input->shape[2] + 2 * padding_h - dilated_kernel_h) / stride_h + 1;
auto output_w = (input->shape[3] + 2 * padding_w - dilated_kernel_w) / stride_w + 1;
tvm::Array<tvm::PrimExpr> output_shape;
output_shape.push_back(input->shape[0]); // N
output_shape.push_back(input->shape[1] * kernel_h * kernel_w); // K
output_shape.push_back(output_h * output_w); // L
// assign output type
reporter->Assign(types[1], TensorType(output_shape, input->dtype));
return true;
}
- RELAY_REGISTER_OP need Im2col with interface: bool Im2colRel(const Array& types, int num_inputs, const Attrs& attrs, const TypeReporter& reporter);
- We must parse attrs for checking they are valid or not;
- We must calculate output shape depend on parse results.
Unfortunately, attrs are not integers, so we have to do such stupid things.
auto kernel_h = tvm::cast(tvm::DataType::Int(32), kernel_size[0]); | ||
auto kernel_w = tvm::cast(tvm::DataType::Int(32), kernel_size[1]); | ||
|
||
auto dilation_h = tvm::cast(tvm::DataType::Int(32), dilation[0]); | ||
auto dilation_w = tvm::cast(tvm::DataType::Int(32), dilation[1]); | ||
|
||
auto padding_h = tvm::cast(tvm::DataType::Int(32), padding[0]); | ||
auto padding_w = tvm::cast(tvm::DataType::Int(32), padding[1]); | ||
|
||
auto stride_h = tvm::cast(tvm::DataType::Int(32), stride[0]); | ||
auto stride_w = tvm::cast(tvm::DataType::Int(32), stride[1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explained as before. tvm::cast would be a better choice than tir::as_const_int if we run script mode, PyTorch script mode could generate complex expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really support script mode. Please don't make your implementation complicated to support script mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just copy from PadCompute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please reference on same file about 155~245 lines.
tvm::te::Tensor pad(const tvm::te::Tensor& t, const tvm::Array<tvm::PrimExpr>& pad_before,
tvm::Array<tvm::PrimExpr> pad_after = tvm::Array<tvm::PrimExpr>(),
PrimExpr pad_value = PrimExpr(), std::string name = "T_pad",
std::string tag = kElementWise, std::string pad_mode = "constant",
const Array<PrimExpr>* dyn_output_shape = nullptr)
with same function tvm::cast to parsing parameters.
A little more, this is no business with script mode, just for parsing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@delldu Please drop support for script mode. Just because your model has control flow doesn't mean these ops also need to be scripted. Remember, everyone can use these op conversion. You can do trace and selectively script certain parts of your model using
torch.jit._script_if_tracing
. This is how MaskRCNN in torchvision is implemented, for example. See https://github.com/pytorch/vision/blob/master/torchvision/models/detection/roi_heads.py#L454
Thanks. It is real good reference. High value !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to copy old code. kernel_size
etc should be constant integers, please remove all casts.
@delldu I don't see a need to support scripting for these ops, so can you simplify this PR? You don't need to test with |
Hello @masahi, @wyc-ruiker, we have simplified test units. Could you please consider accepting this PR ? Or any thing else we need to do ? |
Hi @delldu, there's another masahi's comment that hasn't been discussed. #8443 (comment) |
python/tvm/relay/frontend/pytorch.py
Outdated
Torch F.unfold set kerenl_size, dilation, padding, stride as pairs before calling im2col | ||
but it brokern TVM "if condition expression", so please USE torch._C._nn.im2col instead | ||
of F.unfold and make sure giving paired parameters. Please reference test_forward_im2col | ||
in file tests/python/frontend/pytorch/test_forward.py. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this sentences.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From [https://pytorch.org/docs/stable/_modules/torch/nn/functional.html#unfold]:
def unfold(
input: Tensor, kernel_size: BroadcastingList2[int],
dilation: BroadcastingList2[int] = 1,
padding: BroadcastingList2[int] = 0,
stride: BroadcastingList2[int] = 1
) -> Tensor:
if input.dim() == 4:
msg = "{} must be int or 2-tuple for 4D input"
assert_int_or_pair(kernel_size, "kernel_size", msg)
assert_int_or_pair(dilation, "dilation", msg)
assert_int_or_pair(padding, "padding", msg)
assert_int_or_pair(stride, "stride", msg)
return torch._C._nn.im2col(input, _pair(kernel_size), _pair(dilation), _pair(padding), _pair(stride))
else:
raise NotImplementedError("Input Error: Only 4D input Tensors are supported (got {}D)".format(input.dim()))
You can find:
- _pair(kernel_size), _pair(dilation), _pair(padding), _pair(stride)
- if input.dim() == 4: ... else raise ...
First thing, we hope user call im2col with pairs parameters. Second, we hope user do not use F.unfold directly for it breaks "if return tensor1 else return tensor2" condition, otherwise, type check will failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed usefulness comments in source code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all, please fix the typo and grammar error, otherwise the comments do not make sense. I don't understand what is "if condition expression". Please revisit this problem assuming you don't need to support script mode. torch._C._nn.im2col
is not supposed to be used by users, we should support the usage of torch.unfold
.
|
||
Please reference | ||
`https://pytorch.org/docs/stable/generated/torch.nn.Unfold.html#torch.nn.Unfold` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyTorch specific stuff shouldn't be in this file. Please explain what this function does instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you click "[SOURCE]" link, you will find
def forward(self, input: Tensor) -> Tensor:
return F.unfold(input, self.kernel_size, self.dilation,
self.padding, self.stride)
torch.nn.Unfold forward will all F.unfold, F.unfold will call im2col. If you call F.unfold or nn.Unfold forward, from_pytorch will give you a exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed usefulness comments in source code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please just remove references to PyTorch and describe what it does. This is not frontend code.
# | ||
# !!! DO NOT USE !!! | ||
# F.grid_sample(input, grid, mode='bilinear', padding_mode='zeros', align_corners=True) | ||
# for it broken TVM "if conditional expression" in torch script mode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment doesn't make sense, remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As F.unfold, F.grid_sample also check parameter with if condition. If you call F.grid_sample, you will get exceptions. This can be reproduced with "F.grid_sample" test. Please kindly reference:
F.grid_sample
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed usefulness comments in source code.
@delldu Please drop support for script mode. Just because your model has control flow doesn't mean these ops also need to be scripted. Remember, everyone can use these op conversion. You can do trace and selectively script certain parts of your model using |
Sorry I closed this PR by mistake. |
Array<IndexExpr> kernel_size; | ||
Array<IndexExpr> dilation; | ||
Array<IndexExpr> padding; | ||
Array<IndexExpr> stride; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IndexExpr should be Integer
. If you make this change you can remove all the casts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry let give a little explain.
- Im2Compute must following TVM RELAY_REGISTER_OP interface, so it is
Array<te::Tensor> Im2colCompute(const Attrs& attrs, const Array<te::Tensor>& inputs,
const Type& out_type) {
const auto* param = attrs.as<Im2colAttrs>();
ICHECK(param != nullptr);
return Array<te::Tensor>{
topi::im2col(inputs[0], param->kernel_size, param->dilation, param->padding, param->stride)};
}
- We have two choice, 1)parsing parameters in Im2colCompute, 2) parsing in topi::im2col, here we choice 2), that is in topi::im2col, there are no difference. 3) Change Im2colAttrs.
- Struct Conv2DAttrs is also using IndexExpr instead of Integer, so Im2colAttrs follow their style, in fact, kernel_size mean kernel_size_h/w, dilation means dilation_h/w etc, not pure Integer.
So RELAY_REGISTER_OP ==> Im2colCompute ==> im2col and Im2colAttrs, we almost have no choice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why you can't use Array<Integer>
. There is no need to follow Conv2DAttrs
, if all attributes in im2col ops are integer constant, there is no point using Array<IndexExpr>
and it just makes your implementation unnecessarily complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I see. will modify.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @masahi , @wyc-ruiker ,
First all, thank you help us improve PR. Sounds there is one import issue left, I would give my understanding.
Second, please remember using IndexExpr = ::tvm::PrimExpr;
1. Conclusion
Changing Array<IndexExpr>
to Array<Integer>
in Im2colAttrs is not good.
2. Function Patch
inline tvm::te::Tensor im2col(const tvm::te::Tensor& data, ...)
{
auto input_b = data->shape[0];
...
auto kernel_h = tvm::cast(tvm::DataType::Int(32), kernel_size[0]);
...
tvm::Arraytvm::PrimExpr output_shape;
output_shape.push_back(N);
...
auto pad_value = tvm::tir::make_const(data->dtype, 0);
auto l = [&](tvm::Arraytvm::tir::Var args) {
...
tvm::PrimExpr s_c = indexdiv(indexdiv(k, kernel_h), kernel_w);
indices.push_back(s_c); // C, source channel
...
return tvm::if_then_else(..., data(indices), pad_value);
};
return tvm::te::compute(output_shape, l, name, tag);
}
3. Cause analysis
- Input
The shape of input data isArray<PrimExpr>
;
inline tvm::te::Tensor im2col(const tvm::te::Tensor& data
- Output
output shape is alsoArray<PrimExpr>
.
TVM_DLL Tensor compute(Array<PrimExpr> shape, ...);
- Process
3.1) Many expression in lambda function such as indices, areArray<PrimExpr>
, NOTArray<Integer>
.
3.2) Some function parameters are PrimExpr.
For an example: prototye of indexmod is:
TVM_DLL PrimExpr indexdiv(PrimExpr a, PrimExpr b, Span span = Span());
If we give Integer parameters to indexdiv, only got compile fatal warning and errors ...
4. Test Result
We try to change Array<IndexExpr>
to Array<Integer>
, modified related source code,
fighting again and again, using many methods, finally got many and many building(compile) errors.
5. Inference
tvm::cast
is good choice than as_const_int
for parsing parameters.
The reason comes from their prototype
inline const *int64_t as_const_int(const PrimExpr& x);
TVM_DLL PrimExpr cast(const DataType& t, PrimExpr value, Span span = Span());
Following above results, that is why we can't use Array<Integer>
.
python/tvm/relay/op/nn/_nn.py
Outdated
@@ -1308,3 +1308,6 @@ def dilate_shape_func(attrs, inputs, _): | |||
reg.register_shape_func("nn.bias_add", False, elemwise_shape_func) | |||
reg.register_shape_func("nn.softmax", False, elemwise_shape_func) | |||
reg.register_shape_func("nn.relu", False, elemwise_shape_func) | |||
|
|||
|
|||
reg.register_broadcast_schedule("nn.im2col") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be broadcast if you set TOpPattern
to kOpaque
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we change kOpaque to kInjective ? Frankly, I do not understand it. please teach me. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it kInjective
and use register_injective_schedule
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, we have change kOpaque to kInjective, and test passed.
Dear reviewers,
Help us review this commit, thanks a lot.