Skip to content

Commit 370c71d

Browse files
authored
Merge branch 'main' into asv-benchmarks
2 parents ac204c8 + d15aea4 commit 370c71d

8 files changed

Lines changed: 608 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [dev] - YYYY-MM-DD
88

99
### Added
10+
* Added CLI patch management for NumPy umath with persistent install/status/uninstall and one-shot command patching via `python -m mkl_umath --patch <command>` and `python -m mkl_umath --with-numpy-patch <command>` [gh-216](https://github.com/IntelPython/mkl_umath/pull/216)
1011

1112
### Changed
1213
* Removed `numpy-base` dependency and `USE_NUMPY_BASE` environment variable from conda recipe [gh-200](https://github.com/IntelPython/mkl_umath/pull/200)
14+
* Updated `mkl_umath` patching to work with changes to NumPy Cython API present in NumPy 2.5 [gh-226](https://github.com/IntelPython/mkl_umath/pull/226)
1315

1416
### Fixed
1517

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,45 @@ Where `<numpy_version>` should be the latest version from https://software.repos
3636

3737
# Patching Mechanisms
3838

39-
`mkl_umath` provides a convenient programmatic patch method to enable MKL-accelerated umath operations in NumPy.
39+
`mkl_umath` provides convenient patch methods to enable MKL-accelerated
40+
umath operations in NumPy with or without modifying your code.
41+
42+
## CLI Quickstart
43+
44+
### Persistent patch (all Python sessions)
45+
46+
```bash
47+
# Install
48+
python -m mkl_umath --patch install
49+
50+
# Status (exit code: 0 = installed, 1 = not installed)
51+
python -m mkl_umath --patch status
52+
53+
# Remove
54+
python -m mkl_umath --patch uninstall
55+
```
56+
57+
### Verify patch state
58+
59+
```bash
60+
python -c "import mkl_umath; print(f'mkl_umath.is_patched(): {mkl_umath.is_patched()}')"
61+
```
62+
63+
### One-shot patch (single command only)
64+
65+
```bash
66+
# Script
67+
python -m mkl_umath --with-numpy-patch my_script.py
68+
69+
# Pytest
70+
python -m mkl_umath --with-numpy-patch -m pytest tests/
71+
72+
# One-liner
73+
python -m mkl_umath --with-numpy-patch -c "import mkl_umath; print(f\"mkl_umath.is_patched(): {mkl_umath.is_patched()}\")"
74+
75+
# Non-Python command
76+
python -m mkl_umath --with-numpy-patch -- <command> [args...]
77+
```
4078

4179
## Programmatic Quickstart
4280

mkl_umath/__main__.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright (c) 2026, Intel Corporation
2+
#
3+
# Redistribution and use in source and binary forms, with or without
4+
# modification, are permitted provided that the following conditions are met:
5+
#
6+
# * Redistributions of source code must retain the above copyright notice,
7+
# this list of conditions and the following disclaimer.
8+
# * Redistributions in binary form must reproduce the above copyright
9+
# notice, this list of conditions and the following disclaimer in the
10+
# documentation and/or other materials provided with the distribution.
11+
# * Neither the name of Intel Corporation nor the names of its contributors
12+
# may be used to endorse or promote products derived from this software
13+
# without specific prior written permission.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
26+
"""Command-line interface for mkl_umath."""
27+
28+
import argparse
29+
import sys
30+
31+
32+
def main():
33+
"""Entry point for the CLI."""
34+
parser = argparse.ArgumentParser(
35+
prog="python -m mkl_umath",
36+
description="MKL-accelerated universal functions for NumPy",
37+
)
38+
parser.add_argument(
39+
"-v", "--verbose", action="store_true", help="Enable verbose output"
40+
)
41+
parser.add_argument(
42+
"--patch",
43+
choices=["install", "uninstall", "status"],
44+
help="Manage persistent NumPy umath patching",
45+
)
46+
parser.add_argument(
47+
"--with-numpy-patch",
48+
dest="with_numpy_patch",
49+
nargs=argparse.REMAINDER,
50+
help="Run command with temporary NumPy umath patch",
51+
)
52+
53+
args = parser.parse_args()
54+
55+
if args.patch:
56+
from mkl_umath.patch import (
57+
PatchOperationError,
58+
check_status,
59+
install_patch,
60+
uninstall_patch,
61+
)
62+
63+
try:
64+
if args.patch == "install":
65+
install_patch(verbose=args.verbose)
66+
elif args.patch == "uninstall":
67+
uninstall_patch(verbose=args.verbose)
68+
elif args.patch == "status":
69+
sys.exit(0 if check_status(verbose=args.verbose) else 1)
70+
except PatchOperationError as exc:
71+
print(exc, file=sys.stderr)
72+
sys.exit(1)
73+
74+
elif args.with_numpy_patch is not None:
75+
from mkl_umath.with_patch import run_with_numpy_patch
76+
77+
run_with_numpy_patch(args.with_numpy_patch)
78+
79+
else:
80+
parser.print_help()
81+
sys.exit(1)
82+
83+
84+
if __name__ == "__main__":
85+
main()

mkl_umath/_patch_startup.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) 2026, Intel Corporation
2+
#
3+
# Redistribution and use in source and binary forms, with or without
4+
# modification, are permitted provided that the following conditions are met:
5+
#
6+
# * Redistributions of source code must retain the above copyright notice,
7+
# this list of conditions and the following disclaimer.
8+
# * Redistributions in binary form must reproduce the above copyright
9+
# notice, this list of conditions and the following disclaimer in the
10+
# documentation and/or other materials provided with the distribution.
11+
# * Neither the name of Intel Corporation nor the names of its contributors
12+
# may be used to endorse or promote products derived from this software
13+
# without specific prior written permission.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
26+
"""Helper module for .pth-based persistent patching with error handling."""
27+
28+
try:
29+
import mkl_umath
30+
31+
mkl_umath.patch_numpy_umath()
32+
except Exception:
33+
pass

mkl_umath/patch.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright (c) 2026, Intel Corporation
2+
#
3+
# Redistribution and use in source and binary forms, with or without
4+
# modification, are permitted provided that the following conditions are met:
5+
#
6+
# * Redistributions of source code must retain the above copyright notice,
7+
# this list of conditions and the following disclaimer.
8+
# * Redistributions in binary form must reproduce the above copyright
9+
# notice, this list of conditions and the following disclaimer in the
10+
# documentation and/or other materials provided with the distribution.
11+
# * Neither the name of Intel Corporation nor the names of its contributors
12+
# may be used to endorse or promote products derived from this software
13+
# without specific prior written permission.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
25+
"""Persistent patch management for NumPy umath submodule."""
26+
27+
import site
28+
import sys
29+
import warnings
30+
from pathlib import Path
31+
32+
33+
class PatchOperationError(RuntimeError):
34+
"""Raised when a persistent patch operation cannot be completed."""
35+
36+
37+
def get_pth_path():
38+
"""Get the path to mkl_umath_patch.pth in the appropriate site-packages."""
39+
site_packages = site.getsitepackages()
40+
if site_packages:
41+
target_site = site_packages[0]
42+
else:
43+
target_site = site.getusersitepackages()
44+
return Path(target_site) / "mkl_umath_patch.pth"
45+
46+
47+
PTH_CONTENT = """import mkl_umath._patch_startup"""
48+
49+
50+
def install_patch(verbose=False):
51+
"""Install persistent NumPy umath patch using .pth file."""
52+
pth_path = get_pth_path()
53+
54+
if pth_path.exists():
55+
if verbose:
56+
warnings.warn(
57+
f"Persistent patch already installed at {pth_path}",
58+
UserWarning,
59+
stacklevel=2,
60+
)
61+
return
62+
63+
try:
64+
pth_path.parent.mkdir(parents=True, exist_ok=True)
65+
pth_path.write_text(PTH_CONTENT)
66+
if verbose:
67+
print(f"Persistent patch installed at {pth_path}")
68+
print()
69+
print("NumPy umath will now use MKL-accelerated implementations in")
70+
print("all Python sessions. To disable, run:")
71+
print(" python -m mkl_umath --patch uninstall")
72+
except OSError as e:
73+
raise PatchOperationError(
74+
f"Error installing patch at {pth_path}: {e}\n\n"
75+
"You may need to run with appropriate permissions or install to "
76+
"a user site-packages directory."
77+
) from e
78+
79+
80+
def uninstall_patch(verbose=False):
81+
"""Uninstall persistent NumPy umath patch."""
82+
pth_path = get_pth_path()
83+
84+
if not pth_path.exists():
85+
if verbose:
86+
print("No persistent patch found.")
87+
return
88+
89+
try:
90+
pth_path.unlink()
91+
if verbose:
92+
print(f"Persistent patch removed from {pth_path}")
93+
print()
94+
print("NumPy umath will now use the default implementations.")
95+
except OSError as e:
96+
raise PatchOperationError(
97+
f"Error removing patch at {pth_path}: {e}"
98+
) from e
99+
100+
101+
def check_status(verbose=False):
102+
"""Check if persistent patch is installed."""
103+
pth_path = get_pth_path()
104+
105+
if pth_path.exists():
106+
if verbose:
107+
print(f"Persistent patch is installed at {pth_path}")
108+
print()
109+
print(
110+
"NumPy umath is configured to use MKL-accelerated "
111+
"implementations."
112+
)
113+
return True
114+
else:
115+
if verbose:
116+
print("No persistent patch installed")
117+
print()
118+
print("To enable MKL-accelerated NumPy umath globally, run:")
119+
print(" python -m mkl_umath --patch install")
120+
return False
121+
122+
123+
if __name__ == "__main__":
124+
if len(sys.argv) < 2:
125+
print("Usage: python -m mkl_umath.patch <install|uninstall|status>")
126+
sys.exit(1)
127+
128+
command = sys.argv[1]
129+
try:
130+
if command == "install":
131+
install_patch(verbose=True)
132+
elif command == "uninstall":
133+
uninstall_patch(verbose=True)
134+
elif command == "status":
135+
sys.exit(0 if check_status(verbose=True) else 1)
136+
else:
137+
print(f"Unknown command: {command}")
138+
sys.exit(1)
139+
except PatchOperationError as exc:
140+
print(exc, file=sys.stderr)
141+
sys.exit(1)

0 commit comments

Comments
 (0)