The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Fitting is the expensive part of most Stan analyses. flexstanr can put your machine’s spare cores to work for you from a single switch:
That is the whole user story. This article explains what it does, how to shape it (a core cap, HPC allocation), and the one thing a model author has to add so the switch behaves well on a model that cannot thread.
threading = TRUE doesA fit can use cores two ways at once:
reduce_sum() (or
map_rect), each chain can split that sum across
threads.stan_options(threading = TRUE) looks at the cores the
process is allowed to use, fills chain-parallelism
first (it is “free” – no threading overhead), gives the
leftover cores to threads, writes the result onto the options, and
tells you what it chose:
opts <- stan_options(chains = 4, threading = TRUE)
#> flexstanr: threading enabled. Using 16 of 16 available cores: 4 chains in
#> parallel, 4 threads per chain. Pass max_cores to leave some cores free.The split, given a pool of cores to divide (the pool is all your
machine’s cores, or your max_cores):
| chains | cores to split | chains in parallel | threads per chain |
|---|---|---|---|
| 4 | 4 | 4 | 1 (no spare) |
| 4 | 8 | 4 | 2 |
| 4 | 16 | 4 | 4 |
| 2 | 8 | 2 | 4 |
So threading only “switches on” when the pool has more cores than chains – otherwise every core is spent running chains. Because the default uses all your cores, an 8-core machine running 4 chains splits a pool of 8 (4 chains, 2 threads each).
You then fit as usual; fit_model() applies the
allocation to whichever backend you are on (and, for cmdstanr, compiles
the model with threading enabled):
By default threading = TRUE uses all available
cores. If your machine becomes unresponsive during a fit, cap
it with max_cores (e.g. max_cores = 4):
# use at most 4 cores, however many the machine has
stan_options(chains = 4, threading = TRUE, max_cores = 4)threading = FALSE (the default) leaves parallelism
untouched, so you can still set cores (rstan) or
parallel_chains / threads_per_chain (cmdstanr)
by hand if you want full manual control.
It asks parallelly::availableCores()
for the number of cores the process is allowed to use –
crucially not parallel::detectCores(). It
respects
SLURM_CPUS_PER_TASK,
PBS, SGE, LSF),options(mc.cores = ...),and returns 2 under R CMD check. This makes
threading = TRUE safe to use even on a shared cluster.
Within-chain threads only do anything if the model was compiled with threading:
fit_model() compiles with
cpp_options = list(stan_threads = TRUE) automatically
when the allocation asks for more than one thread, into a separate cache
directory so a threaded and a non-threaded build coexist. Nothing to
do.STAN_NUM_THREADS around the fit. If a model was not
built for threading, the setting is inert – which is exactly what
test_threaded() lets you warn about.Nothing to do: threading = TRUE detects your cores and
splits them, using all of them. To leave some free, use
max_cores, or cap globally with mc.cores:
parallel::detectCores() reports every
core on the compute node (often 64, 128+) regardless of your job’s
allocation. A fit sizing itself from that would launch dozens of threads
inside a job pinned to a handful of cores; the scheduler’s cgroup would
throttle it to a crawl. Because threading = TRUE uses
parallelly::availableCores(), it reads your
allocation instead.
Request one task with several CPUs and let
threading = TRUE divide them. For 4 chains x 2 threads (8
cores):
Chain-parallelism and threads are shared-memory: they cannot span nodes. To scale beyond a single node, run one chain per job with a SLURM array, give each job all its cores for threading, and combine the single-chain fits after.
#SBATCH --array=1-4 # one task per chain
#SBATCH --nodes=1
#SBATCH --cpus-per-task=8 # all 8 cores -> threads for this one chaini <- Sys.getenv("SLURM_ARRAY_TASK_ID")
opts <- stan_options(chains = 1, seed = as.integer(i), threading = TRUE)
fit <- fit_model("my_model", dat_stan = dat, init = init, stan_opts = opts)
saveRDS(fit, sprintf("chain-%s.rds", i))stan_options() is otherwise for how the sampler explores
(iter, warmup, seed,
control = list(adapt_delta = ...), …);
threading is the one knob that reaches out to the
machine.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.