Skip to content

Completely Refactor #179

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

Merged
merged 102 commits into from
May 9, 2025
Merged

Completely Refactor #179

merged 102 commits into from
May 9, 2025

Conversation

willtebbutt
Copy link
Member

@willtebbutt willtebbutt commented Feb 27, 2025

Per #177 this is an attempt to significantly refactor the internals for robustness.

Todo:

  • understand what the desired semantics are regarding copying, per the discussion in Questions About Semantics #178
  • determine whether the task field of a TapedTask ought actually to be part of the public interface -- issue to discussion open at Accessing task field of Libtask TapedTask AdvancedPS.jl#113
  • implement an opt-in mechanism for nested calls containing produce statements
  • tidy up main body of code
  • document how PhiNodes are handled more thoroughly (include a worked example)
  • document the intent of the code + the high-level structure. In particular, re-work the README to explain what the public interface is, and what it does.
  • get all CI passing
  • check that we can make AdvancedPS and Turing work using this PR
  • kwarg support
  • improve nesting mechanism to ensure that we always catch everything
  • optimise nested calls (currently throwing away all return type information to get prototype running) see below
  • benchmark to ensure no regressions
  • add BBCode section to Mooncake docs so that this implementation is intelligible to everyone
  • add more high-level documentation

Linked Issues:

Closes #165
Closes #167
Closes #171
Closes #176

edit: performance optimisation. Everything (as far as I know) is now type-stable where it ought to be, except in the context of nested calls. These are somewhat harder to achieve optimal performance with (you need the IR of the thing that you're going to recurse into in order to figure out the return type of a call-which-might-produce -- since nasty things like recursion exist, it's not completely trivial to do this correctly).

@willtebbutt
Copy link
Member Author

willtebbutt commented May 5, 2025

Actually, @sunxd3 , where do you think might be a good location for the docs for the refs? I'm not 100% sure where to put them, and might be easier for someone who's head is less in the code.

@willtebbutt
Copy link
Member Author

@sunxd3 @mhauru this is ready for another pass when you get some time. Apologies for taking a while to address your comments.

Copy link
Member

@sunxd3 sunxd3 left a comment

Choose a reason for hiding this comment

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

Looks good to me, but maybe wait until another approve.

Copy link
Member

@mhauru mhauru left a comment

Choose a reason for hiding this comment

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

I gave a read to the changes since my last review, plus all the hard stuff in copyable_task.jl that I skipped last time. I can't say I understood everything that was going on there, but I think I got the main ideas, and didn't find anything objectionable, so happy to approve. I left a few comments for things I struggled to understand or where maybe there could be room for style improvements, but consider them optional.

error("unhandled statement which might produce $stmt")
end

# Find any `ID`s and replace them with calls to read whatever is stored
Copy link
Member

Choose a reason for hiding this comment

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

Could this recurring pattern be abstracted into a function?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is an excellent point. I'll give it a go. Would definitely be nice to get some code reuse.

@yebai
Copy link
Member

yebai commented May 6, 2025

To add to Xianda and Marku's comments, I suggest adding an implementation note to explain the key ideas, specifically how phi-nodes are handled and the copious use of Refs and opaque closures. This would significantly aid in fixing compatibility issues with future Julia releases.

Comment on lines +590 to +635
# PhiNodes:
#
# A single `PhiNode`
#
# ID(%1) = φ(ID(#1) => 1, ID(#2) => ID(%n))
#
# sets `ID(%1)` to either `1` or whatever value is currently associated to
# `ID(%n)`, depending upon whether the predecessor block was `ID(#1)` or
# `ID(#2)`. Consequently, a single `PhiNode` can be transformed into something
# along the lines of:
#
# ID(%1) = φ(ID(#1) => 1, ID(#2) => TupleRef(ref_ind_for_ID(%n)))
# ID(%2) = deref_phi(refs, ID(%1))
# set_ref_at!(refs, ref_ind_for_ID(%1), ID(%2))
#
# where `deref_phi` retrieves the value in position `ref_ind_for_ID(%n)` if
# ID(%1) is a `TupleRef`, and `1` otherwise, and `set_ref_at!` sets the `Ref`
# at position `ref_ind_for_ID(%1)` to the value of `ID(%2)`. See the actual
# implementations below.
#
# If we have multiple `PhiNode`s at the start of a block, we must run all of
# them, then dereference all of their values, and finally write all of the
# de-referenced values to the appropriate locations. This is because
# a. we require all `PhiNode`s appear together at the top of a given basic
# block, and
# b. the semantics of `PhiNode`s is that they are all "run" simultaneously. This
# only matters if one `PhiNode` in the block can refer to the value stored in
# the SSA associated to another. For example, something along the lines of:
#
# ID(%1) = φ(ID(#1) => 1, ID(#2) => ID(%2))
# ID(%2) = φ(ID(#1) => 1, ID(#2) => 2)
#
# (we leave it as an exercise for the reader to figure out why this particular
# semantic feature of `PhiNode`s is relevant in this specific case).
#
# So, in general, the code produced by this block will look roughly like
#
# ID(%1) = φ(...)
# ID(%2) = φ(...)
# ID(%3) = φ(...)
# ID(%4) = deref_phi(refs, ID(%1))
# ID(%5) = deref_phi(refs, ID(%2))
# ID(%6) = deref_phi(refs, ID(%3))
# set_ref_at!(refs, ref_ind_for_ID(%1), ID(%4))
# set_ref_at!(refs, ref_ind_for_ID(%2), ID(%5))
# set_ref_at!(refs, ref_ind_for_ID(%3), ID(%6))
Copy link
Member Author

Choose a reason for hiding this comment

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

PhiNode explainer

@willtebbutt
Copy link
Member Author

@yebai I've linked to the current phi node explainer above -- if you think it needs a more thorough explanation please let me know.

I take your point regarding the Refs and OpaqueClosures though. I'll add some extra docs there.

@willtebbutt
Copy link
Member Author

Okay. All comments are now addressed, and I believe that this is good to go. @yebai happy to merge if you are.

@yebai yebai requested a review from mhauru May 9, 2025 12:17
same `Array`, and in general the behaviour of a function depends on this relationship.

The second component of the transformation is implementing the `produce` mechanism, and the
ability to resume computation from where we produced. Roughly speaking, the `IRCode` must be
Copy link
Member

Choose a reason for hiding this comment

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

A hint on how resumption is implemented helps demystify what happens under the hood. I'm not sure what I wrote is accurate, so feel free to improve it.

Suggested change
ability to resume computation from where we produced. Roughly speaking, the `IRCode` must be
ability to resume computation from where we produced (via [`goto` ](https://docs.julialang.org/en/v1/base/base/#Base.@goto)statements). Roughly speaking, the `IRCode` must be

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, right, yes. It's similar, but not quite the same mechanism as the @goto statement. I've tweaked the docstring accordingly.

Copy link

github-actions bot commented May 9, 2025

Libtask.jl documentation for PR #179 is available at:
https://TuringLang.github.io/Libtask.jl/previews/PR179/

Copy link
Member

@mhauru mhauru left a comment

Choose a reason for hiding this comment

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

Thanks for the extra docs @willtebbutt, they are very helpful. Just a few trivial typos I spotted.

willtebbutt and others added 3 commits May 9, 2025 16:49
@willtebbutt
Copy link
Member Author

Thanks @mhauru

@yebai yebai merged commit acfc3df into main May 9, 2025
14 of 19 checks passed
@yebai yebai deleted the wct/refactor branch May 9, 2025 19:13
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.

Use MistyClosures for Libtask 2.0?
6 participants