Skip to content

fix: restore blackjax>=1.6 compatibility for nuts_sampler="blackjax" - #8373

Open
laishettikarthik-tech wants to merge 13 commits into
pymc-devs:mainfrom
laishettikarthik-tech:main
Open

fix: restore blackjax>=1.6 compatibility for nuts_sampler="blackjax"#8373
laishettikarthik-tech wants to merge 13 commits into
pymc-devs:mainfrom
laishettikarthik-tech:main

Conversation

@laishettikarthik-tech

Copy link
Copy Markdown
Contributor

blackjax 1.6 removed the progress_bar parameter from window_adaptation
(unknown kwargs now forward into the NUTS kernel and raise TypeError)
and replaced the progress_bar module (gen_scan_fn helper) with a
context-manager function.

  • Pop progress_bar from adaptation_kwargs before calling
    window_adaptation, not after
  • Dispatch on hasattr(blackjax.progress_bar, "gen_scan_fn") to support
    both the old module-based API and the new context-manager API

Closes #8367

Handle progress_bar parameter for blackjax version compatibility.
Add regression tests for blackjax progress bar compatibility.
@read-the-docs-community

read-the-docs-community Bot commented Jul 21, 2026

Copy link
Copy Markdown

Documentation build overview

📚 pymc | 🛠️ Build #33986219 | 📁 Comparing 28a0ed0 against latest (91157fd)

  🔍 Preview build  

174 files changed · + 64 added · ± 100 modified · - 10 deleted

+ Added

± Modified

- Deleted

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.23%. Comparing base (a4827f0) to head (28a0ed0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pymc/sampling/jax.py 0.00% 12 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #8373      +/-   ##
==========================================
- Coverage   91.83%   90.23%   -1.60%     
==========================================
  Files         128      128              
  Lines       21259    21268       +9     
==========================================
- Hits        19523    19192     -331     
- Misses       1736     2076     +340     
Files with missing lines Coverage Δ
pymc/sampling/jax.py 0.00% <0.00%> (-93.48%) ⬇️

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@laishettikarthik-tech

Copy link
Copy Markdown
Contributor Author

pre-commit.ci autofix

@notluquis notluquis left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirming the reproduction on a configuration not listed in #8367: blackjax 1.6.2, pymc 6.1.0, jax 0.10.2, Python 3.13, macOS arm64. The ordering bug is also present in the released 6.2.0 source, not only on main, so upgrading is not a workaround.

Applied this branch locally with the two changes suggested below. The issue's own reproduction then runs on both paths:

progressbar=False  ->  OK, posterior shape (1, 50), mean 0.1311
progressbar=True   ->  OK, warns about the missing extra and samples without the bar

The hasattr(blackjax.progress_bar, "gen_scan_fn") dispatch checks out: on 1.6.2 blackjax.progress_bar is a function rather than a module, hasattr(..., "gen_scan_fn") is False, and its signature is (label='BlackJAX', print_rate=None, output_file=None), so label="NUTS" is valid.


Separate thought, out of scope for this fix. The reason this surfaced as TypeError: build_kernel.<locals>.kernel() got an unexpected keyword argument — naming a function nobody called, from inside JAX tracing — is that window_adaptation forwards unknown **extra_parameters straight through to the algorithm. Filtering against the real signature would turn any future stale kwarg into an error at its origin:

accepted = inspect.signature(blackjax.window_adaptation).parameters
unknown = set(adaptation_kwargs) - set(accepted)
if unknown:
    raise TypeError(f"blackjax.window_adaptation does not accept {sorted(unknown)}")

A test for progress_bar specifically would not catch the next blackjax API change; this would.

Comment thread pymc/sampling/jax.py
Comment on lines +289 to +293
elif progress_bar:
# blackjax >= 1.6: progress_bar is a context manager that
# monkeypatches jax.lax.scan for its duration instead.
with blackjax.progress_bar(label="NUTS"):
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On blackjax >= 1.6 this context manager is powered by jaxtap, shipped as blackjax[progress]. On a plain blackjax==1.6.2 install it raises:

ImportError: blackjax.progress_bar requires the 'progress' optional extra.
Install it with:  pip install 'blackjax[progress]'

So test_sample_blackjax_nuts_progressbar_true will fail on any CI image that installs blackjax without the extra, and a user passing progressbar=True trades a TypeError for an ImportError. A progress bar aborting a sampling run seems worse than losing the bar, so a fallback may be preferable to adding the extra as a test dependency (warnings is already imported at line 17):

Suggested change
elif progress_bar:
# blackjax >= 1.6: progress_bar is a context manager that
# monkeypatches jax.lax.scan for its duration instead.
with blackjax.progress_bar(label="NUTS"):
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))
elif progress_bar:
try:
with blackjax.progress_bar(label="NUTS"):
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))
except ImportError:
warnings.warn(
"blackjax progress bar needs the 'progress' extra "
"(pip install 'blackjax[progress]'); sampling without it.",
UserWarning,
stacklevel=2,
)
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))

Comment thread pymc/sampling/jax.py Outdated
Comment on lines +296 to +310
return samples, stats

keys = jax.random.split(seed, draws)
if hasattr(blackjax.progress_bar, "gen_scan_fn"):
# blackjax < 1.6: progress_bar is a module exposing gen_scan_fn,
# which wraps jax.lax.scan directly.
scan_fn = blackjax.progress_bar.gen_scan_fn(draws, progress_bar)
_, (samples, stats) = scan_fn(_one_step, last_state, (jnp.arange(draws), keys))
elif progress_bar:
# blackjax >= 1.6: progress_bar is a context manager that
# monkeypatches jax.lax.scan for its duration instead.
with blackjax.progress_bar(label="NUTS"):
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))
else:
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This block repeats lines 283-295 verbatim and sits after the return samples, stats above it, so it is unreachable. Looks like a rebase artefact. It also accounts for the Codecov number — 55.6% patch coverage is roughly what you get when half the added lines cannot execute.

Deleting the added copy leaves the original return samples, stats below it, so the diff does not touch any pre-existing line.

Suggested change
return samples, stats
keys = jax.random.split(seed, draws)
if hasattr(blackjax.progress_bar, "gen_scan_fn"):
# blackjax < 1.6: progress_bar is a module exposing gen_scan_fn,
# which wraps jax.lax.scan directly.
scan_fn = blackjax.progress_bar.gen_scan_fn(draws, progress_bar)
_, (samples, stats) = scan_fn(_one_step, last_state, (jnp.arange(draws), keys))
elif progress_bar:
# blackjax >= 1.6: progress_bar is a context manager that
# monkeypatches jax.lax.scan for its duration instead.
with blackjax.progress_bar(label="NUTS"):
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))
else:
_, (samples, stats) = jax.lax.scan(_one_step, last_state, (jnp.arange(draws), keys))

@laishettikarthik-tech

Copy link
Copy Markdown
Contributor Author

The tests / all_tests failure is unrelated to this PR — it's test_mvstudentt[NUMBA] in test_random_alternative_backends.py, a numerical tolerance mismatch in the Numba multivariate Student-T sampler, nothing to do with jax.py or blackjax. All 133 other tests passed, including the three TestBlackjaxProgressBarCompat tests. Ran the blackjax-specific tests locally too (see earlier comment) — all pass cleanly.

Given it's unrelated, might this need a rerun, or is test_mvstudentt[NUMBA] a known flake?

Comment thread tests/sampling/test_jax.py Outdated
Comment on lines +556 to +558
import blackjax

from pymc.sampling.jax import _blackjax_inference_loop

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

check if local imports can be made global

@ricardoV94
ricardoV94 requested a review from junpenglao August 8, 2026 10:40
Refactor test for progress bar handling in JAX sampling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: pm.sample(nuts_sampler="blackjax") fails during warmup with every blackjax >= 1.6

3 participants