-
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][Text Format] Text Printer Refactor and Debug Printing #2605
Conversation
Can this finally support attributes of operators (e.g. stride, padding) whose types are tvm::Expr, tvm::Array or string? They are not relay expression. |
@merrymercy This PR is intentionally light on new printer functionality and focuses primarily on infrastructure. Once we get the scaffolding down it will be easier to work on the parser and printer. I'm a bit short on time right now so I can't promise that I will be able to add attribute printing, but my hope is that the |
Thanks. Currently, I cannot use the text format because I cannot write attributes in the text format. Although I can hack it to support some constants. |
One note: move include/relay/doc.h into src/relay/ir, just to keep it internal and minimum. |
src/relay/ir/pretty_printer.cc
Outdated
@@ -46,8 +46,7 @@ class PrettyPrinter : | |||
Doc PrintFinal(const NodeRef& node) { | |||
// TODO(@jmp): If these lines are combined it segfaults?? |
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.
fix the todo.
doc_stack_.back() is a reference which get invalidate as Print might push_back into doc_stack_
using a linked list might also fix the problem
df08054
to
1490d79
Compare
This PR is close to feature complete. cc @wweic @merrymercy @MarisaKirisame @tqchen @jroesch. |
|
} | ||
|
||
// indent a new body | ||
// TODO(jmp): indent should be an instance variable of the printer |
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 you fix this? It is little work.
src/relay/ir/pretty_printer.cc
Outdated
* \param prefix The prefix of the name | ||
* \return The returned name. | ||
*/ | ||
Doc GetUniqueName(std::string prefix) { |
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.
const &
|
||
if __name__ == "__main__": | ||
# for _ in range(10): | ||
# print(anf_print(exprs.example())) |
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.
uncomment or remove
one = relay.const(1) | ||
assert gnf_print(relay.Tuple([one, one])) == "v0.0.1\n(1, 1)\n" | ||
|
||
# assert gnf_print(relay.If(relay.const(True), relay.TupleGetItem(relay.Tuple([one, one]), 0), relay.TupleGetItem(relay.Tuple([one, one, relay.const(1)]), 0))) == "v0.0.1\n%0 = True\nif (%0) {\n %1 = 1\n %2 = (%1, %1)\n %2.0\n} else {\n %1 = 1\n %2 = 1\n %3 = (%1, %1, %2)\n %3.0\n}\n" |
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.
uncomment or remove
I will remove hypothesis for now, but it should be added back eventually.
This is out of the scope of this PR. I think it should go in an RFC.
This is fair. There should be a new RFC soon for features that have not been implemented in the parser yet, including tuple projection. I was maintaining the convention in the commented out parts of the parser and in the old text printer.
With the gnf flag enabled (this is done by default in both Python and in C++), the new printer behaves as the old text printer did. The opposite of gnf printing isn't anf, since let nodes are printable with gnf enabled. Rather turning gnf off prevents the creation of temporary variables, which is the style used for anf components of gnf programs. I've received feedback from some developers that this is desirable, and it's useful to bridge the gap between ML and PL. Those who aren't interested in this format can ignore it entirely. Also I disagree that additional flags are harmful. As long as they're optional they do not create unnecessary mental burden. Moreover, it is common for printers to have many flags: |
It might be fine to support a big amount of options in a developer private API. But it is not a good idea to do so in a user-facing API. I was a big fan of a list of thousands of arguments(hey who do not want new features)? However, the problem of having such new features means two things:
In particular, in the case of gnf=False, the semantics of the program is no longer equivalent to the original one when there are shared path (such as ResNet). This can be very problematic, because astext is supposed to create a bidirectional format (in the case of gnf=False this is no longer true) Note that if the program itself is ANF already, printing with the normal mode should work out of the box, so we also do not need such option as well. So to make things simpler for the public facing API, I think we should simply disable this to remove ambiguity. This is mainly because we have the graph node syntax in the program(unlike typical FP languages). This way, the user has one less concept to learn, and we have one precise syntax for the program. If we really want to support some form of such kind of folding in the long term(which I am not sure if it is really necessary). We need to run analysis to detect the nodes that are only referenced once and optionally use an algorithm to decide how to best fold intermediate nodes into an expression. At the same time, the parser has to be updated to support the folded expression. And if we want to switch such printing option on, the flag could be in some global configuration env(something like relay.set_printer_option), which is only used by advanced developers, so users do not have to worry about the additional option. |
Back to the choices of the tuple projection and separator. I think we should settle down the discussion with an RFC before committing the code. It is even more dangerous to have a dynamic text format flying around and claims that we have parser round-tripping (imagine user starts to use the feature then find things break afterwards). A safe choice would be to keep a consistent behavior with the old text printer, (no separator, [] projection), and give up round-tripping in the PR, until we fully resolved and committed to a text format. |
…eed to rethink design
9610766
to
3184de0
Compare
After discussions with @tqchen I have
The PR is now feature complete. Inlining is currently less aggressive than the existing text printer; however, it is more correct. Here is the roadmap going forward before 0.6:
|
Co-Authored-By: joshpoll <[email protected]>
// DSL function implementations | ||
|
||
Doc& Doc::operator<<(const Doc& right) { | ||
this->stream_.insert(this->stream_.end(), right.stream_.begin(), right.stream_.end()); |
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 dont think this is correct.
Use https://en.cppreference.com/w/cpp/iterator/back_insert_iterator 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.
Or just use a for loop.
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 check against this == &right
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 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.
You still has to catch against this == &right.
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.
see latest commit
Thanks, @wweic @MarisaKirisame @joshpoll this is now merged. |
How the printer works
The pretty printer design works roughly as follows. Instead of creating a string directly, you instead create a
Doc
that consists of a vector ofDocAtom
s. The purpose ofDoc
is to separate the text's meaning (e.g. specifying where new lines and indentations should go) from their actual layout. For example, depending on the length of a line, you may want to printor
This is easy to do with an abstract doc model. We can encode choices in our
Doc
specification and then let an algorithm choose the optimal layout later.The current
DocAtom
s areText
andLine
.Text
stores a piece of text with no line breaks andLine
stores a line break and the amount of indentation directly after it. Unless you are working with the API, you should not need to think aboutDocAtom
s except that new lines should be added to aDoc
separately from text or the line and text will be treated as a singleText
atom instead of aLine
and aText
atom.The main way to build a
Doc
is to via the stream-like interface exposed with the followingDoc
constructor and member functions:The remaining functions you need to think about are:
friend Doc Indent(int indent, const Doc& doc)
: This function returns a newDoc
that is likedoc
except all theLine
s inside it have had their indentation extended byindent
.Doc PrintVec(const std::vector<Doc>& vec, const Doc& sep = Doc(", "))
: This intersperses thesep
between every doc invec
.Print
functions