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

Depreciate@submodel l ~ m in favour of l ~ to_submodel(m); rename generated_quantities to returned #696

Merged
merged 77 commits into from
Dec 4, 2024

Conversation

torfjelde
Copy link
Member

@torfjelde torfjelde commented Oct 23, 2024

This adds the @returned_quantities macro as discussed @yebai @mhauru

This is meant to be a replacement for @submodel macro, but without the ability to do automatic prefixing. It ends up looking like

julia> @model function demo1(x)
           x ~ Normal()
           return 1 + abs(x)
       end;

julia> @model function demo2(x, y, z)
            a ~ to_submode(prefix(demo1(x), "sub1"))
            b ~ to_submodel(prefix(demo1(y), "sub2"))
            return z ~ Uniform(-a, b)
       end;

julia> rand(demo2(missing, missing, 0.4))
(var"sub1.x" = 0.5865756059371534, var"sub2.x" = -0.25563799658500047)

Likely TODOs:

  • Add deprecation warning to @submodel telling the user to use @returned_quantities.
  • Do we do the renaming of generated_quantities to returned_quantities in this PR?

Fix #691

Copy link

codecov bot commented Oct 23, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.48%. Comparing base (82842bc) to head (b467c75).
Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #696      +/-   ##
==========================================
+ Coverage   86.38%   86.48%   +0.09%     
==========================================
  Files          35       35              
  Lines        4180     4209      +29     
==========================================
+ Hits         3611     3640      +29     
  Misses        569      569              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@coveralls
Copy link

coveralls commented Oct 23, 2024

Pull Request Test Coverage Report for Build 12144617184

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 34 of 36 (94.44%) changed or added relevant lines in 6 files are covered.
  • 30 unchanged lines in 11 files lost coverage.
  • Overall coverage increased (+0.09%) to 86.481%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/model.jl 14 16 87.5%
Files with Coverage Reduction New Missed Lines %
src/simple_varinfo.jl 1 86.08%
src/varnamedvector.jl 1 89.42%
src/sampler.jl 1 94.55%
src/debug_utils.jl 1 94.39%
ext/DynamicPPLEnzymeCoreExt.jl 1 0.0%
ext/DynamicPPLForwardDiffExt.jl 2 72.22%
src/utils.jl 3 83.16%
src/distribution_wrappers.jl 4 52.78%
src/contexts.jl 4 76.38%
src/values_as_in_model.jl 4 63.64%
Totals Coverage Status
Change from base Build 12138508432: 0.09%
Covered Lines: 3640
Relevant Lines: 4209

💛 - Coveralls

src/submodel_macro.jl Outdated Show resolved Hide resolved
@yebai
Copy link
Member

yebai commented Oct 24, 2024

@torfjelde I suggest we change the prefix feature to a prefix_variables model operation (feel free to come up with better names). Then we could use the same functionality prefix_variables in more places, e.g.

# submodel prefixing
julia> @model function demo2(x, y, z)
            a = @returned_quantities prefix_variables(demo1(x), "sub1")
            b = @returned_quantities prefix_variables(demo1(y), "sub2")
            return z ~ Uniform(-a, b)
       end;

julia> rand(demo2(missing, missing, 0.4))
(var"sub1.x" = 0.5865756059371534, var"sub2.x" = -0.25563799658500047)

# rand prefixing 

julia> ret = rand(prefix_variables(demo1(1.), "prior_sample"))

# generated quantities / predict 

julia> returned_quantities(prefix_variables(demo1(1.), "generated_var_"), chain) 

This would also help unify the syntax of @generated_qunatities and generated_quantities- IIRC, the only difference between them is that generated_quantities lacks the prefixing/renaming feature.

This could be further unified with NamedDist in the future. See, e.g., #414

@torfjelde
Copy link
Member Author

We already have DynamicPPL.prefix, though this doesn't do exactly what you want here. We could easily just add

prefix(model::Model, x) = contextualize(model, PrefixContext(model.context, Symbol(x)))

or something as an additional definition.

However, I'm a bit worred about

  1. It's quite verbose + a bit "too close to internals" for end-users.
  2. To achieve the same performance guarantees that we have currently, we need to wrap everything in Val before calling prefix(model, ...) 😕 This seems non-ideal to me vs. the current approach.

@yebai
Copy link
Member

yebai commented Oct 25, 2024

It's quite verbose + a bit "too close to internals" for end-users.

I like the @returned_quantities(prefix(model, "prefix_")) syntax because it is

  • less mysterious than @returned_quantities model "prefix_"
  • all the other model operations could share this, e.g. rand(prefix(model, "prefix_")) to verify the effects of prefixing, which is very useful

prefix(model, x) is NOT any closer to internals than any other model operation APIs. They are the same, so this is not a problem.

To achieve the same performance guarantees that we have currently, we need to wrap everything in Val before calling prefix(model, ...) 😕 This seems non-ideal to me vs. the current approach.

Point taken, but this is very minor and a bit subjective.

@torfjelde
Copy link
Member Author

torfjelde commented Oct 26, 2024

Point taken, but this is very minor and a bit subjective.

But this means that the user needs to be careful and do prefix(model, Val{:whatever}()); if we just do prefix(model, :whatever), this will lead to type-instabilities. Do we really want to force end-users of Turing.jl to explicitly use Val? 😕

@yebai
Copy link
Member

yebai commented Oct 27, 2024

It is a standard Julia performance trick, so it is okay.

By default, we can print a performance warning message when users call prefix(model, x::String) or similiar.

@yebai
Copy link
Member

yebai commented Oct 27, 2024

I'm also happy to turn prefix into a macro: @prefix(model, :prefix_) if that helps. Then we could do

@returned_quantities @prefix(model, :prefix_)

@torfjelde
Copy link
Member Author

Added a @prefix macro:) See the docstring of @returned_quantities for what it looks like 👍

torfjelde and others added 3 commits October 29, 2024 18:39
…cro' into torfjelde/returned-quantities-macro
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@yebai
Copy link
Member

yebai commented Oct 30, 2024

Thanks, @torfjelde; I'm happy with the changes.

To minimise interface confusion (prefix vs. @prefix, and @returned_quantities vs. returned_quantities), shall we consider keeping only @prefix and @returned_quantities and depreciating generated_quantities and prefix?

Thoughts? @mhauru and @penelopeysm

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.

For prefix/@prefix, maybe keep both but only export the macro? It sounds like unless you know what you are doing, you should use @prefix. And if you know what you're doing, you don't need it be exported. I do generally think it's a good idea to have a macro-free option available if possible.

For returned_quantities/@returned_quantities we still need both, because one is to be used outside of @model, the other inside, right? I forget what we concluded about this in our call, but I do worry users will mix the two up and get confusing errors.

src/submodel_macro.jl Outdated Show resolved Hide resolved
true

julia> # Or using some arbitrary expression.
@model submodel_prefix_expr() = a = @returned_quantities prefix=1 + 2 inner()
Copy link
Member

Choose a reason for hiding this comment

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

I found

@returned_quantities prefix=1 + 2 inner()

hard and unintuitive to parse. I think

@returned_quantities prefix=(1 + 2) inner()

would be much clearer. Not sure if this a documentation issue, or if we should disallow the former.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a documentation issue IMO, as this is not doing any special parsing but reliying on Julia's expression parsing.

docs/src/api.md Outdated Show resolved Hide resolved
@yebai
Copy link
Member

yebai commented Oct 30, 2024

For returned_quantities/@returned_quantities we still need both, because one is to be used outside of @model, the other inside, right?

generated_quantities allows users to fix model parameter values and/or accept MCMC chain objects.
We can throw an error if users try to pass fixed parameter values or chain objects to @returned_quantities called within a model.

Then, @returned_quantities can match generated_quantities / returned_quantities exactly, thus allowing us to remove the generated_quantities / returned_quantities altogether.

@torfjelde
Copy link
Member Author

Then, @returned_quantities can match generated_quantities / returned_quantities exactly, thus allowing us to remove the generated_quantities / returned_quantities altogether.

Just so we're all on the same page: @returned_quantities and returned_quantities will not match since the former only takes a single argument, while the other takes two, right? If so, then why would we want to raise explicit errors for incorrect arguments provided vs. just letting Julia raise the "not implemented error"?

@torfjelde
Copy link
Member Author

Deprecated generated_quantities in favour of returned_quantities + removed the prefix=... argument for @prefix.

@torfjelde
Copy link
Member Author

torfjelde commented Nov 28, 2024

For (2), we could allow theta ~ to_submodel(model_instance, prefix=false) to support your current workflow.

Final question @yebai , in which struct do you want the prefix=false option to live? Sampleable or ReturnedModelWrapper?

EDIT: I put it in Sampleable for now. Let me know if you feel differently.

@yebai
Copy link
Member

yebai commented Nov 29, 2024

I put it in Sampleable for now. Let me know if you feel differently.

That looks good to me.

@torfjelde
Copy link
Member Author

Hmm, thought this would be a simple thing to fix (it's just a depwarn happening that I didn't expect to happen). Think there's something funky going on with Documenter.jl...

@mhauru mhauru mentioned this pull request Dec 2, 2024
@yebai yebai changed the title Adds @returned_quantities macro Depreciate@submodel l ~ m in favour of l ~ to_submodel(m); rename generated_quantities to returned Dec 2, 2024
@torfjelde
Copy link
Member Author

This is now ready to go I think:) Anyone wants a final lookover or just hit the green button?

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'm happy, just had one question about a docstring. Merge when you consider it ready. Thanks @torfjelde for doing all the heavy lifting here!

src/model.jl Outdated Show resolved Hide resolved
@torfjelde
Copy link
Member Author

Seems like we're hitting OOM failures unrelated to this PR again 😕

@yebai yebai merged commit 2252a9b into master Dec 4, 2024
12 of 13 checks passed
@yebai yebai deleted the torfjelde/returned-quantities-macro branch December 4, 2024 10:43
@yebai
Copy link
Member

yebai commented Dec 4, 2024

I merged it manually. This PR has some very nice improvements—many thanks to @torfjelde, @mhauru, and all who helped.

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.

submodel and generated_quantities operations on models
7 participants