# Turing.jl for Causal Inference model

**URL:** https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650
**Category:** Probabilistic Programming
**Tags:** performance, turing
**Created:** [February 2, 2022, 12:51pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650 "2022-02-02T12:51:25Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 2, 2022, 12:51pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/1 "2022-02-02T12:51:25Z")

</div>

I am trying to translate the model from McElreath’s Causal Inference workshop ([youtube](https://www.youtube.com/watch?v=KNPYUVmY3NM), [github](https://github.com/rmcelreath/causal_salad_2021)) to Turing.jl and while the results seem to be the same I get much worse performance with Turing (3 sec. vs 110 sec.) so I was wondering if I am doing something wrong here? I appreciate any suggestions!

```julia
using Distributions,
    DynamicHMC,
    GLM,
    Memoization,    
    Random,
    ReverseDiff,
    RCall,
    StatsBase,
    StatsPlots,
    Turing

## Rethinking Version
R"""
set.seed(1908)
N <- 200 # number of pairs
U <- rnorm(N) # simulate confounds
# birth order and family sizes
B1 <- rbinom(N,size=1,prob=0.5) # 50% first borns
M <- rnorm( N , 2*B1 + U )
B2 <- rbinom(N,size=1,prob=0.5)
D <- rnorm( N , 2*B2 + U + 0*M ) # change the 0 to turn on causal influence of mom
library(rethinking)
library(cmdstanr)
dat <- list(N=N,M=M,D=D,B1=B1,B2=B2)
set.seed(1908)
flbi <- ulam(
    alist(
        # mom model
            M ~ normal( mu , sigma ),
            mu <- a1 + b*B1 + k*U[i],
        # daughter model
            D ~ normal( nu , tau ),
            nu <- a2 + b*B2 + m*M + k*U[i],
        # B1 and B2
            B1 ~ bernoulli(p),
            B2 ~ bernoulli(p),
        # unmeasured confound
            vector[N]:U ~ normal(0,1),
        # priors
            c(a1,a2,b,m) ~ normal( 0 , 0.5 ),
            c(k,sigma,tau) ~ exponential( 1 ),
            p ~ beta(2,2)
    ), data=dat , chains=4 , cores=4 , iter=2000 , cmdstan=TRUE )
posterior <- extract.samples(flbi)
""";
posterior_R = @rget(posterior);
dat_R = @rget(dat);

@model function mom(N, M, D, B1, B2)
    p ~ Beta(2,2)
    k ~ Exponential(1)
    σ ~ Exponential(1)
    τ ~ Exponential(1)
    a1 ~ Normal(0, 0.5)
    a2 ~ Normal(0, 0.5)
    b ~ Normal(0, 0.5)
    m ~ Normal(0, 0.5)
    U ~ filldist(Normal(0,1), N)
    B1 ~ Bernoulli(p)
    B2 ~ Bernoulli(p)

    ν = a2 .+ b * B2 + m * M + k * U
    D .~ Normal.(ν, τ)

    μ = a1 .+ b * B1 + k * U 
    M .~ Normal.(μ, σ)
end

Turing.setrdcache(true)
Turing.setadbackend(:reversediff)

flbi = sample(mom(Int(dat_R[:N]), dat_R[:M], dat_R[:D], dat_R[:B1], dat_R[:B2]), 
    NUTS(1000, 0.65),
    MCMCThreads(),
    2000, 4)

```

---

<div class="post-metadata">

### Author: ![cpfiffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cpfiffer/32/208747_2.png) [@cpfiffer](https://discourse.julialang.org/u/cpfiffer)
#### Post date: [February 2, 2022, 8:27pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/2 "2022-02-02T20:27:37Z")

</div>

The only thing I could think of here is making the `D` and `M` observations `MvNormal`:

```julia
D ~ MvNormal(ν, τ*I)

. . .
M ~ MvNormal(μ, σ*I)

```

though I’m not sure how much of a performance improvement that gives you.

---

<div class="post-metadata">

### Author: ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)
#### Post date: [February 2, 2022, 8:32pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/3 "2022-02-02T20:32:07Z")

</div>

Are you also counting compilation time or only running time?

---

<div class="post-metadata">

### Author: ![DoktorMike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/doktormike/32/2736_2.png) [@DoktorMike](https://discourse.julialang.org/u/DoktorMike)
#### Post date: [February 2, 2022, 8:44pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/4 "2022-02-02T20:44:18Z")

</div>

Can you also post the Stan code? Rethinking generates stan code behind the scenes so maybe that can give some hints to performance differences?

---

<div class="post-metadata">

### Author: ![opera\_malenky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opera_malenky/32/8213_2.png) [@opera\_malenky](https://discourse.julialang.org/u/opera_malenky)
#### Post date: [February 2, 2022, 9:16pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/5 "2022-02-02T21:16:07Z")

</div>

Set the cache (=true) _after_ you choose reversediff, I believe. Beyond that, I’m not sure.

---

<div class="post-metadata">

### Author: ![Christopher\_Fisher](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/christopher_fisher/32/26132_2.png) [@Christopher\_Fisher](https://discourse.julialang.org/u/Christopher_Fisher)
#### Post date: [February 2, 2022, 10:30pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/6 "2022-02-02T22:30:35Z")

</div>

> [@cpfiffer](#):
>
> `M ~ MvNormal(μ, σ*I)`

I was unable to run the Stan code, but using MvNormal decreased the run time from 31 to 7 seconds.

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 10:04am UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/7 "2022-02-03T10:04:35Z")

</div>

> [@DoktorMike](#):
>
> Can you also post the Stan code?

Finally could figure that out

```stan
data{
    int N;
    vector[200] D;
    vector[200] M;
    int B1[200];
    int B2[200];
}
parameters{
    vector[N] U;
    real m;
    real b;
    real a2;
    real a1;
    real<lower=0> tau;
    real<lower=0> sigma;
    real<lower=0> k;
    real<lower=0,upper=1> p;
}
model{
    vector[200] mu;
    vector[200] nu;
    p ~ beta( 2 , 2 );
    k ~ exponential( 1 );
    sigma ~ exponential( 1 );
    tau ~ exponential( 1 );
    a1 ~ normal( 0 , 0.5 );
    a2 ~ normal( 0 , 0.5 );
    b ~ normal( 0 , 0.5 );
    m ~ normal( 0 , 0.5 );
    U ~ normal( 0 , 1 );
    B2 ~ bernoulli( p );
    B1 ~ bernoulli( p );
    for ( i in 1:200 ) {
        nu[i] = a2 + b * B2[i] + m * M[i] + k * U[i];
    }
    D ~ normal( nu , tau );
    for ( i in 1:200 ) {
        mu[i] = a1 + b * B1[i] + k * U[i];
    }
    M ~ normal( mu , sigma );
}

```

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 10:11am UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/8 "2022-02-03T10:11:14Z")

</div>

Thanks for the suggestion! That helped a lot!

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 10:11am UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/9 "2022-02-03T10:11:37Z")

</div>

I hope just running time unless turing recompiles on every call?

---

<div class="post-metadata">

### Author: ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)
#### Post date: [February 3, 2022, 10:45am UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/10 "2022-02-03T10:45:01Z")

</div>

As long as you keep the Julia process running in between calls via the REPL / Pluto, it‘s not recompiling on the second call no

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 3:11pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/11 "2022-02-03T15:11:54Z")

</div>

In that case it should be without recompilation. On the second run I now get

```julia
Wall duration = 21.42 seconds
Compute duration = 65.88 seconds

```

What is the Wall duration?

---

<div class="post-metadata">

### Author: ![Christopher\_Fisher](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/christopher_fisher/32/26132_2.png) [@Christopher\_Fisher](https://discourse.julialang.org/u/Christopher_Fisher)
#### Post date: [February 3, 2022, 3:28pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/12 "2022-02-03T15:28:18Z")

</div>

Wall time is the time for all chains to finish and compute duration is the sum of each chain’s time to complete. Did your last benchmark use `MvNormal`?

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 3:34pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/13 "2022-02-03T15:34:11Z")

</div>

Yes but I did more draws.

For this model

```julia
@model function mom(N, M, D, B1, B2)
    p ~ Beta(2,2)
    k ~ Exponential(1)
    σ ~ Exponential(1)
    τ ~ Exponential(1)
    a1 ~ Normal(0, 0.5)
    a2 ~ Normal(0, 0.5)
    b ~ Normal(0, 0.5)
    m ~ Normal(0, 0.5)
    U ~ filldist(Normal(0,1), N)
    B1 ~ Bernoulli(p)
    B2 ~ Bernoulli(p)

    ν = a2 .+ b * B2 + m * M + k * U
    D ~ MvNormal(ν, τ * I)

    μ = a1 .+ b * B1 + k * U 
    M ~ MvNormal(μ, σ * I)
end

Turing.setadbackend(:reversediff)
Turing.setrdcache(true)

flbi = sample(
    mom(Int(dat_R[:N]), dat_R[:M], dat_R[:D], dat_R[:B1], dat_R[:B2]), 
    NUTS(1000, 0.65),
    MCMCThreads(),
    2_000, 4)

```

I get

```julia
Iterations = 1001:1:3000
Number of chains = 4
Samples per chain = 2000
Wall duration = 10.99 seconds
Compute duration = 37.53 seconds

```

---

<div class="post-metadata">

### Author: ![Christopher\_Fisher](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/christopher_fisher/32/26132_2.png) [@Christopher\_Fisher](https://discourse.julialang.org/u/Christopher_Fisher)
#### Post date: [February 3, 2022, 3:36pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/14 "2022-02-03T15:36:19Z")

</div>

Ok. Thanks. I was unable to run the Stan code. How does the new Turing model compare to Stan when the same number of samples are used?

Edit: My mistake. I think that is the comparison with the same number of samples. So it is within a factor of 4?

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 3:39pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/15 "2022-02-03T15:39:35Z")

</div>

I actually think that 2000 iterations in stan means you run 2000 iterations but in Turing you get 2000 iterations with 1000 burn-in. So with 3000 iterations in stan I get

```julia
All 4 chains finished successfully.
Mean chain execution time: 3.2 seconds.
Total execution time: 3.8 seconds.

```

---

<div class="post-metadata">

### Author: ![Christopher\_Fisher](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/christopher_fisher/32/26132_2.png) [@Christopher\_Fisher](https://discourse.julialang.org/u/Christopher_Fisher)
#### Post date: [February 3, 2022, 3:43pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/16 "2022-02-03T15:43:22Z")

</div>

There is certainly room for improvement, but 11 seconds is reasonable. In my experience (unless something has changed recently), the problem with Julia’s autodiff is that it does not scale as well as Stan’s. I think the other problem is reversediff does not work well with loops, which can be easier to write in some cases.

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 3, 2022, 3:45pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/17 "2022-02-03T15:45:28Z")

</div>

I’ll try to test a couple of different AD backends. Is it ok to set the backend in a live session or would it be better to restart?

---

<div class="post-metadata">

### Author: ![Christopher\_Fisher](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/christopher_fisher/32/26132_2.png) [@Christopher\_Fisher](https://discourse.julialang.org/u/Christopher_Fisher)
#### Post date: [February 3, 2022, 3:48pm UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/18 "2022-02-03T15:48:28Z")

</div>

I think it is fine to start in the same session. Unfortunately, reversediff might be the best at the moment.

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 4, 2022, 10:29am UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/19 "2022-02-04T10:29:28Z")

</div>

Here is my benchmark for two runs of each:

```julia
Turing.setadbackend(:forwarddiff)

Iterations = 1001:1:2000
Number of chains = 4
Samples per chain = 1000
Wall duration = 102.14 seconds
Compute duration = 361.99 seconds

Iterations = 1001:1:2000
Number of chains = 4
Samples per chain = 1000
Wall duration = 95.9 seconds
Compute duration = 333.32 seconds

```

```julia
Turing.setadbackend(:tracker)

Iterations = 1001:1:2000
Number of chains = 4
Samples per chain = 1000
Wall duration = 99.84 seconds
Compute duration = 331.42 seconds

Iterations = 1001:1:2000
Number of chains = 4
Samples per chain = 1000
Wall duration = 86.06 seconds
Compute duration = 298.54 seconds

```

```julia
Turing.setadbackend(:zygote)
# gave up after a couple of minutes; will let it run over the weekend

```

```julia
Turing.setadbackend(:reversediff)
Turing.setrdcache(true)
Iterations = 1001:1:2000
Number of chains = 4
Samples per chain = 1000
Wall duration = 26.13 seconds
Compute duration = 101.67 seconds

Iterations = 1001:1:2000
Number of chains = 4
Samples per chain = 1000
Wall duration = 5.85 seconds
Compute duration = 20.05 seconds

```

---

<div class="post-metadata">

### Author: ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)
#### Post date: [February 4, 2022, 10:30am UTC](https://discourse.julialang.org/t/turing-jl-for-causal-inference-model/75650/20 "2022-02-04T10:30:06Z")

</div>

Yep reversediff seems to be the clear winner in my benchmark
