Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Principle:Pyro ppl Pyro Effect Handler System

From Leeroopedia


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:

  1. Process the message on entry (_pyro_sample message sent into the stack)
  2. Modify the message (change distribution parameters, substitute values, mask log-probabilities)
  3. 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 condition to fix sample sites to observed values.
  • Tracing execution: Wrap with trace to 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 reparam to transform the geometry of the posterior without changing the model.
  • Data subsampling: Use plate and subsample to 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

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment