diff options
-rw-r--r-- | .travis.yml | 4 | ||||
-rw-r--r-- | MANIFEST.in | 1 | ||||
-rw-r--r-- | azure-pipelines.yml | 12 | ||||
-rw-r--r-- | doc/release/upcoming_changes/15759.improvement.rst | 4 | ||||
-rw-r--r-- | doc/source/user/building.rst | 6 | ||||
-rw-r--r-- | numpy/_build_utils/README | 8 | ||||
-rw-r--r-- | numpy/_build_utils/__init__.py | 0 | ||||
-rw-r--r-- | numpy/_build_utils/apple_accelerate.py | 26 | ||||
-rw-r--r-- | numpy/_build_utils/src/apple_sgemv_fix.c | 253 | ||||
-rw-r--r-- | numpy/core/setup.py | 13 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 64 | ||||
-rw-r--r-- | numpy/distutils/system_info.py | 26 | ||||
-rw-r--r-- | numpy/linalg/setup.py | 8 |
13 files changed, 51 insertions, 374 deletions
diff --git a/.travis.yml b/.travis.yml index e019495fb..aa01457fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,8 +71,8 @@ jobs: - BLAS=None - LAPACK=None - ATLAS=None - - NPY_BLAS_ORDER=mkl,blis,openblas,atlas,accelerate,blas - - NPY_LAPACK_ORDER=MKL,OPENBLAS,ATLAS,ACCELERATE,LAPACK + - NPY_BLAS_ORDER=mkl,blis,openblas,atlas,blas + - NPY_LAPACK_ORDER=MKL,OPENBLAS,ATLAS,LAPACK - USE_ASV=1 - python: 3.7 diff --git a/MANIFEST.in b/MANIFEST.in index b58f85d4d..f710c92e6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -21,7 +21,6 @@ include numpy/__init__.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/azure-pipelines.yml b/azure-pipelines.yml index 564f5d8e8..ea2b414b0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -75,10 +75,13 @@ stages: PYTHON_VERSION: '3.6' NPY_USE_BLAS_ILP64: '1' USE_OPENBLAS: '1' - USE_XCODE_10: '1' - Accelerate: - PYTHON_VERSION: '3.6' - USE_OPENBLAS: '0' + # Disable this job: the azure images do not create the problematic + # symlink from Accelerate to OpenBLAS. We still have the test + # at import to detect a buggy Accelerate, just cannot easily trigger + # it with azure. + # Accelerate: + # PYTHON_VERSION: '3.6' + # USE_OPENBLAS: '0' steps: # the @0 refers to the (major) version of the *task* on Microsoft's @@ -145,7 +148,6 @@ stages: BLAS: None LAPACK: None ATLAS: None - ACCELERATE: None CC: /usr/bin/clang condition: eq(variables['USE_OPENBLAS'], '1') - script: python setup.py build -j 4 build_ext --inplace install diff --git a/doc/release/upcoming_changes/15759.improvement.rst b/doc/release/upcoming_changes/15759.improvement.rst new file mode 100644 index 000000000..0a1b255f7 --- /dev/null +++ b/doc/release/upcoming_changes/15759.improvement.rst @@ -0,0 +1,4 @@ +Remove the Accelerate library as a candidate LAPACK library +----------------------------------------------------------- +Apple no longer supports Accelerate. Remove it. + diff --git a/doc/source/user/building.rst b/doc/source/user/building.rst index 546fd7daf..47a0a03c9 100644 --- a/doc/source/user/building.rst +++ b/doc/source/user/building.rst @@ -123,8 +123,7 @@ The default order for the libraries are: 2. BLIS 3. OpenBLAS 4. ATLAS -5. Accelerate (MacOS) -6. BLAS (NetLIB) +5. BLAS (NetLIB) If you wish to build against OpenBLAS but you also have BLIS available one may predefine the order of searching via the environment variable @@ -146,8 +145,7 @@ The default order for the libraries are: 2. OpenBLAS 3. libFLAME 4. ATLAS -5. Accelerate (MacOS) -6. LAPACK (NetLIB) +5. LAPACK (NetLIB) If you wish to build against OpenBLAS but you also have MKL available one diff --git a/numpy/_build_utils/README b/numpy/_build_utils/README deleted file mode 100644 index 6976e0233..000000000 --- a/numpy/_build_utils/README +++ /dev/null @@ -1,8 +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 e69de29bb..000000000 --- a/numpy/_build_utils/__init__.py +++ /dev/null diff --git a/numpy/_build_utils/apple_accelerate.py b/numpy/_build_utils/apple_accelerate.py deleted file mode 100644 index b26aa12ad..000000000 --- a/numpy/_build_utils/apple_accelerate.py +++ /dev/null @@ -1,26 +0,0 @@ -import os -import sys -import re - -__all__ = ['uses_accelerate_framework', 'get_sgemv_fix'] - -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 - -def get_sgemv_fix(): - """ Returns source file needed to correct SGEMV """ - path = os.path.abspath(os.path.dirname(__file__)) - return [os.path.join(path, 'src', 'apple_sgemv_fix.c')] diff --git a/numpy/_build_utils/src/apple_sgemv_fix.c b/numpy/_build_utils/src/apple_sgemv_fix.c deleted file mode 100644 index b1dbeb681..000000000 --- a/numpy/_build_utils/src/apple_sgemv_fix.c +++ /dev/null @@ -1,253 +0,0 @@ -/* This is a collection of ugly hacks to circumvent a bug in - * Apple Accelerate framework's SGEMV subroutine. - * - * See: https://github.com/numpy/numpy/issues/4007 - * - * SGEMV in Accelerate framework will segfault on MacOS X version 10.9 - * (aka Mavericks) if arrays are not aligned to 32 byte boundaries - * and the CPU supports AVX instructions. This can produce segfaults - * in np.dot. - * - * This patch overshadows the symbols cblas_sgemv, sgemv_ and sgemv - * exported by Accelerate to produce the correct behavior. The MacOS X - * version and CPU specs are checked on module import. If Mavericks and - * AVX are detected the call to SGEMV is emulated with a call to SGEMM - * if the arrays are not 32 byte aligned. If the exported symbols cannot - * be overshadowed on module import, a fatal error is produced and the - * process aborts. All the fixes are in a self-contained C file - * and do not alter the multiarray C code. The patch is not applied - * unless NumPy is configured to link with Apple's Accelerate - * framework. - * - */ - -#define NPY_NO_DEPRECATED_API NPY_API_VERSION -#include "Python.h" -#include "numpy/arrayobject.h" - -#include <string.h> -#include <dlfcn.h> -#include <stdlib.h> -#include <stdio.h> -#include <sys/types.h> -#include <sys/sysctl.h> -#include <string.h> - -/* ----------------------------------------------------------------- */ -/* Original cblas_sgemv */ - -#define VECLIB_FILE "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/vecLib" - -enum CBLAS_ORDER {CblasRowMajor=101, CblasColMajor=102}; -enum CBLAS_TRANSPOSE {CblasNoTrans=111, CblasTrans=112, CblasConjTrans=113}; -extern void cblas_xerbla(int info, const char *rout, const char *form, ...); - -typedef void cblas_sgemv_t(const enum CBLAS_ORDER order, - const enum CBLAS_TRANSPOSE TransA, const int M, const int N, - const float alpha, const float *A, const int lda, - const float *X, const int incX, - const float beta, float *Y, const int incY); - -typedef void cblas_sgemm_t(const enum CBLAS_ORDER order, - const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_TRANSPOSE TransB, - const int M, const int N, const int K, - const float alpha, const float *A, const int lda, - const float *B, const int ldb, - const float beta, float *C, const int incC); - -typedef void fortran_sgemv_t( const char* trans, const int* m, const int* n, - const float* alpha, const float* A, const int* ldA, - const float* X, const int* incX, - const float* beta, float* Y, const int* incY ); - -static void *veclib = NULL; -static cblas_sgemv_t *accelerate_cblas_sgemv = NULL; -static cblas_sgemm_t *accelerate_cblas_sgemm = NULL; -static fortran_sgemv_t *accelerate_sgemv = NULL; -static int AVX_and_10_9 = 0; - -/* Dynamic check for AVX support - * __builtin_cpu_supports("avx") is available in gcc 4.8, - * but clang and icc do not currently support it. */ -static inline int -cpu_supports_avx() -{ - int enabled, r; - size_t length = sizeof(enabled); - r = sysctlbyname("hw.optional.avx1_0", &enabled, &length, NULL, 0); - if ( r == 0 && enabled != 0) { - return 1; - } - else { - return 0; - } -} - -/* Check if we are using MacOS X version 10.9 */ -static inline int -using_mavericks() -{ - int r; - char str[32] = {0}; - size_t size = sizeof(str); - r = sysctlbyname("kern.osproductversion", str, &size, NULL, 0); - if ( r == 0 && strncmp(str, "10.9", strlen("10.9")) == 0) { - return 1; - } - else { - return 0; - } -} - -__attribute__((destructor)) -static void unloadlib(void) -{ - if (veclib) dlclose(veclib); -} - -__attribute__((constructor)) -static void loadlib() -/* automatically executed on module import */ -{ - char errormsg[1024]; - int AVX, MAVERICKS; - memset((void*)errormsg, 0, sizeof(errormsg)); - /* check if the CPU supports AVX */ - AVX = cpu_supports_avx(); - /* check if the OS is MacOS X Mavericks */ - MAVERICKS = using_mavericks(); - /* we need the workaround when the CPU supports - * AVX and the OS version is Mavericks */ - AVX_and_10_9 = AVX && MAVERICKS; - /* load vecLib */ - veclib = dlopen(VECLIB_FILE, RTLD_LOCAL | RTLD_FIRST); - if (!veclib) { - veclib = NULL; - snprintf(errormsg, sizeof(errormsg), - "Failed to open vecLib from location '%s'.", VECLIB_FILE); - Py_FatalError(errormsg); /* calls abort() and dumps core */ - } - /* resolve Fortran SGEMV from Accelerate */ - accelerate_sgemv = (fortran_sgemv_t*) dlsym(veclib, "sgemv_"); - if (!accelerate_sgemv) { - unloadlib(); - Py_FatalError("Failed to resolve symbol 'sgemv_'."); - } - /* resolve cblas_sgemv from Accelerate */ - accelerate_cblas_sgemv = (cblas_sgemv_t*) dlsym(veclib, "cblas_sgemv"); - if (!accelerate_cblas_sgemv) { - unloadlib(); - Py_FatalError("Failed to resolve symbol 'cblas_sgemv'."); - } - /* resolve cblas_sgemm from Accelerate */ - accelerate_cblas_sgemm = (cblas_sgemm_t*) dlsym(veclib, "cblas_sgemm"); - if (!accelerate_cblas_sgemm) { - unloadlib(); - Py_FatalError("Failed to resolve symbol 'cblas_sgemm'."); - } -} - -/* ----------------------------------------------------------------- */ -/* Fortran SGEMV override */ - -void sgemv_( const char* trans, const int* m, const int* n, - const float* alpha, const float* A, const int* ldA, - const float* X, const int* incX, - const float* beta, float* Y, const int* incY ) -{ - /* It is safe to use the original SGEMV if we are not using AVX on Mavericks - * or the input arrays A, X and Y are all aligned on 32 byte boundaries. */ - #define BADARRAY(x) (((npy_intp)(void*)x) % 32) - const int use_sgemm = AVX_and_10_9 && (BADARRAY(A) || BADARRAY(X) || BADARRAY(Y)); - if (!use_sgemm) { - accelerate_sgemv(trans,m,n,alpha,A,ldA,X,incX,beta,Y,incY); - return; - } - - /* Arrays are misaligned, the CPU supports AVX, and we are running - * Mavericks. - * - * Emulation of SGEMV with SGEMM: - * - * SGEMV allows vectors to be strided. SGEMM requires all arrays to be - * contiguous along the leading dimension. To emulate striding in SGEMV - * with the leading dimension arguments in SGEMM we compute - * - * Y = alpha * op(A) @ X + beta * Y - * - * as - * - * Y.T = alpha * X.T @ op(A).T + beta * Y.T - * - * Because Fortran uses column major order and X.T and Y.T are row vectors, - * the leading dimensions of X.T and Y.T in SGEMM become equal to the - * strides of the column vectors X and Y in SGEMV. */ - - switch (*trans) { - case 'T': - case 't': - case 'C': - case 'c': - accelerate_cblas_sgemm( CblasColMajor, CblasNoTrans, CblasNoTrans, - 1, *n, *m, *alpha, X, *incX, A, *ldA, *beta, Y, *incY ); - break; - case 'N': - case 'n': - accelerate_cblas_sgemm( CblasColMajor, CblasNoTrans, CblasTrans, - 1, *m, *n, *alpha, X, *incX, A, *ldA, *beta, Y, *incY ); - break; - default: - cblas_xerbla(1, "SGEMV", "Illegal transpose setting: %c\n", *trans); - } -} - -/* ----------------------------------------------------------------- */ -/* Override for an alias symbol for sgemv_ in Accelerate */ - -void sgemv (char *trans, - const int *m, const int *n, - const float *alpha, - const float *A, const int *lda, - const float *B, const int *incB, - const float *beta, - float *C, const int *incC) -{ - sgemv_(trans,m,n,alpha,A,lda,B,incB,beta,C,incC); -} - -/* ----------------------------------------------------------------- */ -/* cblas_sgemv override, based on Netlib CBLAS code */ - -void cblas_sgemv(const enum CBLAS_ORDER order, - const enum CBLAS_TRANSPOSE TransA, const int M, const int N, - const float alpha, const float *A, const int lda, - const float *X, const int incX, const float beta, - float *Y, const int incY) -{ - char TA; - if (order == CblasColMajor) - { - if (TransA == CblasNoTrans) TA = 'N'; - else if (TransA == CblasTrans) TA = 'T'; - else if (TransA == CblasConjTrans) TA = 'C'; - else - { - cblas_xerbla(2, "cblas_sgemv","Illegal TransA setting, %d\n", TransA); - } - sgemv_(&TA, &M, &N, &alpha, A, &lda, X, &incX, &beta, Y, &incY); - } - else if (order == CblasRowMajor) - { - if (TransA == CblasNoTrans) TA = 'T'; - else if (TransA == CblasTrans) TA = 'N'; - else if (TransA == CblasConjTrans) TA = 'N'; - else - { - cblas_xerbla(2, "cblas_sgemv", "Illegal TransA setting, %d\n", TransA); - return; - } - sgemv_(&TA, &N, &M, &alpha, A, &lda, X, &incX, &beta, Y, &incY); - } - else - cblas_xerbla(1, "cblas_sgemv", "Illegal Order setting, %d\n", order); -} diff --git a/numpy/core/setup.py b/numpy/core/setup.py index fcc422545..bf807641d 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -10,9 +10,6 @@ from os.path import join from numpy.distutils import log from distutils.dep_util import newer from distutils.sysconfig import get_config_var -from numpy._build_utils.apple_accelerate import ( - uses_accelerate_framework, get_sgemv_fix - ) from numpy.compat import npy_load_module from setup_common import * # noqa: F403 @@ -392,7 +389,13 @@ def visibility_define(config): def configuration(parent_package='',top_path=None): from numpy.distutils.misc_util import Configuration, dot_join - from numpy.distutils.system_info import get_info + 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 @@ -762,8 +765,6 @@ def configuration(parent_package='',top_path=None): common_src.extend([join('src', 'common', 'cblasfuncs.c'), join('src', 'common', 'python_xerbla.c'), ]) - if uses_accelerate_framework(blas_info): - common_src.extend(get_sgemv_fix()) else: extra_info = {} diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 2edcc680b..ab5ec266e 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -5882,70 +5882,6 @@ 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 3a6a7b29d..df82683dc 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py @@ -362,6 +362,22 @@ 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) @@ -427,7 +443,6 @@ 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, @@ -919,6 +934,9 @@ 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 @@ -1650,8 +1668,8 @@ def get_atlas_version(**config): class lapack_opt_info(system_info): notfounderror = LapackNotFoundError - # List of all known BLAS libraries, in the default order - lapack_order = ['mkl', 'openblas', 'flame', 'atlas', 'accelerate', 'lapack'] + # List of all known LAPACK libraries, in the default order + lapack_order = ['mkl', 'openblas', 'flame', 'atlas', 'lapack'] order_env_var_name = 'NPY_LAPACK_ORDER' def _calc_info_mkl(self): @@ -1823,7 +1841,7 @@ 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', 'accelerate', 'blas'] + blas_order = ['mkl', 'blis', 'openblas', 'atlas', 'blas'] order_env_var_name = 'NPY_BLAS_ORDER' def _calc_info_mkl(self): diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py index 57fdd502b..bb070ed9d 100644 --- a/numpy/linalg/setup.py +++ b/numpy/linalg/setup.py @@ -3,11 +3,17 @@ import sys def configuration(parent_package='', top_path=None): from numpy.distutils.misc_util import Configuration - from numpy.distutils.system_info import get_info, system_info + from numpy.distutils.system_info import ( + get_info, system_info, lapack_opt_info, blas_opt_info) config = Configuration('linalg', parent_package, top_path) 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' |