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

Make PyTensor compatible with numpy 2.0 #1194

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- uses: pre-commit/[email protected]

test:
name: "${{ matrix.os }} test py${{ matrix.python-version }} : fast-compile ${{ matrix.fast-compile }} : float32 ${{ matrix.float32 }} : ${{ matrix.part }}"
name: "${{ matrix.os }} test py${{ matrix.python-version }} numpy${{ matrix.numpy-version }} : fast-compile ${{ matrix.fast-compile }} : float32 ${{ matrix.float32 }} : ${{ matrix.part }}"
needs:
- changes
- style
Expand All @@ -76,6 +76,7 @@ jobs:
matrix:
os: ["ubuntu-latest"]
python-version: ["3.10", "3.12"]
numpy-version: ["~=1.26.0", ">=2.0"]
fast-compile: [0, 1]
float32: [0, 1]
install-numba: [0]
Expand Down Expand Up @@ -105,45 +106,68 @@ jobs:
float32: 1
- part: "--doctest-modules pytensor --ignore=pytensor/misc/check_duplicate_key.py --ignore=pytensor/link"
fast-compile: 1
- numpy-version: "~=1.26.0"
fast-compile: 1
- numpy-version: "~=1.26.0"
float32: 1
- numpy-version: "~=1.26.0"
python-version: "3.12"
- numpy-version: "~=1.26.0"
part: "--doctest-modules pytensor --ignore=pytensor/misc/check_duplicate_key.py --ignore=pytensor/link"
include:
- install-numba: 1
os: "ubuntu-latest"
python-version: "3.10"
numpy-version: "~=2.1.0"
fast-compile: 0
float32: 0
part: "tests/link/numba"
- install-numba: 1
os: "ubuntu-latest"
python-version: "3.12"
numpy-version: "~=2.1.0"
fast-compile: 0
float32: 0
part: "tests/link/numba"
- install-jax: 1
os: "ubuntu-latest"
python-version: "3.10"
numpy-version: ">=2.0"
fast-compile: 0
float32: 0
part: "tests/link/jax"
- install-jax: 1
os: "ubuntu-latest"
python-version: "3.12"
numpy-version: ">=2.0"
fast-compile: 0
float32: 0
part: "tests/link/jax"
- install-torch: 1
os: "ubuntu-latest"
python-version: "3.10"
numpy-version: ">=2.0"
fast-compile: 0
float32: 0
part: "tests/link/pytorch"
- os: macos-15
python-version: "3.12"
numpy-version: ">=2.0"
fast-compile: 0
float32: 0
install-numba: 0
install-jax: 0
install-torch: 0
part: "tests/tensor/test_blas.py tests/tensor/test_elemwise.py tests/tensor/test_math_scipy.py"
- os: "ubuntu-latest"
python-version: "3.10"
numpy-version: "~=1.26.0"
fast-compile: 0
float32: 0
install-numba: 0
install-jax: 0
install-torch: 0
part: "tests/tensor/test_math.py"
ricardoV94 marked this conversation as resolved.
Show resolved Hide resolved

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -174,9 +198,9 @@ jobs:
run: |

if [[ $OS == "macos-15" ]]; then
micromamba install --yes -q "python~=${PYTHON_VERSION}=*_cpython" numpy scipy pip graphviz cython pytest coverage pytest-cov pytest-benchmark pytest-mock libblas=*=*accelerate;
micromamba install --yes -q "python~=${PYTHON_VERSION}=*_cpython" "numpy${NUMPY_VERSION}" scipy pip graphviz cython pytest coverage pytest-cov pytest-benchmark pytest-mock libblas=*=*accelerate;
else
micromamba install --yes -q "python~=${PYTHON_VERSION}=*_cpython" mkl numpy scipy pip mkl-service graphviz cython pytest coverage pytest-cov pytest-benchmark pytest-mock;
micromamba install --yes -q "python~=${PYTHON_VERSION}=*_cpython" mkl "numpy${NUMPY_VERSION}" scipy pip mkl-service graphviz cython pytest coverage pytest-cov pytest-benchmark pytest-mock;
fi
if [[ $INSTALL_NUMBA == "1" ]]; then micromamba install --yes -q -c conda-forge "python~=${PYTHON_VERSION}=*_cpython" "numba>=0.57"; fi
if [[ $INSTALL_JAX == "1" ]]; then micromamba install --yes -q -c conda-forge "python~=${PYTHON_VERSION}=*_cpython" jax jaxlib numpyro && pip install tensorflow-probability; fi
Expand All @@ -193,6 +217,7 @@ jobs:
fi
env:
PYTHON_VERSION: ${{ matrix.python-version }}
NUMPY_VERSION: ${{ matrix.numpy-version }}
INSTALL_NUMBA: ${{ matrix.install-numba }}
INSTALL_JAX: ${{ matrix.install-jax }}
INSTALL_TORCH: ${{ matrix.install-torch}}
Expand Down
2 changes: 1 addition & 1 deletion environment-osx-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ channels:
dependencies:
- python=>3.10
- compilers
- numpy>=1.17.0,<2
- numpy>=1.17.0
- scipy>=1,<2
- filelock>=3.15
- etuples
Expand Down
4 changes: 2 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ channels:
dependencies:
- python>=3.10
- compilers
- numpy>=1.17.0,<2
- scipy>=1,<2
- numpy>=1.17.0
- scipy>=1
- filelock>=3.15
- etuples
- logical-unification
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ keywords = [
dependencies = [
"setuptools>=59.0.0",
"scipy>=1,<2",
"numpy>=1.17.0,<2",
"numpy>=1.17.0",
"filelock>=3.15",
"etuples",
"logical-unification",
Expand Down Expand Up @@ -129,7 +129,7 @@ exclude = ["doc/", "pytensor/_version.py"]
docstring-code-format = true

[tool.ruff.lint]
select = ["B905", "C", "E", "F", "I", "UP", "W", "RUF", "PERF", "PTH", "ISC"]
select = ["B905", "C", "E", "F", "I", "UP", "W", "RUF", "PERF", "PTH", "ISC", "NPY201"]
ignore = ["C408", "C901", "E501", "E741", "RUF012", "PERF203", "ISC001"]
unfixable = [
# zip-strict: the auto-fix adds `strict=False` but we might want `strict=True` instead
Expand Down
4 changes: 2 additions & 2 deletions pytensor/link/c/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1367,8 +1367,8 @@ def cmodule_key_(

# We must always add the numpy ABI version here as
# DynamicModule always add the include <numpy/arrayobject.h>
if np.lib.NumpyVersion(np.__version__) < "1.16.0a":
ndarray_c_version = np.core.multiarray._get_ndarray_c_version()
if np.lib.NumpyVersion(np.__version__) > "1.27.0":
ndarray_c_version = np._core._multiarray_umath._get_ndarray_c_version()
else:
ndarray_c_version = np.core._multiarray_umath._get_ndarray_c_version()
sig.append(f"NPY_ABI_VERSION=0x{ndarray_c_version:X}")
Expand Down
53 changes: 19 additions & 34 deletions pytensor/link/c/c_code/lazylinker_c.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@

#if PY_VERSION_HEX >= 0x03000000
#include "numpy/npy_3kcompat.h"
#define PyCObject_AsVoidPtr NpyCapsule_AsVoidPtr
#define PyCObject_GetDesc NpyCapsule_GetDesc
#define PyCObject_Check NpyCapsule_Check
#endif

#ifndef Py_TYPE
Expand Down Expand Up @@ -323,9 +320,9 @@ static int CLazyLinker_init(CLazyLinker *self, PyObject *args, PyObject *kwds) {
if (PyObject_HasAttrString(thunk, "cthunk")) {
PyObject *cthunk = PyObject_GetAttrString(thunk, "cthunk");
// new reference
assert(cthunk && PyCObject_Check(cthunk));
self->thunk_cptr_fn[i] = PyCObject_AsVoidPtr(cthunk);
self->thunk_cptr_data[i] = PyCObject_GetDesc(cthunk);
assert(cthunk && NpyCapsule_Check(cthunk));
self->thunk_cptr_fn[i] = NpyCapsule_AsVoidPtr(cthunk);
self->thunk_cptr_data[i] = NpyCapsule_GetDesc(cthunk);
Py_DECREF(cthunk);
// cthunk is kept alive by membership in self->thunks
}
Expand Down Expand Up @@ -487,8 +484,8 @@ static PyObject *pycall(CLazyLinker *self, Py_ssize_t node_idx, int verbose) {
PyList_SetItem(self->call_times, node_idx,
PyFloat_FromDouble(t1 - t0 + ti));
PyObject *count = PyList_GetItem(self->call_counts, node_idx);
long icount = PyInt_AsLong(count);
PyList_SetItem(self->call_counts, node_idx, PyInt_FromLong(icount + 1));
long icount = PyLong_AsLong(count);
PyList_SetItem(self->call_counts, node_idx, PyLong_FromLong(icount + 1));
}
} else {
if (verbose) {
Expand All @@ -512,8 +509,8 @@ static int c_call(CLazyLinker *self, Py_ssize_t node_idx, int verbose) {
PyList_SetItem(self->call_times, node_idx,
PyFloat_FromDouble(t1 - t0 + ti));
PyObject *count = PyList_GetItem(self->call_counts, node_idx);
long icount = PyInt_AsLong(count);
PyList_SetItem(self->call_counts, node_idx, PyInt_FromLong(icount + 1));
long icount = PyLong_AsLong(count);
PyList_SetItem(self->call_counts, node_idx, PyLong_FromLong(icount + 1));
} else {
err = fn(self->thunk_cptr_data[node_idx]);
}
Expand Down Expand Up @@ -774,20 +771,20 @@ static PyObject *CLazyLinker_call(PyObject *_self, PyObject *args,
output_subset = (char *)calloc(self->n_output_vars, sizeof(char));
for (int it = 0; it < output_subset_size; ++it) {
PyObject *elem = PyList_GetItem(output_subset_ptr, it);
if (!PyInt_Check(elem)) {
if (!PyLong_Check(elem)) {
err = 1;
PyErr_SetString(PyExc_RuntimeError,
"Some elements of output_subset list are not int");
}
output_subset[PyInt_AsLong(elem)] = 1;
output_subset[PyLong_AsLong(elem)] = 1;
}
}
}

self->position_of_error = -1;
// create constants used to fill the var_compute_cells
PyObject *one = PyInt_FromLong(1);
PyObject *zero = PyInt_FromLong(0);
PyObject *one = PyLong_FromLong(1);
PyObject *zero = PyLong_FromLong(0);

// pre-allocate our return value
Py_INCREF(Py_None);
Expand Down Expand Up @@ -942,11 +939,8 @@ static PyMemberDef CLazyLinker_members[] = {
};

static PyTypeObject lazylinker_ext_CLazyLinkerType = {
#if defined(NPY_PY3K)
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL) 0, /*ob_size*/
#endif

"lazylinker_ext.CLazyLinker", /*tp_name*/
sizeof(CLazyLinker), /*tp_basicsize*/
0, /*tp_itemsize*/
Expand Down Expand Up @@ -987,7 +981,7 @@ static PyTypeObject lazylinker_ext_CLazyLinkerType = {
};

static PyObject *get_version(PyObject *dummy, PyObject *args) {
PyObject *result = PyFloat_FromDouble(0.212);
PyObject *result = PyFloat_FromDouble(0.3);
return result;
}

Expand All @@ -996,7 +990,7 @@ static PyMethodDef lazylinker_ext_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */
};

#if defined(NPY_PY3K)

static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
"lazylinker_ext",
NULL,
Expand All @@ -1006,28 +1000,19 @@ static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
NULL,
NULL,
NULL};
#endif
#if defined(NPY_PY3K)
#define RETVAL m

PyMODINIT_FUNC PyInit_lazylinker_ext(void) {
#else
#define RETVAL
PyMODINIT_FUNC initlazylinker_ext(void) {
#endif

PyObject *m;

lazylinker_ext_CLazyLinkerType.tp_new = PyType_GenericNew;
if (PyType_Ready(&lazylinker_ext_CLazyLinkerType) < 0)
return RETVAL;
#if defined(NPY_PY3K)
return NULL;

m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("lazylinker_ext", lazylinker_ext_methods,
"Example module that creates an extension type.");
#endif
Py_INCREF(&lazylinker_ext_CLazyLinkerType);
PyModule_AddObject(m, "CLazyLinker",
(PyObject *)&lazylinker_ext_CLazyLinkerType);

return RETVAL;
return m;
}
8 changes: 1 addition & 7 deletions pytensor/link/c/c_code/pytensor_mod_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,8 @@
#define PYTENSOR_EXTERN
#endif

#if PY_MAJOR_VERSION < 3
#define PYTENSOR_RTYPE void
#else
#define PYTENSOR_RTYPE PyObject *
#endif

/* We need to redefine PyMODINIT_FUNC to add MOD_PUBLIC in the middle */
#undef PyMODINIT_FUNC
#define PyMODINIT_FUNC PYTENSOR_EXTERN MOD_PUBLIC PYTENSOR_RTYPE
#define PyMODINIT_FUNC PYTENSOR_EXTERN MOD_PUBLIC PyObject *

#endif
2 changes: 1 addition & 1 deletion pytensor/link/c/lazylinker_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
_logger = logging.getLogger(__file__)

force_compile = False
version = 0.212 # must match constant returned in function get_version()
version = 0.3 # must match constant returned in function get_version()
lazylinker_ext: ModuleType | None = None


Expand Down
2 changes: 1 addition & 1 deletion pytensor/link/jax/dispatch/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def assert_size_argument_jax_compatible(node):

@jax_typify.register(Generator)
def jax_typify_Generator(rng, **kwargs):
state = rng.__getstate__()
state = rng.bit_generator.state
state["bit_generator"] = numpy_bit_gens[state["bit_generator"]]

# XXX: Is this a reasonable approach?
Expand Down
9 changes: 8 additions & 1 deletion pytensor/link/numba/dispatch/elemwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
import numba
import numpy as np
from numba.core.extending import overload
from numpy.core.numeric import normalize_axis_index, normalize_axis_tuple


try:
from numpy.lib.array_utils import normalize_axis_index, normalize_axis_tuple
except ModuleNotFoundError:
# numpy < 2.0
from numpy.core.multiarray import normalize_axis_index
from numpy.core.numeric import normalize_axis_tuple

from pytensor.graph.op import Op
from pytensor.link.numba.dispatch import basic as numba_basic
Expand Down
4 changes: 2 additions & 2 deletions pytensor/link/numba/dispatch/random.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Callable
from copy import copy
from copy import copy, deepcopy
from functools import singledispatch
from textwrap import dedent

Expand Down Expand Up @@ -34,7 +34,7 @@ def copy_NumPyRandomGenerator(rng):
def impl(rng):
# TODO: Open issue on Numba?
with numba.objmode(new_rng=types.npy_rng):
new_rng = copy(rng)
new_rng = deepcopy(rng)

return new_rng

Expand Down
2 changes: 1 addition & 1 deletion pytensor/link/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def calculate_reallocate_info(
# where gc
for i in range(idx + 1, len(order)):
if reuse_out is not None:
break # type: ignore
break
for out in order[i].outputs:
if (
getattr(out.type, "ndim", None) == 0
Expand Down
Loading