Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track whether or not let expressions failed to solve in solver #7982

Merged
merged 14 commits into from
Jan 26, 2024

Conversation

abadams
Copy link
Member

@abadams abadams commented Dec 6, 2023

After mutating an expression, the solver needs to know two things:

  1. Did the expression contain the variable we're solving for
  2. Was the expression successfully "solved" for the variable. I.e. the variable only appears once in the leftmost position. We need to know this to know property 1 of any subexpressions (e.g. does the right child of the expression contain the variable). This drives what transformations we do in ways that are guaranteed to terminate and not take exponential time.

We were tracking property 1 through lets but not property 2, and this meant we were doing unhelpful transformations in some cases. I found a case in the wild where this made a pipeline take > 1 hour to compile (I killed it after an hour). It may have been in an infinite transformation loop, or it might have just been exponential. Not sure.

As a drive-by while trying to figure out why it was taking so long I also made remainder checking cheaper in the common case of division by a constant, by avoiding a call to can_prove.

After mutating an expression, the solver needs to know two things:

1) Did the expression contain the variable we're solving for
2) Was the expression successfully "solved" for the variable. I.e. the
variable only appears once in the leftmost position. We need to know
this to know property 1 of any subexpressions (i.e. does the right child
of the expression contain the variable). This drives what
transformations we do in ways that are guaranteed to terminate and not
take exponential time.

We were tracking property 1 through lets but not property 2, and this
meant we were doing unhelpful transformations in some cases. I found a
case in the wild where this made a pipeline take > 1 hour to compile (I
killed it after an hour). It may have been in an infinite transformation
loop, or it might have just been exponential. Not sure.
@@ -44,18 +44,22 @@ class SolveExpression : public IRMutator {
map<Expr, CacheEntry, ExprCompare>::iterator iter = cache.find(e);
if (iter == cache.end()) {
// Not in the cache, call the base class version.
debug(4) << "Mutating " << e << " (" << uses_var << ")\n";
debug(4) << "Mutating " << e << " (" << uses_var << ", " << failed << ")\n";
bool old_uses_var = uses_var;
Copy link
Contributor

Choose a reason for hiding this comment

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

I sure which C++ had a terse way to express this pattern (that wasn't an RIAA class)...

@steven-johnson
Copy link
Contributor

Does this need new test(s)?

@abadams
Copy link
Member Author

abadams commented Dec 6, 2023

It's a failure to scale gracefully with large inputs. For the correctness of it, the existing solver tests are fine.

@abadams
Copy link
Member Author

abadams commented Dec 6, 2023

Looks like there are some real failures to investigate

@steven-johnson
Copy link
Contributor

I assume this is still in-progress?

@abadams
Copy link
Member Author

abadams commented Jan 16, 2024

Yes

@abadams
Copy link
Member Author

abadams commented Jan 22, 2024

The test failure was caused by use of an uninitialized value because I didn't check a return value. I added the must use result macro to reduce_expr_modulo to prevent this problem in future.

@abadams
Copy link
Member Author

abadams commented Jan 26, 2024

Failure unrelated. Just needs an approval

bool reduce_expr_modulo(const Expr &e, int64_t modulus, int64_t *remainder);
bool reduce_expr_modulo(const Expr &e, int64_t modulus, int64_t *remainder, const Scope<ModulusRemainder> &scope);
HALIDE_MUST_USE_RESULT bool reduce_expr_modulo(const Expr &e, int64_t modulus, int64_t *remainder);
HALIDE_MUST_USE_RESULT bool reduce_expr_modulo(const Expr &e, int64_t modulus, int64_t *remainder, const Scope<ModulusRemainder> &scope);
Copy link
Contributor

Choose a reason for hiding this comment

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

"must use result" should really be the default in the language, with the current behavior opt-in :-/

@abadams abadams merged commit 45d7850 into main Jan 26, 2024
18 of 19 checks passed
steven-johnson pushed a commit that referenced this pull request Feb 1, 2024
* Track whether or not let expressions failed to solve in solver

After mutating an expression, the solver needs to know two things:

1) Did the expression contain the variable we're solving for
2) Was the expression successfully "solved" for the variable. I.e. the
variable only appears once in the leftmost position. We need to know
this to know property 1 of any subexpressions (i.e. does the right child
of the expression contain the variable). This drives what
transformations we do in ways that are guaranteed to terminate and not
take exponential time.

We were tracking property 1 through lets but not property 2, and this
meant we were doing unhelpful transformations in some cases. I found a
case in the wild where this made a pipeline take > 1 hour to compile (I
killed it after an hour). It may have been in an infinite transformation
loop, or it might have just been exponential. Not sure.

* Remove surplus comma

* Fix use of uninitialized value that could cause bad transformation
ardier pushed a commit to ardier/Halide-mutation that referenced this pull request Mar 3, 2024
…e#7982)

* Track whether or not let expressions failed to solve in solver

After mutating an expression, the solver needs to know two things:

1) Did the expression contain the variable we're solving for
2) Was the expression successfully "solved" for the variable. I.e. the
variable only appears once in the leftmost position. We need to know
this to know property 1 of any subexpressions (i.e. does the right child
of the expression contain the variable). This drives what
transformations we do in ways that are guaranteed to terminate and not
take exponential time.

We were tracking property 1 through lets but not property 2, and this
meant we were doing unhelpful transformations in some cases. I found a
case in the wild where this made a pipeline take > 1 hour to compile (I
killed it after an hour). It may have been in an infinite transformation
loop, or it might have just been exponential. Not sure.

* Remove surplus comma

* Fix use of uninitialized value that could cause bad transformation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants