Skip to content

fix: correct biased pymc HMC sampling (target_accept)#1908

Open
janfb wants to merge 4 commits into
mainfrom
fix/pymc-c2st-flaky
Open

fix: correct biased pymc HMC sampling (target_accept)#1908
janfb wants to merge 4 commits into
mainfrom
fix/pymc-c2st-flaky

Conversation

@janfb

@janfb janfb commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #1894 because CD is failing again. Once the lockfile fix let the pymc tests run again in CD, test_c2st_pymc_sampler_on_Gaussian failed on the hmc parametrizations with c2st around 0.62.

However, it's not flaky: pymc's HamiltonianMC defaults to target_accept=0.65, which mixes poorly on this peaked Gaussian and biases the samples, regardless of seed, warmup, or number of draws. NUTS works fine.

To fix this:

  • expose seed and target_accept on PyMCSampler
  • set target_accept=0.9 for the hmc_pymc step in MCMCPosterior to get accurate HMC posteriors (c2st ~0.51 vs ~0.62); nuts_pymc/slice_pymc unchanged
  • seed + target_accept=0.95 in the c2st test for reproducible sampling

slice does not take target_accept, so it is only applied to hmc/nuts.

Note: the c2st test is marked slow, I will run the CD on this branch via workflow dispatch.

janfb added 2 commits July 1, 2026 10:59
The hmc parametrizations of test_c2st_pymc_sampler_on_Gaussian were
failing in CD (c2st ~0.62). Root cause is PyMC's HamiltonianMC default
target_accept=0.65, which mixes poorly on this peaked target regardless
of seed, warmup, or number of draws (NUTS is unaffected).

Expose seed and target_accept on PyMCSampler and set target_accept=0.95
(c2st ~0.51-0.54 across seeds and chain counts) plus a fixed seed in the
test for reproducibility.
MCMCPosterior._pymc_mcmc left PyMC's HamiltonianMC at its default
target_accept=0.65, which produces biased hmc_pymc posteriors
(c2st ~0.62 on a Gaussian). Default it to 0.9 for the HMC step, leaving
nuts_pymc and slice_pymc unchanged. hmc_pymc now samples accurately
(c2st ~0.51).
@janfb
janfb requested a review from dgedon July 1, 2026 10:00
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 87.94%. Comparing base (5f227ac) to head (52266ba).
⚠️ Report is 17 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
sbi/inference/posteriors/mcmc_posterior.py 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1908      +/-   ##
==========================================
+ Coverage   87.81%   87.94%   +0.12%     
==========================================
  Files         143      143              
  Lines       13387    13657     +270     
==========================================
+ Hits        11756    12010     +254     
- Misses       1631     1647      +16     
Flag Coverage Δ
fast 81.35% <94.73%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sbi/inference/posteriors/posterior_parameters.py 83.45% <100.00%> (+0.36%) ⬆️
sbi/samplers/mcmc/pymc_wrapper.py 81.57% <100.00%> (+2.16%) ⬆️
sbi/inference/posteriors/mcmc_posterior.py 83.92% <87.50%> (+0.10%) ⬆️

... and 4 files with indirect coverage changes

Comment thread sbi/inference/posteriors/mcmc_posterior.py Outdated
Comment thread sbi/samplers/mcmc/pymc_wrapper.py Outdated
@janfb

janfb commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

thanks for the review @dgedon

when addressing your comments I noticed there was an inconsistency in the default values (0.9 vs 0.95 in the tests), that's fixed now. And I noticed it would be better to have the new target_accept as an argument in the PyMCWrapper, and to not have it for Pyro (unrelated).

@janfb
janfb requested a review from dgedon July 21, 2026 17:34
@janfb janfb mentioned this pull request Jul 22, 2026
9 tasks

@dgedon dgedon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe update the original description of the PR, so it reflects the latest changes, e.g. the 0.9 default.

I approve for now since all remaining comments are smaller details and not functional really. In that way you can merge easier @janfb

target_accept: Target acceptance probability for the gradient-based
samplers (HMC/NUTS). See `MCMCPosteriorParameters` for details. If
`None`, HMC uses `0.9` and NUTS/Pyro keep their backend defaults.
target_accept: Target acceptance probability for PyMC's HMC and NUTS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this always PyMC based or is it also used for exacmple for pyro?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see that you actively removed it from pyro. We should clarify then that this argument is only used for PyMC

target_accept: Target acceptance probability for the gradient-based
samplers (HMC/NUTS). If not provided, uses the value specified at
initialization.
target_accept: Target acceptance probability for PyMC's HMC and NUTS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with pymc specific comments

range_inclusive=False,
)
self._target_accept = (
0.9 if target_accept is None and step == "hmc" else target_accept

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of hard coding it as a number, we should define something like _DEFAULT_HMC_TARGET_ACCEPT = 0.9

Comment on lines +146 to +152
validate_float_range(
target_accept,
"target_accept",
min_val=0.0,
max_val=1.0,
range_inclusive=False,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a better options but worth a short comment: we now have these exact lines a few times in different places within this PR. Maybe there is a reasonable way to consolidate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants