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

[Relay][Testing] Relay-to-Python compilation #3156

Merged
merged 50 commits into from
Jul 10, 2019

Conversation

slyubomirsky
Copy link
Contributor

@slyubomirsky slyubomirsky commented May 9, 2019

For a simple testing utility, @jroesch and I decided that it would be useful to have a translation of Relay programs into equivalent Python code that we could use as an oracle for fuzzing.

Here I have written an attempt at such a translation. The Python code it produces is meant to match the intended semantics of Relay very closely rather than be performant, so there are some ineffeciencies that a smarter implementation could eliminate through analysis (translating let bindings as functions, for example). Again, the point is for the Python code to be evidently correct rather than an implementation that we would use for anything other than testing purposes.

There are a couple of open issues though:

  • Tests for the translation
  • Handling ASTs

@slyubomirsky
Copy link
Contributor Author

slyubomirsky commented May 9, 2019

There's a rather annoying issue in handling ADTs: ConstructorValue uses a relay.Constructor object for representing ADT values -- if we want to use this translation for differential testing, that would require those constructor objects to match by pointer equality!

This makes it really hard to build ADT values in this translation. Granted, I could pass the Relay module as a variable directly to exec() and get the constructors from the module that way*. However, it seems better in the long run for us to take this opportunity to decide on a better representation for ConstructorValue that does not rely on having objects from the module in the value representation.

A simple approach would be a string tag (e.g., '{name of ADT}_{name of constructor}_{index in ADT definition}'), but there are other choices. I think simplifying the representation would be better for future interoperability (like with @MarisaKirisame's ahead-of-time compiler).

*Now that I've thought of it, I'll hack in this variant but I would prefer that we change the representation

@@ -41,6 +41,7 @@ requirements:
- python
- {{ pin_compatible('numpy') }}
- decorator
- astor
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could someone more experienced with managing TVM's dependencies tell me if this is the right manner to indicate a new Python dependency? astor is only needed for testing (I could possibly exclude it entirely, though it would be convenient to have it)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevermind, @tqchen suggested it's better to exclude the new dependency if it's not used directly in tests (it shouldn't be; astor was only meant for debugging)

@slyubomirsky
Copy link
Contributor Author

I would appreciate reviews, if possible: @jroesch, @MarisaKirisame , @zhiics, @wweic

@MarisaKirisame
Copy link
Contributor

I still dont get the problem. Cant you just take that constructor value? I thought they are only constructed when adt is decclard.

@MarisaKirisame
Copy link
Contributor

LGTM

@slyubomirsky
Copy link
Contributor Author

slyubomirsky commented May 9, 2019

Well it's not a "problem" in that I can pass the module into the generated Python program (as I describe in my above comment), but I would prefer not to have to do that and I think it would make it easier in the future to write code that has to produce or accept values produced by Relay.

@wweic
Copy link
Contributor

wweic commented May 9, 2019

I'll post a review today.

Copy link
Member

@zhiics zhiics left a comment

Choose a reason for hiding this comment

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

Looks good to me so far. I will do a more careful review later. Please add some tests. Curious, it looks that ADT is missing here. How would you plan to support them as well?

return name


# returns an variable AST node for the given Relay var depending on
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# returns an variable AST node for the given Relay var depending on
# returns a variable AST node for the given Relay var depending on



# converts the given Relay function into a Python function
def convert_func_node(self, name_hint: str, func: Expr):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def convert_func_node(self, name_hint: str, func: Expr):
def convert_func_node(self, name_hint: str, func: Func):

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is probably a good idea to add finer-grained type checks. I will address those soon.

"""Attributes for nn.global_pool"""


@register_relay_attr_node
Copy link
Contributor

Choose a reason for hiding this comment

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

Should these new attributes node change in 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 needed them for this one because of the way op calls are handled: If the attr node is not registered, then it is treated as just a tvm.NodeBase object and there is thus no keys() method or any way to iterate through the attribute values.

Copy link
Contributor Author

@slyubomirsky slyubomirsky May 10, 2019

Choose a reason for hiding this comment

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

I no longer depend on these changes so I will move them to another PR as requested. Included here: #3175

def run_as_python(expr: Expr, mod=relay.Module()):
'''Converts the given Relay expression into a Python script and
executes it.'''
py_ast = to_python_ast(expr, mod)
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is to_python_ast defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was a mistake, it should be calling to_python above

return (ast.Name(id=gvar.name_hint, ctx=ast.Load()), [])


def visit_let(self, letexp: Expr):
Copy link
Contributor

Choose a reason for hiding this comment

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

add a unit test

return (ast.Attribute(ref, attr='value', ctx=ast.Load()), defs)


def visit_ref_write(self, write: Expr):
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a unit test

@slyubomirsky
Copy link
Contributor Author

slyubomirsky commented May 9, 2019

Yes, I will be adding ADTs today, along with tests. Thanks for the reviews

@slyubomirsky
Copy link
Contributor Author

Unfortunately it looks like Python's ast library is really insistent that all expr and statement nodes include a lineno and col_offset attribute, which means I will either have to manually construct the whole translated program as AST nodes or construct it as a string and parse it, but not mix parsing and manually assembled ASTs (the line numbers and column offsets won't be consistent). I will see which reworking will be easier (probably the manually constructed ASTs)

@slyubomirsky
Copy link
Contributor Author

Next on the agenda: adding tests, as I have resolved the issue of lineno and col_offset and have all of Relay's features represented.

@slyubomirsky
Copy link
Contributor Author

@MarisaKirisame I followed the approach of your AoT compiler for lowering op calls, I would appreciate if you could look at that code. This has let me eliminate the call to Relay's interpreter from inside the generated code, so I will now be able to move the changes to attr nodes to their own PR because I don't use them here.

@MarisaKirisame
Copy link
Contributor

@slyubomirsky I will looked later today/tmr morning.
Also, what is the reason behind calling this a translation instead of compilation? This is clearly a compilation and I think we should call it as such.

@slyubomirsky
Copy link
Contributor Author

Yeah, fair enough on calling it a compilation. I called it a translation because I expected it to be much more modest in scope than it ultimately was. I can rename the file accordingly

@slyubomirsky slyubomirsky changed the title [TVM][WIP] Relay-to-Python translation [Relay][Testing] Relay-to-Python compilation May 15, 2019
@slyubomirsky
Copy link
Contributor Author

Now I have tests running for just about every feature of the translation; I will continue to add them but think it's worth looking over in greater detail. The conversion has ended up being substantially more complicated than I was hoping it would be, so I hope it is not self-defeating in that regard.

@slyubomirsky
Copy link
Contributor Author

@MarisaKirisame do you know if there's any way I could avoid running fusion but still be able to lower operators? I'd prefer this compiler not to have to depend on complicated Relay passes because its purpose is to test Relay

@MarisaKirisame
Copy link
Contributor

@slyubomirsky fuse with opt-level = 0

@slyubomirsky
Copy link
Contributor Author

Good idea, I'll do that too.

@tqchen
Copy link
Member

tqchen commented May 30, 2019

@zhiics
Copy link
Member

zhiics commented May 30, 2019

Sorry for missing this. I will do another review tonight.

Copy link
Contributor

@wweic wweic left a comment

Choose a reason for hiding this comment

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

lgtm. some minor comments.

return name


# returns n variable AST node for the given Relay var depending on
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# returns n variable AST node for the given Relay var depending on
# returns a variable AST node for the given Relay var depending on

return converter.convert(expr)


def run_as_python(expr: Expr, mod=relay.Module(), target=tvm.target.create('llvm')):
Copy link
Contributor

Choose a reason for hiding this comment

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

set mod to None and initialize it to relay..Module() in function body so every expression is evaluated in a fresh module. https://stackoverflow.com/a/33573262

Copy link
Contributor Author

@slyubomirsky slyubomirsky May 31, 2019

Choose a reason for hiding this comment

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

Ah, very good to know! I will change it

Copy link
Member

@zhiics zhiics left a comment

Choose a reason for hiding this comment

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

Only some nitpicks, otherwise ltgm.

return ast.fix_missing_locations(ast.Module(body=body))


def optimize(self, prog: Expr):
Copy link
Member

Choose a reason for hiding this comment

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

Should we add a todo here to move these passes to the pass manager?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I haven't been in the loop on that. If the pass manager is the right way to do these, then we should use it

Copy link
Member

Choose a reason for hiding this comment

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

It's not merged yet... The TODO just helps us to track it.

return re.sub(r'\W', '', name)


# generates a unique variable name starting from the hint
Copy link
Member

Choose a reason for hiding this comment

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

Should we keep the doc format consistent, i.e. moving them under def?
Same to the functions below.

Copy link
Contributor Author

@slyubomirsky slyubomirsky May 31, 2019

Choose a reason for hiding this comment

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

For things that were meant as purely internal functions, I just put the comments on top but I will change it to be consistent, thanks for the feedback.

@slyubomirsky
Copy link
Contributor Author

Thank you for the feedback!

Copy link
Contributor

@wweic wweic left a comment

Choose a reason for hiding this comment

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

thanks.

@jroesch jroesch merged commit db841c2 into apache:master Jul 10, 2019
wweic pushed a commit to wweic/tvm that referenced this pull request Jul 11, 2019
* First pass at Relay-to-Python converter testing utility

* Indicate astor as a dependency

* Add astor dep to host as well

* Typos and small bugs

* Handle ADTs and matching in Python conversion

* Remove any dependency on ast.parse

* Eliminate unnecessary type var field in Python version of ConstructorValue (already gone on C++ side)

* Update constructor value, fix syntax errors

* Don't forget keywords arg on Call nodes

* Fix some incorrect calls to ast nodes

* Fix more calls, a little more cleaning up

* Missing cases in attr conversion

* Lower op calls instead of running them through interpreter, as in @MarisaKirisame's AoT compiler

* We do still need the module

* Remove changes to op attrs: Will PR separately

* Smoke test and corrections

* More tests and fixes

* Ensure imports are properly global in generated Python code

* Add unit tests for refs

* Add unit test for tuple indexing

* Add unit test for if expression

* Remove astor dependency

* Remove astor from meta.yaml too

* Fix if test and add basic local function test

* Add global function test, refactor earlier tests

* Correct 'clause' field in ADT so Python and C++ field names match

* More fixes and tests for matching and constructors

* Dramatically simplify matching: no need for a thunk

* Improve ref writing test

* Ensure local recursion works

* cleanup

* Add test for global recursion

* Add test for higher-order calls

* Get ops working, add basic tests

* Remove accidentally duplicated test

* More docstrings to appease pylint

* Forgot to fix a test using constructor values

* Reduce optimization level in fusion and fix tuple input to operators

* Test op with tuple output, fix tuple output code

* Add unit test for batch norm

* Add a couple more tricky test cases

* Correct nat constructor to drop unnecessary field

* Fix the op attrs file (accidentally reduced it)

* Address review comments

* Adapt to new ConstructorValue representation (no more runtime dep on module)

* Use pass manager and updated interfaces. Extend module.from_expr to accommodate necessary demands

* Use sequential return value

* Lift out nested conditionals

* Replace triple single quotes with triple double quotes

* Use main variable instead of entry_func
wweic pushed a commit to wweic/tvm that referenced this pull request Jul 11, 2019
* First pass at Relay-to-Python converter testing utility

* Indicate astor as a dependency

* Add astor dep to host as well

* Typos and small bugs

* Handle ADTs and matching in Python conversion

* Remove any dependency on ast.parse

* Eliminate unnecessary type var field in Python version of ConstructorValue (already gone on C++ side)

* Update constructor value, fix syntax errors

* Don't forget keywords arg on Call nodes

* Fix some incorrect calls to ast nodes

* Fix more calls, a little more cleaning up

* Missing cases in attr conversion

* Lower op calls instead of running them through interpreter, as in @MarisaKirisame's AoT compiler

* We do still need the module

* Remove changes to op attrs: Will PR separately

* Smoke test and corrections

* More tests and fixes

* Ensure imports are properly global in generated Python code

* Add unit tests for refs

* Add unit test for tuple indexing

* Add unit test for if expression

* Remove astor dependency

* Remove astor from meta.yaml too

* Fix if test and add basic local function test

* Add global function test, refactor earlier tests

* Correct 'clause' field in ADT so Python and C++ field names match

* More fixes and tests for matching and constructors

* Dramatically simplify matching: no need for a thunk

* Improve ref writing test

* Ensure local recursion works

* cleanup

* Add test for global recursion

* Add test for higher-order calls

* Get ops working, add basic tests

* Remove accidentally duplicated test

* More docstrings to appease pylint

* Forgot to fix a test using constructor values

* Reduce optimization level in fusion and fix tuple input to operators

* Test op with tuple output, fix tuple output code

* Add unit test for batch norm

* Add a couple more tricky test cases

* Correct nat constructor to drop unnecessary field

* Fix the op attrs file (accidentally reduced it)

* Address review comments

* Adapt to new ConstructorValue representation (no more runtime dep on module)

* Use pass manager and updated interfaces. Extend module.from_expr to accommodate necessary demands

* Use sequential return value

* Lift out nested conditionals

* Replace triple single quotes with triple double quotes

* Use main variable instead of entry_func
wweic pushed a commit to neo-ai/tvm that referenced this pull request Jul 11, 2019
* First pass at Relay-to-Python converter testing utility

* Indicate astor as a dependency

* Add astor dep to host as well

* Typos and small bugs

* Handle ADTs and matching in Python conversion

* Remove any dependency on ast.parse

* Eliminate unnecessary type var field in Python version of ConstructorValue (already gone on C++ side)

* Update constructor value, fix syntax errors

* Don't forget keywords arg on Call nodes

* Fix some incorrect calls to ast nodes

* Fix more calls, a little more cleaning up

* Missing cases in attr conversion

* Lower op calls instead of running them through interpreter, as in @MarisaKirisame's AoT compiler

* We do still need the module

* Remove changes to op attrs: Will PR separately

* Smoke test and corrections

* More tests and fixes

* Ensure imports are properly global in generated Python code

* Add unit tests for refs

* Add unit test for tuple indexing

* Add unit test for if expression

* Remove astor dependency

* Remove astor from meta.yaml too

* Fix if test and add basic local function test

* Add global function test, refactor earlier tests

* Correct 'clause' field in ADT so Python and C++ field names match

* More fixes and tests for matching and constructors

* Dramatically simplify matching: no need for a thunk

* Improve ref writing test

* Ensure local recursion works

* cleanup

* Add test for global recursion

* Add test for higher-order calls

* Get ops working, add basic tests

* Remove accidentally duplicated test

* More docstrings to appease pylint

* Forgot to fix a test using constructor values

* Reduce optimization level in fusion and fix tuple input to operators

* Test op with tuple output, fix tuple output code

* Add unit test for batch norm

* Add a couple more tricky test cases

* Correct nat constructor to drop unnecessary field

* Fix the op attrs file (accidentally reduced it)

* Address review comments

* Adapt to new ConstructorValue representation (no more runtime dep on module)

* Use pass manager and updated interfaces. Extend module.from_expr to accommodate necessary demands

* Use sequential return value

* Lift out nested conditionals

* Replace triple single quotes with triple double quotes

* Use main variable instead of entry_func
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants