Skip to content

Add FAQ section to improve user guidance #608

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions .github/workflows/version_check.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Set up a temporary environment just to run this script
using Pkg
Pkg.activate(temp=true)
Pkg.activate(temp = true)
Pkg.add(["YAML", "TOML", "JSON", "HTTP"])
import YAML
import TOML
Expand All @@ -18,7 +18,10 @@ end

function major_minor_patch_match(vs...)
first = vs[1]
all(v.:major == first.:major && v.:minor == first.:minor && v.:patch == first.:patch for v in vs)
all(
v.:major == first.:major && v.:minor == first.:minor && v.:patch == first.:patch for
v in vs
)
end

"""
Expand All @@ -34,7 +37,10 @@ function update_project_toml(filename, target_version::VersionNumber)
open(filename, "w") do io
for line in lines
if occursin(r"^Turing\s*=\s*\"\d+\.\d+\"\s*$", line)
println(io, "Turing = \"$(target_version.:major).$(target_version.:minor)\"")
println(
io,
"Turing = \"$(target_version.:major).$(target_version.:minor)\"",
)
else
println(io, line)
end
Expand All @@ -54,7 +60,10 @@ function update_quarto_yml(filename, target_version::VersionNumber)
for line in lines
m = match(r"^(\s+)- text:\s*\"v\d+\.\d+\"\s*$", line)
if m !== nothing
println(io, "$(m[1])- text: \"v$(target_version.:major).$(target_version.:minor)\"")
println(
io,
"$(m[1])- text: \"v$(target_version.:major).$(target_version.:minor)\"",
)
else
println(io, line)
end
Expand Down Expand Up @@ -108,7 +117,7 @@ if ENV["TARGET_IS_MAIN"] == "true"
old_env = Pkg.project().path
Pkg.activate(".")
try
Pkg.add(name="Turing", version=latest_version)
Pkg.add(name = "Turing", version = latest_version)
catch e
# If the Manifest couldn't be updated, the error will be shown later
println(e)
Expand All @@ -118,14 +127,20 @@ if ENV["TARGET_IS_MAIN"] == "true"
manifest_toml = TOML.parsefile(MANIFEST_TOML_PATH)
manifest_version = VersionNumber(manifest_toml["deps"]["Turing"][1]["version"])
if !major_minor_patch_match(latest_version, manifest_version)
push!(errors, "Failed to update $(MANIFEST_TOML_PATH) to match latest Turing.jl version")
push!(
errors,
"Failed to update $(MANIFEST_TOML_PATH) to match latest Turing.jl version",
)
end
end

if isempty(errors)
println("All good")
else
error("The following errors occurred during version checking: \n", join(errors, "\n"))
error(
"The following errors occurred during version checking: \n",
join(errors, "\n"),
)
end

else
Expand All @@ -135,10 +150,12 @@ else
# work as it would involve paging through the list of releases). Instead,
# we just check that the minor versions match.
if !major_minor_match(quarto_version, project_version, manifest_version)
error("The minor versions of Turing.jl in _quarto.yml, Project.toml, and Manifest.toml are inconsistent:
- _quarto.yml: $quarto_version_str
- Project.toml: $project_version_str
- Manifest.toml: $manifest_version
")
error(
"The minor versions of Turing.jl in _quarto.yml, Project.toml, and Manifest.toml are inconsistent:
- _quarto.yml: $quarto_version_str
- Project.toml: $project_version_str
- Manifest.toml: $manifest_version
",
)
end
end
2 changes: 2 additions & 0 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ website:
text: Get Started
- href: tutorials/coin-flipping/
text: Tutorials
- href: faq/
text: FAQ
- href: https://turinglang.org/library/
text: Libraries
- href: https://turinglang.org/news/
Expand Down
137 changes: 137 additions & 0 deletions faq/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: "Frequently Asked Questions"
description: "Common questions and answers about using Turing.jl"
---

## 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:
```julia
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:

```julia
@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:

```julia
@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](../core-functionality/) - basic explanation of the `~` notation and conditioning
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- [Core Functionality](../core-functionality/) - basic explanation of the `~` notation and conditioning
- [Core Functionality]({{< meta 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](../core-functionality/#sampling-multiple-chains) for examples.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
See the [Core Functionality guide](../core-functionality/#sampling-multiple-chains) for examples.
See the [Core Functionality guide]({{< meta core-functionality >}}/#sampling-multiple-chains) for examples.


### 2. Threading Within Models
Using threads inside your model (e.g., `Threads.@threads`) requires more care:

```julia
@model function f(x)
Threads.@threads for i in eachindex(x)
x[i] ~ Normal() # UNSAFE: Assume statements in threads can crash!
end
end
Comment on lines +62 to +66
Copy link
Member

@penelopeysm penelopeysm Jul 3, 2025

Choose a reason for hiding this comment

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

In this model, x is observed so it's fine. 'assume' means that it's a random parameter, so for example something like this:

Suggested change
@model function f(x)
Threads.@threads for i in eachindex(x)
x[i] ~ Normal() # UNSAFE: Assume statements in threads can crash!
end
end
@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
end

```

**Important 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](https://turinglang.org/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]({{< meta usage-performance-tips >}}) - includes specific advice on type stability
- Use `DynamicPPL.DebugUtils.model_warntype` to check type stability of your model

## How do I debug my Turing model?

For debugging both statistical and syntactical issues:
- [Troubleshooting Guide]({{< meta usage-troubleshooting >}}) - common errors and their solutions
- For more advanced debugging, DynamicPPL provides `DynamicPPL.DebugUtils` for inspecting model internals
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- For more advanced debugging, DynamicPPL provides `DynamicPPL.DebugUtils` for inspecting model internals
- For more advanced debugging, DynamicPPL provides [the `DynamicPPL.DebugUtils` module](https://turinglang.org/DynamicPPL.jl/stable/api/#Debugging-Utilities) for inspecting model internals

Friendly link


## What are the main differences between Turing and Stan syntax?

Key syntactic differences include:

- **Parameter blocks**: Stan requires explicit `data`, `parameters`, `transformed parameters`, and `model` blocks. In Turing, everything is defined within the `@model` macro
Copy link
Member

@penelopeysm penelopeysm Jul 3, 2025

Choose a reason for hiding this comment

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

Suggested change
- **Parameter blocks**: Stan requires explicit `data`, `parameters`, `transformed parameters`, and `model` blocks. In Turing, everything is defined within the `@model` macro
- **Parameter blocks**: Stan requires explicit `data`, `parameters`, and `model` blocks. In Turing, everything is defined within the `@model` macro

transformed parameters isn't mandatory

- **Variable declarations**: Stan requires upfront type declarations in parameter blocks. Turing infers types from the sampling statements
- **Transformed data**: Stan has a `transformed data` block for preprocessing. In Turing, data transformations should be done before defining the model
- **Generated quantities**: Stan has a `generated quantities` block. In Turing, use the approach described in [Tracking Extra Quantities]({{< meta usage-tracking-extra-quantities >}})

Example comparison:
```stan
// Stan
data {
int<lower=0> N;
vector[N] y;
}
parameters {
real mu;
real<lower=0> sigma;
}
model {
y ~ normal(mu, sigma);
}
```
Comment on lines +104 to +111
Copy link
Member

@penelopeysm penelopeysm Jul 3, 2025

Choose a reason for hiding this comment

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

So I'm not a super duper expert on Stan, but I think that these parameters don't have priors assigned and thus have completely flat priors (i.e. the prior probability is always 1 for any value of mu and any value of sigma > 0). That would make this not equivalent to the Turing model which has non-flat priors. I think you would need to specify mu ~ normal(0, 1); and sigma ~ normal(0, 1); if you want to include a prior probability that is equivalent to the Turing model below it.


```julia
# Turing
@model function my_model(y)
mu ~ Normal(0, 1)
sigma ~ truncated(Normal(0, 1), 0, Inf)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
sigma ~ truncated(Normal(0, 1), 0, Inf)
sigma ~ truncated(Normal(0, 1); lower=0)

truncated(d, 0, Inf) causes issues with AD (https://turinglang.org/docs/usage/troubleshooting/#nan-gradient has an illustration of this).

y ~ Normal(mu, sigma)
end
```

## Which automatic differentiation backend should I use?

The choice of AD backend can significantly impact performance. See:
- [Automatic Differentiation Guide]({{< meta usage-automatic-differentiation >}}) - comprehensive comparison of ForwardDiff, Mooncake, ReverseDiff, and other backends
- [Performance Tips]({{< meta usage-performance-tips >}}#choose-your-ad-backend) - quick guide on choosing backends
- [AD Backend Benchmarks](https://turinglang.org/ADTests/) - 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]({{< meta usage-performance-tips >}}) and [Troubleshooting Guide]({{< meta usage-troubleshooting >}}) for debugging performance regressions.