Skip to content

Commit 15c3ebb

Browse files
committed
task: module CLI patching methods
1 parent 52ae701 commit 15c3ebb

6 files changed

Lines changed: 505 additions & 0 deletions

File tree

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,84 @@ numpy.allclose(mkl_res, np_res)
8181
# True
8282
```
8383

84+
---
85+
# Patching Mechanisms
86+
87+
`mkl_fft` provides convenient ways to enable MKL-accelerated FFT operations in NumPy with or without modifying your code. It supports both persistent patching (applies to all Python sessions) and one-shot execution (applies only to a single command). It also supports Python functions and context managers that do the same.
88+
89+
## Persistent Patching
90+
91+
### Install Persistent Patch
92+
93+
```bash
94+
python -m mkl_fft patch install
95+
```
96+
97+
### Check Patch Status
98+
99+
```bash
100+
python -m mkl_fft patch status
101+
```
102+
103+
Checks whether the persistent patch is currently installed. Returns exit code 0 if installed, 1 if not installed.
104+
105+
### Uninstall Persistent Patch
106+
107+
```bash
108+
python -m mkl_fft patch uninstall
109+
```
110+
111+
Removes the persistent patch file, restoring NumPy to its default FFT implementation.
112+
113+
## One-Shot Execution
114+
115+
```bash
116+
python -m mkl_fft with_patch <command> [args...]
117+
```
118+
119+
Runs a single command with MKL-accelerated FFT enabled. The patch is only active for that specific execution and does not persist.
120+
121+
**Examples:**
122+
123+
```bash
124+
# Run a Python script with MKL acceleration
125+
python -m mkl_fft with_patch python my_script.py
126+
127+
# Run tests with MKL acceleration
128+
python -m mkl_fft with_patch python -m pytest tests/
129+
130+
# Run a Python one-liner
131+
python -m mkl_fft with_patch python -c "import numpy; print(numpy.fft.fft.__module__)"
132+
133+
# Run benchmarks with MKL acceleration
134+
python -m mkl_fft with_patch python run_benchmarks.py
135+
```
136+
137+
## Programmatic Usage
138+
139+
You can also patch NumPy programmatically in your Python code:
140+
141+
```python
142+
import mkl_fft
143+
144+
# Check if currently patched
145+
if mkl_fft.is_patched():
146+
print("NumPy FFT is using MKL")
147+
148+
# Enable patching globally
149+
mkl_fft.patch_numpy_fft()
150+
151+
# Disable patching
152+
mkl_fft.restore_numpy_fft()
153+
154+
# Use as context manager (recommended for temporary patching)
155+
with mkl_fft.mkl_fft():
156+
# NumPy FFT uses MKL inside this block
157+
import numpy as np
158+
result = np.fft.fft(data)
159+
# NumPy FFT restored outside the block
160+
```
161+
84162
---
85163
# Building from source
86164

mkl_fft/__main__.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) 2017, 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_fft."""
27+
28+
import sys
29+
30+
31+
def main_impl():
32+
if len(sys.argv) < 2:
33+
print("Usage: python -m mkl_fft <command> [args]")
34+
print()
35+
print("Commands:")
36+
print(" patch install Install persistent NumPy FFT patch")
37+
print(" patch uninstall Uninstall persistent NumPy FFT patch")
38+
print(" patch status Check if persistent patch is installed")
39+
print(" with_patch <cmd> Run command with temporary NumPy FFT patch")
40+
print()
41+
print("Examples:")
42+
print(" python -m mkl_fft patch install")
43+
print(" python -m mkl_fft with_patch python script.py")
44+
sys.exit(1)
45+
46+
command = sys.argv[1]
47+
48+
if command == "patch":
49+
from mkl_fft.patch import main as patch_main
50+
51+
patch_main(sys.argv[2:])
52+
elif command == "with_patch":
53+
from mkl_fft.with_patch import main as with_patch_main
54+
55+
with_patch_main(sys.argv[2:])
56+
else:
57+
print(f"Unknown command: {command}")
58+
sys.exit(1)
59+
60+
61+
def main():
62+
"""Entry point for the CLI."""
63+
main_impl()
64+
65+
66+
if __name__ == "__main__":
67+
main()

mkl_fft/_patch_startup.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) 2017, 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_fft
30+
31+
mkl_fft.patch_numpy_fft()
32+
except Exception:
33+
pass

mkl_fft/patch.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Copyright (c) 2017, 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 FFT submodule."""
26+
27+
import argparse
28+
import site
29+
import sys
30+
from pathlib import Path
31+
32+
33+
def get_pth_path():
34+
"""Get the path to mkl_fft_patch.pth in the appropriate site-packages."""
35+
site_packages = site.getsitepackages()
36+
if site_packages:
37+
target_site = site_packages[0]
38+
else:
39+
target_site = site.getusersitepackages()
40+
return Path(target_site) / "mkl_fft_patch.pth"
41+
42+
43+
PTH_CONTENT = """import mkl_fft._patch_startup"""
44+
45+
46+
def install_patch():
47+
"""Install persistent NumPy FFT patch using .pth file."""
48+
pth_path = get_pth_path()
49+
50+
if pth_path.exists():
51+
print(f"Persistent patch already installed at {pth_path}")
52+
return
53+
54+
try:
55+
pth_path.parent.mkdir(parents=True, exist_ok=True)
56+
pth_path.write_text(PTH_CONTENT)
57+
print(f"✓ Persistent patch installed at {pth_path}")
58+
print()
59+
print("NumPy FFT will now use MKL-accelerated implementations in all")
60+
print("Python sessions. To disable, run:")
61+
print(" python -m mkl_fft patch uninstall")
62+
except OSError as e:
63+
print(f"Error installing patch: {e}")
64+
print()
65+
print("You may need to run with appropriate permissions or install to")
66+
print("a user site-packages directory.")
67+
sys.exit(1)
68+
69+
70+
def uninstall_patch():
71+
"""Uninstall persistent NumPy FFT patch."""
72+
pth_path = get_pth_path()
73+
74+
if not pth_path.exists():
75+
print("No persistent patch found.")
76+
return
77+
78+
try:
79+
pth_path.unlink()
80+
print(f"✓ Persistent patch removed from {pth_path}")
81+
print()
82+
print("NumPy FFT will now use the default implementations.")
83+
except OSError as e:
84+
print(f"Error removing patch: {e}")
85+
sys.exit(1)
86+
87+
88+
def check_status():
89+
"""Check if persistent patch is installed."""
90+
pth_path = get_pth_path()
91+
92+
if pth_path.exists():
93+
print(f"✓ Persistent patch is installed at {pth_path}")
94+
print()
95+
print("NumPy FFT is configured to use MKL-accelerated implementations.")
96+
return True
97+
else:
98+
print("✗ No persistent patch installed")
99+
print()
100+
print("To enable MKL-accelerated NumPy FFT globally, run:")
101+
print(" python -m mkl_fft patch install")
102+
return False
103+
104+
105+
def main(args=None):
106+
"""Main entry point for patch command."""
107+
parser = argparse.ArgumentParser(
108+
prog="python -m mkl_fft patch",
109+
description="Manage persistent NumPy FFT patching with MKL acceleration",
110+
)
111+
subparsers = parser.add_subparsers(
112+
dest="command", help="Available commands"
113+
)
114+
115+
subparsers.add_parser("install", help="Install persistent NumPy FFT patch")
116+
subparsers.add_parser(
117+
"uninstall", help="Uninstall persistent NumPy FFT patch"
118+
)
119+
subparsers.add_parser(
120+
"status", help="Check if persistent patch is installed"
121+
)
122+
123+
parsed_args = parser.parse_args(args)
124+
125+
if not parsed_args.command:
126+
parser.print_help()
127+
sys.exit(1)
128+
129+
if parsed_args.command == "install":
130+
install_patch()
131+
elif parsed_args.command == "uninstall":
132+
uninstall_patch()
133+
elif parsed_args.command == "status":
134+
sys.exit(0 if check_status() else 1)
135+
136+
137+
if __name__ == "__main__":
138+
main()

0 commit comments

Comments
 (0)