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

[TE] Support negative indices #9023

Merged
merged 8 commits into from
Jan 12, 2022

Conversation

AndrewZhaoLuo
Copy link
Contributor

@AndrewZhaoLuo AndrewZhaoLuo commented Sep 15, 2021

Negative indices are a pretty common feature in most modern programming languages and libraries. This proposed PR would add optional support for negative indices out of the box for tensors in TE.

A lot of operators from other frontends support negative indices. See ONNX, numpy, and PyTorch where their tensors support negative indices out of the box. Unfortunately TE and the rest of TVM does not.

One benefit of this change is now it is trivial to change operators to support negative indices. This makes adapting frontends to our operators simpler. For example an operator using this simply has to turn the support_negative_indices flag to true for every relevant indexing operation. An example of doing this can be found in https://github.com/AndrewZhaoLuo/tvm/blob/02f1870d7f2e274cfd7e04678691da019ff201f0/include/tvm/topi/transform.h#L1265. Right now how we handle negative indices is by manually converting them to positive indices which can be cumbersome and results in lots of code duplication.

The downside is that it technically adds a little more computation per indexing operation. While these operations are probably memory and not compute bound I am not 100% confident that setting support_negative_indices=true would not result in performance regressions. Furthermore, it will make some of printouts very ugly since it will but the comparison operation in each index statement.

@jroesch also has great points that this might make loop level analysis hard and result in some optimizations harder.

Pros:

  • Clear way to support negative indices

Cons:

  • Printouts will be ugly
  • Might be slower (but can turn off easily)

Unknowns:

  • Dynamic shapes?
  • Effect on compile time optimizations

@masahi
Copy link
Member

masahi commented Sep 15, 2021

For the context, this PR was split from another PR following the discussion #8971 (comment), since this could be potentially a controversial change (change the semantics of te::Tensor indexing)

cc @tqchen @junrushao1994

@mbrookhart
Copy link
Contributor

I like this idea. I've been slowly throwing relay.where around the ops and the importers to solve this problem when I hit it, but that adds some complication to ops, fusion, and the risk of making things slower than they need to be. This would enable things to be much simpler at the Relay level in a number of places, I'm very curious to hear Tianqi and Junru's thoughts.

src/te/tensor.cc Outdated

if (support_negative_indices) {
for (size_t i = 0; i < shape.size(); i++) {
PrimExpr new_index = if_then_else(indices[i] < make_const(indices[i]->dtype, 0),
Copy link
Member

Choose a reason for hiding this comment

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

you want to use select here, because it does not involves a memory operation(thus can evaluate both 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.

Done.

I understand the difference between if_then_else and Select

Don't fully understand why we want to be able to evaluate both sides however. Is this to prevent branching?

@tqchen
Copy link
Member

tqchen commented Sep 15, 2021

I think this can be useful for some of the indexing operations. We could try to make it more explicit though. e.g. introduce a new API. tensor.LookupWithNegativeIndices(indices) and explicitly call into them in these cases

@yzh119
Copy link
Member

yzh119 commented Sep 15, 2021

Is the negative indices used inside TE? If so I wonder can we make it a transformation pass rather than runtime behavior?

@tqchen
Copy link
Member

tqchen commented Sep 15, 2021

@yzh119 I believe the intended usage was for the case where the value of indices are not known at compile time, otherwise compiler will be able to prove and simplify the conditionals

@junrushao
Copy link
Member

junrushao commented Sep 16, 2021

I think I sort of understand the usecase here: some negative indices are not known to be negative until in runtime. This forces us to defer the conversion from compile-time to runtime.

On the other hand, I am not 100% sure if it is the best fix by adding a new argument in the public interface, given that in most cases indices are just positive and well in-range.

I was thinking, if the issue comes from an importer, is it possible to add an operator like normalize_indices, mark it as injective which makes it fusible, so that there isn't architectural change in TE? What do you guys think?

@AndrewZhaoLuo
Copy link
Contributor Author

I think I sort of understand the usecase here: some negative indices are not known to be negative until in runtime. This forces us to defer the conversion from compile-time to runtime.

On the other hand, I am not 100% sure if it is the best fix by adding a new argument in the public interface, given that in most cases indices are just positive and well in-range.

I was thinking, if the issue comes from an importer, is it possible to add an operator like normalize_indices, mark it as injective which makes it fusible, so that there isn't architectural change in TE? What do you guys think?

We do have a normalize_indices on the relay level. I think a problem though is the format might be slightly different so you would need a few different implementations. For example take tf's gather vs gather_nd.

@AndrewZhaoLuo
Copy link
Contributor Author

I'll get ahead with this and start responding to comments if there are no other objections.

@AndrewZhaoLuo
Copy link
Contributor Author

This is ready for another look though I might need some guidance for unit tests

include/tvm/te/tensor.h Outdated Show resolved Hide resolved
Copy link
Member

@junrushao junrushao left a comment

Choose a reason for hiding this comment

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

It looks good to me given we want to fully support numpy-style negative indexing. On the API side, although I still think we can put this as a helper function in topi instead of a member function of te::Tensor, the difference is minimal because we are not making architectural change here. So nice work!

BTW, shall we add a unittest for this?

@AndrewZhaoLuo
Copy link
Contributor Author

It looks good to me given we want to fully support numpy-style negative indexing. On the API side, although I still think we can put this as a helper function in topi instead of a member function of te::Tensor, the difference is minimal because we are not making architectural change here. So nice work!

BTW, shall we add a unittest for this?

I'll look into the changes you suggest and add a unit test.

@junrushao
Copy link
Member

Ah no worries! I’m not suggesting changes :-) just unittests

@AndrewZhaoLuo
Copy link
Contributor Author

@junrushao1994 looked at the unittests for cpp and they seem a little bare. I added a single simple test which logs the following results:

A is of shape (x, y)

A(0, 0):
[23:09:53] /home/andrewzhaoluo/Desktop/dev/tvm/tests/cpp/tensor_test.cc:59: A[0, 0]

A.IndexWithNegativeIndices(-1, -1):
[23:09:53] /home/andrewzhaoluo/Desktop/dev/tvm/tests/cpp/tensor_test.cc:60: A[select((bool)1, (-1 + x), -1), select((bool)1, (-1 + y), -1)]

A.IndexWithNegativeIndices(0, -1):
[23:09:53] /home/andrewzhaoluo/Desktop/dev/tvm/tests/cpp/tensor_test.cc:61: A[select((bool)0, x, 0), select((bool)1, (-1 + y), -1)]

Some guidance on what is sufficient would be helpful.

@masahi
Copy link
Member

masahi commented Jan 9, 2022

What's the status of this PR?

@AndrewZhaoLuo
Copy link
Contributor Author

Oh, forgot about this as I moved to working on other things. We still cool with merging @junrushao1994 ?

@junrushao
Copy link
Member

@AndrewZhaoLuo yeah let's get it merged!

@AndrewZhaoLuo
Copy link
Contributor Author

@junrushao1994 this is now ready to merge

Copy link
Member

@junrushao junrushao left a comment

Choose a reason for hiding this comment

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

LGTM

@junrushao junrushao merged commit 80f9b9f into apache:main Jan 12, 2022
@junrushao
Copy link
Member

Thanks @AndrewZhaoLuo! It's merged :-)

crazydemo pushed a commit to crazydemo/tvm that referenced this pull request Jan 27, 2022
* initial change

* more explicit api

* switch to select

* add support for negative indices

* reduce things further

* lint

* to CamelCase

* unit test

Co-authored-by: Andrew Zhao Luo <andrewzhaoluo@system76-pc.localdomain>
Raghav-Chakravarthy pushed a commit to Raghav-Chakravarthy/tvm that referenced this pull request Jan 28, 2022
* initial change

* more explicit api

* switch to select

* add support for negative indices

* reduce things further

* lint

* to CamelCase

* unit test

Co-authored-by: Andrew Zhao Luo <andrewzhaoluo@system76-pc.localdomain>
ylc pushed a commit to ylc/tvm that referenced this pull request Feb 16, 2022
* initial change

* more explicit api

* switch to select

* add support for negative indices

* reduce things further

* lint

* to CamelCase

* unit test

Co-authored-by: Andrew Zhao Luo <andrewzhaoluo@system76-pc.localdomain>
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.

6 participants