Frequently Asked Questions
Why is this variable being treated as random instead of observed?
This is a common source of confusion. In Turing.jl, you can only condition or fix expressions that explicitly appear on the left-hand side (LHS) of a ~ statement.
For example, if your model contains:
x ~ filldist(Normal(), 2)You cannot directly condition on x[2] using condition(model, @varname(x[2]) => 1.0) because x[2] never appears on the LHS of a ~ statement. Only x as a whole appears there.
However, there is an important exception: when you use the broadcasting operator .~ with a univariate distribution, each element is treated as being separately drawn from that distribution, allowing you to condition on individual elements:
@model function f1()
    x = Vector{Float64}(undef, 3)
    x .~ Normal()  # Each element is a separate draw
end
m1 = f1() | (@varname(x[1]) => 1.0)
sample(m1, NUTS(), 100) # This works!In contrast, you cannot condition on parts of a multivariate distribution because it represents a single distribution over the entire vector:
@model function f2()
    x = Vector{Float64}(undef, 3)
    x ~ MvNormal(zeros(3), I)  # Single multivariate distribution
end
m2 = f2() | (@varname(x[1]) => 1.0)
sample(m2, NUTS(), 100) # This doesn't work!The key insight is that filldist creates a single distribution (not N independent distributions), which is why you cannot condition on individual elements. The distinction is not just about what appears on the LHS of ~, but whether you’re dealing with separate distributions (.~ with univariate) or a single distribution over multiple values (~ with multivariate or filldist).
To understand more about how Turing determines whether a variable is treated as random or observed, see:
- Core Functionality - basic explanation of the ~notation and conditioning
Can I use parallelism / threads in my model?
Yes, but with important caveats! There are two types of parallelism to consider:
1. Parallel Sampling (Multiple Chains)
Turing.jl fully supports sampling multiple chains in parallel:
- Multithreaded sampling: Use MCMCThreads()to run one chain per thread
- Distributed sampling: Use MCMCDistributed()for distributed computing
See the Core Functionality guide for examples.
2. Threading Within Models
Using threads inside your model (e.g., Threads.@threads) requires more care:
@model function f(y)
    x = Vector{Float64}(undef, length(y))
    Threads.@threads for i in eachindex(y)
        x[i] ~ Normal()  # UNSAFE: `assume` statements in @threads can crash!
        y[i] ~ Normal(x[i])  # `observe` statements are okay
    end
endImportant limitations:
- Observe statements: Generally safe to use in threaded loops
- Assume statements (sampling statements): Often crash unpredictably or produce incorrect results
- AD backend compatibility: Many AD backends don’t support threading. Check the multithreaded column in ADTests for compatibility
For safe parallelism within models, consider vectorized operations instead of explicit threading.
How do I check the type stability of my Turing model?
Type stability is crucial for performance. Check out:
- Performance Tips - includes specific advice on type stability
- Use DynamicPPL.DebugUtils.model_warntypeto check type stability of your model
How do I debug my Turing model?
For debugging both statistical and syntactical issues:
- Troubleshooting Guide - common errors and their solutions
- For more advanced debugging, DynamicPPL provides the DynamicPPL.DebugUtilsmodule for inspecting model internals
What are the main differences between Turing, BUGS, and Stan syntax?
Key syntactic differences include:
- Parameter blocks: Stan requires explicit data,parameters, andmodelblocks. In Turing, everything is defined within the@modelmacro
- Variable declarations: Stan requires upfront type declarations in parameter blocks. Turing infers types from the sampling statements
- Transformed data: Stan has a transformed datablock for preprocessing. In Turing, data transformations should be done before defining the model
- Generated quantities: Stan has a generated quantitiesblock. In Turing, use the approach described in Tracking Extra Quantities
Example comparison:
// Stan
data {
  real y;
}
parameters {
  real mu;
  real<lower=0> sigma;
}
model {
  mu ~ normal(0, 1);
  sigma ~ normal(0, 1);
  y ~ normal(mu, sigma);
}# Turing
@model function my_model(y)
    mu ~ Normal(0, 1)
    sigma ~ truncated(Normal(0, 1); lower=0)
    y ~ Normal(mu, sigma)
endWhich automatic differentiation backend should I use?
The choice of AD backend can significantly impact performance. See:
- Automatic Differentiation Guide - comprehensive comparison of ForwardDiff, Mooncake, ReverseDiff, and other backends
- Performance Tips - quick guide on choosing backends
- AD Backend Benchmarks - performance comparisons across various models
I changed one line of my model and now it’s so much slower; why?
Small changes can have big performance impacts. Common culprits include:
- Type instability introduced by the change
- Switching from vectorized to scalar operations (or vice versa)
- Inadvertently causing AD backend incompatibilities
- Breaking assumptions that allowed compiler optimizations
See our Performance Tips and Troubleshooting Guide for debugging performance regressions.