Skip to content

Enable free threaded python support - #159

Open
vlad-perevezentsev wants to merge 30 commits into
masterfrom
enable-free-threaded-python
Open

Enable free threaded python support#159
vlad-perevezentsev wants to merge 30 commits into
masterfrom
enable-free-threaded-python

Conversation

@vlad-perevezentsev

@vlad-perevezentsev vlad-perevezentsev commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

This PR proposes enabling free-threaded Python support in mkl_random and makes the RNG safe for concurrent use

  • Compile mklrand with # cython: freethreading_compatible=True so importing the package no longer re-enables the GIL.
  • Require Cython>=3.1.0 (pyproject.toml, conda recipes, README) and add Free Threading :: 2 - Beta classifier
  • Drop the python-gil pin from the conda recipes.
  • Add a free-threading test and 3.14t / *_cp314t variants to the GH workflow matrices
  • Serialize every MKL stream access under the per-instance lock (default size=None draws, multinomial, multinormal_cholesky, shuffle, seed, randint helpers), fix re-entrancy deadlocks and a get_state heap overflow on concurrent BRNG change

Additionally, fixed memory leaks in set_state (4a0fb51) and logseries (548d625)

Comment thread mkl_random/mklrand.pyx
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# cython: language_level=3
# cython: freethreading_compatible=True

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.

We need to make changes in various places in this file, there are several areas where we are likely creating race conditions. Some that were apparent in review and with some thinking: multinomial, leapfrog, skipahead, multinormal_cholesky, and the two randint_untyped branches for scalars

For example, we need with self.lock: wrapping irk_multinomial_vec in multinomial

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.

We likely need more changes throughout the file

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@ndgrigorian great catch, I missed these
You are right, they race without self.lock
Fixed, thanks!

@antonwolfy antonwolfy added this to the 1.6.0 release milestone Sep 4, 2026
Comment thread mkl_random/tests/test_freethreading.py Outdated

def test_concurrent_sampling_per_instance():
# Each thread owns a private MKLRandomState seeded identically, so the
# per-instance lock + `nogil` sampling must reproduce the single-threaded

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.

Each thread builds its own MKLRandomState — no shared state, so it passes with or without the per-instance lock. Fine as an instance-independence smoke test, but the docstring's "exercises the per-instance lock" claim is misleading.

Comment thread mkl_random/tests/test_freethreading.py Outdated
k, rounds, seed = 32, 20, 777
rs = mkl_random.MKLRandomState(seed)
rs.seed(seed)
ref = Counter(repr(call(rs)) for _ in range(k))

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.

For the multinomial and mvn_cholesky params repr truncates precision, weakening race detection for those two

Comment thread mkl_random/tests/test_freethreading.py Outdated
Comment on lines +115 to +116
assert r.shape == (size,)
assert np.all(np.isfinite(r))

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.

Assertions are too weak. A data race that interleaves stream updates still produces finite floats in [0,1), so the corruption passes.

Comment thread mkl_random/tests/test_freethreading.py Outdated
np.testing.assert_array_equal(r, expected)


def test_concurrent_shared_singleton():

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.

Not FT-gated. On a GIL build it runs as a pure no-op smoke test (the GIL serializes it anyway), giving false confidence.

Comment thread mkl_random/tests/test_freethreading.py Outdated
np.testing.assert_array_equal(r, expected)


def test_concurrent_shared_singleton():

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.

No barrier — unlike _draw_concurrently, the threads aren't released together, so contention on the stream-touching region is minimal and the race window is barely exercised.

Comment thread mkl_random/mklrand.pyx Outdated

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.

operator.index(seed), np.asarray, astype can run user __index__/__array__ that re-enters the generator → self-deadlock

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.

3 participants