Skip to content
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

Merged
merged 22 commits into from
Oct 4, 2024
Merged

Add support for Numpy 2.x (take 2) #442

merged 22 commits into from
Oct 4, 2024

Conversation

maffoo
Copy link
Contributor

@maffoo maffoo commented Aug 31, 2024

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.

@davidhewitt
Copy link
Member

Thank you! @adamreichold do you expect to have capacity to review this in the near future, or should I try to find some time?

@maffoo maffoo mentioned this pull request Aug 31, 2024
@juntyr
Copy link

juntyr commented Sep 20, 2024

gentle ping @adamreichold @davidhewitt - do you have capacity to review

Copy link
Member

@davidhewitt davidhewitt left a 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.

Comment on lines +104 to +110
- 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)
Copy link
Member

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.

Comment on lines 39 to 43
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)()
});
Copy link
Member

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:

Suggested change
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
}

Copy link
Contributor Author

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).

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();
Copy link
Member

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:

Suggested change
pub static API_VERSION: GILOnceCell<c_uint> = GILOnceCell::new();
static API_VERSION: GILOnceCell<c_uint> = GILOnceCell::new();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 63 to 71
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,
)
}
Copy link
Member

Choose a reason for hiding this comment

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

With new helper:

Suggested change
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),
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 80 to 83
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}",
Copy link
Member

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(), ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

@davidhewitt davidhewitt left a 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! 🙏

src/npyffi/mod.rs Outdated Show resolved Hide resolved
@davidhewitt
Copy link
Member

Ah, there is a test failure. It looks like the assertion might just need updating?

@maffoo
Copy link
Contributor Author

maffoo commented Sep 23, 2024

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 npyffi::is_numpy_2 function public so it could be used by the test.

@juntyr
Copy link

juntyr commented Sep 24, 2024

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 npyffi::is_numpy_2 function public so it could be used by the test.

Did you already push your fix?

@maffoo
Copy link
Contributor Author

maffoo commented Sep 24, 2024

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 npyffi::is_numpy_2 function public so it could be used by the test.

Did you already push your fix?

Oops, looks like I forgot; pushed now. Thanks for catching that.

@davidhewitt
Copy link
Member

Hmm, we have a crash on 32 bit windows. This makes me think an FFI definition might be slightly wrong somewhere (using i32 instead of c_long or similar...)

@davidhewitt
Copy link
Member

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.

@maffoo
Copy link
Contributor Author

maffoo commented Sep 30, 2024

Hmm, we have a crash on 32 bit windows. This makes me think an FFI definition might be slightly wrong somewhere (using i32 instead of c_long or similar...)

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 PyDataType_ELSIZE function is returning 0 in places where it shouldn't, while when using numpy 1 on a 32-bit windows build the ELSIZE is correct so cargo tests pass but there is a crash when loading a compiled python extension. I'm still looking into what is going on, but haven't been able to track it down.

@davidhewitt
Copy link
Member

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.

@itamarst
Copy link
Contributor

itamarst commented Oct 1, 2024

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.)

@itamarst
Copy link
Contributor

itamarst commented Oct 1, 2024

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.

@davidhewitt
Copy link
Member

davidhewitt commented Oct 1, 2024

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

@maffoo
Copy link
Contributor Author

maffoo commented Oct 3, 2024

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.

I've disabled compilation on 32-bit windows for now. I used compile_error! to do this but let me know if there's an alternative you would prefer. I filed #448 to track investigating this and potentially re-enabling support for 32-bit windows.

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 ubuntu-latest has updated to python 3.12 and now fails with an error about being in an externally managed environment if you try to pip install anything without being in a virtualenv. I was seeing the same error even with setup-python if using python 3.12, so I set things up for python 3.11 for now (this seems like a problem in setup-python itself). I put up #447 to address the ci issues separately since it also looks like ubuntu-latest no longer supports python 3.7, so we could do that first rather than lumping everything in here.

One other change: I renamed two structs to _PyArray_DescrNumPy2 and _PyArray_LegacyDescr to match what is in the numpy c source, reverting the change following the @adamreichold's comment. I think following the names in the numpy source is worth it, especially since there are some thorny ABI issues with 32-bit windows and having to translate struct names while tracking these down was making things more difficult.

@davidhewitt
Copy link
Member

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!

@maffoo
Copy link
Contributor Author

maffoo commented Oct 3, 2024

As a final step, could you please add a CHANGELOG entry to this PR? Then let's merge and proceed!

Done.

@davidhewitt davidhewitt merged commit f397117 into PyO3:main Oct 4, 2024
46 of 48 checks passed
@juntyr
Copy link

juntyr commented Oct 6, 2024

on a range of semver-incompatible versions, currently `>= 0.13, < 0.16`. Cargo does not
in the README still needs to be updated

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.

6 participants