8000 Introduce guard_or_true, guard_or_false by laithsakka · Pull Request #148430 · pytorch/pytorch · GitHub
[go: up one dir, main page]

Skip to content

Introduce guard_or_true, guard_or_false #148430

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

Closed
wants to merge 12 commits into from

Conversation

laithsakka
Copy link
Contributor
@laithsakka laithsakka commented Mar 4, 2025

Stack from ghstack (oldest at bottom):

some context in this document:
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

But TLDR;
guard_or_true, guard_or_false are better than guard_size_oblivious due to :

  • Easier to reason about what assumptions we are making while reading the code.
  • Avoid size_oblivious complexity that is not needed.
  • Avoid unsoundness that could make guard_size_oblivious(a==1) be true when its not true for some vaue a during runtime.
  • Less data dependent errors for some cases: ex, when doing guard_size_oblivious(a==1) and we know a is a tensor size, if it's traced with a=u1-u2 guard_size_oblivious(a==1) will throw a data dependent error but guard_else_false will just return False.

How is it different from statically_known_true??

if(cond): (normal guarding) will try to evaluate statically and guard on the condition, willing to restrict input space to evaluate cond. if it fails to evaluate due to data dependent error will throw an exception (that could be converted to graph break in some situations).

statically_known_true(cond): would be used when you never want to add a guard (restrict your input space), but just want to do a best effort check to see if you can infer that something is true/false ONLY based on existing constraints.

guard_or_true(cond)/guard_or_false(cond): Those would be used in situations you prefer to guard and know the result of the expression over not guarding, but in case you hit a data dependent error you are ok with just returning true or false.
Some reasons you might be ok with returning true/false instead could be:

  1. It's an optimization I do not want to fail for not performing optimization.
  2. I am willing to deviate from the normal semantics when I have unbacked for the benefit of not failing (See the doc above for more details).

definitely_true(cond): same as guard_or_false(cond) except does not try to do static eval for unbacked (planning to deprecate it and replace uses with guard_or_false or make it alias to guard_or_false)

cc @ezyang @SherlockNoMad @EikanWang @jgong5 @wenzhe-nrv @voznesenskym @penguinwu @Guobing-Chen @XiaobingSuper @zhuhaozhe @blzheng @jiayisunx @ipiszy @yf225 @chenyang78 @kadeng @muchulee8 @amjames @chauhang @aakhundov

Copy link
pytorch-bot bot commented Mar 4, 2025

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/148430

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure, 50 Pending

As of commit b566522 with merge base 5a7588f (image):

NEW FAILURE - The following job has failed:

UNSTABLE - The following job is marked as unstable, possibly due to flakiness on trunk:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

laithsakka added a commit that referenced this pull request Mar 4, 2025
…ompositions.py

ghstack-source-id: 622ec83
Pull Request resolved: #148430
@laithsakka laithsakka changed the title add gaurd else true, guard else false and gaurd_size_oblivious in decompositions.py Introduce guard_else_true, guard_else_false and avoid guard_size_oblivious in decompositions.py Mar 4, 2025
@laithsakka laithsakka requested a review from bobrenjc93 March 4, 2025 07:33
…d_size_oblivious in decompositions.py"


some context in this document:
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

But TLDR; 
guard_else_true, guard_else_false are better than guard_size_oblivious due to :
1) easier to reason about what assumptions we are making while reading the code. 
2) avoid guard_size_oblivious complexity that is not needed. 
3) avoid unsoundness that could make guard_size_oblivious(a==1) be True when its not True for some a during runtime. 
4) less data dependent errors for some cases: ex, when  guard_size_oblivious(a==1)  we know a is a tensor size, but its traced with a=u1-u2 for example 
guard_size_oblivious(a==1)  will throw data dependent error but guard_else_false will just return false. 

cc ezyang SherlockNoMad EikanWang jgong5 wenzhe-nrv voznesenskym penguinwu Guobing-Chen XiaobingSuper zhuhaozhe blzheng jiayisunx ipiszy yf225 chenyang78 kadeng muchulee8 amjames chauhang aakhundov

[ghstack-poisoned]
laithsakka added a commit that referenced this pull request Mar 4, 2025
…ompositions.py

ghstack-source-id: ede0b50
Pull Request resolved: #148430
@laithsakka laithsakka requested a review from pianpwk March 4, 2025 07:53
…d_size_oblivious in decompositions.py"


some context in this document:
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

But TLDR; 
guard_else_true, guard_else_false are better than guard_size_oblivious due to :
1) easier to reason about what assumptions we are making while reading the code. 
2) avoid guard_size_oblivious complexity that is not needed. 
3) avoid unsoundness that could make guard_size_oblivious(a==1) be True when its not True for some a during runtime. 
4) less data dependent errors for some cases: ex, when  guard_size_oblivious(a==1)  we know a is a tensor size, but its traced with a=u1-u2 for example 
guard_size_oblivious(a==1)  will throw data dependent error but guard_else_false will just return false. 

cc ezyang SherlockNoMad EikanWang jgong5 wenzhe-nrv voznesenskym penguinwu Guobing-Chen XiaobingSuper zhuhaozhe blzheng jiayisunx ipiszy yf225 chenyang78 kadeng muchulee8 amjames chauhang aakhundov

[ghstack-poisoned]
laithsakka added a commit that referenced this pull request Mar 4, 2025
…ompositions.py

ghstack-source-id: 424fdbf
Pull Request resolved: #148430
@bobrenjc93
Copy link
Contributor
bobrenjc93 commented Mar 4, 2025

I instinctively don't like the name guard_else_false and guard_else_true. I have a much stronger preference for guard_or_false and guard_or_true since 1) there is already precedence for this type of wording in libraries like Rust's option type (https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or) and 2) it's less verbose 3) it reads more naturally to me. I think we've discussed this a few times, but since we are nearing landing the first PRs, I'd much rather we have the discussion explicitly than implicitly. Do you feel strongly about the else over or? If not can we please change to use the "road more traveled" naming for this type of API?

I do think we want to get buy in from our partners in dynamo/inductor before landing a change like this. As we discussed yesterday, there is nothing more permanent than a temporary commit and this is doubly true for public API changes. I'm adding @jansel, @eellison, @zou3519 and @anijain2305 as reviewers but feel free to add others that you think might have strong opinions about this change.

Copy link
Contributor
@eellison eellison left a comment

Choose a reason for hiding this comment

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

not clear to me what the overlap is of this versus statically_known_true. even in this pr we have some wrong usages. i think we should be careful about adding more apis.

I also dont like the name guard_else_false and guard_else_true.

Comment on lines 327 to 328
and guard_else_true(self.size(0) > 0)
and guard_else_false(input2.size(0) == 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we just be using statically_known_true ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

statically_known_true will try to evaluate this statically, but it will fail in situations of unbacked for example
for the check guard_else_false(input2.size(0) == 1) when self.size(0) is u0 [0, infinity], in this case,
(1) we can not tell statically if u0 ==1 or not.
(2) we can not guard also on u0 being 1, because we do not have a hint.

so what we do, we say we are willing to deviate here from the normal pytorch semantic for the runtime input of input2.size(0)==1 and always take the false branch.
We used to do that indirectly using guard_size_oblivious this is more explicit
see this document for more information about the intuition of guard_size_oblivious
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

Copy link
Contributor

Choose a reason for hiding this comment

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

The decompositions here are not pytorch semantics. they are optimizations as decompositions.

If guard_else_false(input2.size(0) == 1) can be statically_known_true(input2.size(0) == 1).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i see do you want decompositions to fail if input2.size(0) is unbacked?
if thats the case we can address that in different PR, since this PR does not change current semantics
but what you are proposing is to avoid (the avoidance of 0/1 specialization in this case)

Copy link
Contributor
@eellison eellison Mar 4, 2025

Choose a reason for hiding this comment

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

I think it would be more compelling to show use cases of this api where we should actually be using them. all of the usages here are not what we want.

Comment on lines 264 to 276
if guard_size_oblivious(self.shape[1] == 1) or guard_size_oblivious(
if guard_else_false(self.shape[1] == 1) or guard_else_false(
batch2.shape[2] == 1
):
out = (self.unsqueeze(-1) * batch2.unsqueeze(1)).sum(dim=2)
return out
if self.device.type == "cpu":
if guard_size_oblivious(self.size(1) == 1) and guard_size_oblivious(
if guard_else_false(self.size(1) == 1) and guard_else_false(
batch2.size(-1) == 1
):
Copy link
Contributor

Choose a reason for hiding this comment

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

Since these are all optimizations, we should just be doing statically_known_true. or potentially an api that is only willing to add guards on non-export use cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mhmm this mostly keep the semantics of guard_size_oblivious,
moving to statically_known_true will change the behaviour.
i will start failing with unbacked.

this API does what you explained, just for unbacked vs backed instead of export vs non export.

Copy link
Contributor
@bobrenjc93 bobrenjc93 Mar 4, 2025

Choose a reason for hiding this comment

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

@laithsakka I think what @eellison is saying is that in these cases we should change the behavior and move to statically_known_true even for the case of backed symints. Because these are optimizations, we don't want to guard, even if we can.

Copy link
Contributor Author
@laithsakka laithsakka Mar 4, 2025

Choose a reason for hiding this comment

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

if thats the case we can address that in different PR since this one does not change existing behavior.
comment but from his statement "or potentially an api that is only willing to add guards on non-export use cases." it might not be what he wanted

@laithsakka
Copy link
Contributor Author
laithsakka commented Mar 4, 2025

i initially did not have strong opinion about the naming but as i implemented it i found guard_or_false, does not make sense,

first x_or_false is x . logically
and if it was meant to read (either guard or false) I still fell that guard_else_false is better because it impose ordering
guard_otherwise_false works for me also.
the ordering is you try to guard first then if you fail you return false.

@bobrenjc93
Copy link
Contributor
bobrenjc93 commented Mar 4, 2025

first x_or_false is x . logically

Can you elaborate?

In python

>>> None or False
False

In javascript

>>> null || false
false

Also FWIW chatgpt also concurs:
Screenshot 2025-03-04 at 10 30 38 AM

@laithsakka
Copy link
Contributor Author
laithsakka commented Mar 4, 2025

first x_or_false is x . logically

Can you elaborate?

In python

>>> None or False
False

In javascript

>>> null || false
false

Also FWIW chatgpt also concurs: Screenshot 2025-03-04 at 10 30 38 AM

yeh but this is not null_or_false this guard_or_false?
where guard has side effects.

@laithsakka
Copy link
Contributor Author

first x_or_false is x . logically

Can you elaborate?
In python

>>> None or False
False

In javascript

>>> null || false
false

Also FWIW chatgpt also concurs: Screenshot 2025-03-04 at 10 30 38 AM

yeh but this is not null_or_false this guard_or_false? where guard has side effects.

if others agree i dont mind changing the name .

@bobrenjc93
Copy link
Contributor
bobrenjc93 commented Mar 4, 2025

where guard has side effects.

Guard only has side effects in the case where we guard and return True. The main point I'm making is when naming APIs you generally want to conform to common naming patterns rather than invent your own. In the case you decide to take the road less traveled, you should have a very good reason to and so far I'm unconvinced we have one.

@laithsakka laithsakka requested a review from aorenste March 4, 2025 19:05
@laithsakka laithsakka changed the title Introduce guard_else_true, guard_else_false and avoid guard_size_oblivious in decompositions.py Introduce guard_or_true, guard_or_false and avoid guard_size_oblivious in decompositions.py Mar 4, 2025
…ze_oblivious in decompositions.py"


some context in this document:
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

But TLDR; 
guard_else_true, guard_else_false are better than guard_size_oblivious due to :
1) easier to reason about what assumptions we are making while reading the code. 
2) avoid size_oblivious complexity that is not needed. 
3) avoid unsoundness that could make guard_size_oblivious(a==1) be True when its not True for some a during runtime. 
4) less data dependent errors for some cases: ex, when  guard_size_oblivious(a==1)  we know a is a tensor size, but its traced with a=u1-u2 for example 
guard_size_oblivious(a==1)  will throw data dependent error but guard_else_false will just return false. 

cc ezyang SherlockNoMad EikanWang jgong5 wenzhe-nrv voznesenskym penguinwu Guobing-Chen XiaobingSuper zhuhaozhe blzheng jiayisunx ipiszy yf225 chenyang78 kadeng muchulee8 amjames chauhang aakhundov

[ghstack-poisoned]
laithsakka added a commit that referenced this pull request Mar 4, 2025
…ompositions.py

ghstack-source-id: 6c89fad
Pull Request resolved: #148430
@eellison
Copy link
Contributor
eellison commented Mar 4, 2025

I don't think we should have both true and false versions of this. We only have statically_known_true. Not also statically_known_false.

@laithsakka
Copy link
Contributor Author

@pytorchbot merge

@pytorchmergebot
Copy link
Collaborator

Merge started

Your change will be merged once all checks pass (ETA 0-4 Hours).

Learn more about merging in the wiki.

Questions? Feedback? Please reach out to the PyTorch DevX Team

Advanced Debugging
Check the merge workflow status
here

@pytorchmergebot
Copy link
Collaborator

Merge failed

Reason: 1 jobs have failed, first few of them are: inductor / unit-test / cuda12.6-py3.12-gcc9-sm86 / build

Details for Dev Infra team Raised by workflow job

@laithsakka
Copy link
Contributor Author

@pytorchbot commit -i

Copy link
pytorch-bot bot commented Mar 27, 2025

❌ 🤖 pytorchbot command failed:

@pytorchbot: error: argument command: invalid choice: 'commit' (choose from 'merge', 'revert', 'rebase', 'label', 'drci', 'cherry-pick', 'close')

usage: @pytorchbot [-h] {merge,revert,rebase,label,drci,cherry-pick,close} ...

Try @pytorchbot --help for more info.

@laithsakka
Copy link
Contributor Author

@pytorchbot merge -i

@pytorchmergebot
Copy link
Collaborator

@laithsakka
Copy link
Contributor Author
laithsakka commented Mar 27, 2025

cancel

@pytorchmergebot
Copy link
Collaborator

Can't merge closed PR #148430

@laithsakka laithsakka reopened this Mar 27, 2025
@laithsakka
Copy link
Contributor Author

@pytorchbot commit

Copy link
pytorch-bot bot commented Mar 27, 2025

❌ 🤖 pytorchbot command failed:

@pytorchbot: error: argument command: invalid choice: 'commit' (choose from 'merge', 'revert', 'rebase', 'label', 'drci', 'cherry-pick', 'close')

usage: @pytorchbot [-h] {merge,revert,rebase,label,drci,cherry-pick,close} ...

Try @pytorchbot --help for more info.

@laithsakka laithsakka removed Merged ci-no-td Do not run TD on this PR labels Mar 27, 2025
@laithsakka
Copy link
Contributor Author
laithsakka commented Mar 27, 2025

can

@laithsakka
Copy link
Contributor Author

alreaded landed

@laithsakka laithsakka closed this Mar 27, 2025
amathewc pushed a commit to amathewc/pytorch that referenced this pull request Apr 17, 2025
some context in this document:
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

But TLDR;
`guard_or_true`, `guard_or_false` are better than `guard_size_oblivious` due to :
- Easier to reason about what assumptions we are making while reading the code.
- Avoid size_oblivious complexity that is not needed.
- Avoid unsoundness that could make `guard_size_oblivious(a==1)` be true when its not true for some vaue `a` during runtime.
- Less data dependent errors for some cases: ex, when doing `guard_size_oblivious(a==1)` and we know `a` is a tensor size, if it's traced with `a=u1-u2` `guard_size_oblivious(a==1)` will throw a data dependent error but `guard_else_false` will just return `False`.

### How is it different from statically_known_true??
**`if(cond)`:** (normal guarding) will try to evaluate statically and guard on the condition, willing to restrict input space to evaluate cond. if it fails to evaluate due to data dependent error will throw an exception (that could be converted to graph break in some situations).

**`statically_known_true(cond)`:** would be used when you never want to add a guard (restrict your input space), but just want to do a best effort check to see if you can infer that something is true/false ONLY based on existing constraints.

**`guard_or_true(cond)`/`guard_or_false(cond)`:** Those would be used in situations you prefer to guard and know the result of the expression over not guarding, but in case you hit a data dependent error you are ok with just returning true or false.
Some reasons you might be ok with returning true/false instead could be:
1. It's an optimization I do not want to fail for not performing optimization.
2. I am willing to deviate from the normal semantics when I have unbacked for the benefit of not failing (See the doc above for more details).

**`definitely_true(cond)`**: same as `guard_or_false(cond)` except does not try to do static eval for unbacked (planning to deprecate it and replace uses with `guard_or_false` or make it alias to `guard_or_false`)

Pull Request resolved: pytorch#148430
Approved by: https://github.com/bobrenjc93
amathewc pushed a commit to amathewc/pytorch that referenced this pull request Apr 17, 2025
amathewc pushed a commit to amathewc/pytorch that referenced this pull request Apr 17, 2025
some context in this document:
https://docs.google.com/document/d/18nJsj-F2C_QXO7ClwzPcAUENQ-B440B43W7DdDnlDt4/edit?tab=t.0#heading=h.pgebnyi7pocj

But TLDR;
`guard_or_true`, `guard_or_false` are better than `guard_size_oblivious` due to :
- Easier to reason about what assumptions we are making while reading the code.
- Avoid size_oblivious complexity that is not needed.
- Avoid unsoundness that could make `guard_size_oblivious(a==1)` be true when its not true for some vaue `a` during runtime.
- Less data dependent errors for some cases: ex, when doing `guard_size_oblivious(a==1)` and we know `a` is a tensor size, if it's traced with `a=u1-u2` `guard_size_oblivious(a==1)` will throw a data dependent error but `guard_else_false` will just return `False`.

### How is it different from statically_known_true??
**`if(cond)`:** (normal guarding) will try to evaluate statically and guard on the condition, willing to restrict input space to evaluate cond. if it fails to evaluate due to data dependent error will throw an exception (that could be converted to graph break in some situations).

**`statically_known_true(cond)`:** would be used when you never want to add a guard (restrict your input space), but just want to do a best effort check to see if you can infer that something is true/false ONLY based on existing constraints.

**`guard_or_true(cond)`/`guard_or_false(cond)`:** Those would be used in situations you prefer to guard and know the result of the expression over not guarding, but in case you hit a data dependent error you are ok with just returning true or false.
Some reasons you might be ok with returning true/false instead could be:
1. It's an optimization I do not want to fail for not performing optimization.
2. I am willing to deviate from the normal semantics when I have unbacked for the benefit of not failing (See the doc above for more details).

**`definitely_true(cond)`**: same as `guard_or_false(cond)` except does not try to do static eval for unbacked (planning to deprecate it and replace uses with `guard_or_false` or make it alias to `guard_or_false`)

Pull Request resolved: pytorch#148430
Approved by: https://github.com/bobrenjc93
Divigroup-RAP pushed a commit to Divigroup-RAP/PYTORCH that referenced this pull request Apr 22, 2025
Divigroup-RAP pushed a commit to Divigroup-RAP/PYTORCH that referenced this pull request Apr 22, 2025
@github-actions github-actions bot deleted the gh/laithsakka/112/head branch May 2, 2025 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0