[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms - #11574

Merged
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization
Jun 9, 2022
Merged

[TIR] CSE pass : Restrict the equivalence to be decided by a normal form - avoids comparison of terms#11574
tkonolige merged 23 commits into
apache:mainfrom
FranckQC:FranckQC-CSE-normalization

Conversation

@FranckQC

@FranckQCFranckQC commented Jun 4, 2022

Copy link
Copy Markdown
Contributor

This PR addresses the issue described in #11423 .
Here is some context :

The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where the notion of being equivalent was customizable, and no assumption was made about it. That means that the implementation of the equivalence test function EquivalentTerms() - which was at the moment just calling the syntactical equality test EqualTerms() - could be replaced later by a cleverer equality test.

However, having such a generic way of comparing elements meant that in the function SyntacticToSemanticComputations(), where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), the only way was to compare each pair.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.

This PR essentially does the following:

  • When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them.
    That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
    It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.

  • Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
    As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.

To run it ON, simply do:
with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):
before calling build()

Many thanks!

FranckQC added 13 commits June 4, 2022 07:22
…n function, and using this normalization function to compare terms, avoiding O(n²) comparisons.
…nd the second for treating redundant expression by decreasing order of their sizes). Instead, does only one sort, with their sizes, and when equal, with their frequencies. If the frequencies are the same too, uses the syntactical order for the deterministic aspect - as before
… it, otherwise even if we later sort it the harm is done as the canonical representant chosen might have been different
…THe first one ensures that the canonical represantants chosen are always the same, and the second (done with a custom comparison function), that we always introduce orthogonal possibilities in the same order
…al test instead of printing the hashes and relying on the human to verify them
@FranckQC
FranckQC marked this pull request as draft June 4, 2022 13:42
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

The only item failing seems to be a flaky test to me as it's unrelated with the changes introduced by this PR:

In the test file test_custom_datatypes.py, the function test_myfloat() gives:

UserWarning: target_host parameter is going to be deprecated. Please pass in tvm.target.Target(target, host=target_host) instead.

I reported the issue there:
#11580

Everything else seems ok to me.
This PR is now ok to be reviewed :)

@FranckQC
FranckQC marked this pull request as ready for review June 5, 2022 02:35

@tkonoligetkonolige left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@FranckQC thanks for your hard work on this PR!

@mbs-octoml could you also review?

@tqchen In #10544 you had some concerns about using arith::Analyzer in this pass. Do you still have those concerns with this pr? The analyzer is not used by default and is only being used once for each expression.

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
Comment threadsrc/tir/transforms/common_subexpr_elim_tools.cc
@FranckQC

FranckQC commented Jun 8, 2022

Copy link
Copy Markdown
ContributorAuthor

Hopefully, this time everything should be resolved.
The last build and all tests from this afternoon were successful, so fingers crossed it will stay the same with the latest commit (I had forgot the minor thing about the curly braces in the previous one from this afternoon, sorry).

To summarize, we should now have the best of the two worlds with this particular implementation, since identify_equiv_terms has now been brought to the knowledge of the function SyntacticToSemanticComputations():

  • When identify_equiv_terms is false (which is the case by default), we go straight from the hashtable to the vector, without doing any necessary work (thank you @tkonolige for the very good point!).
  • When identify_equiv_terms is true (for people ok with much longer compile time but who want to to common-out as much as possible), it will do something better than what it did before this PR, as it now takes benefit of the normal form function (which defines/implies the equivalence relation). Previously, we did not take advantage of that. And by the way, now this "identify_equiv_terms == true" mode really is usable for someone who wishes to (it wasn't fully plugged before).

Despite all of that, this pass still won't be cheap at compile time, for sure (especially for programs with a lot of things to common out in cascade). But I think it should be acceptable. When I wrote the pass, I focused more on trying to not miss opportunities for commonings, and on the correctness on the pass, rather that on making the pass as cheap as possible at compile time. I guess it's often a tradeoff. I hope that's ok for most users.

Many thanks for having helped to improve the pass everyone, I appreciate it!

Franck

@mbs-octomlmbs-octoml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Normalization is certainly the most general approach and has the benefit you can see what's going on.

However if I were going to do it I'd build that into the hash function directly to avoid the need to repeatably construct new sub-terms on the off chance we have a table hit. You can use debruijn indexes for the vars, encode op argument order only when non-commutative, and so on. Food for thought.

@AndrewZhaoLuoAndrewZhaoLuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these folks have it covered B)

Comment threadsrc/tir/transforms/common_subexpr_elim.cc Outdated
@FranckQC

Copy link
Copy Markdown
ContributorAuthor

Let's get this one merged? :)

@tkonolige
tkonolige merged commit d8678a6 into apache:mainJun 9, 2022
@tkonolige

Copy link
Copy Markdown
Contributor

Thanks @FranckQC! And @mbs-octoml and @AndrewZhaoLuo for reviewing.

Kathryn-cat pushed a commit to Kathryn-cat/tvm that referenced this pull request Jun 10, 2022
…orm - avoids comparison of terms (apache#11574)
The CSE pass had been designed for potentially allowing comparisons (and commonings) of equivalent terms (like (x+y)+z and x+(y+z)), where **the notion of being equivalent was customizable, and no assumption was made about it**. That means that the implementation of the equivalence test function `EquivalentTerms()` - which was at the moment just calling the syntactical equality test `EqualTerms()` - could be replaced later by a cleverer equality test.
However, having such a generic way of comparing elements meant that in the function `SyntacticToSemanticComputations()`, where we were going from a hashtable of syntactical entities to what I called a vector of "semantical entites" (which are just canonical forms/representants of classes of equivalence of terms), **the only way was to compare each pair**.
That resulted in a quadratic behavior of this function, but there was no way around it as in order to merge equivalent entities into their class of equivalence, we had to compare them.
**This PR essentially does the following:**
- When computing the classes of equivalences of terms (therefore transforming a ComputationTable (i.e. a hashtable) into a vector of classes of equivalence) : **instead of comparing each pair of terms, relies on a normalization procedure to obtain a normal form for each of them**.
That transforms a small part of the algorithm that was quadratic to n.logn. However, it's difficult to see improvements in practice, in particular for average sized programs, as that part was a "small" quadratic to a "big" n.logn (finding things in a hash-table, copying it to a vector, etc).
It was probably going from a complexity of ~O(((n²-n)/2) + n.logn) to a complexity of ~O(3n + n.logn), so potential gains would only be expected for very large programs.
- Completely gives the user the possibility to turn ON/OFF the semantical comparisons of terms. It is turned OFF by default (as it's quite longer to compile with it ON, unsurprisingly), which means that by default, the equivalence coincides with the (syntactical) equality of terms.
As the pass was written with the possibility to do these additional commonings (like (x+y)+z and x+(y+z)), it was a good time to fully plug that completely, up to the Python user who can now turn that ON if he wants to. But again, it is OFF by default, so no real change on that.
To run it ON, simply do:
`with tvm.transform.PassContext(config={'tir.enable_equiv_terms_in_cse_tir':True}):`
before calling `build()`
- When this boolean is set to ON, it uses a simple implementation of the normalization function with equivalences that uses `arith::Analyzer::Simplify` as noted by in apache#10544 . Note that this is not a real normalization procedure as it is incomplete (i.e., it is not guarantee to converge to the normal form), but it is correct, and it works well with most properties : associativity of +, distributivity of * on +, etc.
- Clarifies and enhance the test base for the pass. In particular, it adds the tests that were written in apache#10544 but which did not make it through.
- Also add the test ( https://github.com/AndrewZhaoLuo/TVM-Sandbox/blob/19284ddbd6bb28af61c0c2aa8bb334c5c53731a7/tir/test_inconsistent_tir_lowering.py#L1 ) demonstrating the (older) non-deterministic lowering and put it into a proper test, as I found it useful for making sure that this does not happen again. It has been copied from apache#10663 and only slightly adapted (in particular for doing the comparison of hashes automatically instead of printing them and relying on a human to compare them).
Sign up for freeto 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.

4 participants

@FranckQC@tkonolige@AndrewZhaoLuo@mbs-octoml