-
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
[Relay] Add compiler pass tutorial docs #2746
Conversation
docs/dev/relay_add_pass.rst
Outdated
|
||
TODO: The content of this section should be almost identical to the | ||
corresponding section in ``docs/dev/relay_add_op.rst``. Can we just link to | ||
there? |
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.
Also curious about this point.
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.
Linking is good
docs/dev/relay_add_pass.rst
Outdated
|
||
Compiler passes can be used to collect data about Relay programs or transform | ||
them in various ways. The base class used to traverse programs is | ||
``ExprFunctor``. Depending on what you want to do, there are subclasses |
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 recommend using :code:
for class names and other such constructs, as using the backticks without :code:
results in italics rather than typewriter font.
|
||
Adding a Compiler Pass to Relay | ||
=============================== | ||
|
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.
High-level remark about this introductory section: It should be emphasized that writing passes is the key to adding features to Relay because it is how you traverse the AST and inspect the program.
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.
Another good point to emphasize is that Relay's "built in" core language features are implemented using the same pass interface, as a point to show how powerful those are: autodiff and type inference are implemented as passes.
|
||
Writing an Expression Visitor | ||
----------------------------- | ||
|
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 might be good to mention that the recommended practice for writing Visitors is to have a separate public interface method (like Check()
in the example) rather than using VisitExpr()
. This is because there sometimes needs to be some bookkeeping done before invoking the top-level recursion.
Also you should explain that VisitExpr()
(no underscore) is the call necessary for recursive traversal, since the example does not include a recursive traversal (is there an easy enough case that does have one?)
docs/dev/relay_add_pass.rst
Outdated
In Relay, there are three types of nodes that we want to perform constant | ||
folding on: | ||
|
||
- ``LetNode``s |
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.
FYI I don't think RST permits having a character follow the backtick, so best to find a different way to phrase this (I don't know how to escape the "s").
docs/dev/relay_add_pass.rst
Outdated
- ``LetNode``s | ||
- ``TupleItemGetNode``s | ||
- ``CallNode``s | ||
|
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.
Code snippets would be more informative and illustrative for these paragraphs.
docs/dev/relay_add_pass.rst
Outdated
implementations for all node types. Each ``VisitExpr_`` method visits all of | ||
its node fields and creates a new node if any of the resulting fields have | ||
changed. Otherwise, the original node is returned. | ||
|
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 think the description of a mutator vs visitor should come earlier and be more detailed, since the visitor and mutator are very important base classes that anyone writing a pass will have to interact with.
docs/dev/relay_add_pass.rst
Outdated
corresponding section in ``docs/dev/relay_add_op.rst``. Can we just link to | ||
there? | ||
|
||
Summary |
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 summary seems unnecessary since it does not recap the points from the different sections (nor do I believe that should be at the end; a high-level summary would be best for the beginning).
src/relay/pass/fold_constant.cc
Outdated
} | ||
|
||
private: | ||
std::unordered_map<Expr, bool, NodeHash, NodeEqual> memo_; | ||
|
||
void VisitExpr_(const ConstantNode* n) final { | ||
memo_[GetRef<Constant>(n)] = true; |
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.
Is there a test case where the previous default behavior fails? This would be a good regression to add a test for.
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 added this to the patch, mainly to discuss it.
I believe the code before is semantically equivalent to what I've changed it to, so I'm curious why the ConstantNode
case was baked into Check
. Is it just for performance purposes? If not, then I think this change just increases readability.
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.
Oh, I see. It looks like it might be to avoid having constant nodes in the map (trying to reduce memory usage because constant nodes are a trivial case?). Not sure whether the change is necessary.
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.
Yeah, I'm pretty sure it's an optimization now, so I don't think we should change the actual code. But in the tutorial, I'm thinking about having the code snippet differ from the actual code, just for for pedagogical reasons.
Re: your general questions in the top-level post, I think such a tutorial should try to be general in scope since Relay compiler passes are really powerful and we want users to make use of that power. I am also not sure precisely who comprises the target audience, but it would be safe to assume that even if they are familiar with the visitor pattern (as most programmers might be expected to be or would easily be able to read up on it), it would be helpful to give specific examples and explain how Relay's visitors are set up. |
LGTM |
Thanks for the feedback, @slyubomirsky. I've tried to incorporate it in the most recent commit, and I've added some more questions under TODOs. Adding more reviewers! @srkreddy1238 @yongwww Be as nitpicky as you can. |
@jroesch @slyubomirsky @MarisaKirisame @srkreddy1238 @yongwww Pinging reviewers and adding @icemelon9 |
adding @zhiics |
docs/dev/relay_add_pass.rst
Outdated
helper method ``ValueToExpr`` to allow us to place the evaluated expression | ||
back into the AST. | ||
|
||
TODO: Why does the ``CallNode`` case call ``VisitExpr_`` and not |
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.
ExprMutator::VisitExpr_(call) use the default implementation for call, which is just recurse
ExprMutator::VisitExpr(call) do dispatch and reach here again, which is infinite loop
docs/dev/relay_add_pass.rst
Outdated
At a high level, there are three key components to writing a pass: | ||
|
||
- Creating one or more C++ classes that traverse the program | ||
- Registering an API endpoint with the ``TVM_REGISTER_API`` macro that performs the pass |
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 probably want to mention that it is a TVM packed function? Packed function has been mentioned in different docs.
docs/dev/relay_add_pass.rst
Outdated
The base class used to traverse Relay programs is ``ExprFunctor``. The public | ||
interface it provides is a ``VisitExpr`` method that takes an expression and | ||
zero or more arguments and returns an instance of some type. When you extend | ||
this class, you define the AST traversal pattern by providing implementations |
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.
s/providing/overriding/
docs/dev/relay_add_pass.rst
Outdated
~~~~~~~~~~~~~~~~~~~ | ||
|
||
``ExprVisitor`` is for passes that don't modify the program and instead | ||
collect information about the program. With this class, ``VisitExpr`` and the |
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.
instead perform certain program analysis and collect information
docs/dev/relay_add_pass.rst
Outdated
} | ||
|
||
Note that we're calling ``VisitExpr`` and not ``VisitExpr_`` here. This way, | ||
routing to the appropriate implementation is taken care of for you, and you |
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 think we can probably mention here that calling VisitExpr
will again dispatch to the correct or needed VisitExpr_
depending on the node type of each component of a IfNode. I meant we probably need to briefly talk about the functionality of
VisitExpr` as well.
result_ = true; | ||
} | ||
|
||
where ``result_`` is a field. In this case, we don't need to further recurse |
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 think this paragraph is not quite clear why we need result_
and I don't see how we stopped recursing on this visit.
TODO: Why does the ``CallNode`` case call ``VisitExpr_`` and not | ||
``VisitExpr``? | ||
|
||
Registering an API Endpoint |
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.
Probably provide a pointer to the packed function doc?
Add "add to pass.h" or "register in pass manager". |
@weberlo Could you update the pr based on the comments? |
docs/dev/relay_add_pass.rst
Outdated
|
||
And the pass can now be used in C++ and Python, though it's a good idea to | ||
wrap the API in Python, as described in :ref:`relay-add-op`. More detail | ||
about registration can be found in :doc:`runtime.md`. |
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.
One problem when building these docs is that the :doc:runtime.md
reference doesn't get resolved. And we can't use the normal :ref:ayy-lmao
mechanism to point to those docs, because they're in Markdown, and not RST. If nobody knows how RST<->Markdown interop works, I'll need to figure that out.
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.
Can we just rewrite it into rst?
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've found it successful to add explicit references into docs and use :ref:
to point to those references (see the Relay lang ref for examples)
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.
Can we just rewrite it into rst?
Yeah. That seems like the way to go. Gonna rewrite it in rust rst today.
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.
So uhh... why are the docs written in rst? It doesn't seem to highlight C or C++ very well and figuring out how to use its syntax feels like a puzzle.
@jroesch @slyubomirsky @icemelon9 @zhiics Can y'all give another round of review? Let's try to get this merged in soon. |
I merged the initial version, improvements are welcome! |
* Add Relay compiler pass tutorial docs * Add Python API hook wrapping step * Incorporate feedback * More doc iteration * Mooooore iteration * Rewrite `runtime.md` in rst
* Add Relay compiler pass tutorial docs * Add Python API hook wrapping step * Incorporate feedback * More doc iteration * Mooooore iteration * Rewrite `runtime.md` in rst
Here's a rough draft. There are some concerns I have already.
Is this too specific to the constant folding pass? I'd like to make it sufficiently general, but at the same time, depth on a single pass seems valuable.
Who should be the target audience? Do they already know how to use visitors and mutators? Or do they not know what an AST is?
Is constant folding a good example? @MarisaKirisame suggested the ANF-to-GNF pass might be a better fit.
Lemme know what you think and if I should ping anyone else.
@jroesch @slyubomirsky @MarisaKirisame