-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[SLM][Bugfix] Output debug functions as impure #16687
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
[SLM][Bugfix] Output debug functions as impure #16687
Conversation
|
This commit is currently marked as a draft, as it depends on a related bugfix in #16684. |
Prior to this commit, debug functions were generated with `relax.call_pure_packed`. This resulted in unexpected behavior, as `nn.op.print_` can be optimized away as a pure function.r This commit updates debug functions to be generated as impure functions. This requires removing the `with bb.dataflow()` blocks in the SLM-to-relax conversions, as impure functions may not be used in a dataflow block. To restore dataflow blocks when legal, the `ConvertToDataflow` pass is applied.
3554235 to
ffd47e6
Compare
|
The prerequisite PR#16684 has landed, and this PR is now ready for review. |
slyubomirsky
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for ensuring that function purity is correctly used.
Regarding the changes to labeling function purity, inferring purity is a welcome change, but I think there are cases that could prove problematic. One would be recursive or mutually recursive functions, as they were the original reason that annotating purity was mandatory rather than optional and inferred. We have generally wanted to avoid needing multiple passes to properly infer StructInfo in Relax--it may be necessary to detect those cases and warn the user to manually label purity for them. Recursive local functions could also be a problematic case for a similar reason.
include/tvm/relax/expr.h
Outdated
| /*! | ||
| * \brief Mimics the constructor but without body Expr. | ||
| * \note ret_struct_info is required, since it can not deduced by the body. | ||
| * \note `ret_struct_info` and `is_pure` are required, since it can not deduced by the body. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * \note `ret_struct_info` and `is_pure` are required, since it can not deduced by the body. | |
| * \note `ret_struct_info` and `is_pure` are required, since they cannot be deduced from the body. |
Might as well correct the typo too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, and corrected.
|
|
||
|
|
||
| def function(is_pure: bool = True, is_private: bool = False) -> frame.FunctionFrame: | ||
| def function(is_pure: Optional[bool] = None, is_private: bool = False) -> frame.FunctionFrame: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs for is_pure should mention that it's inferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And updated.
| ret_struct_info = body_sinfo; | ||
| } | ||
|
|
||
| bool is_pure = [&]() -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would this behave in the recursive or mutually recursive case? That was the reason for not inferring purity in the first place. Detecting those cases and warning the user that they need to be explicitly annotated would be a reasonable approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, the struct info for a GlobalVar is determined only by the forward declaration, and not by its body. The default for this is determined here (here), where a function is pure unless explicitly annotated otherwise.
We have a similar problem with return values, where the inferred return type of a function may not omitted, resulting in incorrect struct inference in the calling scope. I think the long-term solution to both is the same: To represent the lack of information while parsing, and to infer full information as a post-proc.
|
Good points regarding default values. I think I would describe the current state a bit differently, as currently we do not require function purity to be explicitly annotated. At the C++ level, the Python API, the Relax block builder, and in TVMScript, we provide default arguments stating that a function is pure. This can be overridden in most cases, but in some places such as the relax block builder, there isn't a way to override this default. So, rather than viewing this PR as changing from explicit purity annotations to implicit purity inference, I'd argue that it is only changing the method of purity inference. On |
|
Long-term, I'd like to move some of the purity inference out of the parser and into a later transformation pass. The sequence of PRs would look something like the following:
|
|
I would be in favor of having an extra pass to infer purity, since it clearly seems to be in reach. However, when this subject was raised in design discussions before, the suggestion was turned down due to a preference of having only one pass (normalization) handle StructInfo, of which purity is a part. We could reopen the discussion, of course. |
I'd be interested in hearing the discussion on it. I wouldn't mind having all |
|
I think the key next step would be to avoid producing incorrect information. If the |
|
Oh my mistake, a year ago we actually said that trying to infer it is what we would want. See meeting notes from Feb. 28, 2023 (I can't put in a link to a specific heading). This would be consistent with what we talked about so we should give consideration to the approach of marking unknown purity. I think it's a good idea and certainly worth discussing, though it would mean that we might have to rerun the pass at different points. |
Prior to this commit, debug functions were generated with
relax.call_pure_packed. This resulted in unexpected behavior, asnn.op.print_can be optimized away as a pure function.This commit updates debug functions to be generated as impure functions. This requires removing the
with bb.dataflow()blocks in the SLM-to-relax conversions, as impure functions may not be used in a dataflow block. To restore dataflow blocks when legal, theConvertToDataflowpass is applied.