MCMC¶
Markov-chain Monte Carlo: the HMC
and NUTS kernels, the
MCMC runner, and the
MCMCResult summary.
The runner targets a MonadicProgram. Models that declare every latent
as a sample site need no conversion. For models with nn.Parameters
or intermediate latent sites, the
bayesian_lift_parameters
lift returns a 3-tuple (MonadicProgram, torch.Tensor, dict[str, torch.Tensor]),
whose first element is the matching MonadicProgram. Callers pass
that first element to the runner.
quivers.inference re-exports the kernels, the runner, and its result;
quivers.inference.mcmc also exports the warmup adaptation primitives
and the type aliases that parameterize the kernels and the runner.
mcmc
¶
MCMC kernels, adaptation primitives, and the chain driver.
Public surface (the kernels and driver are also re-exported from
quivers.inference):
MCMCKernel: ABC for Markov kernels on the flat unconstrained latent vector, withKernelStateas its per-chain state andPotentialFnas the potential it evaluates.HMCKernel: Hamiltonian Monte Carlo with leapfrog integration, dual-averaging step-size adaptation, and Welford mass-matrix adaptation, parameterized by aMassMatrixKind.NUTSKernel: No-U-Turn Sampler with multinomial sampling.DualAveraging,WelfordCovariance, andfind_reasonable_step_size: the warmup adaptation primitives the kernels share.MCMC: chain orchestrator with warmup, parallel chains, anInitStrategy, and posterior diagnostics (split-:math:\hat R, effective sample size).MCMCResult: posterior samples and per-chain diagnostics.
InitStrategy
module-attribute
¶
How MCMC chooses each chain's initial point in the unconstrained
latent space: "prior" draws a small seeded standard-normal
perturbation of the origin, "zero" starts at the origin, and
"guide" maps one draw of a fitted guide into the unconstrained
space.
MassMatrixKind
module-attribute
¶
The mass-matrix structure an HMC or NUTS kernel adapts during warmup:
"identity" keeps the unit metric, "diagonal" adapts per-coordinate
variances, and "dense" adapts a full covariance.
DualAveraging
¶
DualAveraging(initial_step_size: float, target_accept: float = 0.8, gamma: float = 0.05, t0: float = 10.0, kappa: float = 0.75)
Nesterov dual averaging for adaptive step-size selection.
Maintains a running smoothed estimate of the log-step-size that
drives the chain's average acceptance probability toward the
user-supplied target_accept.
Algorithm parameters follow Hoffman-Gelman 2014 (Algorithm 6):
:math:\gamma = 0.05, :math:t_0 = 10, :math:\kappa = 0.75.
These are the defaults used by Stan.
| PARAMETER | DESCRIPTION |
|---|---|
initial_step_size
|
Starting step size. The first proposal uses
TYPE:
|
target_accept
|
Target Metropolis acceptance probability. Default
TYPE:
|
gamma
|
Adaptation regularisation. Default
TYPE:
|
t0
|
Stabilisation offset. Default
TYPE:
|
kappa
|
Decay exponent for the smoothed step size. Default
TYPE:
|
Source code in src/quivers/inference/mcmc/adapt.py
step_size
¶
smoothed_step_size
¶
update
¶
Update the step size given the latest Metropolis acceptance probability.
Source code in src/quivers/inference/mcmc/adapt.py
WelfordCovariance
¶
Welford-style online covariance accumulator.
Tracks the running mean and (co)variance of a stream of
D-dimensional vectors. Supports both diagonal and dense
forms: diagonal is sufficient for ill-scaled axis-aligned
posteriors, dense needed when off-axis correlations matter.
The dense form follows the standard pairwise update:
.. math::
M_n = M_{n-1} + (x_n - \bar{x}_{n-1})(x_n - \bar{x}_n)^\top
with :math:\bar{x}_n = \bar{x}_{n-1} + (x_n - \bar{x}_{n-1}) / n.
| PARAMETER | DESCRIPTION |
|---|---|
dim
|
Vector dimension.
TYPE:
|
regularise
|
Apply Stan's regularisation
:math:
TYPE:
|
diagonal
|
Track only the diagonal of the covariance (vector, not
matrix). Default
TYPE:
|
Source code in src/quivers/inference/mcmc/adapt.py
update
¶
update(x: Tensor) -> None
Fold one vector into the running statistics.
Source code in src/quivers/inference/mcmc/adapt.py
covariance
¶
covariance() -> Tensor
Return the empirical covariance (regularised if configured).
Source code in src/quivers/inference/mcmc/adapt.py
reset
¶
Discard accumulated statistics.
MCMC
¶
MCMC(kernel: MCMCKernel, num_warmup: int, num_samples: int, num_chains: int = 4, init_strategy: InitStrategy = 'prior', *, seed: int | None = None, generator: Generator | None = None)
MCMC chain runner.
| PARAMETER | DESCRIPTION |
|---|---|
kernel
|
Markov kernel (e.g.
TYPE:
|
num_warmup
|
Number of adaptation steps. The kernel's adaptation machinery (dual averaging, Welford covariance) runs over this prefix.
TYPE:
|
num_samples
|
Post-warmup samples per chain.
TYPE:
|
num_chains
|
Independent chains. Default
TYPE:
|
init_strategy
|
How to pick each chain's initial position.
TYPE:
|
seed
|
Base seed used to derive an independent local generator for
each chain. Repeated calls to
TYPE:
|
generator
|
Caller-owned CPU generator used to draw one base seed per
TYPE:
|
Source code in src/quivers/inference/mcmc/driver.py
run
¶
run(model: MonadicProgram, x: Tensor, observations: dict[str, Tensor], guide: Guide | None = None) -> MCMCResult
Run the configured kernel for num_chains chains of
num_warmup + num_samples steps each.
Source code in src/quivers/inference/mcmc/driver.py
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
MCMCResult
dataclass
¶
MCMCResult(samples: dict[str, Tensor], log_densities: Tensor, acceptance_rates: Tensor, divergence_counts: Tensor, r_hat: dict[str, Tensor], ess: dict[str, Tensor], num_warmup: int, num_samples: int)
Posterior samples and per-chain diagnostics.
| ATTRIBUTE | DESCRIPTION |
|---|---|
samples |
Per-site posterior draws on the constrained support.
Shape
TYPE:
|
log_densities |
Unconstrained-space log-density (Jacobian-corrected) at
every posterior draw. Shape
TYPE:
|
acceptance_rates |
Per-chain post-warmup acceptance rate. Shape
TYPE:
|
divergence_counts |
Per-chain post-warmup divergence count. Shape
TYPE:
|
r_hat |
Per-site rank-normalized folded split-:math:
TYPE:
|
ess |
Per-site rank-normalized bulk effective sample size pooled
across chains. Same shape convention as
TYPE:
|
num_warmup |
TYPE:
|
num_samples |
TYPE:
|
HMCKernel
¶
HMCKernel(step_size: float = 0.1, num_steps: int = 10, mass_matrix: MassMatrixKind = 'identity', target_accept: float = 0.65, divergence_threshold: float = 1000.0, adapt_step_size: bool = True, adapt_mass_matrix: bool = True)
Bases: MCMCKernel
Hamiltonian Monte Carlo kernel with fixed trajectory length.
| PARAMETER | DESCRIPTION |
|---|---|
step_size
|
Leapfrog step size. Adapted during warmup when
TYPE:
|
num_steps
|
Leapfrog steps per proposal.
TYPE:
|
mass_matrix
|
Mass-matrix shape. Diagonal / dense are adapted during warmup from the empirical covariance of warmup samples.
TYPE:
|
target_accept
|
Target Metropolis acceptance for dual averaging. Default
TYPE:
|
divergence_threshold
|
Energy-error threshold for marking a proposal as divergent. Divergent steps still respect Metropolis correctness but are reported separately so the user can spot pathological regions.
TYPE:
|
Source code in src/quivers/inference/mcmc/hmc.py
NUTSKernel
¶
NUTSKernel(step_size: float = 0.1, max_tree_depth: int = 10, mass_matrix: MassMatrixKind = 'diagonal', target_accept: float = 0.8, divergence_threshold: float = 1000.0, adapt_step_size: bool = True, adapt_mass_matrix: bool = True)
Bases: MCMCKernel
No-U-Turn Sampler with multinomial sampling and the standard U-turn termination (Hoffman-Gelman 2014 algorithms 3 + 6, Betancourt 2017's generalized slice variant for multinomial sampling).
| PARAMETER | DESCRIPTION |
|---|---|
step_size
|
Initial leapfrog step size; adapted via dual averaging.
TYPE:
|
max_tree_depth
|
Maximum tree doubling depth. Default
TYPE:
|
target_accept
|
Target tree-averaged Metropolis acceptance for dual
averaging. Default
TYPE:
|
divergence_threshold
|
Energy-error threshold above which a leapfrog substep is marked divergent and terminates the tree on its branch.
TYPE:
|
Source code in src/quivers/inference/mcmc/hmc.py
KernelState
dataclass
¶
KernelState(position: Tensor, log_density: Tensor, grad_log_density: Tensor, step_count: int = 0, accept_count: int = 0, diverged: bool = False, extras: dict = dict())
Mutable container for one chain's MCMC state.
| ATTRIBUTE | DESCRIPTION |
|---|---|
position |
Current flat unconstrained latent vector. Shape
TYPE:
|
log_density |
Unconstrained-space log-density (Jacobian-corrected
log-joint) at
TYPE:
|
grad_log_density |
Gradient of
TYPE:
|
step_count |
Number of
TYPE:
|
accept_count |
Number of proposals accepted across the chain. Useful for reporting acceptance rate.
TYPE:
|
diverged |
Whether the most recent step's energy error exceeded the kernel's divergence threshold. Reset by each kernel as appropriate.
TYPE:
|
extras |
Per-kernel additional state (e.g. NUTS tree depth, HMC step-size adaptation cumulants).
TYPE:
|
MCMCKernel
¶
Bases: ABC
Abstract Markov kernel on the flat unconstrained latent vector.
Concrete subclasses implement init and step.
Adaptation phases (warmup) typically mutate kernel-internal
state (step size, mass matrix) and freeze it for the sampling
phase; the kernel's is_adapting flag tracks that.
init
abstractmethod
¶
init(registry: LatentRegistry, model: MonadicProgram, x: Tensor, observations: dict[str, Tensor], initial_position: Tensor) -> KernelState
Build the starting KernelState from the supplied
initial flat unconstrained vector. The initial gradient is
evaluated here so step can re-use it.
Source code in src/quivers/inference/mcmc/kernel.py
step
abstractmethod
¶
step(state: KernelState, potential: PotentialFn) -> KernelState
Advance the chain one Metropolis step. The potential
function is constructed once per MCMC.run and
re-used across every step / chain.
Source code in src/quivers/inference/mcmc/kernel.py
start_adaptation
¶
PotentialFn
¶
PotentialFn(model: MonadicProgram, registry: LatentRegistry, x: Tensor, observations: dict[str, Tensor])
Callable that maps a flat unconstrained position to the unconstrained-space negative log-density and its gradient.
HMC and NUTS need both the potential
:math:U(z) = -\log \tilde{p}(z) (where
:math:\tilde{p}(z) = p(T(z), y) \cdot |\det J_T(z)| is the
Jacobian-corrected unconstrained-space joint) and its gradient
:math:\nabla U(z). The two are computed in a single
autograd pass and cached on the kernel state.
| PARAMETER | DESCRIPTION |
|---|---|
model
|
Generative model.
TYPE:
|
registry
|
Latent-site registry for
TYPE:
|
x
|
Model input. Shape
TYPE:
|
observations
|
Observed-site values and host data.
TYPE:
|
Source code in src/quivers/inference/mcmc/kernel.py
log_density
¶
Unconstrained-space log-density (Jacobian-corrected).
Trajectories that wander to the edge of a constrained
support can produce values that fall outside
torch.distributions' validation envelope (e.g. exact
zeros against a strictly-positive support after a long
leapfrog stride). Rather than letting the resulting
ValueError propagate and kill the chain, this method
returns -inf for those positions; the kernel reads
non-finite log-densities as divergent transitions and
rejects them in the Metropolis step.
Source code in src/quivers/inference/mcmc/kernel.py
value_and_grad
¶
Return (log_density, grad_log_density) for z.
z is expected to be a detached tensor; we make a fresh
leaf with requires_grad=True so gradient propagation
doesn't leak into the kernel's accumulated state.
For divergent positions (where the log-density is
-inf), returns a zero gradient: the kernel rejects
the trajectory in the Metropolis step anyway, and a zero
gradient keeps the leapfrog integrator from producing NaN
downstream.
Source code in src/quivers/inference/mcmc/kernel.py
find_reasonable_step_size
¶
find_reasonable_step_size(log_density: Callable[[Tensor], Tensor], grad_log_density: Callable[[Tensor], Tensor], z0: Tensor, initial_step: float = 1.0, target_log_accept: float = log(0.5)) -> float
Heuristic-by-doubling initial step size (Hoffman-Gelman 2014 Algorithm 4).
Starting from initial_step, doubles or halves the step size
until a single leapfrog step's Metropolis log-acceptance crosses
target_log_accept (default :math:\log 0.5). Returns the
step size at the crossing.
Source code in src/quivers/inference/mcmc/adapt.py
Variational warmup¶
WarmupThenHMC fits a guide with SVI and starts the chains from it.
warmup
¶
Run variational warmup before HMC or NUTS.
The orchestrator fits a guide with SVI, initializes the Markov chain from the guide, adapts the mass matrix from its covariance, and runs the configured MCMC driver.
WarmupThenHMC
¶
WarmupThenHMC(guide: Guide, kernel: MCMCKernel, svi_steps: int, mcmc_warmup: int, mcmc_samples: int, num_chains: int = 2, svi_lr: float = 0.01, objective: Objective | None = None)
Composite sampler: SVI warmup followed by HMC / NUTS chain.
| PARAMETER | DESCRIPTION |
|---|---|
guide
|
Variational guide trained in the SVI warmup phase. Any
TYPE:
|
kernel
|
Markov kernel for the sampling phase (typically
TYPE:
|
svi_steps
|
Number of SVI steps in the warmup phase.
TYPE:
|
svi_lr
|
Learning rate for the SVI optimizer. Default
TYPE:
|
mcmc_warmup
|
MCMC kernel's own warmup (on top of the SVI warmup; HMC still benefits from a few step-size-adaptation iterations because the SVI-mean's typical-set position differs from the maximum-density point).
TYPE:
|
mcmc_samples
|
Number of post-warmup MCMC samples per chain.
TYPE:
|
num_chains
|
Number of MCMC chains.
TYPE:
|
objective
|
SVI objective. Default
TYPE:
|
Source code in src/quivers/inference/warmup.py
fit_guide
¶
fit_guide(model: MonadicProgram, x: Tensor, observations: dict[str, Tensor]) -> list[float]
Run the SVI warmup phase and return the per-step losses.
Source code in src/quivers/inference/warmup.py
run_mcmc
¶
run_mcmc(model: MonadicProgram, x: Tensor, observations: dict[str, Tensor]) -> MCMCResult
Run the post-warmup MCMC phase. Chains are seeded from
the fitted guide via MCMC.run's init_strategy='guide'.
Source code in src/quivers/inference/warmup.py
run
¶
run(model: MonadicProgram, x: Tensor, observations: dict[str, Tensor]) -> tuple[list[float], MCMCResult]
Convenience: SVI warmup, then MCMC sampling.
Returns (svi_losses, mcmc_result).