Skip to content

BitGenerator support - #499

Open
flying-sheep wants to merge 66 commits into
PyO3:mainfrom
flying-sheep:pa/bitgen
Open

BitGenerator support#499
flying-sheep wants to merge 66 commits into
PyO3:mainfrom
flying-sheep:pa/bitgen

Conversation

@flying-sheep

@flying-sheep flying-sheep commented Jun 6, 2025

Copy link
Copy Markdown
Contributor

See

Fixes #498

The idea is to have a safe wrapper around the npy_bitgen struct that implements rand::RngCore. That way pyo3 functions could be passed a np.random.Generator, get that wrapper from it, and pass it to Rust APIs, which could then call its methods repeatedly.

The way it’s implemented, the workflow would look like this:

  1. acquire GIL
  2. cast a np.random.BitGenerator instance into a numpy::random::PyBitGenerator.
  3. call .lock() on it to get a numpy::random::PyBitGeneratorGuard.
  4. release GIL
  5. call functions on guard object without needing to hold the GIL

TODO:

  • I see local crashes when running all tests, so there’s probably some UB, I’d appreciate help to fix it.

Safety

If somebody releases the threading lock of the BitGenerator while we’re using it, this isn’t safe 🤔

API design options

I could make this more complex by adding a new trait that is implemented by both PyBitGenerator and PyBitGeneratorGuard, allowing to choose if someone wants to

  • use the PyBitGenerator’s random_* methods directly on that object while holding the GIL and without locking it
  • use it like it’s used now, by locking the np.random.BitGenerator and returning a GIL-free object that can be used.

but for now I just implemented the use case that’s actually desired.

@flying-sheep flying-sheep changed the title BItGenerator support BitGenerator support Jun 6, 2025
@flying-sheep
flying-sheep marked this pull request as ready for review June 8, 2025 12:44

@Icxolu Icxolu left a comment

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.

This looks like a useful addition! Thanks for working on it. I'm definitely not an expert here, but I left a few comment about things that stood out to me. Let me know what you think.
Also, are there any differences between numpy v1 and v2 that we need to consider?

Comment thread .vscode/settings.json Outdated
Comment thread src/random.rs Outdated
Comment thread src/random.rs Outdated
Comment thread src/random.rs Outdated
Comment thread src/random.rs Outdated
Comment thread src/random.rs Outdated
Comment thread src/random.rs Outdated
@flying-sheep

flying-sheep commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

OK, done.

UB fixed

turns out the BitGenerator itself has allocated memory, not just the _capsule, so letting that be freed was the bug. Now we bump its refcount and keep it around until we’re done.

double locking issue fixed

@Icxolu as you found in #499 (comment), the lock is re-entrant, so now we just keep a HashSet of locked bitgens so same-thread guards are unique.

PS: MSRV is 1.83, and in 1.84 ptr.addr() was stabilized, which would be a slightly better API than as usize here

@flying-sheep

flying-sheep commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

And done! Please take a look!

@Icxolu I followed your initial advice and converted it from a lock() -> Guard API into a lock(|guard| ...) API, getting rid of the silently-fallible Drop implementation.

I have gathered some experience using numpy’s generators and figured that using .spawn_one() or .spawn(n) to get more freedom here is totally acceptable.

outstanding questions & tasks

  • naming: we have PyBitGenerator for the maybe-shared one and BitGenerator for the exclusive access one (&mut when locked, by value when owned by us). These names could be better I think.
  • add new()/from_* functions. With the new API it/they would return a owned-by-us BitGenerator (BitGenerator support #499 (comment)) could maybe be combined with this, and change the BitGenerator so it can hold one of multiple backends
  • finally: remove .vscode stuff (BitGenerator support #499 (comment))

@Icxolu

Icxolu commented Jul 23, 2026

Copy link
Copy Markdown
Member

Thanks for picking this back up! It might take me a bit to get my head back into this, but I will try to give this a read as soon as I find some time.

@flying-sheep

Copy link
Copy Markdown
Contributor Author

I’m quite happy with it now :D

Only caveat (apart from naming): There’s a lot of nit-picking possible with the from_numpy API which is why I initially didn’t implement any factory methods. If you want changes there, maybe I’ll just remove that part and you can implement it however you want, or you tell me exactly what you want and I do it.

Comment thread src/random.rs
Comment on lines +309 to +311
pub fn into_shared(self) -> Py<PyBitGenerator> {
self._bit_generator
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

could alternatively be an Into implementation.

@Icxolu Icxolu left a comment

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.

I gave this a first pass. For now I mostly looked at the technical stuff and did not really think hard about naming etc. Unfortunately I think there is still a potential soundness issue with lock, see that comment for more details. I believe spawn does not have the same issue, so even if we can't get lock working, this one should be possible.

Comment thread src/random.rs Outdated
Comment thread src/random.rs Outdated
Comment thread src/random.rs
Comment thread src/random.rs Outdated
Comment thread src/random.rs
@flying-sheep
flying-sheep requested a review from Icxolu August 2, 2026 14:49
Comment thread src/random.rs Outdated
Comment thread src/random.rs
Comment thread src/random.rs Outdated
@flying-sheep
flying-sheep requested review from Icxolu and mejrs August 22, 2026 14:14
@flying-sheep

Copy link
Copy Markdown
Contributor Author

OK, all addressed!

I removed the 3.9 gating as asked, so CI fails now, maybe I should put it back until the CI for 3.8 is removed?

To have BitGenerator::new be more complete, we need to think about wrapping SeedSequence as well, but we could also leave that for a future PR and just add new with_seed factory methods then, keeping new minimal.

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.

rand::RngCore implementation for numpy.random.Generator

4 participants