stanli
The Stan Language Interpreter. Compile and sample Stan models with no C++ toolchain on the machine.
pip install stanli
That is the whole install. No compiler, no make, no CmdStan checkout, no
multi-minute first-run build. One wheel, one shared library, under seven
megabytes.
import stanli
model = stanli.Model(stan_file="eight_schools.stan", data="data.json")
draws = model.sample(seed=1, warmup=1000, samples=1000)
draws["mu"].mean() # one numpy array of draws per constrained parameter
Model preparation takes milliseconds, so the first draw arrives about 20x sooner than a toolchain that compiles C++ per model.
How it works
Every Stan model is a composition of a fixed vocabulary of operations: densities, constraint transforms, linear algebra, elementwise math. stanli ships those precompiled and turns each model into data, a static graph of ops over flat preallocated buffers, instead of generating and compiling C++ per model.
model.stan + data.json
| stanc3, the official OCaml compiler, linked into the library
v
transformed MIR
| lowering: transformed data evaluated eagerly, data-bound loops unrolled,
| then periodic regions re-rolled back into vectorized ops
v
op graph over preallocated value/adjoint arenas
| forward sweep = log density, reverse sweep = gradient
v
NUTS with diagonal-metric adaptation -> draws
The graph doubles as the autodiff tape, so a reverse sweep is a backwards loop over an array rather than a walk through a pointer-chasing tape, and steady-state gradient evaluation allocates nothing.
Two things are not reimplemented, which is what makes the results trustworthy: the compiler is the real stanc3, linked in-process, so the Stan language behaves as the official toolchain makes it behave; and the math is unmodified stan-math, the same code CmdStan runs.
Correctness
Nothing here ships on "looks close".
118 of 120 posteriordb models are differentially verified against CmdStan: same model, same data, same evaluation point, comparing the log density and every single gradient component. 45 agree bitwise. The worst deviation across the entire corpus is 2.6e-12 relative.
The two exceptions are documented rather than hidden. sir's ODE solution
dips about 1e-9 below a declared lower bound at the shared evaluation point,
where CmdStan rejects it too; kronecker_gp matches on the log density and
436 of 438 gradients, differing on the two that flow through eigenvectors of
a nearly degenerate covariance matrix.
Full per-model accuracy table: docs/corpus-status.md
Performance
Per-gradient latency against CmdStan, same models, same evaluation point,
both sides -O3 with FP contraction pinned off:
| model | params | stanli | CmdStan | speedup |
|---|---|---|---|---|
radon_pooled |
3 | 52.9 us | 320.9 us | 6.1x |
arK |
7 | 2.4 us | 12.5 us | 5.2x |
radon_hierarchical_intercept_centered |
391 | 111.6 us | 569.1 us | 5.1x |
radon_county_intercept |
388 | 89.7 us | 431.6 us | 4.8x |
nes |
10 | 19.7 us | 69.3 us | 3.5x |
eight_schools_noncentered |
10 | 0.23 us | 0.74 us | 3.3x |
election88_full |
90 | 295.3 us | 902.0 us | 3.0x |
bym2_offset_only |
3845 | 39.6 us | 114.6 us | 2.9x |
dogs |
3 | 22.0 us | 63.7 us | 2.9x |
kidscore_momiq |
3 | 1.9 us | 4.9 us | 2.6x |
lsat_model |
1006 | 45.5 us | 91.2 us | 2.0x |
state_space_stochastic_level_stochastic_seasonal |
389 | 17.2 us | 26.3 us | 1.5x |
normal_mixture |
3 | 79.0 us | 88.2 us | 1.1x |
low_dim_gauss_mix |
5 | 88.9 us | 98.3 us | 1.1x |
wells_dist100ars_model |
3 | 17.4 us | 19.0 us | 1.1x |
radon_county |
389 | 83.2 us | 82.1 us | 1.0x |
arma11 |
4 | 6.7 us | 6.2 us | 0.93x |
diamonds |
26 | 35.4 us | 31.5 us | 0.89x |
garch11 |
4 | 11.2 us | 9.7 us | 0.86x |
hmm_drive_0 |
6 | 173.0 us | 132.8 us | 0.77x |
hmm_example |
4 | 36.3 us | 27.1 us | 0.75x |
ldaK2 |
7 | 145.9 us | 104.1 us | 0.71x |
iohmm_reg |
29 | 545.2 us | 320.3 us | 0.59x |
The wins come from op granularity. CmdStan's var tape allocates, walks, and frees one node per scalar operation per leapfrog step; stanli pays a fixed cost per op, and a vectorized statement over N elements amortizes that to nothing. Across the whole posteriordb corpus the median is
2.07x and 93 of 119 models are at or above CmdStan.The losses are honest and understood, and they are all one shape: a
recurrence. hmm_*, garch11 and arma11 step through time with each
step reading the last one's parameter-dependent result, which nothing can
vectorize, so the work is scalar on both sides and CmdStan's generated C++
is the faster way to run scalar work. ldaK2 is a mixture over more than
two components, which the fusion pass does not yet widen.
ODE models are the other place stanli is still behind. An ODE right-hand
side is the one user function that cannot be inlined at lowering time,
since the integrator picks the times; it now compiles into a flat register
machine instead of being tree-walked, and the forward sweep keeps the
sensitivities it was already computing instead of solving twice. Together
that is 29x to 39x faster than the tree-walking interpreter it replaces,
which puts lotka_volterra and soil_incubation at 0.58x and 0.63x of
CmdStan rather than 0.015x.
Method and full table: docs/benchmarks.md
API
The surface is small on purpose.
import stanli
# A path to a .stan file, or the model source directly.
model = stanli.Model(stan_file="model.stan", data="data.json")
model = stanli.Model(stan_code=src, data={"J": 8, "y": y, "sigma": sigma})
model.n_unconstrained # length of the unconstrained vector
model.constrained_names # ['mu', 'tau', 'theta.1', ...]
lp, grad = model.log_prob_grad(q) # sampling log density and its gradient
draws = model.sample(seed=1, warmup=1000, samples=1000, delta=0.8)
draws["mu"] # ndarray of length `samples`
data accepts a path to a JSON file or a dict of Python scalars, lists, and
numpy arrays. sample returns one array of constrained draws per scalar
parameter, named the way CmdStan names them, so theta declared as
vector[8] arrives as theta.1 through theta.8.
Platforms
Wheels for macOS (arm64 and x86_64) and Linux (x86_64 and aarch64, manylinux_2_28). Windows is not built yet; it needs a mingw-w64 toolchain, because stan-math does not build under MSVC.
The installed library is 22.2 MB, which is the trade this design makes: ship the compiler and every kernel once, so that nothing is ever built on the user's machine. Roughly half of that is the embedded stanc3 and somewhat under half is stan-math. The interpreter and NUTS together are about 410 KB.
Status
Early, and deliberately narrow. The sampler is Stan's own NUTS with diagonal-metric adaptation. Known limits, stated plainly:
sample()returns declared parameters only. Transformed parameters and generated quantities are computed by the runtime and written by the command line tool, but are not exposed through the Python API yet, so the non-centered eight schools gives youmu,tau, andtheta_tilde, nottheta.- No variational inference, no optimization, no multi-chain threading.
- No convergence diagnostics. Pair it with ArviZ or similar for now.
What is here is verified against CmdStan model by model, and every number on this page is reproducible from the repository.
- Source, issues, and roadmap: github.com/seantalts/stanli
- License: BSD-3-Clause, matching Stan's own.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stanli-0.4.0-py3-none-win_amd64.whl.
File metadata
- Download URL: stanli-0.4.0-py3-none-win_amd64.whl
- Upload date:
- Size: 10.3 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5417d4aaf2b656fd08f1c51d88f7d4d89a31e6b60857d051fa485d48b0d372a0
|
|
| MD5 |
562e4d69399716133876ca2f6768a69c
|
|
| BLAKE2b-256 |
029eec576254611cd887daad99af69c8d24a12cbfa8d1ee1a5da30868a8b1a42
|
Provenance
The following attestation bundles were made for stanli-0.4.0-py3-none-win_amd64.whl:
Publisher:
wheels.yml on seantalts/stanli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stanli-0.4.0-py3-none-win_amd64.whl -
Subject digest:
5417d4aaf2b656fd08f1c51d88f7d4d89a31e6b60857d051fa485d48b0d372a0 - Sigstore transparency entry: 2386687193
- Sigstore integration time:
-
Permalink:
seantalts/stanli@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/seantalts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Trigger Event:
push
-
Statement type:
File details
Details for the file stanli-0.4.0-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: stanli-0.4.0-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 9.2 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06282505174e12a29a0d9d8f96d677acd69a6cc020a23f05713985f1dae08725
|
|
| MD5 |
ea484316c3b40cf8fe94523199e2815b
|
|
| BLAKE2b-256 |
e992cbe9e9dd4838a55fd8934ba365efb011b2cb3cbe8ea1d8534a823027a313
|
Provenance
The following attestation bundles were made for stanli-0.4.0-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
wheels.yml on seantalts/stanli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stanli-0.4.0-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
06282505174e12a29a0d9d8f96d677acd69a6cc020a23f05713985f1dae08725 - Sigstore transparency entry: 2386687189
- Sigstore integration time:
-
Permalink:
seantalts/stanli@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/seantalts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Trigger Event:
push
-
Statement type:
File details
Details for the file stanli-0.4.0-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: stanli-0.4.0-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 8.8 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca14a2b03d0d238572ebeabd309828d76482872d2dd5d3a82c484503ddad4d35
|
|
| MD5 |
b2df90140ed88ec78db04e8242492a1d
|
|
| BLAKE2b-256 |
e23f8cfc2044860e334d22674adf6d1af34a3d7c6299ee2e7b0c25f7a082082b
|
Provenance
The following attestation bundles were made for stanli-0.4.0-py3-none-manylinux_2_28_aarch64.whl:
Publisher:
wheels.yml on seantalts/stanli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stanli-0.4.0-py3-none-manylinux_2_28_aarch64.whl -
Subject digest:
ca14a2b03d0d238572ebeabd309828d76482872d2dd5d3a82c484503ddad4d35 - Sigstore transparency entry: 2386687203
- Sigstore integration time:
-
Permalink:
seantalts/stanli@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/seantalts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Trigger Event:
push
-
Statement type:
File details
Details for the file stanli-0.4.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: stanli-0.4.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 7.9 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03faf9050241f00e9505d480e9ee34e130242afab1aa3a9471081cfeda5e9bdf
|
|
| MD5 |
c39848b54b485e6d5271e0a875bbc5e7
|
|
| BLAKE2b-256 |
0042f40a296a16c4da018884f94a61860c2d717b0b08af33e3aefba53c8ca03c
|
Provenance
The following attestation bundles were made for stanli-0.4.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
wheels.yml on seantalts/stanli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stanli-0.4.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
03faf9050241f00e9505d480e9ee34e130242afab1aa3a9471081cfeda5e9bdf - Sigstore transparency entry: 2386687198
- Sigstore integration time:
-
Permalink:
seantalts/stanli@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/seantalts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Trigger Event:
push
-
Statement type:
File details
Details for the file stanli-0.4.0-py3-none-macosx_10_15_x86_64.whl.
File metadata
- Download URL: stanli-0.4.0-py3-none-macosx_10_15_x86_64.whl
- Upload date:
- Size: 10.8 MB
- Tags: Python 3, macOS 10.15+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb31a3aed01e00998ddab0137fc07f12e72c565b6201db0f5c6db80eafa97f98
|
|
| MD5 |
e320722224e0a77c0ccd34685151cfc8
|
|
| BLAKE2b-256 |
4b4c40eee42e0bbaa01a687b17512a4bc89fe04f663e27bb10143dd44c9efd84
|
Provenance
The following attestation bundles were made for stanli-0.4.0-py3-none-macosx_10_15_x86_64.whl:
Publisher:
wheels.yml on seantalts/stanli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stanli-0.4.0-py3-none-macosx_10_15_x86_64.whl -
Subject digest:
fb31a3aed01e00998ddab0137fc07f12e72c565b6201db0f5c6db80eafa97f98 - Sigstore transparency entry: 2386687200
- Sigstore integration time:
-
Permalink:
seantalts/stanli@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/seantalts
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@ae4b030e8912e9fdbe6bad5ac67ad93a3bdc1986 -
Trigger Event:
push
-
Statement type: