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

[Merged by Bors] - Small simplification of compiler #221

Closed
wants to merge 13 commits into from

Conversation

torfjelde
Copy link
Member

Overview

At the moment, we perform a check at model-expansion as to whether or not vsym(left) in args, where args is the arguments of the model.

  1. If true, we return a block of code which uses DynamicPPL.isassumption to check whether or not to call assume or observe for the the variable present in args.
  2. Otherwise, we generate a block which is identical to the assume block in the if-statement mentioned in (1).

The thing is, DynamicPPL.isassumption performs exactly the same check as above but using DynamicPPL.inargnames, i.e. at runtime. So if we're using TypedVarInfo, the check at macro-expansion vs. at runtime is completely redundant since all the information necessary to determine DynamicPPL.inargnames is available at compile-time.

Therefore I suggest we remove this check at model-expansion, and simply handle it using DynamicPPL.isassumption.

Pros & cons

Pros:

  • No need to pass args around everywhere
  • generate_tilde and generate_dot_tilde are much simpler: two possible blocks we can generate, either a) assume/observe, or b) observe literal.

Cons:

  • We need to perform one more check at runtime when using UntypedVarInfo.

IMO, this is really worth it.

Motivation (sort of)

The main motivation behind this PR is simplification, but there's a different reason why I came across this.

I came to this because I was thinking about trying to "customize" the behavior of ~, and I was thinking of using a macro to do it, e.g. @mymacro x ~ Normal(). Atm we're actually performing model-expansion on the code passed to the macro and thus trying to alter the way DynamicPPL treats ~ using a macro is veeeery difficult since you actually have to work with the expanded code, but let's ignore that issue for now (and take that discussion somewhere else, because IMO we shouldn't do this).

Suppose we didn't perform model-expansions of the code fed to the macros, then you can just copy-paste generate_tilde, customize it do what you want, and BAM, you got yourself a working @mymacro x ~ Normal() which can do neat stuff! This is not possible atm because we don't have access to args, and so you have to take the approach in this PR to get there. That means that it's of course possible to do atm, but it's a bit icky since it ends up looking fundamentally different from generate_tilde rather than just slightly different.

Then we can implement things like a @tilde which will expand to generate_tilde which can be used internally in functions (if the "internal" variables are present in the functions of course, but we can also simplify this in different ways), actually allowing people to modularize their models a bit, and @reparam from #220 using very similar pieces of code, a @track macro can be introduced to deal with the explicit tracking of variables rather than putting this directly in the compiler, etc. Endless opportunities! (Of course, I'm not suggesting we add these, but this makes it a bit easier to explore.)

Copy link
Member

@devmotion devmotion left a comment

Choose a reason for hiding this comment

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

I think it's always great if the implementation can be simplified 🙂 I am a bit worried though about the impact it might have on performance and compile times. So it would be good to perform some benchmarks and check compilation before releasing it. E.g., I assume that in most models most variables are not arguments (i.e. observed) so currently already at macro expansion the observe branch and isassumption checks are elided completely.

src/compiler.jl Outdated
Comment on lines 230 to 237
$left = if $isassumption
$(DynamicPPL.tilde_assume)(
_rng, _context, _sampler, $tmpright, $vn, $inds, _varinfo)
else
$(DynamicPPL.tilde_observe)(
_context, _sampler, $tmpright, $left, $vn, $inds, _varinfo)
$left
end
Copy link
Member

Choose a reason for hiding this comment

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

This is different from the current implementation and might lead to different return values. Currently, we assign $left if it is an assumption but only return tilde_observe(...) if it is not. IIRC this was changed on purpose in some PR a while back but I don't remember the exact details...

Copy link
Member Author

Choose a reason for hiding this comment

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

But in this case I assign $left to $left, i.e. no change, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Like I see what you mean: it does generate different code, but the resulting model will behave exactly the same.

Copy link
Member

Choose a reason for hiding this comment

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

No, the return value is possibly different. Before we had

Suggested change
$left = if $isassumption
$(DynamicPPL.tilde_assume)(
_rng, _context, _sampler, $tmpright, $vn, $inds, _varinfo)
else
$(DynamicPPL.tilde_observe)(
_context, _sampler, $tmpright, $left, $vn, $inds, _varinfo)
$left
end
if $isassumption
$left = $(DynamicPPL.tilde_assume)(
_rng, _context, _sampler, $tmpright, $vn, $inds, _varinfo)
else
$(DynamicPPL.tilde_observe)(
_context, _sampler, $tmpright, $left, $vn, $inds, _varinfo)
end

and we should not change it (at least not in this PR).

Copy link
Member Author

@torfjelde torfjelde Apr 1, 2021

Choose a reason for hiding this comment

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

But there will be never anything on the receiving end of this value though, right? We're replacing a line such as x ~ Normal() with the above.

Also, I introduced this because if you put the assignment of left inside the if statement, you're making the variable (if doesn't exist in the scope, i.e. not part of args) local to that scope, and thus not usable in the rest of the model.

Copy link
Member

Choose a reason for hiding this comment

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

BTW if do not introduce a separate scope. And in the second branch $left is already defined. And an additional why we don't want to do this: imagine you have X[1] ~ Normal(0, 1); if X is an argument to the model, then you would evaluate the log pdf in tilde_observe and then reassign X[1] when you assign something to left - this is not needed (and probably undesired) and might be quite slow in higher-dimensional examples or with ranges of samples.

Copy link
Member Author

Choose a reason for hiding this comment

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

Aaaaah weeeeell do we really care about that? 😅 I didn't think of this though, so good you brought it up 👍 Still, it seems like this isn't something we should actually take into consideration? (you're the PPL-people though, so you know better here for sure)

Btw, this will go on the most recent release of DynamicPPL, which includes breaking changes right? Just in case that plays into the consideration.

Copy link
Member

Choose a reason for hiding this comment

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

Btw, this will go on the most recent release of DynamicPPL, which includes breaking changes right? Just in case that plays into the consideration.

If we introduce breaking changes, we would have to bump the minor version (which is possible, of course). Turing uses (or can use) the latest release of DynamicPPL which is (basically always) the master branch.

Copy link
Member Author

@torfjelde torfjelde Apr 1, 2021

Choose a reason for hiding this comment

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

Ah I thought we just did for AbstractPPL, but I misread before 👍

But what do you say to my previous question: is this something we actually care about? The return-type in the case where this is the last statement?

EDIT: Woah, sorry I missed your previous comment! I see what you mean. Indexing is big issue.

Copy link
Member Author

Choose a reason for hiding this comment

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

BTW if do not introduce a separate scope.

Also, of course! Crap. I got confused because when I tried just using the if-statement, I got complaints that the variable wasn't defined.. Hmm, maybe I just did something wrong. I'll try your suggestion again 👍

return quote
$(top...)
$left = $(DynamicPPL.tilde_assume)(_rng, _context, _sampler, $tmpright, $vn,
$inds, _varinfo)
$isassumption = $(DynamicPPL.isassumption(left))
Copy link
Member

Choose a reason for hiding this comment

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

BTW (I know not part of this PR 🙂): Why do we actually assign the output of isassumption to a variable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep:)

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh haha, I read do we actually assign the output of isassumption to a variable?, didn't see the why.

It's because isassumption generates a larger if-statement which returns a Bool, so we can't e.g. do if $(DynamicPPL.isassumption(left)).

@torfjelde
Copy link
Member Author

So it would be good to perform some benchmarks and check compilation before releasing it. E.g., I assume that in most models most variables are not arguments (i.e. observed) so currently already at macro expansion the observe branch and isassumption checks are elided completely.

100% with you! I did just check quickly a simple model to make sure, and didn't affect runtime. But yeah, should do some additional benchmarking first. Do we have a go-to suite for this or something?

@devmotion
Copy link
Member

Do we have a go-to suite for this or something?

No, unfortunately not (at least I'm not aware of it). At some point we experimented with automatic benchmarking in Turing IIRC, maybe there's still something in the repo?

@torfjelde
Copy link
Member Author

Hmm, aight, I'll see if I can come up with something. Btw the tests pass (just tried locally) 👍

Co-authored-by: David Widmann <[email protected]>
@torfjelde
Copy link
Member Author

Thoughts about updated version @devmotion ?

@devmotion
Copy link
Member

👍 Have you checked if it affects performance, compilation, and/or the compiled code?

@torfjelde
Copy link
Member Author

Nope, I'll do that now; just wanted to know you thought the currently version was good before starting down that path 👍

@devmotion
Copy link
Member

Looks good to me now 👍

@torfjelde
Copy link
Member Author

torfjelde commented Apr 1, 2021

NOTE: See comments follow this one for "analysis" of what's actually going on here.

Okay, so this is going to be a bit of a moutful :upsidedownface: I'd recommend using the links in the TOC to jump around, and potentially have two browser windows next to each other: one looking at the new implementation, and one looking at the old implementation.

If there's anything else you'd like to see comparison for, feel free to mention. I was quite certain what to do here, so I just took some models and compared:)

New implementation

using Pkg
Pkg.status()
   Project DynamicPPL v0.10.8
    Status `~/Projects/public/DynamicPPL.jl/Project.toml`
[80f14c24] AbstractMCMC v2.5.0
[7a57a42e] AbstractPPL v0.1.2
[76274a88] Bijectors v0.8.16
[d360d2e6] ChainRulesCore v0.9.35
[31c24e10] Distributions v0.24.15
[1914dd2f] MacroTools v0.5.6
[c020b1a1] NaturalSort v1.0.0
[9a3f8284] Random
using DynamicPPL, Distributions, BenchmarkTools, MacroTools

Model 1

@time begin
    @model function demo1(x)
        m ~ Normal()
        x ~ Normal(m, 1)

        return (m = m, x = x)
    end
end;
0.000080 seconds (1.05 k allocations: 78.492 KiB, 3161.12% compilation time)
model_def = demo1
data = 1.0
@time model_def(data)();
0.956326 seconds (2.48 M allocations: 156.866 MiB, 5.61% gc time, 99.89% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
1.359 μs (51 allocations: 3.78 KiB)
354.570 ns (2 allocations: 64 bytes)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
588.022 ns (15 allocations: 544 bytes)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#3#4", (:x,), (), (), Tuple{Float64}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:m,), Tuple{DynamicPPL.Metadata{Dict{VarName{:m, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:m, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ── %1  = Base.sle_int(1, 1)::Bool
└───       goto #3 if not %1
2 ── %3  = Base.sle_int(1, 0)::Bool
└───       goto #4
3 ──       nothing::Nothing
4 ┄─ %6  = φ (#2 => %3, #3 => false)::Bool
└───       goto #6 if not %6
5 ──       invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└───       unreachable
6 ──       goto #7
7 ──       goto #8
8 ──       goto #9
9 ──       goto #10
10 ─       goto #11
11 ─       goto #12
12 ─       invoke Core.TypeVar(Symbol("#s73")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s73"<:Distribution, true, true)
│          nothing::Nothing
│          nothing::Nothing
│    %19 = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, m::VarName{:m, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:m,), Tuple{DynamicPPL.Metadata{Dict{VarName{:m, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:m, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│    %20 = Base.getfield(%19, 1)::Float64
│    %21 = Base.getfield(%19, 2)::Float64
│    %22 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %23 = Base.getfield(%22, :x)::Float64
│    %24 = Base.add_float(%23, %21)::Float64
│    %25 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%25, :x, %24)::Float64
└───       goto #14 if not true
13 ─       nothing::Nothing
14 ┄ %29 = %new(Normal{Float64}, %20, 1.0)::Normal{Float64}
└───       goto #15
15 ─       goto #16
16 ─       goto #17
17 ─       invoke Core.TypeVar(Symbol("#s72")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s72"<:Distribution, true, true)
└───       goto #19 if not false
18 ─       nothing::Nothing
19 ┄       goto #21 if not false
20 ─       nothing::Nothing
21 ┄       goto #23 if not false
22 ─       nothing::Nothing
23 ┄ %40 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│    %41 = Base.getfield(%40, :x)::Int64
│    %42 = Base.add_int(%41, 1)::Int64
│    %43 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│          Base.setfield!(%43, :x, %42)::Int64
│    %45 = invoke Distributions.logpdf(%29::Normal{Float64}, _7::Float64)::Float64
│    %46 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %47 = Base.getfield(%46, :x)::Float64
│    %48 = Base.add_float(%47, %45)::Float64
│    %49 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%49, :x, %48)::Float64
│    %51 = %new(NamedTuple{(:m, :x), Tuple{Float64, Float64}}, %20, x@_7)::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
└───       return %51
) => NamedTuple{(:m, :x), Tuple{Float64, Float64}}
expr = @macroexpand begin
    @model function demo1(x)
        m ~ Normal()
        x ~ Normal(m, 1)

        return (m = m, x = x)
    end
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo1(x; )
            var"##evaluator#316" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x)->begin
                        begin
                            begin
                                var"##tmpright#304" = Normal()
                                var"##tmpright#304" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#306" = m
                                var"##inds#307" = ()
                                var"##isassumption#308" = begin
                                        let var"##vn#309" = m
                                            if !((DynamicPPL.inargnames)(var"##vn#309", _model)) || (DynamicPPL.inmissings)(var"##vn#309", _model)
                                                true
                                            else
                                                m === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#308"
                                    m = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#304", var"##vn#306", var"##inds#307", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#304", m, var"##vn#306", var"##inds#307", _varinfo)
                                end
                            end
                            begin
                                var"##tmpright#310" = Normal(m, 1)
                                var"##tmpright#310" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#312" = x
                                var"##inds#313" = ()
                                var"##isassumption#314" = begin
                                        let var"##vn#315" = x
                                            if !((DynamicPPL.inargnames)(var"##vn#315", _model)) || (DynamicPPL.inmissings)(var"##vn#315", _model)
                                                true
                                            else
                                                x === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#314"
                                    x = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#310", var"##vn#312", var"##inds#313", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#310", x, var"##vn#312", var"##inds#313", _varinfo)
                                end
                            end
                            return (m = m, x = x)
                        end
                    end)
            return (Model)(:demo1, var"##evaluator#316", (DynamicPPL.namedtuple)(NamedTuple{(:x,), Tuple{Core.Typeof(x)}}, (x,)), NamedTuple())
        end
    end
end

Model 2

@time begin
    @model function demo2(y) 
        # Our prior belief about the probability of heads in a coin.
        p ~ Beta(1, 1)

        # The number of observations.
        N = length(y)
        for n in 1:N
            # Heads or tails of a coin are drawn from a Bernoulli distribution.
            y[n] ~ Bernoulli(p)
        end
    end;
end;
0.000072 seconds (67 allocations: 6.214 KiB)
model_def = demo2
data = rand(0:1, 10)
@time model_def(data)();
0.476772 seconds (1.17 M allocations: 69.455 MiB, 4.55% gc time, 99.89% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
12.444 μs (184 allocations: 8.22 KiB)
10.385 μs (121 allocations: 3.62 KiB)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
11.550 μs (148 allocations: 4.97 KiB)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#8#9", (:y,), (), (), Tuple{Vector{Int64}}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:p,), Tuple{DynamicPPL.Metadata{Dict{VarName{:p, Tuple{}}, Int64}, Vector{Beta{Float64}}, Vector{VarName{:p, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::Nothing
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::Core.Const(nothing)
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ──        invoke Core.TypeVar(Symbol("#s73")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s73"<:Distribution, true, true)
│           nothing::Nothing
│           nothing::Nothing
│    %4   = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Beta{Float64}(α=1.0, β=1.0)))::Beta{Float64}, p::VarName{:p, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:p,), Tuple{DynamicPPL.Metadata{Dict{VarName{:p, Tuple{}}, Int64}, Vector{Beta{Float64}}, Vector{VarName{:p, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│    %5   = Base.getfield(%4, 1)::Float64
│    %6   = Base.getfield(%4, 2)::Float64
│    %7   = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %8   = Base.getfield(%7, :x)::Float64
│    %9   = Base.add_float(%8, %6)::Float64
│    %10  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│           Base.setfield!(%10, :x, %9)::Float64
│    %12  = Base.arraylen(y)::Int64
│    %13  = Base.sle_int(1, %12)::Bool
│    %14  = Base.ifelse(%13, %12, 0)::Int64
│    %15  = Base.slt_int(%14, 1)::Bool
└───        goto #3 if not %15
2 ──        goto #4
3 ──        goto #4
4 ┄─ %19  = φ (#2 => true, #3 => false)::Bool
│    %20  = φ (#3 => 1)::Int64
│    %21  = φ (#3 => 1)::Int64
│    %22  = Base.not_int(%19)::Bool
└───        goto #40 if not %22
5 ┄─ %24  = φ (#4 => %20, #38 => %104)::Int64
│    %25  = φ (#4 => %21, #38 => %105)::Int64
└───        goto #12 if not true
6 ── %27  = Base.le_float(0.0, %5)::Bool
└───        goto #8 if not %27
7 ── %29  = Base.le_float(%5, 1.0)::Bool
└───        goto #9
8 ──        nothing::Nothing
9 ┄─ %32  = φ (#7 => %29, #8 => false)::Bool
│    %33  = Base.not_int(%32)::Bool
└───        goto #11 if not %33
10 ─ %35  = Distributions.string("Bernoulli", ": the condition ", "zero(p) <= p <= one(p)", " is not satisfied.")::Any
│    %36  = Distributions.ArgumentError(%35)::Any
│           Distributions.throw(%36)::Union{}
└───        unreachable
11 ─        nothing::Nothing
12 ┄ %40  = %new(Bernoulli{Float64}, %5)::Bernoulli{Float64}
└───        goto #13
13 ─        goto #14
14 ─ %43  = invoke Core.TypeVar(Symbol("#s71")::Symbol, Distribution::Any)::TypeVar
│    %44  = Core.apply_type(Main.AbstractVector, %43)::Type{AbstractVector{_A}} where _A
│    %45  = Core.UnionAll(%43, %44)::Any
│    %46  = Core.apply_type(Main.Union, Distribution, %45)::Type
│    %47  = (%40 isa %46)::Bool
└───        goto #39 if not %47
15 ─        nothing::Nothing
│    %50  = Core.tuple(%24)::Tuple{Int64}
│    %51  = Core.tuple(%50)::Tuple{Tuple{Int64}}
│    %52  = invoke VarName(:y::Symbol, %51::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│    %53  = Core.tuple(%24)::Tuple{Int64}
│    %54  = Core.tuple(%53)::Tuple{Tuple{Int64}}
│    %55  = Core.tuple(%24)::Tuple{Int64}
│    %56  = Core.tuple(%55)::Tuple{Tuple{Int64}}
│    %57  = invoke VarName(:y::Symbol, %56::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│    %58  = (DynamicPPL.inargnames)(%57, _model)::Any
│    %59  = !%58::Any
└───        goto #17 if not %59
16 ─        goto #18
17 ─ %62  = (DynamicPPL.inmissings)(%57, _model)::Any
18 ┄ %63  = φ (#16 => %59, #17 => %62)::Any
└───        goto #20 if not %63
19 ─        goto #21
20 ─        Base.arrayref(true, y, %24)::Int64
21 ┄ %67  = φ (#19 => true, #20 => false)::Bool
└───        goto #23 if not %67
22 ─        (DynamicPPL.tilde_assume)(_rng, _context, _sampler, %40, %52, %54, _varinfo)::Union{}
└───        unreachable
23 ─ %71  = Base.arrayref(true, y, %24)::Int64
│    %72  = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│    %73  = Base.getfield(%72, :x)::Int64
│    %74  = Base.add_int(%73, 1)::Int64
│    %75  = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│           Base.setfield!(%75, :x, %74)::Int64
│    %77  = (%71 === 0)::Bool
└───        goto #25 if not %77
24 ─ %79  = Base.sitofp(Float64, 1)::Float64
│    %80  = Base.sub_float(%79, %5)::Float64
└───        goto #28
25 ─ %82  = (%71 === 1)::Bool
└───        goto #27 if not %82
26 ─        goto #28
27 ─        goto #28
28 ┄ %86  = φ (#24 => %80, #26 => %5, #27 => 0.0)::Float64
│    %87  = invoke Distributions.log(%86::Float64)::Float64
└───        goto #29
29 ─        goto #30
30 ─        goto #31
31 ─        goto #32
32 ─        goto #33
33 ─ %93  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %94  = Base.getfield(%93, :x)::Float64
│    %95  = Base.add_float(%94, %87)::Float64
│    %96  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│           Base.setfield!(%96, :x, %95)::Float64
└───        goto #34
34 ─ %99  = (%25 === %14)::Bool
└───        goto #36 if not %99
35 ─        goto #37
36 ─ %102 = Base.add_int(%25, 1)::Int64
└───        goto #37
37 ┄ %104 = φ (#36 => %102)::Int64
│    %105 = φ (#36 => %102)::Int64
│    %106 = φ (#35 => true, #36 => false)::Bool
│    %107 = Base.not_int(%106)::Bool
└───        goto #40 if not %107
38 ─        goto #5
39 ─ %110 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│           Main.throw(%110)::Union{}
└───        unreachable
40 ┄        return nothing
) => Nothing
expr = @macroexpand begin
    @model function demo2(y) 
        # Our prior belief about the probability of heads in a coin.
        p ~ Beta(1, 1)

        # The number of observations.
        N = length(y)
        for n in 1:N
            # Heads or tails of a coin are drawn from a Bernoulli distribution.
            y[n] ~ Bernoulli(p)
        end
    end;
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo2(y; )
            var"##evaluator#376" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, y)->begin
                        begin
                            begin
                                var"##tmpright#364" = Beta(1, 1)
                                var"##tmpright#364" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#366" = p
                                var"##inds#367" = ()
                                var"##isassumption#368" = begin
                                        let var"##vn#369" = p
                                            if !((DynamicPPL.inargnames)(var"##vn#369", _model)) || (DynamicPPL.inmissings)(var"##vn#369", _model)
                                                true
                                            else
                                                p === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#368"
                                    p = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#364", var"##vn#366", var"##inds#367", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#364", p, var"##vn#366", var"##inds#367", _varinfo)
                                end
                            end
                            N = length(y)
                            for n = 1:N
                                var"##tmpright#370" = Bernoulli(p)
                                var"##tmpright#370" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#372" = (VarName)(:y, ((n,),))
                                var"##inds#373" = ((n,),)
                                var"##isassumption#374" = begin
                                        let var"##vn#375" = (VarName)(:y, ((n,),))
                                            if !((DynamicPPL.inargnames)(var"##vn#375", _model)) || (DynamicPPL.inmissings)(var"##vn#375", _model)
                                                true
                                            else
                                                y[n] === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#374"
                                    y[n] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#370", var"##vn#372", var"##inds#373", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#370", y[n], var"##vn#372", var"##inds#373", _varinfo)
                                end
                            end
                        end
                    end)
            return (Model)(:demo2, var"##evaluator#376", (DynamicPPL.namedtuple)(NamedTuple{(:y,), Tuple{Core.Typeof(y)}}, (y,)), NamedTuple())
        end
    end
end

Model 3

@time begin
    @model function demo3(x)
        D, N = size(x)

        # Draw the parameters for cluster 1.
        μ1 ~ Normal()

        # Draw the parameters for cluster 2.
        μ2 ~ Normal()

        μ = [μ1, μ2]

        # Comment out this line if you instead want to draw the weights.
        w = [0.5, 0.5]

        # Draw assignments for each datum and generate it from a multivariate normal.
        k = Vector{Int}(undef, N)
        for i in 1:N
            k[i] ~ Categorical(w)
            x[:,i] ~ MvNormal([μ[k[i]], μ[k[i]]], 1.)
        end
        return k
    end
end;
0.000104 seconds (82 allocations: 8.927 KiB)
model_def = demo3

# Construct 30 data points for each cluster.
N = 30

# Parameters for each cluster, we assume that each cluster is Gaussian distributed in the example.
μs = [-3.5, 0.0]

# Construct the data points.
data = mapreduce(c -> rand(MvNormal([μs[c], μs[c]], 1.), N), hcat, 1:2)
@time model_def(data)();
0.952374 seconds (1.64 M allocations: 91.993 MiB, 2.24% gc time, 99.93% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
184.262 μs (3212 allocations: 178.86 KiB)
129.351 μs (1505 allocations: 81.44 KiB)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
148.655 μs (2270 allocations: 99.30 KiB)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#13#14", (:x,), (), (), Tuple{Matrix{Float64}}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::Vector{Int64}
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::Vector{Int64}
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ───        Base.arraysize(x, 1)::Int64
│     %2   = Base.arraysize(x, 2)::Int64
│     %3   = Base.sle_int(1, 1)::Bool
└────        goto #3 if not %3
2 ─── %5   = Base.sle_int(1, 0)::Bool
└────        goto #4
3 ───        nothing::Nothing
4 ┄── %8   = φ (#2 => %5, #3 => false)::Bool
└────        goto #6 if not %8
5 ───        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
6 ───        goto #7
7 ───        goto #8
8 ───        goto #9
9 ───        goto #10
10 ──        goto #11
11 ──        goto #12
12 ──        invoke Core.TypeVar(Symbol("#s72")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s72"<:Distribution, true, true)
│            nothing::Nothing
│            nothing::Nothing
│     %21  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, μ1::VarName{:μ1, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│     %22  = Base.getfield(%21, 1)::Float64
│     %23  = Base.getfield(%21, 2)::Float64
│     %24  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %25  = Base.getfield(%24, :x)::Float64
│     %26  = Base.add_float(%25, %23)::Float64
│     %27  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%27, :x, %26)::Float64
│     %29  = Base.sle_int(1, 1)::Bool
└────        goto #14 if not %29
13 ── %31  = Base.sle_int(1, 0)::Bool
└────        goto #15
14 ──        nothing::Nothing
15 ┄─ %34  = φ (#13 => %31, #14 => false)::Bool
└────        goto #17 if not %34
16 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
17 ──        goto #18
18 ──        goto #19
19 ──        goto #20
20 ──        goto #21
21 ──        goto #22
22 ──        goto #23
23 ──        invoke Core.TypeVar(Symbol("#s71")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s71"<:Distribution, true, true)
│            nothing::Nothing
│            nothing::Nothing
│     %47  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, μ2::VarName{:μ2, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│     %48  = Base.getfield(%47, 1)::Float64
│     %49  = Base.getfield(%47, 2)::Float64
│     %50  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %51  = Base.getfield(%50, :x)::Float64
│     %52  = Base.add_float(%51, %49)::Float64
│     %53  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%53, :x, %52)::Float64
│     %55  = Core.tuple(%22, %48)::Tuple{Float64, Float64}
│     %56  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%56, 1)::Int64
└────        goto #29 if not true
24 ┄─ %59  = φ (#23 => 1, #28 => %70)::Int64
│     %60  = φ (#23 => 1, #28 => %71)::Int64
│     %61  = φ (#23 => 1, #28 => %64)::Int64
│     %62  = Base.getfield(%55, %59, true)::Float64
│            Base.arrayset(false, %56, %62, %61)::Vector{Float64}
│     %64  = Base.add_int(%61, 1)::Int64
│     %65  = (%60 === 2)::Bool
└────        goto #26 if not %65
25 ──        goto #27
26 ── %68  = Base.add_int(%60, 1)::Int64
└────        goto #27
27 ┄─ %70  = φ (#26 => %68)::Int64
│     %71  = φ (#26 => %68)::Int64
│     %72  = φ (#25 => true, #26 => false)::Bool
│     %73  = Base.not_int(%72)::Bool
└────        goto #29 if not %73
28 ──        goto #24
29 ┄─        goto #30
30 ── %77  = Core.tuple(0.5, 0.5)::Core.Const((0.5, 0.5))
│     %78  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%78, 1)::Int64
└────        goto #36 if not true
31 ┄─ %81  = φ (#30 => 1, #35 => %92)::Int64
│     %82  = φ (#30 => 1, #35 => %93)::Int64
│     %83  = φ (#30 => 1, #35 => %86)::Int64
│     %84  = Base.getfield(%77, %81, true)::Float64
│            Base.arrayset(false, %78, %84, %83)::Vector{Float64}
│     %86  = Base.add_int(%83, 1)::Int64
│     %87  = (%82 === 2)::Bool
└────        goto #33 if not %87
32 ──        goto #34
33 ── %90  = Base.add_int(%82, 1)::Int64
└────        goto #34
34 ┄─ %92  = φ (#33 => %90)::Int64
│     %93  = φ (#33 => %90)::Int64
│     %94  = φ (#32 => true, #33 => false)::Bool
│     %95  = Base.not_int(%94)::Bool
└────        goto #36 if not %95
35 ──        goto #31
36 ┄─        goto #37
37 ── %99  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Int64}, svec(Any, Int64), 0, :(:ccall), Vector{Int64}, :(%2), :(%2)))::Vector{Int64}
│     %100 = Base.sle_int(1, %2)::Bool
│     %101 = Base.ifelse(%100, %2, 0)::Int64
│     %102 = Base.slt_int(%101, 1)::Bool
└────        goto #39 if not %102
38 ──        goto #40
39 ──        goto #40
40 ┄─ %106 = φ (#38 => true, #39 => false)::Bool
│     %107 = φ (#39 => 1)::Int64
│     %108 = φ (#39 => 1)::Int64
│     %109 = Base.not_int(%106)::Bool
└────        goto #134 if not %109
41 ┄─ %111 = φ (#40 => %107, #131 => %349)::Int64
│     %112 = φ (#40 => %108, #131 => %350)::Int64
│     %113 = Base.sle_int(1, 1)::Bool
└────        goto #43 if not %113
42 ── %115 = Base.sle_int(1, 0)::Bool
└────        goto #44
43 ──        nothing::Nothing
44 ┄─ %118 = φ (#42 => %115, #43 => false)::Bool
└────        goto #46 if not %118
45 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
46 ──        goto #47
47 ──        goto #48
48 ──        goto #49
49 ──        goto #50
50 ──        goto #54 if not true
51 ── %127 = invoke Distributions.isprobvec(%78::Vector{Float64})::Bool
│     %128 = Base.not_int(%127)::Bool
└────        goto #53 if not %128
52 ── %130 = Distributions.string("Categorical", ": the condition ", "isprobvec(p)", " is not satisfied.")::Any
│     %131 = Distributions.ArgumentError(%130)::Any
│            Distributions.throw(%131)::Union{}
└────        unreachable
53 ──        nothing::Nothing
54 ┄─ %135 = Base.arraylen(%78)::Int64
│     %136 = Base.slt_int(%135, 0)::Bool
│     %137 = Base.ifelse(%136, 0, %135)::Int64
│     %138 = %new(Base.OneTo{Int64}, %137)::Base.OneTo{Int64}
│     %139 = Base.sle_int(1, 1)::Bool
└────        goto #56 if not %139
55 ── %141 = Base.sle_int(1, 0)::Bool
└────        goto #57
56 ──        nothing::Nothing
57 ┄─ %144 = φ (#55 => %141, #56 => false)::Bool
└────        goto #59 if not %144
58 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
59 ──        goto #60
60 ──        goto #61
61 ──        goto #62
62 ──        goto #63
63 ── %152 = invoke $(QuoteNode(Distributions.var"#_#34#35"()))(true::Bool, Categorical{Float64, Vector{Float64}}::Type{Categorical{Float64, Vector{Float64}}}, %138::Base.OneTo{Int64}, %78::Vector{Float64})::Categorical{Float64, Vector{Float64}}
└────        goto #64
64 ──        goto #65
65 ──        goto #66
66 ──        goto #67
67 ──        goto #68
68 ── %158 = invoke Core.TypeVar(Symbol("#s69")::Symbol, Distribution::Any)::TypeVar
│     %159 = Core.apply_type(Main.AbstractVector, %158)::Type{AbstractVector{_A}} where _A
│     %160 = Core.UnionAll(%158, %159)::Any
│     %161 = Core.apply_type(Main.Union, Distribution, %160)::Type
│     %162 = (%152 isa %161)::Bool
└────        goto #133 if not %162
69 ──        nothing::Nothing
│     %165 = Core.tuple(%111)::Tuple{Int64}
│     %166 = Core.tuple(%165)::Tuple{Tuple{Int64}}
│     %167 = invoke VarName(:k::Symbol, %166::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│     %168 = Core.tuple(%111)::Tuple{Int64}
│     %169 = Core.tuple(%168)::Tuple{Tuple{Int64}}
│     %170 = Core.tuple(%111)::Tuple{Int64}
│     %171 = Core.tuple(%170)::Tuple{Tuple{Int64}}
│     %172 = invoke VarName(:k::Symbol, %171::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│     %173 = (DynamicPPL.inargnames)(%172, _model)::Any
│     %174 = !%173::Any
└────        goto #71 if not %174
70 ──        goto #72
71 ── %177 = (DynamicPPL.inmissings)(%172, _model)::Any
72 ┄─ %178 = φ (#70 => %174, #71 => %177)::Any
└────        goto #74 if not %178
73 ──        goto #75
74 ──        Base.arrayref(true, %99, %111)::Int64
75 ┄─ %182 = φ (#73 => true, #74 => false)::Bool
└────        goto #77 if not %182
76 ── %184 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, %152, %167, %169, _varinfo)::Any
│            Base.setindex!(%99, %184, %111)::Any
└────        goto #95
77 ── %187 = Base.arrayref(true, %99, %111)::Int64
│     %188 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│     %189 = Base.getfield(%188, :x)::Int64
│     %190 = Base.add_int(%189, 1)::Int64
│     %191 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│            Base.setfield!(%191, :x, %190)::Int64
│     %193 = Base.getfield(%152, :p)::Vector{Float64}
│     %194 = Base.getfield(%152, :support)::Base.OneTo{Int64}
│     %195 = invoke Base.Sort.searchsorted(%194::Base.OneTo{Int64}, %187::Int64, $(QuoteNode(Base.Order.ForwardOrdering()))::Base.Order.ForwardOrdering)::UnitRange{Int64}
│     %196 = Base.getfield(%195, :stop)::Int64
│     %197 = Base.getfield(%195, :start)::Int64
│     %198 = Base.Checked.checked_ssub_int(%196, %197)::Tuple{Int64, Bool}
│     %199 = Base.getfield(%198, 1)::Int64
│     %200 = Base.getfield(%198, 2)::Bool
└────        goto #79 if not %200
78 ──        invoke Base.Checked.throw_overflowerr_binaryop(:-::Symbol, %196::Int64, %197::Int64)::Union{}
└────        unreachable
79 ──        goto #80
80 ── %205 = Base.Checked.checked_sadd_int(%199, 1)::Tuple{Int64, Bool}
│     %206 = Base.getfield(%205, 1)::Int64
│     %207 = Base.getfield(%205, 2)::Bool
└────        goto #82 if not %207
81 ──        invoke Base.Checked.throw_overflowerr_binaryop(:+::Symbol, %199::Int64, 1::Int64)::Union{}
└────        unreachable
82 ──        goto #83
83 ──        goto #84
84 ── %213 = Base.slt_int(0, %206)::Bool
└────        goto #85
85 ──        goto #87 if not %213
86 ── %216 = Base.arrayref(true, %193, %187)::Float64
└────        goto #88
87 ──        goto #88
88 ┄─ %219 = φ (#86 => %216, #87 => 0.0)::Float64
│     %220 = invoke Distributions.log(%219::Float64)::Float64
└────        goto #89
89 ──        goto #90
90 ──        goto #91
91 ──        goto #92
92 ──        goto #93
93 ── %226 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %227 = Base.getfield(%226, :x)::Float64
│     %228 = Base.add_float(%227, %220)::Float64
│     %229 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%229, :x, %228)::Float64
└────        goto #94
94 ──        nothing::Nothing
95 ┄─ %233 = Base.arrayref(true, %99, %111)::Int64
│     %234 = Base.arrayref(true, %56, %233)::Float64
│     %235 = Base.arrayref(true, %99, %111)::Int64
│     %236 = Base.arrayref(true, %56, %235)::Float64
│     %237 = Core.tuple(%234, %236)::Tuple{Float64, Float64}
│     %238 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%238, 1)::Int64
└────        goto #101 if not true
96 ┄─ %241 = φ (#95 => 1, #100 => %252)::Int64
│     %242 = φ (#95 => 1, #100 => %253)::Int64
│     %243 = φ (#95 => 1, #100 => %246)::Int64
│     %244 = Base.getfield(%237, %241, true)::Float64
│            Base.arrayset(false, %238, %244, %243)::Vector{Float64}
│     %246 = Base.add_int(%243, 1)::Int64
│     %247 = (%242 === 2)::Bool
└────        goto #98 if not %247
97 ──        goto #99
98 ── %250 = Base.add_int(%242, 1)::Int64
└────        goto #99
99 ┄─ %252 = φ (#98 => %250)::Int64
│     %253 = φ (#98 => %250)::Int64
│     %254 = φ (#97 => true, #98 => false)::Bool
│     %255 = Base.not_int(%254)::Bool
└────        goto #101 if not %255
100 ─        goto #96
101 ┄        goto #102
102 ─ %259 = Base.arraylen(%238)::Int64
│     %260 = Base.arraylen(%238)::Int64
│     %261 = (%259 === %260)::Bool
└────        goto #104 if not %261
103 ─ %263 = %new(PDMats.ScalMat{Float64}, %259, 1.0, 1.0)::PDMats.ScalMat{Float64}
│     %264 = %new(IsoNormal, %238, %263)::IsoNormal
└────        goto #105
104 ─ %266 = Distributions.DimensionMismatch("The dimensions of mu and Sigma are inconsistent.")::Any
│            Distributions.throw(%266)::Union{}
└────        unreachable
105 ─        goto #106
106 ─ %270 = invoke Core.TypeVar(Symbol("#s68")::Symbol, Distribution::Any)::TypeVar
│     %271 = Core.apply_type(Main.AbstractVector, %270)::Type{AbstractVector{_A}} where _A
│     %272 = Core.UnionAll(%270, %271)::Any
│     %273 = Core.apply_type(Main.Union, Distribution, %272)::Type
│     %274 = (%264 isa %273)::Bool
└────        goto #132 if not %274
107 ─        nothing::Nothing
└────        goto #109 if not false
108 ─        nothing::Nothing
109 ┄        goto #111 if not false
110 ─        nothing::Nothing
111 ┄ %281 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64
│     %283 = Base.slt_int(%281, 0)::Bool
│     %284 = Base.ifelse(%283, 0, %281)::Int64
│     %285 = %new(Base.OneTo{Int64}, %284)::Base.OneTo{Int64}
│     %286 = %new(Base.Slice{Base.OneTo{Int64}}, %285)::Base.Slice{Base.OneTo{Int64}}
└────        goto #116 if not true
112 ─ %288 = Core.tuple(%286, %111)::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}
│            Base.arraysize(x, 1)::Int64
│     %290 = Base.arraysize(x, 2)::Int64
│     %291 = Base.slt_int(%290, 0)::Bool
│     %292 = Base.ifelse(%291, 0, %290)::Int64
│     %293 = Base.sle_int(1, %111)::Bool
│     %294 = Base.sle_int(%111, %292)::Bool
│     %295 = Base.and_int(%293, %294)::Bool
│     %296 = Base.and_int(%295, true)::Bool
│     %297 = Base.and_int(true, %296)::Bool
└────        goto #114 if not %297
113 ─        goto #115
114 ─        invoke Base.throw_boundserror(_7::Matrix{Float64}, %288::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})::Union{}
└────        unreachable
115 ─        nothing::Nothing
116 ┄        invoke Base._unsafe_getindex($(QuoteNode(IndexLinear()))::IndexLinear, _7::Matrix{Float64}, %286::Base.Slice{Base.OneTo{Int64}}, %111::Int64)::Vector{Float64}
└────        goto #117
117 ─        goto #118
118 ─        goto #120 if not false
119 ─        nothing::Nothing
120 ┄ %308 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64
│     %310 = Base.slt_int(%308, 0)::Bool
│     %311 = Base.ifelse(%310, 0, %308)::Int64
│     %312 = %new(Base.OneTo{Int64}, %311)::Base.OneTo{Int64}
│     %313 = %new(Base.Slice{Base.OneTo{Int64}}, %312)::Base.Slice{Base.OneTo{Int64}}
└────        goto #125 if not true
121 ─ %315 = Core.tuple(%313, %111)::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}
│            Base.arraysize(x, 1)::Int64
│     %317 = Base.arraysize(x, 2)::Int64
│     %318 = Base.slt_int(%317, 0)::Bool
│     %319 = Base.ifelse(%318, 0, %317)::Int64
│     %320 = Base.sle_int(1, %111)::Bool
│     %321 = Base.sle_int(%111, %319)::Bool
│     %322 = Base.and_int(%320, %321)::Bool
│     %323 = Base.and_int(%322, true)::Bool
│     %324 = Base.and_int(true, %323)::Bool
└────        goto #123 if not %324
122 ─        goto #124
123 ─        invoke Base.throw_boundserror(_7::Matrix{Float64}, %315::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})::Union{}
└────        unreachable
124 ─        nothing::Nothing
125 ┄ %330 = invoke Base._unsafe_getindex($(QuoteNode(IndexLinear()))::IndexLinear, _7::Matrix{Float64}, %313::Base.Slice{Base.OneTo{Int64}}, %111::Int64)::Vector{Float64}
└────        goto #126
126 ─        goto #127
127 ─ %333 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│     %334 = Base.getfield(%333, :x)::Int64
│     %335 = Base.add_int(%334, 1)::Int64
│     %336 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│            Base.setfield!(%336, :x, %335)::Int64
│     %338 = invoke Distributions.logpdf(%264::IsoNormal, %330::Vector{Float64})::Float64
│     %339 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %340 = Base.getfield(%339, :x)::Float64
│     %341 = Base.add_float(%340, %338)::Float64
│     %342 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%342, :x, %341)::Float64
│     %344 = (%112 === %101)::Bool
└────        goto #129 if not %344
128 ─        goto #130
129 ─ %347 = Base.add_int(%112, 1)::Int64
└────        goto #130
130 ┄ %349 = φ (#129 => %347)::Int64
│     %350 = φ (#129 => %347)::Int64
│     %351 = φ (#128 => true, #129 => false)::Bool
│     %352 = Base.not_int(%351)::Bool
└────        goto #134 if not %352
131 ─        goto #41
132 ─ %355 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│            Main.throw(%355)::Union{}
└────        unreachable
133 ─ %358 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│            Main.throw(%358)::Union{}
└────        unreachable
134 ┄        return %99
) => Vector{Int64}
expr = @macroexpand begin
    @model function demo3(x)
        D, N = size(x)

        # Draw the parameters for cluster 1.
        μ1 ~ Normal()

        # Draw the parameters for cluster 2.
        μ2 ~ Normal()

        μ = [μ1, μ2]

        # Comment out this line if you instead want to draw the weights.
        w = [0.5, 0.5]

        # Draw assignments for each datum and generate it from a multivariate normal.
        k = Vector{Int}(undef, N)
        for i in 1:N
            k[i] ~ Categorical(w)
            x[:,i] ~ MvNormal([μ[k[i]], μ[k[i]]], 1.)
        end
        return k
    end
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo3(x; )
            var"##evaluator#460" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x)->begin
                        begin
                            (D, N) = size(x)
                            begin
                                var"##tmpright#436" = Normal()
                                var"##tmpright#436" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#438" = μ1
                                var"##inds#439" = ()
                                var"##isassumption#440" = begin
                                        let var"##vn#441" = μ1
                                            if !((DynamicPPL.inargnames)(var"##vn#441", _model)) || (DynamicPPL.inmissings)(var"##vn#441", _model)
                                                true
                                            else
                                                μ1 === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#440"
                                    μ1 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#436", var"##vn#438", var"##inds#439", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#436", μ1, var"##vn#438", var"##inds#439", _varinfo)
                                end
                            end
                            begin
                                var"##tmpright#442" = Normal()
                                var"##tmpright#442" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#444" = μ2
                                var"##inds#445" = ()
                                var"##isassumption#446" = begin
                                        let var"##vn#447" = μ2
                                            if !((DynamicPPL.inargnames)(var"##vn#447", _model)) || (DynamicPPL.inmissings)(var"##vn#447", _model)
                                                true
                                            else
                                                μ2 === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#446"
                                    μ2 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#442", var"##vn#444", var"##inds#445", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#442", μ2, var"##vn#444", var"##inds#445", _varinfo)
                                end
                            end
                            μ = [μ1, μ2]
                            w = [0.5, 0.5]
                            k = Vector{Int}(undef, N)
                            for i = 1:N
                                begin
                                    var"##tmpright#448" = Categorical(w)
                                    var"##tmpright#448" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                    var"##vn#450" = (VarName)(:k, ((i,),))
                                    var"##inds#451" = ((i,),)
                                    var"##isassumption#452" = begin
                                            let var"##vn#453" = (VarName)(:k, ((i,),))
                                                if !((DynamicPPL.inargnames)(var"##vn#453", _model)) || (DynamicPPL.inmissings)(var"##vn#453", _model)
                                                    true
                                                else
                                                    k[i] === missing
                                                end
                                            end
                                        end
                                    if var"##isassumption#452"
                                        k[i] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#448", var"##vn#450", var"##inds#451", _varinfo)
                                    else
                                        (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#448", k[i], var"##vn#450", var"##inds#451", _varinfo)
                                    end
                                end
                                begin
                                    var"##tmpright#454" = MvNormal([μ[k[i]], μ[k[i]]], 1.0)
                                    var"##tmpright#454" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                    var"##vn#456" = (VarName)(:x, ((:, i),))
                                    var"##inds#457" = ((:, i),)
                                    var"##isassumption#458" = begin
                                            let var"##vn#459" = (VarName)(:x, ((:, i),))
                                                if !((DynamicPPL.inargnames)(var"##vn#459", _model)) || (DynamicPPL.inmissings)(var"##vn#459", _model)
                                                    true
                                                else
                                                    x[:, i] === missing
                                                end
                                            end
                                        end
                                    if var"##isassumption#458"
                                        x[:, i] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#454", var"##vn#456", var"##inds#457", _varinfo)
                                    else
                                        (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#454", x[:, i], var"##vn#456", var"##inds#457", _varinfo)
                                    end
                                end
                            end
                            return k
                        end
                    end)
            return (Model)(:demo3, var"##evaluator#460", (DynamicPPL.namedtuple)(NamedTuple{(:x,), Tuple{Core.Typeof(x)}}, (x,)), NamedTuple())
        end
    end
end

Old implementation

using Pkg
projpath = "/tmp/DynamicPPLOld/"
if !ispath(projpath)
    Pkg.generate(projpath)
end
Pkg.activate(projpath)
pkg"add DynamicPPL#master"
pkg"add Distributions MacroTools"
Pkg.status()
   Project DynamicPPLOld v0.1.0
    Status `/tmp/DynamicPPLOld/Project.toml`
[31c24e10] Distributions v0.24.15
[366bfd00] DynamicPPL v0.10.8 `https://github.com/TuringLang/DynamicPPL.jl.git#master`
[1914dd2f] MacroTools v0.5.6
using DynamicPPL, Distributions, BenchmarkTools, MacroTools

Model 1

@time begin
    @model function demo1(x)
        m ~ Normal()
        x ~ Normal(m, 1)

        return (m = m, x = x)
    end
end;
0.000063 seconds (1.04 k allocations: 77.980 KiB, 2780.30% compilation time)
model_def = demo1
data = 1.0
1.0
@time model_def(data)();
0.911163 seconds (2.48 M allocations: 156.716 MiB, 3.54% gc time, 99.90% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
1.392 μs (51 allocations: 3.78 KiB)
365.995 ns (2 allocations: 64 bytes)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
603.599 ns (15 allocations: 544 bytes)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#3#4", (:x,), (), (), Tuple{Float64}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:m,), Tuple{DynamicPPL.Metadata{Dict{VarName{:m, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:m, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ── %1  = Base.sle_int(1, 1)::Bool
└───       goto #3 if not %1
2 ── %3  = Base.sle_int(1, 0)::Bool
└───       goto #4
3 ──       nothing::Nothing
4 ┄─ %6  = φ (#2 => %3, #3 => false)::Bool
└───       goto #6 if not %6
5 ──       invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└───       unreachable
6 ──       goto #7
7 ──       goto #8
8 ──       goto #9
9 ──       goto #10
10 ─       goto #11
11 ─       goto #12
12 ─       invoke Core.TypeVar(Symbol("#s73")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s73"<:Distribution, true, true)
│    %17 = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, m::VarName{:m, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:m,), Tuple{DynamicPPL.Metadata{Dict{VarName{:m, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:m, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│    %18 = Base.getfield(%17, 1)::Float64
│    %19 = Base.getfield(%17, 2)::Float64
│    %20 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %21 = Base.getfield(%20, :x)::Float64
│    %22 = Base.add_float(%21, %19)::Float64
│    %23 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%23, :x, %22)::Float64
└───       goto #14 if not true
13 ─       nothing::Nothing
14 ┄ %27 = %new(Normal{Float64}, %18, 1.0)::Normal{Float64}
└───       goto #15
15 ─       goto #16
16 ─       goto #17
17 ─       invoke Core.TypeVar(Symbol("#s72")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s72"<:Distribution, true, true)
└───       goto #19 if not false
18 ─       nothing::Nothing
19 ┄       goto #21 if not false
20 ─       nothing::Nothing
21 ┄       goto #23 if not false
22 ─       nothing::Nothing
23 ┄ %38 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│    %39 = Base.getfield(%38, :x)::Int64
│    %40 = Base.add_int(%39, 1)::Int64
│    %41 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│          Base.setfield!(%41, :x, %40)::Int64
│    %43 = invoke Distributions.logpdf(%27::Normal{Float64}, _7::Float64)::Float64
│    %44 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %45 = Base.getfield(%44, :x)::Float64
│    %46 = Base.add_float(%45, %43)::Float64
│    %47 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%47, :x, %46)::Float64
│    %49 = %new(NamedTuple{(:m, :x), Tuple{Float64, Float64}}, %18, x@_7)::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
└───       return %49
) => NamedTuple{(:m, :x), Tuple{Float64, Float64}}
expr = @macroexpand begin
    @model function demo1(x)
        m ~ Normal()
        x ~ Normal(m, 1)

        return (m = m, x = x)
    end
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo1(x; )
            var"##evaluator#312" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x)->begin
                        begin
                            begin
                                var"##tmpright#302" = Normal()
                                var"##tmpright#302" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#304" = m
                                var"##inds#305" = ()
                                m = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#302", var"##vn#304", var"##inds#305", _varinfo)
                            end
                            begin
                                var"##tmpright#306" = Normal(m, 1)
                                var"##tmpright#306" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#308" = x
                                var"##inds#309" = ()
                                var"##isassumption#310" = begin
                                        let var"##vn#311" = x
                                            if !((DynamicPPL.inargnames)(var"##vn#311", _model)) || (DynamicPPL.inmissings)(var"##vn#311", _model)
                                                true
                                            else
                                                x === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#310"
                                    x = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#306", var"##vn#308", var"##inds#309", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#306", x, var"##vn#308", var"##inds#309", _varinfo)
                                end
                            end
                            return (m = m, x = x)
                        end
                    end)
            return (Model)(:demo1, var"##evaluator#312", (DynamicPPL.namedtuple)(NamedTuple{(:x,), Tuple{Core.Typeof(x)}}, (x,)), NamedTuple())
        end
    end
end

Model 2

@time begin
    @model function demo2(y) 
        # Our prior belief about the probability of heads in a coin.
        p ~ Beta(1, 1)

        # The number of observations.
        N = length(y)
        for n in 1:N
            # Heads or tails of a coin are drawn from a Bernoulli distribution.
            y[n] ~ Bernoulli(p)
        end
    end;
end;
0.000056 seconds (64 allocations: 5.765 KiB)
model_def = demo2
data = rand(0:1, 10)
10-element Vector{Int64}:
 0
 1
 0
 0
 1
 1
 0
 1
 1
 0
@time model_def(data)();
0.500863 seconds (1.17 M allocations: 69.291 MiB, 5.45% gc time, 99.90% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
12.696 μs (184 allocations: 8.22 KiB)
10.425 μs (121 allocations: 3.62 KiB)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
11.517 μs (148 allocations: 4.97 KiB)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#8#9", (:y,), (), (), Tuple{Vector{Int64}}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:p,), Tuple{DynamicPPL.Metadata{Dict{VarName{:p, Tuple{}}, Int64}, Vector{Beta{Float64}}, Vector{VarName{:p, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::Nothing
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::Core.Const(nothing)
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ──        invoke Core.TypeVar(Symbol("#s73")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s73"<:Distribution, true, true)
│    %2   = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Beta{Float64}(α=1.0, β=1.0)))::Beta{Float64}, p::VarName{:p, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:p,), Tuple{DynamicPPL.Metadata{Dict{VarName{:p, Tuple{}}, Int64}, Vector{Beta{Float64}}, Vector{VarName{:p, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│    %3   = Base.getfield(%2, 1)::Float64
│    %4   = Base.getfield(%2, 2)::Float64
│    %5   = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %6   = Base.getfield(%5, :x)::Float64
│    %7   = Base.add_float(%6, %4)::Float64
│    %8   = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│           Base.setfield!(%8, :x, %7)::Float64
│    %10  = Base.arraylen(y)::Int64
│    %11  = Base.sle_int(1, %10)::Bool
│    %12  = Base.ifelse(%11, %10, 0)::Int64
│    %13  = Base.slt_int(%12, 1)::Bool
└───        goto #3 if not %13
2 ──        goto #4
3 ──        goto #4
4 ┄─ %17  = φ (#2 => true, #3 => false)::Bool
│    %18  = φ (#3 => 1)::Int64
│    %19  = φ (#3 => 1)::Int64
│    %20  = Base.not_int(%17)::Bool
└───        goto #40 if not %20
5 ┄─ %22  = φ (#4 => %18, #38 => %102)::Int64
│    %23  = φ (#4 => %19, #38 => %103)::Int64
└───        goto #12 if not true
6 ── %25  = Base.le_float(0.0, %3)::Bool
└───        goto #8 if not %25
7 ── %27  = Base.le_float(%3, 1.0)::Bool
└───        goto #9
8 ──        nothing::Nothing
9 ┄─ %30  = φ (#7 => %27, #8 => false)::Bool
│    %31  = Base.not_int(%30)::Bool
└───        goto #11 if not %31
10 ─ %33  = Distributions.string("Bernoulli", ": the condition ", "zero(p) <= p <= one(p)", " is not satisfied.")::Any
│    %34  = Distributions.ArgumentError(%33)::Any
│           Distributions.throw(%34)::Union{}
└───        unreachable
11 ─        nothing::Nothing
12 ┄ %38  = %new(Bernoulli{Float64}, %3)::Bernoulli{Float64}
└───        goto #13
13 ─        goto #14
14 ─ %41  = invoke Core.TypeVar(Symbol("#s71")::Symbol, Distribution::Any)::TypeVar
│    %42  = Core.apply_type(Main.AbstractVector, %41)::Type{AbstractVector{_A}} where _A
│    %43  = Core.UnionAll(%41, %42)::Any
│    %44  = Core.apply_type(Main.Union, Distribution, %43)::Type
│    %45  = (%38 isa %44)::Bool
└───        goto #39 if not %45
15 ─        nothing::Nothing
│    %48  = Core.tuple(%22)::Tuple{Int64}
│    %49  = Core.tuple(%48)::Tuple{Tuple{Int64}}
│    %50  = invoke VarName(:y::Symbol, %49::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│    %51  = Core.tuple(%22)::Tuple{Int64}
│    %52  = Core.tuple(%51)::Tuple{Tuple{Int64}}
│    %53  = Core.tuple(%22)::Tuple{Int64}
│    %54  = Core.tuple(%53)::Tuple{Tuple{Int64}}
│    %55  = invoke VarName(:y::Symbol, %54::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│    %56  = (DynamicPPL.inargnames)(%55, _model)::Any
│    %57  = !%56::Any
└───        goto #17 if not %57
16 ─        goto #18
17 ─ %60  = (DynamicPPL.inmissings)(%55, _model)::Any
18 ┄ %61  = φ (#16 => %57, #17 => %60)::Any
└───        goto #20 if not %61
19 ─        goto #21
20 ─        Base.arrayref(true, y, %22)::Int64
21 ┄ %65  = φ (#19 => true, #20 => false)::Bool
└───        goto #23 if not %65
22 ─        (DynamicPPL.tilde_assume)(_rng, _context, _sampler, %38, %50, %52, _varinfo)::Union{}
└───        unreachable
23 ─ %69  = Base.arrayref(true, y, %22)::Int64
│    %70  = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│    %71  = Base.getfield(%70, :x)::Int64
│    %72  = Base.add_int(%71, 1)::Int64
│    %73  = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│           Base.setfield!(%73, :x, %72)::Int64
│    %75  = (%69 === 0)::Bool
└───        goto #25 if not %75
24 ─ %77  = Base.sitofp(Float64, 1)::Float64
│    %78  = Base.sub_float(%77, %3)::Float64
└───        goto #28
25 ─ %80  = (%69 === 1)::Bool
└───        goto #27 if not %80
26 ─        goto #28
27 ─        goto #28
28 ┄ %84  = φ (#24 => %78, #26 => %3, #27 => 0.0)::Float64
│    %85  = invoke Distributions.log(%84::Float64)::Float64
└───        goto #29
29 ─        goto #30
30 ─        goto #31
31 ─        goto #32
32 ─        goto #33
33 ─ %91  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %92  = Base.getfield(%91, :x)::Float64
│    %93  = Base.add_float(%92, %85)::Float64
│    %94  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│           Base.setfield!(%94, :x, %93)::Float64
└───        goto #34
34 ─ %97  = (%23 === %12)::Bool
└───        goto #36 if not %97
35 ─        goto #37
36 ─ %100 = Base.add_int(%23, 1)::Int64
└───        goto #37
37 ┄ %102 = φ (#36 => %100)::Int64
│    %103 = φ (#36 => %100)::Int64
│    %104 = φ (#35 => true, #36 => false)::Bool
│    %105 = Base.not_int(%104)::Bool
└───        goto #40 if not %105
38 ─        goto #5
39 ─ %108 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│           Main.throw(%108)::Union{}
└───        unreachable
40 ┄        return nothing
) => Nothing
expr = @macroexpand begin
    @model function demo2(y) 
        # Our prior belief about the probability of heads in a coin.
        p ~ Beta(1, 1)

        # The number of observations.
        N = length(y)
        for n in 1:N
            # Heads or tails of a coin are drawn from a Bernoulli distribution.
            y[n] ~ Bernoulli(p)
        end
    end;
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo2(y; )
            var"##evaluator#368" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, y)->begin
                        begin
                            begin
                                var"##tmpright#358" = Beta(1, 1)
                                var"##tmpright#358" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#360" = p
                                var"##inds#361" = ()
                                p = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#358", var"##vn#360", var"##inds#361", _varinfo)
                            end
                            N = length(y)
                            for n = 1:N
                                var"##tmpright#362" = Bernoulli(p)
                                var"##tmpright#362" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#364" = (VarName)(:y, ((n,),))
                                var"##inds#365" = ((n,),)
                                var"##isassumption#366" = begin
                                        let var"##vn#367" = (VarName)(:y, ((n,),))
                                            if !((DynamicPPL.inargnames)(var"##vn#367", _model)) || (DynamicPPL.inmissings)(var"##vn#367", _model)
                                                true
                                            else
                                                y[n] === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#366"
                                    y[n] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#362", var"##vn#364", var"##inds#365", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#362", y[n], var"##vn#364", var"##inds#365", _varinfo)
                                end
                            end
                        end
                    end)
            return (Model)(:demo2, var"##evaluator#368", (DynamicPPL.namedtuple)(NamedTuple{(:y,), Tuple{Core.Typeof(y)}}, (y,)), NamedTuple())
        end
    end
end

Model 3

@time begin
    @model function demo3(x)
        D, N = size(x)

        # Draw the parameters for cluster 1.
        μ1 ~ Normal()

        # Draw the parameters for cluster 2.
        μ2 ~ Normal()

        μ = [μ1, μ2]

        # Comment out this line if you instead want to draw the weights.
        w = [0.5, 0.5]

        # Draw assignments for each datum and generate it from a multivariate normal.
        k = Vector{Int}(undef, N)
        for i in 1:N
            k[i] ~ Categorical(w)
            x[:,i] ~ MvNormal([μ[k[i]], μ[k[i]]], 1.)
        end
        return k
    end
end;
0.000099 seconds (73 allocations: 7.454 KiB)
model_def = demo3

# Construct 30 data points for each cluster.
N = 30

# Parameters for each cluster, we assume that each cluster is Gaussian distributed in the example.
μs = [-3.5, 0.0]

# Construct the data points.
data = mapreduce(c -> rand(MvNormal([μs[c], μs[c]], 1.), N), hcat, 1:2)
2×60 Matrix{Float64}:
 -3.8499   -3.94499  -6.02555  -5.9249   …  -0.456392   1.65714    1.34903
 -2.53594  -4.13365  -2.93801  -4.93416      2.21184   -0.237133  -0.719036
@time model_def(data)();
0.832803 seconds (1.60 M allocations: 89.767 MiB, 2.39% gc time, 99.94% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
173.638 μs (3032 allocations: 175.11 KiB)
118.367 μs (1325 allocations: 77.69 KiB)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
137.463 μs (2090 allocations: 95.55 KiB)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#13#14", (:x,), (), (), Tuple{Matrix{Float64}}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::Vector{Int64}
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::Vector{Int64}
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ───        Base.arraysize(x, 1)::Int64
│     %2   = Base.arraysize(x, 2)::Int64
│     %3   = Base.sle_int(1, 1)::Bool
└────        goto #3 if not %3
2 ─── %5   = Base.sle_int(1, 0)::Bool
└────        goto #4
3 ───        nothing::Nothing
4 ┄── %8   = φ (#2 => %5, #3 => false)::Bool
└────        goto #6 if not %8
5 ───        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
6 ───        goto #7
7 ───        goto #8
8 ───        goto #9
9 ───        goto #10
10 ──        goto #11
11 ──        goto #12
12 ──        invoke Core.TypeVar(Symbol("#s72")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s72"<:Distribution, true, true)
│     %19  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, μ1::VarName{:μ1, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│     %20  = Base.getfield(%19, 1)::Float64
│     %21  = Base.getfield(%19, 2)::Float64
│     %22  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %23  = Base.getfield(%22, :x)::Float64
│     %24  = Base.add_float(%23, %21)::Float64
│     %25  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%25, :x, %24)::Float64
│     %27  = Base.sle_int(1, 1)::Bool
└────        goto #14 if not %27
13 ── %29  = Base.sle_int(1, 0)::Bool
└────        goto #15
14 ──        nothing::Nothing
15 ┄─ %32  = φ (#13 => %29, #14 => false)::Bool
└────        goto #17 if not %32
16 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
17 ──        goto #18
18 ──        goto #19
19 ──        goto #20
20 ──        goto #21
21 ──        goto #22
22 ──        goto #23
23 ──        invoke Core.TypeVar(Symbol("#s71")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s71"<:Distribution, true, true)
│     %43  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, μ2::VarName{:μ2, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│     %44  = Base.getfield(%43, 1)::Float64
│     %45  = Base.getfield(%43, 2)::Float64
│     %46  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %47  = Base.getfield(%46, :x)::Float64
│     %48  = Base.add_float(%47, %45)::Float64
│     %49  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%49, :x, %48)::Float64
│     %51  = Core.tuple(%20, %44)::Tuple{Float64, Float64}
│     %52  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%52, 1)::Int64
└────        goto #29 if not true
24 ┄─ %55  = φ (#23 => 1, #28 => %66)::Int64
│     %56  = φ (#23 => 1, #28 => %67)::Int64
│     %57  = φ (#23 => 1, #28 => %60)::Int64
│     %58  = Base.getfield(%51, %55, true)::Float64
│            Base.arrayset(false, %52, %58, %57)::Vector{Float64}
│     %60  = Base.add_int(%57, 1)::Int64
│     %61  = (%56 === 2)::Bool
└────        goto #26 if not %61
25 ──        goto #27
26 ── %64  = Base.add_int(%56, 1)::Int64
└────        goto #27
27 ┄─ %66  = φ (#26 => %64)::Int64
│     %67  = φ (#26 => %64)::Int64
│     %68  = φ (#25 => true, #26 => false)::Bool
│     %69  = Base.not_int(%68)::Bool
└────        goto #29 if not %69
28 ──        goto #24
29 ┄─        goto #30
30 ── %73  = Core.tuple(0.5, 0.5)::Core.Const((0.5, 0.5))
│     %74  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%74, 1)::Int64
└────        goto #36 if not true
31 ┄─ %77  = φ (#30 => 1, #35 => %88)::Int64
│     %78  = φ (#30 => 1, #35 => %89)::Int64
│     %79  = φ (#30 => 1, #35 => %82)::Int64
│     %80  = Base.getfield(%73, %77, true)::Float64
│            Base.arrayset(false, %74, %80, %79)::Vector{Float64}
│     %82  = Base.add_int(%79, 1)::Int64
│     %83  = (%78 === 2)::Bool
└────        goto #33 if not %83
32 ──        goto #34
33 ── %86  = Base.add_int(%78, 1)::Int64
└────        goto #34
34 ┄─ %88  = φ (#33 => %86)::Int64
│     %89  = φ (#33 => %86)::Int64
│     %90  = φ (#32 => true, #33 => false)::Bool
│     %91  = Base.not_int(%90)::Bool
└────        goto #36 if not %91
35 ──        goto #31
36 ┄─        goto #37
37 ── %95  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Int64}, svec(Any, Int64), 0, :(:ccall), Vector{Int64}, :(%2), :(%2)))::Vector{Int64}
│     %96  = Base.sle_int(1, %2)::Bool
│     %97  = Base.ifelse(%96, %2, 0)::Int64
│     %98  = Base.slt_int(%97, 1)::Bool
└────        goto #39 if not %98
38 ──        goto #40
39 ──        goto #40
40 ┄─ %102 = φ (#38 => true, #39 => false)::Bool
│     %103 = φ (#39 => 1)::Int64
│     %104 = φ (#39 => 1)::Int64
│     %105 = Base.not_int(%102)::Bool
└────        goto #108 if not %105
41 ┄─ %107 = φ (#40 => %103, #105 => %284)::Int64
│     %108 = φ (#40 => %104, #105 => %285)::Int64
│     %109 = Base.sle_int(1, 1)::Bool
└────        goto #43 if not %109
42 ── %111 = Base.sle_int(1, 0)::Bool
└────        goto #44
43 ──        nothing::Nothing
44 ┄─ %114 = φ (#42 => %111, #43 => false)::Bool
└────        goto #46 if not %114
45 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
46 ──        goto #47
47 ──        goto #48
48 ──        goto #49
49 ──        goto #50
50 ──        goto #54 if not true
51 ── %123 = invoke Distributions.isprobvec(%74::Vector{Float64})::Bool
│     %124 = Base.not_int(%123)::Bool
└────        goto #53 if not %124
52 ── %126 = Distributions.string("Categorical", ": the condition ", "isprobvec(p)", " is not satisfied.")::Any
│     %127 = Distributions.ArgumentError(%126)::Any
│            Distributions.throw(%127)::Union{}
└────        unreachable
53 ──        nothing::Nothing
54 ┄─ %131 = Base.arraylen(%74)::Int64
│     %132 = Base.slt_int(%131, 0)::Bool
│     %133 = Base.ifelse(%132, 0, %131)::Int64
│     %134 = %new(Base.OneTo{Int64}, %133)::Base.OneTo{Int64}
│     %135 = Base.sle_int(1, 1)::Bool
└────        goto #56 if not %135
55 ── %137 = Base.sle_int(1, 0)::Bool
└────        goto #57
56 ──        nothing::Nothing
57 ┄─ %140 = φ (#55 => %137, #56 => false)::Bool
└────        goto #59 if not %140
58 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
59 ──        goto #60
60 ──        goto #61
61 ──        goto #62
62 ──        goto #63
63 ── %148 = invoke $(QuoteNode(Distributions.var"#_#34#35"()))(true::Bool, Categorical{Float64, Vector{Float64}}::Type{Categorical{Float64, Vector{Float64}}}, %134::Base.OneTo{Int64}, %74::Vector{Float64})::Categorical{Float64, Vector{Float64}}
└────        goto #64
64 ──        goto #65
65 ──        goto #66
66 ──        goto #67
67 ──        goto #68
68 ── %154 = invoke Core.TypeVar(Symbol("#s69")::Symbol, Distribution::Any)::TypeVar
│     %155 = Core.apply_type(Main.AbstractVector, %154)::Type{AbstractVector{_A}} where _A
│     %156 = Core.UnionAll(%154, %155)::Any
│     %157 = Core.apply_type(Main.Union, Distribution, %156)::Type
│     %158 = (%148 isa %157)::Bool
└────        goto #107 if not %158
69 ──        nothing::Nothing
│     %161 = Core.tuple(%107)::Tuple{Int64}
│     %162 = Core.tuple(%161)::Tuple{Tuple{Int64}}
│     %163 = invoke VarName(:k::Symbol, %162::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│     %164 = Core.tuple(%107)::Tuple{Int64}
│     %165 = Core.tuple(%164)::Tuple{Tuple{Int64}}
│     %166 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, %148, %163, %165, _varinfo)::Any
│            Base.setindex!(%95, %166, %107)::Any
│     %168 = Base.arrayref(true, %95, %107)::Int64
│     %169 = Base.arrayref(true, %52, %168)::Float64
│     %170 = Base.arrayref(true, %95, %107)::Int64
│     %171 = Base.arrayref(true, %52, %170)::Float64
│     %172 = Core.tuple(%169, %171)::Tuple{Float64, Float64}
│     %173 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%173, 1)::Int64
└────        goto #75 if not true
70 ┄─ %176 = φ (#69 => 1, #74 => %187)::Int64
│     %177 = φ (#69 => 1, #74 => %188)::Int64
│     %178 = φ (#69 => 1, #74 => %181)::Int64
│     %179 = Base.getfield(%172, %176, true)::Float64
│            Base.arrayset(false, %173, %179, %178)::Vector{Float64}
│     %181 = Base.add_int(%178, 1)::Int64
│     %182 = (%177 === 2)::Bool
└────        goto #72 if not %182
71 ──        goto #73
72 ── %185 = Base.add_int(%177, 1)::Int64
└────        goto #73
73 ┄─ %187 = φ (#72 => %185)::Int64
│     %188 = φ (#72 => %185)::Int64
│     %189 = φ (#71 => true, #72 => false)::Bool
│     %190 = Base.not_int(%189)::Bool
└────        goto #75 if not %190
74 ──        goto #70
75 ┄─        goto #76
76 ── %194 = Base.arraylen(%173)::Int64
│     %195 = Base.arraylen(%173)::Int64
│     %196 = (%194 === %195)::Bool
└────        goto #78 if not %196
77 ── %198 = %new(PDMats.ScalMat{Float64}, %194, 1.0, 1.0)::PDMats.ScalMat{Float64}
│     %199 = %new(IsoNormal, %173, %198)::IsoNormal
└────        goto #79
78 ── %201 = Distributions.DimensionMismatch("The dimensions of mu and Sigma are inconsistent.")::Any
│            Distributions.throw(%201)::Union{}
└────        unreachable
79 ──        goto #80
80 ── %205 = invoke Core.TypeVar(Symbol("#s68")::Symbol, Distribution::Any)::TypeVar
│     %206 = Core.apply_type(Main.AbstractVector, %205)::Type{AbstractVector{_A}} where _A
│     %207 = Core.UnionAll(%205, %206)::Any
│     %208 = Core.apply_type(Main.Union, Distribution, %207)::Type
│     %209 = (%199 isa %208)::Bool
└────        goto #106 if not %209
81 ──        nothing::Nothing
└────        goto #83 if not false
82 ──        nothing::Nothing
83 ┄─        goto #85 if not false
84 ──        nothing::Nothing
85 ┄─ %216 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64
│     %218 = Base.slt_int(%216, 0)::Bool
│     %219 = Base.ifelse(%218, 0, %216)::Int64
│     %220 = %new(Base.OneTo{Int64}, %219)::Base.OneTo{Int64}
│     %221 = %new(Base.Slice{Base.OneTo{Int64}}, %220)::Base.Slice{Base.OneTo{Int64}}
└────        goto #90 if not true
86 ── %223 = Core.tuple(%221, %107)::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}
│            Base.arraysize(x, 1)::Int64
│     %225 = Base.arraysize(x, 2)::Int64
│     %226 = Base.slt_int(%225, 0)::Bool
│     %227 = Base.ifelse(%226, 0, %225)::Int64
│     %228 = Base.sle_int(1, %107)::Bool
│     %229 = Base.sle_int(%107, %227)::Bool
│     %230 = Base.and_int(%228, %229)::Bool
│     %231 = Base.and_int(%230, true)::Bool
│     %232 = Base.and_int(true, %231)::Bool
└────        goto #88 if not %232
87 ──        goto #89
88 ──        invoke Base.throw_boundserror(_7::Matrix{Float64}, %223::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})::Union{}
└────        unreachable
89 ──        nothing::Nothing
90 ┄─        invoke Base._unsafe_getindex($(QuoteNode(IndexLinear()))::IndexLinear, _7::Matrix{Float64}, %221::Base.Slice{Base.OneTo{Int64}}, %107::Int64)::Vector{Float64}
└────        goto #91
91 ──        goto #92
92 ──        goto #94 if not false
93 ──        nothing::Nothing
94 ┄─ %243 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64
│     %245 = Base.slt_int(%243, 0)::Bool
│     %246 = Base.ifelse(%245, 0, %243)::Int64
│     %247 = %new(Base.OneTo{Int64}, %246)::Base.OneTo{Int64}
│     %248 = %new(Base.Slice{Base.OneTo{Int64}}, %247)::Base.Slice{Base.OneTo{Int64}}
└────        goto #99 if not true
95 ── %250 = Core.tuple(%248, %107)::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}
│            Base.arraysize(x, 1)::Int64
│     %252 = Base.arraysize(x, 2)::Int64
│     %253 = Base.slt_int(%252, 0)::Bool
│     %254 = Base.ifelse(%253, 0, %252)::Int64
│     %255 = Base.sle_int(1, %107)::Bool
│     %256 = Base.sle_int(%107, %254)::Bool
│     %257 = Base.and_int(%255, %256)::Bool
│     %258 = Base.and_int(%257, true)::Bool
│     %259 = Base.and_int(true, %258)::Bool
└────        goto #97 if not %259
96 ──        goto #98
97 ──        invoke Base.throw_boundserror(_7::Matrix{Float64}, %250::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})::Union{}
└────        unreachable
98 ──        nothing::Nothing
99 ┄─ %265 = invoke Base._unsafe_getindex($(QuoteNode(IndexLinear()))::IndexLinear, _7::Matrix{Float64}, %248::Base.Slice{Base.OneTo{Int64}}, %107::Int64)::Vector{Float64}
└────        goto #100
100 ─        goto #101
101 ─ %268 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│     %269 = Base.getfield(%268, :x)::Int64
│     %270 = Base.add_int(%269, 1)::Int64
│     %271 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│            Base.setfield!(%271, :x, %270)::Int64
│     %273 = invoke Distributions.logpdf(%199::IsoNormal, %265::Vector{Float64})::Float64
│     %274 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %275 = Base.getfield(%274, :x)::Float64
│     %276 = Base.add_float(%275, %273)::Float64
│     %277 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%277, :x, %276)::Float64
│     %279 = (%108 === %97)::Bool
└────        goto #103 if not %279
102 ─        goto #104
103 ─ %282 = Base.add_int(%108, 1)::Int64
└────        goto #104
104 ┄ %284 = φ (#103 => %282)::Int64
│     %285 = φ (#103 => %282)::Int64
│     %286 = φ (#102 => true, #103 => false)::Bool
│     %287 = Base.not_int(%286)::Bool
└────        goto #108 if not %287
105 ─        goto #41
106 ─ %290 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│            Main.throw(%290)::Union{}
└────        unreachable
107 ─ %293 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│            Main.throw(%293)::Union{}
└────        unreachable
108 ┄        return %95
) => Vector{Int64}
expr = @macroexpand begin
    @model function demo3(x)
        D, N = size(x)

        # Draw the parameters for cluster 1.
        μ1 ~ Normal()

        # Draw the parameters for cluster 2.
        μ2 ~ Normal()

        μ = [μ1, μ2]

        # Comment out this line if you instead want to draw the weights.
        w = [0.5, 0.5]

        # Draw assignments for each datum and generate it from a multivariate normal.
        k = Vector{Int}(undef, N)
        for i in 1:N
            k[i] ~ Categorical(w)
            x[:,i] ~ MvNormal([μ[k[i]], μ[k[i]]], 1.)
        end
        return k
    end
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo3(x; )
            var"##evaluator#440" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x)->begin
                        begin
                            (D, N) = size(x)
                            begin
                                var"##tmpright#422" = Normal()
                                var"##tmpright#422" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#424" = μ1
                                var"##inds#425" = ()
                                μ1 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#422", var"##vn#424", var"##inds#425", _varinfo)
                            end
                            begin
                                var"##tmpright#426" = Normal()
                                var"##tmpright#426" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#428" = μ2
                                var"##inds#429" = ()
                                μ2 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#426", var"##vn#428", var"##inds#429", _varinfo)
                            end
                            μ = [μ1, μ2]
                            w = [0.5, 0.5]
                            k = Vector{Int}(undef, N)
                            for i = 1:N
                                begin
                                    var"##tmpright#430" = Categorical(w)
                                    var"##tmpright#430" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                    var"##vn#432" = (VarName)(:k, ((i,),))
                                    var"##inds#433" = ((i,),)
                                    k[i] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#430", var"##vn#432", var"##inds#433", _varinfo)
                                end
                                begin
                                    var"##tmpright#434" = MvNormal([μ[k[i]], μ[k[i]]], 1.0)
                                    var"##tmpright#434" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                    var"##vn#436" = (VarName)(:x, ((:, i),))
                                    var"##inds#437" = ((:, i),)
                                    var"##isassumption#438" = begin
                                            let var"##vn#439" = (VarName)(:x, ((:, i),))
                                                if !((DynamicPPL.inargnames)(var"##vn#439", _model)) || (DynamicPPL.inmissings)(var"##vn#439", _model)
                                                    true
                                                else
                                                    x[:, i] === missing
                                                end
                                            end
                                        end
                                    if var"##isassumption#438"
                                        x[:, i] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#434", var"##vn#436", var"##inds#437", _varinfo)
                                    else
                                        (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#434", x[:, i], var"##vn#436", var"##inds#437", _varinfo)
                                    end
                                end
                            end
                            return k
                        end
                    end)
            return (Model)(:demo3, var"##evaluator#440", (DynamicPPL.namedtuple)(NamedTuple{(:x,), Tuple{Core.Typeof(x)}}, (x,)), NamedTuple())
        end
    end
end

Comparison

Model 1

diff -y model1_lowered_old.jl model1_lowered_new.jl
CodeInfo(                                                       CodeInfo(
1 ── %1  = Base.sle_int(1, 1)::Bool             1 ── %1  = Base.sle_int(1, 1)::Bool
└───       goto #3 if not %1                            └───       goto #3 if not %1
2 ── %3  = Base.sle_int(1, 0)::Bool             2 ── %3  = Base.sle_int(1, 0)::Bool
└───       goto #4                                      └───       goto #4
3 ──       nothing::Nothing                     3 ──       nothing::Nothing
4 ┄─ %6  = φ (#2 => %3, #3 => false)::Bool      4 ┄─ %6  = φ (#2 => %3, #3 => false)::Bool
└───       goto #6 if not %6                            └───       goto #6 if not %6
5 ──       invoke Base.getindex(()::Tuple, 1::Int64)    5 ──       invoke Base.getindex(()::Tuple, 1::Int64)
└───       unreachable                                  └───       unreachable
6 ──       goto #7                                      6 ──       goto #7
7 ──       goto #8                                      7 ──       goto #8
8 ──       goto #9                                      8 ──       goto #9
9 ──       goto #10                                     9 ──       goto #10
10 ─       goto #11                                     10 ─       goto #11
11 ─       goto #12                                     11 ─       goto #12
12 ─       invoke Core.TypeVar(Symbol("#s73")::Symbol   12 ─       invoke Core.TypeVar(Symbol("#s73")::Symbol
│    %17 = invoke DynamicPPL.assume(_2::Random.Mersen | │          nothing::Nothing
│    %18 = Base.getfield(%17, 1)::Float64     | │          nothing::Nothing
│    %19 = Base.getfield(%17, 2)::Float64     | │    %19 = invoke DynamicPPL.assume(_2::Random.Mersen
│    %20 = Base.getfield(_varinfo, :logp)::Base.R |     │    %20 = Base.getfield(%19, 1)::Float64
│    %21 = Base.getfield(%20, :x)::Float64    | │    %21 = Base.getfield(%19, 2)::Float64
│    %22 = Base.add_float(%21, %19)::Float64  | │    %22 = Base.getfield(_varinfo, :logp)::Base.R
│    %23 = Base.getfield(_varinfo, :logp)::Base.R |     │    %23 = Base.getfield(%22, :x)::Float64
│          Base.setfield!(%23, :x, %22)::Float64        │    %24 = Base.add_float(%23, %21)::Float64
                                                              > │    %25 = Base.getfield(_varinfo, :logp)::Base.R
                                                              > │          Base.setfield!(%25, :x, %24)::Float64
└───       goto #14 if not true                         └───       goto #14 if not true
13 ─       nothing::Nothing                     13 ─       nothing::Nothing
14 ┄ %27 = %new(Normal{Float64}, %18, 1.0)::Norma |     14 ┄ %29 = %new(Normal{Float64}, %20, 1.0)::Norma
└───       goto #15                                     └───       goto #15
15 ─       goto #16                                     15 ─       goto #16
16 ─       goto #17                                     16 ─       goto #17
17 ─       invoke Core.TypeVar(Symbol("#s72")::Symbol   17 ─       invoke Core.TypeVar(Symbol("#s72")::Symbol
└───       goto #19 if not false                        └───       goto #19 if not false
18 ─       nothing::Nothing                     18 ─       nothing::Nothing
19 ┄       goto #21 if not false                        19 ┄       goto #21 if not false
20 ─       nothing::Nothing                     20 ─       nothing::Nothing
21 ┄       goto #23 if not false                        21 ┄       goto #23 if not false
22 ─       nothing::Nothing                     22 ─       nothing::Nothing
23 ┄ %38 = Base.getfield(_varinfo, :num_produce): |     23 ┄ %40 = Base.getfield(_varinfo, :num_produce):
│    %39 = Base.getfield(%38, :x)::Int64      | │    %41 = Base.getfield(%40, :x)::Int64
│    %40 = Base.add_int(%39, 1)::Int64        | │    %42 = Base.add_int(%41, 1)::Int64
│    %41 = Base.getfield(_varinfo, :num_produce): |     │    %43 = Base.getfield(_varinfo, :num_produce):
│          Base.setfield!(%41, :x, %40)::Int64  │          Base.setfield!(%43, :x, %42)::Int64
│    %43 = invoke Distributions.logpdf(%27::Normal{Fl | │    %45 = invoke Distributions.logpdf(%29::Normal{Fl
│    %44 = Base.getfield(_varinfo, :logp)::Base.R |     │    %46 = Base.getfield(_varinfo, :logp)::Base.R
│    %45 = Base.getfield(%44, :x)::Float64    | │    %47 = Base.getfield(%46, :x)::Float64
│    %46 = Base.add_float(%45, %43)::Float64  | │    %48 = Base.add_float(%47, %45)::Float64
│    %47 = Base.getfield(_varinfo, :logp)::Base.R |     │    %49 = Base.getfield(_varinfo, :logp)::Base.R
│          Base.setfield!(%47, :x, %46)::Float64        │          Base.setfield!(%49, :x, %48)::Float64
│    %49 = %new(NamedTuple{(:m, :x), Tuple{Float64, F | │    %51 = %new(NamedTuple{(:m, :x), Tuple{Float64, F
└───       return %49                                 | └───       return %51
) => NamedTuple{(:m, :x), Tuple{Float64, Float64}}              ) => NamedTuple{(:m, :x), Tuple{Float64, Float64}}

Model 2

diff -y model2_lowered_old.jl model2_lowered_new.jl
CodeInfo(                                                       CodeInfo(
1 ──        invoke Core.TypeVar(Symbol("#s73")::Symbo | 1 ───        Base.arraysize(x, 1)::Int64
│    %2   = invoke DynamicPPL.assume(_2::Random.Merse | │     %2   = Base.arraysize(x, 2)::Int64
│    %3   = Base.getfield(%2, 1)::Float64     | │     %3   = Base.sle_int(1, 1)::Bool
│    %4   = Base.getfield(%2, 2)::Float64     | └────        goto #3 if not %3
│    %5   = Base.getfield(_varinfo, :logp)::Base. |     2 ─── %5   = Base.sle_int(1, 0)::Bool
│    %6   = Base.getfield(%5, :x)::Float64    | └────        goto #4
│    %7   = Base.add_float(%6, %4)::Float64   | 3 ───        nothing::Nothing
│    %8   = Base.getfield(_varinfo, :logp)::Base. |     4 ┄── %8   = φ (#2 => %5, #3 => false)::Bool
│           Base.setfield!(%8, :x, %7)::Float64 └────        goto #6 if not %8
│    %10  = Base.arraylen(y)::Int64           | 5 ───        invoke Base.getindex(()::Tuple, 1::Int64
│    %11  = Base.sle_int(1, %10)::Bool        | └────        unreachable
│    %12  = Base.ifelse(%11, %10, 0)::Int64   | 6 ───        goto #7
│    %13  = Base.slt_int(%12, 1)::Bool        | 7 ───        goto #8
└───        goto #3 if not %13                        | 8 ───        goto #9
2 ──        goto #4                                   | 9 ───        goto #10
3 ──        goto #4                                   | 10 ──        goto #11
4 ┄─ %17  = φ (#2 => true, #3 => false)::Bool | 11 ──        goto #12
│    %18  = φ (#3 => 1)::Int64                | 12 ──        invoke Core.TypeVar(Symbol("#s72")::Symb
│    %19  = φ (#3 => 1)::Int64                | │            nothing::Nothing
│    %20  = Base.not_int(%17)::Bool           | │            nothing::Nothing
└───        goto #40 if not %20                       | │     %21  = invoke DynamicPPL.assume(_2::Random.Mers
5 ┄─ %22  = φ (#4 => %18, #38 => %102)::Int64 | │     %22  = Base.getfield(%21, 1)::Float64
│    %23  = φ (#4 => %19, #38 => %103)::Int64 | │     %23  = Base.getfield(%21, 2)::Float64
└───        goto #12 if not true                      | │     %24  = Base.getfield(_varinfo, :logp)::Base
6 ── %25  = Base.le_float(0.0, %3)::Bool      | │     %25  = Base.getfield(%24, :x)::Float64
└───        goto #8 if not %25                        | │     %26  = Base.add_float(%25, %23)::Float64
7 ── %27  = Base.le_float(%3, 1.0)::Bool      | │     %27  = Base.getfield(_varinfo, :logp)::Base
└───        goto #9                                   | │            Base.setfield!(%27, :x, %26)::Float6
8 ──        nothing::Nothing                  | │     %29  = Base.sle_int(1, 1)::Bool
9 ┄─ %30  = φ (#7 => %27, #8 => false)::Bool  | └────        goto #14 if not %29
│    %31  = Base.not_int(%30)::Bool           | 13 ── %31  = Base.sle_int(1, 0)::Bool
└───        goto #11 if not %31                       | └────        goto #15
10 ─ %33  = Distributions.string("Bernoulli", ": the  | 14 ──        nothing::Nothing
│    %34  = Distributions.ArgumentError(%33)::Any       15 ┄─ %34  = φ (#13 => %31, #14 => false)::Bool
│           Distributions.throw(%34)::Union{} | └────        goto #17 if not %34
└───        unreachable                               | 16 ──        invoke Base.getindex(()::Tuple, 1::Int64
11 ─        nothing::Nothing                  | └────        unreachable
12 ┄ %38  = %new(Bernoulli{Float64}, %3)::Bernoul |     17 ──        goto #18
└───        goto #13                                  | 18 ──        goto #19
13 ─        goto #14                                  | 19 ──        goto #20
14 ─ %41  = invoke Core.TypeVar(Symbol("#s71")::Symbo | 20 ──        goto #21
│    %42  = Core.apply_type(Main.AbstractVector, %41)   21 ──        goto #22
│    %43  = Core.UnionAll(%41, %42)::Any      | 22 ──        goto #23
│    %44  = Core.apply_type(Main.Union, Distribution, | 23 ──        invoke Core.TypeVar(Symbol("#s71")::Symb
│    %45  = (%38 isa %44)::Bool               | │            nothing::Nothing
└───        goto #39 if not %45                       | │            nothing::Nothing
15 ─        nothing::Nothing                  | │     %47  = invoke DynamicPPL.assume(_2::Random.Mers
│    %48  = Core.tuple(%22)::Tuple{Int64}     | │     %48  = Base.getfield(%47, 1)::Float64
│    %49  = Core.tuple(%48)::Tuple{Tuple{Int64}}        │     %49  = Base.getfield(%47, 2)::Float64
│    %50  = invoke VarName(:y::Symbol, %49::Tuple{Tup | │     %50  = Base.getfield(_varinfo, :logp)::Base
│    %51  = Core.tuple(%22)::Tuple{Int64}     | │     %51  = Base.getfield(%50, :x)::Float64
│    %52  = Core.tuple(%51)::Tuple{Tuple{Int64}}        │     %52  = Base.add_float(%51, %49)::Float64
│    %53  = Core.tuple(%22)::Tuple{Int64}     | │     %53  = Base.getfield(_varinfo, :logp)::Base
│    %54  = Core.tuple(%53)::Tuple{Tuple{Int64}}        │            Base.setfield!(%53, :x, %52)::Float6
│    %55  = invoke VarName(:y::Symbol, %54::Tuple{Tup | │     %55  = Core.tuple(%22, %48)::Tuple{Float64,
│    %56  = (DynamicPPL.inargnames)(%55, _model): |     │     %56  = $(Expr(:foreigncall, :(:jl_alloc_array_1
│    %57  = !%56::Any                         | │            Base.arraysize(%56, 1)::Int64
└───        goto #17 if not %57                       | └────        goto #29 if not true
16 ─        goto #18                                  | 24 ┄─ %59  = φ (#23 => 1, #28 => %70)::Int64
17 ─ %60  = (DynamicPPL.inmissings)(%55, _model): |     │     %60  = φ (#23 => 1, #28 => %71)::Int64
18 ┄ %61  = φ (#16 => %57, #17 => %60)::Any   | │     %61  = φ (#23 => 1, #28 => %64)::Int64
└───        goto #20 if not %61                       | │     %62  = Base.getfield(%55, %59, true)::Float
19 ─        goto #21                                  | │            Base.arrayset(false, %56, %62, %61):
20 ─        Base.arrayref(true, y, %22)::Int64  │     %64  = Base.add_int(%61, 1)::Int64
21 ┄ %65  = φ (#19 => true, #20 => false)::Bool │     %65  = (%60 === 2)::Bool
└───        goto #23 if not %65                       | └────        goto #26 if not %65
22 ─        (DynamicPPL.tilde_assume)(_rng, _context, | 25 ──        goto #27
└───        unreachable                               | 26 ── %68  = Base.add_int(%60, 1)::Int64
23 ─ %69  = Base.arrayref(true, y, %22)::Int64  └────        goto #27
│    %70  = Base.getfield(_varinfo, :num_produce) |     27 ┄─ %70  = φ (#26 => %68)::Int64
│    %71  = Base.getfield(%70, :x)::Int64     | │     %71  = φ (#26 => %68)::Int64
│    %72  = Base.add_int(%71, 1)::Int64       | │     %72  = φ (#25 => true, #26 => false)::Bool
│    %73  = Base.getfield(_varinfo, :num_produce) |     │     %73  = Base.not_int(%72)::Bool
│           Base.setfield!(%73, :x, %72)::Int64 └────        goto #29 if not %73
│    %75  = (%69 === 0)::Bool                 | 28 ──        goto #24
└───        goto #25 if not %75                       | 29 ┄─        goto #30
24 ─ %77  = Base.sitofp(Float64, 1)::Float64  | 30 ── %77  = Core.tuple(0.5, 0.5)::Core.Const((0.
│    %78  = Base.sub_float(%77, %3)::Float64  | │     %78  = $(Expr(:foreigncall, :(:jl_alloc_array_1
└───        goto #28                                  | │            Base.arraysize(%78, 1)::Int64
25 ─ %80  = (%69 === 1)::Bool                 | └────        goto #36 if not true
└───        goto #27 if not %80                       | 31 ┄─ %81  = φ (#30 => 1, #35 => %92)::Int64
26 ─        goto #28                                  | │     %82  = φ (#30 => 1, #35 => %93)::Int64
27 ─        goto #28                                  | │     %83  = φ (#30 => 1, #35 => %86)::Int64
28 ┄ %84  = φ (#24 => %78, #26 => %3, #27 => 0.0) |     │     %84  = Base.getfield(%77, %81, true)::Float
│    %85  = invoke Distributions.log(%84::Float64)      │            Base.arrayset(false, %78, %84, %83):
└───        goto #29                                  | │     %86  = Base.add_int(%83, 1)::Int64
29 ─        goto #30                                  | │     %87  = (%82 === 2)::Bool
30 ─        goto #31                                  | └────        goto #33 if not %87
31 ─        goto #32                                  | 32 ──        goto #34
32 ─        goto #33                                  | 33 ── %90  = Base.add_int(%82, 1)::Int64
33 ─ %91  = Base.getfield(_varinfo, :logp)::Base. |     └────        goto #34
│    %92  = Base.getfield(%91, :x)::Float64   | 34 ┄─ %92  = φ (#33 => %90)::Int64
│    %93  = Base.add_float(%92, %85)::Float64 | │     %93  = φ (#33 => %90)::Int64
│    %94  = Base.getfield(_varinfo, :logp)::Base. |     │     %94  = φ (#32 => true, #33 => false)::Bool
│           Base.setfield!(%94, :x, %93)::Float64       │     %95  = Base.not_int(%94)::Bool
└───        goto #34                                  | └────        goto #36 if not %95
34 ─ %97  = (%23 === %12)::Bool               | 35 ──        goto #31
└───        goto #36 if not %97                       | 36 ┄─        goto #37
35 ─        goto #37                                  | 37 ── %99  = $(Expr(:foreigncall, :(:jl_alloc_array_1
36 ─ %100 = Base.add_int(%23, 1)::Int64       | │     %100 = Base.sle_int(1, %2)::Bool
└───        goto #37                                  | │     %101 = Base.ifelse(%100, %2, 0)::Int64
37 ┄ %102 = φ (#36 => %100)::Int64            | │     %102 = Base.slt_int(%101, 1)::Bool
│    %103 = φ (#36 => %100)::Int64            | └────        goto #39 if not %102
│    %104 = φ (#35 => true, #36 => false)::Bool 38 ──        goto #40
│    %105 = Base.not_int(%104)::Bool          | 39 ──        goto #40
└───        goto #40 if not %105                      | 40 ┄─ %106 = φ (#38 => true, #39 => false)::Bool
38 ─        goto #5                                   | │     %107 = φ (#39 => 1)::Int64
39 ─ %108 = Main.ArgumentError("Right-hand side of a  | │     %108 = φ (#39 => 1)::Int64
│           Main.throw(%108)::Union{}         | │     %109 = Base.not_int(%106)::Bool
└───        unreachable                               | └────        goto #134 if not %109
40 ┄        return nothing                            | 41 ┄─ %111 = φ (#40 => %107, #131 => %349)::Int64
> Nothing                                                     | │     %112 = φ (#40 => %108, #131 => %350)::Int64
                                                                │     %113 = Base.sle_int(1, 1)::Bool
                                                              > └────        goto #43 if not %113
                                                              > 42 ── %115 = Base.sle_int(1, 0)::Bool
                                                              > └────        goto #44
                                                              > 43 ──        nothing::Nothing
                                                              > 44 ┄─ %118 = φ (#42 => %115, #43 => false)::Bool
                                                                └────        goto #46 if not %118
                                                              > 45 ──        invoke Base.getindex(()::Tuple, 1::Int64
                                                              > └────        unreachable
                                                              > 46 ──        goto #47
                                                              > 47 ──        goto #48
                                                              > 48 ──        goto #49
                                                              > 49 ──        goto #50
                                                              > 50 ──        goto #54 if not true
                                                              > 51 ── %127 = invoke Distributions.isprobvec(%78::Vect
                                                              > │     %128 = Base.not_int(%127)::Bool
                                                              > └────        goto #53 if not %128
                                                              > 52 ── %130 = Distributions.string("Categorical", ": t
                                                              > │     %131 = Distributions.ArgumentError(%130)::A
                                                              > │            Distributions.throw(%131)::Union{}
                                                                └────        unreachable
                                                              > 53 ──        nothing::Nothing
                                                              > 54 ┄─ %135 = Base.arraylen(%78)::Int64
                                                              > │     %136 = Base.slt_int(%135, 0)::Bool
                                                              > │     %137 = Base.ifelse(%136, 0, %135)::Int64
                                                                │     %138 = %new(Base.OneTo{Int64}, %137)::Base.
                                                              > │     %139 = Base.sle_int(1, 1)::Bool
                                                              > └────        goto #56 if not %139
                                                              > 55 ── %141 = Base.sle_int(1, 0)::Bool
                                                              > └────        goto #57
                                                              > 56 ──        nothing::Nothing
                                                              > 57 ┄─ %144 = φ (#55 => %141, #56 => false)::Bool
                                                                └────        goto #59 if not %144
                                                              > 58 ──        invoke Base.getindex(()::Tuple, 1::Int64
                                                              > └────        unreachable
                                                              > 59 ──        goto #60
                                                              > 60 ──        goto #61
                                                              > 61 ──        goto #62
                                                              > 62 ──        goto #63
                                                              > 63 ── %152 = invoke $(QuoteNode(Distributions.var"#_#
                                                              > └────        goto #64
                                                              > 64 ──        goto #65
                                                              > 65 ──        goto #66
                                                              > 66 ──        goto #67
                                                              > 67 ──        goto #68
                                                              > 68 ── %158 = invoke Core.TypeVar(Symbol("#s69")::Symb
                                                              > │     %159 = Core.apply_type(Main.AbstractVector, %15
                                                              > │     %160 = Core.UnionAll(%158, %159)::Any
                                                              > │     %161 = Core.apply_type(Main.Union, Distribution
                                                              > │     %162 = (%152 isa %161)::Bool
                                                              > └────        goto #133 if not %162
                                                              > 69 ──        nothing::Nothing
                                                              > │     %165 = Core.tuple(%111)::Tuple{Int64}
                                                              > │     %166 = Core.tuple(%165)::Tuple{Tuple{Int64}
                                                              > │     %167 = invoke VarName(:k::Symbol, %166::Tuple{T
                                                              > │     %168 = Core.tuple(%111)::Tuple{Int64}
                                                              > │     %169 = Core.tuple(%168)::Tuple{Tuple{Int64}
                                                              > │     %170 = Core.tuple(%111)::Tuple{Int64}
                                                              > │     %171 = Core.tuple(%170)::Tuple{Tuple{Int64}
                                                              > │     %172 = invoke VarName(:k::Symbol, %171::Tuple{T
                                                              > │     %173 = (DynamicPPL.inargnames)(%172, _model)
                                                                │     %174 = !%173::Any
                                                              > └────        goto #71 if not %174
                                                              > 70 ──        goto #72
                                                              > 71 ── %177 = (DynamicPPL.inmissings)(%172, _model)
                                                                72 ┄─ %178 = φ (#70 => %174, #71 => %177)::Any
                                                                └────        goto #74 if not %178
                                                              > 73 ──        goto #75
                                                              > 74 ──        Base.arrayref(true, %99, %111)::Int6
                                                              > 75 ┄─ %182 = φ (#73 => true, #74 => false)::Bool
                                                                └────        goto #77 if not %182
                                                              > 76 ── %184 = (DynamicPPL.tilde_assume)(_rng, _context
                                                              > │            Base.setindex!(%99, %184, %111)::Any
                                                                └────        goto #95
                                                              > 77 ── %187 = Base.arrayref(true, %99, %111)::Int6
                                                              > │     %188 = Base.getfield(_varinfo, :num_produce)
                                                                │     %189 = Base.getfield(%188, :x)::Int64
                                                              > │     %190 = Base.add_int(%189, 1)::Int64
                                                              > │     %191 = Base.getfield(_varinfo, :num_produce)
                                                                │            Base.setfield!(%191, :x, %190)::Int6
                                                              > │     %193 = Base.getfield(%152, :p)::Vector{Floa
                                                              > │     %194 = Base.getfield(%152, :support)::Base.
                                                              > │     %195 = invoke Base.Sort.searchsorted(%194::Base
                                                              > │     %196 = Base.getfield(%195, :stop)::Int64
                                                                │     %197 = Base.getfield(%195, :start)::Int64
                                                                │     %198 = Base.Checked.checked_ssub_int(%196, %197
                                                              > │     %199 = Base.getfield(%198, 1)::Int64
                                                              > │     %200 = Base.getfield(%198, 2)::Bool
                                                              > └────        goto #79 if not %200
                                                              > 78 ──        invoke Base.Checked.throw_overflowerr_bi
                                                              > └────        unreachable
                                                              > 79 ──        goto #80
                                                              > 80 ── %205 = Base.Checked.checked_sadd_int(%199, 1)
                                                                │     %206 = Base.getfield(%205, 1)::Int64
                                                              > │     %207 = Base.getfield(%205, 2)::Bool
                                                              > └────        goto #82 if not %207
                                                              > 81 ──        invoke Base.Checked.throw_overflowerr_bi
                                                              > └────        unreachable
                                                              > 82 ──        goto #83
                                                              > 83 ──        goto #84
                                                              > 84 ── %213 = Base.slt_int(0, %206)::Bool
                                                              > └────        goto #85
                                                              > 85 ──        goto #87 if not %213
                                                              > 86 ── %216 = Base.arrayref(true, %193, %187)::Flo
                                                              > └────        goto #88
                                                              > 87 ──        goto #88
                                                              > 88 ┄─ %219 = φ (#86 => %216, #87 => 0.0)::Float64
                                                                │     %220 = invoke Distributions.log(%219::Float64)
                                                                └────        goto #89
                                                              > 89 ──        goto #90
                                                              > 90 ──        goto #91
                                                              > 91 ──        goto #92
                                                              > 92 ──        goto #93
                                                              > 93 ── %226 = Base.getfield(_varinfo, :logp)::Base
                                                              > │     %227 = Base.getfield(%226, :x)::Float64
                                                              > │     %228 = Base.add_float(%227, %220)::Float64
                                                                │     %229 = Base.getfield(_varinfo, :logp)::Base
                                                              > │            Base.setfield!(%229, :x, %228)::Floa
                                                              > └────        goto #94
                                                              > 94 ──        nothing::Nothing
                                                              > 95 ┄─ %233 = Base.arrayref(true, %99, %111)::Int6
                                                              > │     %234 = Base.arrayref(true, %56, %233)::Floa
                                                              > │     %235 = Base.arrayref(true, %99, %111)::Int6
                                                              > │     %236 = Base.arrayref(true, %56, %235)::Floa
                                                              > │     %237 = Core.tuple(%234, %236)::Tuple{Float6
                                                              > │     %238 = $(Expr(:foreigncall, :(:jl_alloc_array_1
                                                              > │            Base.arraysize(%238, 1)::Int64
                                                              > └────        goto #101 if not true
                                                              > 96 ┄─ %241 = φ (#95 => 1, #100 => %252)::Int64
                                                                │     %242 = φ (#95 => 1, #100 => %253)::Int64
                                                                │     %243 = φ (#95 => 1, #100 => %246)::Int64
                                                                │     %244 = Base.getfield(%237, %241, true)::Flo
                                                              > │            Base.arrayset(false, %238, %244, %243)
                                                                │     %246 = Base.add_int(%243, 1)::Int64
                                                              > │     %247 = (%242 === 2)::Bool
                                                              > └────        goto #98 if not %247
                                                              > 97 ──        goto #99
                                                              > 98 ── %250 = Base.add_int(%242, 1)::Int64
                                                              > └────        goto #99
                                                              > 99 ┄─ %252 = φ (#98 => %250)::Int64
                                                              > │     %253 = φ (#98 => %250)::Int64
                                                              > │     %254 = φ (#97 => true, #98 => false)::Bool
                                                                │     %255 = Base.not_int(%254)::Bool
                                                              > └────        goto #101 if not %255
                                                              > 100 ─        goto #96
                                                              > 101 ┄        goto #102
                                                              > 102 ─ %259 = Base.arraylen(%238)::Int64
                                                              > │     %260 = Base.arraylen(%238)::Int64
                                                              > │     %261 = (%259 === %260)::Bool
                                                              > └────        goto #104 if not %261
                                                              > 103 ─ %263 = %new(PDMats.ScalMat{Float64}, %259, 1.0,
                                                              > │     %264 = %new(IsoNormal, %238, %263)::IsoNorm
                                                              > └────        goto #105
                                                              > 104 ─ %266 = Distributions.DimensionMismatch("The dim
                                                              > │            Distributions.throw(%266)::Union{}
                                                                └────        unreachable
                                                              > 105 ─        goto #106
                                                              > 106 ─ %270 = invoke Core.TypeVar(Symbol("#s68")::Symb
                                                              > │     %271 = Core.apply_type(Main.AbstractVector, %27
                                                              > │     %272 = Core.UnionAll(%270, %271)::Any
                                                              > │     %273 = Core.apply_type(Main.Union, Distribution
                                                              > │     %274 = (%264 isa %273)::Bool
                                                              > └────        goto #132 if not %274
                                                              > 107 ─        nothing::Nothing
                                                              > └────        goto #109 if not false
                                                              > 108 ─        nothing::Nothing
                                                              > 109 ┄        goto #111 if not false
                                                              > 110 ─        nothing::Nothing
                                                              > 111 ┄ %281 = Base.arraysize(x, 1)::Int64
                                                              > │            Base.arraysize(x, 2)::Int64
                                                              > │     %283 = Base.slt_int(%281, 0)::Bool
                                                              > │     %284 = Base.ifelse(%283, 0, %281)::Int64
                                                                │     %285 = %new(Base.OneTo{Int64}, %284)::Base.
                                                              > │     %286 = %new(Base.Slice{Base.OneTo{Int64}}, %285
                                                              > └────        goto #116 if not true
                                                              > 112 ─ %288 = Core.tuple(%286, %111)::Tuple{Base.S
                                                              > │            Base.arraysize(x, 1)::Int64
                                                              > │     %290 = Base.arraysize(x, 2)::Int64
                                                              > │     %291 = Base.slt_int(%290, 0)::Bool
                                                              > │     %292 = Base.ifelse(%291, 0, %290)::Int64
                                                                │     %293 = Base.sle_int(1, %111)::Bool
                                                              > │     %294 = Base.sle_int(%111, %292)::Bool
                                                              > │     %295 = Base.and_int(%293, %294)::Bool
                                                              > │     %296 = Base.and_int(%295, true)::Bool
                                                              > │     %297 = Base.and_int(true, %296)::Bool
                                                              > └────        goto #114 if not %297
                                                              > 113 ─        goto #115
                                                              > 114 ─        invoke Base.throw_boundserror(_7::Matrix
                                                              > └────        unreachable
                                                              > 115 ─        nothing::Nothing
                                                              > 116 ┄        invoke Base._unsafe_getindex($(QuoteNode
                                                              > └────        goto #117
                                                              > 117 ─        goto #118
                                                              > 118 ─        goto #120 if not false
                                                              > 119 ─        nothing::Nothing
                                                              > 120 ┄ %308 = Base.arraysize(x, 1)::Int64
                                                              > │            Base.arraysize(x, 2)::Int64
                                                              > │     %310 = Base.slt_int(%308, 0)::Bool
                                                              > │     %311 = Base.ifelse(%310, 0, %308)::Int64
                                                                │     %312 = %new(Base.OneTo{Int64}, %311)::Base.
                                                              > │     %313 = %new(Base.Slice{Base.OneTo{Int64}}, %312
                                                              > └────        goto #125 if not true
                                                              > 121 ─ %315 = Core.tuple(%313, %111)::Tuple{Base.S
                                                              > │            Base.arraysize(x, 1)::Int64
                                                              > │     %317 = Base.arraysize(x, 2)::Int64
                                                              > │     %318 = Base.slt_int(%317, 0)::Bool
                                                              > │     %319 = Base.ifelse(%318, 0, %317)::Int64
                                                                │     %320 = Base.sle_int(1, %111)::Bool
                                                              > │     %321 = Base.sle_int(%111, %319)::Bool
                                                              > │     %322 = Base.and_int(%320, %321)::Bool
                                                              > │     %323 = Base.and_int(%322, true)::Bool
                                                              > │     %324 = Base.and_int(true, %323)::Bool
                                                              > └────        goto #123 if not %324
                                                              > 122 ─        goto #124
                                                              > 123 ─        invoke Base.throw_boundserror(_7::Matrix
                                                              > └────        unreachable
                                                              > 124 ─        nothing::Nothing
                                                              > 125 ┄ %330 = invoke Base._unsafe_getindex($(QuoteNode
                                                              > └────        goto #126
                                                              > 126 ─        goto #127
                                                              > 127 ─ %333 = Base.getfield(_varinfo, :num_produce)
                                                                │     %334 = Base.getfield(%333, :x)::Int64
                                                              > │     %335 = Base.add_int(%334, 1)::Int64
                                                              > │     %336 = Base.getfield(_varinfo, :num_produce)
                                                                │            Base.setfield!(%336, :x, %335)::Int6
                                                              > │     %338 = invoke Distributions.logpdf(%264::IsoNor
                                                              > │     %339 = Base.getfield(_varinfo, :logp)::Base
                                                              > │     %340 = Base.getfield(%339, :x)::Float64
                                                              > │     %341 = Base.add_float(%340, %338)::Float64
                                                                │     %342 = Base.getfield(_varinfo, :logp)::Base
                                                              > │            Base.setfield!(%342, :x, %341)::Floa
                                                              > │     %344 = (%112 === %101)::Bool
                                                              > └────        goto #129 if not %344
                                                              > 128 ─        goto #130
                                                              > 129 ─ %347 = Base.add_int(%112, 1)::Int64
                                                              > └────        goto #130
                                                              > 130 ┄ %349 = φ (#129 => %347)::Int64
                                                              > │     %350 = φ (#129 => %347)::Int64
                                                              > │     %351 = φ (#128 => true, #129 => false)::Boo
                                                              > │     %352 = Base.not_int(%351)::Bool
                                                              > └────        goto #134 if not %352
                                                              > 131 ─        goto #41
                                                              > 132 ─ %355 = Main.ArgumentError("Right-hand side of a
                                                              > │            Main.throw(%355)::Union{}
                                                              > └────        unreachable
                                                              > 133 ─ %358 = Main.ArgumentError("Right-hand side of a
                                                              > │            Main.throw(%358)::Union{}
                                                              > └────        unreachable
                                                              > 134 ┄        return %99
                                                              > ) => Vector{Int64}

Model 3

diff -y model3_lowered_old.jl model3_lowered_new.jl
CodeInfo(                                                       CodeInfo(
1 ───        Base.arraysize(x, 1)::Int64        1 ───        Base.arraysize(x, 1)::Int64
│     %2   = Base.arraysize(x, 2)::Int64        │     %2   = Base.arraysize(x, 2)::Int64
│     %3   = Base.sle_int(1, 1)::Bool           │     %3   = Base.sle_int(1, 1)::Bool
└────        goto #3 if not %3                          └────        goto #3 if not %3
2 ─── %5   = Base.sle_int(1, 0)::Bool           2 ─── %5   = Base.sle_int(1, 0)::Bool
└────        goto #4                                    └────        goto #4
3 ───        nothing::Nothing                   3 ───        nothing::Nothing
4 ┄── %8   = φ (#2 => %5, #3 => false)::Bool    4 ┄── %8   = φ (#2 => %5, #3 => false)::Bool
└────        goto #6 if not %8                          └────        goto #6 if not %8
5 ───        invoke Base.getindex(()::Tuple, 1::Int64   5 ───        invoke Base.getindex(()::Tuple, 1::Int64
└────        unreachable                                └────        unreachable
6 ───        goto #7                                    6 ───        goto #7
7 ───        goto #8                                    7 ───        goto #8
8 ───        goto #9                                    8 ───        goto #9
9 ───        goto #10                                   9 ───        goto #10
10 ──        goto #11                                   10 ──        goto #11
11 ──        goto #12                                   11 ──        goto #12
12 ──        invoke Core.TypeVar(Symbol("#s72")::Symb   12 ──        invoke Core.TypeVar(Symbol("#s72")::Symb
│     %19  = invoke DynamicPPL.assume(_2::Random.Mers | │            nothing::Nothing
│     %20  = Base.getfield(%19, 1)::Float64   | │            nothing::Nothing
│     %21  = Base.getfield(%19, 2)::Float64   | │     %21  = invoke DynamicPPL.assume(_2::Random.Mers
│     %22  = Base.getfield(_varinfo, :logp)::Base |     │     %22  = Base.getfield(%21, 1)::Float64
│     %23  = Base.getfield(%22, :x)::Float64  | │     %23  = Base.getfield(%21, 2)::Float64
│     %24  = Base.add_float(%23, %21)::Float64  │     %24  = Base.getfield(_varinfo, :logp)::Base
│     %25  = Base.getfield(_varinfo, :logp)::Base |     │     %25  = Base.getfield(%24, :x)::Float64
│            Base.setfield!(%25, :x, %24)::Float6 |     │     %26  = Base.add_float(%25, %23)::Float64
│     %27  = Base.sle_int(1, 1)::Bool         | │     %27  = Base.getfield(_varinfo, :logp)::Base
└────        goto #14 if not %27                      | │            Base.setfield!(%27, :x, %26)::Float6
13 ── %29  = Base.sle_int(1, 0)::Bool         | │     %29  = Base.sle_int(1, 1)::Bool
                                                              > └────        goto #14 if not %29
                                                              > 13 ── %31  = Base.sle_int(1, 0)::Bool
└────        goto #15                                   └────        goto #15
14 ──        nothing::Nothing                   14 ──        nothing::Nothing
15 ┄─ %32  = φ (#13 => %29, #14 => false)::Bool 15 ┄─ %34  = φ (#13 => %31, #14 => false)::Bool
└────        goto #17 if not %32                      | └────        goto #17 if not %34
16 ──        invoke Base.getindex(()::Tuple, 1::Int64   16 ──        invoke Base.getindex(()::Tuple, 1::Int64
└────        unreachable                                └────        unreachable
17 ──        goto #18                                   17 ──        goto #18
18 ──        goto #19                                   18 ──        goto #19
19 ──        goto #20                                   19 ──        goto #20
20 ──        goto #21                                   20 ──        goto #21
21 ──        goto #22                                   21 ──        goto #22
22 ──        goto #23                                   22 ──        goto #23
23 ──        invoke Core.TypeVar(Symbol("#s71")::Symb   23 ──        invoke Core.TypeVar(Symbol("#s71")::Symb
│     %43  = invoke DynamicPPL.assume(_2::Random.Mers | │            nothing::Nothing
│     %44  = Base.getfield(%43, 1)::Float64   | │            nothing::Nothing
│     %45  = Base.getfield(%43, 2)::Float64   | │     %47  = invoke DynamicPPL.assume(_2::Random.Mers
│     %46  = Base.getfield(_varinfo, :logp)::Base |     │     %48  = Base.getfield(%47, 1)::Float64
│     %47  = Base.getfield(%46, :x)::Float64  | │     %49  = Base.getfield(%47, 2)::Float64
│     %48  = Base.add_float(%47, %45)::Float64  │     %50  = Base.getfield(_varinfo, :logp)::Base
│     %49  = Base.getfield(_varinfo, :logp)::Base |     │     %51  = Base.getfield(%50, :x)::Float64
│            Base.setfield!(%49, :x, %48)::Float6 |     │     %52  = Base.add_float(%51, %49)::Float64
│     %51  = Core.tuple(%20, %44)::Tuple{Float64, |     │     %53  = Base.getfield(_varinfo, :logp)::Base
│     %52  = $(Expr(:foreigncall, :(:jl_alloc_array_1 | │            Base.setfield!(%53, :x, %52)::Float6
│            Base.arraysize(%52, 1)::Int64    | │     %55  = Core.tuple(%22, %48)::Tuple{Float64,
                                                              > │     %56  = $(Expr(:foreigncall, :(:jl_alloc_array_1
                                                              > │            Base.arraysize(%56, 1)::Int64
└────        goto #29 if not true                       └────        goto #29 if not true
24 ┄─ %55  = φ (#23 => 1, #28 => %66)::Int64  | 24 ┄─ %59  = φ (#23 => 1, #28 => %70)::Int64
│     %56  = φ (#23 => 1, #28 => %67)::Int64  | │     %60  = φ (#23 => 1, #28 => %71)::Int64
│     %57  = φ (#23 => 1, #28 => %60)::Int64  | │     %61  = φ (#23 => 1, #28 => %64)::Int64
│     %58  = Base.getfield(%51, %55, true)::Float |     │     %62  = Base.getfield(%55, %59, true)::Float
│            Base.arrayset(false, %52, %58, %57): |     │            Base.arrayset(false, %56, %62, %61):
│     %60  = Base.add_int(%57, 1)::Int64      | │     %64  = Base.add_int(%61, 1)::Int64
│     %61  = (%56 === 2)::Bool                | │     %65  = (%60 === 2)::Bool
└────        goto #26 if not %61                      | └────        goto #26 if not %65
25 ──        goto #27                                   25 ──        goto #27
26 ── %64  = Base.add_int(%56, 1)::Int64      | 26 ── %68  = Base.add_int(%60, 1)::Int64
└────        goto #27                                   └────        goto #27
27 ┄─ %66  = φ (#26 => %64)::Int64            | 27 ┄─ %70  = φ (#26 => %68)::Int64
│     %67  = φ (#26 => %64)::Int64            | │     %71  = φ (#26 => %68)::Int64
│     %68  = φ (#25 => true, #26 => false)::Bool        │     %72  = φ (#25 => true, #26 => false)::Bool
│     %69  = Base.not_int(%68)::Bool          | │     %73  = Base.not_int(%72)::Bool
└────        goto #29 if not %69                      | └────        goto #29 if not %73
28 ──        goto #24                                   28 ──        goto #24
29 ┄─        goto #30                                   29 ┄─        goto #30
30 ── %73  = Core.tuple(0.5, 0.5)::Core.Const((0. |     30 ── %77  = Core.tuple(0.5, 0.5)::Core.Const((0.
│     %74  = $(Expr(:foreigncall, :(:jl_alloc_array_1 | │     %78  = $(Expr(:foreigncall, :(:jl_alloc_array_1
│            Base.arraysize(%74, 1)::Int64    | │            Base.arraysize(%78, 1)::Int64
└────        goto #36 if not true                       └────        goto #36 if not true
31 ┄─ %77  = φ (#30 => 1, #35 => %88)::Int64  | 31 ┄─ %81  = φ (#30 => 1, #35 => %92)::Int64
│     %78  = φ (#30 => 1, #35 => %89)::Int64  | │     %82  = φ (#30 => 1, #35 => %93)::Int64
│     %79  = φ (#30 => 1, #35 => %82)::Int64  | │     %83  = φ (#30 => 1, #35 => %86)::Int64
│     %80  = Base.getfield(%73, %77, true)::Float |     │     %84  = Base.getfield(%77, %81, true)::Float
│            Base.arrayset(false, %74, %80, %79): |     │            Base.arrayset(false, %78, %84, %83):
│     %82  = Base.add_int(%79, 1)::Int64      | │     %86  = Base.add_int(%83, 1)::Int64
│     %83  = (%78 === 2)::Bool                | │     %87  = (%82 === 2)::Bool
└────        goto #33 if not %83                      | └────        goto #33 if not %87
32 ──        goto #34                                   32 ──        goto #34
33 ── %86  = Base.add_int(%78, 1)::Int64      | 33 ── %90  = Base.add_int(%82, 1)::Int64
└────        goto #34                                   └────        goto #34
34 ┄─ %88  = φ (#33 => %86)::Int64            | 34 ┄─ %92  = φ (#33 => %90)::Int64
│     %89  = φ (#33 => %86)::Int64            | │     %93  = φ (#33 => %90)::Int64
│     %90  = φ (#32 => true, #33 => false)::Bool        │     %94  = φ (#32 => true, #33 => false)::Bool
│     %91  = Base.not_int(%90)::Bool          | │     %95  = Base.not_int(%94)::Bool
└────        goto #36 if not %91                      | └────        goto #36 if not %95
35 ──        goto #31                                   35 ──        goto #31
36 ┄─        goto #37                                   36 ┄─        goto #37
37 ── %95  = $(Expr(:foreigncall, :(:jl_alloc_array_1 | 37 ── %99  = $(Expr(:foreigncall, :(:jl_alloc_array_1
│     %96  = Base.sle_int(1, %2)::Bool        | │     %100 = Base.sle_int(1, %2)::Bool
│     %97  = Base.ifelse(%96, %2, 0)::Int64   | │     %101 = Base.ifelse(%100, %2, 0)::Int64
│     %98  = Base.slt_int(%97, 1)::Bool       | │     %102 = Base.slt_int(%101, 1)::Bool
└────        goto #39 if not %98                      | └────        goto #39 if not %102
38 ──        goto #40                                   38 ──        goto #40
39 ──        goto #40                                   39 ──        goto #40
40 ┄─ %102 = φ (#38 => true, #39 => false)::Bool        40 ┄─ %106 = φ (#38 => true, #39 => false)::Bool
│     %103 = φ (#39 => 1)::Int64              | │     %107 = φ (#39 => 1)::Int64
│     %104 = φ (#39 => 1)::Int64              | │     %108 = φ (#39 => 1)::Int64
│     %105 = Base.not_int(%102)::Bool         | │     %109 = Base.not_int(%106)::Bool
└────        goto #108 if not %105                    | └────        goto #134 if not %109
41 ┄─ %107 = φ (#40 => %103, #105 => %284)::Int64       41 ┄─ %111 = φ (#40 => %107, #131 => %349)::Int64
│     %108 = φ (#40 => %104, #105 => %285)::Int64       │     %112 = φ (#40 => %108, #131 => %350)::Int64
│     %109 = Base.sle_int(1, 1)::Bool         | │     %113 = Base.sle_int(1, 1)::Bool
└────        goto #43 if not %109                     | └────        goto #43 if not %113
42 ── %111 = Base.sle_int(1, 0)::Bool         | 42 ── %115 = Base.sle_int(1, 0)::Bool
└────        goto #44                                   └────        goto #44
43 ──        nothing::Nothing                   43 ──        nothing::Nothing
44 ┄─ %114 = φ (#42 => %111, #43 => false)::Bool        44 ┄─ %118 = φ (#42 => %115, #43 => false)::Bool
└────        goto #46 if not %114                     | └────        goto #46 if not %118
45 ──        invoke Base.getindex(()::Tuple, 1::Int64   45 ──        invoke Base.getindex(()::Tuple, 1::Int64
└────        unreachable                                └────        unreachable
46 ──        goto #47                                   46 ──        goto #47
47 ──        goto #48                                   47 ──        goto #48
48 ──        goto #49                                   48 ──        goto #49
49 ──        goto #50                                   49 ──        goto #50
50 ──        goto #54 if not true                       50 ──        goto #54 if not true
51 ── %123 = invoke Distributions.isprobvec(%74::Vect | 51 ── %127 = invoke Distributions.isprobvec(%78::Vect
│     %124 = Base.not_int(%123)::Bool         | │     %128 = Base.not_int(%127)::Bool
└────        goto #53 if not %124                     | └────        goto #53 if not %128
52 ── %126 = Distributions.string("Categorical", ": t | 52 ── %130 = Distributions.string("Categorical", ": t
│     %127 = Distributions.ArgumentError(%126)::A |     │     %131 = Distributions.ArgumentError(%130)::A
│            Distributions.throw(%127)::Union{} │            Distributions.throw(%131)::Union{}
└────        unreachable                                └────        unreachable
53 ──        nothing::Nothing                   53 ──        nothing::Nothing
54 ┄─ %131 = Base.arraylen(%74)::Int64        | 54 ┄─ %135 = Base.arraylen(%78)::Int64
│     %132 = Base.slt_int(%131, 0)::Bool      | │     %136 = Base.slt_int(%135, 0)::Bool
│     %133 = Base.ifelse(%132, 0, %131)::Int64  │     %137 = Base.ifelse(%136, 0, %135)::Int64
│     %134 = %new(Base.OneTo{Int64}, %133)::Base. |     │     %138 = %new(Base.OneTo{Int64}, %137)::Base.
│     %135 = Base.sle_int(1, 1)::Bool         | │     %139 = Base.sle_int(1, 1)::Bool
└────        goto #56 if not %135                     | └────        goto #56 if not %139
55 ── %137 = Base.sle_int(1, 0)::Bool         | 55 ── %141 = Base.sle_int(1, 0)::Bool
└────        goto #57                                   └────        goto #57
56 ──        nothing::Nothing                   56 ──        nothing::Nothing
57 ┄─ %140 = φ (#55 => %137, #56 => false)::Bool        57 ┄─ %144 = φ (#55 => %141, #56 => false)::Bool
└────        goto #59 if not %140                     | └────        goto #59 if not %144
58 ──        invoke Base.getindex(()::Tuple, 1::Int64   58 ──        invoke Base.getindex(()::Tuple, 1::Int64
└────        unreachable                                └────        unreachable
59 ──        goto #60                                   59 ──        goto #60
60 ──        goto #61                                   60 ──        goto #61
61 ──        goto #62                                   61 ──        goto #62
62 ──        goto #63                                   62 ──        goto #63
63 ── %148 = invoke $(QuoteNode(Distributions.var"#_# | 63 ── %152 = invoke $(QuoteNode(Distributions.var"#_#
└────        goto #64                                   └────        goto #64
64 ──        goto #65                                   64 ──        goto #65
65 ──        goto #66                                   65 ──        goto #66
66 ──        goto #67                                   66 ──        goto #67
67 ──        goto #68                                   67 ──        goto #68
68 ── %154 = invoke Core.TypeVar(Symbol("#s69")::Symb | 68 ── %158 = invoke Core.TypeVar(Symbol("#s69")::Symb
│     %155 = Core.apply_type(Main.AbstractVector, %15 | │     %159 = Core.apply_type(Main.AbstractVector, %15
│     %156 = Core.UnionAll(%154, %155)::Any   | │     %160 = Core.UnionAll(%158, %159)::Any
│     %157 = Core.apply_type(Main.Union, Distribution | │     %161 = Core.apply_type(Main.Union, Distribution
│     %158 = (%148 isa %157)::Bool            | │     %162 = (%152 isa %161)::Bool
└────        goto #107 if not %158                    | └────        goto #133 if not %162
69 ──        nothing::Nothing                   69 ──        nothing::Nothing
│     %161 = Core.tuple(%107)::Tuple{Int64}   | │     %165 = Core.tuple(%111)::Tuple{Int64}
│     %162 = Core.tuple(%161)::Tuple{Tuple{Int64} |     │     %166 = Core.tuple(%165)::Tuple{Tuple{Int64}
│     %163 = invoke VarName(:k::Symbol, %162::Tuple{T | │     %167 = invoke VarName(:k::Symbol, %166::Tuple{T
│     %164 = Core.tuple(%107)::Tuple{Int64}   | │     %168 = Core.tuple(%111)::Tuple{Int64}
│     %165 = Core.tuple(%164)::Tuple{Tuple{Int64} |     │     %169 = Core.tuple(%168)::Tuple{Tuple{Int64}
│     %166 = (DynamicPPL.tilde_assume)(_rng, _context | │     %170 = Core.tuple(%111)::Tuple{Int64}
│            Base.setindex!(%95, %166, %107)::Any       │     %171 = Core.tuple(%170)::Tuple{Tuple{Int64}
│     %168 = Base.arrayref(true, %95, %107)::Int6 |     │     %172 = invoke VarName(:k::Symbol, %171::Tuple{T
│     %169 = Base.arrayref(true, %52, %168)::Floa |     │     %173 = (DynamicPPL.inargnames)(%172, _model)
│     %170 = Base.arrayref(true, %95, %107)::Int6 |     │     %174 = !%173::Any
│     %171 = Base.arrayref(true, %52, %170)::Floa |     └────        goto #71 if not %174
│     %172 = Core.tuple(%169, %171)::Tuple{Float6 |     70 ──        goto #72
│     %173 = $(Expr(:foreigncall, :(:jl_alloc_array_1 | 71 ── %177 = (DynamicPPL.inmissings)(%172, _model)
│            Base.arraysize(%173, 1)::Int64   | 72 ┄─ %178 = φ (#70 => %174, #71 => %177)::Any
└────        goto #75 if not true                     | └────        goto #74 if not %178
70 ┄─ %176 = φ (#69 => 1, #74 => %187)::Int64 | 73 ──        goto #75
│     %177 = φ (#69 => 1, #74 => %188)::Int64 | 74 ──        Base.arrayref(true, %99, %111)::Int6
│     %178 = φ (#69 => 1, #74 => %181)::Int64 | 75 ┄─ %182 = φ (#73 => true, #74 => false)::Bool
│     %179 = Base.getfield(%172, %176, true)::Flo |     └────        goto #77 if not %182
│            Base.arrayset(false, %173, %179, %178)     76 ── %184 = (DynamicPPL.tilde_assume)(_rng, _context
│     %181 = Base.add_int(%178, 1)::Int64     | │            Base.setindex!(%99, %184, %111)::Any
│     %182 = (%177 === 2)::Bool               | └────        goto #95
└────        goto #72 if not %182                     | 77 ── %187 = Base.arrayref(true, %99, %111)::Int6
71 ──        goto #73                                 | │     %188 = Base.getfield(_varinfo, :num_produce)
72 ── %185 = Base.add_int(%177, 1)::Int64     | │     %189 = Base.getfield(%188, :x)::Int64
└────        goto #73                                 | │     %190 = Base.add_int(%189, 1)::Int64
73 ┄─ %187 = φ (#72 => %185)::Int64           | │     %191 = Base.getfield(_varinfo, :num_produce)
│     %188 = φ (#72 => %185)::Int64           | │            Base.setfield!(%191, :x, %190)::Int6
│     %189 = φ (#71 => true, #72 => false)::Bool        │     %193 = Base.getfield(%152, :p)::Vector{Floa
│     %190 = Base.not_int(%189)::Bool         | │     %194 = Base.getfield(%152, :support)::Base.
└────        goto #75 if not %190                     | │     %195 = invoke Base.Sort.searchsorted(%194::Base
74 ──        goto #70                                 | │     %196 = Base.getfield(%195, :stop)::Int64
75 ┄─        goto #76                                 | │     %197 = Base.getfield(%195, :start)::Int64
76 ── %194 = Base.arraylen(%173)::Int64       | │     %198 = Base.Checked.checked_ssub_int(%196, %197
│     %195 = Base.arraylen(%173)::Int64       | │     %199 = Base.getfield(%198, 1)::Int64
│     %196 = (%194 === %195)::Bool            | │     %200 = Base.getfield(%198, 2)::Bool
└────        goto #78 if not %196                     | └────        goto #79 if not %200
77 ── %198 = %new(PDMats.ScalMat{Float64}, %194, 1.0, | 78 ──        invoke Base.Checked.throw_overflowerr_bi
│     %199 = %new(IsoNormal, %173, %198)::IsoNorm <
└────        goto #79                                 <
78 ── %201 = Distributions.DimensionMismatch("The dim <
│            Distributions.throw(%201)::Union{}
└────        unreachable                                └────        unreachable
79 ──        goto #80                                   79 ──        goto #80
80 ── %205 = invoke Core.TypeVar(Symbol("#s68")::Symb | 80 ── %205 = Base.Checked.checked_sadd_int(%199, 1)
│     %206 = Core.apply_type(Main.AbstractVector, %20 | │     %206 = Base.getfield(%205, 1)::Int64
│     %207 = Core.UnionAll(%205, %206)::Any   | │     %207 = Base.getfield(%205, 2)::Bool
│     %208 = Core.apply_type(Main.Union, Distribution | └────        goto #82 if not %207
│     %209 = (%199 isa %208)::Bool            | 81 ──        invoke Base.Checked.throw_overflowerr_bi
└────        goto #106 if not %209                    | └────        unreachable
81 ──        nothing::Nothing                 | 82 ──        goto #83
└────        goto #83 if not false                    | 83 ──        goto #84
82 ──        nothing::Nothing                 | 84 ── %213 = Base.slt_int(0, %206)::Bool
83 ┄─        goto #85 if not false                    | └────        goto #85
84 ──        nothing::Nothing                 | 85 ──        goto #87 if not %213
85 ┄─ %216 = Base.arraysize(x, 1)::Int64      | 86 ── %216 = Base.arrayref(true, %193, %187)::Flo
                                                              > └────        goto #88
                                                              > 87 ──        goto #88
                                                              > 88 ┄─ %219 = φ (#86 => %216, #87 => 0.0)::Float64
                                                                │     %220 = invoke Distributions.log(%219::Float64)
                                                                └────        goto #89
                                                              > 89 ──        goto #90
                                                              > 90 ──        goto #91
                                                              > 91 ──        goto #92
                                                              > 92 ──        goto #93
                                                              > 93 ── %226 = Base.getfield(_varinfo, :logp)::Base
                                                              > │     %227 = Base.getfield(%226, :x)::Float64
                                                              > │     %228 = Base.add_float(%227, %220)::Float64
                                                                │     %229 = Base.getfield(_varinfo, :logp)::Base
                                                              > │            Base.setfield!(%229, :x, %228)::Floa
                                                              > └────        goto #94
                                                              > 94 ──        nothing::Nothing
                                                              > 95 ┄─ %233 = Base.arrayref(true, %99, %111)::Int6
                                                              > │     %234 = Base.arrayref(true, %56, %233)::Floa
                                                              > │     %235 = Base.arrayref(true, %99, %111)::Int6
                                                              > │     %236 = Base.arrayref(true, %56, %235)::Floa
                                                              > │     %237 = Core.tuple(%234, %236)::Tuple{Float6
                                                              > │     %238 = $(Expr(:foreigncall, :(:jl_alloc_array_1
                                                              > │            Base.arraysize(%238, 1)::Int64
                                                              > └────        goto #101 if not true
                                                              > 96 ┄─ %241 = φ (#95 => 1, #100 => %252)::Int64
                                                                │     %242 = φ (#95 => 1, #100 => %253)::Int64
                                                                │     %243 = φ (#95 => 1, #100 => %246)::Int64
                                                                │     %244 = Base.getfield(%237, %241, true)::Flo
                                                              > │            Base.arrayset(false, %238, %244, %243)
                                                                │     %246 = Base.add_int(%243, 1)::Int64
                                                              > │     %247 = (%242 === 2)::Bool
                                                              > └────        goto #98 if not %247
                                                              > 97 ──        goto #99
                                                              > 98 ── %250 = Base.add_int(%242, 1)::Int64
                                                              > └────        goto #99
                                                              > 99 ┄─ %252 = φ (#98 => %250)::Int64
                                                              > │     %253 = φ (#98 => %250)::Int64
                                                              > │     %254 = φ (#97 => true, #98 => false)::Bool
                                                                │     %255 = Base.not_int(%254)::Bool
                                                              > └────        goto #101 if not %255
                                                              > 100 ─        goto #96
                                                              > 101 ┄        goto #102
                                                              > 102 ─ %259 = Base.arraylen(%238)::Int64
                                                              > │     %260 = Base.arraylen(%238)::Int64
                                                              > │     %261 = (%259 === %260)::Bool
                                                              > └────        goto #104 if not %261
                                                              > 103 ─ %263 = %new(PDMats.ScalMat{Float64}, %259, 1.0,
                                                              > │     %264 = %new(IsoNormal, %238, %263)::IsoNorm
                                                              > └────        goto #105
                                                              > 104 ─ %266 = Distributions.DimensionMismatch("The dim
                                                              > │            Distributions.throw(%266)::Union{}
                                                                └────        unreachable
                                                              > 105 ─        goto #106
                                                              > 106 ─ %270 = invoke Core.TypeVar(Symbol("#s68")::Symb
                                                              > │     %271 = Core.apply_type(Main.AbstractVector, %27
                                                              > │     %272 = Core.UnionAll(%270, %271)::Any
                                                              > │     %273 = Core.apply_type(Main.Union, Distribution
                                                              > │     %274 = (%264 isa %273)::Bool
                                                              > └────        goto #132 if not %274
                                                              > 107 ─        nothing::Nothing
                                                              > └────        goto #109 if not false
                                                              > 108 ─        nothing::Nothing
                                                              > 109 ┄        goto #111 if not false
                                                              > 110 ─        nothing::Nothing
                                                              > 111 ┄ %281 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64        │            Base.arraysize(x, 2)::Int64
│     %218 = Base.slt_int(%216, 0)::Bool      | │     %283 = Base.slt_int(%281, 0)::Bool
│     %219 = Base.ifelse(%218, 0, %216)::Int64  │     %284 = Base.ifelse(%283, 0, %281)::Int64
│     %220 = %new(Base.OneTo{Int64}, %219)::Base. |     │     %285 = %new(Base.OneTo{Int64}, %284)::Base.
│     %221 = %new(Base.Slice{Base.OneTo{Int64}}, %220 | │     %286 = %new(Base.Slice{Base.OneTo{Int64}}, %285
└────        goto #90 if not true                     | └────        goto #116 if not true
86 ── %223 = Core.tuple(%221, %107)::Tuple{Base.S |     112 ─ %288 = Core.tuple(%286, %111)::Tuple{Base.S
│            Base.arraysize(x, 1)::Int64        │            Base.arraysize(x, 1)::Int64
│     %225 = Base.arraysize(x, 2)::Int64      | │     %290 = Base.arraysize(x, 2)::Int64
│     %226 = Base.slt_int(%225, 0)::Bool      | │     %291 = Base.slt_int(%290, 0)::Bool
│     %227 = Base.ifelse(%226, 0, %225)::Int64  │     %292 = Base.ifelse(%291, 0, %290)::Int64
│     %228 = Base.sle_int(1, %107)::Bool      | │     %293 = Base.sle_int(1, %111)::Bool
│     %229 = Base.sle_int(%107, %227)::Bool   | │     %294 = Base.sle_int(%111, %292)::Bool
│     %230 = Base.and_int(%228, %229)::Bool   | │     %295 = Base.and_int(%293, %294)::Bool
│     %231 = Base.and_int(%230, true)::Bool   | │     %296 = Base.and_int(%295, true)::Bool
│     %232 = Base.and_int(true, %231)::Bool   | │     %297 = Base.and_int(true, %296)::Bool
└────        goto #88 if not %232                     | └────        goto #114 if not %297
87 ──        goto #89                                 | 113 ─        goto #115
88 ──        invoke Base.throw_boundserror(_7::Matrix | 114 ─        invoke Base.throw_boundserror(_7::Matrix
└────        unreachable                              | └────        unreachable
89 ──        nothing::Nothing                 | 115 ─        nothing::Nothing
90 ┄─        invoke Base._unsafe_getindex($(QuoteNode | 116 ┄        invoke Base._unsafe_getindex($(QuoteNode
└────        goto #91                                 | └────        goto #117
91 ──        goto #92                                 | 117 ─        goto #118
92 ──        goto #94 if not false                    | 118 ─        goto #120 if not false
93 ──        nothing::Nothing                 | 119 ─        nothing::Nothing
94 ┄─ %243 = Base.arraysize(x, 1)::Int64      | 120 ┄ %308 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64        │            Base.arraysize(x, 2)::Int64
│     %245 = Base.slt_int(%243, 0)::Bool      | │     %310 = Base.slt_int(%308, 0)::Bool
│     %246 = Base.ifelse(%245, 0, %243)::Int64  │     %311 = Base.ifelse(%310, 0, %308)::Int64
│     %247 = %new(Base.OneTo{Int64}, %246)::Base. |     │     %312 = %new(Base.OneTo{Int64}, %311)::Base.
│     %248 = %new(Base.Slice{Base.OneTo{Int64}}, %247 | │     %313 = %new(Base.Slice{Base.OneTo{Int64}}, %312
└────        goto #99 if not true                     | └────        goto #125 if not true
95 ── %250 = Core.tuple(%248, %107)::Tuple{Base.S |     121 ─ %315 = Core.tuple(%313, %111)::Tuple{Base.S
│            Base.arraysize(x, 1)::Int64        │            Base.arraysize(x, 1)::Int64
│     %252 = Base.arraysize(x, 2)::Int64      | │     %317 = Base.arraysize(x, 2)::Int64
│     %253 = Base.slt_int(%252, 0)::Bool      | │     %318 = Base.slt_int(%317, 0)::Bool
│     %254 = Base.ifelse(%253, 0, %252)::Int64  │     %319 = Base.ifelse(%318, 0, %317)::Int64
│     %255 = Base.sle_int(1, %107)::Bool      | │     %320 = Base.sle_int(1, %111)::Bool
│     %256 = Base.sle_int(%107, %254)::Bool   | │     %321 = Base.sle_int(%111, %319)::Bool
│     %257 = Base.and_int(%255, %256)::Bool   | │     %322 = Base.and_int(%320, %321)::Bool
│     %258 = Base.and_int(%257, true)::Bool   | │     %323 = Base.and_int(%322, true)::Bool
│     %259 = Base.and_int(true, %258)::Bool   | │     %324 = Base.and_int(true, %323)::Bool
└────        goto #97 if not %259                     | └────        goto #123 if not %324
96 ──        goto #98                                 | 122 ─        goto #124
97 ──        invoke Base.throw_boundserror(_7::Matrix | 123 ─        invoke Base.throw_boundserror(_7::Matrix
└────        unreachable                              | └────        unreachable
98 ──        nothing::Nothing                 | 124 ─        nothing::Nothing
99 ┄─ %265 = invoke Base._unsafe_getindex($(QuoteNode | 125 ┄ %330 = invoke Base._unsafe_getindex($(QuoteNode
└────        goto #100                                | └────        goto #126
100 ─        goto #101                                | 126 ─        goto #127
101 ─ %268 = Base.getfield(_varinfo, :num_produce)      127 ─ %333 = Base.getfield(_varinfo, :num_produce)
│     %269 = Base.getfield(%268, :x)::Int64   | │     %334 = Base.getfield(%333, :x)::Int64
│     %270 = Base.add_int(%269, 1)::Int64     | │     %335 = Base.add_int(%334, 1)::Int64
│     %271 = Base.getfield(_varinfo, :num_produce)      │     %336 = Base.getfield(_varinfo, :num_produce)
│            Base.setfield!(%271, :x, %270)::Int6 |     │            Base.setfield!(%336, :x, %335)::Int6
│     %273 = invoke Distributions.logpdf(%199::IsoNor | │     %338 = invoke Distributions.logpdf(%264::IsoNor
│     %274 = Base.getfield(_varinfo, :logp)::Base |     │     %339 = Base.getfield(_varinfo, :logp)::Base
│     %275 = Base.getfield(%274, :x)::Float64 | │     %340 = Base.getfield(%339, :x)::Float64
│     %276 = Base.add_float(%275, %273)::Float64        │     %341 = Base.add_float(%340, %338)::Float64
│     %277 = Base.getfield(_varinfo, :logp)::Base |     │     %342 = Base.getfield(_varinfo, :logp)::Base
│            Base.setfield!(%277, :x, %276)::Floa |     │            Base.setfield!(%342, :x, %341)::Floa
│     %279 = (%108 === %97)::Bool             | │     %344 = (%112 === %101)::Bool
└────        goto #103 if not %279                    | └────        goto #129 if not %344
102 ─        goto #104                                | 128 ─        goto #130
103 ─ %282 = Base.add_int(%108, 1)::Int64     | 129 ─ %347 = Base.add_int(%112, 1)::Int64
└────        goto #104                                | └────        goto #130
104 ┄ %284 = φ (#103 => %282)::Int64          | 130 ┄ %349 = φ (#129 => %347)::Int64
│     %285 = φ (#103 => %282)::Int64          | │     %350 = φ (#129 => %347)::Int64
│     %286 = φ (#102 => true, #103 => false)::Boo |     │     %351 = φ (#128 => true, #129 => false)::Boo
│     %287 = Base.not_int(%286)::Bool         | │     %352 = Base.not_int(%351)::Bool
└────        goto #108 if not %287                    | └────        goto #134 if not %352
105 ─        goto #41                                 | 131 ─        goto #41
106 ─ %290 = Main.ArgumentError("Right-hand side of a | 132 ─ %355 = Main.ArgumentError("Right-hand side of a
│            Main.throw(%290)::Union{}        | │            Main.throw(%355)::Union{}
└────        unreachable                                └────        unreachable
107 ─ %293 = Main.ArgumentError("Right-hand side of a | 133 ─ %358 = Main.ArgumentError("Right-hand side of a
│            Main.throw(%293)::Union{}        | │            Main.throw(%358)::Union{}
└────        unreachable                                └────        unreachable
108 ┄        return %95                               | 134 ┄        return %99
) => Vector{Int64}                                              ) => Vector{Int64}

@devmotion
Copy link
Member

It seems the new implementation leads to more allocations, and so possible also worse performance, when compiling the model. Also running model 3 is significantly slower and leads to more allocations, both with typed and "untyped" VarInfo. I guess both results are not completely surprising - we introduce more branches which puts more pressure on the compiler. The diffs are difficult to read, is there any significant difference, in particular for the third model?

@torfjelde
Copy link
Member Author

The diffs are difficult to read, is there any significant difference, in particular for the third model?

Yeaaah it seems like it fails to infer what's going to happen with DynamicPPL.inargnames in statement 69. You can see this in the output above for model 3, but here:

image

So VarName isn't inferred properly 😕

@torfjelde
Copy link
Member Author

torfjelde commented Apr 2, 2021

Ah, but this makes sense since this is within the for-loop (btw, I realized I included the @macroexpand for only one model in the above; fixed now!):

var"##vn#432" = (VarName)(:k, ((i,),))

Hmm, so I get why the optimization was introduced in the first place then. It avoids unnecessarily leaving the option of something being observed if not present in args of the model, which can cause inefficiencies when VarName is not static.

@torfjelde
Copy link
Member Author

I guess what we could do (as I believe I mentioned at some point before) is to include both implementations, e.g. make available a generate_mainbody which doesn't take args. This can be easily used by callers "external" to DynamicPPL.

It does "unnecessarily" make the code more cluttered though... Not sure.

@devmotion
Copy link
Member

I wonder if it would help the compiler if isassumption would be a proper function that avoids the chain of boolean checks and exploits multiple dispatch (with e.g. a trait) to evaluate to true if a variable is an argument to the model, if possible.

@torfjelde
Copy link
Member Author

torfjelde commented Apr 2, 2021

You mean, e.g.

function isassumption(vn::VarName, model::Model, left)
    !inargnames(vn, model) || inmissings(vn, model) || false
end

function isassumption(vn::VarName, model::Model, left::Missing)
    !inargnames(vn, model) || inmissings(vn, model) || true
end

EDIT: I guess the issue is that we're not going to be able to evaluate if something is missing using this approach, since left won't be defined for most variables which are not in args.

EDIT 2: Though we can "fix" this by doing:

$isassumption = $(DynamicPPL.isassumption)($vn, _model) || $left === missing

instead, and dropping the left argument from my above impl. Trying this out now.

EDIT 3: Doesn't seem to help. Also, thinking about it I don't expect it to? I guess the issue lies in the fact that inargnames and inmissings will always fail to be inferred when the symbol passed to VarName isn't static.

@devmotion
Copy link
Member

I thought maybe something like:

isassumption(vn, model) = !inargnames(vn, model) || inmissings(vn, model)

isassumption(vn, model, left) = _isassumption(Val{isassumption(vn, model)}(), left)

_isassumption(::Val{true}, left) = true
_isassumption(::Val{false}, ::Missing) = true
_isassumption(::Val{false}, left) = false

The main intention would be to enforce that the static checks are prioritized and that the runtime checks for missing are only needed if it seems to be an observation according to the static checks. I am not sure if it makes a difference but my hope would be that the dispatches are easier to handle for the compiler than the if-else statements.

@devmotion
Copy link
Member

Good point regarding left, so yeah one would have to keep the static checks more separate.

@torfjelde
Copy link
Member Author

torfjelde commented Apr 2, 2021

Re-stating another edit from above: I guess the issue lies in the fact that inargnames and inmissings will always fail to be inferred when the symbol passed to VarName isn't static.

I.e. not sure we can actually circumvent this for non-static VarName 😕

@devmotion
Copy link
Member

You mean the indices, e.g., the i in the for-loop in the third model? I thought the static checks only use the symbol, ie k and x?

@devmotion
Copy link
Member

And it seems it should be possible to support dynamic analysis for indices as well but again prioritize static checks?

@torfjelde
Copy link
Member Author

You mean the indices, e.g., the i in the for-loop in the third model? I thought the static checks only use the symbol, ie k and x?

Truuue! But for some reason it seems like the diversion through VarName messes things up. If I instead interpolate Val($(QuoteNode(vsym(left)))) and implement isassumption and others for Val{sym}, it I get the same allocations as the current approach.

Is it because we effectively do VarName{$(vsym(left))}(inds), and the compiler fails to do the constant-propagation properly? So it becomes VarName{:k}(inds) and because it doesn't know inds it just gives up?

@torfjelde
Copy link
Member Author

torfjelde commented Apr 2, 2021

EDIT: It actually has fewer allocations for Model 2 than the #master!

EDIT 2: Slightly higher compilation-time it seems though.

EDIT 3: Eh, compilation time is a bit incoclosive. Model 1 is slower, Model 2 is faster, and Model 3 is about the same.

EDIT 4: It's consistently faster than old implementation though! E.g. check out the numbers for Model 2 in particular.

When interpolating vsym and using Val I get this.

using Pkg
Pkg.status()
   Project DynamicPPL v0.10.8
    Status `~/Projects/public/DynamicPPL.jl/Project.toml`
[80f14c24] AbstractMCMC v2.5.0
[7a57a42e] AbstractPPL v0.1.2
[76274a88] Bijectors v0.8.16
[d360d2e6] ChainRulesCore v0.9.35
[31c24e10] Distributions v0.24.15
[1914dd2f] MacroTools v0.5.6
[c020b1a1] NaturalSort v1.0.0
[9a3f8284] Random
using DynamicPPL, Distributions, BenchmarkTools, MacroTools
┌ Info: Precompiling DynamicPPL [366bfd00-2699-11ea-058f-f148b4cae6d8]
└ @ Base loading.jl:1317

Model 1

@time begin
    @model function demo1(x)
        m ~ Normal()
        x ~ Normal(m, 1)

        return (m = m, x = x)
    end
end;
0.000064 seconds (1.05 k allocations: 78.643 KiB, 2625.44% compilation time)
model_def = demo1
data = 1.0
@time model_def(data)();
0.972937 seconds (2.48 M allocations: 157.059 MiB, 5.01% gc time, 99.90% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
1.350 μs (51 allocations: 3.78 KiB)
348.690 ns (2 allocations: 64 bytes)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
586.150 ns (15 allocations: 544 bytes)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#3#4", (:x,), (), (), Tuple{Float64}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:m,), Tuple{DynamicPPL.Metadata{Dict{VarName{:m, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:m, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ── %1  = Base.sle_int(1, 1)::Bool
└───       goto #3 if not %1
2 ── %3  = Base.sle_int(1, 0)::Bool
└───       goto #4
3 ──       nothing::Nothing
4 ┄─ %6  = φ (#2 => %3, #3 => false)::Bool
└───       goto #6 if not %6
5 ──       invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└───       unreachable
6 ──       goto #7
7 ──       goto #8
8 ──       goto #9
9 ──       goto #10
10 ─       goto #11
11 ─       goto #12
12 ─       invoke Core.TypeVar(Symbol("#s73")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s73"<:Distribution, true, true)
│          nothing::Nothing
│          nothing::Nothing
│    %19 = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, m::VarName{:m, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:m,), Tuple{DynamicPPL.Metadata{Dict{VarName{:m, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:m, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│    %20 = Base.getfield(%19, 1)::Float64
│    %21 = Base.getfield(%19, 2)::Float64
│    %22 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %23 = Base.getfield(%22, :x)::Float64
│    %24 = Base.add_float(%23, %21)::Float64
│    %25 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%25, :x, %24)::Float64
└───       goto #14 if not true
13 ─       nothing::Nothing
14 ┄ %29 = %new(Normal{Float64}, %20, 1.0)::Normal{Float64}
└───       goto #15
15 ─       goto #16
16 ─       goto #17
17 ─       invoke Core.TypeVar(Symbol("#s72")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s72"<:Distribution, true, true)
└───       goto #19 if not false
18 ─       nothing::Nothing
19 ┄       goto #21 if not false
20 ─       nothing::Nothing
21 ┄       goto #23 if not false
22 ─       nothing::Nothing
23 ┄ %40 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│    %41 = Base.getfield(%40, :x)::Int64
│    %42 = Base.add_int(%41, 1)::Int64
│    %43 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│          Base.setfield!(%43, :x, %42)::Int64
│    %45 = invoke Distributions.logpdf(%29::Normal{Float64}, _7::Float64)::Float64
│    %46 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %47 = Base.getfield(%46, :x)::Float64
│    %48 = Base.add_float(%47, %45)::Float64
│    %49 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%49, :x, %48)::Float64
│    %51 = %new(NamedTuple{(:m, :x), Tuple{Float64, Float64}}, %20, x@_7)::NamedTuple{(:m, :x), Tuple{Float64, Float64}}
└───       return %51
) => NamedTuple{(:m, :x), Tuple{Float64, Float64}}
expr = @macroexpand begin
    @model function demo1(x)
        m ~ Normal()
        x ~ Normal(m, 1)

        return (m = m, x = x)
    end
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo1(x; )
            var"##evaluator#316" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x)->begin
                        begin
                            begin
                                var"##tmpright#304" = Normal()
                                var"##tmpright#304" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#306" = m
                                var"##inds#307" = ()
                                var"##isassumption#308" = begin
                                        let var"##sym#309" = :m
                                            if !((DynamicPPL.inargnames)(Val(var"##sym#309"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#309"), _model)
                                                true
                                            else
                                                m === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#308"
                                    m = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#304", var"##vn#306", var"##inds#307", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#304", m, var"##vn#306", var"##inds#307", _varinfo)
                                end
                            end
                            begin
                                var"##tmpright#310" = Normal(m, 1)
                                var"##tmpright#310" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#312" = x
                                var"##inds#313" = ()
                                var"##isassumption#314" = begin
                                        let var"##sym#315" = :x
                                            if !((DynamicPPL.inargnames)(Val(var"##sym#315"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#315"), _model)
                                                true
                                            else
                                                x === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#314"
                                    x = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#310", var"##vn#312", var"##inds#313", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#310", x, var"##vn#312", var"##inds#313", _varinfo)
                                end
                            end
                            return (m = m, x = x)
                        end
                    end)
            return (Model)(:demo1, var"##evaluator#316", (DynamicPPL.namedtuple)(NamedTuple{(:x,), Tuple{Core.Typeof(x)}}, (x,)), NamedTuple())
        end
    end
end

Model 2

@time begin
    @model function demo2(y) 
        # Our prior belief about the probability of heads in a coin.
        p ~ Beta(1, 1)

        # The number of observations.
        N = length(y)
        for n in 1:N
            # Heads or tails of a coin are drawn from a Bernoulli distribution.
            y[n] ~ Bernoulli(p)
        end
    end;
end;
0.000071 seconds (67 allocations: 6.221 KiB)
model_def = demo2
data = rand(0:1, 10)
@time model_def(data)();
0.406918 seconds (885.57 k allocations: 52.709 MiB, 2.80% gc time, 99.88% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
10.022 μs (144 allocations: 7.28 KiB)
8.049 μs (81 allocations: 2.69 KiB)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
9.077 μs (108 allocations: 4.03 KiB)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#8#9", (:y,), (), (), Tuple{Vector{Int64}}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:p,), Tuple{DynamicPPL.Metadata{Dict{VarName{:p, Tuple{}}, Int64}, Vector{Beta{Float64}}, Vector{VarName{:p, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::Nothing
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::Core.Const(nothing)
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ──       invoke Core.TypeVar(Symbol("#s73")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s73"<:Distribution, true, true)
│          nothing::Nothing
│          nothing::Nothing
│    %4  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Beta{Float64}(α=1.0, β=1.0)))::Beta{Float64}, p::VarName{:p, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:p,), Tuple{DynamicPPL.Metadata{Dict{VarName{:p, Tuple{}}, Int64}, Vector{Beta{Float64}}, Vector{VarName{:p, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│    %5  = Base.getfield(%4, 1)::Float64
│    %6  = Base.getfield(%4, 2)::Float64
│    %7  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %8  = Base.getfield(%7, :x)::Float64
│    %9  = Base.add_float(%8, %6)::Float64
│    %10 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%10, :x, %9)::Float64
│    %12 = Base.arraylen(y)::Int64
│    %13 = Base.sle_int(1, %12)::Bool
│    %14 = Base.ifelse(%13, %12, 0)::Int64
│    %15 = Base.slt_int(%14, 1)::Bool
└───       goto #3 if not %15
2 ──       goto #4
3 ──       goto #4
4 ┄─ %19 = φ (#2 => true, #3 => false)::Bool
│    %20 = φ (#3 => 1)::Int64
│    %21 = φ (#3 => 1)::Int64
│    %22 = Base.not_int(%19)::Bool
└───       goto #38 if not %22
5 ┄─ %24 = φ (#4 => %20, #36 => %93)::Int64
│    %25 = φ (#4 => %21, #36 => %94)::Int64
└───       goto #12 if not true
6 ── %27 = Base.le_float(0.0, %5)::Bool
└───       goto #8 if not %27
7 ── %29 = Base.le_float(%5, 1.0)::Bool
└───       goto #9
8 ──       nothing::Nothing
9 ┄─ %32 = φ (#7 => %29, #8 => false)::Bool
│    %33 = Base.not_int(%32)::Bool
└───       goto #11 if not %33
10 ─ %35 = Distributions.string("Bernoulli", ": the condition ", "zero(p) <= p <= one(p)", " is not satisfied.")::Any
│    %36 = Distributions.ArgumentError(%35)::Any
│          Distributions.throw(%36)::Union{}
└───       unreachable
11 ─       nothing::Nothing
12 ┄ %40 = %new(Bernoulli{Float64}, %5)::Bernoulli{Float64}
└───       goto #13
13 ─       goto #14
14 ─ %43 = invoke Core.TypeVar(Symbol("#s71")::Symbol, Distribution::Any)::TypeVar
│    %44 = Core.apply_type(Main.AbstractVector, %43)::Type{AbstractVector{_A}} where _A
│    %45 = Core.UnionAll(%43, %44)::Any
│    %46 = Core.apply_type(Main.Union, Distribution, %45)::Type
│    %47 = (%40 isa %46)::Bool
└───       goto #37 if not %47
15 ─       nothing::Nothing
│    %50 = Core.tuple(%24)::Tuple{Int64}
│    %51 = Core.tuple(%50)::Tuple{Tuple{Int64}}
│          invoke VarName(:y::Symbol, %51::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
└───       goto #17 if not false
16 ─       nothing::Nothing
17 ┄       goto #19 if not false
18 ─       nothing::Nothing
19 ┄       Base.arrayref(true, y, %24)::Int64
└───       goto #21 if not false
20 ─       nothing::Nothing
21 ┄ %60 = Base.arrayref(true, y, %24)::Int64
│    %61 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│    %62 = Base.getfield(%61, :x)::Int64
│    %63 = Base.add_int(%62, 1)::Int64
│    %64 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│          Base.setfield!(%64, :x, %63)::Int64
│    %66 = (%60 === 0)::Bool
└───       goto #23 if not %66
22 ─ %68 = Base.sitofp(Float64, 1)::Float64
│    %69 = Base.sub_float(%68, %5)::Float64
└───       goto #26
23 ─ %71 = (%60 === 1)::Bool
└───       goto #25 if not %71
24 ─       goto #26
25 ─       goto #26
26 ┄ %75 = φ (#22 => %69, #24 => %5, #25 => 0.0)::Float64
│    %76 = invoke Distributions.log(%75::Float64)::Float64
└───       goto #27
27 ─       goto #28
28 ─       goto #29
29 ─       goto #30
30 ─       goto #31
31 ─ %82 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│    %83 = Base.getfield(%82, :x)::Float64
│    %84 = Base.add_float(%83, %76)::Float64
│    %85 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│          Base.setfield!(%85, :x, %84)::Float64
└───       goto #32
32 ─ %88 = (%25 === %14)::Bool
└───       goto #34 if not %88
33 ─       goto #35
34 ─ %91 = Base.add_int(%25, 1)::Int64
└───       goto #35
35 ┄ %93 = φ (#34 => %91)::Int64
│    %94 = φ (#34 => %91)::Int64
│    %95 = φ (#33 => true, #34 => false)::Bool
│    %96 = Base.not_int(%95)::Bool
└───       goto #38 if not %96
36 ─       goto #5
37 ─ %99 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│          Main.throw(%99)::Union{}
└───       unreachable
38 ┄       return nothing
) => Nothing
expr = @macroexpand begin
    @model function demo2(y) 
        # Our prior belief about the probability of heads in a coin.
        p ~ Beta(1, 1)

        # The number of observations.
        N = length(y)
        for n in 1:N
            # Heads or tails of a coin are drawn from a Bernoulli distribution.
            y[n] ~ Bernoulli(p)
        end
    end;
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo2(y; )
            var"##evaluator#376" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, y)->begin
                        begin
                            begin
                                var"##tmpright#364" = Beta(1, 1)
                                var"##tmpright#364" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#366" = p
                                var"##inds#367" = ()
                                var"##isassumption#368" = begin
                                        let var"##sym#369" = :p
                                            if !((DynamicPPL.inargnames)(Val(var"##sym#369"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#369"), _model)
                                                true
                                            else
                                                p === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#368"
                                    p = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#364", var"##vn#366", var"##inds#367", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#364", p, var"##vn#366", var"##inds#367", _varinfo)
                                end
                            end
                            N = length(y)
                            for n = 1:N
                                var"##tmpright#370" = Bernoulli(p)
                                var"##tmpright#370" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#372" = (VarName)(:y, ((n,),))
                                var"##inds#373" = ((n,),)
                                var"##isassumption#374" = begin
                                        let var"##sym#375" = :y
                                            if !((DynamicPPL.inargnames)(Val(var"##sym#375"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#375"), _model)
                                                true
                                            else
                                                y[n] === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#374"
                                    y[n] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#370", var"##vn#372", var"##inds#373", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#370", y[n], var"##vn#372", var"##inds#373", _varinfo)
                                end
                            end
                        end
                    end)
            return (Model)(:demo2, var"##evaluator#376", (DynamicPPL.namedtuple)(NamedTuple{(:y,), Tuple{Core.Typeof(y)}}, (y,)), NamedTuple())
        end
    end
end

Model 3

@time begin
    @model function demo3(x)
        D, N = size(x)

        # Draw the parameters for cluster 1.
        μ1 ~ Normal()

        # Draw the parameters for cluster 2.
        μ2 ~ Normal()

        μ = [μ1, μ2]

        # Comment out this line if you instead want to draw the weights.
        w = [0.5, 0.5]

        # Draw assignments for each datum and generate it from a multivariate normal.
        k = Vector{Int}(undef, N)
        for i in 1:N
            k[i] ~ Categorical(w)
            x[:,i] ~ MvNormal([μ[k[i]], μ[k[i]]], 1.)
        end
        return k
    end
end;
0.000087 seconds (82 allocations: 8.938 KiB)
model_def = demo3

# Construct 30 data points for each cluster.
N = 30

# Parameters for each cluster, we assume that each cluster is Gaussian distributed in the example.
μs = [-3.5, 0.0]

# Construct the data points.
data = mapreduce(c -> rand(MvNormal([μs[c], μs[c]], 1.), N), hcat, 1:2)
@time model_def(data)();
0.831684 seconds (1.72 M allocations: 96.554 MiB, 2.65% gc time, 99.93% compilation time)
@btime $(model_def(data))();

m = model_def(data);
var_info = VarInfo(m);

@btime $m($var_info);
173.637 μs (3032 allocations: 175.11 KiB)
120.504 μs (1325 allocations: 77.69 KiB)
untyped_var_info = VarInfo();
m(untyped_var_info)

@btime $m($untyped_var_info);
136.038 μs (2090 allocations: 95.55 KiB)
m = model_def(data);
var_info = VarInfo(m);
@code_warntype m(var_info)
Variables
  model::Model{var"#13#14", (:x,), (), (), Tuple{Matrix{Float64}}, Tuple{}}
  args::Tuple{TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64}}

Body::Vector{Int64}
1 ─ %1 = Random.GLOBAL_RNG::Core.Const(Random._GLOBAL_RNG())
│   %2 = Core.tuple(%1)::Core.Const((Random._GLOBAL_RNG(),))
│   %3 = Core._apply_iterate(Base.iterate, model, %2, args)::Vector{Int64}
└──      return %3
rng = DynamicPPL.Random.MersenneTwister(42);
spl = DynamicPPL.SampleFromPrior()
ctx = DynamicPPL.DefaultContext()
@code_typed m.f(rng, m, var_info, spl, ctx, m.args...)
CodeInfo(
1 ───        Base.arraysize(x, 1)::Int64
│     %2   = Base.arraysize(x, 2)::Int64
│     %3   = Base.sle_int(1, 1)::Bool
└────        goto #3 if not %3
2 ─── %5   = Base.sle_int(1, 0)::Bool
└────        goto #4
3 ───        nothing::Nothing
4 ┄── %8   = φ (#2 => %5, #3 => false)::Bool
└────        goto #6 if not %8
5 ───        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
6 ───        goto #7
7 ───        goto #8
8 ───        goto #9
9 ───        goto #10
10 ──        goto #11
11 ──        goto #12
12 ──        invoke Core.TypeVar(Symbol("#s72")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s72"<:Distribution, true, true)
│            nothing::Nothing
│            nothing::Nothing
│     %21  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, μ1::VarName{:μ1, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│     %22  = Base.getfield(%21, 1)::Float64
│     %23  = Base.getfield(%21, 2)::Float64
│     %24  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %25  = Base.getfield(%24, :x)::Float64
│     %26  = Base.add_float(%25, %23)::Float64
│     %27  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%27, :x, %26)::Float64
│     %29  = Base.sle_int(1, 1)::Bool
└────        goto #14 if not %29
13 ── %31  = Base.sle_int(1, 0)::Bool
└────        goto #15
14 ──        nothing::Nothing
15 ┄─ %34  = φ (#13 => %31, #14 => false)::Bool
└────        goto #17 if not %34
16 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
17 ──        goto #18
18 ──        goto #19
19 ──        goto #20
20 ──        goto #21
21 ──        goto #22
22 ──        goto #23
23 ──        invoke Core.TypeVar(Symbol("#s71")::Symbol, Distribution::Any)::Core.Compiler.PartialTypeVar(var"#s71"<:Distribution, true, true)
│            nothing::Nothing
│            nothing::Nothing
│     %47  = invoke DynamicPPL.assume(_2::Random.MersenneTwister, _5::SampleFromPrior, $(QuoteNode(Normal{Float64}(μ=0.0, σ=1.0)))::Normal{Float64}, μ2::VarName{:μ2, Tuple{}}, _4::TypedVarInfo{NamedTuple{(:μ1, :μ2, :k), Tuple{DynamicPPL.Metadata{Dict{VarName{:μ1, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ1, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:μ2, Tuple{}}, Int64}, Vector{Normal{Float64}}, Vector{VarName{:μ2, Tuple{}}}, Vector{Float64}, Vector{Set{DynamicPPL.Selector}}}, DynamicPPL.Metadata{Dict{VarName{:k, Tuple{Tuple{Int64}}}, Int64}, Vector{Categorical{Float64, Vector{Float64}}}, Vector{VarName{:k, Tuple{Tuple{Int64}}}}, Vector{Int64}, Vector{Set{DynamicPPL.Selector}}}}}, Float64})::Tuple{Float64, Float64}
│     %48  = Base.getfield(%47, 1)::Float64
│     %49  = Base.getfield(%47, 2)::Float64
│     %50  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %51  = Base.getfield(%50, :x)::Float64
│     %52  = Base.add_float(%51, %49)::Float64
│     %53  = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%53, :x, %52)::Float64
│     %55  = Core.tuple(%22, %48)::Tuple{Float64, Float64}
│     %56  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%56, 1)::Int64
└────        goto #29 if not true
24 ┄─ %59  = φ (#23 => 1, #28 => %70)::Int64
│     %60  = φ (#23 => 1, #28 => %71)::Int64
│     %61  = φ (#23 => 1, #28 => %64)::Int64
│     %62  = Base.getfield(%55, %59, true)::Float64
│            Base.arrayset(false, %56, %62, %61)::Vector{Float64}
│     %64  = Base.add_int(%61, 1)::Int64
│     %65  = (%60 === 2)::Bool
└────        goto #26 if not %65
25 ──        goto #27
26 ── %68  = Base.add_int(%60, 1)::Int64
└────        goto #27
27 ┄─ %70  = φ (#26 => %68)::Int64
│     %71  = φ (#26 => %68)::Int64
│     %72  = φ (#25 => true, #26 => false)::Bool
│     %73  = Base.not_int(%72)::Bool
└────        goto #29 if not %73
28 ──        goto #24
29 ┄─        goto #30
30 ── %77  = Core.tuple(0.5, 0.5)::Core.Const((0.5, 0.5))
│     %78  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%78, 1)::Int64
└────        goto #36 if not true
31 ┄─ %81  = φ (#30 => 1, #35 => %92)::Int64
│     %82  = φ (#30 => 1, #35 => %93)::Int64
│     %83  = φ (#30 => 1, #35 => %86)::Int64
│     %84  = Base.getfield(%77, %81, true)::Float64
│            Base.arrayset(false, %78, %84, %83)::Vector{Float64}
│     %86  = Base.add_int(%83, 1)::Int64
│     %87  = (%82 === 2)::Bool
└────        goto #33 if not %87
32 ──        goto #34
33 ── %90  = Base.add_int(%82, 1)::Int64
└────        goto #34
34 ┄─ %92  = φ (#33 => %90)::Int64
│     %93  = φ (#33 => %90)::Int64
│     %94  = φ (#32 => true, #33 => false)::Bool
│     %95  = Base.not_int(%94)::Bool
└────        goto #36 if not %95
35 ──        goto #31
36 ┄─        goto #37
37 ── %99  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Int64}, svec(Any, Int64), 0, :(:ccall), Vector{Int64}, :(%2), :(%2)))::Vector{Int64}
│     %100 = Base.sle_int(1, %2)::Bool
│     %101 = Base.ifelse(%100, %2, 0)::Int64
│     %102 = Base.slt_int(%101, 1)::Bool
└────        goto #39 if not %102
38 ──        goto #40
39 ──        goto #40
40 ┄─ %106 = φ (#38 => true, #39 => false)::Bool
│     %107 = φ (#39 => 1)::Int64
│     %108 = φ (#39 => 1)::Int64
│     %109 = Base.not_int(%106)::Bool
└────        goto #108 if not %109
41 ┄─ %111 = φ (#40 => %107, #105 => %289)::Int64
│     %112 = φ (#40 => %108, #105 => %290)::Int64
│     %113 = Base.sle_int(1, 1)::Bool
└────        goto #43 if not %113
42 ── %115 = Base.sle_int(1, 0)::Bool
└────        goto #44
43 ──        nothing::Nothing
44 ┄─ %118 = φ (#42 => %115, #43 => false)::Bool
└────        goto #46 if not %118
45 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
46 ──        goto #47
47 ──        goto #48
48 ──        goto #49
49 ──        goto #50
50 ──        goto #54 if not true
51 ── %127 = invoke Distributions.isprobvec(%78::Vector{Float64})::Bool
│     %128 = Base.not_int(%127)::Bool
└────        goto #53 if not %128
52 ── %130 = Distributions.string("Categorical", ": the condition ", "isprobvec(p)", " is not satisfied.")::Any
│     %131 = Distributions.ArgumentError(%130)::Any
│            Distributions.throw(%131)::Union{}
└────        unreachable
53 ──        nothing::Nothing
54 ┄─ %135 = Base.arraylen(%78)::Int64
│     %136 = Base.slt_int(%135, 0)::Bool
│     %137 = Base.ifelse(%136, 0, %135)::Int64
│     %138 = %new(Base.OneTo{Int64}, %137)::Base.OneTo{Int64}
│     %139 = Base.sle_int(1, 1)::Bool
└────        goto #56 if not %139
55 ── %141 = Base.sle_int(1, 0)::Bool
└────        goto #57
56 ──        nothing::Nothing
57 ┄─ %144 = φ (#55 => %141, #56 => false)::Bool
└────        goto #59 if not %144
58 ──        invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└────        unreachable
59 ──        goto #60
60 ──        goto #61
61 ──        goto #62
62 ──        goto #63
63 ── %152 = invoke $(QuoteNode(Distributions.var"#_#34#35"()))(true::Bool, Categorical{Float64, Vector{Float64}}::Type{Categorical{Float64, Vector{Float64}}}, %138::Base.OneTo{Int64}, %78::Vector{Float64})::Categorical{Float64, Vector{Float64}}
└────        goto #64
64 ──        goto #65
65 ──        goto #66
66 ──        goto #67
67 ──        goto #68
68 ── %158 = invoke Core.TypeVar(Symbol("#s69")::Symbol, Distribution::Any)::TypeVar
│     %159 = Core.apply_type(Main.AbstractVector, %158)::Type{AbstractVector{_A}} where _A
│     %160 = Core.UnionAll(%158, %159)::Any
│     %161 = Core.apply_type(Main.Union, Distribution, %160)::Type
│     %162 = (%152 isa %161)::Bool
└────        goto #107 if not %162
69 ──        nothing::Nothing
│     %165 = Core.tuple(%111)::Tuple{Int64}
│     %166 = Core.tuple(%165)::Tuple{Tuple{Int64}}
│     %167 = invoke VarName(:k::Symbol, %166::Tuple{Tuple{Int64}})::VarName{_A, Tuple{Tuple{Int64}}} where _A
│     %168 = Core.tuple(%111)::Tuple{Int64}
│     %169 = Core.tuple(%168)::Tuple{Tuple{Int64}}
│            nothing::Nothing
│     %171 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, %152, %167, %169, _varinfo)::Any
│            Base.setindex!(%99, %171, %111)::Any
│     %173 = Base.arrayref(true, %99, %111)::Int64
│     %174 = Base.arrayref(true, %56, %173)::Float64
│     %175 = Base.arrayref(true, %99, %111)::Int64
│     %176 = Base.arrayref(true, %56, %175)::Float64
│     %177 = Core.tuple(%174, %176)::Tuple{Float64, Float64}
│     %178 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Float64}, svec(Any, Int64), 0, :(:ccall), Vector{Float64}, 2, 2))::Vector{Float64}
│            Base.arraysize(%178, 1)::Int64
└────        goto #75 if not true
70 ┄─ %181 = φ (#69 => 1, #74 => %192)::Int64
│     %182 = φ (#69 => 1, #74 => %193)::Int64
│     %183 = φ (#69 => 1, #74 => %186)::Int64
│     %184 = Base.getfield(%177, %181, true)::Float64
│            Base.arrayset(false, %178, %184, %183)::Vector{Float64}
│     %186 = Base.add_int(%183, 1)::Int64
│     %187 = (%182 === 2)::Bool
└────        goto #72 if not %187
71 ──        goto #73
72 ── %190 = Base.add_int(%182, 1)::Int64
└────        goto #73
73 ┄─ %192 = φ (#72 => %190)::Int64
│     %193 = φ (#72 => %190)::Int64
│     %194 = φ (#71 => true, #72 => false)::Bool
│     %195 = Base.not_int(%194)::Bool
└────        goto #75 if not %195
74 ──        goto #70
75 ┄─        goto #76
76 ── %199 = Base.arraylen(%178)::Int64
│     %200 = Base.arraylen(%178)::Int64
│     %201 = (%199 === %200)::Bool
└────        goto #78 if not %201
77 ── %203 = %new(PDMats.ScalMat{Float64}, %199, 1.0, 1.0)::PDMats.ScalMat{Float64}
│     %204 = %new(IsoNormal, %178, %203)::IsoNormal
└────        goto #79
78 ── %206 = Distributions.DimensionMismatch("The dimensions of mu and Sigma are inconsistent.")::Any
│            Distributions.throw(%206)::Union{}
└────        unreachable
79 ──        goto #80
80 ── %210 = invoke Core.TypeVar(Symbol("#s68")::Symbol, Distribution::Any)::TypeVar
│     %211 = Core.apply_type(Main.AbstractVector, %210)::Type{AbstractVector{_A}} where _A
│     %212 = Core.UnionAll(%210, %211)::Any
│     %213 = Core.apply_type(Main.Union, Distribution, %212)::Type
│     %214 = (%204 isa %213)::Bool
└────        goto #106 if not %214
81 ──        nothing::Nothing
└────        goto #83 if not false
82 ──        nothing::Nothing
83 ┄─        goto #85 if not false
84 ──        nothing::Nothing
85 ┄─ %221 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64
│     %223 = Base.slt_int(%221, 0)::Bool
│     %224 = Base.ifelse(%223, 0, %221)::Int64
│     %225 = %new(Base.OneTo{Int64}, %224)::Base.OneTo{Int64}
│     %226 = %new(Base.Slice{Base.OneTo{Int64}}, %225)::Base.Slice{Base.OneTo{Int64}}
└────        goto #90 if not true
86 ── %228 = Core.tuple(%226, %111)::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}
│            Base.arraysize(x, 1)::Int64
│     %230 = Base.arraysize(x, 2)::Int64
│     %231 = Base.slt_int(%230, 0)::Bool
│     %232 = Base.ifelse(%231, 0, %230)::Int64
│     %233 = Base.sle_int(1, %111)::Bool
│     %234 = Base.sle_int(%111, %232)::Bool
│     %235 = Base.and_int(%233, %234)::Bool
│     %236 = Base.and_int(%235, true)::Bool
│     %237 = Base.and_int(true, %236)::Bool
└────        goto #88 if not %237
87 ──        goto #89
88 ──        invoke Base.throw_boundserror(_7::Matrix{Float64}, %228::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})::Union{}
└────        unreachable
89 ──        nothing::Nothing
90 ┄─        invoke Base._unsafe_getindex($(QuoteNode(IndexLinear()))::IndexLinear, _7::Matrix{Float64}, %226::Base.Slice{Base.OneTo{Int64}}, %111::Int64)::Vector{Float64}
└────        goto #91
91 ──        goto #92
92 ──        goto #94 if not false
93 ──        nothing::Nothing
94 ┄─ %248 = Base.arraysize(x, 1)::Int64
│            Base.arraysize(x, 2)::Int64
│     %250 = Base.slt_int(%248, 0)::Bool
│     %251 = Base.ifelse(%250, 0, %248)::Int64
│     %252 = %new(Base.OneTo{Int64}, %251)::Base.OneTo{Int64}
│     %253 = %new(Base.Slice{Base.OneTo{Int64}}, %252)::Base.Slice{Base.OneTo{Int64}}
└────        goto #99 if not true
95 ── %255 = Core.tuple(%253, %111)::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}
│            Base.arraysize(x, 1)::Int64
│     %257 = Base.arraysize(x, 2)::Int64
│     %258 = Base.slt_int(%257, 0)::Bool
│     %259 = Base.ifelse(%258, 0, %257)::Int64
│     %260 = Base.sle_int(1, %111)::Bool
│     %261 = Base.sle_int(%111, %259)::Bool
│     %262 = Base.and_int(%260, %261)::Bool
│     %263 = Base.and_int(%262, true)::Bool
│     %264 = Base.and_int(true, %263)::Bool
└────        goto #97 if not %264
96 ──        goto #98
97 ──        invoke Base.throw_boundserror(_7::Matrix{Float64}, %255::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})::Union{}
└────        unreachable
98 ──        nothing::Nothing
99 ┄─ %270 = invoke Base._unsafe_getindex($(QuoteNode(IndexLinear()))::IndexLinear, _7::Matrix{Float64}, %253::Base.Slice{Base.OneTo{Int64}}, %111::Int64)::Vector{Float64}
└────        goto #100
100 ─        goto #101
101 ─ %273 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│     %274 = Base.getfield(%273, :x)::Int64
│     %275 = Base.add_int(%274, 1)::Int64
│     %276 = Base.getfield(_varinfo, :num_produce)::Base.RefValue{Int64}
│            Base.setfield!(%276, :x, %275)::Int64
│     %278 = invoke Distributions.logpdf(%204::IsoNormal, %270::Vector{Float64})::Float64
│     %279 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│     %280 = Base.getfield(%279, :x)::Float64
│     %281 = Base.add_float(%280, %278)::Float64
│     %282 = Base.getfield(_varinfo, :logp)::Base.RefValue{Float64}
│            Base.setfield!(%282, :x, %281)::Float64
│     %284 = (%112 === %101)::Bool
└────        goto #103 if not %284
102 ─        goto #104
103 ─ %287 = Base.add_int(%112, 1)::Int64
└────        goto #104
104 ┄ %289 = φ (#103 => %287)::Int64
│     %290 = φ (#103 => %287)::Int64
│     %291 = φ (#102 => true, #103 => false)::Bool
│     %292 = Base.not_int(%291)::Bool
└────        goto #108 if not %292
105 ─        goto #41
106 ─ %295 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│            Main.throw(%295)::Union{}
└────        unreachable
107 ─ %298 = Main.ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions.")::Any
│            Main.throw(%298)::Union{}
└────        unreachable
108 ┄        return %99
) => Vector{Int64}
expr = @macroexpand begin
    @model function demo3(x)
        D, N = size(x)

        # Draw the parameters for cluster 1.
        μ1 ~ Normal()

        # Draw the parameters for cluster 2.
        μ2 ~ Normal()

        μ = [μ1, μ2]

        # Comment out this line if you instead want to draw the weights.
        w = [0.5, 0.5]

        # Draw assignments for each datum and generate it from a multivariate normal.
        k = Vector{Int}(undef, N)
        for i in 1:N
            k[i] ~ Categorical(w)
            x[:,i] ~ MvNormal([μ[k[i]], μ[k[i]]], 1.)
        end
        return k
    end
end
expr |> Base.remove_linenums!
quote
    begin
        $(Expr(:meta, :doc))
        function demo3(x; )
            var"##evaluator#460" = ((_rng::Random.AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x)->begin
                        begin
                            (D, N) = size(x)
                            begin
                                var"##tmpright#436" = Normal()
                                var"##tmpright#436" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#438" = μ1
                                var"##inds#439" = ()
                                var"##isassumption#440" = begin
                                        let var"##sym#441" = :μ1
                                            if !((DynamicPPL.inargnames)(Val(var"##sym#441"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#441"), _model)
                                                true
                                            else
                                                μ1 === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#440"
                                    μ1 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#436", var"##vn#438", var"##inds#439", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#436", μ1, var"##vn#438", var"##inds#439", _varinfo)
                                end
                            end
                            begin
                                var"##tmpright#442" = Normal()
                                var"##tmpright#442" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                var"##vn#444" = μ2
                                var"##inds#445" = ()
                                var"##isassumption#446" = begin
                                        let var"##sym#447" = :μ2
                                            if !((DynamicPPL.inargnames)(Val(var"##sym#447"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#447"), _model)
                                                true
                                            else
                                                μ2 === missing
                                            end
                                        end
                                    end
                                if var"##isassumption#446"
                                    μ2 = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#442", var"##vn#444", var"##inds#445", _varinfo)
                                else
                                    (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#442", μ2, var"##vn#444", var"##inds#445", _varinfo)
                                end
                            end
                            μ = [μ1, μ2]
                            w = [0.5, 0.5]
                            k = Vector{Int}(undef, N)
                            for i = 1:N
                                begin
                                    var"##tmpright#448" = Categorical(w)
                                    var"##tmpright#448" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                    var"##vn#450" = (VarName)(:k, ((i,),))
                                    var"##inds#451" = ((i,),)
                                    var"##isassumption#452" = begin
                                            let var"##sym#453" = :k
                                                if !((DynamicPPL.inargnames)(Val(var"##sym#453"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#453"), _model)
                                                    true
                                                else
                                                    k[i] === missing
                                                end
                                            end
                                        end
                                    if var"##isassumption#452"
                                        k[i] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#448", var"##vn#450", var"##inds#451", _varinfo)
                                    else
                                        (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#448", k[i], var"##vn#450", var"##inds#451", _varinfo)
                                    end
                                end
                                begin
                                    var"##tmpright#454" = MvNormal([μ[k[i]], μ[k[i]]], 1.0)
                                    var"##tmpright#454" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
                                    var"##vn#456" = (VarName)(:x, ((:, i),))
                                    var"##inds#457" = ((:, i),)
                                    var"##isassumption#458" = begin
                                            let var"##sym#459" = :x
                                                if !((DynamicPPL.inargnames)(Val(var"##sym#459"), _model)) || (DynamicPPL.inmissings)(Val(var"##sym#459"), _model)
                                                    true
                                                else
                                                    x[:, i] === missing
                                                end
                                            end
                                        end
                                    if var"##isassumption#458"
                                        x[:, i] = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#454", var"##vn#456", var"##inds#457", _varinfo)
                                    else
                                        (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#454", x[:, i], var"##vn#456", var"##inds#457", _varinfo)
                                    end
                                end
                            end
                            return k
                        end
                    end)
            return (Model)(:demo3, var"##evaluator#460", (DynamicPPL.namedtuple)(NamedTuple{(:x,), Tuple{Core.Typeof(x)}}, (x,)), NamedTuple())
        end
    end
end

@torfjelde
Copy link
Member Author

torfjelde commented Apr 5, 2021

OK, great! I just wanted to make sure that I understand the benchmark results correctly. I'll open a PR to AbstractPPL as soon as CI tests are available (TuringLang/AbstractPPL.jl#16).

Lovely:) Thanks!

Atm the major reason why this isn't possible is because we need to know args at the expansion of such a @tilde, which is going to get real annoying to deal with.

It is not obvious to me why one has to deal with the model arguments in a macro - can you show a simple example?

using DynamicPPL, MacroTools
using Distributions
import Random

# Since we don't need access to `args` at expansion, we can re-use
# the implementation of `generate_mainbody`.
"""
    @tilde x ~ Distribution

Generates the equivalent of a tilde-statement in DynamicPPL.
"""
macro tilde(expr)
    esc(DynamicPPL.generate_mainbody(__module__, expr, false))
end

"""
    @submodel_def expr

Insert variables internal to `DynamicPPL.@model` at beginning of `args` in method definition.
"""
macro submodel_def(expr)
    modeldef = MacroTools.splitdef(expr)
    modeldef[:args] = vcat(
        [
            :(_rng::$(Random.AbstractRNG)),
            :(_model::$(DynamicPPL.Model)),
            :(_varinfo::$(DynamicPPL.AbstractVarInfo)),
            :(_sampler::$(DynamicPPL.AbstractSampler)),
            :(_context::$(DynamicPPL.AbstractContext)),
        ],
        modeldef[:args]
    )
    return esc(MacroTools.combinedef(modeldef))
end

"""
    @submodel_call f(args...; kwargs...)

Insert variables internal to `@model` at beginning of `args` in a function call.
"""
macro submodel_call(expr)
    # If not a `:call`, don't do anything.
    if !Meta.isexpr(expr, :call)
        return expr
    end

    # Insert arguments internal to `@model`.
    has_kwargs = Meta.isexpr(expr.args[2], :parameters)
    kwargs = has_kwargs ? expr.args[2] : []
    args_start_idx = 2 + has_kwargs
    args = if length(expr.args)  args_start_idx
        expr.args[args_start_idx:end]
    else
        []
    end

    expr.args = vcat(
        expr.args[1],
        kwargs,
        [:(_rng), :(_model), :(_varinfo), :(_sampler), :(_context)],
        args
    )

    return esc(expr)
end
julia> @submodel_def function example_submodel(x)
           @tilde x ~ Normal()

           return x
       end
example_submodel (generic function with 1 method)

julia> @model function demo(x)
           # Equivalent of
           #   `example_submodel(_rng, _model, _varinfo, _sampler, _context)`
           @submodel_call example_submodel(x)
       end false
demo (generic function with 1 method)

julia> m = demo(missing)
Model{var"#1#2", (:x,), (), (:x,), Tuple{Missing}, Tuple{}}(:demo, 
var"#1#2"(), (x = missing,), NamedTuple())

julia> vi = VarInfo(m);

julia> vi[@varname(x)]
-0.6285896547007351

julia> m = demo(1.0)
Model{var"#1#2", (:x,), (), (), Tuple{Float64}, Tuple{}}(:demo, var"#1#2"(), (x = 1.0,), NamedTuple())

julia> m()
1.0

Some example-output of the macros defined here (because I can't embed julia-repl examples in the docstrings on GitHub):

julia> @macroexpand @tilde x ~ Normal()
quote
    var"##tmpright#257" = Normal()
    var"##tmpright#257" isa Union{Distribution, AbstractVector{<:Distribution}} || throw(ArgumentError("Right-hand side of a ~ must be subtype of Distribution or a vector of Distributions."))
    var"##vn#259" = (VarName){:x}()
    var"##inds#260" = ()
    var"##isassumption#261" = begin
            let var"##vn#262" = (VarName){:x}()
                if !((DynamicPPL.inargnames)(var"##vn#262", _model)) || (DynamicPPL.inmissings)(var"##vn#262", _model)
                    true
                else
                    x === missing
                end
            end
        end
    if var"##isassumption#261"
        x = (DynamicPPL.tilde_assume)(_rng, _context, _sampler, var"##tmpright#257", var"##vn#259", var"##inds#260", _varinfo)
    else
        (DynamicPPL.tilde_observe)(_context, _sampler, var"##tmpright#257", x, var"##vn#259", var"##inds#260", _varinfo)
    end
end
julia> @macroexpand(@submodel_def function f(x, y; z = 1)
           return x + y + z
       end) |> Base.remove_linenums!
:(function f(_rng::AbstractRNG, _model::Model, _varinfo::AbstractVarInfo, _sampler::AbstractMCMC.AbstractSampler, _context::DynamicPPL.AbstractContext, x, y; z = 1)
      return x + y + z
  end)
julia> @macroexpand @submodel_call f(x, y; z = 1)
:(f(_rng, _model, _varinfo, _sampler, _context, x, y; z = 1))

It's worth pointing out that I'm not sure this is something we want to "offer" the users 😅 : But at least such a thing is possible.

Of course one could just re-implement the generate_mainbody from this PR and call it something like generate_mainbody_no_args which doesn't require args to do the above, buuuut that seems unnecessarily complicated.

@devmotion
Copy link
Member

devmotion commented Apr 5, 2021

Ah I see, yes, the @tilde example would be simpler with this PR. Although it would still be possible to define it without having to reimplement generate_mainbody (however, it would then always perform a runtime check as in this PR):

struct CatchAllArgs end
Base.in(::Symbol, ::CatchAllArgs) = true

"""
    @tilde x ~ Distribution

Generates the equivalent of a tilde-statement in DynamicPPL.
"""
macro tilde(expr)
    esc(DynamicPPL.generate_mainbody(__module__, expr, CatchAllArgs(), false))
end

I also thought about something like @submodel_call a while ago but it seemed the main problem/annoyance would be that the variables in the submodel call are not available in the model scope and that the submodels must use distinct variable names. However, it would be sufficient for models with strictly separated compartments. Actually, one does not need @submodel_def and could just use @model to define the submodel, and then use submodel.f in the @submodel_def, where submodel is an instance of the submodel.

@torfjelde
Copy link
Member Author

Although it would still be possible to define it without having to reimplement generate_mainbody (however, it would then always perform a runtime check as in this PR):

Ah yes this is better! But it still complicates things a bit 😕

I also thought about something like @submodel_call a while ago but it seemed the main problem/annoyance would be that the variables in the submodel call are not available in the model scope and that the submodels must use distinct variable names. However, it would be sufficient for models with strictly separated compartments.

Agreed 👍 You could of course return the values to make them accessible in the main scope, which one might argue is the best way to handle submodels anyways, e.g. often you have some process with latent variables that you don't care about and so you're fine with those not being availabe in the global scope.

We could also start getting fancy, doing something like

@submodel (a, b, c) ~ submodel1(args...) # `submodel1` actually contains variables `(a, b, c, d, e, f)`

where which generates something like

begin
    @submodel_call submodel1.f(args...)
    _varinfo[@varname(a)], _varinfo[@varname(b)], _varinfo[@varname(c)]
end

And for the variables which are NOT then returned back to the main scope, we could even add in an identifier to the variable names somehow, e.g. prefixing the variable names with the name of the model/function name, so that when the latent variables show up in the resulting chain, it's all good.

Actually, one does not need @submodel_def and could just use @model to define the submodel, and then use submodel.f in the @submodel_def, where submodel is an instance of the submodel.

The only annoying thing here (and the reason why I implemented a "regular" function instead), is that you need to actually instantiate the model which can be a bit annoying, e.g. in the above example you need to also instantiate the submodel on the same argument (though I guess you could always make it missing)

@model function submodel(x)
    x ~ Normal()
end
submodel (generic function with 1 method)
subm = submodel(missing)
subm()
0.885312684587446
@model function demo(x)
    @submodel_call subm.f(x)
end false
demo (generic function with 1 method)
m = demo(missing)
m()
0.0007458004381980198
m = demo(1.0)
m()
1.0

@devmotion
Copy link
Member

And for the variables which are NOT then returned back to the main scope, we could even add in an identifier to the variable names somehow, e.g. prefixing the variable names with the name of the model/function name, so that when the latent variables show up in the resulting chain, it's all good.

It should be added anyway since otherwise variables of the same name will mess with each other in the VarInfo object.

The only annoying thing here (and the reason why I implemented a "regular" function instead), is that you need to actually instantiate the model which can be a bit annoying

You can hide this by instantiating it implicitly. E.g., one could define

macro submodel(expr)
	return :(_evaluate($(esc(:_rng)), $(esc(expr)), $(esc(:_varinfo)), $(esc(:_sampler)), $(esc(:_context))))
end

and then just write

@model function demo1(x)
    x ~ Normal()
end

@model function demo2(x, y)
    @submodel demo1(x)
    y ~ Uniform()
end

In principle, one could even use a different name for x (but one has to be careful with interactions between other parts of the model):

@model function demo2(a, y)
    @submodel demo1(a)
    y ~ Uniform()
end

@torfjelde
Copy link
Member Author

Oh wow, nice!

I literally just converted build_output into a build_evaluator, and then replaced the @submodel_def and @tilde with a new @submodel_def which only builds the evaluator, thinking this would make things neater.

But what you just did is waaaay nicer! I guess there would be a super-tiny overhead to _evaluate, or nah?

@torfjelde
Copy link
Member Author

torfjelde commented Apr 6, 2021

So really the only thing missing is just passing around a prefix to generate_tilde, etc. and we have pretty decent support for sub-models?

EDIT: On second thought, I guess this wouldn't be sufficient since we'd have to add the prefix at DPPL-compile time, at which point we don't know whether or not it's a submodel.

EDIT 2: No matter, being able to use a submodel but having to "manually" make sure that you're not using the same variable names, is still very, very useful.

EDIT 3: On third thought, even if you have to specify the prefix at definition, it would still be immensely useful. Most of the times you have a particular part of the model that you want to use as a sub-model in different models, and being able to just do:

@model function m(args...)
    # ...
end true "prefix_yo"

would be very helpful.

@devmotion
Copy link
Member

I assume it would be most intuitive/convenient if you can specify the prefix when including the submodel, not when defining it. Not sure if there are any dispatch issues but my first try would be to call _evaluate not with _context but with some PrefixContext(prefix, _context) that would just rewrite any VarName in tilde_observe and tilde_assume such that vsym(newvarname) == Symbol(prefix, vsym(varname)) and then call tilde_observe/tilde_assume with the new VarName and the wrapped _context. Maybe there are some other issues though I haven't thought of 🤷

BTW coming back to this PR, there's a new version of AbstractPPL now with the changes in the AbstractPPL PR. I think it would be good to merge #223 first (because otherwise the integration tests don't work) and then we can probably merge this PR I think if there are no major concerns 🙂

@torfjelde
Copy link
Member Author

Contexts! YES! I was thinking "should we include a _prefix as internal variable or something?" but nah, contexts seems like a really nice fit!

But yeah, we can sort that ought in a separet issue/PR 👍 I'll merge and get tests running 👍

@torfjelde
Copy link
Member Author

bors try

bors bot added a commit that referenced this pull request Apr 7, 2021
Co-authored-by: David Widmann <[email protected]>
Copy link
Member

@devmotion devmotion left a comment

Choose a reason for hiding this comment

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

LGTM

@torfjelde
Copy link
Member Author

Should I bump version and bors it?

@torfjelde
Copy link
Member Author

bors r+

bors bot pushed a commit that referenced this pull request Apr 7, 2021
## Overview
At the moment, we perform a check at model-expansion as to whether or not `vsym(left) in args`, where `args` is the arguments of the model. 
1. If `true`, we return a block of code which uses `DynamicPPL.isassumption` to check whether or not to call `assume` or `observe` for the the variable present in `args`. 
2. Otherwise, we generate a block which is identical to the `assume` block in the if-statement mentioned in (1).

The thing is, `DynamicPPL.isassumption` performs exactly the same check as above but using `DynamicPPL.inargnames`, i.e. at runtime. So if we're using  `TypedVarInfo`, the check at macro-expansion vs. at runtime is completely redundant since all the information necessary to determine `DynamicPPL.inargnames` is available at compile-time.

Therefore I suggest we remove this check at model-expansion, and simply handle it using `DynamicPPL.isassumption`.

## Pros & cons
Pros:
- No need to pass `args` around everywhere
- `generate_tilde` and `generate_dot_tilde` are much simpler: two possible blocks we can generate, either a) assume/observe, or b) observe literal.

Cons:
- We need to perform _one_ more check at runtime when using `UntypedVarInfo`.


**IMO, this is really worth it.**

## Motivation (sort of)
The main motivation behind this PR is simplification, but there's a different reason why I came across this.

I came to this because I was thinking about trying to "customize" the behavior of `~`, and I was thinking of using a macro to do it, e.g. `@mymacro x ~ Normal()`. Atm we're actually performing model-expansion on the code passed to the macro and thus trying to alter the way DynamicPPL treats `~` using a macro is veeeery difficult since you actually have to work with the *expanded* code, but let's ignore that issue for now (and take that discussion somewhere else, because IMO we shouldn't do this). 

Suppose we didn't perform model-expansions of the code fed to the macros, then you can just copy-paste `generate_tilde`, customize it do what you want, and BAM, you got yourself a working `@mymacro x ~ Normal()` which can do neat stuff! This is *not* possible atm because we don't have access to `args`, and so you have to take the approach in this PR to get there. That means that it's of course possible to do atm, but it's a bit icky since it ends up looking fundamentally different from `generate_tilde` rather than just slightly different.

Then we can implement things like a `@tilde` which will expand to `generate_tilde` which can be used *internally* in functions (if the "internal" variables are present in the functions of course, but we can also simplify this in different ways), actually allowing people to modularize their models a bit, and `@reparam` from #220 using very similar pieces of code, a `@track` macro can be introduced to deal with the explicit tracking of variables rather than putting this directly in the compiler, etc. Endless opportunities! (Of course, I'm not suggesting we add these, but this makes it a bit easier to explore.)

Co-authored-by: David Widmann <[email protected]>
@bors bors bot changed the title Small simplification of compiler [Merged by Bors] - Small simplification of compiler Apr 7, 2021
@bors bors bot closed this Apr 7, 2021
@bors bors bot deleted the tor/compiler-simplification branch April 7, 2021 23:49
bors bot pushed a commit that referenced this pull request May 18, 2021
Part of the motivations for #221 and #222 was so we could add submodels/model-nesting.

Well, now we can. 

Special thanks to @devmotion who reviewed those PRs (several times), improving them significantly, made additional PRs and suggested the current impl of the `@submodel` ❤️ 

EDIT: We fixed the performance:) This now has zero runtime overhead! See comment-section.
EDIT 2: Thanks to @devmotion, we can now alos deal with dynamically specified prefices!

- [Motivating example: AR1-prior](#org46a90a5)
- [Demos](#org7e05701)
- [Can it ever fail?](#org75acb71)
- [Benchmarks](#orga99bcf4)


<a id="org46a90a5"></a>

# Motivating example: AR1-prior

```julia
using Turing
using DynamicPPL
```

```julia
# Could have made model which samples `num_obs` AR1 samples simulatenously,
# but for the sake of showing off dynamic prefixes, we'll only use a vector-implementation.
# The matrix implementation will be quite a bit faster too, but oh well.
@model function AR1(num_steps, α, μ, σ, ::Type{TV} = Vector{Float64}) where {TV}
    η ~ MvNormal(num_steps, 1.0)
    δ = sqrt(1 - α^2)

    x = TV(undef, num_steps)
    x[1] = η[1]
    @inbounds for t = 2:num_steps
        x[t] = @. α * x[t - 1] + δ * η[t]
    end

    return @. μ + σ * x
end

# Generate an observation
σ_obs = 0.1
num_obs = 5
num_steps = 10

ar1 = AR1(num_steps, 0.5, 1.0, 1.0)
ys = mapreduce(hcat, 1:num_obs) do i
    ar1() + σ_obs * randn(num_steps)
end
```

    10×5 Matrix{Float64}:
      2.30189    0.301618  1.73268   -0.65096    1.46835
      2.11187   -1.34878   2.3728     1.02125    3.28422
     -0.249064   0.769488  1.34044    3.22175    2.52196
     -0.25863   -0.216914  0.528954   3.04756    3.8234
      0.372122   0.473511  0.708068   0.76197    0.202003
      0.41487    0.759435  1.80162    0.790204   0.12331
      1.32585    0.567929  2.74316    1.0874     2.82701
      1.84307    1.16138   1.36382    0.735388   1.07423
      3.20139    0.75177   1.57236    0.865401  -0.315341
      1.22479    1.35688   2.8239     0.597959   0.587955

```julia
@model function demo(y)
    α ~ Uniform()
    μ ~ Normal()
    σ ~ truncated(Normal(), 0, Inf)

    num_steps = size(y, 1)
    num_obs = size(y, 2)
    @inbounds for i = 1:num_obs
        x = @SubModel $(Symbol("ar1_$i")) AR1(num_steps, α, μ, σ)
        y[:, i] ~ MvNormal(x, 0.1)
    end
end;

m = demo(y);
vi = VarInfo(m);
```

```julia
keys(vi)
```

    8-element Vector{VarName{sym, Tuple{}} where sym}:
     α
     μ
     σ
     ar1_1.η
     ar1_2.η
     ar1_3.η
     ar1_4.η
     ar1_5.η

```julia
vi[@varname α]
```

    0.9383208224122919

```julia
chain = sample(m, NUTS(1_000, 0.8), 3_000);
```

    ┌ Info: Found initial step size
    │   ϵ = 0.025
    └ @ Turing.Inference /home/tor/.julia/packages/Turing/rHLGJ/src/inference/hmc.jl:188
    Sampling: 100%|█████████████████████████████████████████| Time: 0:04:00

```julia
chain[1001:end, [:α, :μ, :σ], :]
```

    Chains MCMC chain (2000×3×1 Array{Float64, 3}):
    
    Iterations        = 1001:3000
    Thinning interval = 1
    Chains            = 1
    Samples per chain = 2000
    parameters        = α, μ, σ
    internals         = 
    
    Summary Statistics
      parameters      mean       std   naive_se      mcse        ess      rhat 
          Symbol   Float64   Float64    Float64   Float64    Float64   Float64 
    
               α    0.5474    0.1334     0.0030    0.0073   159.6969    0.9995
               μ    1.0039    0.2733     0.0061    0.0168   169.9106    1.0134
               σ    1.1294    0.1807     0.0040    0.0106   166.8670    0.9998
    
    Quantiles
      parameters      2.5%     25.0%     50.0%     75.0%     97.5% 
          Symbol   Float64   Float64   Float64   Float64   Float64 
    
               α    0.2684    0.4625    0.5534    0.6445    0.7861
               μ    0.4248    0.8227    1.0241    1.2011    1.4801
               σ    0.8781    1.0018    1.0989    1.2239    1.5472

Yay! We recovered the true parameters :tada:

```julia
@benchmark $m($vi)
```

    BenchmarkTools.Trial: 
      memory estimate:  12.05 KiB
      allocs estimate:  123
      --------------
      minimum time:     15.091 μs (0.00% GC)
      median time:      17.861 μs (0.00% GC)
      mean time:        19.582 μs (5.23% GC)
      maximum time:     10.293 ms (99.46% GC)
      --------------
      samples:          10000
      evals/sample:     1


<a id="org7e05701"></a>

# Demos

```julia
using DynamicPPL, Distributions
```

    ┌ Info: Precompiling DynamicPPL [366bfd00-2699-11ea-058f-f148b4cae6d8]
    └ @ Base loading.jl:1317

```julia
@model function demo1(x)
    x ~ Normal()
end;
@model function demo2(x, y)
    @SubModel demo1(x)
    y ~ Uniform()
end false;
m2 = demo2(missing, missing);
vi2 = VarInfo(m2);
keys(vi2)
```

    2-element Vector{VarName{sym, Tuple{}} where sym}:
     x
     y

```julia
println(vi2[VarName(Symbol("x"))])
println(vi2[VarName(Symbol("y"))])
```

    0.3069117531180063
    0.7325324947386318

We can also `observe` without issues:

```julia
@model function demo2(x, y)
    @SubModel demo1(x)
    y ~ Normal(x)
end false;
m2 = demo2(1000.0, missing);
vi2 = VarInfo(m2);
keys(vi2)
```

    1-element Vector{VarName{:y, Tuple{}}}:
     y

```julia
vi2[@varname y]
```

    1000.3905079427211

```julia
DynamicPPL.getlogp(vi2)
```

    -500001.9141252931

But what if the models have the same variable-names?!

"Sure, this is cool and all, but can we even use the values from the nested values in the parent model?"

```julia
@model function demo_return(x)
    x ~ Normal()
    return x
end;

@model function demo_useval(x, y)
    x1 = @SubModel sub1 demo_return(x)
    x2 = @SubModel sub2 demo_return(y)

    z ~ Normal(x1 + x2 + 100, 1.0)
end false;
vi = VarInfo(demo_useval(missing, missing));
keys(vi)
```

    3-element Vector{VarName{sym, Tuple{}} where sym}:
     sub1.x
     sub2.x
     z

```julia
vi[@varname z]
```

    101.09066854862154

And just to prove a point:

```julia
@model function nested(x, y)
    @SubModel 1 nested1(x, y)
    y ~ Uniform()
end false;
@model function nested1(x, y)
    @SubModel 2 nested2(x, y)
    y ~ Uniform()
end false;
@model function nested2(x, y)
    z = @SubModel 3 nested3(x, y)
    y ~ Normal(z, 1.0)
end false;
@model function nested3(x, y)
    x ~ Uniform()
    y ~ Normal(-100.0, 1.0)

    return x + 1000
end false;

m = nested(missing, missing);
vi = VarInfo(m);
keys(vi)
```

    5-element Vector{VarName{sym, Tuple{}} where sym}:
     1.2.3.x
     1.2.3.y
     1.2.y
     1.y
     y

```julia
vi[VarName(Symbol("1.2.y"))]
```

    1000.5609156083766

```julia
DynamicPPL.getlogp(vi)
```

    -4.620040828101227


<a id="org75acb71"></a>

# Can it ever fail?

Yeah, if the user doesn't provide the prefix, it can:

```julia
@model function nested(x, y)
    @SubModel nested1(x, y)
    y ~ Uniform()
end false;
@model function nested1(x, y)
    @SubModel nested2(x, y)
    y ~ Uniform()
end false;
@model function nested2(x, y)
    z = @SubModel nested3(x, y)
    y ~ Normal(z, 1.0)
end false;
@model function nested3(x, y)
    x ~ Uniform()
    y ~ Normal(-100.0, 1.0)

    return x + 1000
end false;

m = nested(missing, missing);
vi = VarInfo(m);
keys(vi)
```

    2-element Vector{VarName{sym, Tuple{}} where sym}:
     x
     y

```julia
# Inner-most value is recorded (i.e. the first one reached)
vi[@varname y]
```

    -100.16836599596732

And it messes up the logp computation:

```julia
DynamicPPL.getlogp(vi)
```

    -Inf

But I could imagine there's a way for us to fix this, or at least warn the user when this happens.


<a id="orga99bcf4"></a>

# Benchmarks

At this point you're probably wondering, "but does it have any overhead (at runtime)?". For a "shallow" nestings, nah, but if you go deep enough there seems to be a tiny bit (likely because we're calling the "constructor" for the model):

```julia
using BenchmarkTools

@model function base(x, y)
    x ~ Uniform()
    y ~ Uniform()
    y1 ~ Uniform()
    z = x + 1000
    y12 ~ Normal()
    y123 ~ Normal(-100.0, 1.0)
end

m1 = base(missing, missing);
vi1 = VarInfo(m1);
```

```julia
@model function nested_shallow(x, y)
    @SubModel 1 nested1_shallow(x, y)
    y ~ Uniform()
end false;
@model function nested1_shallow(x, y)
    x ~ Uniform()
    y ~ Uniform()
    z = x + 1000
    y12 ~ Normal()
    y123 ~ Normal(-100.0, 1.0)
end false;

m2 = nested_shallow(missing, missing);
vi2 = VarInfo(m2);
```

```julia
@model function nested(x, y)
    @SubModel 1 nested1(x, y)
    y ~ Uniform()
end false;
@model function nested1(x, y)
    @SubModel 2 nested2(x, y)
    y ~ Uniform()
end false;
@model function nested2(x, y)
    z = @SubModel 3 nested3(x, y)
    y ~ Normal(z, 1.0)
end false;
@model function nested3(x, y)
    x ~ Uniform()
    y ~ Normal(-100.0, 1.0)

    return x + 1000
end

m3 = nested(missing, missing);
vi3 = VarInfo(m3);
```

```julia
@model function nested_noprefix(x, y)
    @SubModel nested_noprefix1(x, y)
    y ~ Uniform()
end false;
@model function nested_noprefix1(x, y)
    @SubModel nested_noprefix2(x, y)
    y1 ~ Uniform()
end false;
@model function nested_noprefix2(x, y)
    z = @SubModel nested_noprefix3(x, y)
    y2 ~ Normal(z, 1.0)
end false;
@model function nested_noprefix3(x, y)
    x ~ Uniform()
    y3 ~ Normal(-100.0, 1.0)

    return x + 1000
end

m4 = nested_noprefix(missing, missing);
vi4 = VarInfo(m4);
```

```julia
keys(vi1)
```

    5-element Vector{VarName{sym, Tuple{}} where sym}:
     x
     y
     y1
     y12
     y123

```julia
keys(vi2)
```

    5-element Vector{VarName{sym, Tuple{}} where sym}:
     1.x
     1.y
     1.y12
     1.y123
     y

```julia
keys(vi3)
```

    5-element Vector{VarName{sym, Tuple{}} where sym}:
     1.2.3.x
     1.2.3.y
     1.2.y
     1.y
     y

```julia
keys(vi4)
```

    5-element Vector{VarName{sym, Tuple{}} where sym}:
     x
     y3
     y2
     y1
     y

```julia
@benchmark $m1($vi1)
```

    BenchmarkTools.Trial: 
      memory estimate:  160 bytes
      allocs estimate:  5
      --------------
      minimum time:     1.714 μs (0.00% GC)
      median time:      1.747 μs (0.00% GC)
      mean time:        1.835 μs (0.00% GC)
      maximum time:     6.894 μs (0.00% GC)
      --------------
      samples:          10000
      evals/sample:     10

```julia
@benchmark $m2($vi2)
```

    BenchmarkTools.Trial: 
      memory estimate:  160 bytes
      allocs estimate:  5
      --------------
      minimum time:     1.759 μs (0.00% GC)
      median time:      1.778 μs (0.00% GC)
      mean time:        1.819 μs (0.00% GC)
      maximum time:     5.563 μs (0.00% GC)
      --------------
      samples:          10000
      evals/sample:     10

```julia
@benchmark $m3($vi3)
```

    BenchmarkTools.Trial: 
      memory estimate:  160 bytes
      allocs estimate:  5
      --------------
      minimum time:     1.718 μs (0.00% GC)
      median time:      1.746 μs (0.00% GC)
      mean time:        1.787 μs (0.00% GC)
      maximum time:     5.758 μs (0.00% GC)
      --------------
      samples:          10000
      evals/sample:     10

```julia
@benchmark $m4($vi4)
```

    BenchmarkTools.Trial: 
      memory estimate:  160 bytes
      allocs estimate:  5
      --------------
      minimum time:     1.672 μs (0.00% GC)
      median time:      1.696 μs (0.00% GC)
      mean time:        1.756 μs (0.00% GC)
      maximum time:     4.882 μs (0.00% GC)
      --------------
      samples:          10000
      evals/sample:     10

Notice that the number of allocations have increased for the deeply nested model. Seems like the Julia compiler isn't too good at inferring the return-types of Turing-models? This seems to be the case too by looking at the lowered code. I haven't given this too much thought yet btw; likely is a way for us to help the compiler here.
@torfjelde torfjelde mentioned this pull request May 18, 2021
bors bot pushed a commit that referenced this pull request Jul 13, 2021
Changes to DPPL can often have quite significant effects for compilation time and performance of both itself and downstream packages. It's also sometimes difficult to discover these performance regressions.

E.g. in #221 we made a small simplification to the compiler and it ended up taking quite a while to figure out what was going wrong and had to test several models to identify the issue.

So, this is a WIP PR for including a small set of models which we can `weave` into a document where we can look at the changes. It's unclear to me whether this should go in DPPL itself or in a separate package. I found it useful myself and figured I'd put it here so we can start maybe get some "standard" benchmarks to run for testing purposes. IMO we don't need many of them, as we will add more as we go along.

For each model the following will be included in the document:
- Benchmarked evaluation of the model on untyped and typed `VarInfo`.
- Timing of the compilation of the model in the typed `VarInfo`.
- Lowered code for the model.
  - If `:prefix` is provided to `weave`, the string-representation of `code_typed` for the evaluation of the model will be saved to a file `$(prefix)_(model.name)`. Furthermore, if `:prefix_old` is provided, pointing to `:prefix` used for a previous run (likely using a different version of DPPL), we will `diff` the `code_typed` for the two models by loading the saved files.
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.

2 participants