-
Notifications
You must be signed in to change notification settings - Fork 37
Fix potential undefined variable in model compiler output #1110
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
Benchmark Report for Commit d528e3bComputer InformationBenchmark Results |
2ad7d54 to
8f508fc
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1110 +/- ##
=======================================
Coverage 81.23% 81.23%
=======================================
Files 40 40
Lines 3805 3805
=======================================
Hits 3091 3091
Misses 714 714 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
I thought I'd add tests, but it seems that it's also impossible to reproduce this without @model function f(x)
β ~ Normal()
μ = β .* x
y ~ MvNormal(μ, I)
end
m = f([0.0]) | (; y=[0.0])
v = VarInfo(m)
args, kwargs = DynamicPPL.make_evaluate_args_and_kwargs(m, v)
Libtask.TapedTask(nothing, m.f, args...)actually works just fine, even on current DPPL main, because all of the offending IR just gets compiled away. So I think the testing has to be done in Turing. |
8f508fc to
57897bf
Compare
|
DynamicPPL.jl documentation for PR #1110 is available at: |
mhauru
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.
Looks good, thanks. I don't mind the lack of a test too much, it's hard to test for and the comment guards against someone reverting this inadvertently.
It seems odd to me that Libtask checks whether check_tilde_rhs returns a ProducedValue. I thought that this was opt-in, i.e. by default it should assume that check_tilde_rhs doesn't return a ProducedValue unless we declared it with might_produce. But this requires more knowledge of Libtask internals than I have right now.
I wonder if the answer to this has something to do with the distinction between :dynamic, :call, and :invoke, and which ones trigger the might_produce stuff. I couldn't explain it to you though.
57897bf to
d528e3b
Compare
Fixes TuringLang/Libtask.jl#200.
What does this change?
To see what's different, consider the output of
Before this PR, this gives something like this for the
y ~ MvNormal(...)line:This PR changes it to something like this:
From DynamicPPL's point of view this is entirely equivalent, and indeed both model functions work perfectly fine, if you execute them one time from front to back.
Why?
The problem (as seen in TuringLang/Libtask.jl#200) comes with two things:
Firstly, when the Julia code in the first example is lowered to SSA IR, the compiler inserts a check right before
tilde_observe!!which throws an error if!inargnames(...)was true and ifyis undefined. I don't know exactly why this is needed, but it makes sense that if they = DynamicPPL.getconditioned...line was executed, thenymust be defined.The IR looks something like this. The check is in the second-last line, and the value of
%58is (ultimately) derived from%52, which is!inargnames(...).Secondly, when Libtask makes a resumable task out of this, it doesn't know whether
check_tilde_rhs"produces". (See also footnote.) That means that it has to insert a resume point between thecheck_tilde_rhsand the call to:throw_undef_if_not.The problem with this is, if
check_tilde_rhsindeed produces, then when we resume the function, we'll jump straight to thethrow_undef_if_notcall. But if we jump in here,%58is not defined, because we haven't run theinargnamescheck!With this PR, it ensures that
yis always defined regardless of whether it's inargnames or not. That means that the compiler doesn't insert thethrow_undef_if_notline into the IR, and it means that Libtask doesn't fall over.Notes
It seems odd to me that Libtask checks whether
check_tilde_rhsreturns aProducedValue. I thought that this was opt-in, i.e. by default it should assume thatcheck_tilde_rhsdoesn't return aProducedValueunless we declared it withmight_produce. But this requires more knowledge of Libtask internals than I have right now.Identifying the root cause also allows us to construct a Turing-free MWE for the same issue. One could argue that this is really an upstream bug, but fixing that will probably be substantially more difficult (and perhaps impossible).
In either case, I think it's just a good idea to fix it in DynamicPPL anyway because having variables that are only sometimes defined is not good (JET has also started to complain about these more often). I have a suspicion that the compiler's
throw_undef_if_notis precisely what stops JET from complaining about it in this case (since we do run JET tests on model outputs).One might wonder why Libtask only errored with specific models. I think that it's because for simpler models, the
inargnamescheck (or possibly something at an even higher level, likeisassumption) can be completely compiled away and so the emitted IR doesn't have this structure.