From e28da7a3d50862fa99b8b704d60fc6543b5af631 Mon Sep 17 00:00:00 2001 From: Matthew Badin Date: Wed, 28 Apr 2021 16:56:01 -0700 Subject: [PATCH 1/4] BLD: Enable Accelerate Framework --- MANIFEST.in | 1 + numpy/_build_utils/README | 9 ++++ numpy/_build_utils/__init__.py | 0 numpy/_build_utils/apple_accelerate.py | 21 +++++++++ numpy/core/setup.py | 9 ++-- numpy/core/tests/test_multiarray.py | 65 ++++++++++++++++++++++++++ numpy/distutils/system_info.py | 54 ++++++++------------- numpy/linalg/setup.py | 5 -- 8 files changed, 119 insertions(+), 45 deletions(-) create mode 100644 numpy/_build_utils/README create mode 100644 numpy/_build_utils/__init__.py create mode 100644 numpy/_build_utils/apple_accelerate.py diff --git a/MANIFEST.in b/MANIFEST.in index 8ec62123b998..856c64d0c6b9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -22,6 +22,7 @@ include numpy/*.pxd # Note that sub-directories that don't have __init__ are apparently not # included by 'recursive-include', so list those separately recursive-include numpy * +recursive-include numpy/_build_utils * recursive-include numpy/linalg/lapack_lite * recursive-include tools * # Add sdist files whose use depends on local configuration. diff --git a/numpy/_build_utils/README b/numpy/_build_utils/README new file mode 100644 index 000000000000..73d93593e6f9 --- /dev/null +++ b/numpy/_build_utils/README @@ -0,0 +1,9 @@ +======= +WARNING +======= + +This directory (numpy/_build_utils) is *not* part of the public numpy API, + - it is internal build support for numpy. + - it is only present in source distributions or during an in place build + - it is *not* installed with the rest of numpy + diff --git a/numpy/_build_utils/__init__.py b/numpy/_build_utils/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/numpy/_build_utils/apple_accelerate.py b/numpy/_build_utils/apple_accelerate.py new file mode 100644 index 000000000000..8ce54619ee5c --- /dev/null +++ b/numpy/_build_utils/apple_accelerate.py @@ -0,0 +1,21 @@ +import os +import sys +import re + +__all__ = ['uses_accelerate_framework'] + +def uses_accelerate_framework(info): + """ Returns True if Accelerate framework is used for BLAS/LAPACK """ + # If we're not building on Darwin (macOS), don't use Accelerate + if sys.platform != "darwin": + return False + # If we're building on macOS, but targeting a different platform, + # don't use Accelerate. + if os.getenv('_PYTHON_HOST_PLATFORM', None): + return False + r_accelerate = re.compile("Accelerate") + extra_link_args = info.get('extra_link_args', '') + for arg in extra_link_args: + if r_accelerate.search(arg): + return True + return False diff --git a/numpy/core/setup.py b/numpy/core/setup.py index df405bcaf487..f59b67c88bc1 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -10,6 +10,10 @@ from numpy.distutils import log from distutils.dep_util import newer from sysconfig import get_config_var + +from numpy._build_utils.apple_accelerate import ( + uses_accelerate_framework + ) from numpy.compat import npy_load_module from setup_common import * # noqa: F403 @@ -405,11 +409,6 @@ def configuration(parent_package='',top_path=None): from numpy.distutils.system_info import (get_info, blas_opt_info, lapack_opt_info) - # Accelerate is buggy, disallow it. See also numpy/linalg/setup.py - for opt_order in (blas_opt_info.blas_order, lapack_opt_info.lapack_order): - if 'accelerate' in opt_order: - opt_order.remove('accelerate') - config = Configuration('core', parent_package, top_path) local_dir = config.local_path codegen_dir = join(local_dir, 'code_generators') diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 073433bd1cad..40b435f2e20a 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -6194,6 +6194,71 @@ def test_dot_array_order(self): assert_equal(np.dot(b, a), res) assert_equal(np.dot(b, b), res) + def test_accelerate_framework_sgemv_fix(self): + + def aligned_array(shape, align, dtype, order='C'): + d = dtype(0) + N = np.prod(shape) + tmp = np.zeros(N * d.nbytes + align, dtype=np.uint8) + address = tmp.__array_interface__["data"][0] + for offset in range(align): + if (address + offset) % align == 0: + break + tmp = tmp[offset:offset+N*d.nbytes].view(dtype=dtype) + return tmp.reshape(shape, order=order) + + def as_aligned(arr, align, dtype, order='C'): + aligned = aligned_array(arr.shape, align, dtype, order) + aligned[:] = arr[:] + return aligned + + def assert_dot_close(A, X, desired): + assert_allclose(np.dot(A, X), desired, rtol=1e-5, atol=1e-7) + + m = aligned_array(100, 15, np.float32) + s = aligned_array((100, 100), 15, np.float32) + np.dot(s, m) # this will always segfault if the bug is present + + testdata = itertools.product((15,32), (10000,), (200,89), ('C','F')) + for align, m, n, a_order in testdata: + # Calculation in double precision + A_d = np.random.rand(m, n) + X_d = np.random.rand(n) + desired = np.dot(A_d, X_d) + # Calculation with aligned single precision + A_f = as_aligned(A_d, align, np.float32, order=a_order) + X_f = as_aligned(X_d, align, np.float32) + assert_dot_close(A_f, X_f, desired) + # Strided A rows + A_d_2 = A_d[::2] + desired = np.dot(A_d_2, X_d) + A_f_2 = A_f[::2] + assert_dot_close(A_f_2, X_f, desired) + # Strided A columns, strided X vector + A_d_22 = A_d_2[:, ::2] + X_d_2 = X_d[::2] + desired = np.dot(A_d_22, X_d_2) + A_f_22 = A_f_2[:, ::2] + X_f_2 = X_f[::2] + assert_dot_close(A_f_22, X_f_2, desired) + # Check the strides are as expected + if a_order == 'F': + assert_equal(A_f_22.strides, (8, 8 * m)) + else: + assert_equal(A_f_22.strides, (8 * n, 8)) + assert_equal(X_f_2.strides, (8,)) + # Strides in A rows + cols only + X_f_2c = as_aligned(X_f_2, align, np.float32) + assert_dot_close(A_f_22, X_f_2c, desired) + # Strides just in A cols + A_d_12 = A_d[:, ::2] + desired = np.dot(A_d_12, X_d_2) + A_f_12 = A_f[:, ::2] + assert_dot_close(A_f_12, X_f_2c, desired) + # Strides in A cols and X + assert_dot_close(A_f_12, X_f_2, desired) + + class MatmulCommon: """Common tests for '@' operator and numpy.matmul. diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py index 9e192329f635..082b029d7047 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py @@ -375,22 +375,6 @@ def add_system_root(library_root): so_ext = get_shared_lib_extension() -def is_symlink_to_accelerate(filename): - accelpath = '/System/Library/Frameworks/Accelerate.framework' - return (sys.platform == 'darwin' and os.path.islink(filename) and - os.path.realpath(filename).startswith(accelpath)) - - -_accel_msg = ( - 'Found {filename}, but that file is a symbolic link to the ' - 'MacOS Accelerate framework, which is not supported by NumPy. ' - 'You must configure the build to use a different optimized library, ' - 'or disable the use of optimized BLAS and LAPACK by setting the ' - 'environment variables NPY_BLAS_ORDER="" and NPY_LAPACK_ORDER="" ' - 'before building NumPy.' -) - - def get_standard_file(fname): """Returns a list of files named 'fname' from 1) System-wide directory (directory-location of this module) @@ -539,6 +523,7 @@ def get_info(name, notfound_action=0): 'blis': blis_info, # use blas_opt instead 'lapack_mkl': lapack_mkl_info, # use lapack_opt instead 'blas_mkl': blas_mkl_info, # use blas_opt instead + 'accelerate': accelerate_info, # use blas_opt instead 'openblas64_': openblas64__info, 'openblas64__lapack': openblas64__lapack_info, 'openblas_ilp64': openblas_ilp64_info, @@ -1029,9 +1014,6 @@ def _find_lib(self, lib_dir, lib, exts): for prefix in lib_prefixes: p = self.combine_paths(lib_dir, prefix + lib + ext) if p: - # p[0] is the full path to the binary library file. - if is_symlink_to_accelerate(p[0]): - raise RuntimeError(_accel_msg.format(filename=p[0])) break if p: assert len(p) == 1 @@ -1766,10 +1748,18 @@ def get_atlas_version(**config): class lapack_opt_info(system_info): notfounderror = LapackNotFoundError + # List of all known LAPACK libraries, in the default order - lapack_order = ['mkl', 'openblas', 'flame', 'atlas', 'lapack'] + lapack_order = ['accelerate', 'mkl', 'openblas', 'flame', 'atlas', 'lapack'] order_env_var_name = 'NPY_LAPACK_ORDER' + def _calc_info_accelerate(self): + info = get_info('accelerate') + if info: + self.set_info(**info) + return True + return False + def _calc_info_mkl(self): info = get_info('lapack_mkl') if info: @@ -1820,13 +1810,6 @@ def _calc_info_atlas(self): return True return False - def _calc_info_accelerate(self): - info = get_info('accelerate') - if info: - self.set_info(**info) - return True - return False - def _get_info_blas(self): # Default to get the optimized BLAS implementation info = get_info('blas_opt') @@ -1942,9 +1925,17 @@ class lapack64__opt_info(lapack_ilp64_opt_info): class blas_opt_info(system_info): notfounderror = BlasNotFoundError # List of all known BLAS libraries, in the default order - blas_order = ['mkl', 'blis', 'openblas', 'atlas', 'blas'] + + blas_order = ['accelerate', 'mkl', 'blis', 'openblas', 'atlas', 'blas'] order_env_var_name = 'NPY_BLAS_ORDER' + def _calc_info_accelerate(self): + info = get_info('accelerate') + if info: + self.set_info(**info) + return True + return False + def _calc_info_mkl(self): info = get_info('blas_mkl') if info: @@ -1979,13 +1970,6 @@ def _calc_info_atlas(self): return True return False - def _calc_info_accelerate(self): - info = get_info('accelerate') - if info: - self.set_info(**info) - return True - return False - def _calc_info_blas(self): # Warn about a non-optimized BLAS library warnings.warn(BlasOptNotFoundError.__doc__ or '', stacklevel=3) diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py index 5c9f2a4cb56c..e2944f38c33d 100644 --- a/numpy/linalg/setup.py +++ b/numpy/linalg/setup.py @@ -9,11 +9,6 @@ def configuration(parent_package='', top_path=None): config.add_subpackage('tests') - # Accelerate is buggy, disallow it. See also numpy/core/setup.py - for opt_order in (blas_opt_info.blas_order, lapack_opt_info.lapack_order): - if 'accelerate' in opt_order: - opt_order.remove('accelerate') - # Configure lapack_lite src_dir = 'lapack_lite' From ee56322f91788c97d4a41fdf4ae66aa45310553c Mon Sep 17 00:00:00 2001 From: Matthew Badin Date: Fri, 30 Apr 2021 14:03:55 -0700 Subject: [PATCH 2/4] BLD: Address lint issues and reviewer comments. --- MANIFEST.in | 1 - numpy/_build_utils/README | 9 --------- numpy/_build_utils/__init__.py | 0 numpy/_build_utils/apple_accelerate.py | 21 --------------------- numpy/core/setup.py | 4 ---- numpy/core/tests/test_multiarray.py | 2 +- numpy/distutils/system_info.py | 6 ++++-- 7 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 numpy/_build_utils/README delete mode 100644 numpy/_build_utils/__init__.py delete mode 100644 numpy/_build_utils/apple_accelerate.py diff --git a/MANIFEST.in b/MANIFEST.in index 856c64d0c6b9..8ec62123b998 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -22,7 +22,6 @@ include numpy/*.pxd # Note that sub-directories that don't have __init__ are apparently not # included by 'recursive-include', so list those separately recursive-include numpy * -recursive-include numpy/_build_utils * recursive-include numpy/linalg/lapack_lite * recursive-include tools * # Add sdist files whose use depends on local configuration. diff --git a/numpy/_build_utils/README b/numpy/_build_utils/README deleted file mode 100644 index 73d93593e6f9..000000000000 --- a/numpy/_build_utils/README +++ /dev/null @@ -1,9 +0,0 @@ -======= -WARNING -======= - -This directory (numpy/_build_utils) is *not* part of the public numpy API, - - it is internal build support for numpy. - - it is only present in source distributions or during an in place build - - it is *not* installed with the rest of numpy - diff --git a/numpy/_build_utils/__init__.py b/numpy/_build_utils/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/numpy/_build_utils/apple_accelerate.py b/numpy/_build_utils/apple_accelerate.py deleted file mode 100644 index 8ce54619ee5c..000000000000 --- a/numpy/_build_utils/apple_accelerate.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys -import re - -__all__ = ['uses_accelerate_framework'] - -def uses_accelerate_framework(info): - """ Returns True if Accelerate framework is used for BLAS/LAPACK """ - # If we're not building on Darwin (macOS), don't use Accelerate - if sys.platform != "darwin": - return False - # If we're building on macOS, but targeting a different platform, - # don't use Accelerate. - if os.getenv('_PYTHON_HOST_PLATFORM', None): - return False - r_accelerate = re.compile("Accelerate") - extra_link_args = info.get('extra_link_args', '') - for arg in extra_link_args: - if r_accelerate.search(arg): - return True - return False diff --git a/numpy/core/setup.py b/numpy/core/setup.py index f59b67c88bc1..d1229ee8f143 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -10,10 +10,6 @@ from numpy.distutils import log from distutils.dep_util import newer from sysconfig import get_config_var - -from numpy._build_utils.apple_accelerate import ( - uses_accelerate_framework - ) from numpy.compat import npy_load_module from setup_common import * # noqa: F403 diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 40b435f2e20a..012dcbc6c8d5 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -6219,7 +6219,7 @@ def assert_dot_close(A, X, desired): s = aligned_array((100, 100), 15, np.float32) np.dot(s, m) # this will always segfault if the bug is present - testdata = itertools.product((15,32), (10000,), (200,89), ('C','F')) + testdata = itertools.product((15, 32), (10000,), (200, 89), ('C', 'F')) for align, m, n, a_order in testdata: # Calculation in double precision A_d = np.random.rand(m, n) diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py index 082b029d7047..7a031bf9e519 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py @@ -1750,7 +1750,8 @@ class lapack_opt_info(system_info): notfounderror = LapackNotFoundError # List of all known LAPACK libraries, in the default order - lapack_order = ['accelerate', 'mkl', 'openblas', 'flame', 'atlas', 'lapack'] + lapack_order = ['mkl', 'openblas', 'flame', + 'accelerate', 'atlas', 'lapack'] order_env_var_name = 'NPY_LAPACK_ORDER' def _calc_info_accelerate(self): @@ -1926,7 +1927,8 @@ class blas_opt_info(system_info): notfounderror = BlasNotFoundError # List of all known BLAS libraries, in the default order - blas_order = ['accelerate', 'mkl', 'blis', 'openblas', 'atlas', 'blas'] + blas_order = ['mkl', 'blis', 'openblas', + 'accelerate', 'atlas', 'blas'] order_env_var_name = 'NPY_BLAS_ORDER' def _calc_info_accelerate(self): From ec479e0edceb5759d24d4521e56ad8f63cc26354 Mon Sep 17 00:00:00 2001 From: Matthew Badin Date: Sat, 1 May 2021 11:30:24 -0700 Subject: [PATCH 3/4] BLD: Minimize diff --- numpy/distutils/system_info.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py index 7a031bf9e519..234b4afc9d94 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py @@ -1754,13 +1754,6 @@ class lapack_opt_info(system_info): 'accelerate', 'atlas', 'lapack'] order_env_var_name = 'NPY_LAPACK_ORDER' - def _calc_info_accelerate(self): - info = get_info('accelerate') - if info: - self.set_info(**info) - return True - return False - def _calc_info_mkl(self): info = get_info('lapack_mkl') if info: @@ -1811,6 +1804,13 @@ def _calc_info_atlas(self): return True return False + def _calc_info_accelerate(self): + info = get_info('accelerate') + if info: + self.set_info(**info) + return True + return False + def _get_info_blas(self): # Default to get the optimized BLAS implementation info = get_info('blas_opt') @@ -1931,13 +1931,6 @@ class blas_opt_info(system_info): 'accelerate', 'atlas', 'blas'] order_env_var_name = 'NPY_BLAS_ORDER' - def _calc_info_accelerate(self): - info = get_info('accelerate') - if info: - self.set_info(**info) - return True - return False - def _calc_info_mkl(self): info = get_info('blas_mkl') if info: @@ -1972,6 +1965,13 @@ def _calc_info_atlas(self): return True return False + def _calc_info_accelerate(self): + info = get_info('accelerate') + if info: + self.set_info(**info) + return True + return False + def _calc_info_blas(self): # Warn about a non-optimized BLAS library warnings.warn(BlasOptNotFoundError.__doc__ or '', stacklevel=3) From 06fffd94729a920961648dd86a8b8e86f6c25326 Mon Sep 17 00:00:00 2001 From: Matthew Badin Date: Tue, 4 May 2021 13:08:35 -0700 Subject: [PATCH 4/4] DOC: Add release note. --- doc/release/upcoming_changes/18874.change.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doc/release/upcoming_changes/18874.change.rst diff --git a/doc/release/upcoming_changes/18874.change.rst b/doc/release/upcoming_changes/18874.change.rst new file mode 100644 index 000000000000..c86fed83e739 --- /dev/null +++ b/doc/release/upcoming_changes/18874.change.rst @@ -0,0 +1,11 @@ +Enable Accelerate Framework +---------------------------- +With the release of macOS 11.3, several different issues that +numpy was encountering when using Accelerate Framework's +implementation of BLAS and LAPACK should be resolved. This +change enables the Accelerate Framework as an option on macOS. +If additional issues are found, please file a bug report +against Accelerate using the developer feedback assistant +tool (https://developer.apple.com/bug-reporting/). We +intend to address issues promptly and plan to continue +supporting and updating our BLAS and LAPACK libraries.