diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000000..af7e9806aa --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,62 @@ +name: cuML wheels + +on: + workflow_call: + inputs: + versioneer-override: + type: string + default: '' + build-tag: + type: string + default: '' + branch: + required: true + type: string + date: + required: true + type: string + sha: + required: true + type: string + build-type: + type: string + default: nightly + +concurrency: + group: "cuml-${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + cuml-wheel: + uses: rapidsai/shared-action-workflows/.github/workflows/wheels-manylinux.yml@main + with: + repo: rapidsai/cuml + + build-type: ${{ inputs.build-type }} + branch: ${{ inputs.branch }} + sha: ${{ inputs.sha }} + date: ${{ inputs.date }} + + package-dir: python + package-name: cuml + + # Note that this approach to cloning repos obviates any modification to + # the CMake variables in get_cumlprims_mg.cmake since CMake will just use + # the clone as is. + extra-repo: rapidsai/cumlprims_mg + extra-repo-sha: branch-22.12 + extra-repo-deploy-key: CUMLPRIMS_SSH_PRIVATE_DEPLOY_KEY + + python-package-versioneer-override: ${{ inputs.versioneer-override }} + python-package-build-tag: ${{ inputs.build-tag }} + + skbuild-configure-options: "-DCUML_BUILD_WHEELS=ON -DDETECT_CONDA_ENV=OFF -DCPM_cumlprims_mg_SOURCE=/project/cumlprims_mg/" + + # Always want to test against latest dask/distributed. + test-before-amd64: "pip install git+https://github.com/dask/dask.git@main git+https://github.com/dask/distributed.git@main git+https://github.com/rapidsai/dask-cuda.git@branch-22.12" + # On arm also need to install cupy from the specific webpage and CMake + # because treelite needs to be compiled (no wheels available for arm). + test-before-arm64: "pip install cupy-cuda11x -f https://pip.cupy.dev/aarch64 && pip install cmake && pip install git+https://github.com/dask/dask.git@main git+https://github.com/dask/distributed.git@main git+https://github.com/rapidsai/dask-cuda.git@branch-22.12" + test-extras: test + test-unittest: "pytest -v ./python/cuml/tests -k 'not test_silhouette_score_batched'" + secrets: inherit diff --git a/.gitignore b/.gitignore index 6a85de8349..b1bc1283dd 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ dask-worker-space/ tmp/ .hypothesis wheels/ +wheelhouse/ _skbuild/ ## files pickled in notebook when ran during python docstring generation diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index d42f28e1b8..9e9e040a56 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -28,11 +28,6 @@ rapids_cuda_init_architectures(CUML) project(CUML VERSION 22.12.00 LANGUAGES CXX CUDA) -# Needed because GoogleBenchmark changes the state of FindThreads.cmake, causing subsequent runs to -# have different values for the `Threads::Threads` target. Setting this flag ensures -# `Threads::Threads` is the same value in first run and subsequent runs. -set(THREADS_PREFER_PTHREAD_FLAG ON) - # Write the version header rapids_cmake_write_version_file(include/cuml/version_config.hpp) @@ -71,6 +66,15 @@ option(CUDA_STATIC_RUNTIME "Statically link the CUDA toolkit runtime and librari option(CUML_USE_RAFT_STATIC "Build and statically link the RAFT libraries" OFF) option(CUML_USE_FAISS_STATIC "Build and statically link the FAISS library for nearest neighbors search on GPU" OFF) option(CUML_USE_TREELITE_STATIC "Build and statically link the treelite library" OFF) +option(CUML_EXPORT_TREELITE_LINKAGE "Whether to publicly or privately link treelite to libcuml++" OFF) +option(CUML_USE_CUMLPRIMS_MG_STATIC "Build and statically link the cumlprims_mg library" OFF) +# The options below allow incorporating libcuml into another build process +# without installing all its components. This is useful if total file size is +# at a premium and we do not expect other consumers to use any APIs of the +# dependency except those that are directly linked to by the dependent library. +option(CUML_EXCLUDE_RAFT_FROM_ALL "Exclude RAFT targets from cuML's 'all' target" OFF) +option(CUML_EXCLUDE_TREELITE_FROM_ALL "Exclude Treelite targets from cuML's 'all' target" OFF) +option(CUML_EXCLUDE_CUMLPRIMS_MG_FROM_ALL "Exclude cumlprims_mg targets from cuML's 'all' target" OFF) option(CUML_RAFT_CLONE_ON_PIN "Explicitly clone RAFT branch when pinned to non-feature branch" ON) message(VERBOSE "CUML_CPP: Building libcuml_c shared library. Contains the cuML C API: ${BUILD_CUML_C_LIBRARY}") @@ -104,6 +108,13 @@ set(RMM_LOGGING_LEVEL "INFO" CACHE STRING "Choose the logging level.") set_property(CACHE RMM_LOGGING_LEVEL PROPERTY STRINGS "TRACE" "DEBUG" "INFO" "WARN" "ERROR" "CRITICAL" "OFF") message(VERBOSE "CUML_CPP: RMM_LOGGING_LEVEL = '${RMM_LOGGING_LEVEL}'.") +if(BUILD_CUML_TESTS OR BUILD_PRIMS_TESTS) + # Needed because GoogleBenchmark changes the state of FindThreads.cmake, causing subsequent runs to + # have different values for the `Threads::Threads` target. Setting this flag ensures + # `Threads::Threads` is the same value in first run and subsequent runs. + set(THREADS_PREFER_PTHREAD_FLAG ON) +endif() + ############################################################################## # - Target names ------------------------------------------------------------- @@ -132,15 +143,7 @@ endif() set(_ctk_static_suffix "") if(CUDA_STATIC_RUNTIME) - # If we're statically linking CTK cuBLAS, - # we also want to statically link BLAS - set(BLA_STATIC ON) - set(_ctk_static_suffix "_static") - set(_ctk_static_suffix_cufft "_static_nocallback") - # Control legacy FindCUDA.cmake behavior too - # Remove this after we push it into rapids-cmake: - # https://github.com/rapidsai/rapids-cmake/pull/259 - set(CUDA_USE_STATIC_CUDA_RUNTIME ON) + set(_ctk_static_suffix "_static_nocallback") endif() if (NOT DISABLE_OPENMP) @@ -212,8 +215,11 @@ endif() # add third party dependencies using CPM rapids_cpm_init() +rapids_cmake_install_lib_dir(lib_dir) -find_package(Threads) +if(BUILD_CUML_TESTS OR BUILD_PRIMS_TESTS) + find_package(Threads) +endif() include(cmake/thirdparty/get_raft.cmake) @@ -262,6 +268,20 @@ SECTIONS ]=]) endif() +# Copy the interface include directories from INCLUDED_TARGET to TARGET. +# This is necessary when INCLUDED_TARGET was compiled statically but includes +# public APIs that may still require consumers to have the same interface +# headers available. +function(copy_interface_excludes) + set(_options "") + set(_one_value TARGET INCLUDED_TARGET) + set(_multi_value "") + cmake_parse_arguments(_CUML_INCLUDES "${_options}" "${_one_value}" + "${_multi_value}" ${ARGN}) + get_target_property(_includes ${_CUML_INCLUDES_INCLUDED_TARGET} INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(${_CUML_INCLUDES_TARGET} PUBLIC ${_includes}) +endfunction() + if(BUILD_CUML_CPP_LIBRARY) # single GPU components @@ -534,19 +554,67 @@ if(BUILD_CUML_CPP_LIBRARY) $ ) + set(_cuml_cpp_public_libs) + set(_cuml_cpp_private_libs) + + if(CUML_USE_RAFT_STATIC AND (TARGET raft::raft)) + copy_interface_excludes(INCLUDED_TARGET raft::raft TARGET ${CUML_CPP_TARGET}) + + if(CUML_USE_RAFT_NN AND (TARGET faiss::faiss)) + copy_interface_excludes(INCLUDED_TARGET faiss::faiss TARGET ${CUML_CPP_TARGET}) + endif() + + if(CUML_USE_RAFT_DIST AND (TARGET cuco::cuco)) + list(APPEND _cuml_cpp_private_libs cuco::cuco) + endif() + endif() + + if(CUML_USE_TREELITE_STATIC AND (TARGET treelite::treelite_static)) + # By default, TREELITE_LIBS will contain both treelite::treelite_static and + # treelite::treelite_runtime_static if we are linking statically, but these + # two targets have duplicate symbols so we can only link to one of them. + set(TREELITE_LIBS treelite::treelite_static) + + copy_interface_excludes(INCLUDED_TARGET treelite::treelite_static TARGET ${CUML_CPP_TARGET}) + elseif(CUML_EXPORT_TREELITE_LINKAGE) + list(APPEND _cuml_cpp_public_libs ${TREELITE_LIBS}) + endif() + + if(CUML_USE_CUMLPRIMS_MG_STATIC AND (TARGET cumlprims_mg::cumlprims_mg)) + copy_interface_excludes(INCLUDED_TARGET cumlprims_mg::cumlprims_mg TARGET ${CUML_CPP_TARGET}) + endif() + + # These are always private: + list(APPEND _cuml_cpp_private_libs + $ + $<$:CUDA::cufft${_ctk_static_suffix}> + ${TREELITE_LIBS} + ${OpenMP_CXX_LIB_NAMES} + $<$,$>:NCCL::NCCL> + $<$:${MPI_CXX_LIBRARIES}> + ) + + set(_cuml_cpp_libs_var_name "_cuml_cpp_public_libs") + if(CUDA_STATIC_RUNTIME) + set(_cuml_cpp_libs_var_name "_cuml_cpp_private_libs") + # Add CTK include paths because we're going to make our CTK library links private below + target_include_directories(${CUML_CPP_TARGET} SYSTEM PUBLIC ${CUDAToolkit_INCLUDE_DIRS}) + endif() + + # The visibility of these depend on whether we're linking the CTK statically, + # because cumlprims_mg and cuML inherit their CUDA libs from the raft::raft + # INTERFACE target. + list(APPEND ${_cuml_cpp_libs_var_name} + raft::raft + $<$:raft::nn> + $<$:raft::distance> + $ + ) + target_link_libraries(${CUML_CPP_TARGET} - PUBLIC - raft::raft - $<$:raft::nn> - $<$:raft::distance> - PRIVATE - $<$:CUDA::cufft${_ctk_static_suffix_cufft}> - ${TREELITE_LIBS} - $<$:GPUTreeShap::GPUTreeShap> - ${OpenMP_CXX_LIB_NAMES} - $<$,$>:NCCL::NCCL> - $<$:${MPI_CXX_LIBRARIES}> - $<$:cumlprims_mg::cumlprims_mg> + PUBLIC rmm::rmm + ${_cuml_cpp_public_libs} + PRIVATE ${_cuml_cpp_private_libs} ) # If we export the libdmlc symbols, they can lead to weird crashes with other @@ -556,7 +624,6 @@ if(BUILD_CUML_CPP_LIBRARY) target_link_options(${CUML_CPP_TARGET} PRIVATE "-Wl,--exclude-libs,libprotobuf.a") # ensure CUDA symbols aren't relocated to the middle of the debug build binaries target_link_options(${CUML_CPP_TARGET} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") - endif() ############################################################################# @@ -611,7 +678,6 @@ endif() # ################################################################################################### # # - install targets ------------------------------------------------------------------------------- -rapids_cmake_install_lib_dir( lib_dir ) include(CPack) set(CUML_TARGETS ${CUML_CPP_TARGET}) diff --git a/cpp/cmake/thirdparty/get_cumlprims_mg.cmake b/cpp/cmake/thirdparty/get_cumlprims_mg.cmake index 16137ed340..0bbc91fc17 100644 --- a/cpp/cmake/thirdparty/get_cumlprims_mg.cmake +++ b/cpp/cmake/thirdparty/get_cumlprims_mg.cmake @@ -19,12 +19,20 @@ set(CUML_BRANCH_VERSION_cumlprims_mg "${CUML_VERSION_MAJOR}.${CUML_VERSION_MINOR function(find_and_configure_cumlprims_mg) - set(oneValueArgs VERSION FORK PINNED_TAG CLONE_ON_PIN) + set(oneValueArgs VERSION FORK PINNED_TAG BUILD_STATIC EXCLUDE_FROM_ALL CLONE_ON_PIN) cmake_parse_arguments(PKG "" "${oneValueArgs}" "" ${ARGN} ) if(PKG_CLONE_ON_PIN AND NOT PKG_PINNED_TAG STREQUAL "branch-${CUML_BRANCH_VERSION_cumlprims_mg}") message("Pinned tag found: ${PKG_PINNED_TAG}. Cloning cumlprims locally.") set(CPM_DOWNLOAD_cumlprims_mg ON) + elseif(PKG_BUILD_STATIC AND (NOT CPM_cumlprims_mg_SOURCE)) + message(STATUS "CUML: Cloning cumlprims_mg locally to build static libraries.") + set(CPM_DOWNLOAD_cumlprims_mg ON) + endif() + + set(CUMLPRIMS_MG_BUILD_SHARED_LIBS ON) + if(PKG_BUILD_STATIC) + set(CUMLPRIMS_MG_BUILD_SHARED_LIBS OFF) endif() rapids_cpm_find(cumlprims_mg ${PKG_VERSION} @@ -32,8 +40,14 @@ function(find_and_configure_cumlprims_mg) BUILD_EXPORT_SET cuml-exports INSTALL_EXPORT_SET cuml-exports CPM_ARGS - GIT_REPOSITORY git@github.com:${PKG_FORK}/cumlprims_mg.git - GIT_TAG ${PKG_PINNED_TAG} + GIT_REPOSITORY git@github.com:${PKG_FORK}/cumlprims_mg.git + GIT_TAG ${PKG_PINNED_TAG} + EXCLUDE_FROM_ALL ${PKG_EXCLUDE_FROM_ALL} + SOURCE_SUBDIR cpp + OPTIONS + "BUILD_TESTS OFF" + "BUILD_BENCHMARKS OFF" + "BUILD_SHARED_LIBS ${CUMLPRIMS_MG_BUILD_SHARED_LIBS}" ) endfunction() @@ -48,9 +62,11 @@ endfunction() # cumlprims_mg as part of building cuml itself, set the CMake variable # `-D CPM_cumlprims_mg_SOURCE=/path/to/cumlprims_mg` ### -find_and_configure_cumlprims_mg(VERSION ${CUML_MIN_VERSION_cumlprims_mg} +find_and_configure_cumlprims_mg(VERSION ${CUML_MIN_VERSION_cumlprims_mg} FORK rapidsai PINNED_TAG branch-${CUML_BRANCH_VERSION_cumlprims_mg} + BUILD_STATIC ${CUML_USE_CUMLPRIMS_MG_STATIC} + EXCLUDE_FROM_ALL ${CUML_EXCLUDE_CUMLPRIMS_MG_FROM_ALL} # When PINNED_TAG above doesn't match cuml, # force local cumlprims_mg clone in build directory # even if it's already installed. diff --git a/cpp/cmake/thirdparty/get_raft.cmake b/cpp/cmake/thirdparty/get_raft.cmake index 9fe2e222b5..feba32e1fa 100644 --- a/cpp/cmake/thirdparty/get_raft.cmake +++ b/cpp/cmake/thirdparty/get_raft.cmake @@ -18,54 +18,60 @@ set(CUML_MIN_VERSION_raft "${CUML_VERSION_MAJOR}.${CUML_VERSION_MINOR}.00") set(CUML_BRANCH_VERSION_raft "${CUML_VERSION_MAJOR}.${CUML_VERSION_MINOR}") function(find_and_configure_raft) - set(oneValueArgs VERSION FORK PINNED_TAG USE_RAFT_DIST USE_RAFT_NN USE_RAFT_STATIC USE_FAISS_STATIC CLONE_ON_PIN NVTX) + set(oneValueArgs VERSION FORK PINNED_TAG EXCLUDE_FROM_ALL USE_RAFT_DIST USE_RAFT_NN USE_RAFT_STATIC USE_FAISS_STATIC CLONE_ON_PIN NVTX) cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if(PKG_CLONE_ON_PIN AND NOT PKG_PINNED_TAG STREQUAL "branch-${CUML_BRANCH_VERSION_raft}") - message(STATUS "CUML: RAFT pinned tag found: ${PKG_PINNED_TAG}. Cloning raft locally.") - set(CPM_DOWNLOAD_raft ON) - endif() - - if(PKG_USE_RAFT_STATIC) - message(STATUS "CUML: Cloning raft locally to build static libraries.") - set(CPM_DOWNLOAD_raft ON) + message(STATUS "CUML: RAFT pinned tag found: ${PKG_PINNED_TAG}. Cloning raft locally.") + set(CPM_DOWNLOAD_raft ON) + elseif(PKG_USE_RAFT_STATIC AND (NOT CPM_raft_SOURCE)) + message(STATUS "CUML: Cloning raft locally to build static libraries.") + set(CPM_DOWNLOAD_raft ON) endif() if(PKG_USE_RAFT_DIST) - string(APPEND RAFT_COMPONENTS "distance") + string(APPEND RAFT_COMPONENTS "distance") endif() + if(PKG_USE_RAFT_NN) - string(APPEND RAFT_COMPONENTS " nn") + string(APPEND RAFT_COMPONENTS " nn") endif() if(PKG_USE_RAFT_DIST AND PKG_USE_RAFT_NN) - set(RAFT_COMPILE_LIBRARIES ON) + set(RAFT_COMPILE_LIBRARIES ON) else() - set(RAFT_COMPILE_LIBRARIES OFF) + set(RAFT_COMPILE_LIBRARIES OFF) endif() # We need to set this each time so that on subsequent calls to cmake # the raft-config.cmake re-evaluates the RAFT_NVTX value set(RAFT_NVTX ${PKG_NVTX}) + set(RAFT_BUILD_SHARED_LIBS ON) + if(${PKG_USE_RAFT_STATIC}) + set(RAFT_BUILD_SHARED_LIBS OFF) + endif() + message(VERBOSE "CUML: raft FIND_PACKAGE_ARGUMENTS COMPONENTS ${RAFT_COMPONENTS}") rapids_cpm_find(raft ${PKG_VERSION} - GLOBAL_TARGETS raft::raft - BUILD_EXPORT_SET cuml-exports - INSTALL_EXPORT_SET cuml-exports - COMPONENTS ${RAFT_COMPONENTS} - CPM_ARGS - GIT_REPOSITORY https://github.com/${PKG_FORK}/raft.git - GIT_TAG ${PKG_PINNED_TAG} - SOURCE_SUBDIR cpp - OPTIONS - "BUILD_TESTS OFF" - "RAFT_COMPILE_LIBRARIES ${RAFT_COMPILE_LIBRARIES}" - "RAFT_COMPILE_NN_LIBRARY ${PKG_USE_RAFT_NN}" - "RAFT_COMPILE_DIST_LIBRARY ${PKG_USE_RAFT_DIST}" - "RAFT_USE_FAISS_STATIC ${PKG_USE_FAISS_STATIC}" + GLOBAL_TARGETS raft::raft + BUILD_EXPORT_SET cuml-exports + INSTALL_EXPORT_SET cuml-exports + COMPONENTS ${RAFT_COMPONENTS} + CPM_ARGS + GIT_REPOSITORY https://github.com/${PKG_FORK}/raft.git + GIT_TAG ${PKG_PINNED_TAG} + SOURCE_SUBDIR cpp + EXCLUDE_FROM_ALL ${PKG_EXCLUDE_FROM_ALL} + OPTIONS + "BUILD_TESTS OFF" + "BUILD_SHARED_LIBS ${RAFT_BUILD_SHARED_LIBS}" + "RAFT_COMPILE_LIBRARIES ${RAFT_COMPILE_LIBRARIES}" + "RAFT_COMPILE_NN_LIBRARY ${PKG_USE_RAFT_NN}" + "RAFT_COMPILE_DIST_LIBRARY ${PKG_USE_RAFT_DIST}" + "RAFT_USE_FAISS_STATIC ${PKG_USE_FAISS_STATIC}" ) if(raft_ADDED) @@ -82,8 +88,8 @@ endfunction() # CPM_raft_SOURCE=/path/to/local/raft find_and_configure_raft(VERSION ${CUML_MIN_VERSION_raft} FORK rapidsai - PINNED_TAG branch-${CUML_BRANCH_VERSION_raft} - + PINNED_TAG branch-${CUML_BRANCH_VERSION_raft} + EXCLUDE_FROM_ALL ${CUML_EXCLUDE_RAFT_FROM_ALL} # When PINNED_TAG above doesn't match cuml, # force local raft clone in build directory # even if it's already installed. diff --git a/cpp/cmake/thirdparty/get_treelite.cmake b/cpp/cmake/thirdparty/get_treelite.cmake index 199b2a5fdb..ec6929be9e 100644 --- a/cpp/cmake/thirdparty/get_treelite.cmake +++ b/cpp/cmake/thirdparty/get_treelite.cmake @@ -14,16 +14,17 @@ # limitations under the License. #============================================================================= + function(find_and_configure_treelite) - set(oneValueArgs VERSION PINNED_TAG BUILD_STATIC_LIBS) + set(oneValueArgs VERSION PINNED_TAG EXCLUDE_FROM_ALL BUILD_STATIC_LIBS) cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if(NOT PKG_BUILD_STATIC_LIBS) - list(APPEND TREELITE_LIBS treelite::treelite treelite::treelite_runtime) + list(APPEND TREELITE_LIBS treelite::treelite treelite::treelite_runtime) else() - list(APPEND TREELITE_LIBS treelite::treelite_static treelite::treelite_runtime_static) + list(APPEND TREELITE_LIBS treelite::treelite_static treelite::treelite_runtime_static) endif() rapids_cpm_find(Treelite ${PKG_VERSION} @@ -32,6 +33,7 @@ function(find_and_configure_treelite) CPM_ARGS GIT_REPOSITORY https://github.com/dmlc/treelite.git GIT_TAG ${PKG_PINNED_TAG} + EXCLUDE_FROM_ALL ${PKG_EXCLUDE_FROM_ALL} OPTIONS "USE_OPENMP ON" "BUILD_STATIC_LIBS ${PKG_BUILD_STATIC_LIBS}" @@ -90,4 +92,5 @@ endfunction() find_and_configure_treelite(VERSION 3.0.1 PINNED_TAG cb09d539dce7e67f60cc33e4fac6b95b7185847a + EXCLUDE_FROM_ALL ${CUML_EXCLUDE_TREELITE_FROM_ALL} BUILD_STATIC_LIBS ${CUML_USE_TREELITE_STATIC}) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 0a2131cdde..6f2f59189d 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -18,6 +18,11 @@ include(../fetch_rapids.cmake) set(CUML_VERSION 22.12.00) +# We always need CUDA for cuML because the raft dependency brings in a +# header-only cuco dependency that enables CUDA unconditionally. +include(rapids-cuda) +rapids_cuda_init_architectures(cuml-python) + project( cuml-python VERSION ${CUML_VERSION} @@ -25,12 +30,13 @@ project( # language to be enabled here. The test project that is built in scikit-build to verify # various linking options for the python library is hardcoded to build with C, so until # that is fixed we need to keep C. - C CXX + C CXX CUDA ) ################################################################################ # - User Options -------------------------------------------------------------- option(FIND_CUML_CPP "Search for existing CUML C++ installations before defaulting to local files" OFF) +option(CUML_BUILD_WHEELS "Whether this build is generating a Python wheel." OFF) option(SINGLEGPU "Disable all mnmg components and comms libraries" OFF) set(CUML_RAFT_CLONE_ON_PIN OFF) @@ -44,64 +50,62 @@ set(CUML_CPP_TARGET "cuml++") ################################################################################ # - Process User Options ------------------------------------------------------ - -# If the user requested it, we attempt to find cuml. +# If the user requested it, we attempt to find cuml. if(FIND_CUML_CPP) + # We need to call get_treelite explicitly because we need the correct + # ${TREELITE_LIBS} definition for RF include(rapids-cpm) include(rapids-export) rapids_cpm_init() - include(rapids-cuda) - rapids_cuda_init_architectures(cuml-python) - enable_language(CUDA) - - # variables used by get_raft.cmake, we can get rid of them when this is solved: - # https://github.com/rapidsai/rapids-cmake/issues/228 - set(CUML_VERSION_MAJOR "${cuml-python_VERSION_MAJOR}") - set(CUML_VERSION_MINOR "${cuml-python_VERSION_MINOR}") - set(CUML_USE_RAFT_NN ON) - set(CUML_USE_RAFT_DIST ON) - - # We need to call get_raft due to cuML asking for raft::nn and - # raft::distance targets - # see issue https://github.com/rapidsai/cuml/issues/4843 - include(../cpp/cmake/thirdparty/get_raft.cmake) - - # We need to call get_treelite explicitly because we need the correct - # ${TREELITE_LIBS} definition for RF include(../cpp/cmake/thirdparty/get_treelite.cmake) find_package(cuml ${CUML_VERSION} REQUIRED) - else() set(cuml_FOUND OFF) endif() include(rapids-cython) -if(NOT cuml_FOUND) - # TODO: This will not be necessary once we upgrade to CMake 3.22, which will pull in the required - # languages for the C++ project even if this project does not require those languges. - include(rapids-cuda) - rapids_cuda_init_architectures(cuml-python) - enable_language(CUDA) - - # Since cuml only enables CUDA optionally, we need to manually include the file that - # rapids_cuda_init_architectures relies on `project` including. - include("${CMAKE_PROJECT_cuml-python_INCLUDE}") +if(CUML_BUILD_WHEELS) + set(CUML_PYTHON_TREELITE_TARGET treelite::treelite_static) +else() + set(CUML_PYTHON_TREELITE_TARGET treelite::treelite) +endif() +if(NOT cuml_FOUND) set(BUILD_CUML_TESTS OFF) set(BUILD_PRIMS_TESTS OFF) set(BUILD_CUML_C_LIBRARY OFF) set(BUILD_CUML_EXAMPLES OFF) set(BUILD_CUML_BENCH OFF) set(BUILD_CUML_PRIMS_BENCH OFF) - message(STATUS "installing packages") - add_subdirectory(../cpp cuml-cpp) + set(CUML_EXPORT_TREELITE_LINKAGE ON) + + set(_exclude_from_all "") + if(CUML_BUILD_WHEELS) + # Statically link dependencies if building wheels + set(CUDA_STATIC_RUNTIME ON) + set(CUML_USE_RAFT_STATIC ON) + set(CUML_USE_FAISS_STATIC ON) + set(CUML_USE_TREELITE_STATIC ON) + set(CUML_USE_CUMLPRIMS_MG_STATIC ON) + # Don't install the static libs into wheels + set(CUML_EXCLUDE_RAFT_FROM_ALL ON) + set(RAFT_EXCLUDE_FAISS_FROM_ALL ON) + set(CUML_EXCLUDE_TREELITE_FROM_ALL ON) + set(CUML_EXCLUDE_CUMLPRIMS_MG_FROM_ALL ON) + + # Don't install the cuML C++ targets into wheels + set(_exclude_from_all EXCLUDE_FROM_ALL) + endif() + + add_subdirectory(../cpp cuml-cpp ${_exclude_from_all}) set(cython_lib_dir cuml) install(TARGETS ${CUML_CPP_TARGET} DESTINATION ${cython_lib_dir}) endif() set(cuml_sg_libraries cuml::${CUML_CPP_TARGET}) +set(cuml_mg_libraries cuml::${CUML_CPP_TARGET}) if(NOT SINGLEGPU) include(../cpp/cmake/thirdparty/get_cumlprims_mg.cmake) diff --git a/python/LICENSE b/python/LICENSE new file mode 120000 index 0000000000..ea5b60640b --- /dev/null +++ b/python/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/python/_custom_build/backend.py b/python/_custom_build/backend.py new file mode 100644 index 0000000000..46a7d79855 --- /dev/null +++ b/python/_custom_build/backend.py @@ -0,0 +1,40 @@ +# Copyright (c) 2022, NVIDIA CORPORATION. + +"""Custom build backend for cuml to get versioned requirements. + +Based on https://setuptools.pypa.io/en/latest/build_meta.html +""" +import os +from functools import wraps + +from setuptools import build_meta as _orig + +# Alias the required bits +prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel +build_wheel = _orig.build_wheel +build_sdist = _orig.build_sdist + + +def replace_requirements(func): + @wraps(func) + def wrapper(config_settings=None): + orig_list = getattr(_orig, func.__name__)(config_settings) + suffix = os.getenv('RAPIDS_PY_WHEEL_CUDA_SUFFIX', default='') + append_list = [ + f"rmm{suffix}", + f"pylibraft{suffix}", + ] + return orig_list + append_list + + return wrapper + + +get_requires_for_build_wheel = replace_requirements( + _orig.get_requires_for_build_wheel +) +get_requires_for_build_sdist = replace_requirements( + _orig.get_requires_for_build_sdist +) +get_requires_for_build_editable = replace_requirements( + _orig.get_requires_for_build_editable +) diff --git a/python/cuml/cluster/CMakeLists.txt b/python/cuml/cluster/CMakeLists.txt index 53e0d4986f..de5a23b655 100644 --- a/python/cuml/cluster/CMakeLists.txt +++ b/python/cuml/cluster/CMakeLists.txt @@ -30,7 +30,7 @@ add_subdirectory(hdbscan) rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES $,${cuml_sg_libraries},${cuml_mg_libraries}> + LINKED_LIBRARIES "${cuml_mg_libraries}" MODULE_PREFIX cluster_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/cluster/hdbscan/CMakeLists.txt b/python/cuml/cluster/hdbscan/CMakeLists.txt index 0a242a5768..2b7722eae6 100644 --- a/python/cuml/cluster/hdbscan/CMakeLists.txt +++ b/python/cuml/cluster/hdbscan/CMakeLists.txt @@ -19,7 +19,7 @@ set(cython_sources rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES ${cuml_sg_libraries} + LINKED_LIBRARIES "${cuml_sg_libraries}" MODULE_PREFIX cluster_hdbscan_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/common/CMakeLists.txt b/python/cuml/common/CMakeLists.txt index 5e49f65a8a..ea81f3c623 100644 --- a/python/cuml/common/CMakeLists.txt +++ b/python/cuml/common/CMakeLists.txt @@ -29,7 +29,7 @@ endif() rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES $,${cuml_sg_libraries},${cuml_mg_libraries}> + LINKED_LIBRARIES "${cuml_mg_libraries}" MODULE_PREFIX common_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/common/input_utils.py b/python/cuml/common/input_utils.py index 971dac4aa9..d450d3eb5b 100644 --- a/python/cuml/common/input_utils.py +++ b/python/cuml/common/input_utils.py @@ -314,7 +314,10 @@ def check_order(arr_order): # format conversion - if isinstance(X, (dask_cudf.core.Series, dask_cudf.core.DataFrame)): + if ( + has_dask_cudf() + and isinstance(X, (dask_cudf.core.Series, dask_cudf.core.DataFrame)) + ): # TODO: Warn, but not when using dask_sql X = X.compute() @@ -573,7 +576,10 @@ def convert_dtype(X, if the conversion would lose information. """ - if isinstance(X, (dask_cudf.core.Series, dask_cudf.core.DataFrame)): + if ( + has_dask_cudf() + and isinstance(X, (dask_cudf.core.Series, dask_cudf.core.DataFrame)) + ): # TODO: Warn, but not when using dask_sql X = X.compute() diff --git a/python/cuml/decomposition/CMakeLists.txt b/python/cuml/decomposition/CMakeLists.txt index 668952a178..70bcdb0de5 100644 --- a/python/cuml/decomposition/CMakeLists.txt +++ b/python/cuml/decomposition/CMakeLists.txt @@ -29,7 +29,7 @@ endif() rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES $,${cuml_sg_libraries},${cuml_mg_libraries}> + LINKED_LIBRARIES "${cuml_mg_libraries}" MODULE_PREFIX decomposition_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/ensemble/CMakeLists.txt b/python/cuml/ensemble/CMakeLists.txt index 34c8456bc0..957440a4e0 100644 --- a/python/cuml/ensemble/CMakeLists.txt +++ b/python/cuml/ensemble/CMakeLists.txt @@ -12,8 +12,6 @@ # the License. # ============================================================================= - - set(cython_sources randomforest_common.pyx randomforest_shared.pyx @@ -21,7 +19,7 @@ set(cython_sources randomforestregressor.pyx ) -set(linked_libraries +set(linked_libraries ${cuml_sg_libraries} ${TREELITE_LIBS}) diff --git a/python/cuml/explainer/CMakeLists.txt b/python/cuml/explainer/CMakeLists.txt index 17fb835f49..70f41bd14f 100644 --- a/python/cuml/explainer/CMakeLists.txt +++ b/python/cuml/explainer/CMakeLists.txt @@ -12,19 +12,21 @@ # the License. # ============================================================================= - - set(cython_sources base.pyx kernel_shap.pyx permutation_shap.pyx tree_shap.pyx ) +set(linked_libraries + "${cuml_sg_libraries}" + "${CUML_PYTHON_TREELITE_TARGET}" +) rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES "${cuml_sg_libraries}" + LINKED_LIBRARIES "${linked_libraries}" MODULE_PREFIX explainer_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/fil/CMakeLists.txt b/python/cuml/fil/CMakeLists.txt index 69eac4d2ea..b720caac78 100644 --- a/python/cuml/fil/CMakeLists.txt +++ b/python/cuml/fil/CMakeLists.txt @@ -17,11 +17,15 @@ set(cython_sources fil.pyx ) +set(linked_libraries + "${cuml_sg_libraries}" + "${CUML_PYTHON_TREELITE_TARGET}" +) rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES "${cuml_sg_libraries}" + LINKED_LIBRARIES "${linked_libraries}" MODULE_PREFIX fil_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/linear_model/CMakeLists.txt b/python/cuml/linear_model/CMakeLists.txt index f9e6981d73..6bf5b9dc95 100644 --- a/python/cuml/linear_model/CMakeLists.txt +++ b/python/cuml/linear_model/CMakeLists.txt @@ -35,7 +35,7 @@ endif() rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES $,${cuml_sg_libraries},${cuml_mg_libraries}> + LINKED_LIBRARIES "${cuml_mg_libraries}" MODULE_PREFIX linear_model_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/metrics/cluster/CMakeLists.txt b/python/cuml/metrics/cluster/CMakeLists.txt index 179f6c70f3..d678de583e 100644 --- a/python/cuml/metrics/cluster/CMakeLists.txt +++ b/python/cuml/metrics/cluster/CMakeLists.txt @@ -31,4 +31,3 @@ rapids_cython_create_modules( LINKED_LIBRARIES "${cuml_sg_libraries}" ASSOCIATED_TARGETS cuml ) - diff --git a/python/cuml/neighbors/CMakeLists.txt b/python/cuml/neighbors/CMakeLists.txt index 3af0257f5e..cd3e671318 100644 --- a/python/cuml/neighbors/CMakeLists.txt +++ b/python/cuml/neighbors/CMakeLists.txt @@ -31,7 +31,7 @@ endif() rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES $,${cuml_sg_libraries},${cuml_mg_libraries}> + LINKED_LIBRARIES "${cuml_mg_libraries}" MODULE_PREFIX neighbors_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/solvers/CMakeLists.txt b/python/cuml/solvers/CMakeLists.txt index e8b2137cd8..15404c372d 100644 --- a/python/cuml/solvers/CMakeLists.txt +++ b/python/cuml/solvers/CMakeLists.txt @@ -29,7 +29,7 @@ endif() rapids_cython_create_modules( CXX SOURCE_FILES "${cython_sources}" - LINKED_LIBRARIES $,${cuml_sg_libraries},${cuml_mg_libraries}> + LINKED_LIBRARIES "${cuml_mg_libraries}" MODULE_PREFIX solvers_ ASSOCIATED_TARGETS cuml ) diff --git a/python/cuml/tests/conftest.py b/python/cuml/tests/conftest.py index 0515c952fa..686acd4d06 100644 --- a/python/cuml/tests/conftest.py +++ b/python/cuml/tests/conftest.py @@ -100,7 +100,7 @@ def pytest_collection_modifyitems(config, items): # Mark the tests as "skip" if needed if not should_run_unit: skip_unit = pytest.mark.skip( - reason="Stress tests run with --run_unit flag.") + reason="Unit tests run with --run_unit flag.") for item in items: if "unit" in item.keywords: item.add_marker(skip_unit) diff --git a/python/cuml/tests/test_doctest.py b/python/cuml/tests/test_doctest.py index 1ae20238db..6ff940af09 100644 --- a/python/cuml/tests/test_doctest.py +++ b/python/cuml/tests/test_doctest.py @@ -104,7 +104,20 @@ def test_docstring(docstring): with contextlib.redirect_stdout(doctest_stdout): runner.run(docstring) results = runner.summarize() - assert not results.failed, ( - f"{results.failed} of {results.attempted} doctests failed for " - f"{docstring.name}:\n{doctest_stdout.getvalue()}" - ) + try: + assert not results.failed, ( + f"{results.failed} of {results.attempted} doctests failed for " + f"{docstring.name}:\n{doctest_stdout.getvalue()}" + ) + except AssertionError: + # If some failed but all the failures were due to lack of multiGPU + # support, we can skip. This code assumes that any MG-related failures + # mean that all doctests failed for that reason, which is heavy-handed + # and could miss a few things but is much easier than trying to + # identify every line corresponding to any Exception raised. + if ( + "cuML has not been built with multiGPU support" + in doctest_stdout.getvalue() + ): + pytest.skip("Doctest requires MG support.") + raise diff --git a/python/pyproject.toml b/python/pyproject.toml index 0261c0b09d..2afcab578c 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -21,4 +21,9 @@ requires = [ "scikit-build>=0.13.1", "cmake>=3.23.1,!=3.25.0", "ninja", + "treelite==3.0.1", + "treelite_runtime==3.0.1", + "versioneer", ] +build-backend = "backend" +backend-path = ["_custom_build"] diff --git a/python/setup.py b/python/setup.py index e17a513218..65cb110322 100644 --- a/python/setup.py +++ b/python/setup.py @@ -14,6 +14,7 @@ # limitations under the License. # +import glob import os import shutil import sys @@ -21,36 +22,50 @@ from setuptools import find_packages -from setuputils import clean_folder -from setuputils import get_environment_option -from setuputils import get_cli_option - import versioneer from skbuild import setup -install_requires = ['numba', 'cython'] ############################################################################## -# - Print of build options used by setup.py -------------------------------- +# - Helper functions +def get_cli_option(name): + if name in sys.argv: + print("-- Detected " + str(name) + " build option.") + return True -cuda_home = get_environment_option("CUDA_HOME") -libcuml_path = get_environment_option('CUML_BUILD_PATH') + else: + return False -clean_artifacts = get_cli_option('clean') -############################################################################## -# - Dependencies include and lib folder setup -------------------------------- +def clean_folder(path): + """ + Function to clean all Cython and Python artifacts and cache folders. It + cleans the folder as well as its direct children recursively. + + Parameters + ---------- + path : String + Path to the folder to be cleaned. + """ + shutil.rmtree(path + '/__pycache__', ignore_errors=True) + + folders = glob.glob(path + '/*/') + for folder in folders: + shutil.rmtree(folder + '/__pycache__', ignore_errors=True) + + clean_folder(folder) -if not cuda_home: - nvcc_path = shutil.which('nvcc') - if (not nvcc_path): - raise FileNotFoundError("nvcc not found.") + cython_exts = glob.glob(folder + '/*.cpp') + cython_exts.extend(glob.glob(folder + '/*.cpython*')) + for file in cython_exts: + os.remove(file) - cuda_home = str(Path(nvcc_path).parent.parent) - print("-- Using nvcc to detect CUDA, found at " + str(cuda_home)) -cuda_include_dir = os.path.join(cuda_home, "include") -cuda_lib_dir = os.path.join(cuda_home, "lib64") +############################################################################## +# - Print of build options used by setup.py -------------------------------- + +clean_artifacts = get_cli_option('clean') + ############################################################################## # - Clean target ------------------------------------------------------------- @@ -60,7 +75,6 @@ # Reset these paths since they may be deleted below treelite_path = False - libcuml_path = False try: setup_file_path = str(Path(__file__).parent.absolute()) @@ -89,31 +103,69 @@ sys.exit(0) -if not libcuml_path: - libcuml_path = '../cpp/build/' - -cmdclass = versioneer.get_cmdclass() - ############################################################################## # - Python package generation ------------------------------------------------ -setup(name='cuml', +# Make versioneer produce PyPI-compatible nightly versions for wheels. +if "RAPIDS_PY_WHEEL_VERSIONEER_OVERRIDE" in os.environ: + orig_get_versions = versioneer.get_versions + + version_override = os.environ["RAPIDS_PY_WHEEL_VERSIONEER_OVERRIDE"] + + def get_versions(): + data = orig_get_versions() + data["version"] = version_override + return data + + versioneer.get_versions = get_versions + + +cuda_suffix = os.getenv("RAPIDS_PY_WHEEL_CUDA_SUFFIX", default="") +setup(name=f'cuml{cuda_suffix}', + version=os.getenv("RAPIDS_PY_WHEEL_VERSIONEER_OVERRIDE", + default=versioneer.get_version()), description="cuML - RAPIDS ML Algorithms", - version=versioneer.get_version(), + url="https://github.com/rapidsai/cuml", + author="NVIDIA Corporation", + license="Apache", classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9" ], - author="NVIDIA Corporation", - url="https://github.com/rapidsai/cuml", - setup_requires=['Cython>=0.29,<0.30'], + cmdclass=versioneer.get_cmdclass(), + include_package_data=True, packages=find_packages(include=['cuml', 'cuml.*']), package_data={ key: ["*.pxd"] for key in find_packages(include=['cuml', 'cuml.*']) }, - install_requires=install_requires, - license="Apache", - cmdclass=cmdclass, + install_requires=[ + "numba", + "scipy", + "seaborn", + "treelite==3.0.1", + "treelite_runtime==3.0.1", + f"cudf{cuda_suffix}", + f"dask-cudf{cuda_suffix}", + f"pylibraft{cuda_suffix}", + f"raft-dask{cuda_suffix}", + ], + extras_require={ + "test": [ + "pytest", + "hypothesis", + "pytest-xdist", + "pytest-benchmark", + "nltk", + "dask-ml", + "numpydoc", + "umap-learn", + "statsmodels", + "scikit-learn==0.24", + "hdbscan @ git+https://github.com/scikit-learn-contrib/hdbscan.git@master", # noqa:E501 + "dask-glm @ git+https://github.com/dask/dask-glm@main", + "dask-cuda" + ] + }, zip_safe=False) diff --git a/python/setuputils.py b/python/setuputils.py deleted file mode 100644 index a8085802be..0000000000 --- a/python/setuputils.py +++ /dev/null @@ -1,86 +0,0 @@ -# -# Copyright (c) 2018-2022, NVIDIA CORPORATION. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import glob -import os -import re -import shutil -import sys - - -def get_environment_option(name): - env_variable = os.environ.get(name, False) - - if not env_variable: - print("-- " + name + " environment variable not set.") - - else: - print("-- " + name + " detected with value: " + str(env_variable)) - - return env_variable - - -def get_cli_option(name): - if name in sys.argv: - print("-- Detected " + str(name) + " build option.") - return True - - else: - return False - - -def clean_folder(path): - """ - Function to clean all Cython and Python artifacts and cache folders. It - clean the folder as well as its direct children recursively. - - Parameters - ---------- - path : String - Path to the folder to be cleaned. - """ - shutil.rmtree(path + '/__pycache__', ignore_errors=True) - - folders = glob.glob(path + '/*/') - for folder in folders: - shutil.rmtree(folder + '/__pycache__', ignore_errors=True) - - clean_folder(folder) - - cython_exts = glob.glob(folder + '/*.cpp') - cython_exts.extend(glob.glob(folder + '/*.cpython*')) - for file in cython_exts: - os.remove(file) - - -def get_cuda_version_from_header(cuda_include_dir, delimiter=""): - - cuda_version = None - - with open(os.path.join(cuda_include_dir, "cuda.h"), encoding="utf-8") as f: - for line in f.readlines(): - if re.search(r"#define CUDA_VERSION ", line) is not None: - cuda_version = line - break - - if cuda_version is None: - raise TypeError("CUDA_VERSION not found in cuda.h") - cuda_version = int(cuda_version.split()[2]) - return "%d%s%d" % ( - cuda_version // 1000, - delimiter, - (cuda_version % 1000) // 10, - )