diff --git a/.travis.yml b/.travis.yml index 7498e1d66ca0..8c05bdb5b1de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,6 +53,11 @@ matrix: - python: 3.6 env: - PYTHONOPTIMIZE=2 + - BLAS=None + - LAPACK=None + - ATLAS=None + - NPY_BLAS_ORDER=mkl,blis,openblas,atlas,accelerate,blas + - NPY_LAPACK_ORDER=MKL,OPENBLAS,ATLAS,ACCELERATE,LAPACK - USE_ASV=1 - python: 3.5 dist: trusty # remove after April 2019 diff --git a/doc/source/user/building.rst b/doc/source/user/building.rst index d224951dd5db..a9ec496c5722 100644 --- a/doc/source/user/building.rst +++ b/doc/source/user/building.rst @@ -118,12 +118,70 @@ means that g77 has been used. If libgfortran.so is a dependency, gfortran has been used. If both are dependencies, this means both have been used, which is almost always a very bad idea. +Accelerated BLAS/LAPACK libraries +--------------------------------- + +NumPy searches for optimized linear algebra libraries such as BLAS and LAPACK. +There are specific orders for searching these libraries, as described below. + +BLAS +~~~~ + +The default order for the libraries are: + +1. MKL +2. BLIS +3. OpenBLAS +4. ATLAS +5. Accelerate (MacOS) +6. 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 +``NPY_BLAS_ORDER`` which is a comma-separated list of the above names which +is used to determine what to search for, for instance:: + + NPY_BLAS_ORDER=ATLAS,blis,openblas,MKL python setup.py build + +will prefer to use ATLAS, then BLIS, then OpenBLAS and as a last resort MKL. +If neither of these exists the build will fail (names are compared +lower case). + +LAPACK +~~~~~~ + +The default order for the libraries are: + +1. MKL +2. OpenBLAS +3. ATLAS +4. Accelerate (MacOS) +5. LAPACK (NetLIB) + + +If you wish to build against OpenBLAS but you also have MKL available one +may predefine the order of searching via the environment variable +``NPY_LAPACK_ORDER`` which is a comma-separated list of the above names, +for instance:: + + NPY_LAPACK_ORDER=ATLAS,openblas,MKL python setup.py build + +will prefer to use ATLAS, then OpenBLAS and as a last resort MKL. +If neither of these exists the build will fail (names are compared +lower case). + + Disabling ATLAS and other accelerated libraries ------------------------------------------------ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Usage of ATLAS and other accelerated libraries in NumPy can be disabled via:: + NPY_BLAS_ORDER= NPY_LAPACK_ORDER= python setup.py build + +or:: + BLAS=None LAPACK=None ATLAS=None python setup.py build diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py index 4d923ad266ec..8cc628897f09 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py @@ -473,6 +473,13 @@ class LapackSrcNotFoundError(LapackNotFoundError): the LAPACK_SRC environment variable.""" +class BlasOptNotFoundError(NotFoundError): + """ + Optimized (vendor) Blas libraries are not found. + Falls back to netlib Blas library which has worse performance. + A better performance should be easily gained by switching + Blas library.""" + class BlasNotFoundError(NotFoundError): """ Blas (http://www.netlib.org/blas/) libraries not found. @@ -1539,139 +1546,219 @@ def get_atlas_version(**config): class lapack_opt_info(system_info): notfounderror = LapackNotFoundError + # Default order of LAPACK checks + lapack_order = ['mkl', 'openblas', 'atlas', 'accelerate', 'lapack'] - def calc_info(self): + def _calc_info_mkl(self): + info = get_info('lapack_mkl') + if info: + self.set_info(**info) + return True + return False - lapack_mkl_info = get_info('lapack_mkl') - if lapack_mkl_info: - self.set_info(**lapack_mkl_info) - return + def _calc_info_openblas(self): + info = get_info('openblas_lapack') + if info: + self.set_info(**info) + return True + info = get_info('openblas_clapack') + if info: + self.set_info(**info) + return True + return False - openblas_info = get_info('openblas_lapack') - if openblas_info: - self.set_info(**openblas_info) - return + def _calc_info_atlas(self): + info = get_info('atlas_3_10_threads') + if not info: + info = get_info('atlas_3_10') + if not info: + info = get_info('atlas_threads') + if not info: + info = get_info('atlas') + if info: + # Figure out if ATLAS has lapack... + # If not we need the lapack library, but not BLAS! + l = info.get('define_macros', []) + if ('ATLAS_WITH_LAPACK_ATLAS', None) in l \ + or ('ATLAS_WITHOUT_LAPACK', None) in l: + # Get LAPACK (with possible warnings) + # If not found we don't accept anything + # since we can't use ATLAS with LAPACK! + lapack_info = self._get_info_lapack() + if not lapack_info: + return False + dict_append(info, **lapack_info) + self.set_info(**info) + return True + return False - openblas_info = get_info('openblas_clapack') - if openblas_info: - self.set_info(**openblas_info) - return + def _calc_info_accelerate(self): + info = get_info('accelerate') + if info: + self.set_info(**info) + return True + return False - atlas_info = get_info('atlas_3_10_threads') - if not atlas_info: - atlas_info = get_info('atlas_3_10') - if not atlas_info: - atlas_info = get_info('atlas_threads') - if not atlas_info: - atlas_info = get_info('atlas') - - accelerate_info = get_info('accelerate') - if accelerate_info and not atlas_info: - self.set_info(**accelerate_info) - return + def _get_info_blas(self): + # Default to get the optimized BLAS implementation + info = get_info('blas_opt') + if not info: + warnings.warn(BlasNotFoundError.__doc__ or '', stacklevel=3) + info_src = get_info('blas_src') + if not info_src: + warnings.warn(BlasSrcNotFoundError.__doc__ or '', stacklevel=3) + return {} + dict_append(info, libraries=[('fblas_src', info_src)]) + return info - need_lapack = 0 - need_blas = 0 - info = {} - if atlas_info: - l = atlas_info.get('define_macros', []) - if ('ATLAS_WITH_LAPACK_ATLAS', None) in l \ - or ('ATLAS_WITHOUT_LAPACK', None) in l: - need_lapack = 1 - info = atlas_info + def _get_info_lapack(self): + info = get_info('lapack') + if not info: + warnings.warn(LapackNotFoundError.__doc__ or '', stacklevel=3) + info_src = get_info('lapack_src') + if not info_src: + warnings.warn(LapackSrcNotFoundError.__doc__ or '', stacklevel=3) + return {} + dict_append(info, libraries=[('flapack_src', info_src)]) + return info - else: - warnings.warn(AtlasNotFoundError.__doc__, stacklevel=2) - need_blas = 1 - need_lapack = 1 + def _calc_info_lapack(self): + info = self._get_info_lapack() + if info: + info_blas = self._get_info_blas() + dict_append(info, **info_blas) dict_append(info, define_macros=[('NO_ATLAS_INFO', 1)]) + self.set_info(**info) + return True + return False - if need_lapack: - lapack_info = get_info('lapack') - #lapack_info = {} ## uncomment for testing - if lapack_info: - dict_append(info, **lapack_info) - else: - warnings.warn(LapackNotFoundError.__doc__, stacklevel=2) - lapack_src_info = get_info('lapack_src') - if not lapack_src_info: - warnings.warn(LapackSrcNotFoundError.__doc__, stacklevel=2) - return - dict_append(info, libraries=[('flapack_src', lapack_src_info)]) - - if need_blas: - blas_info = get_info('blas') - if blas_info: - dict_append(info, **blas_info) - else: - warnings.warn(BlasNotFoundError.__doc__, stacklevel=2) - blas_src_info = get_info('blas_src') - if not blas_src_info: - warnings.warn(BlasSrcNotFoundError.__doc__, stacklevel=2) - return - dict_append(info, libraries=[('fblas_src', blas_src_info)]) + def calc_info(self): + user_order = os.environ.get('NPY_LAPACK_ORDER', None) + if user_order is None: + lapack_order = self.lapack_order + else: + # the user has requested the order of the + # check they are all in the available list, a COMMA SEPARATED list + user_order = user_order.lower().split(',') + non_existing = [] + lapack_order = [] + for order in user_order: + if order in self.lapack_order: + lapack_order.append(order) + elif len(order) > 0: + non_existing.append(order) + if len(non_existing) > 0: + raise ValueError("lapack_opt_info user defined " + "LAPACK order has unacceptable " + "values: {}".format(non_existing)) + + for lapack in lapack_order: + if getattr(self, '_calc_info_{}'.format(lapack))(): + return - self.set_info(**info) - return + if 'lapack' not in lapack_order: + # Since the user may request *not* to use any library, we still need + # to raise warnings to signal missing packages! + warnings.warn(LapackNotFoundError.__doc__ or '', stacklevel=2) + warnings.warn(LapackSrcNotFoundError.__doc__ or '', stacklevel=2) class blas_opt_info(system_info): notfounderror = BlasNotFoundError + # Default order of BLAS checks + blas_order = ['mkl', 'blis', 'openblas', 'atlas', 'accelerate', 'blas'] - def calc_info(self): + def _calc_info_mkl(self): + info = get_info('blas_mkl') + if info: + self.set_info(**info) + return True + return False - blas_mkl_info = get_info('blas_mkl') - if blas_mkl_info: - self.set_info(**blas_mkl_info) - return + def _calc_info_blis(self): + info = get_info('blis') + if info: + self.set_info(**info) + return True + return False - blis_info = get_info('blis') - if blis_info: - self.set_info(**blis_info) - return + def _calc_info_openblas(self): + info = get_info('openblas') + if info: + self.set_info(**info) + return True + return False - openblas_info = get_info('openblas') - if openblas_info: - self.set_info(**openblas_info) - return + def _calc_info_atlas(self): + info = get_info('atlas_3_10_blas_threads') + if not info: + info = get_info('atlas_3_10_blas') + if not info: + info = get_info('atlas_blas_threads') + if not info: + info = get_info('atlas_blas') + if info: + self.set_info(**info) + return True + return False - atlas_info = get_info('atlas_3_10_blas_threads') - if not atlas_info: - atlas_info = get_info('atlas_3_10_blas') - if not atlas_info: - atlas_info = get_info('atlas_blas_threads') - if not atlas_info: - atlas_info = get_info('atlas_blas') - - accelerate_info = get_info('accelerate') - if accelerate_info and not atlas_info: - self.set_info(**accelerate_info) - return + def _calc_info_accelerate(self): + info = get_info('accelerate') + if info: + self.set_info(**info) + return True + return False - need_blas = 0 + def _calc_info_blas(self): + # Warn about a non-optimized BLAS library + warnings.warn(BlasOptNotFoundError.__doc__ or '', stacklevel=3) info = {} - if atlas_info: - info = atlas_info + dict_append(info, define_macros=[('NO_ATLAS_INFO', 1)]) + + blas = get_info('blas') + if blas: + dict_append(info, **blas) else: - warnings.warn(AtlasNotFoundError.__doc__, stacklevel=2) - need_blas = 1 - dict_append(info, define_macros=[('NO_ATLAS_INFO', 1)]) + # Not even BLAS was found! + warnings.warn(BlasNotFoundError.__doc__ or '', stacklevel=3) - if need_blas: - blas_info = get_info('blas') - if blas_info: - dict_append(info, **blas_info) - else: - warnings.warn(BlasNotFoundError.__doc__, stacklevel=2) - blas_src_info = get_info('blas_src') - if not blas_src_info: - warnings.warn(BlasSrcNotFoundError.__doc__, stacklevel=2) - return - dict_append(info, libraries=[('fblas_src', blas_src_info)]) + blas_src = get_info('blas_src') + if not blas_src: + warnings.warn(BlasSrcNotFoundError.__doc__ or '', stacklevel=3) + return False + dict_append(info, libraries=[('fblas_src', blas_src)]) self.set_info(**info) - return + return True + + def calc_info(self): + user_order = os.environ.get('NPY_BLAS_ORDER', None) + if user_order is None: + blas_order = self.blas_order + else: + # the user has requested the order of the + # check they are all in the available list + user_order = user_order.lower().split(',') + non_existing = [] + blas_order = [] + for order in user_order: + if order in self.blas_order: + blas_order.append(order) + elif len(order) > 0: + non_existing.append(order) + if len(non_existing) > 0: + raise ValueError("blas_opt_info user defined BLAS order has unacceptable values: {}".format(non_existing)) + + for blas in blas_order: + if getattr(self, '_calc_info_{}'.format(blas))(): + return + + if 'blas' not in blas_order: + # Since the user may request *not* to use any library, we still need + # to raise warnings to signal missing packages! + warnings.warn(BlasNotFoundError.__doc__ or '', stacklevel=2) + warnings.warn(BlasSrcNotFoundError.__doc__ or '', stacklevel=2) class blas_info(system_info):