-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Numpy 2.x (take 2) #442
Conversation
By default, the extension is compile with support for numpy 1 and 2 (with runtime checks to pick the right binary offset where needed). Features or fields that are specific to a version are hidden by default. Users can opt-out of numpy 1 + numpy 2 by disabling default features and selecting a version. The library panics if the runtime version does not match the compilation version if only one version is selected.
Contributed by @stinodego
…n bounds for the MSRV build.
Thank you! @adamreichold do you expect to have capacity to review this in the near future, or should I try to find some time? |
gentle ping @adamreichold @davidhewitt - do you have capacity to review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. I had kept this on my TODO list having not seen a reply here from @adamreichold, but sickness has slowed me down all over the place.
Overall this looks great to me, and it follows all the comments from @adamreichold's review. Thanks very much for moving things along.
I have just a couple of small changes I'd like to see, focussing on the version-specific FFI functions. Given they panic at runtime, I think we should expose a helper is_numpy_2()
function so that users can check before calling (and triggering the panic).
Once those are done, if we don't hear from @adamreichold before, I am happy to merge this and carry on with the 0.22 release steps.
- name: Edit Cargo.toml and enable new resolver | ||
run: | | ||
import toml | ||
cargo_toml = toml.load("Cargo.toml") | ||
cargo_toml["package"]["resolver"] = "2" | ||
with open("Cargo.toml", "w") as f: | ||
toml.dump(cargo_toml, f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A separate task, I think we can bump MSRV and remove this.
src/npyffi/mod.rs
Outdated
API_VERSION.get_or_init(py, || unsafe { | ||
#[allow(non_snake_case)] | ||
let PyArray_GetNDArrayCFeatureVersion = api.offset(211) as *const extern "C" fn() -> c_uint; | ||
(*PyArray_GetNDArrayCFeatureVersion)() | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't work if we happen to load the ufunc API first here (we get a nasty crash).
I would prefer to see this moved into a new public function:
API_VERSION.get_or_init(py, || unsafe { | |
#[allow(non_snake_case)] | |
let PyArray_GetNDArrayCFeatureVersion = api.offset(211) as *const extern "C" fn() -> c_uint; | |
(*PyArray_GetNDArrayCFeatureVersion)() | |
}); | |
fn is_numpy_2() -> bool { | |
API_VERSION.get_or_init(py, || unsafe { PY_ARRAY_API.PyArray_GetNDArrayCVersion()}) >= API_VERSION_2_0 | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I changed places where we check the version to use this, and re-ordered the conditionals so that we always check if is_numpy_2(py)
.
src/npyffi/mod.rs
Outdated
types::{PyAnyMethods, PyCapsule, PyCapsuleMethods, PyModule}, | ||
PyResult, Python, | ||
}; | ||
|
||
pub const API_VERSION_2_0: c_uint = 0x00000012; | ||
|
||
pub static API_VERSION: GILOnceCell<c_uint> = GILOnceCell::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should not make the direct cache public:
pub static API_VERSION: GILOnceCell<c_uint> = GILOnceCell::new(); | |
static API_VERSION: GILOnceCell<c_uint> = GILOnceCell::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/npyffi/mod.rs
Outdated
let api_version = *API_VERSION.get(py).expect("API_VERSION is initialized"); | ||
if api_version >= API_VERSION_2_0 { | ||
panic!( | ||
"{} requires API < {:08X} (NumPy 1) but the runtime version is API {:08X}", | ||
stringify!($fname), | ||
API_VERSION_2_0, | ||
api_version, | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With new helper:
let api_version = *API_VERSION.get(py).expect("API_VERSION is initialized"); | |
if api_version >= API_VERSION_2_0 { | |
panic!( | |
"{} requires API < {:08X} (NumPy 1) but the runtime version is API {:08X}", | |
stringify!($fname), | |
API_VERSION_2_0, | |
api_version, | |
) | |
} | |
assert!( | |
!is_numpy_2(), | |
"{} requires API < {:08X} (NumPy 1) but the runtime version is API {:08X}", | |
stringify!($fname), | |
API_VERSION_2_0, | |
API_VERSION.get(py).expect("API_VERSION is initialized), | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/npyffi/mod.rs
Outdated
let api_version = *API_VERSION.get(py).expect("API_VERSION is initialized"); | ||
if api_version < API_VERSION_2_0 { | ||
panic!( | ||
"{} requires API {:08X} or greater (NumPy 2) but the runtime version is API {:08X}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, let's change this to an assert!(is_numpy_2(), ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, many thanks! 🙏
Ah, there is a test failure. It looks like the assertion might just need updating? |
Fixed the test; since we run this test against both numpy2 and numpy1 I made the |
Did you already push your fix? |
Oops, looks like I forgot; pushed now. Thanks for catching that. |
Hmm, we have a crash on 32 bit windows. This makes me think an FFI definition might be slightly wrong somewhere (using |
I'm happy to try to help debug this, but it might be 1-2 weeks before I can find a good debugging session for this. There's a lot of competing priorities for me right now personally and also in PyO3 between the Python 3.13 freethreaded release and various auxiliary projects. |
I've been trying to debug the issue on my fork where I can run CI: maffoo#2. One problem is that that when using numpy 2 on a 32-bit windows build, the |
Thank you. I just took a long stare at the headers too, and really don't see anything obvious going on. I can hope to make time later in the week or so to also try to debug. |
Just out of curiosity, is supporting 32-bit Windows actually that important at this point? Some data points on usage:
https://discuss.python.org/t/consider-downgrading-windows-32-bit-from-tier-1-to-tier-2-or-tier-3-in-python-3-13/33719/24 has some relevant discussion. (There's also "open an issue, merge this, and move on, and see if anyone cares", rather than explicitly saying you won't support it.) |
Adding to above data points, latest release of SciPy doesn't appear to have 32-bit Windows builds. Conda has dropped 32-bit Windows as well. |
I think that's a pragmatic option and while it's not ideal, I would be happy to for the moment set it up so that 32-bit windows will fail to compile and point at an issue. That way we can get feedback on whether it's worth fixing. Moving ahead with merge would also allow us to get on with 0.22 release in parallel with investigating this problem. Also in a similar vein I just opened #445 |
I've disabled compilation on 32-bit windows for now. I used I also removed 32-bit windows from the ci; I had to add some setup-python actions to get things to run on my fork, as it seems that One other change: I renamed two structs to |
Perfect, CI all green! Thanks so much for all the help here 🎉 As a final step, could you please add a CHANGELOG entry to this PR? Then let's merge and proceed! |
Done. |
Line 128 in 505a79c
|
This builds on the work on @aMarcireau in #429. Per the suggestion from @davidhewitt (#435 (comment)), I've rebased it on main to pick up CI fixes and made some changes to incorporate the latest review comments from @adamreichold.