-
Notifications
You must be signed in to change notification settings - Fork 32
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
evaluate!!
shenanigans
#720
Comments
In reality, I didn't manage to direct enough team-wide attention towards I appreciate that this answer doesn't provide a solution, but I want to explain the historical context of the above situation that @penelopeysm correctly identifies. |
As @yebai answered the AbstractPPL.jl question, I'll just address potential simplifiications for In the past we always passed the I'd be happy to remove the default arguments for The only user-facing thing we have to worry about here is that we want stuff like model(rng) to work as intended, and so we'd need to put this "conversion" into the arguments going into the leaf-call of Overall I very much agree with you that this is all very messy 😕 |
I think we could also push evaluate!!(__model__, __context__) as an evaluation interface. I'm not sure how much work this might incur, but it seems sensible to me. |
evaluate!!
as it standsOur current implementation of
evaluate!!
is as follows.DynamicPPL.jl/src/model.jl
Lines 890 to 935 in ba490bf
Here's a little exercise for anyone reading this code. There are 4 arguments which are seemingly optional,
rng
,varinfo
,sampler
, andcontext
, hence there are 2^4 = 16 possible invocations ofevaluate!!
.Figure out: (1) which (if any) of them error, and (2) which of them wrap the context in a
SamplingContext
(line 907).Answers:
If the table above seems to suggest that "most of the time it's wrapped", this is not really true in practice: the vast majority of calls to
evaluate!!
in the DynamicPPL repository are of the formevaluate!!(model, varinfo, context)
, which is one of the non-wrapped cases.As you may guess, I'm not a fan of over-using multiple dispatch for this purpose!
It is very difficult to reason about (as the exercise above should have shown), it can easily lead to bugs (#629) or method ambiguities (#636), and the structure of the code does not clearly communicate what we're really trying to say (which is that 'the arguments are optional').
Keyword arguments
In many other programming languages, optional arguments are handled using keyword arguments that have default values. The benefits of this are:
f(1, 2)
withf(; add=1, multiply=2)
);f(2, 1)
withf(; multiply=2, add=1)
).Unfortunately, I dabbled with turning
evaluate!!
into a function that used keyword arguments (see https://github.com/TuringLang/DynamicPPL.jl/blob/py/evaluate/src/model.jl#L904-L927), and this led to a noticeable performance hit:Benchmarks
Setup:
master:
This PR:
I don't fully know the reasons for this, but possible explanations are overheads arising from the handling of keyword arguments (https://docs.julialang.org/en/v1/devdocs/functions/#Keyword-arguments) and also potential type instability, as
kwcall
generates a local variable that is aUnion
of all the keyword arguments. You can see the type instability e.g. withWhere to go from here?
Firstly, at the very least, I reckon the wrapping in
SamplingContext
should really be done at the call site, not insideevaluate!!
itself. This shouldn't be too hard to fix, it's a matter of identifying invocations ofevaluate!!
according to the above table. This would be a nice quick win.As for the method itself: one possibility is to make no changes. As someone developing and maintaining this codebase, dealing with dispatch rabbit holes like this one is a pretty frustrating time sink, so I'm really not keen to keep it this way. (Imagine how it's like for someone completely new, too. If we want to encourage open-source contributions, things like these could be quite discouraging.)
The other possibility that I can see is to just restrict the number of possible invocations. For example, I think keeping the three following methods would preserve almost all of the required behaviour. Some invocations that do not follow the patterns below will have to be modified, e.g. by specifying additional arguments which used to be defaults.
evaluate!!(model)
evaluate!!(model, varinfo, context)
evaluate!!(model, rng, varinfo, sampler, context)
But maybe there's some other pathway that I don't know of, which both preserves flexibility and performance while improving readability?
AbstractPPL and interfaces
evaluate!!
is really defined in AbstractPPL, but AbstractPPL doesn't specify an actual interface forevaluate!!
(e.g. a function signature).Personally, I don't fully understand the purpose of an empty interface. It tells you that you can call some function
evaluate!!
on a model, but it doesn't tell you how to call it (i.e. what arguments to pass it). It's also impossible to test for correctness of the interface because there is no specification.The result of having such an under-specified interface is that packages that depend on the interface are free to implement
evaluate!!
however they wish, and the interface becomes meaningless. This is partly proven by the fact that I was able to swap outevaluate!!
for one with keyword arguments without causing any upstream breakage. The general problem of having such unenforcable interfaces is described in e.g. https://invenia.github.io/blog/2020/11/06/interfacetesting/.This is probably a broader question but I think we need to evaluate (haha) why we are putting these zero-specification interfaces in AbstractPPL. If they're meant to have more specification, then they should be documented with signatures, and an interface testing function added. If not, then my current opinion is that it makes more sense to just keep the definition inside DynamicPPL.
Even if there's some other modelling package out there that uses
AbstractPPL.evaluate!!
, the fact is that they can implement it with whatever signature they wish, which means that there is nothing in common between our and their implementations of it (apart from the name, which as explained above carries no meaning in practice).The text was updated successfully, but these errors were encountered: