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

Adds torchscript Compatibility to box_convert #2737

Merged
merged 10 commits into from
Oct 7, 2020

Conversation

oke-aditya
Copy link
Contributor

Follow up of #2710 .

  1. I fixed the small naming issue as mentioned.
  2. Added tests for JIT

# def test_bbox_convert_jit(self):
# box_tensor = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0],
# [10, 15, 30, 35], [23, 35, 93, 95]], dtype=torch.float)
def test_bbox_convert_jit(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was wondering if it would be more simple to check scripted function in all your test_bbox_*:

scripted_fn = torch.jit.script(ops.box_convert)

for fn in [ops.box_convert, scripted_fn]:
    # ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Possible, this PR is open for a lot of refactoring 👍 .

@oke-aditya
Copy link
Contributor Author

oke-aditya commented Oct 1, 2020

The error sounds really trivial, but I cannot understand why it shouldn't be able to script it?
From CircleCI build. I could reproduce this locally on my Windows PC as well.
image
image

@vfdev-5
Copy link
Collaborator

vfdev-5 commented Oct 1, 2020

Error says it is missing else branch, so it means not all clauses are well covered...

@oke-aditya
Copy link
Contributor Author

Is there a need for else branch for JIT to work? I have simply written if elif and not written any else block in entire code reason being in future we can simply continue coding without trying to recall what else was.
Other Tests worked well so I guess there is no logical error in code. May I know what should be probable fix?
I'm not much experienced in such jit errors so sorry if this question is too dumb.

@vfdev-5
Copy link
Collaborator

vfdev-5 commented Oct 1, 2020

I think we have to recode box_convert such that it has more clean control flow. For example

def box_convert(boxes: Tensor, in_fmt: str, out_fmt: str) -> Tensor:

    if in_fmt == out_fmt:
        boxes_converted = boxes.clone()
        return boxes_converted
    
    if in_fmt == "xyxy":
        if out_fmt == "cxcywh":
            return _box_xyxy_to_cxcywh(boxes)
        else:
            raise ValueError("...")
    elif in_fmt == "xywh":
        if out_fmt == "xyxy":
            return _box_xywh_to_xyxy(boxes)
        else:
            raise ValueError("...")
    elif in_fmt == "cxcywh":
        if out_fmt == "xyxy":
            return _box_cxcywh_to_xyxy(boxes)
        else:
            raise ValueError("...")
    else:
        raise ValueError("...")

Any thoughts ?

PS: maybe we have to replace assert by if something raise exception

assert in_fmt in allowed_fmts

@oke-aditya
Copy link
Contributor Author

oke-aditya commented Oct 1, 2020

Let @fmassa have a look.
The above is OK though I preferred assert statement. Still, I don't understand why JIT must need else after if
Torchscript support is a priority, we can refactor it to get it to work (even if code becomes a bit clumsy)

@vfdev-5
Copy link
Collaborator

vfdev-5 commented Oct 1, 2020

Still, I don't understand why JIT must need else after if

Because some clauses do not define the output and it is not evident whether they couldn't happen in runtime.

@oke-aditya
Copy link
Contributor Author

Oh now I get the point. IIRC JIT needs to know what will exactly happen to code in future so it needs all possibilities right?
That's why it can compile Just In Time before execution. 👍

@oke-aditya
Copy link
Contributor Author

oke-aditya commented Oct 5, 2020

I got the JIT test passing by simply adding else with ValueError. Now we can refactor the code.
Please let me know the suggestions. 👍
@fmassa @vfdev-5

@codecov
Copy link

codecov bot commented Oct 5, 2020

Codecov Report

Merging #2737 into master will increase coverage by 0.32%.
The diff coverage is 85.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2737      +/-   ##
==========================================
+ Coverage   72.42%   72.75%   +0.32%     
==========================================
  Files          96       96              
  Lines        8313     8309       -4     
  Branches     1293     1292       -1     
==========================================
+ Hits         6021     6045      +24     
+ Misses       1903     1871      -32     
- Partials      389      393       +4     
Impacted Files Coverage Δ
torchvision/ops/boxes.py 95.29% <82.35%> (+2.03%) ⬆️
torchvision/ops/_box_convert.py 100.00% <100.00%> (ø)
torchvision/io/_video_opt.py 39.37% <0.00%> (+16.25%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6e639d3...3094f03. Read the comment docs.

Copy link
Collaborator

@vfdev-5 vfdev-5 left a comment

Choose a reason for hiding this comment

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

Thanks for the update.

@@ -167,25 +167,35 @@ def box_convert(boxes: Tensor, in_fmt: str, out_fmt: str) -> Tensor:
boxes_xyxy = _box_xywh_to_xyxy(boxes)
if out_fmt == "cxcywh":
boxes_converted = _box_xyxy_to_cxcywh(boxes_xyxy)
else:
raise ValueError("Unsupported Boundig Box Conversions for given in_fmt and out_fmt")
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice to add tests to improve the coverage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is assert statement before these ValueError codeblocks. So the test will never reach this stage. Should I remove the assert statements?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes, please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added 👍

@oke-aditya oke-aditya requested a review from vfdev-5 October 5, 2020 18:43
Copy link
Member

@fmassa fmassa left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the work @oke-aditya !

TorchScript can be tricky at times, and many times we don't quite understand what's going on and why it doesn't want to accept our code.

Your PR looks ok to me (although I would prefer to keep the assertion checks in lines 157-159).
I've also committed two different refactoring proposals in https://github.com/fmassa/vision-1/tree/proposal_box_convert_torchscript (one in each commit), which I think could make the code reading easier and with less checks.

Let me know what you think!

Comment on lines 157 to 159
allowed_fmts = ("xyxy", "xywh", "cxcywh")
assert in_fmt in allowed_fmts
assert out_fmt in allowed_fmts
Copy link
Member

Choose a reason for hiding this comment

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

Is this not supported by torchscript?
I just tried the following and it worked:

In [1]: import torch

In [2]: def f(x: str):
   ...:     L = ('a', 'b')
   ...:     assert x in L
   ...:     return x
   ...:

In [3]: ff = torch.jit.script(f)

In [4]: print(ff.code)
def f(x: str) -> str:
  if torch.__contains__(["a", "b"], x):
    pass
  else:
    ops.prim.RaiseException("AssertionError: ")
  return x

Copy link
Contributor Author

@oke-aditya oke-aditya Oct 6, 2020

Choose a reason for hiding this comment

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

I asked @vfdev-5 and removed it. We removed it since then tests will not trigger value error as assert would stop this. I added tests to check ValueError too. I am not sure if I should add it back.

Copy link
Member

Choose a reason for hiding this comment

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

If assert is not good, then let's change instead to ValueError, something like

if in_fmt not in allowed_fmts:
    raise ValueError(...)

Having those checks is important for better user experience, otherwise their code could silently fail

@@ -167,25 +164,35 @@ def box_convert(boxes: Tensor, in_fmt: str, out_fmt: str) -> Tensor:
boxes_xyxy = _box_xywh_to_xyxy(boxes)
if out_fmt == "cxcywh":
boxes_converted = _box_xyxy_to_cxcywh(boxes_xyxy)
else:
raise ValueError("Unsupported Bounding Box Conversions for given in_fmt and out_fmt")
Copy link
Member

Choose a reason for hiding this comment

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

You don't actually need those to make torchscript happy. The problem is that boxes_converted is not defined in the other branch.
You can define it outside of the if so that torchscript knows its type.

@oke-aditya
Copy link
Contributor Author

Both proposals look fine to me. I guess it is matter of coding style.
I find that propoal 1 is better.
I think we don't need boxes_converted or rather we can have inplace parameter if needed.
Let me try with torchscript for proposal1. If it works well we can keep that 👍

@fmassa
Copy link
Member

fmassa commented Oct 6, 2020

@oke-aditya we don't need boxes_converted, and we don't need inplace flag either because we won't be modifying the tensor inplace.
Notice that doing

def f(a):
    a = a + 1
    return a

doesn't change the original a (it's out of place), while

def f(a):
    a += 1
    return a

performs it in-place.

The advantage of proposal 2 is that it's slightly simpler, as we don't need to have some torchscript-specific constructs like defining first the type of the variable, and having to assert it being not None to let torchscript know about its return type.

@oke-aditya
Copy link
Contributor Author

oke-aditya commented Oct 6, 2020

Sounds great. Let me re-run with proposal2 with the ValueError tests as well. Once the tests pass. I will push it 👍

@oke-aditya oke-aditya requested a review from fmassa October 7, 2020 07:59
Copy link
Member

@fmassa fmassa left a comment

Choose a reason for hiding this comment

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

Thanks a lot!

@fmassa fmassa merged commit 5320f74 into pytorch:master Oct 7, 2020
@oke-aditya oke-aditya deleted the tscript_bbox branch October 7, 2020 08:33
bryant1410 pushed a commit to bryant1410/vision-1 that referenced this pull request Nov 22, 2020
* fixies small bug in box_convert

* activates jit test

* Passes JIT test

* fixes typo

* adds error tests, removes assert

* implements to proposal2
vfdev-5 pushed a commit to Quansight/vision that referenced this pull request Dec 4, 2020
* fixies small bug in box_convert

* activates jit test

* Passes JIT test

* fixes typo

* adds error tests, removes assert

* implements to proposal2
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