Make Gibbs kernels composable - #2249
Conversation
Add a composite `Gibbs` kernel with `CustomGibbs` and `DiscreteGibbs` block kernels, opt-in `refresh`/`wrap_model`/`get_constrain_fn` hooks on `MCMCKernel`, and public `HMC` accessors. `HMCGibbs` and `DiscreteHMCGibbs` become facades over `Gibbs`; `HMCECS` and `MixedHMC` are standalone wrappers built on the public accessors. Fixes `HMCECS` failing on `warmup()` followed by `run()`, reused RNG keys, and the dead stay branch of the modified random-walk proposal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Benchmark reportthis PR - run time: 1 slower, 0 faster
compile time: unchanged across 32 benchmarksSignificant changes (1) ──────── run time ─────── ───── compile time ─────
benchmark baseline this PR Δ baseline this PR Δ
───────────────────────────────────────────────────────────────────────────────
- categorical_log_prob 2.0 ms 2.3 ms +10.5% 71.2 ms 73.8 ms +3.7%Red is slower, green is faster; a row is coloured by the worse of its two columns. A delta in parentheses cleared the threshold on a measurement below the resolution floor, so it is shown without being called a change. † marks a benchmark that could not be compared — see below. Full results
|
| baseline | this PR | |
|---|---|---|
| ref | master |
composable-gibbs |
| commit | 999d8d1f |
a231cc86 |
| numpyro | 0.21.0 | 0.21.0 |
| jax | 0.11.1 | 0.11.1 |
| backend | cpu | cpu |
| python | 3.14.7 | 3.14.7 |
Runner: Linux-6.17.0-1022-azure-x86_64-with-glibc2.39, 4 CPUs.
Produced by this benchmark run.
… modules - Fix TracerArrayConversionError in DiscreteGibbs.init and MixedHMC.init under pmap: ravel_pytree stages a jnp.concatenate even on constant inputs inside pmap's staging trace, so np.asarray of its result raised on CI where the upgraded JAX exposes multiple CPU devices by default. The flat support sizes are now built with pure numpy (_flat_support_sizes, ravel_pytree leaf order), keeping them static on the kernel. Adds a device-independent regression test (single-chain pmap over init/sample for DiscreteGibbs, MixedHMC, DiscreteHMCGibbs) and runs test_gibbs.py -k chain under the forced multi-device CI step. Note: the stored dtype changes int32 -> int64 (benign, consumers re-wrap with jnp.asarray). - Merge numpyro/infer/gibbs_util.py into numpyro/infer/gibbs.py (helpers first, discrete proposals next to DiscreteGibbs); update importers and doc refs. - Add _typing.py, infer/gibbs.py, infer/hmc.py, infer/hmc_gibbs.py, infer/mcmc.py, infer/mixed_hmc.py to the ty include list and fix all 47 diagnostics: widen numpyro.util.cond's pred annotation to bool | jax.Array, drop the None sentinel of Gibbs._sites in favor of (), assert-narrow lazily initialized attributes, rename HMC.postprocess_fn params to match the base class, and swap mixed_hmc's deprecated 5-arg lax.cond call for numpyro's own cond wrapper (also honors NUMPYRO_DISABLE_CONTROL_FLOW_PRIM). make lint now type-checks every module this PR touches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a231cc8 addressing the CI failures plus two structural follow-ups:
|
|
@fehiepsi @Qazalbash I think this one si ready for review 🤗 |
Implements the design in #2243 for #898.
Summary
Gibbs([(kernel, sites), ...])composite kernel plusCustomGibbsandDiscreteGibbsblock kernels (numpyro/infer/gibbs.py,gibbs_util.py). Blocks can be HMC/NUTS,DiscreteGibbs,CustomGibbs,MixedHMCor a nestedGibbs.MCMCKernel.refresh/wrap_model/get_constrain_fnhooks and publicHMC.get_potential_fn/get_constrain_fn/refresh/wrap_model, so no kernel reads another kernel's private attributes.HMCGibbsandDiscreteHMCGibbsare thin facades overGibbs(constructors, behavior andextra_fields=["hmc_state.*"]unchanged);HMCECSandMixedHMCare standalone wrappers on the public accessors.HMCECSno longer fails onwarmup()followed byrun()(model is wrapped once at construction); unused/reused RNG keys; the dead stay branch of the modified random-walk proposal;init_paramsis no longer mutated.Deviations from the design doc (found during implementation)
MCMCKernel.get_constrain_fn, is needed so the composite can condition siblings on any block's constrained values (the design only had it onHMC).changed-gated refresh is done at two points per sweep (before a block, on earlier siblings; after the sweep, on later siblings), which keeps it state-free and sound._prepare_model_for_potential(..., enum=)is explicit andDiscreteGibbspassesenum=topotential_energy, otherwiseenumerate="parallel"sites give wrong energies and every discrete block would import funsor.Gibbs(its constrain function would need the siblings' values, which is circular).HMCECSwas decoupled before the facades, since it subclassedHMCGibbs.Notes for reviewers
DiscreteHMCGibbsandHMCECSdocstring assertions were loosened from 0.1 to 0.2: the refactor changes the RNG streams, and across 8 to 10 seeds the seed-to-seed sd of the asserted means is 0.07 (GMM) and 0.1 to 0.14 (ECS) on master and this branch alike, with no bias.HMCGibbsStateis now aGibbsStatesubclass (z, block_states, rng_key) with anhmc_stateproperty; positional construction of the old 3-tuple no longer works.test/infer/test_gibbs.pyandtest/infer/test_kernel_hooks.py; existing Gibbs/ECS/pickle tests pass.