diff options
author | Matthew Badin <mbadin@apple.com> | 2021-04-28 16:56:01 -0700 |
---|---|---|
committer | Matthew Badin <mbadin@apple.com> | 2021-04-28 17:20:11 -0700 |
commit | e28da7a3d50862fa99b8b704d60fc6543b5af631 (patch) | |
tree | 57f4c3684d5333c319d3b7a3cbcdbb087418acc4 /numpy | |
parent | 53e34d49d46b0999927160444a7dd0650efafad7 (diff) | |
download | numpy-e28da7a3d50862fa99b8b704d60fc6543b5af631.tar.gz |
BLD: Enable Accelerate Framework
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/_build_utils/README | 9 | ||||
-rw-r--r-- | numpy/_build_utils/__init__.py | 0 | ||||
-rw-r--r-- | numpy/_build_utils/apple_accelerate.py | 21 | ||||
-rw-r--r-- | numpy/core/setup.py | 9 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 65 | ||||
-rw-r--r-- | numpy/distutils/system_info.py | 54 | ||||
-rw-r--r-- | numpy/linalg/setup.py | 5 |
7 files changed, 118 insertions, 45 deletions
diff --git a/numpy/_build_utils/README b/numpy/_build_utils/README new file mode 100644 index 000000000..73d93593e --- /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 000000000..e69de29bb --- /dev/null +++ b/numpy/_build_utils/__init__.py diff --git a/numpy/_build_utils/apple_accelerate.py b/numpy/_build_utils/apple_accelerate.py new file mode 100644 index 000000000..8ce54619e --- /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 df405bcaf..f59b67c88 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -10,6 +10,10 @@ from os.path import join 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 073433bd1..40b435f2e 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -6194,6 +6194,71 @@ class TestDot: 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 9e192329f..082b029d7 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py @@ -375,22 +375,6 @@ default_src_dirs = [_m for _m in default_src_dirs if os.path.isdir(_m)] 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 @@ class system_info: 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 @@ class lapack_opt_info(system_info): 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 @@ class blas_opt_info(system_info): 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 5c9f2a4cb..e2944f38c 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' |