Principle:Pyro ppl Pyro Effect Handler System
| Knowledge Sources | |
|---|---|
| Domains | Probabilistic Programming, Programming Language Theory, Algebraic Effects |
| Last Updated | 2026-02-09 09:00 GMT |
Overview
Effect handlers provide a principled mechanism for intercepting, modifying, and composing the side effects of probabilistic program statements without altering the program's source code.
Description
In probabilistic programming, a model is a generative function that invokes sample and observe statements. The semantics of these statements depend on the inference algorithm being used: during prior sampling, a sample statement draws from its prior; during variational inference, it may draw from a guide distribution; during conditioning, an observe statement contributes a log-likelihood term.
Algebraic effect handlers provide a clean abstraction for this polymorphism. The core idea, drawn from programming language theory, is to separate the declaration of an effectful operation from its interpretation. A probabilistic program declares that it performs sampling and observation effects. An effect handler wraps the program and intercepts these effects, providing context-specific behavior.
In Pyro's design, this is realized through the Messenger pattern. Each Messenger is a context manager that installs itself on a global handler stack. When a probabilistic primitive (such as pyro.sample) is called, the runtime walks the handler stack, giving each Messenger the opportunity to:
- Process the message on entry (
_pyro_samplemessage sent into the stack) - Modify the message (change distribution parameters, substitute values, mask log-probabilities)
- Post-process the message on exit (record trace entries, accumulate log-weights)
Handlers compose by stacking: an inner handler's modifications are visible to outer handlers. This composition is associative, enabling modular construction of complex inference strategies from simple, reusable components.
Key handler categories include:
- Value manipulation:
condition,substitute,replay,do-- override sampled values - Distribution manipulation:
lift,reparam,equalize-- modify the sampling distribution - Bookkeeping:
trace,guide,infer_config-- record execution metadata - Control flow:
block,escape,markov-- alter which sites are visible or how enumeration proceeds - Scaling:
scale,mask,plate,indep,subsample-- adjust log-probability contributions for data subsampling and conditional independence - Environment:
seed,uncondition,broadcast-- control randomness and broadcasting behavior
Usage
Use the effect handler system whenever you need to change the interpretation of a probabilistic program without modifying its source. Common scenarios include:
- Conditioning on observed data: Wrap the model with
conditionto fix sample sites to observed values. - Tracing execution: Wrap with
traceto record all sample sites and their values, distributions, and log-probabilities. - Composing inference algorithms: Stack multiple handlers to build SVI, importance sampling, or MCMC inference loops.
- Reparameterization: Use
reparamto transform the geometry of the posterior without changing the model. - Data subsampling: Use
plateandsubsampleto scale log-likelihood contributions for mini-batch training.
Theoretical Basis
The theoretical foundation rests on algebraic effects and handlers from type theory.
An effect signature declares operations without specifying their behavior:
# Effect signature (abstract operations)
Effect ProbProg:
sample(name, distribution) -> value
observe(name, distribution, value) -> None
A handler gives a concrete interpretation:
# Handler: provides semantics for each operation
Handler Trace(program):
trace = {}
on sample(name, dist):
value = dist.sample()
trace[name] = (dist, value)
resume(value)
on observe(name, dist, value):
trace[name] = (dist, value, dist.log_prob(value))
resume(None)
return trace
Handlers compose via stacking. Given handlers H1 and H2, the composed handler applies H1's interpretation first, then H2's:
# Composition: inner handler processes first, outer handler sees result
with H2:
with H1:
model()
# Message flow: model -> H1._process -> H2._process -> execute -> H2._postprocess -> H1._postprocess
The message-passing protocol can be formalized as:
Let M = (fn, args, value, stop, ...) be a message dictionary. For a stack of handlers [h_1, ..., h_n]:
# Upward pass (process_message):
for i in n, n-1, ..., 1:
h_i.process_message(M)
if M.stop:
break
# Apply default behavior if not stopped:
if not M.stop:
M.value = M.fn(*M.args)
# Downward pass (postprocess_message):
for i in 1, 2, ..., n:
h_i.postprocess_message(M)
This protocol guarantees that handlers are composable: any combination of handlers produces well-defined behavior, and the order of stacking determines the semantics.
Related Pages
- Implementation:Pyro_ppl_Pyro_Messenger_Base
- Implementation:Pyro_ppl_Pyro_Poutine_Handlers
- Implementation:Pyro_ppl_Pyro_Poutine_Runtime
- Implementation:Pyro_ppl_Pyro_BlockMessenger
- Implementation:Pyro_ppl_Pyro_BroadcastMessenger
- Implementation:Pyro_ppl_Pyro_ConditionMessenger
- Implementation:Pyro_ppl_Pyro_DoMessenger
- Implementation:Pyro_ppl_Pyro_EqualizeMessenger
- Implementation:Pyro_ppl_Pyro_EscapeMessenger
- Implementation:Pyro_ppl_Pyro_GuideMessenger
- Implementation:Pyro_ppl_Pyro_IndepMessenger
- Implementation:Pyro_ppl_Pyro_InferConfigMessenger
- Implementation:Pyro_ppl_Pyro_LiftMessenger
- Implementation:Pyro_ppl_Pyro_MarkovMessenger
- Implementation:Pyro_ppl_Pyro_MaskMessenger
- Implementation:Pyro_ppl_Pyro_PlateMessenger
- Implementation:Pyro_ppl_Pyro_ReparamMessenger
- Implementation:Pyro_ppl_Pyro_ReplayMessenger
- Implementation:Pyro_ppl_Pyro_ScaleMessenger
- Implementation:Pyro_ppl_Pyro_SeedMessenger
- Implementation:Pyro_ppl_Pyro_SubsampleMessenger
- Implementation:Pyro_ppl_Pyro_SubstituteMessenger
- Implementation:Pyro_ppl_Pyro_UnconditionMessenger