-
Notifications
You must be signed in to change notification settings - Fork 35
[Merged by Bors] - **BREAKING** Make evaluator a method of the model function #316
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
Conversation
Turing tests fail with some weird precompilation error locally: julia> using Turing
[ Info: Precompiling Turing [fce5fe82-541a-59a6-adf8-730c64b5f9a0]
ERROR: LoadError: LoadError: cannot assign a value to variable Core.Core from module Turing But since the same error occurs with the latest release of DynamicPPL I guess it's not caused by this PR. |
bors try |
tryBuild failed: |
At least the errors can be reproduced with CI 🙂 At least one warning seems to be caused by a bug introduced in the latest release of DistributionsAD, I'll open a PR. |
The errors can be reproduced with the master branch as well: #317 So I am sure the approach here does not cause them. |
Wait, how does this work for multiple models named |
No, this was my initial thought but if you define e.g. |
Ooooh true! But this does mean that we can't actually do Btw, I do quite like the idea of just having the evaluator available like this irregardless of trying to support dispatching on |
No. Personally I don't think this is too limiting since you can still dispatch on the arguments as well. I.e., in this case you just have to include more type parameters in your dispatch. |
Good point 👍 I'm quite happy with this approach:) The only thing I'm worried about is serialization: deserializing a |
Serialization should not be an issue - |
It is though. Try running the following two blocks in two different REPLs. REPL 1: julia> using DynamicPPL, Distributions, Serialization
julia> @model demo() = x ~ Normal()
demo (generic function with 2 methods)
julia> m = demo();
julia> serialize("model.jls", m) REPL 2: julia> using DynamicPPL, Distributions, Serialization
julia> m = deserialize("model.jls")
ERROR: UndefVarError: #demo not defined
[...] This works just fine in the current release because the evaluator will be serialized together with the model. |
Oh yeah, that's not surprising actually, generic functions have to be redefined in Julia and are not serialized/deserialized. Only closures are. It worked before automatically since |
So you have to load the model and redefine the function with |
Yeah exactly, that was my point:)
Eeehh and is that something we want to be the case? 😕 |
I don't think it's a design goal but a limitation of the Julia serializer that we can live with. Usually you know which models you work with and then you can also redefine/rewrite the In any case, IMO one should not use serialization for persistent storage since there are no guarantees that one can deserialize it with a different Julia or package version. JLSO.jl solves these issues but is still limited by the Julia serializer. |
Hmm, not sure I agree with this given that we there's a way to circumvent it, i.e. the current approach.
Really not the case in modelling projects. You usually have a bunch of models but a single post-processing/visualize/analysis script which makes few assumptions about the model (maybe that all the models have some common target variable or something). Being able to just load the model used to produce a chain is then very useful.
I like this though. But this also works with the current anonymous functions. Though it def ends up being nicer with the approach in this PR.
Sure but this is AFAIK an irrelevant issue for exactly the reason you mention, i.e. that JLSO.jl uses the Julia serializer under the hood anyways. |
Personally I think this one disadvantage (that you have to make sure the function is defined if you deserialize a model) is completely outweighed by the advantage of removing hidden closures and being able to access the evaluator and dispatch on models without any hacks or heuristics and without having to instantiate a model. Even more so since the disadvantage is caused by a limitation of the Julia serializer itself. Moreover, the serialization tests in https://github.com/TuringLang/DynamicPPL.jl/blob/master/test/serialization.jl all pass. These tests were broken in previous design iterations and one reason (surely not the only one) for sticking with the current design was that it fixed the |
I'm on the fence atm, i.e. not sure what I prefer. I def agree that this is a much nicer approach and if it wasn't for the serialization issue I would have been 100% on board. My issue is that from a user-perspective we're unnecessarily (again, from the user perspective) removing a feature. I don't like that 😕
I don't understand why this is very relevant though. Telling a user "we can do this, but really Julia should be able to handle this other case so we're going to not support the feature and wait until maybe Julia can handle our approach instead" seems a bit weird, no? 😅 Usually we take the opposite approach: hack around Julia's limitations, and then once they're no longer there we drop the hacks. |
How about we leave it for a couple of days, and have a chat about it? Maybe we join the Thursday meeting with @yebai and @phipsgabler ? Should get some input from the rest of the team too. And I really want to emphasize that I'm not just trying to be a contrarian here; I genuinely think this is the better approach, but I also really don't like that it hurts the user a bit 😕 |
We should just provide a proper way to store models instead or with some improved serialization 🤷 The issue is not newly introduced by this PR, already currently it exists for anyone who tries to serialize a IMO this PR does not only degrade user experience and remove features but also adds features, e.g., now you can dispatch on your model without having to instantiate it, with an arguably intuitive design (you can just dispatch on |
If we had this, I'm 100% on board:)
Sure, but nobody does that 😅
Exactly, you lose the ability to make the traits persistent between serialization. But given that model traits is not a thing yet I'm more inclined towards the route which provides a feature with some limitations but without any breaking changes (this PR is breaking since the serialization behavior now changes).
Yeah, yeah of course! I'm not saying this PR doesn't have any benefits, haha. I'm just saying that it also comes with one annoying drawback, and because of this I don't want to rush it. As of right now, I do think this PR will be supersede #314 , but I want to give it a bit more thought. |
Yes, no need to rush it. I just thought this morning that the idea could actually work and wanted to put up a PR. It turned out much easier and cleaner than I had expected 🙂 And ideally we would improve and fix how models are saved first to avoid any regression. |
I think I love this. If it can be made to work. I just need to understand the serialization issue further... give me some time :D |
Btw, this PR "solve" (we shouldn't be serializing/deserializing using different versions of a package anyways, but this at least makes it more likely to work):
On this PR, the deserialized version will still point to evaluator of |
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.
bors try |
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.
Dope:)
Dope:) We also need a version bump btw. And this would be breaking, right? As we mentioned before, it can break serialized models, etc. |
Yes, I did not want to rush it and hence did not plan to merge it before the meeting. I thought maybe @yebai wants to have a look at the PR as well. |
Didn't we have a dev branch for that? :D |
We deleted it a long time ago, mostly since it did not encourage to release new features timely, dev diverged from the master branch and syncing in a more or less automatic way created a lot of useless commits and was messy in general. |
@torfjelde I updated the version number to 0.15.0. |
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.
Many thanks @devmotion and @torfjelde - looks good to me!
bors r+ |
This PR implements the idea in https://github.com/TuringLang/DynamicPPL.jl/pull/314/files#r693968700. All DynamicPPL tests pass, I only had to update the doctests (since the number of methods that are created by `@model` changed from 1 to 2). **This PR is breaking:** it breaks deserialization of models since Julia can't deserialize generic functions (see below). It allows to do the following: ```julia julia> using DynamicPPL, Distributions julia> @macroexpand @model demo() = x ~ Normal() quote function demo(__model__::Model, __varinfo__::AbstractVarInfo, __context__::DynamicPPL.AbstractContext; ) #= REPL[4]:1 =# begin var"##vn#257" = (VarName){:x}() var"##inds#258" = () var"##isassumption#259" = begin let var"##vn#260" = (VarName){:x}() if (DynamicPPL.contextual_isassumption)(__context__, var"##vn#260") if !((DynamicPPL.inargnames)(var"##vn#260", __model__)) || (DynamicPPL.inmissings)(var"##vn#260", __model__) true else x === missing end else false end end end if var"##isassumption#259" x = (DynamicPPL.tilde_assume!)(__context__, (DynamicPPL.unwrap_right_vn)((DynamicPPL.check_tilde_rhs)(Normal()), var"##vn#257")..., var"##inds#258", __varinfo__) else if !((DynamicPPL.inargnames)(var"##vn#257", __model__)) x = (DynamicPPL.getvalue_nested)(__context__, var"##vn#257") end (DynamicPPL.tilde_observe!)(__context__, (DynamicPPL.check_tilde_rhs)(Normal()), x, var"##vn#257", var"##inds#258", __varinfo__) end end end begin $(Expr(:meta, :doc)) function demo(; ) #= REPL[4]:1 =# return (Model)(:demo, demo, NamedTuple(), NamedTuple()) end end end julia> @model demo() = x ~ Normal() demo (generic function with 2 methods) julia> f(x) = false f (generic function with 1 method) julia> f(::Model{typeof(demo)}) = true f (generic function with 2 methods) julia> f(demo()) true julia> demo() isa Model{typeof(demo)} true ``` Co-authored-by: David Widmann <[email protected]>
Build failed: |
bors r+ |
This PR implements the idea in https://github.com/TuringLang/DynamicPPL.jl/pull/314/files#r693968700. All DynamicPPL tests pass, I only had to update the doctests (since the number of methods that are created by `@model` changed from 1 to 2). **This PR is breaking:** it breaks deserialization of models since Julia can't deserialize generic functions (see below). It allows to do the following: ```julia julia> using DynamicPPL, Distributions julia> @macroexpand @model demo() = x ~ Normal() quote function demo(__model__::Model, __varinfo__::AbstractVarInfo, __context__::DynamicPPL.AbstractContext; ) #= REPL[4]:1 =# begin var"##vn#257" = (VarName){:x}() var"##inds#258" = () var"##isassumption#259" = begin let var"##vn#260" = (VarName){:x}() if (DynamicPPL.contextual_isassumption)(__context__, var"##vn#260") if !((DynamicPPL.inargnames)(var"##vn#260", __model__)) || (DynamicPPL.inmissings)(var"##vn#260", __model__) true else x === missing end else false end end end if var"##isassumption#259" x = (DynamicPPL.tilde_assume!)(__context__, (DynamicPPL.unwrap_right_vn)((DynamicPPL.check_tilde_rhs)(Normal()), var"##vn#257")..., var"##inds#258", __varinfo__) else if !((DynamicPPL.inargnames)(var"##vn#257", __model__)) x = (DynamicPPL.getvalue_nested)(__context__, var"##vn#257") end (DynamicPPL.tilde_observe!)(__context__, (DynamicPPL.check_tilde_rhs)(Normal()), x, var"##vn#257", var"##inds#258", __varinfo__) end end end begin $(Expr(:meta, :doc)) function demo(; ) #= REPL[4]:1 =# return (Model)(:demo, demo, NamedTuple(), NamedTuple()) end end end julia> @model demo() = x ~ Normal() demo (generic function with 2 methods) julia> f(x) = false f (generic function with 1 method) julia> f(::Model{typeof(demo)}) = true f (generic function with 2 methods) julia> f(demo()) true julia> demo() isa Model{typeof(demo)} true ``` Co-authored-by: David Widmann <[email protected]>
Pull request successfully merged into master. Build succeeded: |
This PR implements the idea in https://github.com/TuringLang/DynamicPPL.jl/pull/314/files#r693968700. All DynamicPPL tests pass, I only had to update the doctests (since the number of methods that are created by
@model
changed from 1 to 2).This PR is breaking: it breaks deserialization of models since Julia can't deserialize generic functions (see below).
It allows to do the following: