diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f8c33b6a23d..8b75663e30cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,8 @@ else() endif() endif() +include(cmake/ChooseBlas.cmake) + if(USE_ASAN) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=address") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address") diff --git a/ci/docker/Dockerfile.build.android_armv8 b/ci/docker/Dockerfile.build.android_armv8 index f7de86763457..1437e16d2548 100644 --- a/ci/docker/Dockerfile.build.android_armv8 +++ b/ci/docker/Dockerfile.build.android_armv8 @@ -73,7 +73,7 @@ ENV CXX=${CROSS_ROOT}/bin/${CROSS_TRIPLE}-clang++ # Build ARM dependencies. COPY install/android_arm64_openblas.sh /work/ RUN /work/android_arm64_openblas.sh -ENV CPLUS_INCLUDE_PATH /work/deps/OpenBLAS +ENV OpenBLAS_HOME=/work/deps/OpenBLAS ARG USER_ID=0 ARG GROUP_ID=0 diff --git a/ci/docker/install/android_arm64_openblas.sh b/ci/docker/install/android_arm64_openblas.sh index 1c3014f6cca9..02d3740bd14a 100755 --- a/ci/docker/install/android_arm64_openblas.sh +++ b/ci/docker/install/android_arm64_openblas.sh @@ -25,8 +25,41 @@ pushd . git clone https://github.com/xianyi/OpenBLAS.git cd OpenBLAS make -j$(nproc) TARGET=ARMV8 ARM_SOFTFP_ABI=1 HOSTCC=gcc NOFORTRAN=1 libs -# Can't be run (utility not compiled for the target platform) -#make install -cp *.h /usr/include -cp libopenblas.a /usr/local/lib + +# Ideally, a simple `make install` would do the job. However, OpenBLAS fails when running `make +# install` because it tries to execute `getarch`, which is an utility that is not compiled for the +# target platform. +# +# In order to not need to install the library we set the variable OpenBLAS_HOME in the exported +# docker image. +# +# An important gotcha here to explain the reason of not doing the `make install` by hand: +# +# given that the compiler is installed in a different path than the usual (as is the case when +# doing crosscompilation), then the compiler uses different paths to look for the includes and +# libraries. So it is necessary to install it into the correct directory of includes for the +# configured compiler ($CC and $CXX). + +# For example, When querying the default include directories with the current docker image, I get: +# +# $ $CXX -Wp,-v -x c++ - -fsyntax-only +# clang -cc1 version 6.0.2 based upon LLVM 6.0.2svn default target x86_64-unknown-linux-gnu +# ignoring nonexistent directory "/usr/aarch64-linux-android/bin/. +# ./lib/gcc/aarch64-linux-android/4.9.x/../../../../include/c++/4.9.x/backward" +# ignoring nonexistent directory "/usr/aarch64-linux-android/bin/../sysroot/usr/local/include" +# ignoring nonexistent directory "/usr/aarch64-linux-android/bin/../sysroot/include" +# #include "..." search starts here: +# #include <...> search starts here: +# /work/deps/OpenBLAS +# /usr/aarch64-linux-android/bin/../lib/gcc/aarch64-linux-android/4.9.x/../../../. +# ./include/c++/4.9.x +# /usr/aarch64-linux-android/bin/../lib/gcc/aarch64-linux-android/4.9.x/../../../. +# ./include/c++/4.9.x/aarch64-linux-android +# /usr/aarch64-linux-android/lib64/clang/6.0.2/include +# /usr/aarch64-linux-android/bin/../sysroot/usr/include +# End of search list. +# +# The directory `/usr/include` is not in that list. +# +# As you can see, it is just easier to set the OpenBLAS_HOME variable. popd diff --git a/cmake/ChooseBlas.cmake b/cmake/ChooseBlas.cmake index 5f4af2d89c91..02e7c2fc4e4c 100644 --- a/cmake/ChooseBlas.cmake +++ b/cmake/ChooseBlas.cmake @@ -23,7 +23,7 @@ if(USE_MKL_IF_AVAILABLE) find_package(MKL) endif() if(MKL_FOUND) - if(USE_MKLDNN) + if(USE_MKLDNN) set(BLAS "open") else() set(BLAS "MKL") @@ -40,8 +40,8 @@ if(BLAS STREQUAL "Atlas" OR BLAS STREQUAL "atlas") add_definitions(-DMXNET_USE_BLAS_ATLAS=1) elseif(BLAS STREQUAL "Open" OR BLAS STREQUAL "open") find_package(OpenBLAS REQUIRED) - include_directories(SYSTEM ${OpenBLAS_INCLUDE_DIR}) - list(APPEND mshadow_LINKER_LIBS ${OpenBLAS_LIB}) + include_directories(SYSTEM ${OpenBLAS_INCLUDE_DIRS}) + list(APPEND mshadow_LINKER_LIBS ${OpenBLAS_LIBRARIES}) add_definitions(-DMSHADOW_USE_CBLAS=1) add_definitions(-DMSHADOW_USE_MKL=0) add_definitions(-DMXNET_USE_BLAS_OPEN=1) diff --git a/cmake/Modules/FindOpenBLAS.cmake b/cmake/Modules/FindOpenBLAS.cmake index a3a79caae461..8036a209b2fd 100644 --- a/cmake/Modules/FindOpenBLAS.cmake +++ b/cmake/Modules/FindOpenBLAS.cmake @@ -14,78 +14,181 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +# +# Finds the OpenBLAS library. +# +# The following variables are set after configuration is done: +# +# - OpenBLAS_FOUND +# - OpenBLAS_INCLUDE_DIRS +# - OpenBLAS_LIBRARIES +# +# This script will try to find the OpenBLAS library using the following RESULT_VARs in this order: +# +# 1 - Use find_package(OpenBLAS) in Config mode. +# 2 - Find the files manually +# +# At each step that was just described, in order to guarantee that the library was found correctly, +# this script tries to compile this simple program: +# +# #include +# int main() { +# cblas_sasum(0, (float*)0, 0); +# return 0; +# } +# +# If this simple program can't be compiled with the OpenBLAS library discovered in the previous +# step, then it assumes the step failed and moves on to the next step. +# +# To control where OpenBLAS should be searched for, one can set the environment variable +# `OpenBLAS_HOME` point to the installation directory of OpenBLAS: +# +# set OpenBLAS_HOME=c:\mxnet + +include(CheckCSourceCompiles) +function(check_openblas_compiles RESULT_VAR) + message(STATUS "Testing a simple OpenBLAS program with: (${RESULT_VAR}, " + "includes: ${OpenBLAS_INCLUDE_DIRS}, " + "libs: ${OpenBLAS_LIBRARIES})") + set(CMAKE_REQUIRED_INCLUDES "${OpenBLAS_INCLUDE_DIRS}") + set(CMAKE_REQUIRED_LIBRARIES "${OpenBLAS_LIBRARIES}") + + check_c_source_compiles(" + #include + int main() { cblas_sasum(0, (float*)0, 0); return 0; } + " OPENBLAS_CAN_COMPILE) + + unset(CMAKE_REQUIRED_INCLUDES CACHE) + unset(CMAKE_REQUIRED_LIBRARIES CACHE) + + if (NOT ${CAN_COMPILE}) + message(WARNING "Couldn't compile a simple program to test for OpenBLAS presence using the '${RESULT_VAR}' RESULT_VAR. " + "To verify the error from the compiler, run cmake with the flag `--debug-trycompile`. " + "Check that the include directories are correct and contain the 'cblas.h' file: ${OpenBLAS_INCLUDE_DIRS} " + "Check that the library directory also contains the compiled library: '${OpenBLAS_LIBRARIES}'.") + else() + message(STATUS "Found OpenBLAS via ${RESULT_VAR} (include: ${OpenBLAS_INCLUDE_DIRS})") + message(STATUS "Found OpenBLAS via ${RESULT_VAR} (lib: ${OpenBLAS_LIBRARIES})") + endif() + + set(${RESULT_VAR} ${OPENBLAS_CAN_COMPILE} PARENT_SCOPE) + unset(OPENBLAS_CAN_COMPILE CACHE) +endfunction() + +macro(unset_openblas_variables) + unset(OpenBLAS_FOUND) + unset(OpenBLAS_FOUND CACHE) + unset(OpenBLAS_LIBRARIES) + unset(OpenBLAS_LIBRARIES CACHE) + unset(OpenBLAS_INCLUDE_DIRS) + unset(OpenBLAS_INCLUDE_DIRS CACHE) +endmacro() + +# Try config-mode +find_package(OpenBLAS + NO_MODULE # Forcing Config mode, otherwise this would be an infinite loop. + PATHS $ENV{OpenBLAS_HOME} + ) +if (OpenBLAS_FOUND) + check_openblas_compiles(OpenBLAS_CONFIG_MODE) + if (OpenBLAS_CONFIG_MODE) + return() + else() + unset_openblas_variables() + endif() +endif() + +# Try finding the files manually +if(CMAKE_CROSSCOMPILING) + set(OpenBLAS_INCLUDE_SEARCH_PATHS + ${OpenBLAS_INCLUDE_SEARCH_PATHS} + + "$ENV{CROSS_ROOT}" + "${CROSS_ROOT}" + ) +endif() + +set(OpenBLAS_INCLUDE_SEARCH_PATHS + ${OpenBLAS_INCLUDE_SEARCH_PATHS} + + "$ENV{OpenBLAS_HOME}" + "${OpenBLAS_HOME}" + "${OpenBLAS_ROOT}" + + /usr + /usr/include/openblas + /usr/include/openblas-base + /usr/local + /usr/local/include/openblas + /usr/local/include/openblas-base + /opt/OpenBLAS + /usr/local/opt/openblas + + "${PROJECT_SOURCE_DIR}/3rdparty/OpenBLAS" + "${PROJECT_SOURCE_DIR}/thirdparty/OpenBLAS" + ) + +if(CMAKE_CROSSCOMPILING) + set(Open_BLAS_LIB_SEARCH_PATHS + ${Open_BLAS_LIB_SEARCH_PATHS} + + "$ENV{CROSS_ROOT}" + "${CROSS_ROOT}" + ) +endif() + +set(OpenBLAS_LIB_SEARCH_PATHS + ${OpenBLAS_LIB_SEARCH_PATHS} + + "$ENV{OpenBLAS_HOME}" + "${OpenBLAS_HOME}" + "${OpenBLAS_ROOT}" + + / + /lib/openblas-base + /usr + /usr/lib/openblas-base + /usr/local/ + /opt/OpenBLAS + /usr/local/opt/openblas + + "${PROJECT_SOURCE_DIR}/3rdparty/OpenBLAS" + "${PROJECT_SOURCE_DIR}/thirdparty/OpenBLAS" + ) + +find_path(OpenBLAS_INCLUDE_DIRS + NAMES cblas.h + PATHS ${OpenBLAS_INCLUDE_SEARCH_PATHS} + PATH_SUFFIXES include include/openblas) + +set(OpenBLAS_LIB_NAMES openblas libopenblas) + +if(CMAKE_CROSSCOMPILING) + message(STATUS "Will try to link to OpenBLAS statically") + set(OpenBLAS_LIB_NAMES libopenblas.a ${OpenBLAS_LIB_NAMES}) +endif() + +if(MSVC) + # The OpenBLAS distributed in SourceForge has this non-standard file extension: *.a + # From the README there, it shows: + # libopenblas.dll The shared library for Visual Studio and GCC. + # libopenblas.a The static library. Only work with GCC. + # libopenblas.dll.a The import library for Visual Studio. + # We have to use `libopenblas.dll.a` since this is the library file compiled for Visual + # Studio. This might not be ideal for Windows users since they will also need the DLL to be on + # their PATH, but that's how this distribution of OpenBLAS is. + list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES .dll.a) +endif() + +find_library(OpenBLAS_LIBRARIES + NAMES ${OpenBLAS_LIB_NAMES} + PATHS ${OpenBLAS_LIB_SEARCH_PATHS} + PATH_SUFFIXES lib lib64) -file(TO_CMAKE_PATH "$ENV{OpenBLAS_HOME}" OpenBLAS_HOME) -file(TO_CMAKE_PATH "$ENV{OpenBLAS}" OpenBLAS_DIR) - -SET(Open_BLAS_INCLUDE_SEARCH_PATHS - /usr/include - /usr/include/openblas - /usr/include/openblas-base - /usr/local/include - /usr/local/include/openblas - /usr/local/include/openblas-base - /opt/OpenBLAS/include - /usr/local/opt/openblas/include - ${PROJECT_SOURCE_DIR}/3rdparty/OpenBLAS/include - ${PROJECT_SOURCE_DIR}/thirdparty/OpenBLAS/include - ${OpenBLAS_HOME} - ${OpenBLAS_HOME}/include -) - -SET(Open_BLAS_LIB_SEARCH_PATHS - /lib/ - /lib/openblas-base - /lib64/ - /usr/lib - /usr/lib/openblas-base - /usr/lib64 - /usr/local/lib - /usr/local/lib64 - /opt/OpenBLAS/lib - /usr/local/opt/openblas/lib - ${PROJECT_SOURCE_DIR}/3rdparty/OpenBLAS/lib - ${PROJECT_SOURCE_DIR}/thirdparty/OpenBLAS/lib - ${OpenBLAS_DIR} - ${OpenBLAS_DIR}/lib - ${OpenBLAS_HOME} - ${OpenBLAS_HOME}/lib - ) - -FIND_PATH(OpenBLAS_INCLUDE_DIR NAMES cblas.h PATHS ${Open_BLAS_INCLUDE_SEARCH_PATHS}) -FIND_LIBRARY(OpenBLAS_LIB NAMES openblas PATHS ${Open_BLAS_LIB_SEARCH_PATHS}) -IF(NOT OpenBLAS_LIB) - FIND_FILE(OpenBLAS_LIB NAMES libopenblas.dll.a PATHS ${Open_BLAS_LIB_SEARCH_PATHS}) -ENDIF() - -SET(OpenBLAS_FOUND ON) - -# Check include files -IF(NOT OpenBLAS_INCLUDE_DIR) - SET(OpenBLAS_FOUND OFF) - MESSAGE(STATUS "Could not find OpenBLAS include. Turning OpenBLAS_FOUND off") -ENDIF() - -# Check libraries -IF(NOT OpenBLAS_LIB) - SET(OpenBLAS_FOUND OFF) - MESSAGE(STATUS "Could not find OpenBLAS lib. Turning OpenBLAS_FOUND off") -ENDIF() - -IF (OpenBLAS_FOUND) - IF (NOT OpenBLAS_FIND_QUIETLY) - MESSAGE(STATUS "Found OpenBLAS libraries: ${OpenBLAS_LIB}") - MESSAGE(STATUS "Found OpenBLAS include: ${OpenBLAS_INCLUDE_DIR}") - ENDIF (NOT OpenBLAS_FIND_QUIETLY) -ELSE (OpenBLAS_FOUND) - IF (OpenBLAS_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find OpenBLAS") - ENDIF (OpenBLAS_FIND_REQUIRED) -ENDIF (OpenBLAS_FOUND) - -MARK_AS_ADVANCED( - OpenBLAS_INCLUDE_DIR - OpenBLAS_LIB - OpenBLAS -) +check_openblas_compiles(OpenBLAS_MANUAL_MODE) +if (NOT OpenBLAS_MANUAL_MODE) + unset_openblas_variables() +endif() +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenBLAS DEFAULT_MSG OpenBLAS_INCLUDE_DIRS OpenBLAS_LIBRARIES)