diff --git a/.figures/support_vector_machine.png b/.figures/support_vector_machine.png new file mode 100644 index 000000000..4cab819d6 Binary files /dev/null and b/.figures/support_vector_machine.png differ diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 696f8ca4c..6da1586ae 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - documentation pull_request: branches: - main @@ -21,7 +22,6 @@ jobs: # install dependencies - name: Dependancies run: | - echo "deb http://dk.archive.ubuntu.com/ubuntu/ hirsute main universe" | sudo tee -a /etc/apt/sources.list sudo apt update sudo apt-get install -y doxygen graphviz # configure project via CMake diff --git a/.github/workflows/msvc_windows.yml b/.github/workflows/msvc_windows.yml index 310dae20d..78b345d0f 100644 --- a/.github/workflows/msvc_windows.yml +++ b/.github/workflows/msvc_windows.yml @@ -18,7 +18,7 @@ jobs: run: | mkdir PLSSVM/build cd PLSSVM/build - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_TESTING=ON -DPLSSVM_GENERATE_TEST_FILE=OFF -DPLSSVM_ENABLE_LTO=OFF -DPLSSVM_ENABLE_ASSERTS=ON.. + cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_TESTING=ON -DPLSSVM_GENERATE_TEST_FILE=OFF -DPLSSVM_ENABLE_LTO=OFF -DPLSSVM_ENABLE_ASSERTS=ON .. - name: "Build Step" run: | cd PLSSVM/build @@ -26,4 +26,4 @@ jobs: - name: "Test Step" run: | cd PLSSVM/build - ctest -C Debug -V \ No newline at end of file + ctest --output-on-failure -C Debug \ No newline at end of file diff --git a/.gitignore b/.gitignore index cfb373f3b..83560d1f8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ build*/ cmake-build*/ CMakeLists.txt.user CMakeCache.txt +CMakeSettings.json CMakeFiles CMakeScripts Testing @@ -52,20 +53,14 @@ CTestTestfile.cmake # IDEs ================================ .idea/ .vscode/ +.vs/ # Project related ================================ - -# ignore everything in the autogenerated folder, except the README -include/plssvm/backends/autogenerated/* -!include/plssvm/backends/autogenerated/README.md -src/plssvm/backends/autogenerated/* -!src/plssvm/backends/autogenerated/README.md -tests/backends/autogenerated/* -!tests/backends/autogenerated/README.md - # auto-generated version header include/plssvm/version/version.hpp +# auto-generated git source +src/plssvm/version/git_metadata/git_metadata.cpp # generated documentation !docs/ @@ -74,11 +69,12 @@ docs/* !docs/CMakeLists.txt !docs/plssvm-train.1.in !docs/plssvm-predict.1.in +!docs/plssvm-scale.1.in # data in test folder !tests/data/ tests/data/* -!tests/data/models/ +!tests/data/model/ !tests/data/libsvm/ !tests/data/arff/ -!tests/data/predict/ +!tests/data/predict/ \ No newline at end of file diff --git a/.jenkins/Jenkinsfile b/.jenkins/Jenkinsfile new file mode 100644 index 000000000..1d6de07f5 --- /dev/null +++ b/.jenkins/Jenkinsfile @@ -0,0 +1,755 @@ +#!groovy + +def buildbadge = addEmbeddableBadgeConfiguration(id: "Jenkins", subject: "Jenkins Tests", status: "skipped") + +if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { + print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by branch indexing..." + if (env.BRANCH_NAME != "master") { + if (env.BUILD_NUMBER != "1") { // Always execute first build to load this configuration and thus the triggers + print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} skipped due being triggered by Branch Indexing instead of SCM change!" + buildbadge.setStatus('skipped') + currentBuild.result = 'ABORTED' + return // early exit to avoid redundant builds + } + } +} else { + print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by SCM change..." + print "Proceeding!" +} + +pipeline { + agent { label 'pcsgs02 || pcsgs03 || pcsgs04 || pcsgs05' } + + options { + buildDiscarder( + logRotator( + daysToKeepStr: "21", + numToKeepStr: "50", + artifactDaysToKeepStr: "21", + artifactNumToKeepStr: "50" + ) + ) + disableConcurrentBuilds() + } + + triggers { + githubPush() // Trigger by push to respective github branch + pollSCM 'H/30 * * * *' // Fallback polling solution as some pushes are somehow lost + } + + environment { + GITHUB_TOKEN = credentials('GITHUB_TOKEN') + BRANCH_NAME = "${env.BRANCH_NAME}" + WORKSPACE= "${env.WORKSPACE}" + + // ROCM / HIP + ROCM_PATH = "/opt/rocm" + LD_LIBRARY_PATH = "${env.ROCM_PATH}/lib64:${env.ROCM_PATH}/lib:${env.ROCM_PATH}/opencl/lib/x86_64:${env.ROCM_PATH}/hsa/lib:${env.ROCM_PATH}/hip/lib:${env.LD_LIBRARY_PATH}" + CPLUS_INCLUDE_PATH = "${env.ROCM_PATH}/hip/include:${env.CPLUS_INCLUDE_PATH}" + CMAKE_PREFIX_PATH = "${env.ROCM_PATH}/hip:${env.ROCM_PATH}:${env.CMAKE_PREFIX_PATH}" + + // PoCL + POCL_PATH = "${env.WORKSPACE}/pocl-install" + + // GCC + GCC_PATH = "${env.WORKSPACE}/gcc-install" + + // DPCPP + DPCPP_PATH = "${env.WORKSPACE}/llvm/build" + DPCPP_FORCE_REBUILD = "FALSE" + + // hipSYCL + HIPSYCL_PATH = "${env.WORKSPACE}/hipsycl-install" + HIPSYCL_FORCE_REBUILD = "FALSE" + + // install path + MAKE_INSTALL_PATH = "${env.WORKSPACE}/plssvm_install" + MAKE_INSTALL_LIBRARY_SAMPLE_REBUILD = "FALSE" + + + } + + stages{ + stage('Dependency -- CMake') + { + steps{ + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh ''' + cd ${WORKSPACE} + if [ -f cmake-3.25.2-linux-x86_64.tar.gz* ]; then + rm cmake-3.25.2-linux-x86_64.tar.gz* + rm -rf cmake-3.25.2-linux-x86_64 + fi + if [ ! -d cmake-3.25.2-linux-x86_64 ]; then + wget https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-x86_64.tar.gz + tar xvzf cmake-3.25.2-linux-x86_64.tar.gz + rm cmake-3.25.2-linux-x86_64.tar.gz* + fi + ''' + } + + } + } + parallel builders + } + } + } + stage('Dependency -- Ninja') + { + steps{ + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh ''' + cd ${WORKSPACE} + if [ ! -f ninja ]; then + wget https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-linux.zip + unzip ninja-linux.zip + rm ninja-linux.zip* + fi + ''' + } + + } + } + parallel builders + } + } + } + stage('Checkout PLSSVM') { + steps { + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + dir("${env.WORKSPACE}/plssvm") { + checkout scm + } + checkout scm + + } + + } + + } + parallel builders + } + } + } + stage('Dependency -- gcc') + { + steps{ + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh ''' + cd ${WORKSPACE} + if [ ! -d "$GCC_PATH" ] || [ "$GCC_FORCE_REBUILD" = "TRUE" ]; then + .jenkins/scripts/build-gcc.sh $GCC_PATH + fi + + ''' + } + + } + } + parallel builders + } + } + } + stage('Dependency -- Python dependencies') + { + steps{ + sh ''' + cd ${WORKSPACE} + python3 -m pip install --user -r install/python_requirements.txt + ''' + } + } + stage('Generate test files') { + steps { + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh ''' + + cd ${WORKSPACE}/plssvm + python3 utility_scripts/generate_data.py --output tests/data/5000x2000.libsvm --format libsvm --problem planes --samples 5000 --features 2000 + python3 utility_scripts/generate_data.py --output tests/data/500x100.libsvm --format libsvm --problem planes --samples 500 --features 100 + ''' + } + } + + } + parallel builders + } + } + } + stage('Dependency -- DPC++') + { + environment { + PATH="$WORKSPACE:$GCC_PATH/bin:$WORKSPACE/cmake-3.25.2-linux-x86_64/bin:${env.PATH}" + } + steps{ + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh ''' + cd ${WORKSPACE} + . /usr/local.nfs/Modules/latest/init/sh + echo $PATH + module load cuda/11.4.3 + if [ ! -d "$DPCPP_PATH" ] || [ "$DPCPP_FORCE_REBUILD" = "TRUE" ]; then + rm -rf llvm + git clone --dept 1 --branch sycl-nightly/20221102 https://github.com/intel/llvm.git llvm + cd llvm + rm -rf build + whereis g++ + python buildbot/configure.py --cuda --llvm-external-projects="clang-tools-extra,compiler-rt,openmp" --cmake-opt="-DENABLE_LIBOMPTARGET=OFF" + python buildbot/compile.py + cmake --build build -- omp + cmake --build build -- install + rm -rf "$HIPSYCL_PATH" + fi + + ''' + } + } + builders['pcsgs09'] = { + node('pcsgs09') { + sh ''' + cd ${WORKSPACE} + if [ ! -d "$DPCPP_PATH" ] || [ "$DPCPP_FORCE_REBUILD" = "TRUE" ]; then + rm -rf llvm + git clone --dept 1 --branch sycl-nightly/20230110 https://github.com/intel/llvm.git llvm + cd llvm + python buildbot/configure.py --hip --llvm-external-projects="clang-tools-extra,compiler-rt,openmp" --cmake-opt="-DENABLE_LIBOMPTARGET=OFF" --cmake-opt=-DSYCL_BUILD_PI_HIP_ROCM_DIR=${ROCM_PATH} --hip-platform AMD + python buildbot/compile.py + cmake --build build -- omp + cmake --build build -- install + + rm -rf "$HIPSYCL_PATH" + fi + + ''' + } + } + } + parallel builders + } + } + } + + stage('Dependency -- hipSYCL') + { + environment { + PATH="$WORKSPACE/cmake-3.25.2-linux-x86_64/bin:$DPCPP_PATH/bin:${env.PATH}" + LIBRARY_PATH="$DPCPP_PATH/lib:${env.LIBRARY_PATH}" + LD_LIBRARY_PATH="$DPCPP_PATH/lib:${env.LD_LIBRARY_PATH}" + CPLUS_INCLUDE_PATH="$DPCPP_PATH/install/include/sycl:$DPCPP_PATH/install/include:$DPCPP_PATH/projects/openmp/runtime/src:${env.CPLUS_INCLUDE_PATH}" + } + steps{ + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09' ,'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh ''' + cd ${WORKSPACE} + if [ ! "$NODE_NAME" == "pcsgs09" ]; then + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/11.4.3 + fi + if [ ! -d "$HIPSYCL_PATH" ] || [ "$HIPSYCL_FORCE_REBUILD" = "TRUE" ]; then + rm -rf hipSYCL + rm -rf "$HIPSYCL_PATH" + + mkdir $HIPSYCL_PATH + git clone https://github.com/illuhad/hipSYCL.git hipSYCL + cd hipSYCL + git checkout 012e16d6d3d57330c176d7d536f657b0d8a9a197 + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HIPSYCL_PATH -DCMAKE_CXX_COMPILER=clang++ .. + make -j + make install + cd $WORKSPACE + rm -rf hipSYCL + fi + ''' + } + } + } + parallel builders + } + } + } + stage('Dependency -- PoCL') + { + + steps{ + + script{ + def labels = [ 'pcsgs02', 'pcsgs03', 'pcsgs04', 'pcsgs05', 'pcsgs09', 'pcsgs11'] + def builders = [: ] + for (x in labels){ + def label = x + builders[label] = { + node(label) { + sh '${WORKSPACE}/.jenkins/scripts/build-pocl.sh' + } + + } + } + parallel builders + } + + } + } + + stage('Build and Test'){ + + matrix{ + axes { + axis { + name 'BACKEND' + values 'OPENMP', 'CUDA', 'OPENCL', 'DPCPP', 'HIPSYCL', 'HIP' + } + axis { + name 'ARCHITECTURE' + values 'NVIDIA', 'AMD', 'CPU' + } + axis { + name 'BUILD_TYPE' + values 'Release', 'Debug' + } + } + excludes { + exclude { + axis{ + name 'BACKEND' + values 'HIP' + } + axis{ + name 'ARCHITECTURE' + notValues 'AMD' + } + } + exclude { + axis{ + name 'BACKEND' + values 'CUDA' + } + axis{ + name 'ARCHITECTURE' + notValues 'NVIDIA' + } + } + exclude { + axis{ + name 'BACKEND' + values 'OPENMP' + } + axis{ + name 'ARCHITECTURE' + notValues 'CPU' + } + } + exclude { + axis{ + name 'BACKEND' + values 'DPCPP' + } + axis{ + name 'ARCHITECTURE' + values 'CPU' + } + } + exclude { + axis{ + name 'BACKEND' + values 'HIPSYCL' + } + axis{ + name 'BUILD_TYPE' + values 'Debug' + } + } + } + + + + agent { label " ${env.ARCHITECTURE.equalsIgnoreCase('AMD') ? 'pcsgs09' : 'pcsgs02 || pcsgs03 || pcsgs04 || pcsgs05' }"} + + stages{ + stage('Build PLSSVM'){ + steps{ + sh ''' + export COMPILER=g++ + export TARGET_PLATFORM=nvidia:sm_86 + if [ ! "$NODE_NAME" == "pcsgs09" ]; then + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/12.0.1 + fi + if [ "$BACKEND" == "DPCPP" ] || [ "$BACKEND" == "HIPSYCL" ]; then + export PATH="$DPCPP_PATH/bin:${PATH}" + export LIBRARY_PATH="$DPCPP_PATH/lib:${LIBRARY_PATH}" + export LD_LIBRARY_PATH="$DPCPP_PATH/lib:${LD_LIBRARY_PATH}" + export PLSSVM_SYCL_DPCPP_INCLUDE_DIR="$DPCPP_PATH/install/include" + export COMPILER=$DPCPP_PATH/bin/clang++ + export export CPLUS_INCLUDE_PATH="$DPCPP_PATH/projects/openmp/runtime/src:${CPLUS_INCLUDE_PATH}" + fi + if [ "$BACKEND" == "HIPSYCL" ]; then + export PATH="$HIPSYCL_PATH/bin:${PATH}" + export LIBRARY_PATH="$HIPSYCL_PATH/lib:${LIBRARY_PATH}" + export LD_LIBRARY_PATH="$HIPSYCL_PATH/lib:${LD_LIBRARY_PATH}" + export CPLUS_INCLUDE_PATH="$HIPSYCL_PATH/include:${CPLUS_INCLUDE_PATH}" + export PLSSVM_SYCL_HIPSYCL_INCLUDE_DIR="${HIPSYCL_PATH}/include" + fi + if [ "$ARCHITECTURE" == "CPU" ]; then + PATH="$POCL_PATH/bin:${PATH}" + CPLUS_INCLUDE_PATH="$POCL_PATH/include:${CPLUS_INCLUDE_PATH}" + LIBRARY_PATH="$POCL_PATH/lib:${LIBRARY_PATH}" + LD_LIBRARY_PATH="$POCL_PATH/lib:${LD_LIBRARY_PATH}" + CMAKE_PREFIX_PATH="$POCL_PATH:${CMAKE_PREFIX_PATH}" + export TARGET_PLATFORM=cpu + fi + if [ "$ARCHITECTURE" == "AMD" ]; then + #export ROCM_PATH_="/opt/rocm" + export LD_LIBRARY_PATH="${ROCM_PATH}/lib64:${ROCM_PATH}/lib:${ROCM_PATH}/opencl/lib/x86_64:${ROCM_PATH}/hsa/lib:${ROCM_PATH}/hip/lib:${LD_LIBRARY_PATH}" + export CPLUS_INCLUDE_PATH="${ROCM_PATH}/hip/include:${CPLUS_INCLUDE_PATH}" + export CMAKE_PREFIX_PATH="${ROCM_PATH}/hip:${ROCM_PATH}:${CMAKE_PREFIX_PATH}" + export TARGET_PLATFORM=amd:gfx906 + export LIBRARY_PATH=$LD_LIBRARY_PATH + fi + if [ "$BACKEND" == "HIP" ]; then + export COMPILER=hipcc + fi + + if [ "$BUILD_TYPE" == Release ]; then + export NUM_DATA_POINTS=5000 + export NUM_FEATURES=2000 + else + export NUM_DATA_POINTS=500 + export NUM_FEATURES=100 + fi + + cd ${WORKSPACE}/plssvm + mkdir -p build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + cd build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + rm -rf * + rm -rf $MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + $WORKSPACE/cmake-3.25.2-linux-x86_64/bin/cmake -DCMAKE_INSTALL_PREFIX=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DCMAKE_CXX_COMPILER=$COMPILER \ + -DPLSSVM_TARGET_PLATFORMS=${TARGET_PLATFORM}\ + -DPLSSVM_ENABLE_ASSERTS=ON \ + -DPLSSVM_ENABLE_OPENMP_BACKEND=$([[ $BACKEND == OPENMP ]] && echo "ON" || echo "OFF") \ + -DPLSSVM_ENABLE_CUDA_BACKEND=$([[ $BACKEND == CUDA ]] && echo "ON" || echo "OFF") \ + -DPLSSVM_ENABLE_HIP_BACKEND=$([[ $BACKEND == HIP ]] && echo "ON" || echo "OFF") \ + -DPLSSVM_ENABLE_OPENCL_BACKEND=$([[ $BACKEND == OPENCL ]] && echo "ON" || echo "OFF") \ + -DPLSSVM_ENABLE_SYCL_HIPSYCL_BACKEND=$([[ $BACKEND == HIPSYCL ]] && echo "ON" || echo "OFF") \ + -DPLSSVM_ENABLE_SYCL_DPCPP_BACKEND=$([[ $BACKEND == DPCPP ]] && echo "ON" || echo "OFF") \ + -DPLSSVM_ENABLE_LANGUAGE_BINDINGS=ON \ + -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=$NUM_DATA_POINTS \ + -DPLSSVM_TEST_FILE_NUM_FEATURES=$NUM_FEATURES \ + ../../ + make -j4 install + ''' + + } + } + stage('Ctest PLSSVM'){ + steps{ + sh ''' + if [ ! "$NODE_NAME" == "pcsgs09" ]; then + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/12.0.1 + fi + if [ "$BACKEND" == "DPCPP" ] || [ "$BACKEND" == "HIPSYCL" ]; then + export LD_LIBRARY_PATH="$DPCPP_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$BACKEND" == "HIPSYCL" ]; then + export LD_LIBRARY_PATH="$HIPSYCL_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$ARCHITECTURE" == "CPU" ]; then + export PATH="$POCL_PATH/bin:${PATH}" + export LD_LIBRARY_PATH="$POCL_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$ARCHITECTURE" == "AMD" ]; then + export ROCM_PATH_="/opt/rocm" + export LD_LIBRARY_PATH="${ROCM_PATH}/lib64:${ROCM_PATH}/lib:${ROCM_PATH}/opencl/lib/x86_64:${ROCM_PATH}/hsa/lib:${ROCM_PATH}/hip/lib:${LD_LIBRARY_PATH}" + fi + cd ${WORKSPACE}/plssvm/build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + ctest -j4 --output-on-failure --no-compress-output -T Test + ''' + } + } + stage('Test install'){ + steps{ + sh ''' + if [ ! "$NODE_NAME" == "pcsgs09" ]; then + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/12.0.1 + fi + if [ "$BACKEND" == "DPCPP" ] || [ "$BACKEND" == "HIPSYCL" ]; then + export LD_LIBRARY_PATH="$DPCPP_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$BACKEND" == "HIPSYCL" ]; then + export LD_LIBRARY_PATH="$HIPSYCL_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$ARCHITECTURE" == "CPU" ]; then + LD_LIBRARY_PATH="$POCL_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$ARCHITECTURE" == "AMD" ]; then + #export ROCM_PATH_="/opt/rocm" + export LD_LIBRARY_PATH="${ROCM_PATH}/lib64:${ROCM_PATH}/lib:${ROCM_PATH}/opencl/lib/x86_64:${ROCM_PATH}/hsa/lib:${ROCM_PATH}/hip/lib:${LD_LIBRARY_PATH}" + fi + cd $MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + export PATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/bin:$PATH + export LD_LIBRARY_PATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/lib:$LD_LIBRARY_PATH + plssvm-scale -l -1.0 -u 1.0 ${WORKSPACE}/plssvm/tests/data/libsvm/5x4.libsvm scaled_5x4.libsvm + if [ $BACKEND == "DPCPP" ] || [ $BACKEND == "HIPSYCL" ]; then + plssvm-train --backend sycl --sycl_implementation_type $BACKEND scaled_5x4.libsvm + plssvm-predict --backend sycl --sycl_implementation_type $BACKEND scaled_5x4.libsvm scaled_5x4.libsvm.model + else + plssvm-train --backend $BACKEND scaled_5x4.libsvm + plssvm-predict --backend $BACKEND scaled_5x4.libsvm scaled_5x4.libsvm.model + fi + ''' + } + } + + stage('Test CMake Library integration'){ + steps{ + sh ''' + export COMPILER=g++ + export TARGET_PLATFORM=nvidia:sm_86 + if [ ! "$NODE_NAME" == "pcsgs09" ]; then + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/12.0.1 + fi + if [ "$BACKEND" == "DPCPP" ] || [ "$BACKEND" == "HIPSYCL" ]; then + export PATH="$DPCPP_PATH/bin:${PATH}" + export LIBRARY_PATH="$DPCPP_PATH/lib:${LIBRARY_PATH}" + export LD_LIBRARY_PATH="$DPCPP_PATH/lib:${LD_LIBRARY_PATH}" + export PLSSVM_SYCL_DPCPP_INCLUDE_DIR="$DPCPP_PATH/install/include" + export COMPILER=$DPCPP_PATH/bin/clang++ + export export CPLUS_INCLUDE_PATH="$DPCPP_PATH/projects/openmp/runtime/src:${CPLUS_INCLUDE_PATH}" + fi + if [ "$BACKEND" == "HIPSYCL" ]; then + export PATH="$HIPSYCL_PATH/bin:${PATH}" + export LIBRARY_PATH="$HIPSYCL_PATH/lib:${LIBRARY_PATH}" + export LD_LIBRARY_PATH="$HIPSYCL_PATH/lib:${LD_LIBRARY_PATH}" + export CPLUS_INCLUDE_PATH="$HIPSYCL_PATH/include:${CPLUS_INCLUDE_PATH}" + export PLSSVM_SYCL_HIPSYCL_INCLUDE_DIR="${HIPSYCL_PATH}/include" + fi + if [ "$ARCHITECTURE" == "CPU" ]; then + PATH="$POCL_PATH/bin:${PATH}" + CPLUS_INCLUDE_PATH="$POCL_PATH/include:${CPLUS_INCLUDE_PATH}" + LIBRARY_PATH="$POCL_PATH/lib:${LIBRARY_PATH}" + LD_LIBRARY_PATH="$POCL_PATH/lib:${LD_LIBRARY_PATH}" + CMAKE_PREFIX_PATH="$POCL_PATH:${CMAKE_PREFIX_PATH}" + export TARGET_PLATFORM=cpu + fi + if [ "$ARCHITECTURE" == "AMD" ]; then + #export ROCM_PATH_="/opt/rocm" + export LD_LIBRARY_PATH="${ROCM_PATH}/lib64:${ROCM_PATH}/lib:${ROCM_PATH}/opencl/lib/x86_64:${ROCM_PATH}/hsa/lib:${ROCM_PATH}/hip/lib:${LD_LIBRARY_PATH}" + export CPLUS_INCLUDE_PATH="${ROCM_PATH}/hip/include:${CPLUS_INCLUDE_PATH}" + export CMAKE_PREFIX_PATH="${ROCM_PATH}/hip:${ROCM_PATH}:${CMAKE_PREFIX_PATH}" + export TARGET_PLATFORM=amd:gfx906 + export LIBRARY_PATH=$LD_LIBRARY_PATH + fi + if [ "$BACKEND" == "HIP" ]; then + export COMPILER=hipcc + fi + cd $WORKSPACE/plssvm/examples/cpp + mkdir -p build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + cd build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + rm -rf * + export CMAKE_PREFIX_PATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/share/plssvm/cmake:$CMAKE_PREFIX_PATH + export LD_LIBRARY_PATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/lib:$LD_LIBRARY_PATH + export CPLUS_INCLUDE_PATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/include:$CPLUS_INCLUDE_PATH + $WORKSPACE/cmake-3.25.2-linux-x86_64/bin/cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_CXX_COMPILER=$COMPILER ../../ + make -j4 + ./prog ${WORKSPACE}/plssvm/tests/data/libsvm/5x4.libsvm + ''' + + } + } + stage('Test python bindings'){ + steps{ + sh ''' + if [ ! "$NODE_NAME" == "pcsgs09" ]; then + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/12.0.1 + fi + if [ "$BACKEND" == "DPCPP" ] || [ "$BACKEND" == "HIPSYCL" ]; then + export LD_LIBRARY_PATH="$DPCPP_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$BACKEND" == "HIPSYCL" ]; then + export LD_LIBRARY_PATH="$HIPSYCL_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$ARCHITECTURE" == "CPU" ]; then + LD_LIBRARY_PATH="$POCL_PATH/lib:${LD_LIBRARY_PATH}" + fi + if [ "$ARCHITECTURE" == "AMD" ]; then + #export ROCM_PATH_="/opt/rocm" + export LD_LIBRARY_PATH="${ROCM_PATH}/lib64:${ROCM_PATH}/lib:${ROCM_PATH}/opencl/lib/x86_64:${ROCM_PATH}/hsa/lib:${ROCM_PATH}/hip/lib:${LD_LIBRARY_PATH}" + fi + cd $WORKSPACE/plssvm/examples/python + mkdir -p build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + cd build/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE} + rm -rf * + export LD_LIBRARY_PATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/lib:$LD_LIBRARY_PATH + export PYTHONPATH=$MAKE_INSTALL_PATH/${BACKEND}_${BUILD_TYPE}_${ARCHITECTURE}/lib:$PYTHONPATH + python3 ../../sklearn_like_svc.py + cp ${WORKSPACE}/plssvm/tests/data/libsvm/5x4.libsvm train_data.libsvm + cp ${WORKSPACE}/plssvm/tests/data/libsvm/5x4.libsvm test_data.libsvm + python3 ../../main.py + ''' + + } + } + } + } + } + stage('Coverage Analysis') { + steps { + dir('plssvm') { + sh ''' + echo $HOSTNAME + . /usr/local.nfs/Modules/latest/init/sh + module load cuda/12.0.1 + export PATH="$DPCPP_PATH/bin:${PATH}" + export LIBRARY_PATH="$DPCPP_PATH/lib:${LIBRARY_PATH}" + export LD_LIBRARY_PATH="$DPCPP_PATH/lib:${LD_LIBRARY_PATH}" + export PLSSVM_SYCL_DPCPP_INCLUDE_DIR="$DPCPP_PATH/install/include" + export COMPILER=$DPCPP_PATH/bin/clang++ + export export CPLUS_INCLUDE_PATH="$DPCPP_PATH/projects/openmp/runtime/src:${CPLUS_INCLUDE_PATH}" + export PATH="$HIPSYCL_PATH/bin:${PATH}" + export LIBRARY_PATH="$HIPSYCL_PATH/lib:${LIBRARY_PATH}" + export LD_LIBRARY_PATH="$HIPSYCL_PATH/lib:${LD_LIBRARY_PATH}" + export CPLUS_INCLUDE_PATH="$HIPSYCL_PATH/include:${CPLUS_INCLUDE_PATH}" + export PLSSVM_SYCL_HIPSYCL_INCLUDE_DIR="${HIPSYCL_PATH}/include" + + mkdir -p $WORKSPACE/plssvm/build/Debug_cov + cd $WORKSPACE/plssvm/build/Debug_cov + rm -rf * + $WORKSPACE/cmake-3.25.2-linux-x86_64/bin/cmake -DCMAKE_BUILD_TYPE=Coverage -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CC_COMPILER=gcc -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=50 -DPLSSVM_TEST_FILE_NUM_FEATURES=10 -DPLSSVM_ENABLE_SYCL_HIPSYCL_BACKEND=OFF ../../ + make -j36 coverage + wget -N https://raw.githubusercontent.com/eriwen/lcov-to-cobertura-xml/master/lcov_cobertura/lcov_cobertura.py + python lcov_cobertura.py test_clean.info + ''' + } + cobertura coberturaReportFile: "plssvm/build/Debug_cov/coverage.xml" + } + } // end Coverage Analysis +} + + + + post { + always { + // Process the CTest xml output with the xUnit plugin + xunit ( + testTimeMargin: '3000', + thresholdMode: 1, + thresholds: [ + failed(failureThreshold: '0') + ], + tools: [CTest( + pattern: 'plssvm/build/*/Testing/**/*.xml', + deleteOutputFiles: true, + failIfNotNew: false, + skipNoTestFiles: true, + stopProcessingIfError: true + )] + ) + } + success { + script { + buildbadge.setStatus('success') + } + sh ''' + gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') + curl --verbose\ + --request POST \ + --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ + --header "Content-Type: application/json" \ + --header "authorization: Bearer ${gitlab_token}" \ + --data "{ + \\"state\\": \\"success\\", + \\"context\\": \\"Jenkins CPU tests\\", + \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", + \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" + }" + ''' + } + failure { + script { + buildbadge.setStatus('failing') + } + sh ''' + gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') + curl --verbose\ + --request POST \ + --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ + --header "Content-Type: application/json" \ + --header "authorization: Bearer ${gitlab_token}" \ + --data "{ + \\"state\\": \\"failure\\", + \\"context\\": \\"Jenkins CPU tests\\", + \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", + \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" + }" + ''' + } + aborted { + script { + buildbadge.setStatus('aborted') + } + sh ''' + gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') + curl --verbose\ + --request POST \ + --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ + --header "Content-Type: application/json" \ + --header "authorization: Bearer ${gitlab_token}" \ + --data "{ + \\"state\\": \\"error\\", + \\"context\\": \\"Jenkins CPU tests\\", + \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", + \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" + }" + ''' + } + } +} \ No newline at end of file diff --git a/.jenkins/Jenkinsfile-Tests-CPU b/.jenkins/Jenkinsfile-Tests-CPU deleted file mode 100644 index 6771b0581..000000000 --- a/.jenkins/Jenkinsfile-Tests-CPU +++ /dev/null @@ -1,409 +0,0 @@ -#!groovy - -def buildbadge = addEmbeddableBadgeConfiguration(id: "Jenkins", subject: "Jenkins Tests", status: "skipped") - -if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by branch indexing..." - if (env.BRANCH_NAME != "master") { - if (env.BUILD_NUMBER != "1") { // Always execute first build to load this configuration and thus the triggers - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} skipped due being triggered by Branch Indexing instead of SCM change!" - buildbadge.setStatus('skipped') - currentBuild.result = 'ABORTED' - return // early exit to avoid redundant builds - } - } -} else { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by SCM change..." - print "Proceeding!" -} - -pipeline { - agent { label 'pcsgs02 || pcsgs03 || pcsgs04 || pcsgs05' } - - options { - buildDiscarder( - logRotator( - daysToKeepStr: "21", - numToKeepStr: "50", - artifactDaysToKeepStr: "21", - artifactNumToKeepStr: "50" - ) - ) - disableConcurrentBuilds() - } - - triggers { - githubPush() // Trigger by push to respective github branch - pollSCM 'H/30 * * * *' // Fallback polling solution as some pushes are somehow lost - } - - environment { - GITHUB_TOKEN = credentials('GITHUB_TOKEN') - BRANCH_NAME = "${env.BRANCH_NAME}" - } - - stages { - stage('Init') { - steps { - dir('plssvm') { - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"pending\\", - \\"context\\": \\"Jenkins CPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } - } - stage('Checkout PLSSVM') { - steps { - dir('plssvm') { - checkout scm - } - } - } - stage('Setup Python Dependencies'){ - steps{ - sh ''' - /usr/bin/python3.8 -m pip install --user arff - /usr/bin/python3.8 -m pip install --user numpy - /usr/bin/python3.8 -m pip install --user pandas - /usr/bin/python3.8 -m pip install --user sklearn - /usr/bin/python3.8 -m pip install --user argparse - ''' - } - } - // TODO: enable DPC++ on CPUs (missing OpenCL CPU runtime) - // Release - // OpenMP backend - stage('Build PLSSVM Release - OpenMP') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=ON -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - OpenMP') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL backend - stage('Build PLSSVM Release - OpenCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - spack load pocl - export LIBRARY_PATH=/import/sgs.scratch/breyerml/spack/opt/spack/linux-ubuntu20.04-cascadelake/gcc-9.3.0/pocl-1.8-cufe6pralz3viyfybyyhp4cgipqadbac/lib:$LIBRARY_PATH - module use /home/breyerml/.modulefiles/ - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - OpenCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - spack load pocl - export LIBRARY_PATH=/import/sgs.scratch/breyerml/spack/opt/spack/linux-ubuntu20.04-cascadelake/gcc-9.3.0/pocl-1.8-cufe6pralz3viyfybyyhp4cgipqadbac/lib:$LIBRARY_PATH - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // SYCL backend: hipSYCL - stage('Build PLSSVM Release - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cpu_2 - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - hipSYCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cpu_2 - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } -// // OpenMP + OpenCL + hipSYCL backends simultaneously -// stage('Build PLSSVM Release - All Available') { -// steps { -// dir('plssvm') { -// sh ''' -// source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh -// module use /home/breyerml/.modulefiles/ -// spack load pocl -// module load network_hipsycl_cpu_pocl -// mkdir -p build/Release -// cd build/Release -// rm -rf * -// cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=ON -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=500 -DPLSSVM_TEST_FILE_NUM_FEATURES=200 ../../ -// make -j4 -// ''' -// } -// } -// } -// stage('Run Tests Release - All Available') { -// steps { -// dir('plssvm') { -// warnError('Release tests failed!') { -// sh ''' -// source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh -// module use /home/breyerml/.modulefiles/ -// spack load pocl -// module load network_hipsycl_cpu_pocl -// cd build/Release -// ctest -j4 --no-compress-output -T Test -// ''' -// } -// } -// } -// } - // Debug - // OpenMP backend - stage('Build PLSSVM Debug - OpenMP') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=ON -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - OpenMP') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL backend - stage('Build PLSSVM Debug - OpenCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - spack load pocl - export LIBRARY_PATH=/import/sgs.scratch/breyerml/spack/opt/spack/linux-ubuntu20.04-cascadelake/gcc-9.3.0/pocl-1.8-cufe6pralz3viyfybyyhp4cgipqadbac/lib:$LIBRARY_PATH - module use /home/breyerml/.modulefiles/ - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - OpenCL') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - spack load pocl - export LIBRARY_PATH=/import/sgs.scratch/breyerml/spack/opt/spack/linux-ubuntu20.04-cascadelake/gcc-9.3.0/pocl-1.8-cufe6pralz3viyfybyyhp4cgipqadbac/lib:$LIBRARY_PATH - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // SYCL backend: hipSYCL - stage('Build PLSSVM Debug - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cpu_2 - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - hipSYCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cpu_2 - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // Coverage Analysis - stage('Perform Coverage Analysis') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Debug_cov - cd build/Debug_cov - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Coverage -DPLSSVM_TARGET_PLATFORMS="cpu" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 coverage - wget -N https://raw.githubusercontent.com/eriwen/lcov-to-cobertura-xml/master/lcov_cobertura/lcov_cobertura.py - python lcov_cobertura.py test_clean.info - ''' - cobertura coberturaReportFile: 'build/Debug_cov/coverage.xml' - } - } - } - } - post { - always { - // Process the CTest xml output with the xUnit plugin - xunit ( - testTimeMargin: '3000', - thresholdMode: 1, - thresholds: [ - skipped(failureThreshold: '0'), - failed(failureThreshold: '0') - ], - tools: [CTest( - pattern: 'plssvm/build/*/Testing/**/*.xml', - deleteOutputFiles: true, - failIfNotNew: false, - skipNoTestFiles: true, - stopProcessingIfError: true - )] - ) - - } - success { - script { - buildbadge.setStatus('success') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"success\\", - \\"context\\": \\"Jenkins CPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - failure { - script { - buildbadge.setStatus('failing') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"failure\\", - \\"context\\": \\"Jenkins CPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - aborted { - script { - buildbadge.setStatus('aborted') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"error\\", - \\"context\\": \\"Jenkins CPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins CPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-CPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } -} \ No newline at end of file diff --git a/.jenkins/Jenkinsfile-Tests-GPU_AMD b/.jenkins/Jenkinsfile-Tests-GPU_AMD deleted file mode 100644 index b933c9169..000000000 --- a/.jenkins/Jenkinsfile-Tests-GPU_AMD +++ /dev/null @@ -1,471 +0,0 @@ -#!groovy - -def buildbadge = addEmbeddableBadgeConfiguration(id: "Jenkins", subject: "Jenkins Tests", status: "skipped") - -if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by branch indexing..." - if (env.BRANCH_NAME != "master") { - if (env.BUILD_NUMBER != "1") { // Always execute first build to load this configuration and thus the triggers - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} skipped due being triggered by Branch Indexing instead of SCM change!" - buildbadge.setStatus('skipped') - currentBuild.result = 'ABORTED' - return // early exit to avoid redundant builds - } - } -} else { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by SCM change..." - print "Proceeding!" -} - - -pipeline { - agent { label 'sgs_amd_gpu_node' } - - options { - buildDiscarder( - logRotator( - daysToKeepStr: "21", - numToKeepStr: "50", - artifactDaysToKeepStr: "21", - artifactNumToKeepStr: "50" - ) - ) - disableConcurrentBuilds() - } - - triggers { - githubPush() // Trigger by push to respective github branch - pollSCM 'H/30 * * * *' // Fallback polling solution as some pushes are somehow lost - } - - environment { - GITHUB_TOKEN = credentials('GITHUB_TOKEN') - BRANCH_NAME = "${env.BRANCH_NAME}" - } - - stages { - stage('Init') { - steps { - dir('plssvm') { - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"pending\\", - \\"context\\": \\"Jenkins AMD GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins AMD GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_AMD/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } - } - stage('Checkout PLSSVM') { - steps { - dir('plssvm') { - checkout scm - } - } - } - stage('Setup Python Dependencies'){ - steps{ - sh ''' - /usr/bin/python3.8 -m pip install --user arff - /usr/bin/python3.8 -m pip install --user numpy - /usr/bin/python3.8 -m pip install --user pandas - /usr/bin/python3.8 -m pip install --user sklearn - /usr/bin/python3.8 -m pip install --user argparse - ''' - } - } - // Release - // HIP backend - stage('Build PLSSVM Release - HIP') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=ON -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - HIP') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL backend - stage('Build PLSSVM Release - OpenCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DOpenCL_INCLUDE_DIR=/data/scratch/breyerml/Programs/dpcpp_2022_03_04/build/include/sycl -DOpenCL_LIBRARY=/data/scratch/breyerml/Programs/dpcpp_2022_03_04/build/lib -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - OpenCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // hipSYCL backend - stage('Build PLSSVM Release - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/hipsycl - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - hipSYCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/hipsycl - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // DPC++ backend - stage('Build PLSSVM Release - DPC++') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_ASSERTS=ON ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - DPC++') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/dpcpp - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL + hipSYCL + DPC++ backends simultaneously - stage('Build PLSSVM Release - All Available') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/dpcpp plssvm/pcsgs09/hipsycl - - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DOpenCL_INCLUDE_DIR=/data/scratch/breyerml/Programs/dpcpp_2022_03_04/build/include/sycl -DOpenCL_LIBRARY=/data/scratch/breyerml/Programs/dpcpp_2022_03_04/build/lib -DPLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION=dpcpp -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=500 -DPLSSVM_TEST_FILE_NUM_FEATURES=200 ../.. - make -j4 - ''' - } - } - } - stage('Run Tests Release - All Available') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/dpcpp plssvm/pcsgs09/hipsycl - - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // Debug - // HIP backend - stage('Build PLSSVM Debug - HIP') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=ON -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - HIP') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL backend - stage('Build PLSSVM Debug - OpenCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DOpenCL_INCLUDE_DIR=/data/scratch/breyerml/Programs/dpcpp_2022_03_04/build/include/sycl -DOpenCL_LIBRARY=/data/scratch/breyerml/Programs/dpcpp_2022_03_04/build/lib -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - OpenCL') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/dpcpp - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // hipSYCL backend - stage('Build PLSSVM Debug - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/hipsycl - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - hipSYCL') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load plssvm/pcsgs09/hip - module load plssvm/pcsgs09/hipsycl - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } -// // DPC++ backend -// stage('Run Tests Debug - DPC++') { -// steps { -// dir('plssvm') { -// sh ''' -// source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh -// module use /home/breyerml/.modulefiles/ -// module load plssvm/pcsgs09/hip -// module load plssvm/pcsgs09/dpcpp -// mkdir -p build/Debug -// cd build/Debug -// rm -rf * -// cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="amd:gfx906" -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ -// make -j4 -// ''' -// } -// } -// } -// stage('Run Tests Debug - DPC++') { -// steps { -// dir('plssvm') { -// warnError('Debug tests failed!') { -// sh ''' -// source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh -// module use /home/breyerml/.modulefiles/ -// module load plssvm/pcsgs09/hip -// module load plssvm/pcsgs09/dpcpp -// cd build/Debug -// ctest -j4 --no-compress-output -T Test -// ''' -// } -// } -// } -// } - } - post { - always { - // Process the CTest xml output with the xUnit plugin - xunit ( - testTimeMargin: '3000', - thresholdMode: 1, - thresholds: [ - skipped(failureThreshold: '0'), - failed(failureThreshold: '0') - ], - tools: [CTest( - pattern: 'plssvm/build/*/Testing/**/*.xml', - deleteOutputFiles: true, - failIfNotNew: false, - skipNoTestFiles: true, - stopProcessingIfError: true - )] - ) - - } - success { - script { - buildbadge.setStatus('success') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"success\\", - \\"context\\": \\"Jenkins AMD GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins AMD GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_AMD/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - failure { - script { - buildbadge.setStatus('failing') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"failure\\", - \\"context\\": \\"Jenkins AMD GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins AMD GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_AMD/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - aborted { - script { - buildbadge.setStatus('aborted') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"error\\", - \\"context\\": \\"Jenkins AMD GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins AMD GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_AMD/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } -} diff --git a/.jenkins/Jenkinsfile-Tests-GPU_NVIDIA b/.jenkins/Jenkinsfile-Tests-GPU_NVIDIA deleted file mode 100644 index 8631b09d4..000000000 --- a/.jenkins/Jenkinsfile-Tests-GPU_NVIDIA +++ /dev/null @@ -1,475 +0,0 @@ -#!groovy - -def buildbadge = addEmbeddableBadgeConfiguration(id: "Jenkins", subject: "Jenkins Tests", status: "skipped") - -if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by branch indexing..." - if (env.BRANCH_NAME != "master") { - if (env.BUILD_NUMBER != "1") { // Always execute first build to load this configuration and thus the triggers - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} skipped due being triggered by Branch Indexing instead of SCM change!" - buildbadge.setStatus('skipped') - currentBuild.result = 'ABORTED' - return // early exit to avoid redundant builds - } - } -} else { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by SCM change..." - print "Proceeding!" -} - -pipeline { - agent { label 'pcsgs02 || pcsgs03 || pcsgs04 || pcsgs05' } - - options { - buildDiscarder( - logRotator( - daysToKeepStr: "21", - numToKeepStr: "50", - artifactDaysToKeepStr: "21", - artifactNumToKeepStr: "50" - ) - ) - disableConcurrentBuilds() - } - - triggers { - githubPush() // Trigger by push to respective github branch - pollSCM 'H/30 * * * *' // Fallback polling solution as some pushes are somehow lost - } - - environment { - GITHUB_TOKEN = credentials('GITHUB_TOKEN') - BRANCH_NAME = "${env.BRANCH_NAME}" - } - - stages { - stage('Init') { - steps { - dir('plssvm') { - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"pending\\", - \\"context\\": \\"Jenkins NVIDIA GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins NVIDIA GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_NVIDIA/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } - } - stage('Checkout PLSSVM') { - steps { - dir('plssvm') { - checkout scm - } - } - } - stage('Setup Python Dependencies'){ - steps{ - sh ''' - /usr/bin/python3.8 -m pip install --user arff - /usr/bin/python3.8 -m pip install --user numpy - /usr/bin/python3.8 -m pip install --user pandas - /usr/bin/python3.8 -m pip install --user sklearn - /usr/bin/python3.8 -m pip install --user argparse - ''' - } - } - // Release - // CUDA backend - stage('Build PLSSVM Release - CUDA') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cuda/11.2.2 cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=ON -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - CUDA') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - module load cuda/11.2.2 - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL backend - stage('Build PLSSVM Release - OpenCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cuda/11.2.2 cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - OpenCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - module load cuda/11.2.2 - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // hipSYCL backend - stage('Build PLSSVM Release - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cuda - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - hipSYCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cuda - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // DPC++ backend - stage('Build PLSSVM Release - DPC++') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_dpcpp_cuda - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Release - DPC++') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_dpcpp_cuda - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // CUDA + OpenCL + hipSYCL + DPC++ backends simultaneously - stage('Build PLSSVM Release - All Available') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load cuda/11.2.2 - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - - # DPC++ - export PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/bin:$PATH - export LIBRARY_PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/lib:$LIBRARY_PATH - export LD_LIBRARY_PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/install/lib:$LD_LIBRARY_PATH - export CPLUS_INCLUDE_PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/projects/openmp/runtime/src:$CPLUS_INCLUDE_PATH - - export PLSSVM_SYCL_DPCPP_INCLUDE_DIR=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/install/include - - # hipSYCL - export PATH=/import/sgs.local/scratch/breyerml/sycl/hipsycl/cuda/bin:$PATH - export LD_LIBRARY_PATH=/import/sgs.local/scratch/breyerml/sycl/hipsycl/cuda/lib:$LD_LIBRARY_PATH - - export PLSSVM_SYCL_HIPSYCL_INCLUDE_DIR=/import/sgs.local/scratch/breyerml/sycl/hipsycl/cuda/include - - mkdir -p build/Release - cd build/Release - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DOpenCL_INCLUDE_DIR=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/include/sycl -DOpenCL_LIBRARY=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/lib -DPLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION=dpcpp -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=ON -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=500 -DPLSSVM_TEST_FILE_NUM_FEATURES=200 ../.. - make -j4 - ''' - } - } - } - stage('Run Tests Release - All Available') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load cuda/11.2.2 - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 - - # DPC++ - export PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/bin:$PATH - export LIBRARY_PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/lib:$LIBRARY_PATH - export LD_LIBRARY_PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/install/lib:$LD_LIBRARY_PATH - export CPLUS_INCLUDE_PATH=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/projects/openmp/runtime/src:$CPLUS_INCLUDE_PATH - - export PLSSVM_SYCL_DPCPP_INCLUDE_DIR=/import/sgs.local/scratch/breyerml/sycl/dpcpp/cuda/build/install/include - - # hipSYCL - export PATH=/import/sgs.local/scratch/breyerml/sycl/hipsycl/cuda/bin:$PATH - export LD_LIBRARY_PATH=/import/sgs.local/scratch/breyerml/sycl/hipsycl/cuda/lib:$LD_LIBRARY_PATH - - export PLSSVM_SYCL_HIPSYCL_INCLUDE_DIR=/import/sgs.local/scratch/breyerml/sycl/hipsycl/cuda/include - - cd build/Release - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // Debug - // CUDA backend - stage('Build PLSSVM Debug - CUDA') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cuda/11.2.2 cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=ON -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - CUDA') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cuda/11.2.2 - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // OpenCL backend - stage('Build PLSSVM Debug - OpenCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cuda/11.2.2 cmake-3.22.2-gcc-9.3.0-wi6mnc2 - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - OpenCL') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module load cuda/11.2.2 - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // hipSYCL backend - stage('Build PLSSVM Debug - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cuda - mkdir -p build/Debug - cd build/Debug - rm -rf * - cmake -DCMAKE_BUILD_TYPE=Debug -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - make -j4 - ''' - } - } - } - stage('Run Tests Debug - hipSYCL') { - steps { - dir('plssvm') { - warnError('Debug tests failed!') { - sh ''' - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - module use /home/breyerml/.modulefiles/ - module load network_hipsycl_cuda - cd build/Debug - ctest -j4 --no-compress-output -T Test - ''' - } - } - } - } - // DPC++ backend - // stage('Build PLSSVM Debug - DPC++') { - // steps { - // dir('plssvm') { - // sh ''' - // source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - // module use /home/breyerml/.modulefiles/ - // module load network_dpcpp_cuda - // mkdir -p build/Debug - // cd build/Debug - // rm -rf * - // cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS="nvidia:sm_86" -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_TEST_FILE_NUM_DATA_POINTS=100 -DPLSSVM_TEST_FILE_NUM_FEATURES=20 ../../ - // make -j4 - // ''' - // } - // } - // } - // stage('Run Tests Debug - DPC++') { - // steps { - // dir('plssvm') { - // warnError('Debug tests failed!') { - // sh ''' - // source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh - // module use /home/breyerml/.modulefiles/ - // module load network_dpcpp_cuda - // cd build/Debug - // ctest -j4 --no-compress-output -T Test - // ''' - // } - // } - // } - // } - } - post { - always { - // Process the CTest xml output with the xUnit plugin - xunit ( - testTimeMargin: '3000', - thresholdMode: 1, - thresholds: [ - skipped(failureThreshold: '0'), - failed(failureThreshold: '0') - ], - tools: [CTest( - pattern: 'plssvm/build/*/Testing/**/*.xml', - deleteOutputFiles: true, - failIfNotNew: false, - skipNoTestFiles: true, - stopProcessingIfError: true - )] - ) - - } - success { - script { - buildbadge.setStatus('success') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"success\\", - \\"context\\": \\"Jenkins NVIDIA GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins NVIDIA GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_NVIDIA/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - failure { - script { - buildbadge.setStatus('failing') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"failure\\", - \\"context\\": \\"Jenkins NVIDIA GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins NVIDIA GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_NVIDIA/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - aborted { - script { - buildbadge.setStatus('aborted') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"error\\", - \\"context\\": \\"Jenkins NVIDIA GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins NVIDIA GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-GPU_NVIDIA/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } -} \ No newline at end of file diff --git a/.jenkins/Jenkinsfile-Tests-Multi-GPU b/.jenkins/Jenkinsfile-Tests-Multi-GPU deleted file mode 100644 index 875164ad1..000000000 --- a/.jenkins/Jenkinsfile-Tests-Multi-GPU +++ /dev/null @@ -1,352 +0,0 @@ -#!groovy - -def buildbadge = addEmbeddableBadgeConfiguration(id: "Jenkins", subject: "Jenkins Tests", status: "skipped") - -if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by branch indexing..." - if (env.BRANCH_NAME != "master") { - if (env.BUILD_NUMBER != "1") { // Always execute first build to load this configuration and thus the triggers - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} skipped due being triggered by Branch Indexing instead of SCM change!" - buildbadge.setStatus('skipped') - currentBuild.result = 'ABORTED' - return // early exit to avoid redundant builds - } - } -} else { - print "INFO: Build on ${env.BRANCH_NAME}/${env.BUILD_NUMBER} triggered by SCM change..." - print "Proceeding!" -} - - -pipeline { - agent { label 'argon-fs'} - - options { - buildDiscarder( - logRotator( - daysToKeepStr: "21", - numToKeepStr: "50", - artifactDaysToKeepStr: "21", - artifactNumToKeepStr: "50" - ) - ) - } - - triggers { - githubPush() // Trigger by push to respective github branch - pollSCM 'H/15 * * * *' // Fallback polling solution as some pushes are somehow lost - } - - environment { - GITHUB_TOKEN = credentials('GITHUB_TOKEN') - BRANCH_NAME = "${env.BRANCH_NAME}" - } - - stages { - stage('Init') { - steps { - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"pending\\", - \\"context\\": \\"Jenkins Multi-GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins Multi-GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-Multi-GPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } - stage('Checkout PLSSVM') { - steps { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 0:05:00 -D /scratch bash -c "\ - cd /scratch && \ - mkdir -p jenkins && cd jenkins; \ - mkdir -p plssvm/${BUILD_TAG} && cd plssvm/${BUILD_TAG} && \ - rm -rf PLSSVM && \ - git clone git@github.com:SC-SGS/PLSSVM.git PLSSVM && \ - cd PLSSVM && \ - pwd && \ - git checkout ${GIT_COMMIT}" - ''' - sh ''' - mkdir ${BUILD_TAG} - ''' - } - } - stage('Setup Python Dependencies'){ - steps{ - sh ''' - /usr/bin/python3.8 -m pip install --user arff - /usr/bin/python3.8 -m pip install --user numpy - /usr/bin/python3.8 -m pip install --user pandas - /usr/bin/python3.8 -m pip install --user sklearn - /usr/bin/python3.8 -m pip install --user argparse - ''' - } - } - stage('Generate Test File Sequentially'){ - steps{ - dir('plssvm'){ - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 00:05:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM bash -c "\ - python3 utility_scripts/generate_data.py --output tests/data/5000x2000.libsvm --format libsvm --problem planes --samples 5000 --features 2000" - ''' - } - } - } - stage('Build and Test'){ - parallel { - // CUDA backend - stage('Build and Test - CUDA'){ - stages{ - stage('Build PLSSVM Release - CUDA') { - steps { - dir('plssvm') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 00:05:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM bash -c "\ - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh &&\ - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 &&\ - module load cuda/11.2.2 &&\ - mkdir -p build/Release_cuda &&\ - cd build/Release_cuda &&\ - rm -rf * &&\ - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS='nvidia:sm_61' -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=ON -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -S ../../ &&\ - make -j4" - ''' - } - } - } - stage('Run Tests Release - CUDA') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 01:00:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM --gres=gpu:2 bash -c "\ - module load cuda/11.2.2 &&\ - cd build/Release_cuda &&\ - ctest -j4 --no-compress-output -T Test --timeout 1200; \ - returncode=$? && \ - cp -r Testing /data/argon-fs/sgs/jenkins/workspace/$(basename ${WORKSPACE})/${BUILD_TAG}/Testing &&\ - exit $returncode" - ''' - } - } - } - } - } - } - // OpenCL backend - stage('Build and Test - OpenCL'){ - stages{ - stage('Build PLSSVM Release - OpenCL') { - steps { - dir('plssvm') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 00:05:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM bash -c "\ - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh &&\ - module load cmake-3.22.2-gcc-9.3.0-wi6mnc2 &&\ - module load cuda/11.2.2 &&\ - mkdir -p build/Release_opencl &&\ - cd build/Release_opencl &&\ - rm -rf * &&\ - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS='nvidia:sm_61' -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=ON -S ../../ &&\ - make -j4" - ''' - } - } - } - stage('Run Tests Release - OpenCL') { - steps { - dir('plssvm') { - warnError('Release tests failed!') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 01:00:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM --gres=gpu:2 bash -c "\ - module load cuda/11.2.2 &&\ - cd build/Release_opencl &&\ - ctest -j4 --no-compress-output -T Test --timeout 1200; \ - returncode=$? && \ - cp -r Testing /data/argon-fs/sgs/jenkins/workspace/$(basename ${WORKSPACE})/${BUILD_TAG}/Testing &&\ - exit $returncode" - ''' - } - } - } - } - } - } - // hipSYCL backend - stage('Build and Test - hipSYCL'){ - stages{ - stage('Build PLSSVM Release - hipSYCL') { - steps { - dir('plssvm') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 00:05:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM bash -c "\ - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh &&\ - module load cuda/11.2.2 &&\ - module use /home/breyerml/.modulefiles/ &&\ - module load plssvm/argon-gtx/hipsycl &&\ - mkdir -p build/Release_hipsycl &&\ - cd build/Release_hipsycl &&\ - rm -rf * &&\ - cmake -DCMAKE_BUILD_TYPE=Release -DPLSSVM_TARGET_PLATFORMS='nvidia:sm_61' -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_HIP_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -S ../../ &&\ - make -j4 " - ''' - } - } - } - stage('Run Tests Release - hipSYCL') { - steps { - dir('plssvm') { - warnError('hipSYCL Release tests failed!') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 01:00:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM --gres=gpu:2 bash -c "\ - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh &&\ - module load cuda/11.2.2 &&\ - module use /home/breyerml/.modulefiles/ &&\ - module load plssvm/argon-gtx/hipsycl &&\ - cd build/Release_hipsycl &&\ - ctest -j4 --no-compress-output -T Test --timeout 1200; \ - returncode=$? && \ - cp -r Testing /data/argon-fs/sgs/jenkins/workspace/$(basename ${WORKSPACE})/${BUILD_TAG}/Testing_hip && \ - exit $returncode" - ''' - } - } - } - } - } - } - stage('Build and Test - DPC++'){ - stages{ - stage('Build PLSSVM Release - DPC++') { - steps { - dir('plssvm') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 00:05:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM bash -c "\ - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh &&\ - module load cuda/11.2.2 &&\ - module use /home/breyerml/.modulefiles/ &&\ - module load plssvm/argon-gtx/dpcpp &&\ - mkdir -p build/Release_dpcpp &&\ - cd build/Release_dpcpp &&\ - rm -rf * &&\ - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DPLSSVM_TARGET_PLATFORMS='nvidia:sm_61' -DPLSSVM_ENABLE_ASSERTS=ON -DPLSSVM_ENABLE_OPENMP_BACKEND=OFF -DPLSSVM_ENABLE_CUDA_BACKEND=OFF -DPLSSVM_ENABLE_OPENCL_BACKEND=OFF -DPLSSVM_ENABLE_SYCL_BACKEND=ON -DPLSSVM_ENABLE_LTO=OFF -S ../../ &&\ - make -j4 " - ''' - } - } - } - stage('Run Tests Release - DPC++') { - steps { - dir('plssvm') { - warnError('DPC++ Release tests failed!') { - sh ''' - srun -w argon-gtx -N 1 -n 1 -t 01:00:00 -D /scratch/jenkins/plssvm/${BUILD_TAG}/PLSSVM --gres=gpu:2 bash -c "\ - source /import/sgs.local/scratch/breyerml/spack/share/spack/setup-env.sh &&\ - module load cuda/11.2.2 &&\ - module use /home/breyerml/.modulefiles/ &&\ - module load plssvm/argon-gtx/dpcpp &&\ - cd build/Release_dpcpp &&\ - ctest -j4 --no-compress-output -T Test --timeout 1200; \ - returncode=$? && \ - cp -r Testing /data/argon-fs/sgs/jenkins/workspace/$(basename ${WORKSPACE})/${BUILD_TAG}/Testing_dpcpp &&\ - exit $returncode" - ''' - } - } - } - } - } - } - } - } - } - post { - always { - // Process the CTest xml output with the xUnit plugin - xunit ( - testTimeMargin: '3000', - thresholdMode: 1, - thresholds: [ - skipped(failureThreshold: '0'), - failed(failureThreshold: '0') - ], - tools: [CTest( - pattern: '${BUILD_TAG}/Testing*/**/*.xml', - deleteOutputFiles: true, - failIfNotNew: false, - skipNoTestFiles: true, - stopProcessingIfError: true - )] - ) - sh ''' - srun -w argon-gtx -n 1 -t 00:05:00 bash -c "rm -rf /data/scratch/jenkins/plssvm/${BUILD_TAG}" - rm -rf ${BUILD_TAG} - ''' - } - success { - script { - buildbadge.setStatus('success') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"success\\", - \\"context\\": \\"Jenkins Multi-GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins Multi-GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-Multi-GPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - failure { - script { - buildbadge.setStatus('failing') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"failure\\", - \\"context\\": \\"Jenkins Multi-GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins Multi-GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-Multi-GPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - aborted { - script { - buildbadge.setStatus('aborted') - } - sh ''' - gitlab_token=$(echo ${GITHUB_TOKEN} | cut -f2 -d':') - curl --verbose\ - --request POST \ - --url "https://api.github.com/repos/SC-SGS/PLSSVM/statuses/$GIT_COMMIT" \ - --header "Content-Type: application/json" \ - --header "authorization: Bearer ${gitlab_token}" \ - --data "{ - \\"state\\": \\"error\\", - \\"context\\": \\"Jenkins Multi-GPU tests\\", - \\"description\\": \\"Jenkins CI Job: Jenkins Multi-GPU tests\\", - \\"target_url\\": \\"https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/job/Github-Multi-GPU/job/${BRANCH_NAME}/$BUILD_NUMBER\\" - }" - ''' - } - } -} diff --git a/tests/data/libsvm/0x0.libsvm b/.jenkins/NVIDIA/Jenkinsfile similarity index 100% rename from tests/data/libsvm/0x0.libsvm rename to .jenkins/NVIDIA/Jenkinsfile diff --git a/.jenkins/scripts/build-dpcpp b/.jenkins/scripts/build-dpcpp new file mode 100644 index 000000000..e69de29bb diff --git a/.jenkins/scripts/build-gcc.sh b/.jenkins/scripts/build-gcc.sh new file mode 100755 index 000000000..379ebc874 --- /dev/null +++ b/.jenkins/scripts/build-gcc.sh @@ -0,0 +1,33 @@ +#!/usr/bin/bash + +set -x +set -e + +GCC_VERSION=12.2.0 + +if [ -n "$1" ] ; then + GCC_ROOT_DIR=$1 +else + GCC_ROOT_DIR=gcc-${GCC_VERSION}_install +fi + +if [ ! -d gcc-$GCC_VERSION ] ; then + wget ftp://ftp.fu-berlin.de/unix/languages/gcc/releases/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.xz + tar xf gcc-$GCC_VERSION.tar.xz + rm gcc-$GCC_VERSION.tar.xz +fi + +cd gcc-$GCC_VERSION +./contrib/download_prerequisites +cd .. + +mkdir -p build-gcc-$GCC_VERSION +cd build-gcc-$GCC_VERSION +../gcc-$GCC_VERSION/configure --prefix="$GCC_ROOT_DIR" --disable-nls --enable-languages=c,c++ --disable-multilib +make -j "$(nproc)" +make install +cd .. +rm -rf gcc-$GCC_VERSION +rm -rf build-gcc-$GCC_VERSION + + diff --git a/.jenkins/scripts/build-pocl.sh b/.jenkins/scripts/build-pocl.sh new file mode 100755 index 000000000..669fe4adf --- /dev/null +++ b/.jenkins/scripts/build-pocl.sh @@ -0,0 +1,17 @@ +#!/usr/bin/bash + +POCL_VERSION=3.1 +if [ -f pocl.tar.gz ]; then + rm -rf pocl.tar.gz* +fi +if [ ! -d "$POCL_PATH" ]; then + wget https://github.com/pocl/pocl/archive/refs/tags/v${POCL_VERSION}.tar.gz -O pocl.tar.gz + tar xzf pocl.tar.gz + cd pocl-${POCL_VERSION} || exit + mkdir -p build + cd build || exit + cmake -DCMAKE_INSTALL_PREFIX="$POCL_PATH" -DCMAKE_C_COMPILER=/import/sgs.scratch/vancraar/spack/opt/spack/linux-ubuntu20.04-skylake/gcc-9.4.0/llvm-14.0.6-s7bbf6lgiqt47pxskcquoxj7fpttlyxs/bin/clang -DCMAKE_CXX_COMPILER=/import/sgs.scratch/vancraar/spack/opt/spack/linux-ubuntu20.04-skylake/gcc-9.4.0/llvm-14.0.6-s7bbf6lgiqt47pxskcquoxj7fpttlyxs/bin/clang++ -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF -DENABLE_CUDA=OFF -DENABLE_TCE=OFF -DENABLE_HSA=OFF -DLLVM_PATH=/import/sgs.scratch/vancraar/spack/opt/spack/linux-ubuntu20.04-skylake/gcc-9.4.0/llvm-14.0.6-s7bbf6lgiqt47pxskcquoxj7fpttlyxs -DWITH_LLVM_CONFIG=/import/sgs.scratch/vancraar/spack/opt/spack/linux-ubuntu20.04-skylake/gcc-9.4.0/llvm-14.0.6-s7bbf6lgiqt47pxskcquoxj7fpttlyxs/bin/llvm-config -DINSTALL_OPENCL_HEADERS=True -DENABLE_LLVM=True -DSTATIC_LLVM=True -DENABLE_ICD=False .. + make -j 4 install + cd ../.. + rm pocl.tar.gz* +fi \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 247e597cc..f0787ea97 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,42 +7,62 @@ cmake_minimum_required(VERSION 3.21) project("PLSSVM - Parallel Least Squares Support Vector Machine" - VERSION 1.2.0 + VERSION 2.0.0 LANGUAGES CXX DESCRIPTION "A Least Squares Support Vector Machine implementation using different backends.") -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/add_custom_build_type.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/set_local_and_parent.cmake) +## include some generally used utility scripts +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/add_coverage_build_type.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/utility_macros.cmake) + +## add custom module files +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules") + +## set default CMake build type +set(PLSSVM_DEFAULT_BUILD_TYPE "RelWithDebInfo") +set(PLSSVM_ALLOWED_BUILD_TYPES "Debug;Release;MinSizeRel;RelWithDebInfo;Coverage") +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${PLSSVM_DEFAULT_BUILD_TYPE}' as none was specified.") + set(CMAKE_BUILD_TYPE "${PLSSVM_DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${PLSSVM_ALLOWED_BUILD_TYPES}) +else () + if ("${CMAKE_BUILD_TYPE}" IN_LIST PLSSVM_ALLOWED_BUILD_TYPES) + message(STATUS "The build type is '${CMAKE_BUILD_TYPE}'.") + else () + message(FATAL_ERROR "Unrecognized build type '${CMAKE_BUILD_TYPE}'! Allowed build types are: ${PLSSVM_ALLOWED_BUILD_TYPES}") + endif () +endif () -## configure version file -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/include/plssvm/version/version.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/include/plssvm/version/version.hpp - @ONLY -) ######################################################################################################################## ## create and set necessary base properties ## ######################################################################################################################## ## set base sources set(PLSSVM_BASE_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/execution_range.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/file_reader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/sha256.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/string_utility.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/exceptions/exceptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/exceptions/source_location.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/backend_types.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/kernel_types.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/csvm.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/parameter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/parameter_predict.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/parameter_train.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/target_platforms.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/backends/SYCL/kernel_invocation_type.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/backends/SYCL/implementation_type.cpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/backends/SYCL/kernel_invocation_type.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/backends/SYCL/implementation_type.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/cmd/parser_predict.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/cmd/parser_scale.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/cmd/parser_train.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/io/file_reader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/execution_range.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/layout.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/logger.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/performance_tracker.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/sha256.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/string_utility.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/detail/utility.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/exceptions/exceptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/version/version.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/version/git_metadata/git_metadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/backend_types.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/file_format_types.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/kernel_function_types.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/parameter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/target_platforms.cpp + ) ## create base library: linked against all backend libraries set(PLSSVM_BASE_LIBRARY_NAME plssvm-base) @@ -64,21 +84,27 @@ target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC target_compile_features(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC cxx_std_17) ## additional base library compile options target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC - $<$:-Wall -Wextra -Wdouble-promotion -fno-common -Wshadow -Wcast-qual -Wnull-dereference -Wctor-dtor-privacy -Wnon-virtual-dtor -Wsuggest-override -Wextra-semi -Wunreachable-code -Wuninitialized> - $<$:-Wstrict-null-sentinel -Wlogical-op -Wduplicated-branches -Wimplicit-fallthrough=5> + $<$:$<$:-Wall -Wextra -Wdouble-promotion -fno-common -Wshadow -Wcast-qual + -Wnull-dereference -Wctor-dtor-privacy -Wnon-virtual-dtor -Wextra-semi -Wunreachable-code -Wuninitialized + -fPIC> + $<$:-Wsuggest-override -Wstrict-null-sentinel -Wlogical-op -Wduplicated-branches -Wimplicit-fallthrough=5> $<$:-Wdocumentation -Wmost> - $<$:/W4> + $<$:/W4 /bigobj /wd4459>> ) -## nvcc doesn't recognize -Werror=??? option so only set it when using a CXX compiler + +## nvcc doesn't recognize -Werror=??? option, so only set it when using a CXX compiler target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $<$:-Werror=switch -fstrict-enums> - $<$:/we4062> + $<$:/we4062 /wd4005> ) ## enable additional optimization flags only in RELEASE mode target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $<$,$>:-ffast-math -march=native> $<$,$>:/fp:fast> ) +target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC + $<$:NOMINMAX> + ) ######################################################################################################################## @@ -86,92 +112,132 @@ target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC ######################################################################################################################## # check for OpenMP (not for the backend!) find_package(OpenMP 4.0 QUIET) -if(OpenMP_FOUND) - message(STATUS "Found OpenMP ${OpenMP_CXX_VERSION} to speed up file parsing.") - target_link_libraries(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC OpenMP::OpenMP_CXX) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler '-fopenmp'") -else() +if (OpenMP_FOUND) + message(STATUS "Found OpenMP ${OpenMP_CXX_VERSION} to speed-up library utilities (like file parsing).") + set(PLSSVM_FOUND_OPENMP_FOR_UTILITY "${OpenMP_CXX_VERSION}") + target_link_libraries(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC OpenMP::OpenMP_CXX -fopenmp) + target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $<$:-fopenmp>) +else () + # disable warning for unknown OpenMP pragmas if no OpenMP could be found target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $<$:-Wno-unknown-pragmas> $<$:/wd4068> ) -endif() +endif () ## setup dependencies include(FetchContent) +list(APPEND CMAKE_MESSAGE_INDENT "Dependencies: ") ## try finding cxxopts find_package(cxxopts 3.0.0 QUIET) -if(cxxopts_FOUND) +if (cxxopts_FOUND) message(STATUS "Found package cxxopts.") -else() + target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC ${cxxopts_INCLUDE_DIR}) +else () message(STATUS "Couldn't find package cxxopts. Building from source ...") set(PLSSVM_cxxopts_VERSION v3.0.0) # fetch command line parser library cxxopts FetchContent_Declare(cxxopts - GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git - GIT_TAG ${PLSSVM_cxxopts_VERSION} - GIT_SHALLOW TRUE + GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git + GIT_TAG ${PLSSVM_cxxopts_VERSION} + GIT_SHALLOW TRUE + QUIET - set(CXXOPTS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(CXXOPTS_BUILD_TESTS OFF CACHE BOOL "" FORCE) - ) + set (CXXOPTS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set (CXXOPTS_BUILD_TESTS OFF CACHE BOOL "" FORCE) + ) FetchContent_MakeAvailable(cxxopts) add_dependencies(${PLSSVM_BASE_LIBRARY_NAME} cxxopts) target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $ $ - ) - message(STATUS "Installed cxxopts version ${PLSSVM_cxxopts_VERSION}.") -endif() + ) + message(STATUS "Installing cxxopts version ${PLSSVM_cxxopts_VERSION}.") +endif () ## try finding fast_float find_package(fast_float QUIET) -if(fast_float_FOUND) +if (fast_float_FOUND) message(STATUS "Found package fast_float.") -else() + target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC ${fast_float_INCLUDE_DIR}) +else () message(STATUS "Couldn't find package fast_float. Building from source ...") set(PLSSVM_fast_float_VERSION v3.4.0) # fetch float parsing library fast_float FetchContent_Declare(fast_float - GIT_REPOSITORY https://github.com/fastfloat/fast_float - GIT_TAG ${PLSSVM_fast_float_VERSION} - GIT_SHALLOW TRUE + GIT_REPOSITORY https://github.com/fastfloat/fast_float + GIT_TAG ${PLSSVM_fast_float_VERSION} + GIT_SHALLOW TRUE + QUIET - set(FASTFLOAT_TEST OFF CACHE BOOL "" FORCE) - set(FASTFLOAT_SANITIZE OFF CACHE BOOL "" FORCE) - ) + set (FASTFLOAT_TEST OFF CACHE BOOL "" FORCE) + set (FASTFLOAT_SANITIZE OFF CACHE BOOL "" FORCE) + ) FetchContent_GetProperties(fast_float) - if(NOT fast_float_POPULATED) + if (NOT fast_float_POPULATED) FetchContent_Populate(fast_float) add_subdirectory(${fast_float_SOURCE_DIR} EXCLUDE_FROM_ALL) - endif() + endif () add_dependencies(${PLSSVM_BASE_LIBRARY_NAME} fast_float) target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $ $ ) - message(STATUS "Installed fast_float version ${PLSSVM_fast_float_VERSION}.") -endif() + list(APPEND PLSSVM_TARGETS_TO_INSTALL "fast_float") + message(STATUS "Installing fast_float version ${PLSSVM_fast_float_VERSION}.") +endif () + +## try finding igor +find_package(igor QUIET) +if (igor_FOUND) + message(STATUS "Found package igor.") + target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC ${igor_INCLUDE_DIR}) +else () + message(STATUS "Couldn't find package igor. Building from source ...") + set(PLSSVM_igor_VERSION a5224c60d266974d3f407191583fe266cbe1c93d) + # fetch named argument library igor + FetchContent_Declare(igor + GIT_REPOSITORY https://github.com/bluescarni/igor + GIT_TAG ${PLSSVM_igor_VERSION} + GIT_SHALLOW TRUE + QUIET + + set (IGOR_BUILD_TESTS OFF CACHE BOOL "" FORCE) + ) + FetchContent_GetProperties(igor) + if (NOT igor_POPULATED) + FetchContent_Populate(igor) + add_subdirectory(${igor_SOURCE_DIR} EXCLUDE_FROM_ALL) + endif () + add_dependencies(${PLSSVM_BASE_LIBRARY_NAME} igor) + target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC + $ + $ + ) + list(APPEND PLSSVM_TARGETS_TO_INSTALL "igor") + message(STATUS "Installing igor version ${PLSSVM_igor_VERSION}.") +endif () ## try finding fmt -find_package(fmt 8.0.0 QUIET) -if(fmt_FOUND) +find_package(fmt 8.1.1 QUIET) +if (fmt_FOUND) message(STATUS "Found package fmt.") -else() +else () message(STATUS "Couldn't find package fmt. Building from source ...") set(PLSSVM_fmt_VERSION 8.1.1) # fetch string formatting library fmt FetchContent_Declare(fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG ${PLSSVM_fmt_VERSION} - GIT_SHALLOW TRUE - - set(FMT_TEST OFF CACHE BOOL "" FORCE) - set(FMT_DOC OFF CACHE BOOL "" FORCE) - set(FMT_INSTALL OFF CACHE BOOL "" FORCE) - set(FMT_SYSTEM_HEADERS ON CACHE BOOL "" FORCE) - ) + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG ${PLSSVM_fmt_VERSION} + GIT_SHALLOW TRUE + QUIET + + set (FMT_TEST OFF CACHE BOOL "" FORCE) + set (FMT_DOC OFF CACHE BOOL "" FORCE) + set (FMT_INSTALL OFF CACHE BOOL "" FORCE) + set (FMT_SYSTEM_HEADERS ON CACHE BOOL "" FORCE) + ) FetchContent_MakeAvailable(fmt) set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(${PLSSVM_BASE_LIBRARY_NAME} fmt) @@ -181,11 +247,11 @@ else() ) # append fmt to installed targets if build from source list(APPEND PLSSVM_TARGETS_TO_INSTALL "fmt") - message(STATUS "Installed {fmt} version ${PLSSVM_fmt_VERSION}.") -endif() -set_property(TARGET ${PLSSVM_BASE_LIBRARY_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + message(STATUS "Installing {fmt} version ${PLSSVM_fmt_VERSION}.") +endif () target_link_libraries(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC fmt::fmt) +list(POP_BACK CMAKE_MESSAGE_INDENT) ######################################################################################################################## ## create executables ## @@ -196,44 +262,54 @@ add_executable(${PLSSVM_EXECUTABLE_TRAIN_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/src/m ## create predict executable set(PLSSVM_EXECUTABLE_PREDICT_NAME plssvm-predict) add_executable(${PLSSVM_EXECUTABLE_PREDICT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/src/main_predict.cpp) +## create scale executable +set(PLSSVM_EXECUTABLE_SCALE_NAME plssvm-scale) +add_executable(${PLSSVM_EXECUTABLE_SCALE_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/src/main_scale.cpp) ## append executables to installed targets -list(APPEND PLSSVM_TARGETS_TO_INSTALL ${PLSSVM_EXECUTABLE_TRAIN_NAME} ${PLSSVM_EXECUTABLE_PREDICT_NAME}) +list(APPEND PLSSVM_TARGETS_TO_INSTALL ${PLSSVM_EXECUTABLE_TRAIN_NAME} ${PLSSVM_EXECUTABLE_PREDICT_NAME} ${PLSSVM_EXECUTABLE_SCALE_NAME}) ######################################################################################################################## ## setup code coverage analysis ## ######################################################################################################################## ## coverage analysis only possible with the Coverage CMAKE_BUILD_TYPE string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) -if(uppercase_CMAKE_BUILD_TYPE MATCHES "COVERAGE") +if (uppercase_CMAKE_BUILD_TYPE MATCHES "COVERAGE") # must be linux - if(WIN32 OR APPLE) + if (WIN32 OR APPLE) message(FATAL_ERROR "Only Linux is supported for the coverage analysis.") - endif() + endif () # must be GCC - if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU") message(FATAL_ERROR "Only GCC is supported for the coverage analysis.") - endif() + endif () + # tests must be available for a coverage analysis + message(STATUS "Enabling tests since they are necessary for the coverage analysis.") + set(PLSSVM_ENABLE_TESTING ON CACHE BOOL "" FORCE) + # assertions must be available for a coverage analysis + message(STATUS "Enabling assertions since they are necessary for the coverage analysis.") + set(PLSSVM_ENABLE_ASSERTS ON CACHE BOOL "" FORCE) message(STATUS "Enable code coverage analysis using lcov.") # Create the coverage target. Run coverage tests with 'make coverage' add_custom_target(coverage - COMMAND lcov --zerocounters --directory . - COMMAND lcov --capture -d . --initial --output-file test_base.info - COMMAND mkdir -p coverage - COMMAND ${CMAKE_MAKE_PROGRAM} test || true - COMMAND lcov --capture -d . --output-file test_test.info - COMMAND lcov --add-tracefile test_base.info --add-tracefile test_test.info -o test_total.info - COMMAND lcov --remove test_total.info '/usr/*' '*/build/*' '*/tests/*' -o test_clean.info - COMMAND genhtml test_clean.info --output-directory coverage --title "PLSSVM Test Coverage" --show-details --legend - BYPRODUCTS ${CMAKE_BINARY_DIR}/test_base.info ${CMAKE_BINARY_DIR}/test_test.info ${CMAKE_BINARY_DIR}/test_total.info ${CMAKE_BINARY_DIR}/test_clean.info ${CMAKE_BINARY_DIR}/coverage - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) + COMMAND lcov --zerocounters --directory . + COMMAND lcov --capture -d . --initial --output-file test_base.info + COMMAND mkdir -p coverage + COMMAND ${CMAKE_MAKE_PROGRAM} test || true + COMMAND lcov --capture -d . --output-file test_test.info + COMMAND lcov --add-tracefile test_base.info --add-tracefile test_test.info -o test_total.info + COMMAND lcov --remove test_total.info '/usr/*' '*/build/*' '*/tests/*' '*/_deps/*' -o test_clean.info + COMMAND genhtml test_clean.info --output-directory coverage --title "PLSSVM Test Coverage" --show-details --legend + BYPRODUCTS ${CMAKE_BINARY_DIR}/test_base.info ${CMAKE_BINARY_DIR}/test_test.info ${CMAKE_BINARY_DIR}/test_total.info ${CMAKE_BINARY_DIR}/test_clean.info ${CMAKE_BINARY_DIR}/coverage + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) # add executables as coverage target add_dependencies(coverage ${PLSSVM_EXECUTABLE_TRAIN_NAME}) add_dependencies(coverage ${PLSSVM_EXECUTABLE_PREDICT_NAME}) + add_dependencies(coverage ${PLSSVM_EXECUTABLE_SCALE_NAME}) # add custom target `make clean_coverage` which calls `make clean` and also removes all generate *.gcda and *.gcno files add_custom_target(clean_coverage) @@ -244,7 +320,7 @@ if(uppercase_CMAKE_BUILD_TYPE MATCHES "COVERAGE") COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/delete_coverage_files.cmake" TARGET clean_coverage ) -endif() +endif () ######################################################################################################################## @@ -252,16 +328,16 @@ endif() ######################################################################################################################## ## the target platforms and architectures must either be set using the CMake command line option ## PLSSVM_TARGET_PLATFORMS or the environment variable with the same name -if(DEFINED PLSSVM_TARGET_PLATFORMS) +if (DEFINED PLSSVM_TARGET_PLATFORMS) set(PLSSVM_TARGET_PLATFORMS ${PLSSVM_TARGET_PLATFORMS} CACHE STRING "The target platforms to compile for." FORCE) -elseif(DEFINED ENV{PLSSVM_TARGET_PLATFORMS}) +elseif (DEFINED ENV{PLSSVM_TARGET_PLATFORMS}) set(PLSSVM_TARGET_PLATFORMS $ENV{PLSSVM_TARGET_PLATFORMS} CACHE STRING "The target platforms to compile for." FORCE) -else() +else () # check for Python3 and all necessary libraries find_package(Python3 COMPONENTS Interpreter Development) - if(NOT Python3_FOUND) + if (NOT Python3_FOUND) message(FATAL_ERROR "Can't find Python3. Please manually specify PLSSVM_TARGET_PLATFORMS (e.g. -DPLSSVM_TARGET_PLATFORMS=\"cpu;nvidia:sm_70,sm_86;amd:gfx906;intel:skl\"!") - endif() + endif () include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_python_libs.cmake) set(PLSSVM_TARGET_PLATFORMS_PYTHON_SCRIPT_REQUIRED_LIBS cpuinfo GPUtil pyamdgpuinfo pylspci) @@ -277,63 +353,63 @@ else() ) # an error occurred when running our python script - if(NOT ${PLSSVM_PYTHON_TARGET_PLATFORMS_EXIT_CODE} EQUAL 0) + if (NOT ${PLSSVM_PYTHON_TARGET_PLATFORMS_EXIT_CODE} EQUAL 0) message(FATAL_ERROR - "Error running 'utility_scripts/plssvm_target_platforms.py'." + "Error running '${CMAKE_CURRENT_SOURCE_DIR}/utility_scripts/plssvm_target_platforms.py'." "Please manually specify PLSSVM_TARGET_PLATFORMS (e.g. -DPLSSVM_TARGET_PLATFORMS=\"cpu;nvidia:sm_70,sm_86;amd:gfx906;intel:skl\"!" - ) - endif() + ) + endif () # set PLSSVM_TARGET_PLATFORMS string(STRIP "${PLSSVM_PYTHON_TARGET_PLATFORMS_OUTPUT}" PLSSVM_TARGET_PLATFORMS) message(STATUS "Automatically derived PLSSVM_TARGET_PLATFORMS=\"${PLSSVM_TARGET_PLATFORMS}\".") -endif() +endif () ## PLSSVM_TARGET_PLATFORMS must not be empty -if(PLSSVM_TARGET_PLATFORMS STREQUAL "") +if (PLSSVM_TARGET_PLATFORMS STREQUAL "") message(FATAL_ERROR "PLSSVM_TARGET_PLATFORMS must not be empty!") -endif() +endif () ## parse provided target platforms include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/parse_architecture_info.cmake) set(PLSSVM_PLATFORM_NAME_LIST "automatic") -foreach(PLSSVM_PLATFORM ${PLSSVM_TARGET_PLATFORMS}) - if(PLSSVM_PLATFORM MATCHES "^cpu") +foreach (PLSSVM_PLATFORM ${PLSSVM_TARGET_PLATFORMS}) + if (PLSSVM_PLATFORM MATCHES "^cpu") # parse provided CPU architectures parse_architecture_info(${PLSSVM_PLATFORM} PLSSVM_CPU_TARGET_ARCHS PLSSVM_NUM_CPU_TARGET_ARCHS) - if(PLSSVM_NUM_CPU_TARGET_ARCHS GREATER 1) + if (PLSSVM_NUM_CPU_TARGET_ARCHS GREATER 1) message(FATAL_ERROR "Target platform \"cpu\" must at most have one architecture specification!") - endif() + endif () target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_HAS_CPU_TARGET) list(APPEND PLSSVM_PLATFORM_NAME_LIST "cpu") - elseif(PLSSVM_PLATFORM MATCHES "^nvidia") + elseif (PLSSVM_PLATFORM MATCHES "^nvidia") # parse provided NVIDIA GPU architectures parse_architecture_info(${PLSSVM_PLATFORM} PLSSVM_NVIDIA_TARGET_ARCHS PLSSVM_NUM_NVIDIA_TARGET_ARCHS) - if(PLSSVM_NUM_NVIDIA_TARGET_ARCHS EQUAL 0) + if (PLSSVM_NUM_NVIDIA_TARGET_ARCHS EQUAL 0) message(FATAL_ERROR "Target platform \"nvidia\" must at least have one architecture specification!") - endif() + endif () target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_HAS_NVIDIA_TARGET) list(APPEND PLSSVM_PLATFORM_NAME_LIST "gpu_nvidia") - elseif(PLSSVM_PLATFORM MATCHES "^amd") + elseif (PLSSVM_PLATFORM MATCHES "^amd") # parse provided AMD GPU architectures parse_architecture_info(${PLSSVM_PLATFORM} PLSSVM_AMD_TARGET_ARCHS PLSSVM_NUM_AMD_TARGET_ARCHS) - if(PLSSVM_NUM_AMD_TARGET_ARCHS EQUAL 0) + if (PLSSVM_NUM_AMD_TARGET_ARCHS EQUAL 0) message(FATAL_ERROR "Target platform \"amd\" must at least have one architecture specification!") - endif() + endif () target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_HAS_AMD_TARGET) list(APPEND PLSSVM_PLATFORM_NAME_LIST "gpu_amd") - elseif(PLSSVM_PLATFORM MATCHES "^intel") + elseif (PLSSVM_PLATFORM MATCHES "^intel") # parse provided Intel GPU architectures parse_architecture_info(${PLSSVM_PLATFORM} PLSSVM_INTEL_TARGET_ARCHS PLSSVM_NUM_INTEL_TARGET_ARCHS) - if(PLSSVM_NUM_INTEL_TARGET_ARCHS EQUAL 0) + if (PLSSVM_NUM_INTEL_TARGET_ARCHS EQUAL 0) message(FATAL_ERROR "Target platform \"intel\" must at least have one architecture specification!") - endif() + endif () target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_HAS_INTEL_TARGET) list(APPEND PLSSVM_PLATFORM_NAME_LIST "gpu_intel") - else() + else () message(FATAL_ERROR "Unrecognized target platform \"${PLSSVM_PLATFORM}\"! Must be one of: cpu nvidia amd intel") - endif() -endforeach() + endif () +endforeach () ######################################################################################################################## @@ -342,47 +418,48 @@ endforeach() ## check for OpenMP backend set(PLSSVM_ENABLE_OPENMP_BACKEND AUTO CACHE STRING "Enable OpenMP Backend") set_property(CACHE PLSSVM_ENABLE_OPENMP_BACKEND PROPERTY STRINGS AUTO ON OFF) -if(PLSSVM_ENABLE_OPENMP_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_OPENMP_BACKEND) +if (PLSSVM_ENABLE_OPENMP_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_OPENMP_BACKEND) add_subdirectory(src/plssvm/backends/OpenMP) -endif() +endif () ## check for CUDA backend set(PLSSVM_ENABLE_CUDA_BACKEND AUTO CACHE STRING "Enable CUDA Backend") set_property(CACHE PLSSVM_ENABLE_CUDA_BACKEND PROPERTY STRINGS AUTO ON OFF) -if(PLSSVM_ENABLE_CUDA_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_CUDA_BACKEND) +if (PLSSVM_ENABLE_CUDA_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_CUDA_BACKEND) add_subdirectory(src/plssvm/backends/CUDA) -endif() +endif () ## check for HIP backend set(PLSSVM_ENABLE_HIP_BACKEND AUTO CACHE STRING "Enable HIP Backend") set_property(CACHE PLSSVM_ENABLE_HIP_BACKEND PROPERTY STRINGS AUTO ON OFF) -if(PLSSVM_ENABLE_HIP_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_HIP_BACKEND) +if (PLSSVM_ENABLE_HIP_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_HIP_BACKEND) add_subdirectory(src/plssvm/backends/HIP) -endif() +endif () ## check for OpenCL backend set(PLSSVM_ENABLE_OPENCL_BACKEND AUTO CACHE STRING "Enable OpenCL Backend") set_property(CACHE PLSSVM_ENABLE_OPENCL_BACKEND PROPERTY STRINGS AUTO ON OFF) -if(PLSSVM_ENABLE_OPENCL_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_OPENCL_BACKEND) +if (PLSSVM_ENABLE_OPENCL_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_OPENCL_BACKEND) add_subdirectory(src/plssvm/backends/OpenCL) -endif() +endif () ## check for SYCL backend set(PLSSVM_ENABLE_SYCL_BACKEND AUTO CACHE STRING "Enable SYCL Backend") set_property(CACHE PLSSVM_ENABLE_SYCL_BACKEND PROPERTY STRINGS AUTO ON OFF) -if(PLSSVM_ENABLE_SYCL_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_SYCL_BACKEND) +if (PLSSVM_ENABLE_SYCL_BACKEND MATCHES "AUTO" OR PLSSVM_ENABLE_SYCL_BACKEND) add_subdirectory(src/plssvm/backends/SYCL) -endif() +endif () ## check if ANY backend is available/has been enabled get_target_property(PLSSVM_LINKED_BACKENDS ${PLSSVM_ALL_LIBRARY_NAME} INTERFACE_LINK_LIBRARIES) -if(NOT PLSSVM_LINKED_BACKENDS) +if (NOT PLSSVM_LINKED_BACKENDS) message(FATAL_ERROR "At least one backend must be available!") -endif() +endif () ## link backends against executables target_link_libraries(${PLSSVM_EXECUTABLE_TRAIN_NAME} ${PLSSVM_ALL_LIBRARY_NAME}) target_link_libraries(${PLSSVM_EXECUTABLE_PREDICT_NAME} ${PLSSVM_ALL_LIBRARY_NAME}) +target_link_libraries(${PLSSVM_EXECUTABLE_SCALE_NAME} ${PLSSVM_ALL_LIBRARY_NAME}) ######################################################################################################################## @@ -390,134 +467,165 @@ target_link_libraries(${PLSSVM_EXECUTABLE_PREDICT_NAME} ${PLSSVM_ALL_LIBRARY_NAM ######################################################################################################################## ## add assert option option(PLSSVM_ENABLE_ASSERTS "Enables PLSSVM asserts even if NDEBUG is set." OFF) -if(PLSSVM_ENABLE_ASSERTS) +if (PLSSVM_ENABLE_ASSERTS) message(STATUS "Enable additional debugging assertions.") target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_ENABLE_ASSERTS) -endif() +endif () ## set specific thread block sizes if requested -if(DEFINED ENV{PLSSVM_THREAD_BLOCK_SIZE}) +if (DEFINED ENV{PLSSVM_THREAD_BLOCK_SIZE}) set(PLSSVM_THREAD_BLOCK_SIZE $ENV{PLSSVM_THREAD_BLOCK_SIZE} CACHE STRING "The used thread block size." FORCE) -endif() -if(DEFINED PLSSVM_THREAD_BLOCK_SIZE) +endif () +if (DEFINED PLSSVM_THREAD_BLOCK_SIZE) if (PLSSVM_THREAD_BLOCK_SIZE MATCHES "^[0-9]+$" AND PLSSVM_THREAD_BLOCK_SIZE GREATER 0) message(STATUS "Set THREAD_BLOCK_SIZE to ${PLSSVM_THREAD_BLOCK_SIZE}.") # add target definition target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_THREAD_BLOCK_SIZE=${PLSSVM_THREAD_BLOCK_SIZE}) - else() + else () message(SEND_ERROR "PLSSVM_THREAD_BLOCK_SIZE must be an integer greater than 0 but is \"${PLSSVM_THREAD_BLOCK_SIZE}\"!") - endif() -endif() + endif () +endif () ## set specific internal block sizes of requested -if(DEFINED ENV{PLSSVM_INTERNAL_BLOCK_SIZE}) +if (DEFINED ENV{PLSSVM_INTERNAL_BLOCK_SIZE}) set(PLSSVM_INTERNAL_BLOCK_SIZE $ENV{PLSSVM_INTERNAL_BLOCK_SIZE} CACHE STRING "The used internal block size." FORCE) -endif() -if(DEFINED PLSSVM_INTERNAL_BLOCK_SIZE) +endif () +if (DEFINED PLSSVM_INTERNAL_BLOCK_SIZE) if (PLSSVM_INTERNAL_BLOCK_SIZE MATCHES "^[0-9]+$" AND PLSSVM_INTERNAL_BLOCK_SIZE GREATER 0) message(STATUS "Set INTERNAL_BLOCK_SIZE to ${PLSSVM_INTERNAL_BLOCK_SIZE}.") # add target definition target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_INTERNAL_BLOCK_SIZE=${PLSSVM_INTERNAL_BLOCK_SIZE}) - else() + else () message(SEND_ERROR "PLSSVM_INTERNAL_BLOCK_SIZE must be an integer greater than 0 but is \"${PLSSVM_INTERNAL_BLOCK_SIZE}\"!") - endif() -endif() + endif () +endif () -## set specific internal block sizes of requested -if(DEFINED ENV{PLSSVM_OPENMP_BLOCK_SIZE}) +## set specific OpenMP block sizes of requested +if (DEFINED ENV{PLSSVM_OPENMP_BLOCK_SIZE}) set(PLSSVM_OPENMP_BLOCK_SIZE $ENV{PLSSVM_OPENMP_BLOCK_SIZE} CACHE STRING "The used block size for the OpenMP kernel." FORCE) -endif() -if(DEFINED PLSSVM_OPENMP_BLOCK_SIZE) +endif () +if (DEFINED PLSSVM_OPENMP_BLOCK_SIZE) if (PLSSVM_OPENMP_BLOCK_SIZE MATCHES "^[0-9]+$" AND PLSSVM_OPENMP_BLOCK_SIZE GREATER 0) message(STATUS "Set PLSSVM_OPENMP_BLOCK_SIZE to ${PLSSVM_OPENMP_BLOCK_SIZE}.") # add target definition target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_OPENMP_BLOCK_SIZE=${PLSSVM_OPENMP_BLOCK_SIZE}) - else() + else () message(SEND_ERROR "PLSSVM_OPENMP_BLOCK_SIZE must be an integer greater than 0 but is \"${PLSSVM_OPENMP_BLOCK_SIZE}\"!") - endif() -endif() + endif () +endif () + +## enable or disable the performance tracker +option(PLSSVM_ENABLE_PERFORMANCE_TRACKING "Enable performance tracking to YAML files for the PLSSVM executables plssvm-train, plssvm-predict, and plssvm-scale." OFF) +if (PLSSVM_ENABLE_PERFORMANCE_TRACKING) + message(STATUS "Enabled performance tracking to YAML files for the PLSSVM executables plssvm-train, plssvm-predict, and plssvm-scale.") + + # add target definitions + target_compile_definitions(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC PLSSVM_PERFORMANCE_TRACKER_ENABLED) +endif () -## change executable floating points from double precision to single precision -option(PLSSVM_EXECUTABLES_USE_SINGLE_PRECISION "Build the ${PLSSVM_EXECUTABLE_TRAIN_NAME} and ${PLSSVM_EXECUTABLE_PREDICT_NAME} executables with single precision instead of double precision." OFF) -if(PLSSVM_EXECUTABLES_USE_SINGLE_PRECISION) - message(STATUS "Using single precision floating point numbers for ${PLSSVM_EXECUTABLE_TRAIN_NAME} and ${PLSSVM_EXECUTABLE_PREDICT_NAME}.") - target_compile_definitions(${PLSSVM_EXECUTABLE_TRAIN_NAME} PRIVATE PLSSVM_EXECUTABLES_USE_SINGLE_PRECISION) - target_compile_definitions(${PLSSVM_EXECUTABLE_PREDICT_NAME} PRIVATE PLSSVM_EXECUTABLES_USE_SINGLE_PRECISION) -endif() ######################################################################################################################## ## check for Link Time Optimization ## ######################################################################################################################## ## enable Link Time Optimization (LTO) option(PLSSVM_ENABLE_LTO "Enable Link Time Optimizations." ON) -if(PLSSVM_ENABLE_LTO) +if (PLSSVM_ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT PLSSVM_LTO_SUPPORTED LANGUAGES CXX) - if(PLSSVM_LTO_SUPPORTED) + if (PLSSVM_LTO_SUPPORTED) message(STATUS "Interprocedural optimization (IPO/LTO) enabled.") set_property(TARGET ${PLSSVM_EXECUTABLE_TRAIN_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) set_property(TARGET ${PLSSVM_EXECUTABLE_PREDICT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) - else() + set_property(TARGET ${PLSSVM_EXECUTABLE_SCALE_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + else () message(STATUS "Interprocedural optimization (IPO/LTO) not supported.") - endif() -endif() + endif () +endif () ######################################################################################################################## ## enable documentation generation via doxygen ## ######################################################################################################################## option(PLSSVM_ENABLE_DOCUMENTATION "Build documentation using doxygen." OFF) -if(PLSSVM_ENABLE_DOCUMENTATION) +if (PLSSVM_ENABLE_DOCUMENTATION) add_subdirectory(docs) -endif() +endif () ######################################################################################################################## ## enable testing via google test ## ######################################################################################################################## option(PLSSVM_ENABLE_TESTING "Build tests for all backends." ON) -if(PLSSVM_ENABLE_TESTING) +if (PLSSVM_ENABLE_TESTING) enable_testing() add_subdirectory(tests) -endif() +endif () + + +######################################################################################################################## +## enable language bindings ## +######################################################################################################################## +option(PLSSVM_ENABLE_LANGUAGE_BINDINGS "Build langauge bindings, e.g., for Python." OFF) +if (PLSSVM_ENABLE_LANGUAGE_BINDINGS) + add_subdirectory(bindings) +endif () ######################################################################################################################## ## print short (backend) summary ## ######################################################################################################################## +message(STATUS "") message(STATUS "Enabled backends with respective targets:") set(PLSSVM_BACKEND_NAME_LIST "automatic") -if(TARGET ${PLSSVM_OPENMP_BACKEND_LIBRARY_NAME}) +if (TARGET ${PLSSVM_OPENMP_BACKEND_LIBRARY_NAME}) message(STATUS "${PLSSVM_OPENMP_BACKEND_SUMMARY_STRING}") list(APPEND PLSSVM_BACKEND_NAME_LIST "openmp") -endif() -if(TARGET ${PLSSVM_CUDA_BACKEND_LIBRARY_NAME}) +endif () +if (TARGET ${PLSSVM_CUDA_BACKEND_LIBRARY_NAME}) message(STATUS "${PLSSVM_CUDA_BACKEND_SUMMARY_STRING}") list(APPEND PLSSVM_BACKEND_NAME_LIST "cuda") -endif() -if(TARGET ${PLSSVM_HIP_BACKEND_LIBRARY_NAME}) +endif () +if (TARGET ${PLSSVM_HIP_BACKEND_LIBRARY_NAME}) message(STATUS "${PLSSVM_HIP_BACKEND_SUMMARY_STRING}") list(APPEND PLSSVM_BACKEND_NAME_LIST "hip") -endif() -if(TARGET ${PLSSVM_OPENCL_BACKEND_LIBRARY_NAME}) +endif () +if (TARGET ${PLSSVM_OPENCL_BACKEND_LIBRARY_NAME}) message(STATUS "${PLSSVM_OPENCL_BACKEND_SUMMARY_STRING}") list(APPEND PLSSVM_BACKEND_NAME_LIST "opencl") -endif() -if(TARGET ${PLSSVM_SYCL_BACKEND_LIBRARY_NAME}) - foreach(SUMMARY_STRING ${PLSSVM_SYCL_BACKEND_SUMMARY_STRINGS}) +endif () +if (TARGET ${PLSSVM_SYCL_BACKEND_LIBRARY_NAME}) + foreach (SUMMARY_STRING ${PLSSVM_SYCL_BACKEND_SUMMARY_STRINGS}) message(STATUS "${SUMMARY_STRING}") - endforeach() + endforeach () list(APPEND PLSSVM_BACKEND_NAME_LIST "sycl") -endif() +endif () +message(STATUS "") + + +######################################################################################################################## +## configure version and git metadata files ## +######################################################################################################################## +message(STATUS "Configuring version and git metadata.") +set(PRE_CONFIGURE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/version/git_metadata/git_metadata.cpp.in") +set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/plssvm/version/git_metadata/git_metadata.cpp") +set(GIT_FAIL_IF_NONZERO_EXIT FALSE) +include(cmake/git_watcher.cmake) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/plssvm/version/version.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/include/plssvm/version/version.hpp + @ONLY +) ######################################################################################################################## ## generate man pages ## ######################################################################################################################## +## assemble the SYCL manpage entry +message(STATUS "Generating manpage files.") string(REPLACE ";" "|" PLSSVM_PLATFORM_NAME_LIST "${PLSSVM_PLATFORM_NAME_LIST}") string(REPLACE ";" "|" PLSSVM_BACKEND_NAME_LIST "${PLSSVM_BACKEND_NAME_LIST}") string(REPLACE ";" "|" PLSSVM_SYCL_BACKEND_NAME_LIST "${PLSSVM_SYCL_BACKEND_NAME_LIST}") -if(TARGET ${PLSSVM_SYCL_BACKEND_LIBRARY_NAME}) +if (TARGET ${PLSSVM_SYCL_BACKEND_LIBRARY_NAME}) set(PLSSVM_SYCL_IMPLEMENTATION_TYPE_MANPAGE_ENTRY " .TP .B --sycl_implementation_type @@ -528,8 +636,18 @@ choose the SYCL implementation to be used in the SYCL backend: ${PLSSVM_SYCL_BAC .B --sycl_kernel_invocation_type choose the kernel invocation type when using SYCL as backend: automatic|nd_range|hierarchical (default: automatic) ") -endif() +endif () set(PLSSVM_SYCL_MANPAGE_ENTRY "${PLSSVM_SYCL_KERNEL_INVOCATION_TYPE_MANPAGE_ENTRY}${PLSSVM_SYCL_IMPLEMENTATION_TYPE_MANPAGE_ENTRY}") +## assemble the performance tracker manpage entry +if (PLSSVM_ENABLE_PERFORMANCE_TRACKING) + set(PLSSVM_PERFORMANCE_TRACKER_MANPAGE_ENTRY " +.TP +.B --performance_tracking +choose the output YAML file where the performance tracking entries should be stored; if not provided, the results are dumped to stderr +") +endif () + +## configure the manpage files configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-train.1.in ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-train.1 @@ -541,6 +659,11 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-predict.1 @ONLY ) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-scale.1.in + ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-scale.1 + @ONLY +) ######################################################################################################################## @@ -553,22 +676,31 @@ install(TARGETS ${PLSSVM_TARGETS_TO_INSTALL} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # all files that are neither executables, shared lib or headers LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # all shared lib files RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # all executables -) + ) ## mark header to install via 'make install' install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" -) + ) +## install header only libraries fast_float and igor header via 'make install' +install(DIRECTORY "${fast_float_SOURCE_DIR}/include/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + ) +install(DIRECTORY "${igor_SOURCE_DIR}/include/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + ) ## only mark fmt headers to install if build via FetchContent -if(NOT fmt_FOUND) +if (NOT fmt_FOUND) install(DIRECTORY "${fmt_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - ) -endif() + ) +endif () ## mark man pages to install -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-train.1 ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-predict.1 +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-train.1 + ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-predict.1 + ${CMAKE_CURRENT_SOURCE_DIR}/docs/plssvm-scale.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" -) + ) ## manage version comparison include(CMakePackageConfigHelpers) @@ -590,10 +722,10 @@ install(EXPORT plssvm_Targets FILE plssvmTargets.cmake NAMESPACE plssvm:: DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/plssvm/cmake -) + ) ## create file containing the build configuration and version information install(FILES "${PROJECT_BINARY_DIR}/plssvmConfig.cmake" "${PROJECT_BINARY_DIR}/plssvmConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/plssvm/cmake -) + ) diff --git a/README.md b/README.md index fdd424106..245f0223a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # PLSSVM - Parallel Least Squares Support Vector Machine -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e780a63075ce40c29c49d3df4f57c2af)](https://www.codacy.com/gh/SC-SGS/PLSSVM/dashboard?utm_source=github.com&utm_medium=referral&utm_content=SC-SGS/PLSSVM&utm_campaign=Badge_Grade)   [![Generate documentation](https://github.com/SC-SGS/PLSSVM/actions/workflows/documentation.yml/badge.svg)](https://sc-sgs.github.io/PLSSVM/)   [![Build Status Linux CPU](https://simsgs.informatik.uni-stuttgart.de/jenkins/buildStatus/icon?job=PLSSVM%2FGithub-CPU%2Fmain&subject=Linux+CPU)](https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/view/All/job/Github-CPU/job/main/)   [![Build Status Linux NVIDIA GPU](https://simsgs.informatik.uni-stuttgart.de/jenkins/buildStatus/icon?job=PLSSVM%2FGithub-GPU_NVIDIA%2Fmain&subject=Linux+NVIDIA+GPU)](https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/view/All/job/Github-GPU_NVIDIA/job/main/)   [![Build Status Linux AMD GPU](https://simsgs.informatik.uni-stuttgart.de/jenkins/buildStatus/icon?job=PLSSVM%2FGithub-GPU_AMD%2Fmain&subject=Linux+AMG+GPU)](https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/view/All/job/Github-GPU_AMD/job/main/)   [![Build Status Linux Multi-GPU](https://simsgs.informatik.uni-stuttgart.de/jenkins/buildStatus/icon?job=PLSSVM%2FGithub-Multi-GPU%2Fmain&subject=Linux+Multi-GPU)](https://simsgs.informatik.uni-stuttgart.de/jenkins/view/PLSSVM/job/PLSSVM/view/All/job/Github-Multi-GPU/job/main/)   [![Windows CPU](https://github.com/SC-SGS/PLSSVM/actions/workflows/msvc_windows.yml/badge.svg)](https://github.com/SC-SGS/PLSSVM/actions/workflows/msvc_windows.yml) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e780a63075ce40c29c49d3df4f57c2af)](https://www.codacy.com/gh/SC-SGS/PLSSVM/dashboard?utm_source=github.com&utm_medium=referral&utm_content=SC-SGS/PLSSVM&utm_campaign=Badge_Grade)   [![Generate documentation](https://github.com/SC-SGS/PLSSVM/actions/workflows/documentation.yml/badge.svg)](https://sc-sgs.github.io/PLSSVM/) A [Support Vector Machine (SVM)](https://en.wikipedia.org/wiki/Support-vector_machine) is a supervised machine learning model. In its basic form SVMs are used for binary classification tasks. @@ -11,6 +11,10 @@ This is also the reason, why SVMs are also called "large margin classifiers". To predict to which class a new, unseen data point belongs, the SVM simply has to calculate on which side of the previously calculated hyperplane the data point lies. This is very efficient since it only involves a single scalar product of the size corresponding to the numer of features of the data set. +

+ strong scaling CPU +

+ However, normal SVMs suffer in their potential parallelizability. Determining the hyperplane boils down to solving a convex quadratic problem. For this, most SVM implementations use Sequential Minimal Optimization (SMO), an inherently sequential algorithm. @@ -35,7 +39,7 @@ The currently available frameworks (also called backends in our PLSSVM implement - [CUDA](https://developer.nvidia.com/cuda-zone) - [HIP](https://github.com/ROCm-Developer-Tools/HIP) (only tested on AMD GPUs) - [OpenCL](https://www.khronos.org/opencl/) -- [SYCL](https://www.khronos.org/sycl/) (tested implementations are [DPC++](https://github.com/intel/llvm) and [hipSYCL](https://github.com/illuhad/hipSYCL); specifically the commits [faaba28](https://github.com/intel/llvm/tree/faaba28541138d7ad39a7fa85fa85b863560b45f) and [6962942](https://github.com/illuhad/hipSYCL/tree/6962942c430a7b221eb167b4272c29cf397cda06) respectivelly) +- [SYCL](https://www.khronos.org/sycl/) (tested implementations are [DPC++](https://github.com/intel/llvm) and [hipSYCL](https://github.com/illuhad/hipSYCL); specifically the versions [sycl-nightly/20230110](https://github.com/intel/llvm/tree/sycl-nightly/20230110) and hipSYCL commit [eb67fc4](https://github.com/illuhad/hipSYCL/commit/eb67fc46d6732b5c4f137ce5564f6adfba57eaa1)) ## Getting Started @@ -45,10 +49,11 @@ General dependencies: - a C++17 capable compiler (e.g. [`gcc`](https://gcc.gnu.org/) or [`clang`](https://clang.llvm.org/)) - [CMake](https://cmake.org/) 3.21 or newer -- [cxxopts โ‰ฅ v3.0.0](https://github.com/jarro2783/cxxopts), [fast_float](https://github.com/fastfloat/fast_float) and [{fmt} โ‰ฅ v8.0.0](https://github.com/fmtlib/fmt) (all three are automatically build during the CMake configuration if they couldn't be found using the respective `find_package` call) +- [cxxopts โ‰ฅ v3.0.0](https://github.com/jarro2783/cxxopts), [fast_float](https://github.com/fastfloat/fast_float), [{fmt} โ‰ฅ v8.1.1](https://github.com/fmtlib/fmt), and [igor](https://github.com/bluescarni/igor) (all four are automatically build during the CMake configuration if they couldn't be found using the respective `find_package` call) - [GoogleTest โ‰ฅ v1.11.0](https://github.com/google/googletest) if testing is enabled (automatically build during the CMake configuration if `find_package(GTest)` wasn't successful) - [doxygen](https://www.doxygen.nl/index.html) if documentation generation is enabled -- [OpenMP](https://www.openmp.org/) 4.0 or newer (optional) to speed-up file parsing +- [Pybind11 โ‰ฅ v2.10.3](https://github.com/pybind/pybind11) if Python bindings are enabled +- [OpenMP](https://www.openmp.org/) 4.0 or newer (optional) to speed-up library utilities (like file parsing) - multiple Python modules used in the utility scripts, to install all modules use `pip install --user -r install/python_requirements.txt` Additional dependencies for the OpenMP backend: @@ -75,7 +80,7 @@ Additional dependencies for the SYCL backend: Additional dependencies if `PLSSVM_ENABLE_TESTING` and `PLSSVM_GENERATE_TEST_FILE` are both set to `ON`: -- [Python3](https://www.python.org/) with the [`argparse`](https://docs.python.org/3/library/argparse.html), [`timeit`](https://docs.python.org/3/library/timeit.html) and [`sklearn`](https://scikit-learn.org/stable/) modules +- [Python3](https://www.python.org/) with the [`argparse`](https://docs.python.org/3/library/argparse.html), [`timeit`](https://docs.python.org/3/library/timeit.html), [`sklearn`](https://scikit-learn.org/stable/), and [`humanize`](https://pypi.org/project/humanize/) modules ### Building @@ -99,18 +104,17 @@ Valid targets are: - `amd`: compile for AMD GPUs; **at least one** architectural specification is necessary, e.g., `amd:gfx906` - `intel`: compile for Intel GPUs; **at least one** architectural specification is necessary, e.g., `intel:skl` -At least one of the above targets must be present. +At least one of the above targets must be present. If the option `PLSSVM_TARGET_PLATFORMS` is not present, the targets +are automatically determined using the Python3 `utility_scripts/plssvm_target_platforms.py` script (required Python3 dependencies: +[`argparse`](https://docs.python.org/3/library/argparse.html), [`py-cpuinfo`](https://pypi.org/project/py-cpuinfo/), +[`GPUtil`](https://pypi.org/project/GPUtil/), [`pyamdgpuinfo`](https://pypi.org/project/pyamdgpuinfo/), and +[`pylspci`](https://pypi.org/project/pylspci/)). Note that when using DPC++ only a single architectural specification for `cpu`, `nvidia` or `amd` is allowed. -To retrieve the architectural specifications of the current system, a simple Python3 script `utility/plssvm_target_platforms.py` is provided -(required Python3 dependencies: -[`argparse`](https://docs.python.org/3/library/argparse.html), [`py-cpuinfo`](https://pypi.org/project/py-cpuinfo/), -[`GPUtil`](https://pypi.org/project/GPUtil/), [`pyamdgpuinfo`](https://pypi.org/project/pyamdgpuinfo/), and -[`pylspci`](https://pypi.org/project/pylspci/)) ```bash -python3 utility/plssvm_target_platforms.py --help +python3 utility_scripts/plssvm_target_platforms.py --help usage: plssvm_target_platforms.py [-h] [--quiet] optional arguments: @@ -118,7 +122,7 @@ optional arguments: --quiet only output the final PLSSVM_TARGET_PLATFORMS string ``` -Example invocations: +Example invocation: ```bash python3 utility_scripts/plssvm_target_platforms.py @@ -129,7 +133,13 @@ Found 1 NVIDIA GPU(s): Possible -DPLSSVM_TARGET_PLATFORMS entries: cpu:avx512;nvidia:sm_86 +``` + +or with the `--quiet` flag given: + + +```bash python3 utility_scripts/plssvm_target_platforms.py --quiet cpu:avx512;intel:dg1 ``` @@ -140,9 +150,6 @@ If the architectural information for the requested GPU could not be retrieved, o - for AMD GPUs: [clang AMDGPU backend usage](https://llvm.org/docs/AMDGPUUsage.html) - for Intel GPUs and CPUs: [Ahead of Time Compilation](https://www.intel.com/content/www/us/en/develop/documentation/oneapi-dpcpp-cpp-compiler-dev-guide-and-reference/top/compilation/ahead-of-time-compilation.html) and [Intel graphics processor table](https://dgpu-docs.intel.com/devices/hardware-table.html) -If the `PLSSVM_TARGET_PLATFORMS` options isn't set during the CMake invocation and isn't set as environment variable, -CMake tries to execute the above script and uses its output to automatically set the `PLSSVM_TARGET_PLATFORMS`. -This, however, requires the Python packages to be installed. #### Optional CMake Options @@ -178,10 +185,12 @@ The `[optional_options]` can be one or multiple of: - `PLSSVM_ENABLE_ASSERTS=ON|OFF` (default: `OFF`): enables custom assertions regardless whether the `DEBUG` macro is defined or not - `PLSSVM_THREAD_BLOCK_SIZE` (default: `16`): set a specific thread block size used in the GPU kernels (for fine-tuning optimizations) - `PLSSVM_INTERNAL_BLOCK_SIZE` (default: `6`: set a specific internal block size used in the GPU kernels (for fine-tuning optimizations) -- `PLSSVM_EXECUTABLES_USE_SINGLE_PRECISION` (default: `OFF`): enables single precision calculations instead of double precision for the `plssvm-train` and `plssvm-preidct` executables +- `PLSSVM_OPENMP_BLOCK_SIZE` (default: `64`): set a specific block size used in the OpenMP kernels - `PLSSVM_ENABLE_LTO=ON|OFF` (default: `ON`): enable interprocedural optimization (IPO/LTO) if supported by the compiler - `PLSSVM_ENABLE_DOCUMENTATION=ON|OFF` (default: `OFF`): enable the `doc` target using doxygen +- `PLSSVM_ENABLE_PERFORMANCE_TRACKING`: enable gathering performance characteristics for the three executables using YAML files; example Python3 scripts to perform performance measurements and to process the resulting YAML files can be found in the `utility_scripts/` directory (requires the Python3 modules [wrapt-timeout-decorator](https://pypi.org/project/wrapt-timeout-decorator/), [`pyyaml`](https://pyyaml.org/), and [`pint`](https://pint.readthedocs.io/en/stable/)) - `PLSSVM_ENABLE_TESTING=ON|OFF` (default: `ON`): enable testing using GoogleTest and ctest +- `PLSSVM_ENABLE_LANGUAGE_BINDINGS=ON|OFF` (default: `OFF`): enable language bindings If `PLSSVM_ENABLE_TESTING` is set to `ON`, the following options can also be set: @@ -189,18 +198,39 @@ If `PLSSVM_ENABLE_TESTING` is set to `ON`, the following options can also be set - `PLSSVM_TEST_FILE_NUM_DATA_POINTS` (default: `5000`): the number of data points in the test file - `PLSSVM_TEST_FILE_NUM_FEATURES` (default: `2000`): the number of features per data point in the test file +If `PLSSVM_ENABLE_LANGUAGE_BINDINGS` is set to `ON`, the following option can also be set: + +- `PLSSVM_ENABLE_PYTHON_BINDINGS=ON|OFF` (default: `PLSSVM_ENABLE_LANGUAGE_BINDINGS`): enable Python bindings using Pybind11 + +If `PLSSVM_ENABLE_PYTHON_BINDINGS` is set to `ON`, the following options can also be set: + +- `PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE` (default: `double`): the default `real_type` used if the generic `plssvm.Model` and `plssvm.DataSet` Python classes are used +- `PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE` (default: `std::string`): the default `label_type` used if the generic `plssvm.Model` and `plssvm.DataSet` Python classes are used + If the SYCL backend is available additional options can be set. + +- `PLSSVM_ENABLE_SYCL_HIPSYCL_BACKEND=ON|OFF|AUTO` (default: `AUTO`): + - `ON`: check for hipSYCL as implementation for the SYCL backend and fail if not available + - `AUTO`: check for hipSYCL as implementation for the SYCL backend but **do not** fail if not available + - `OFF`: do not check for hipSYCL as implementation for the SYCL backend + +- `PLSSVM_ENABLE_SYCL_DPCPP_BACKEND=ON|OFF|AUTO` (default: `AUTO`): + - `ON`: check for DPC++ as implementation for the SYCL backend and fail if not available + - `AUTO`: check for DPC++ as implementation for the SYCL backend but **do not** fail if not available + - `OFF`: do not check for DPC++ as implementation for the SYCL backend + To use DPC++ for SYCL simply set the `CMAKE_CXX_COMPILER` to the respective DPC++ clang executable during CMake invocation. If the SYCL implementation is DPC++ the following additional options are available: -- `PLSSVM_SYCL_BACKEND_DPCPP_USE_LEVEL_ZERO` (default: `OFF`): use Level-Zero as the DPC++ backend instead of OpenCL +- `PLSSVM_SYCL_BACKEND_DPCPP_USE_LEVEL_ZERO` (default: `OFF`): use DPC++'s Level-Zero backend instead of its OpenCL backend +- `PLSSVM_SYCL_BACKEND_DPCPP_GPU_AMD_USE_HIP` (default: `ON`): use DPC++'s HIP backend instead of its OpenCL backend for AMD GPUs - `PLSSVM_SYCL_BACKEND_DPCPP_ENABLE_AOT` (default: `ON`): enable Ahead-of-Time (AOT) compilation for the specified target platforms If more than one SYCL implementation is available the environment variables `PLSSVM_SYCL_HIPSYCL_INCLUDE_DIR` and `PLSSVM_SYCL_DPCPP_INCLUDE_DIR` **must** be set to the respective SYCL include paths. Note that those paths **must not** be present in the `CPLUS_INCLUDE_PATH` environment variable or compilation will fail. -- `PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION` (`dpcpp`|`hipsycl`): specify the preferred SYCL implementation if the `sycl_implementation_type` is set to `automatic`; additional the specified SYCL implementation is used in the `plssvm::sycl` namespace, the other implementations are available in the `plssvm::dpcpp` and `plssvm::hipsycl` namespace respectively +- `PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION` (`dpcpp`|`hipsycl`): specify the preferred SYCL implementation if the `sycl_implementation_type` option is set to `automatic`; additional the specified SYCL implementation is used in the `plssvm::sycl` namespace, the other implementations are available in the `plssvm::dpcpp` and `plssvm::hipsycl` namespace respectively ### Running the tests @@ -242,9 +272,19 @@ The library supports the `install` target: cmake --build . -- install ``` +Afterward, the necessary exports should be performed: + +```bash +export CMAKE_PREFIX_PATH=${CMAKE_INSTALL_PREFIX}/share/plssvm/cmake:${CMAKE_PREFIX_PATH} +export MANPATH=${CMAKE_INSTALL_PREFIX}/share/man:$MANPATH + +export PATH=${CMAKE_INSTALL_PREFIX}/bin:${PATH} +export LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib:${LD_LIBRARY_PATH} +``` + ## Usage -### Generating data +### Generating artificial data The repository comes with a Python3 script (in the `utility_scripts/` directory) to simply generate arbitrarily large data sets. @@ -252,11 +292,11 @@ In order to use all functionality, the following Python3 modules must be install [`argparse`](https://docs.python.org/3/library/argparse.html), [`timeit`](https://docs.python.org/3/library/timeit.html), [`numpy`](https://pypi.org/project/numpy/), [`pandas`](https://pypi.org/project/pandas/), [`sklearn`](https://scikit-learn.org/stable/), [`arff`](https://pypi.org/project/arff/), -[`matplotlib`](https://pypi.org/project/matplotlib/) and -[`mpl_toolkits`](https://pypi.org/project/matplotlib/) +[`matplotlib`](https://pypi.org/project/matplotlib/), [`mpl_toolkits`](https://pypi.org/project/matplotlib/), +and [`humanize`](https://pypi.org/project/humanize/). ```bash -python3 utility_scripts/generate_data**.py --help +python3 utility_scripts/generate_data.py --help usage: generate_data.py [-h] --output OUTPUT --format FORMAT [--problem PROBLEM] --samples SAMPLES [--test_samples TEST_SAMPLES] --features FEATURES [--plot] optional arguments: @@ -294,14 +334,21 @@ Usage: -r, --coef0 arg set coef0 in kernel function (default: 0) -c, --cost arg set the parameter C (default: 1) -e, --epsilon arg set the tolerance of termination criterion (default: 0.001) + -i, --max_iter arg set the maximum number of CG iterations (default: num_features) -b, --backend arg choose the backend: automatic|openmp|cuda|hip|opencl|sycl (default: automatic) -p, --target_platform arg choose the target platform: automatic|cpu|gpu_nvidia|gpu_amd|gpu_intel (default: automatic) --sycl_kernel_invocation_type arg choose the kernel invocation type when using SYCL as backend: automatic|nd_range|hierarchical (default: automatic) --sycl_implementation_type arg choose the SYCL implementation to be used in the SYCL backend: automatic|dpcpp|hipsycl (default: automatic) - -q, --quiet quiet mode (no outputs) + --performance_tracking arg + the output YAML file where the performance tracking results are written to; if not provided, the results are dumped to stderr + --use_strings_as_labels use strings as labels instead of plane numbers + --use_float_as_real_type use floats as real types instead of doubles + --verbosity choose the level of verbosity: full|timing|libsvm|quiet (default: full) + -q, --quiet quiet mode (no outputs regardless the provided verbosity level!) -h, --help print this helper message + -v, --version print version information --input training_set_file --model model_file @@ -341,11 +388,12 @@ The `--target_platform=automatic` option works for the different backends as fol - `OpenMP`: always selects a CPU - `CUDA`: always selects an NVIDIA GPU (if no NVIDIA GPU is available, throws an exception) +- `HIP`: always selects an AMD GPU (if no AMD GPU is available, throws an exception) - `OpenCL`: tries to find available devices in the following order: NVIDIA GPUs ๐Ÿ ฆ AMD GPUs ๐Ÿ ฆ Intel GPUs ๐Ÿ ฆ CPU - `SYCL`: tries to find available devices in the following order: NVIDIA GPUs ๐Ÿ ฆ AMD GPUs ๐Ÿ ฆ Intel GPUs ๐Ÿ ฆ CPU The `--sycl_kernel_invocation_type` and `--sycl_implementation_type` flags are only used if the `--backend` is `sycl`, otherwise a warning is emitted on `stderr`. -If the `--sycl_kernel_invocation_type` is `automatic`, the `nd_range` invocation type is always used, except for hipSYCL on CPUs where the hierarchical formulation is used instead. +If the `--sycl_kernel_invocation_type` is `automatic`, the `nd_range` invocation type is always used, except for hipSYCL on CPUs where the hierarchical formulation is used instead (if hipSYCL wasn't build with `omp.accelerated`). If the `--sycl_implementation_type` is `automatic`, the used SYCL implementation is determined by the `PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION` cmake flag. ### Predicting @@ -360,8 +408,14 @@ Usage: -p, --target_platform arg choose the target platform: automatic|cpu|gpu_nvidia|gpu_amd|gpu_intel (default: automatic) --sycl_implementation_type arg choose the SYCL implementation to be used in the SYCL backend: automatic|dpcpp|hipsycl (default: automatic) - -q, --quiet quiet mode (no outputs) + --performance_tracking arg + the output YAML file where the performance tracking results are written to; if not provided, the results are dumped to stderr + --use_strings_as_labels use strings as labels instead of plane numbers + --use_float_as_real_type use floats as real types instead of doubles + --verbosity choose the level of verbosity: full|timing|libsvm|quiet (default: full) + -q, --quiet quiet mode (no outputs regardless the provided verbosity level!) -h, --help print this helper message + -v, --version print version information --test test_file --model model_file --output output_file @@ -381,7 +435,44 @@ Another example targeting NVIDIA GPUs using the SYCL backend looks like: The `--target_platform=automatic` and `--sycl_implementation_type` flags work like in the training (`./plssvm-train`) case. -For more information see the `man` pages for `plssvm-train` and `plssvm-predict` (which are installed via `cmake --build . -- install`). +### Scaling + +```bash +LS-SVM with multiple (GPU-)backends +Usage: + ./plssvm-scale [OPTION...] input_file [scaled_file] + + -l, --lower arg lower is the lowest (minimal) value allowed in each dimension (default: -1) + -u, --upper arg upper is the highest (maximal) value allowed in each dimension (default: 1) + -f, --format arg the file format to output the scaled data set to (default: libsvm) + -s, --save_filename arg the file to which the scaling factors should be saved + -r, --restore_filename arg the file from which previous scaling factors should be loaded + --performance_tracking arg + the output YAML file where the performance tracking results are written to; if not provided, the results are dumped to stderr + --use_strings_as_labels use strings as labels instead of plane numbers + --use_float_as_real_type use floats as real types instead of doubles + --verbosity choose the level of verbosity: full|timing|libsvm|quiet (default: full) + -q, --quiet quiet mode (no outputs regardless the provided verbosity level!) + -h, --help print this helper message + -v, --version print version information + --input input_file + --scaled scaled_file +``` + +An example invocation could look like: + +```bash +./plssvm-scale -l -0.5 -u 1.5 --input /path/to/input_file --scaled /path/to/scaled_file +``` + +An example invocation to scale a train and test file in the same way looks like: + +```bash +./plssvm-scale -l -1.0 -u 1.0 -s scaling_parameter.txt train_file.libsvm train_file_scaled.libsvm +./plssvm-scale -r scaling_parameter.txt test_file.libsvm test_file_scaled.libsvm +``` + +For more information see the `man` pages for `plssvm-train`, `plssvm-predict`, and `plssvm-scale` (which are installed via `cmake --build . -- install`). ## Example code for usage as library @@ -396,27 +487,31 @@ A simple C++ program (`main.cpp`) using this library could look like: int main() { try { - // parse SVM parameter from command line - plssvm::parameter params; - params.backend = plssvm::backend_type::cuda; + + // create a new C-SVM parameter set, explicitly overriding the default kernel function + const plssvm::parameter params{ plssvm::kernel_type = plssvm::kernel_function_type::polynomial }; - params.parse_train_file("train_file.libsvm"); + // create two data sets: one with the training data scaled to [-1, 1] + // and one with the test data scaled like the training data + const plssvm::data_set train_data{ "train_file.libsvm", { -1.0, 1.0 } }; + const plssvm::data_set test_data{ "test_file.libsvm", train_data.scaling_factors()->get() }; - // create C-SVM (based on selected backend) - auto svm = plssvm::make_csvm(params); + // create C-SVM using the default backend and the previously defined parameter + const auto svm = plssvm::make_csvm(params); - // learn - svm->learn(); + // fit using the training data, (optionally) set the termination criterion + const plssvm::model model = svm->fit(train_data, plssvm::epsilon = 10e-6); - // get accuracy - std::cout << "accuracy: " << svm->accuracy() << std::endl; + // get accuracy of the trained model + const double model_accuracy = svm->score(model); + std::cout << "model accuracy: " << model_accuracy << std::endl; - // predict - std::vector point = { ... }; - std::cout << "label: " << svm->predict(point) << std::endl; + // predict the labels + const std::vector label = svm->predict(model, test_data); // write model file to disk - svm->write_model("model_file.libsvm"); + model.save("model_file.libsvm"); + } catch (const plssvm::exception &e) { std::cerr << e.what_with_loc() << std::endl; } catch (const std::exception &e) { @@ -443,6 +538,67 @@ target_compile_features(prog PUBLIC cxx_std_17) target_link_libraries(prog PUBLIC plssvm::plssvm-all) ``` +### Using the Python bindings + +Roughly the same can be achieved using our Python bindings with the following Python script: + +```python +import plssvm + +try: + # create a new C-SVM parameter set, explicitly overriding the default kernel function + params = plssvm.Parameter(kernel_type=plssvm.KernelFunctionType.POLYNOMIAL) + + # create two data sets: one with the training data scaled to [-1, 1] + # and one with the test data scaled like the training data + train_data = plssvm.DataSet("train_data.libsvm", scaling=(-1.0, 1.0)) + test_data = plssvm.DataSet("test_data.libsvm", scaling=train_data.scaling_factors()) + + # create C-SVM using the default backend and the previously defined parameter + svm = plssvm.CSVM(params) + + # fit using the training data, (optionally) set the termination criterion + model = svm.fit(train_data, epsilon=10e-6) + + # get accuracy of the trained model + model_accuracy = svm.score(model) + print("model accuracy: {}".format(model_accuracy)) + + # predict labels + label = svm.predict(model, test_data) + + # write model file to disk + model.save("model_file.libsvm") +except plssvm.PLSSVMError as e: + print(e) +except RuntimeError as e: + print(e) +``` + +**Note:** it may be necessary to set `PYTHONPATH` to the `lib` folder in the PLSSVM install path. + +We also provide Python bindings for a `plssvm.SVC` class that offers the same interface as the [`sklearn.svm.SVC`](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html) class. +Note that currently not all functionality has been implemented in PLSSVM. +The respective functions will throw a Python `AttributeError` if called. + +## Citing PLSSVM + +If you use PLSSVM in your research, we kindly request you to cite: + +```text +@inproceedings{9835379, + author={Van Craen, Alexander and Breyer, Marcel and Pfl\"{u}ger, Dirk}, + booktitle={2022 IEEE International Parallel and Distributed Processing Symposium Workshops (IPDPSW)}, + title={PLSSVM: A (multi-)GPGPU-accelerated Least Squares Support Vector Machine}, + year={2022}, + volume={}, + number={}, + pages={818-827}, + doi={10.1109/IPDPSW55747.2022.00138} +} +``` +For a full list of all publications involving PLSSVM see our [Wiki Page](https://github.com/SC-SGS/PLSSVM/wiki/List-of-Publications-involving-PLSSVM). + ## License The PLSSVM library is distributed under the MIT [license](https://github.com/SC-SGS/PLSSVM/blob/main/LICENSE.md). diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 000000000..fd25c782c --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,23 @@ +## Authors: Alexander Van Craen, Marcel Breyer +## Copyright (C): 2018-today The PLSSVM project - All Rights Reserved +## License: This file is part of the PLSSVM project which is released under the MIT license. +## See the LICENSE.md file in the project root for full license information. +######################################################################################################################## + + +include(CMakeDependentOption) + +list(APPEND CMAKE_MESSAGE_INDENT "Bindings: ") + + +# enable Python langauge bindings +cmake_dependent_option(PLSSVM_ENABLE_PYTHON_BINDINGS "Build langauge bindings for Python." ON "PLSSVM_ENABLE_LANGUAGE_BINDINGS" OFF) +if (PLSSVM_ENABLE_PYTHON_BINDINGS) + add_subdirectory(Python) +endif () + + +# explicitly update install targets in parent scope +set(PLSSVM_TARGETS_TO_INSTALL "${PLSSVM_TARGETS_TO_INSTALL}" PARENT_SCOPE) + +list(POP_BACK CMAKE_MESSAGE_INDENT) \ No newline at end of file diff --git a/bindings/Python/CMakeLists.txt b/bindings/Python/CMakeLists.txt new file mode 100644 index 000000000..c9872ab7f --- /dev/null +++ b/bindings/Python/CMakeLists.txt @@ -0,0 +1,121 @@ +## Authors: Alexander Van Craen, Marcel Breyer +## Copyright (C): 2018-today The PLSSVM project - All Rights Reserved +## License: This file is part of the PLSSVM project which is released under the MIT license. +## See the LICENSE.md file in the project root for full license information. +######################################################################################################################## + +include(${PROJECT_SOURCE_DIR}/cmake/utility_macros.cmake) + +message(STATUS "Building Python language bindings for PLSSVM.") + +## try finding pybind11 +find_package(pybind11 2.10.3 QUIET) +if (pybind11_FOUND) + message(STATUS "Found package pybind11.") +else () + message(STATUS "Couldn't find package pybind11. Building from source ...") + set(PLSSVM_pybind11_VERSION v2.10.3) + # fetch pybind11 library for creating Python bindings + FetchContent_Declare(pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG ${PLSSVM_pybind11_VERSION} + GIT_SHALLOW TRUE + QUIET + ) + FetchContent_MakeAvailable(pybind11) + target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC + $ + $ + ) + message(STATUS "Installed pybind11 version ${PLSSVM_pybind11_VERSION}.") +endif () + +# set source files that are always used +set(PLSSVM_PYTHON_BINDINGS_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/exceptions/exceptions.cpp + + ${CMAKE_CURRENT_LIST_DIR}/detail/logger.cpp + ${CMAKE_CURRENT_LIST_DIR}/detail/performance_tracker.cpp + + ${CMAKE_CURRENT_LIST_DIR}/version/version.cpp + + ${CMAKE_CURRENT_LIST_DIR}/backend_types.cpp + ${CMAKE_CURRENT_LIST_DIR}/csvm.cpp + ${CMAKE_CURRENT_LIST_DIR}/data_set.cpp + ${CMAKE_CURRENT_LIST_DIR}/file_format_types.cpp + ${CMAKE_CURRENT_LIST_DIR}/kernel_function_types.cpp + ${CMAKE_CURRENT_LIST_DIR}/model.cpp + ${CMAKE_CURRENT_LIST_DIR}/parameter.cpp + ${CMAKE_CURRENT_LIST_DIR}/target_platforms.cpp + + ${CMAKE_CURRENT_LIST_DIR}/sklearn.cpp + + ${CMAKE_CURRENT_LIST_DIR}/main.cpp + ) +# set backends specific source files if the respective backend is used +if (TARGET ${PLSSVM_OPENMP_BACKEND_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/openmp_csvm.cpp) +endif () +if (TARGET ${PLSSVM_CUDA_BACKEND_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/cuda_csvm.cpp) +endif () +if (TARGET ${PLSSVM_HIP_BACKEND_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/hip_csvm.cpp) +endif () +if (TARGET ${PLSSVM_OPENCL_BACKEND_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/opencl_csvm.cpp) +endif () +if (TARGET ${PLSSVM_SYCL_BACKEND_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/sycl.cpp) +endif () +if (TARGET ${PLSSVM_SYCL_BACKEND_HIPSYCL_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/hipsycl_csvm.cpp) +endif () +if (TARGET ${PLSSVM_SYCL_BACKEND_DPCPP_LIBRARY_NAME}) + list(APPEND PLSSVM_PYTHON_BINDINGS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/backends/dpcpp_csvm.cpp) +endif () + +# create pybind11 module +set(PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME plssvm) +pybind11_add_module(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} ${PLSSVM_PYTHON_BINDINGS_SOURCES}) + +# set default real type +set(PLSSVM_PYTHON_BINDINGS_POSSIBLE_REAL_TYPE "float;double") +set(PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE "double" CACHE STRING "The preferred type of the data points for the Python bindings.") +set_property(CACHE PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE PROPERTY STRINGS ${PLSSVM_PYTHON_BINDINGS_POSSIBLE_REAL_TYPE}) +if (NOT "${PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE}" IN_LIST PLSSVM_PYTHON_BINDINGS_POSSIBLE_REAL_TYPE) + message(FATAL_ERROR "The provided real_type \"${PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE}\" is not one of the allowed values: \"${PLSSVM_PYTHON_BINDINGS_POSSIBLE_REAL_TYPE}\"") +endif () +message(STATUS "The preferred real_type for the Python bindings is \"${PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE}\".") + +# set default label type +set(PLSSVM_PYTHON_BINDINGS_POSSIBLE_LABEL_TYPE "bool;char;signed char;unsigned char;short;unsigned short;int;unsigned int;long;unsigned long;long long;unsigned long long;float;double;long double;std::string") +set(PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE "std::string" CACHE STRING "The preferred type of the labels for the Python bindings.") +set_property(CACHE PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE PROPERTY STRINGS ${PLSSVM_PYTHON_BINDINGS_POSSIBLE_LABEL_TYPE}) +if (NOT "${PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE}" IN_LIST PLSSVM_PYTHON_BINDINGS_POSSIBLE_LABEL_TYPE) + message(FATAL_ERROR "The provided label_type \"${PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE}\" is not one of the allowed values: \"${PLSSVM_PYTHON_BINDINGS_POSSIBLE_LABEL_TYPE}\"") +endif () +message(STATUS "The preferred label_type for the Python bindings is \"${PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE}\".") + +# add necessary compile definitions for the default real_type and label_type +target_compile_definitions(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} PRIVATE PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE=${PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE}) +target_compile_definitions(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} PRIVATE PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE=${PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE}) +if (PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE STREQUAL "std::string") + target_compile_definitions(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} PRIVATE PLSSVM_PYTHON_BINDINGS_LABEL_TYPE_IS_STRING) +endif () + +# add necessary compile options +target_include_directories(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} PRIVATE ${PLSSVM_ALL_LIBRARY_NAME}) +target_compile_definitions(${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME} PRIVATE PYBIND11_DETAILED_ERROR_MESSAGES) + +# disable clang compiler warning +target_compile_options(${PLSSVM_BASE_LIBRARY_NAME} PUBLIC $<$:-Wno-self-assign-overloaded>) + +# find required python libraries +find_package(PythonLibs REQUIRED) +target_include_directories(${PLSSVM_BASE_LIBRARY_NAME} PRIVATE ${PYTHON_INCLUDE_DIRS}) +target_link_libraries(${PLSSVM_BASE_LIBRARY_NAME} PRIVATE ${GPU_SAMPLER_LIBRARY_NAME}) + +# append pybind11 bindings library to installed targets +append_local_and_parent(PLSSVM_TARGETS_TO_INSTALL ${PLSSVM_PYTHON_BINDINGS_LIBRARY_NAME}) \ No newline at end of file diff --git a/bindings/Python/backend_types.cpp b/bindings/Python/backend_types.cpp new file mode 100644 index 000000000..7af6bbf19 --- /dev/null +++ b/bindings/Python/backend_types.cpp @@ -0,0 +1,29 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backend_types.hpp" + +#include "pybind11/pybind11.h" // py::module_, py::enum_ +#include "pybind11/stl.h" // support for STL types: std::vector + +namespace py = pybind11; + +void init_backend_types(py::module_ &m) { + // bind enum class + py::enum_(m, "BackendType") + .value("AUTOMATIC", plssvm::backend_type::automatic, "the default backend; depends on the specified target platform") + .value("OPENMP", plssvm::backend_type::openmp, "OpenMP to target CPUs only (currently no OpenMP target offloading support)") + .value("CUDA", plssvm::backend_type::cuda, "CUDA to target NVIDIA GPUs only") + .value("HIP", plssvm::backend_type::hip, "HIP to target AMD and NVIDIA GPUs") + .value("OPENCL", plssvm::backend_type::opencl, "OpenCL to target CPUs and GPUs from different vendors") + .value("SYCL", plssvm::backend_type::sycl, "SYCL o target CPUs and GPUs from different vendors; currently tested SYCL implementations are DPC++ and hipSYCL"); + + // bind free functions + m.def("list_available_backends", &plssvm::list_available_backends, "list the available backends (as found during CMake configuration)"); + m.def("determine_default_backend", &plssvm::determine_default_backend, "determine the default backend given the list of available backends and target platforms", py::arg("available_backends") = plssvm::list_available_backends(), py::arg("available_target_platforms") = plssvm::list_available_target_platforms()); +} \ No newline at end of file diff --git a/bindings/Python/backends/cuda_csvm.cpp b/bindings/Python/backends/cuda_csvm.cpp new file mode 100644 index 000000000..7c5d9e1ac --- /dev/null +++ b/bindings/Python/backends/cuda_csvm.cpp @@ -0,0 +1,56 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/CUDA/csvm.hpp" +#include "plssvm/backends/CUDA/exceptions.hpp" + +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/parameter.hpp" // plssvm::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "../utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter, register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init +#include "pybind11/stl.h" // support for STL types + +#include // std::make_unique + +namespace py = pybind11; + +void init_cuda_csvm(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the CUDA CSVM bindings + py::module_ cuda_module = m.def_submodule("cuda", "a module containing all CUDA backend specific functionality"); + + // bind the CSVM using the CUDA backend + py::class_(cuda_module, "CSVM") + .def(py::init<>(), "create an SVM with the automatic target platform and default parameter object") + .def(py::init(), "create an SVM with the automatic target platform and provided parameter object") + .def(py::init(), "create an SVM with the provided target platform and default parameter object") + .def(py::init(), "create an SVM with the provided target platform and parameter object") + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the default target platform + return std::make_unique(params); + }), + "create an SVM with the default target platform and keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the provided target platform + return std::make_unique(target, params); + }), + "create an SVM with the provided target platform and keyword arguments"); + + // register CUDA backend specific exceptions + register_py_exception(cuda_module, "BackendError", base_exception); +} \ No newline at end of file diff --git a/bindings/Python/backends/dpcpp_csvm.cpp b/bindings/Python/backends/dpcpp_csvm.cpp new file mode 100644 index 000000000..099c9f39e --- /dev/null +++ b/bindings/Python/backends/dpcpp_csvm.cpp @@ -0,0 +1,63 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/SYCL/DPCPP/csvm.hpp" +#include "plssvm/backends/SYCL/exceptions.hpp" + +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/parameter.hpp" // plssvm::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "../utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter, register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init +#include "pybind11/stl.h" // support for STL types + +#include // std::make_unique + +namespace py = pybind11; + +py::module_ init_dpcpp_csvm(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the DPCPP CSVM bindings + py::module_ dpcpp_module = m.def_submodule("dpcpp", "a module containing all DPC++ SYCL backend specific functionality"); + + // bind the CSVM using the DPCPP backend + py::class_(dpcpp_module, "CSVM") + .def(py::init<>(), "create an SVM with the automatic target platform and default parameter object") + .def(py::init(), "create an SVM with the automatic target platform and provided parameter object") + .def(py::init(), "create an SVM with the provided target platform and default parameter object") + .def(py::init(), "create an SVM with the provided target platform and parameter object") + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost", "sycl_kernel_invocation_type" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // set SYCL kernel invocation type + const plssvm::sycl::kernel_invocation_type invoc = args.contains("sycl_kernel_invocation_type") ? args["sycl_kernel_invocation_type"].cast() : plssvm::sycl::kernel_invocation_type::automatic; + // create CSVM with the default target platform + return std::make_unique(params, plssvm::sycl_kernel_invocation_type = invoc); + }), + "create an SVM with the default target platform and keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost", "sycl_kernel_invocation_type" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // set SYCL kernel invocation type + const plssvm::sycl::kernel_invocation_type invoc = args.contains("sycl_kernel_invocation_type") ? args["sycl_kernel_invocation_type"].cast() : plssvm::sycl::kernel_invocation_type::automatic; + // create CSVM with the default target platform + return std::make_unique(target, params, plssvm::sycl_kernel_invocation_type = invoc); + }), + "create an SVM with the provided target platform and keyword arguments") + .def("get_kernel_invocation_type", &plssvm::dpcpp::csvm::get_kernel_invocation_type, "get the kernel invocation type used in this SYCL SVM"); + + // register DPCPP backend specific exceptions + register_py_exception(dpcpp_module, "BackendError", base_exception); + + return dpcpp_module; +} diff --git a/bindings/Python/backends/hip_csvm.cpp b/bindings/Python/backends/hip_csvm.cpp new file mode 100644 index 000000000..e90d404b2 --- /dev/null +++ b/bindings/Python/backends/hip_csvm.cpp @@ -0,0 +1,56 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/HIP/csvm.hpp" +#include "plssvm/backends/HIP/exceptions.hpp" + +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/parameter.hpp" // plssvm::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "../utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter, register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init +#include "pybind11/stl.h" // support for STL types + +#include // std::make_unique + +namespace py = pybind11; + +void init_hip_csvm(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the HIP CSVM bindings + py::module_ hip_module = m.def_submodule("hip", "a module containing all HIP backend specific functionality"); + + // bind the CSVM using the HIP backend + py::class_(hip_module, "CSVM") + .def(py::init<>(), "create an SVM with the automatic target platform and default parameter object") + .def(py::init(), "create an SVM with the automatic target platform and provided parameter object") + .def(py::init(), "create an SVM with the provided target platform and default parameter object") + .def(py::init(), "create an SVM with the provided target platform and parameter object") + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the default target platform + return std::make_unique(params); + }), + "create an SVM with the default target platform and keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the provided target platform + return std::make_unique(target, params); + }), + "create an SVM with the provided target platform and keyword arguments"); + + // register HIP backend specific exceptions + register_py_exception(hip_module, "BackendError", base_exception); +} \ No newline at end of file diff --git a/bindings/Python/backends/hipsycl_csvm.cpp b/bindings/Python/backends/hipsycl_csvm.cpp new file mode 100644 index 000000000..651fafa2b --- /dev/null +++ b/bindings/Python/backends/hipsycl_csvm.cpp @@ -0,0 +1,63 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/SYCL/exceptions.hpp" +#include "plssvm/backends/SYCL/hipSYCL/csvm.hpp" + +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/parameter.hpp" // plssvm::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "../utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter, register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init +#include "pybind11/stl.h" // support for STL types + +#include // std::make_unique + +namespace py = pybind11; + +py::module_ init_hipsycl_csvm(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the hipSYCL CSVM bindings + py::module_ hipsycl_module = m.def_submodule("hipsycl", "a module containing all hipSYCL SYCL backend specific functionality"); + + // bind the CSVM using the hipSYCL backend + py::class_(hipsycl_module, "CSVM") + .def(py::init<>(), "create an SVM with the automatic target platform and default parameter object") + .def(py::init(), "create an SVM with the automatic target platform and provided parameter object") + .def(py::init(), "create an SVM with the provided target platform and default parameter object") + .def(py::init(), "create an SVM with the provided target platform and parameter object") + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost", "sycl_kernel_invocation_type" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // set SYCL kernel invocation type + const plssvm::sycl::kernel_invocation_type invoc = args.contains("sycl_kernel_invocation_type") ? args["sycl_kernel_invocation_type"].cast() : plssvm::sycl::kernel_invocation_type::automatic; + // create CSVM with the default target platform + return std::make_unique(params, plssvm::sycl_kernel_invocation_type = invoc); + }), + "create an SVM with the default target platform and keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost", "sycl_kernel_invocation_type" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // set SYCL kernel invocation type + const plssvm::sycl::kernel_invocation_type invoc = args.contains("sycl_kernel_invocation_type") ? args["sycl_kernel_invocation_type"].cast() : plssvm::sycl::kernel_invocation_type::automatic; + // create CSVM with the default target platform + return std::make_unique(target, params, plssvm::sycl_kernel_invocation_type = invoc); + }), + "create an SVM with the provided target platform and keyword arguments") + .def("get_kernel_invocation_type", &plssvm::hipsycl::csvm::get_kernel_invocation_type, "get the kernel invocation type used in this SYCL SVM"); + + // register hipSYCL backend specific exceptions + register_py_exception(hipsycl_module, "BackendError", base_exception); + + return hipsycl_module; +} \ No newline at end of file diff --git a/bindings/Python/backends/opencl_csvm.cpp b/bindings/Python/backends/opencl_csvm.cpp new file mode 100644 index 000000000..a26973931 --- /dev/null +++ b/bindings/Python/backends/opencl_csvm.cpp @@ -0,0 +1,56 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/OpenCL/csvm.hpp" +#include "plssvm/backends/OpenCL/exceptions.hpp" + +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/parameter.hpp" // plssvm::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "../utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter, register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init +#include "pybind11/stl.h" // support for STL types + +#include // std::make_unique + +namespace py = pybind11; + +void init_opencl_csvm(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the OpenCL CSVM bindings + py::module_ opencl_module = m.def_submodule("opencl", "a module containing all OpenCL backend specific functionality"); + + // bind the CSVM using the OpenCL backend + py::class_(opencl_module, "CSVM") + .def(py::init<>(), "create an SVM with the automatic target platform and default parameter object") + .def(py::init(), "create an SVM with the automatic target platform and provided parameter object") + .def(py::init(), "create an SVM with the provided target platform and default parameter object") + .def(py::init(), "create an SVM with the provided target platform and parameter object") + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the default target platform + return std::make_unique(params); + }), + "create an SVM with the default target platform and keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the provided target platform + return std::make_unique(target, params); + }), + "create an SVM with the provided target platform and keyword arguments"); + + // register OpenCL backend specific exceptions + register_py_exception(opencl_module, "BackendError", base_exception); +} \ No newline at end of file diff --git a/bindings/Python/backends/openmp_csvm.cpp b/bindings/Python/backends/openmp_csvm.cpp new file mode 100644 index 000000000..6a03e4edf --- /dev/null +++ b/bindings/Python/backends/openmp_csvm.cpp @@ -0,0 +1,56 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/OpenMP/csvm.hpp" +#include "plssvm/backends/OpenMP/exceptions.hpp" + +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/parameter.hpp" // plssvm::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "../utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter, register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init +#include "pybind11/stl.h" // support for STL types + +#include // std::make_unique + +namespace py = pybind11; + +void init_openmp_csvm(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the OpenMP CSVM bindings + py::module_ openmp_module = m.def_submodule("openmp", "a module containing all OpenMP backend specific functionality"); + + // bind the CSVM using the OpenMP backend + py::class_(openmp_module, "CSVM") + .def(py::init<>(), "create an SVM with the automatic target platform and default parameter object") + .def(py::init(), "create an SVM with the automatic target platform and provided parameter object") + .def(py::init(), "create an SVM with the provided target platform and default parameter object") + .def(py::init(), "create an SVM with the provided target platform and parameter object") + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the default target platform + return std::make_unique(params); + }), + "create an SVM with the default target platform and keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args); + // create CSVM with the provided target platform + return std::make_unique(target, params); + }), + "create an SVM with the provided target platform and keyword arguments"); + + // register OpenMP backend specific exceptions + register_py_exception(openmp_module, "BackendError", base_exception); +} \ No newline at end of file diff --git a/bindings/Python/backends/sycl.cpp b/bindings/Python/backends/sycl.cpp new file mode 100644 index 000000000..f82990928 --- /dev/null +++ b/bindings/Python/backends/sycl.cpp @@ -0,0 +1,56 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/backends/SYCL/exceptions.hpp" +#include "plssvm/backends/SYCL/implementation_type.hpp" +#include "plssvm/backends/SYCL/kernel_invocation_type.hpp" + +#include "../utility.hpp" // register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::enum_, py::exception +#include "pybind11/stl.h" // support for STL types: std:vector + +#define PLSSVM_CONCATENATE_DETAIL(x, y) x##y +#define PLSSVM_CONCATENATE(x, y) PLSSVM_CONCATENATE_DETAIL(x, y) + +namespace py = pybind11; + +py::module_ init_hipsycl_csvm(py::module_ &, const py::exception &); +py::module_ init_dpcpp_csvm(py::module_ &, const py::exception &); + +void init_sycl(py::module_ &m, const py::exception &base_exception) { + // use its own submodule for the SYCL specific bindings + py::module_ sycl_module = m.def_submodule("sycl", "a module containing all SYCL backend specific functionality"); + + // register SYCL backend specific exceptions + register_py_exception(sycl_module, "BackendError", base_exception); + + // bind the two enum classes + py::enum_(sycl_module, "ImplementationType") + .value("AUTOMATIC", plssvm::sycl::implementation_type::automatic, "use the available SYCL implementation; if more than one implementation is available, the macro PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION must be defined during the CMake configuration") + .value("DPCPP", plssvm::sycl::implementation_type::dpcpp, "use DPC++ as SYCL implementation") + .value("HIPSYCL", plssvm::sycl::implementation_type::hipsycl, "use hipSYCL as SYCL implementation"); + + sycl_module.def("list_available_sycl_implementations", &plssvm::sycl::list_available_sycl_implementations, "list all available SYCL implementations"); + + py::enum_(sycl_module, "KernelInvocationType") + .value("AUTOMATIC", plssvm::sycl::kernel_invocation_type::automatic, "use the best kernel invocation type for the current SYCL implementation and target hardware platform") + .value("ND_RANGE", plssvm::sycl::kernel_invocation_type::nd_range, "use the nd_range kernel invocation type") + .value("HIERARCHICAL", plssvm::sycl::kernel_invocation_type::hierarchical, "use the hierarchical kernel invocation type"); + +// initialize SYCL binding classes +#if defined(PLSSVM_SYCL_BACKEND_HAS_HIPSYCL) + const py::module_ hipsycl_module = init_hipsycl_csvm(m, base_exception); +#endif +#if defined(PLSSVM_SYCL_BACKEND_HAS_DPCPP) + const py::module_ dpcpp_module = init_dpcpp_csvm(m, base_exception); +#endif + + // "alias" one of the DPC++ or hipSYCL CSVMs to be the default SYCL CSVM + sycl_module.attr("CSVM") = PLSSVM_CONCATENATE(PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION, _module).attr("CSVM"); +} \ No newline at end of file diff --git a/bindings/Python/csvm.cpp b/bindings/Python/csvm.cpp new file mode 100644 index 000000000..17ebef89e --- /dev/null +++ b/bindings/Python/csvm.cpp @@ -0,0 +1,182 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/csvm.hpp" +#include "plssvm/csvm_factory.hpp" + +#include "utility.hpp" // check_kwargs_for_correctness, convert_kwargs_to_parameter + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::kwargs, py::overload_cast, py::const_ + +#include // std::size_t +#include // std::string +#include // std::tuple_element_t, std::tuple_size_v +#include // std::is_same_v +#include // std::integer_sequence, std::make_integer_sequence + +namespace py = pybind11; + +template +void instantiate_csvm_functions(py::class_ &c, plssvm::detail::real_type_label_type_combination) { + c.def( + "fit", [](const plssvm::csvm &self, const plssvm::data_set &data, const py::kwargs &args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "epsilon", "max_iter" }); + + if (args.contains("epsilon") && args.contains("max_iter")) { + return self.fit(data, plssvm::epsilon = args["epsilon"].cast(), plssvm::max_iter = args["max_iter"].cast()); + } else if (args.contains("epsilon")) { + return self.fit(data, plssvm::epsilon = args["epsilon"].cast()); + } else if (args.contains("max_iter")) { + return self.fit(data, plssvm::max_iter = args["max_iter"].cast()); + } else { + return self.fit(data); + } + }, + "fit a model using the current SVM on the provided data") + .def( + "predict", [](const plssvm::csvm &self, const plssvm::model &model, const plssvm::data_set &data) { + if constexpr (std::is_same_v) { + return self.predict(model, data); + } else { + return vector_to_pyarray(self.predict(model, data)); + } + }, + "predict the labels for a data set using a previously learned model") + .def("score", py::overload_cast &>(&plssvm::csvm::score, py::const_), "calculate the accuracy of the model") + .def("score", py::overload_cast &, const plssvm::data_set &>(&plssvm::csvm::score, py::const_), "calculate the accuracy of a data set using the model"); +} + +template +void instantiate_csvm_functions(py::class_ &c, std::integer_sequence) { + (instantiate_csvm_functions(c, std::tuple_element_t{}), ...); +} + +template +void instantiate_model_bindings(py::class_ &c) { + instantiate_csvm_functions(c, std::make_integer_sequence>{}); +} + +std::unique_ptr assemble_csvm(const plssvm::backend_type backend, const plssvm::target_platform target, const py::kwargs &args, plssvm::parameter input_params = {}) { + // check keyword arguments + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost", "sycl_implementation_type", "sycl_kernel_invocation_type" }); + // if one of the value keyword parameter is provided, set the respective value + const plssvm::parameter params = convert_kwargs_to_parameter(args, input_params); + // parse SYCL specific keyword arguments + if (backend == plssvm::backend_type::sycl) { + // sycl specific flags + plssvm::sycl::implementation_type impl_type = plssvm::sycl::implementation_type::automatic; + if (args.contains("sycl_implementation_type")) { + impl_type = args["sycl_implementation_type"].cast(); + } + plssvm::sycl::kernel_invocation_type invocation_type = plssvm::sycl::kernel_invocation_type::automatic; + if (args.contains("sycl_kernel_invocation_type")) { + invocation_type = args["sycl_kernel_invocation_type"].cast(); + } + + return plssvm::make_csvm(backend, target, params, plssvm::sycl_implementation_type = impl_type, plssvm::sycl_kernel_invocation_type = invocation_type); + } else { + return plssvm::make_csvm(backend, target, params); + } +} + +void init_csvm(py::module_ &m) { + const py::module_ pure_virtual_model = m.def_submodule("__pure_virtual"); + + py::class_ pycsvm(pure_virtual_model, "__pure_virtual_base_CSVM"); + pycsvm.def("get_params", &plssvm::csvm::get_params, "get the parameter used for this SVM") + .def( + "set_params", [](plssvm::csvm &self, const plssvm::parameter ¶ms) { + self.set_params(params); + }, + "update the parameter used for this SVM using a plssvm.Parameter object") + .def( + "set_params", [](plssvm::csvm &self, const py::kwargs &args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // convert kwargs to parameter and update csvm internal parameter + self.set_params(convert_kwargs_to_parameter(args, self.get_params())); + }, + "update the parameter used for this SVM using keyword arguments") + .def("get_target_platform", &plssvm::csvm::get_target_platform, "get the actual target platform this SVM runs on"); + + // instantiate all functions using all available real_type x label_type combinations + instantiate_model_bindings(pycsvm); + + // bind plssvm::make_csvm factory function to a "generic" Python csvm class + py::class_(m, "CSVM", pycsvm, py::module_local()) + // IMPLICIT BACKEND + .def(py::init([]() { + return plssvm::make_csvm(); + }), + "create an SVM with the default backend, default target platform and default parameters") + .def(py::init([](const plssvm::parameter ¶ms) { + return plssvm::make_csvm(params); + }), + "create an SVM with the default backend, default target platform and provided parameter object") + .def(py::init([](const plssvm::target_platform target) { + return plssvm::make_csvm(target); + }), + "create an SVM with the default backend, provided target platform and default parameters") + .def(py::init([](const plssvm::target_platform target, const plssvm::parameter ¶ms) { + return plssvm::make_csvm(target, params); + }), + "create an SVM with the default backend, provided target platform and provided parameter object") + .def(py::init([](const plssvm::target_platform target, const plssvm::parameter ¶ms, const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(plssvm::determine_default_backend(), target, args, params); + }), + "create an SVM with the default backend, provided target platform, provided parameter object, and provided keyword arguments") + .def(py::init([](const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(plssvm::determine_default_backend(), plssvm::determine_default_target_platform(), args); + }), + "create an SVM with the default backend, default target platform and provided keyword arguments") + .def(py::init([](const plssvm::target_platform target, const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(plssvm::determine_default_backend(), target, args); + }), + "create an SVM with the default backend, provided target platform and provided keyword arguments") + // EXPLICIT BACKEND + .def(py::init([](const plssvm::backend_type backend) { + return plssvm::make_csvm(backend); + }), + "create an SVM with the provided backend, default target platform and default parameters") + .def(py::init([](const plssvm::backend_type backend, const plssvm::parameter ¶ms) { + return plssvm::make_csvm(backend, params); + }), + "create an SVM with the provided backend, default target platform and provided parameter object") + .def(py::init([](const plssvm::backend_type backend, const plssvm::parameter ¶ms, const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(backend, plssvm::determine_default_target_platform(), args, params); + }), + "create an SVM with the provided backend, default target platform, provided parameter object, and provided keyword arguments") + .def(py::init([](const plssvm::backend_type backend, const plssvm::target_platform target) { + return plssvm::make_csvm(backend, target); + }), + "create an SVM with the provided backend, provided target platform and default parameters") + .def(py::init([](const plssvm::backend_type backend, const plssvm::target_platform target, const plssvm::parameter ¶ms) { + return plssvm::make_csvm(backend, target, params); + }), + "create an SVM with the provided backend, provided target platform and provided parameter object") + .def(py::init([](const plssvm::backend_type backend, const plssvm::target_platform target, const plssvm::parameter ¶ms, const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(backend, target, args, params); + }), + "create an SVM with the provided backend, provided target platform, provided parameter object, and provided keyword arguments") + .def(py::init([](const plssvm::backend_type backend, const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(backend, plssvm::determine_default_target_platform(), args); + }), + "create an SVM with the provided backend, default target platform and provided keyword arguments") + .def(py::init([](const plssvm::backend_type backend, const plssvm::target_platform target, const py::kwargs &args) { + // assemble CSVM + return assemble_csvm(backend, target, args); + }), + "create an SVM with the provided backend, provided target platform and provided keyword arguments"); +} \ No newline at end of file diff --git a/bindings/Python/data_set.cpp b/bindings/Python/data_set.cpp new file mode 100644 index 000000000..ed2ce523c --- /dev/null +++ b/bindings/Python/data_set.cpp @@ -0,0 +1,260 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/data_set.hpp" +#include "plssvm/detail/type_list.hpp" // plssvm::detail::real_type_label_type_combination_list + +#include "utility.hpp" // check_kwargs_for_correctness, assemble_unique_class_name, + // pyarray_to_vector, pyarray_to_string_vector, pylist_to_string_vector, pyarray_to_matrix + +#include "fmt/core.h" // fmt::format +#include "fmt/format.h" // fmt::join +#include "pybind11/numpy.h" // py::array_t +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init, py::return_value_policy, py::arg, py::kwargs, py::value_error, py::pos_only, py::list +#include "pybind11/stl.h" // support for STL types + +#include // std::array +#include // std::size_t +#include // std::string +#include // std::tuple_element_t, std::tuple_size_v +#include // std::is_same_v +#include // std::move, std::integer_sequence, std::make_integer_sequence + +namespace py = pybind11; + +template +typename data_set_type::scaling create_scaling_object(const py::kwargs &args) { + using real_type = typename data_set_type::real_type; + + if (args.contains("scaling")) { + typename data_set_type::scaling scaling{ real_type{ -1.0 }, real_type{ 1.0 } }; + + // try to directly convert it to a plssvm::data_set_type::scaling object + try { + scaling = args["scaling"].cast(); + } catch (const py::cast_error &e) { + // can't cast to plssvm::data_set_type::scaling + // -> try an std::array instead! + try { + const auto interval = args["scaling"].cast>(); + scaling = typename data_set_type::scaling{ interval[0], interval[1] }; + } catch (...) { + // rethrow exception if this also did not succeed + throw; + } + } + return scaling; + } else { + throw py::attribute_error{ "Can't extract scaling information, no scaling keyword argument given!" }; + } +} + +template +void instantiate_data_set_bindings(py::module_ &m, plssvm::detail::real_type_label_type_combination) { + using data_set_type = plssvm::data_set; + using size_type = typename data_set_type::size_type; + + // create the Python type names based on the provided real_type and label_type + const std::string class_name_scaling_factors = assemble_unique_class_name("DataSetScalingFactors"); + const std::string class_name_scaling = assemble_unique_class_name("DataSetScaling"); + const std::string class_name = assemble_unique_class_name("DataSet"); + + // bind the plssvm::data_set::scaling internal "factors" struct + py::class_(m, class_name_scaling_factors.c_str()) + .def(py::init(), "create a new scaling factor", py::arg("feature"), py::arg("lower"), py::arg("upper")) + .def_readonly("feature", &data_set_type::scaling::factors::feature, "the feature index for which the factors are valid") + .def_readonly("lower", &data_set_type::scaling::factors::lower, "the lower scaling factor") + .def_readonly("upper", &data_set_type::scaling::factors::upper, "the upper scaling factor") + .def("__repr__", [class_name_scaling_factors](const typename data_set_type::scaling::factors &self) { + return fmt::format("", + class_name_scaling_factors, + self.feature, + self.lower, + self.upper); + }); + + // bind the plssvm::data_set internal "scaling" struct + py::class_(m, class_name_scaling.c_str()) + .def(py::init(), "create new scaling factors for the range [lower, upper]", py::arg("lower"), py::arg("upper")) + .def(py::init([](const std::array interval) { + return typename data_set_type::scaling{ interval[0], interval[1] }; + }), + "create new scaling factors for the range [lower, upper]") + .def(py::init(), "read the scaling factors from the file") + .def("save", &data_set_type::scaling::save, "save the scaling factors to a file") + .def_readonly("scaling_interval", &data_set_type::scaling::scaling_interval, "the interval to which the data points are scaled") + .def_property_readonly( + "scaling_factors", [](const typename data_set_type::scaling &scaling) { + return vector_to_pyarray(scaling.scaling_factors); + }, + "the scaling factors for each feature") + .def("__repr__", [class_name_scaling](const typename data_set_type::scaling &self) { + return fmt::format("", + class_name_scaling, + self.scaling_interval.first, + self.scaling_interval.second, + self.scaling_factors.size()); + }); + + // bind the data set class + py::class_ py_data_set(m, class_name.c_str()); + // bind constructor taking a data set file + py_data_set.def(py::init([](const std::string &file_name, py::kwargs args) { + // check for valid keys + check_kwargs_for_correctness(args, { "file_format", "scaling" }); + + // call the constructor corresponding to the provided keyword arguments + if (args.contains("file_format") && args.contains("scaling")) { + return data_set_type{ file_name, args["file_format"].cast(), create_scaling_object(args) }; + } else if (args.contains("file_format")) { + return data_set_type{ file_name, args["file_format"].cast() }; + } else if (args.contains("scaling")) { + return data_set_type{ file_name, create_scaling_object(args) }; + } else { + return data_set_type{ file_name }; + } + }), + "create a new data set from the provided file and additional optional parameters"); + // bind constructor taking only data points without labels + py_data_set.def(py::init([](py::array_t data, py::kwargs args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "scaling" }); + + if (args.contains("scaling")) { + return data_set_type{ pyarray_to_matrix(data), create_scaling_object(args) }; + } else { + return data_set_type{ pyarray_to_matrix(data) }; + } + }), + "create a new data set without labels given additional optional parameters"); + + if constexpr (!std::is_same_v) { + py_data_set.def(py::init([](py::array_t data, py::array_t labels, py::kwargs args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "scaling" }); + + if (args.contains("scaling")) { + return data_set_type{ pyarray_to_matrix(data), pyarray_to_vector(labels), create_scaling_object(args) }; + } else { + return data_set_type{ pyarray_to_matrix(data), pyarray_to_vector(labels) }; + } + }), + "create a new data set with labels from a numpy array given additional optional parameters"); + } else { + // if the requested label_type is std::string, accept numpy arrays with real_type and convert them to a std::string internally + py_data_set.def(py::init([](py::array_t data, py::array_t labels, py::kwargs args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "scaling" }); + + if (args.contains("scaling")) { + return data_set_type{ pyarray_to_matrix(data), pyarray_to_string_vector(labels), create_scaling_object(args) }; + } else { + return data_set_type{ pyarray_to_matrix(data), pyarray_to_string_vector(labels) }; + } + }), + "create a new data set with labels from a numpy array given additional optional parameters"); + // if the requested label_type is std::string, accept a python list (which can contain py::str) and convert them to a std::string internally + py_data_set.def(py::init([](py::array_t data, const py::list &labels, py::kwargs args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "scaling" }); + + if (args.contains("scaling")) { + return data_set_type{ pyarray_to_matrix(data), pylist_to_string_vector(labels), create_scaling_object(args) }; + } else { + return data_set_type{ pyarray_to_matrix(data), pylist_to_string_vector(labels) }; + } + }), + "create a new data set with labels from a Python list given additional optional parameters"); + } + + py_data_set.def("save", py::overload_cast(&data_set_type::save, py::const_), "save the data set to a file using the provided file format type") + .def("save", py::overload_cast(&data_set_type::save, py::const_), "save the data set to a file automatically deriving the file format type from the file extension") + .def("num_data_points", &data_set_type::num_data_points, "the number of data points in the data set") + .def("num_features", &data_set_type::num_features, "the number of features per data point") + .def("data", &data_set_type::data, py::return_value_policy::reference_internal, "the data saved as 2D vector") + .def( + "data", [](const data_set_type &data) { + return matrix_to_pyarray(data.data()); + }, + "the data saved as 2D vector") + .def("has_labels", &data_set_type::has_labels, "check whether the data set has labels") + .def( + "labels", [](const data_set_type &self) { + if (!self.has_labels()) { + throw py::attribute_error{ "'DataSet' object has no function 'labels'. Maybe this DataSet was created without labels?" }; + } else { + if constexpr (std::is_same_v) { + return self.labels()->get(); + } else { + return vector_to_pyarray(self.labels()->get()); + } + } + }, + "the labels") + .def("num_different_labels", &data_set_type::num_different_labels, "the number of different labels") + .def( + "different_labels", [](const data_set_type &self) { + if (!self.has_labels()) { + throw py::attribute_error{ "'DataSet' object has no function 'different_labels'. Maybe this DataSet was created without labels?" }; + } else { + if constexpr (std::is_same_v) { + return self.different_labels().value(); + } else { + return vector_to_pyarray(self.different_labels().value()); + } + } + }, + "the different labels") + .def("is_scaled", &data_set_type::is_scaled, "check whether the original data has been scaled to [lower, upper] bounds") + .def( + "scaling_factors", [](const data_set_type &self) { + if (!self.is_scaled()) { + throw py::attribute_error{ "'DataSet' object has no function 'scaling_factors'. Maybe this DataSet has not been scaled?" }; + } else { + return self.scaling_factors().value(); + } + }, + py::return_value_policy::reference_internal, + "the factors used to scale this data set") + .def("__repr__", [class_name](const data_set_type &self) { + std::string optional_repr{}; + if (self.has_labels()) { + optional_repr += fmt::format(", labels: [{}]", fmt::join(self.different_labels().value(), ", ")); + } + if (self.is_scaled()) { + optional_repr += fmt::format(", scaling: [{}, {}]", + self.scaling_factors()->get().scaling_interval.first, + self.scaling_factors()->get().scaling_interval.second); + } + return fmt::format("", + class_name, + self.num_data_points(), + self.num_features(), + optional_repr); + }); +} + +template +void instantiate_data_set_bindings(py::module_ &m, std::integer_sequence) { + (instantiate_data_set_bindings(m, std::tuple_element_t{}), ...); +} + +template +void instantiate_data_set_bindings(py::module_ &m) { + instantiate_data_set_bindings(m, std::make_integer_sequence>{}); +} + +void init_data_set(py::module_ &m) { + // bind all data_set classes + instantiate_data_set_bindings(m); + + // create aliases + m.attr("DataSetScalingFactors") = m.attr(assemble_unique_class_name("DataSetScalingFactors").c_str()); + m.attr("DataSetScaling") = m.attr(assemble_unique_class_name("DataSetScaling").c_str()); + m.attr("DataSet") = m.attr(assemble_unique_class_name("DataSet").c_str()); +} \ No newline at end of file diff --git a/bindings/Python/detail/logger.cpp b/bindings/Python/detail/logger.cpp new file mode 100644 index 000000000..bb043080c --- /dev/null +++ b/bindings/Python/detail/logger.cpp @@ -0,0 +1,37 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/detail/logger.hpp" + +#include "pybind11/operators.h" // pybind operator overloading +#include "pybind11/pybind11.h" // py::module_ + +namespace py = pybind11; + +void init_logger(py::module_ &m) { + // bind enum class + py::enum_ verb_enum(m, "VerbosityLevel"); + verb_enum.value("QUIET", plssvm::verbosity_level::quiet, "nothing is logged to the standard output to stdout") + .value("LIBSVM", plssvm::verbosity_level::libsvm, "log the same messages as LIBSVM (used for better LIBSVM conformity) to stdout") + .value("TIMING", plssvm::verbosity_level::timing, "log all messages related to timing information to stdout") + .value("FULL", plssvm::verbosity_level::full, "log all messages to stdout"); + + // bind the bitwise operations + verb_enum.def(py::self | py::self) + .def(py::self |= py::self) + .def(py::self & py::self) + .def(py::self &= py::self); + + // enable or disable verbose output + m.def( + "quiet", []() { plssvm::verbosity = plssvm::verbosity_level::quiet; }, "no command line output is made during calls to PLSSVM functions"); + m.def( + "get_verbosity", []() { return plssvm::verbosity; }, "get the currently set verbosity level for all PLSSVM outputs to stdout"); + m.def( + "set_verbosity", [](const plssvm::verbosity_level verb) { plssvm::verbosity = verb; }, "set the verbosity level for all PLSSVM outputs to stdout"); +} \ No newline at end of file diff --git a/bindings/Python/detail/performance_tracker.cpp b/bindings/Python/detail/performance_tracker.cpp new file mode 100644 index 000000000..601511a1f --- /dev/null +++ b/bindings/Python/detail/performance_tracker.cpp @@ -0,0 +1,38 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/detail/performance_tracker.hpp" + +#include "pybind11/pybind11.h" // py::module_ + +namespace py = pybind11; + +void init_performance_tracker([[maybe_unused]] py::module_ &m) { +#if defined(PLSSVM_PERFORMANCE_TRACKER_ENABLED) + + // use a detail.PerformanceTracker submodule for the performance tracking bindings + py::module_ detail_module = m.def_submodule("detail", "a module containing detail functionality for the performance tracker"); + py::module_ performance_tracker_module = detail_module.def_submodule("PerformanceTracker", "whatever"); + + // bind the performance tracker functions + performance_tracker_module + .def("add_string_tracking_entry", [](const std::string &category, const std::string &name, const std::string &value) { + plssvm::detail::global_tracker->add_tracking_entry(plssvm::detail::tracking_entry{ category, name, value }); + }) + .def("add_parameter_tracking_entry", [](const plssvm::parameter ¶ms) { + plssvm::detail::global_tracker->add_tracking_entry(plssvm::detail::tracking_entry{ "parameter", "", params }); + }) + .def( + "pause", []() { plssvm::detail::global_tracker->pause_tracking(); }, "pause performance tracking") + .def( + "resume", []() { plssvm::detail::global_tracker->resume_tracking(); }, "resume performance tracking") + .def( + "save", [](const std::string &filename) { plssvm::detail::global_tracker->save(filename); }, "save the performance tracking results to the specified yaml file"); + +#endif +} \ No newline at end of file diff --git a/bindings/Python/exceptions/exceptions.cpp b/bindings/Python/exceptions/exceptions.cpp new file mode 100644 index 000000000..a2a56e57f --- /dev/null +++ b/bindings/Python/exceptions/exceptions.cpp @@ -0,0 +1,27 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/exceptions/exceptions.hpp" + +#include "utility.hpp" // register_py_exception + +#include "pybind11/pybind11.h" // py::module_, py::exception + +namespace py = pybind11; + +void init_exceptions(py::module_ &m, const py::exception &base_exception) { + // register all basic PLSSVM exceptions + register_py_exception(m, "InvalidParameterError", base_exception); + register_py_exception(m, "FileReaderError", base_exception); + register_py_exception(m, "DataSetError", base_exception); + register_py_exception(m, "FileNotFoundError", base_exception); + register_py_exception(m, "InvalidFileFormatError", base_exception); + register_py_exception(m, "UnsupportedBackendError", base_exception); + register_py_exception(m, "UnsupportedKernelTypeError", base_exception); + register_py_exception(m, "GPUDevicePtrError", base_exception); +} \ No newline at end of file diff --git a/bindings/Python/file_format_types.cpp b/bindings/Python/file_format_types.cpp new file mode 100644 index 000000000..2f309f545 --- /dev/null +++ b/bindings/Python/file_format_types.cpp @@ -0,0 +1,20 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/file_format_types.hpp" + +#include "pybind11/pybind11.h" // py::module_, py::enum_ + +namespace py = pybind11; + +void init_file_format_types(py::module_ &m) { + // bind enum class + py::enum_(m, "FileFormatType") + .value("LIBSVM", plssvm::file_format_type::libsvm, "the LIBSVM file format (default); for the file format specification see: https://www.csie.ntu.edu.tw/~cjlin/libsvm/faq.html") + .value("ARFF", plssvm::file_format_type::arff, "the ARFF file format; for the file format specification see: https://www.cs.waikato.ac.nz/~ml/weka/arff.html"); +} \ No newline at end of file diff --git a/bindings/Python/kernel_function_types.cpp b/bindings/Python/kernel_function_types.cpp new file mode 100644 index 000000000..01e5e101f --- /dev/null +++ b/bindings/Python/kernel_function_types.cpp @@ -0,0 +1,63 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/kernel_function_types.hpp" +#include "plssvm/parameter.hpp" // plssvm::parameter + +#include "pybind11/pybind11.h" // py::module_, py::enum_, py::arg, py::pos_only +#include "pybind11/stl.h" // support for STL types: std::vector, std::optional + +#include // std::optional, std::nullopt +#include // std::vector + +namespace py = pybind11; + +void init_kernel_function_types(py::module_ &m) { + // bind enum class + py::enum_(m, "KernelFunctionType") + .value("LINEAR", plssvm::kernel_function_type::linear, "linear kernel function: ") + .value("POLYNOMIAL", plssvm::kernel_function_type::polynomial, "polynomial kernel function: (gamma * + coef0)^degree") + .value("RBF", plssvm::kernel_function_type::rbf, "radial basis function: e^(-gamma * ||u - v||^2)"); + + // bind free functions + m.def("kernel_function_type_to_math_string", &plssvm::kernel_function_type_to_math_string, "return the mathematical representation of a KernelFunctionType"); + + const plssvm::parameter default_params{}; + + m.def("linear_kernel_function", &plssvm::kernel_function, "apply the linear kernel function to two vectors"); + m.def( + "polynomial_kernel_function", [](const std::vector &x, const std::vector &y, const int degree, const std::optional gamma, const double coef0) { + return plssvm::kernel_function(x, y, degree, gamma.has_value() ? gamma.value() : 1.0 / static_cast(x.size()), coef0); + }, + "apply the polynomial kernel function to two vectors", + py::arg("x"), + py::arg("y"), + py::pos_only(), + py::arg("degree") = default_params.degree.value(), + py::arg("gamma") = std::nullopt, + py::arg("coef0") = default_params.coef0.value()); + m.def( + "rbf_kernel_function", [](const std::vector &x, const std::vector &y, const std::optional gamma) { + return plssvm::kernel_function(x, y, gamma.has_value() ? gamma.value() : 1.0 / static_cast(x.size())); + }, + "apply the radial basis function kernel function to two vectors", + py::arg("x"), + py::arg("y"), + py::pos_only(), + py::arg("gamma") = std::nullopt); + + m.def( + "kernel_function", [](const std::vector &x, const std::vector &y, plssvm::parameter params) { + // set default gamma value + if (params.gamma.is_default()) { + params.gamma = 1.0 / static_cast(x.size()); + } + return plssvm::kernel_function(x, y, params); + }, + "apply the kernel function defined in the parameter object to two vectors"); +} diff --git a/bindings/Python/main.cpp b/bindings/Python/main.cpp new file mode 100644 index 000000000..72ff7ef97 --- /dev/null +++ b/bindings/Python/main.cpp @@ -0,0 +1,84 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/exceptions/exceptions.hpp" // plssvm::exception + +#include "pybind11/pybind11.h" // PYBIND11_MODULE, py::module_, py::exception, py::register_exception_translator + +#include // std::exception_ptr, std::rethrow_exception + +namespace py = pybind11; + +// forward declare binding functions +void init_logger(py::module_ &); +void init_performance_tracker(py::module_ &); +void init_target_platforms(py::module_ &); +void init_backend_types(py::module_ &); +void init_file_format_types(py::module_ &); +void init_kernel_function_types(py::module_ &); +void init_parameter(py::module_ &); +void init_model(py::module_ &); +void init_data_set(py::module_ &); +void init_version(py::module_ &); +void init_exceptions(py::module_ &, const py::exception &); +void init_csvm(py::module_ &); +void init_openmp_csvm(py::module_ &, const py::exception &); +void init_cuda_csvm(py::module_ &, const py::exception &); +void init_hip_csvm(py::module_ &, const py::exception &); +void init_opencl_csvm(py::module_ &, const py::exception &); +void init_sycl(py::module_ &, const py::exception &); +void init_sklearn(py::module_ &); + +PYBIND11_MODULE(plssvm, m) { + m.doc() = "Parallel Least Squares Support Vector Machine"; + + // register PLSSVM base exception + static py::exception base_exception(m, "PLSSVMError"); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) { + std::rethrow_exception(p); + } + } catch (const plssvm::exception &e) { + base_exception(e.what_with_loc().c_str()); + } + }); + + // NOTE: the order matters. DON'T CHANGE IT! + init_logger(m); + init_performance_tracker(m); + init_target_platforms(m); + init_backend_types(m); + init_file_format_types(m); + init_kernel_function_types(m); + init_parameter(m); + init_model(m); + init_data_set(m); + init_version(m); + init_exceptions(m, base_exception); + init_csvm(m); + + // init bindings for the specific backends ONLY if the backend has been enabled +#if defined(PLSSVM_HAS_OPENMP_BACKEND) + init_openmp_csvm(m, base_exception); +#endif +#if defined(PLSSVM_HAS_CUDA_BACKEND) + init_cuda_csvm(m, base_exception); +#endif +#if defined(PLSSVM_HAS_HIP_BACKEND) + init_hip_csvm(m, base_exception); +#endif +#if defined(PLSSVM_HAS_OPENCL_BACKEND) + init_opencl_csvm(m, base_exception); +#endif +#if defined(PLSSVM_HAS_SYCL_BACKEND) + init_sycl(m, base_exception); +#endif + + init_sklearn(m); +} \ No newline at end of file diff --git a/bindings/Python/model.cpp b/bindings/Python/model.cpp new file mode 100644 index 000000000..5ffda7c69 --- /dev/null +++ b/bindings/Python/model.cpp @@ -0,0 +1,93 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/model.hpp" +#include "plssvm/detail/type_list.hpp" // plssvm::detail::real_type_label_type_combination_list + +#include "utility.hpp" // assemble_unique_class_name, vector_to_pyarray, matrix_to_pyarray + +#include "fmt/core.h" // fmt::format +#include "pybind11/pybind11.h" // py::module_, py::class_, py::return_value_policy +#include "pybind11/stl.h" // support for STL types: std::vector + +#include // std::size_t +#include // std::string +#include // std::tuple_element_t, std::tuple_size_v +#include // std::is_same_v +#include // std::integer_sequence, std::make_integer_sequence + +namespace py = pybind11; + +template +void instantiate_model_bindings(py::module_ &m, plssvm::detail::real_type_label_type_combination) { + using model_type = plssvm::model; + + const std::string class_name = assemble_unique_class_name("Model"); + + py::class_(m, class_name.c_str()) + .def(py::init(), "load a previously learned model from a file") + .def("save", &model_type::save, "save the current model to a file") + .def("num_support_vectors", &model_type::num_support_vectors, "the number of support vectors (note: all training points become support vectors for LSSVMs)") + .def("num_features", &model_type::num_features, "the number of features of the support vectors") + .def("get_params", &model_type::get_params, py::return_value_policy::reference_internal, "the SVM parameter used to learn this model") + .def( + "support_vectors", [](const model_type &self) { + return matrix_to_pyarray(self.support_vectors()); + }, + "the support vectors (note: all training points become support vectors for LSSVMs)") + .def( + "labels", [](const model_type &self) { + if constexpr (std::is_same_v) { + return self.labels(); + } else { + return vector_to_pyarray(self.labels()); + } + }, + "the labels") + .def("num_different_labels", &model_type::num_different_labels, "the number of different labels") + .def( + "different_labels", [](const model_type &self) { + if constexpr (std::is_same_v) { + return self.different_labels(); + } else { + return vector_to_pyarray(self.different_labels()); + } + }, + "the different labels") + .def( + "weights", [](const model_type &self) { + return vector_to_pyarray(self.weights()); + }, + "the weights learned for each support vector") + .def("rho", &model_type::rho, "the bias value after learning") + .def("__repr__", [class_name](const model_type &self) { + return fmt::format("", + class_name, + self.num_support_vectors(), + self.num_features(), + self.rho()); + }); +} + +template +void instantiate_model_bindings(py::module_ &m, std::integer_sequence) { + (instantiate_model_bindings(m, std::tuple_element_t{}), ...); +} + +template +void instantiate_model_bindings(py::module_ &m) { + instantiate_model_bindings(m, std::make_integer_sequence>{}); +} + +void init_model(py::module_ &m) { + // bind all model classes + instantiate_model_bindings(m); + + // create alias + m.attr("Model") = m.attr(assemble_unique_class_name("Model").c_str()); +} \ No newline at end of file diff --git a/bindings/Python/parameter.cpp b/bindings/Python/parameter.cpp new file mode 100644 index 000000000..c61217862 --- /dev/null +++ b/bindings/Python/parameter.cpp @@ -0,0 +1,76 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/parameter.hpp" + +#include "utility.hpp" + +#include "fmt/core.h" // fmt::format +#include "pybind11/operators.h" // support for operators +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init, py::arg, py::return_value_policy, py::self +#include "pybind11/stl.h" // support for STL types + +namespace py = pybind11; + +void init_parameter(py::module_ &m) { + // bind parameter class + py::class_(m, "Parameter") + .def(py::init<>()) + .def(py::init()) + .def(py::init([](const py::kwargs &args) { + // check for valid keys + check_kwargs_for_correctness(args, { "kernel_type", "degree", "gamma", "coef0", "cost" }); + // if one of the value named parameter is provided, set the respective value + return convert_kwargs_to_parameter(args); + }), + "create a new SVM parameter object") + .def_property( + "kernel_type", + [](const plssvm::parameter &self) { return self.kernel_type.value(); }, + [](plssvm::parameter &self, const plssvm::kernel_function_type kernel_type) { self.kernel_type = kernel_type; }, + py::return_value_policy::reference, + "change the used kernel function: linear, polynomial, and rbf") + .def_property( + "degree", + [](const plssvm::parameter &self) { return self.degree.value(); }, + [](plssvm::parameter &self, const int degree) { self.degree = degree; }, + py::return_value_policy::reference, + "change the degree parameter for the polynomial kernel function") + .def_property( + "gamma", + [](const plssvm::parameter &self) { return self.gamma.value(); }, + [](plssvm::parameter &self, const double gamma) { self.gamma = gamma; }, + py::return_value_policy::reference, + "change the gamma parameter for the polynomial and rbf kernel functions") + .def_property( + "coef0", + [](const plssvm::parameter &self) { return self.coef0.value(); }, + [](plssvm::parameter &self, const double coef0) { self.coef0 = coef0; }, + py::return_value_policy::reference, + "change the coef0 parameter for the polynomial kernel function") + .def_property( + "cost", + [](const plssvm::parameter &self) { return self.cost.value(); }, + [](plssvm::parameter &self, const double cost) { self.cost = cost; }, + py::return_value_policy::reference, + "change the cost parameter for the CSVM") + .def("equivalent", &plssvm::parameter::equivalent, "check whether two parameter objects are equivalent, i.e., the SVM parameter important for the current 'kernel_type' are the same") + .def(py::self == py::self, "check whether two parameter objects are identical") + .def(py::self != py::self, "check whether two parameter objects are different") + .def("__repr__", [](const plssvm::parameter &self) { + return fmt::format("", + self.kernel_type, + self.degree, + self.gamma, + self.coef0, + self.cost); + }); + + // bind free functions + m.def("equivalent", &plssvm::equivalent, "check whether two parameter objects are equivalent, i.e., the SVM parameter important for the current 'kernel_type' are the same"); +} \ No newline at end of file diff --git a/bindings/Python/sklearn.cpp b/bindings/Python/sklearn.cpp new file mode 100644 index 000000000..42e8449c3 --- /dev/null +++ b/bindings/Python/sklearn.cpp @@ -0,0 +1,427 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/core.hpp" + +#include "utility.hpp" // check_kwargs_for_correctness, assemble_unique_class_name, pyarray_to_vector, pyarray_to_matrix + +#include "fmt/core.h" // fmt::format +#include "pybind11/numpy.h" // support for STL types +#include "pybind11/operators.h" // support for operators +#include "pybind11/pybind11.h" // py::module_, py::class_, py::init, py::arg, py::return_value_policy, py::self +#include "pybind11/stl.h" // support for STL types + +#include // std::size_t +#include // std::int32_t +#include // std::map +#include // std::unique_ptr, std::make_unique +#include // std::iota +#include // std::optional, std::nullopt +#include // std::stringstream +#include // std::string +#include // std::tuple +#include // std::vector + +namespace py = pybind11; + +// TODO: implement missing functionality + +// dummy +struct svc { + // the types + using real_type = PLSSVM_PYTHON_BINDINGS_PREFERRED_REAL_TYPE; + using label_type = PLSSVM_PYTHON_BINDINGS_PREFERRED_LABEL_TYPE; + using data_set_type = plssvm::data_set; + using model_type = plssvm::model; + + std::optional epsilon{}; + std::optional max_iter{}; + + std::unique_ptr svm_{ plssvm::make_csvm() }; + std::unique_ptr data_{}; + std::unique_ptr model_{}; +}; + +void parse_provided_params(svc &self, const py::kwargs &args) { + // check keyword arguments + check_kwargs_for_correctness(args, { "C", "kernel", "degree", "gamma", "coef0", "shrinking", "probability", "tol", "cache_size", "class_weight", "verbose", "max_iter", "decision_function_shape", "break_ties", "random_state" }); + + if (args.contains("C")) { + self.svm_->set_params(plssvm::cost = args["C"].cast()); + } + if (args.contains("kernel")) { + std::stringstream ss{ args["kernel"].cast() }; + plssvm::kernel_function_type kernel{}; + ss >> kernel; + self.svm_->set_params(plssvm::kernel_type = kernel); + } + if (args.contains("degree")) { + self.svm_->set_params(plssvm::degree = args["degree"].cast()); + } + if (args.contains("gamma")) { + // TODO: correctly reflect sklearn's scale, auto, and value options + self.svm_->set_params(plssvm::gamma = args["gamma"].cast()); + } + if (args.contains("coef0")) { + self.svm_->set_params(plssvm::coef0 = args["coef0"].cast()); + } + if (args.contains("shrinking")) { + throw py::attribute_error{ "The 'shrinking' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } + if (args.contains("probability")) { + throw py::attribute_error{ "The 'probability' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } + if (args.contains("tol")) { + self.epsilon = args["tol"].cast(); + } + if (args.contains("cache_size")) { + throw py::attribute_error{ "The 'cache_size' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } + if (args.contains("class_weight")) { + throw py::attribute_error{ "The 'class_weight' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } + if (args.contains("verbose")) { + if (args["verbose"].cast()) { + plssvm::verbosity = plssvm::verbosity_level::full; + } else { + plssvm::verbosity = plssvm::verbosity_level::quiet; + } + } + if (args.contains("max_iter")) { + self.max_iter = args["max_iter"].cast(); + } + if (args.contains("decision_function_shape")) { + throw py::attribute_error{ "The 'decision_function_shape' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } + if (args.contains("break_ties")) { + throw py::attribute_error{ "The 'break_ties' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } + if (args.contains("random_state")) { + throw py::attribute_error{ "The 'random_state' parameter for a call to the 'SVC' constructor is not implemented yet!" }; + } +} + +void fit(svc &self) { + // fit the model using potentially provided keyword arguments + if (self.epsilon.has_value() && self.max_iter.has_value()) { + self.model_ = std::make_unique(self.svm_->fit(*self.data_, plssvm::epsilon = self.epsilon.value(), plssvm::max_iter = self.max_iter.value())); + } else if (self.epsilon.has_value()) { + self.model_ = std::make_unique(self.svm_->fit(*self.data_, plssvm::epsilon = self.epsilon.value())); + } else if (self.max_iter.has_value()) { + self.model_ = std::make_unique(self.svm_->fit(*self.data_, plssvm::max_iter = self.max_iter.value())); + } else { + self.model_ = std::make_unique(self.svm_->fit(*self.data_)); + } +} + +void init_sklearn(py::module_ &m) { + // documentation based on sklearn.svm.SVC documentation + py::class_ py_svc(m, "SVC"); + py_svc.def(py::init([](const py::kwargs &args) { + // to silence constructor messages + if (args.contains("verbose")) { + if (args["verbose"].cast()) { + plssvm::verbosity = plssvm::verbosity_level::full; + } else { + plssvm::verbosity = plssvm::verbosity_level::quiet; + } + } + + // create SVC class + auto self = std::make_unique(); + parse_provided_params(*self, args); + return self; + }), + "Construct a new SVM classifier.") + + // FUNCTIONS + .def("decision_function", [](const svc &, py::array_t) { + throw py::attribute_error{ "'SVC' object has no function 'decision_function' (not implemented)" }; + }); +#if !defined(PLSSVM_PYTHON_BINDINGS_LABEL_TYPE_IS_STRING) + py_svc.def( + "fit", [](svc &self, py::array_t data, py::array_t labels, std::optional> sample_weight) { + if (sample_weight.has_value()) { + throw py::attribute_error{ "The 'sample_weight' parameter for a call to 'fit' is not implemented yet!" }; + } + + // fit the model using potentially provided keyword arguments + self.data_ = std::make_unique(pyarray_to_matrix(data), pyarray_to_vector(labels)); + fit(self); + }, + "Fit the SVM model according to the given training data.", + py::arg("X"), + py::arg("y"), + py::pos_only(), + py::arg("sample_weight") = std::nullopt); +#else + py_svc.def( + "fit", [](svc &self, py::array_t data, py::array_t labels, const std::optional> &sample_weight) { + if (sample_weight.has_value()) { + throw py::attribute_error{ "The 'sample_weight' parameter for a call to 'fit' is not implemented yet!" }; + } + + // fit the model using potentially provided keyword arguments + self.data_ = std::make_unique(pyarray_to_matrix(data), pyarray_to_string_vector(labels)); + fit(self); + }, + "Fit the SVM model according to the given training data.", + py::arg("X"), + py::arg("y"), + py::pos_only(), + py::arg("sample_weight") = std::nullopt) + .def( + "fit", [](svc &self, py::array_t data, const py::list &labels, const std::optional> &sample_weight) { + if (sample_weight.has_value()) { + throw py::attribute_error{ "The 'sample_weight' parameter for a call to 'fit' is not implemented yet!" }; + } + + // fit the model using potentially provided keyword arguments + self.data_ = std::make_unique(pyarray_to_matrix(data), pylist_to_string_vector(labels)); + fit(self); + }, + "Fit the SVM model according to the given training data.", + py::arg("X"), + py::arg("y"), + py::pos_only(), + py::arg("sample_weight") = std::nullopt); +#endif + py_svc.def( + "get_params", [](const svc &self) { + const plssvm::parameter params = self.svm_->get_params(); + + // fill a Python dictionary with the supported keys and values + py::dict py_params; + py_params["C"] = params.cost.value(); + py_params["kernel"] = fmt::format("{}", params.kernel_type); + py_params["degree"] = params.degree.value(); + // TODO: correctly reflect sklearn's scale, auto, and value options + py_params["gamma"] = params.gamma.value(); + py_params["coef0"] = params.coef0.value(); + // py_params["shrinking"]; + // py_params["probability"]; + py_params["tol"] = self.epsilon.value_or(typename svc::real_type{ 1e-3 }); + // py_params["cache_size"]; + // py_params["class_weight"]; + py_params["verbose"] = plssvm::verbosity != plssvm::verbosity_level::quiet; + py_params["max_iter"] = self.max_iter.value_or(-1); + // py_params["decision_function_shape"]; + // py_params["break_ties"]; + // py_params["random_state"]; + + return py_params; + }, + "Get parameters for this estimator.") + .def( + "predict", [](svc &self, py::array_t data) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "This SVC instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." }; + } else { + const typename svc::data_set_type data_to_predict{ pyarray_to_matrix(data) }; + if constexpr (std::is_same_v) { + return self.svm_->predict(*self.model_, data_to_predict); + } else { + return vector_to_pyarray(self.svm_->predict(*self.model_, data_to_predict)); + } + } + }, + "Perform classification on samples in X.") + .def("predict_log_proba", [](const svc &, py::array_t) { + throw py::attribute_error{ "'SVC' object has no function 'predict_log_proba' (not implemented)" }; + }) + .def("predict_proba", [](const svc &, py::array_t) { + throw py::attribute_error{ "'SVC' object has no function 'predict_proba' (not implemented)" }; + }); +#if !defined(PLSSVM_PYTHON_BINDINGS_LABEL_TYPE_IS_STRING) + py_svc.def( + "score", [](svc &self, py::array_t data, py::array_t labels, std::optional> sample_weight) { + if (sample_weight.has_value()) { + throw py::attribute_error{ "The 'sample_weight' parameter for a call to 'fit' is not implemented yet!" }; + } + + if (self.model_ == nullptr) { + throw py::attribute_error{ "This SVC instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." }; + } else { + const typename svc::data_set_type data_to_score{ pyarray_to_matrix(data), pyarray_to_vector(labels) }; + return self.svm_->score(*self.model_, data_to_score); + } + }, + "Return the mean accuracy on the given test data and labels.", + py::arg("X"), + py::arg("y"), + py::pos_only(), + py::arg("sample_weight") = std::nullopt); +#else + py_svc.def( + "score", [](svc &self, py::array_t data, py::array_t labels, const std::optional> &sample_weight) { + if (sample_weight.has_value()) { + throw py::attribute_error{ "The 'sample_weight' parameter for a call to 'fit' is not implemented yet!" }; + } + + if (self.model_ == nullptr) { + throw py::attribute_error{ "This SVC instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." }; + } else { + const typename svc::data_set_type data_to_score{ pyarray_to_matrix(data), pyarray_to_string_vector(labels) }; + return self.svm_->score(*self.model_, data_to_score); + } + }, + "Return the mean accuracy on the given test data and labels.", + py::arg("X"), + py::arg("y"), + py::pos_only(), + py::arg("sample_weight") = std::nullopt) + .def( + "score", [](svc &self, py::array_t data, py::list labels, const std::optional> &sample_weight) { + if (sample_weight.has_value()) { + throw py::attribute_error{ "The 'sample_weight' parameter for a call to 'fit' is not implemented yet!" }; + } + + if (self.model_ == nullptr) { + throw py::attribute_error{ "This SVC instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." }; + } else { + const typename svc::data_set_type data_to_score{ pyarray_to_matrix(data), pylist_to_string_vector(labels) }; + return self.svm_->score(*self.model_, data_to_score); + } + }, + "Return the mean accuracy on the given test data and labels.", + py::arg("X"), + py::arg("y"), + py::pos_only(), + py::arg("sample_weight") = std::nullopt); +#endif + py_svc.def( + "set_params", [](svc &self, const py::kwargs &args) { + parse_provided_params(self, args); + }, + "Set the parameters of this estimator.") + + // ATTRIBUTES + .def_property_readonly("class_weight_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'class_weight_' (not implemented)" }; + }) + .def_property_readonly( + "classes_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'classes_'" }; + } else { + if constexpr (std::is_same_v) { + return self.data_->different_labels().value(); + } else { + return vector_to_pyarray(self.data_->different_labels().value()); + } + } + }, + "The classes labels. ndarray of shape (n_classes,)") + .def_property_readonly("coef_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'coef_' (not implemented)" }; + }) + .def_property_readonly("coef_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'dual_coef_' (not implemented)" }; + }) + .def_property_readonly( + "fit_status_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'fit_status_'" }; + } else { + return 0; + } + }, + "0 if correctly fitted, 1 otherwise (will raise exception). int") + .def_property_readonly("intercept_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'intercept_' (not implemented)" }; + }) + .def_property_readonly( + "n_features_in_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'n_features_in_'" }; + } else { + return static_cast(self.data_->num_features()); + } + }, + "Number of features seen during fit. int") + .def_property_readonly("feature_names_in_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'feature_names_in_' (not implemented)" }; + }) + .def_property_readonly("n_iter_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'n_iter_' (not implemented)" }; + }) + .def_property_readonly( + "support_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'support_'" }; + } else { + // all data points are support vectors + const auto size = static_cast(self.model_->num_support_vectors()); + py::array_t py_array(size); + const py::buffer_info buffer = py_array.request(); + int *ptr = static_cast(buffer.ptr); + for (int i = 0; i < size; ++i) { + ptr[i] = i; + } + return py_array; + } + }, + "Indices of support vectors. ndarray of shape (n_SV)") + .def_property_readonly( + "support_vectors_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'support_vectors_'" }; + } else { + // all data points are support vectors + return matrix_to_pyarray(self.model_->support_vectors()); + } + }, + "Support vectors. ndarray of shape (n_SV, n_features)") + .def_property_readonly( + "n_support_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'n_support_'" }; + } else { + // all data points are support vectors + using real_type = typename svc::real_type; + using label_type = typename svc::label_type; + + // use a map to count the number of data points per label + std::unordered_map counts{}; + + // retrieve necessary values + const std::vector different_labels = self.data_->different_labels().value(); + const std::vector &labels = self.data_->labels()->get(); + const std::vector &weights = self.model_->weights(); + for (std::size_t i = 0; i < self.model_->num_support_vectors(); ++i) { + // only count if the weight is non-zero + if (weights[i] != real_type{ 0.0 }) { + ++counts[labels[i]]; + } + } + + // create vector from map + std::vector n_support(different_labels.size()); + for (std::size_t i = 0; i < n_support.size(); ++i) { + n_support[i] = counts[different_labels[i]]; + } + return vector_to_pyarray(n_support); + } + }, + "Number of support vectors for each class. ndarray of shape (n_classes,), dtype=int32") + .def_property_readonly("probA_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'probA_' (not implemented)" }; + }) + .def_property_readonly("probB_", [](const svc &) { + throw py::attribute_error{ "'SVC' object has no attribute 'probB_' (not implemented)" }; + }) + .def_property_readonly( + "shape_fit_", [](const svc &self) { + if (self.model_ == nullptr) { + throw py::attribute_error{ "'SVC' object has no attribute 'shape_fit_'" }; + } else { + return std::tuple{ static_cast(self.data_->num_data_points()), static_cast(self.data_->num_features()) }; + } + }, + "Array dimensions of training vector X. tuple of int of shape (n_dimensions_of_X,)"); +} \ No newline at end of file diff --git a/bindings/Python/target_platforms.cpp b/bindings/Python/target_platforms.cpp new file mode 100644 index 000000000..3d2ca39a5 --- /dev/null +++ b/bindings/Python/target_platforms.cpp @@ -0,0 +1,28 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/target_platforms.hpp" + +#include "pybind11/pybind11.h" // py::module_, py::enum_ +#include "pybind11/stl.h" // support for STL types: std::vector + +namespace py = pybind11; + +void init_target_platforms(py::module_ &m) { + // bind enum class + py::enum_(m, "TargetPlatform") + .value("AUTOMATIC", plssvm::target_platform::automatic, "the default target with respect to the used backend type; checks for available devices in the following order: NVIDIA GPUs -> AMD GPUs -> Intel GPUs -> CPUs") + .value("CPU", plssvm::target_platform::cpu, "target CPUs only (Intel, AMD, IBM, ...)") + .value("GPU_NVIDIA", plssvm::target_platform::gpu_nvidia, "target GPUs from NVIDIA") + .value("GPU_AMD", plssvm::target_platform::gpu_amd, "target GPUs from AMD") + .value("GPU_INTEL", plssvm::target_platform::gpu_intel, "target GPUs from Intel"); + + // bind free functions + m.def("list_available_target_platforms", &plssvm::list_available_target_platforms, "list the available target platforms (as defined during CMake configuration)"); + m.def("determine_default_target_platform", &plssvm::determine_default_target_platform, "determine the default target platform given the list of available target platforms", py::arg("platform_device_list") = plssvm::list_available_target_platforms()); +} \ No newline at end of file diff --git a/bindings/Python/utility.hpp b/bindings/Python/utility.hpp new file mode 100644 index 000000000..e4f350e62 --- /dev/null +++ b/bindings/Python/utility.hpp @@ -0,0 +1,259 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Utility functions used for creating the Pybind11 Python bindings. + */ + +#ifndef PLSSVM_BINDINGS_PYTHON_UTILITY_HPP_ +#define PLSSVM_BINDINGS_PYTHON_UTILITY_HPP_ +#pragma once + +#include "plssvm/detail/utility.hpp" // plssvm::detail::contains +#include "plssvm/parameter.hpp" // plssvm::parameter + +#include "fmt/format.h" // fmt::format +#include "pybind11/numpy.h" // py::array_t, py::buffer_info +#include "pybind11/pybind11.h" // py::kwargs, py::value_error, py::exception, py::str +#include "pybind11/stl.h" // support for STL types + +#include // std::exception_ptr, std::rethrow_exception +#include // std::string +#include // std::string_view +#include // std::vector + +namespace py = pybind11; + +/** + * @brief Convert a `std::vector` to a Python Numpy array. + * @tparam T the type in the array + * @param[in] vec the vector to convert + * @return the Python Numpy array (`[[nodiscard]]`) + */ +template +[[nodiscard]] py::array_t vector_to_pyarray(const std::vector &vec) { + py::array_t py_array(vec.size()); + py::buffer_info buffer = py_array.request(); + T *ptr = static_cast(buffer.ptr); + for (typename std::vector::size_type i = 0; i < vec.size(); ++i) { + ptr[i] = vec[i]; + } + return py_array; +} + +/** + * @brief Convert a `std::vector>` to a Python Numpy array. + * @tparam T the type in the array + * @param[in] mat the 2D vector to convert + * @return the Python Numpy array (`[[nodiscard]]`) + */ +template +[[nodiscard]] py::array_t matrix_to_pyarray(const std::vector> &mat) { + const typename std::vector>::size_type num_data_points = mat.size(); + const typename std::vector>::size_type num_features = mat.front().size(); + + py::array_t py_array({ num_data_points, num_features }); + py::buffer_info buffer = py_array.request(); + T *ptr = static_cast(buffer.ptr); + for (typename std::vector>::size_type i = 0; i < num_data_points; ++i) { + for (typename std::vector>::size_type j = 0; j < num_features; ++j) { + ptr[i * num_features + j] = mat[i][j]; + } + } + return py_array; +} + +/** + * @brief Convert a Python Numpy array to a `std::vector`. + * @tparam T the type in the array + * @param[in] vec the Python Numpy array to convert + * @return the `std::vector` (`[[nodiscard]]`) + */ +template +[[nodiscard]] std::vector pyarray_to_vector(const py::array_t &vec) { + // check dimensions + if (vec.ndim() != 1) { + throw py::value_error{ fmt::format("the provided array must have exactly one dimension but has {}!", vec.ndim()) }; + } + + // convert py::array to std::vector + return std::vector(vec.data(0), vec.data(0) + vec.shape(0)); +} + +/** + * @brief Convert a Python Numpy array to a `std::vector`. + * @tparam T the type in the array + * @param[in] vec the Python Numpy array to convert + * @return the `std::vector` (`[[nodiscard]]`) + */ +template +[[nodiscard]] std::vector pyarray_to_string_vector(const py::array_t &vec) { + // check dimensions + if (vec.ndim() != 1) { + throw py::value_error{ fmt::format("the provided array must have exactly one dimension but has {}!", vec.ndim()) }; + } + + // convert labels to strings + std::vector tmp(vec.shape(0)); + for (std::vector::size_type i = 0; i < tmp.size(); ++i) { + tmp[i] = fmt::format("{}", *vec.data(i)); + } + + return tmp; +} + +/** + * @brief Convert a Python List to a `std::vector`. + * @param[in] list the Python List to convert + * @return the `std::vector` (`[[nodiscard]]`) + */ +[[nodiscard]] inline std::vector pylist_to_string_vector(const py::list &list) { + // convert a Python list containing strings to a std::vector + std::vector tmp(py::len(list)); + for (std::vector::size_type i = 0; i < tmp.size(); ++i) { + tmp[i] = list[i].cast().cast(); + } + + return tmp; +} + +/** + * @brief Convert a Python Numpy array to a `std::vector>`. + * @tparam T the type in the array + * @param[in] mat the 2D Python Numpy matrix to convert + * @return to 2D matrix of `std::vector>` (`[[nodiscard]]`) + */ +template +[[nodiscard]] std::vector> pyarray_to_matrix(const py::array_t &mat) { + // check dimensions + if (mat.ndim() != 2) { + throw py::value_error{ fmt::format("the provided matrix must have exactly two dimensions but has {}!", mat.ndim()) }; + } + + // convert py::array to std::vector> + std::vector> tmp(mat.shape(0)); + for (typename std::vector>::size_type i = 0; i < tmp.size(); ++i) { + tmp[i] = std::vector(mat.data(i, 0), mat.data(i, 0) + mat.shape(1)); + } + + return tmp; +} + +/** + * @brief Check that the Python kwargs @p args only contain keyword arguments with names present in @p valid_named_args. + * @param[in] args the Python keyword arguments + * @param[in] valid_named_args the valid keyword arguments + * @throws pybind11::value_error if an illegal keyword arguments has been provided + */ +inline void check_kwargs_for_correctness(const py::kwargs &args, const std::vector &valid_named_args) { + for (const auto &[key, value] : args) { + if (!plssvm::detail::contains(valid_named_args, key.cast())) { + throw py::value_error(fmt::format("Invalid argument \"{}={}\" provided!", key.cast(), value.cast())); + } + } +} + +/** + * @brief Convert the Python kwargs @p args to an `plssvm::parameter` object. + * @param[in] args the Python keyword arguments + * @param[in] params the baseline parameter + * @return the `plssvm::parameter` object filled with the keyword @p args (`[[nodiscard]]`) + */ +[[nodiscard]] inline plssvm::parameter convert_kwargs_to_parameter(const py::kwargs &args, plssvm::parameter params = {}) { + if (args.contains("kernel_type")) { + params.kernel_type = args["kernel_type"].cast(); + } + if (args.contains("degree")) { + params.degree = args["degree"].cast(); + } + if (args.contains("gamma")) { + params.gamma = args["gamma"].cast(); + } + if (args.contains("coef0")) { + params.coef0 = args["coef0"].cast(); + } + if (args.contains("cost")) { + params.cost = args["cost"].cast(); + } + return params; +} + +/** + * @brief Register the PLSSVM @p Exception type as an Python exception with the @p py_exception_name derived from @p BaseException. + * @tparam Exception the PLSSVM exception to register in Python + * @tparam BaseException the Python base exception + * @param[in, out] m the module in which the Python exception is located + * @param[in] py_exception_name the name of the Python exception + * @param[in] base_exception the Python exception the new exception should be derived from + */ +template +void register_py_exception(py::module_ &m, const std::string &py_exception_name, BaseException &base_exception) { + static py::exception py_exception(m, py_exception_name.c_str(), base_exception.ptr()); + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) { + std::rethrow_exception(p); + } + } catch (const Exception &e) { + py_exception(e.what_with_loc().c_str()); + } + }); +} + +namespace detail { + +/** + * @def PLSSVM_CREATE_NUMPY_NAME_MAPPING + * @brief Map the @p type to its Numpy type name pendant @p numpy_name. + */ +#define PLSSVM_CREATE_NUMPY_NAME_MAPPING(type, numpy_name) \ + template <> \ + [[nodiscard]] constexpr inline std::string_view numpy_name_mapping() { return numpy_name; } + +/** + * @brief Tries to convert the given type to its Numpy name. + * @details The definition is marked as **deleted** if `T` isn't a valid mapped type. + * @tparam T the type to convert to a string + * @return the name of `T` (`[[nodiscard]]`) + */ +template +[[nodiscard]] constexpr inline std::string_view numpy_name_mapping() = delete; + +PLSSVM_CREATE_NUMPY_NAME_MAPPING(bool, "bool") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(char, "char") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(signed char, "byte") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(unsigned char, "ubyte") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(short, "short") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(unsigned short, "ushort") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(int, "intc") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(unsigned int, "uintc") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(long, "int") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(unsigned long, "uint") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(long long, "longlong") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(unsigned long long, "ulonglong") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(float, "float") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(double, "double") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(long double, "longdouble") +PLSSVM_CREATE_NUMPY_NAME_MAPPING(std::string, "string") + +#undef PLSSVM_CREATE_NUMPY_NAME_MAPPING + +} // namespace detail + +/** + * @brief Append the type information to the base @p class_name. + * @tparam real_type the type of the data points to convert to its Numpy name + * @tparam label_type the type of the labels to convert to its Numpy name + * @param class_name the base class name (the type names are appended to it) + * @return the unique class name + */ +template +[[nodiscard]] inline std::string assemble_unique_class_name(const std::string_view class_name) { + return fmt::format("{}_{}_{}", class_name, detail::numpy_name_mapping(), detail::numpy_name_mapping()); +} + +#endif // PLSSVM_BINDINGS_PYTHON_UTILITY_HPP_ diff --git a/bindings/Python/version/version.cpp b/bindings/Python/version/version.cpp new file mode 100644 index 000000000..393122cac --- /dev/null +++ b/bindings/Python/version/version.cpp @@ -0,0 +1,33 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + */ + +#include "plssvm/version/version.hpp" + +#include "pybind11/pybind11.h" // py::module_, py::class_, py::object +#include "pybind11/stl.h" // support for STL types: std::string + +namespace py = pybind11; + +// dummy class +class version {}; + +void init_version(py::module_ &m) { + // bind global version information + // complexity necessary to enforce read-only + py::class_(m, "version") + .def_property_readonly_static( + "name", [](const py::object & /* self */) { return plssvm::version::name; }, "the name of the PLSSVM library") + .def_property_readonly_static( + "version", [](const py::object & /* self */) { return plssvm::version::version; }, "the used version of the PLSSVM library") + .def_property_readonly_static( + "major", [](const py::object & /* self */) { return plssvm::version::major; }, "the used major version of the PLSSVM library") + .def_property_readonly_static( + "minor", [](const py::object & /* self */) { return plssvm::version::minor; }, "the used minor version of the PLSSVM library") + .def_property_readonly_static( + "patch", [](const py::object & /* self */) { return plssvm::version::patch; }, "the used patch version of the PLSSVM library"); +} \ No newline at end of file diff --git a/cmake/add_custom_build_type.cmake b/cmake/add_coverage_build_type.cmake similarity index 93% rename from cmake/add_custom_build_type.cmake rename to cmake/add_coverage_build_type.cmake index 9492966ba..dfbbad146 100644 --- a/cmake/add_custom_build_type.cmake +++ b/cmake/add_coverage_build_type.cmake @@ -8,24 +8,24 @@ SET(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used by the C++ compiler during coverage builds." - FORCE ) + FORCE) SET(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used by the C compiler during coverage builds." - FORCE ) + FORCE) SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage -lgcov" CACHE STRING "Flags used for linking binaries during coverage builds." - FORCE ) + FORCE) SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage -lgcov" CACHE STRING "Flags used by the shared libraries linker during coverage builds." - FORCE ) + FORCE) MARK_AS_ADVANCED( CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE - CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + CMAKE_SHARED_LINKER_FLAGS_COVERAGE) # update the documentation string of CMAKE_BUILD_TYPE for GUIs set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING @@ -35,10 +35,10 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel" "Coverage") # update CMAKE_CONFIGURATION_TYPES -if(CMAKE_CONFIGURATION_TYPES) +if (CMAKE_CONFIGURATION_TYPES) list(APPEND CMAKE_CONFIGURATION_TYPES Coverage) list(REMOVE_DUPLICATES CMAKE_CONFIGURATION_TYPES) set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING "Available configuration types." FORCE) -endif() +endif () diff --git a/cmake/assemble_summary_string.cmake b/cmake/assemble_summary_string.cmake index d4c31d6c7..7aa4b8d63 100644 --- a/cmake/assemble_summary_string.cmake +++ b/cmake/assemble_summary_string.cmake @@ -6,26 +6,26 @@ function(assemble_summary_string out_var) set(PLSSVM_SUMMARY_STRING_ASSEMBLE "") - if(DEFINED PLSSVM_CPU_TARGET_ARCHS) + if (DEFINED PLSSVM_CPU_TARGET_ARCHS) # add cpu platform - if(PLSSVM_NUM_CPU_TARGET_ARCHS EQUAL 0) + if (PLSSVM_NUM_CPU_TARGET_ARCHS EQUAL 0) string(APPEND PLSSVM_SUMMARY_STRING_ASSEMBLE " cpu,") - else() + else () string(APPEND PLSSVM_SUMMARY_STRING_ASSEMBLE " cpu (${PLSSVM_CPU_TARGET_ARCHS}),") - endif() - endif() - if(DEFINED PLSSVM_NVIDIA_TARGET_ARCHS) + endif () + endif () + if (DEFINED PLSSVM_NVIDIA_TARGET_ARCHS) # add nvidia platform string(APPEND PLSSVM_SUMMARY_STRING_ASSEMBLE " nvidia (${PLSSVM_NVIDIA_TARGET_ARCHS}),") - endif() - if(DEFINED PLSSVM_AMD_TARGET_ARCHS) + endif () + if (DEFINED PLSSVM_AMD_TARGET_ARCHS) # add amd platform string(APPEND PLSSVM_SUMMARY_STRING_ASSEMBLE " amd (${PLSSVM_AMD_TARGET_ARCHS}),") - endif() - if(DEFINED PLSSVM_INTEL_TARGET_ARCHS) + endif () + if (DEFINED PLSSVM_INTEL_TARGET_ARCHS) # add intel platform string(APPEND PLSSVM_SUMMARY_STRING_ASSEMBLE " intel (${PLSSVM_INTEL_TARGET_ARCHS}),") - endif() + endif () # remove last comma string(REGEX REPLACE ",$" "" PLSSVM_SUMMARY_STRING_ASSEMBLE "${PLSSVM_SUMMARY_STRING_ASSEMBLE}") set(${out_var} "${PLSSVM_SUMMARY_STRING_ASSEMBLE}" PARENT_SCOPE) diff --git a/cmake/check_python_libs.cmake b/cmake/check_python_libs.cmake index f252a328e..67c4c37e4 100644 --- a/cmake/check_python_libs.cmake +++ b/cmake/check_python_libs.cmake @@ -5,7 +5,7 @@ ######################################################################################################################## function(check_python_libs required_libraries error_string) - foreach(PLSSVM_PYTHON_LIB ${required_libraries}) + foreach (PLSSVM_PYTHON_LIB ${required_libraries}) # search for Python package execute_process( COMMAND ${Python3_EXECUTABLE} -c "import ${PLSSVM_PYTHON_LIB}" @@ -13,12 +13,12 @@ function(check_python_libs required_libraries error_string) OUTPUT_QUIET) # emit error if package couldn't be found - if(NOT ${PLSSVM_PYTHON_LIB_EXIT_CODE} EQUAL 0) + if (NOT ${PLSSVM_PYTHON_LIB_EXIT_CODE} EQUAL 0) message(FATAL_ERROR "The '${PLSSVM_PYTHON_LIB}' Python3 package is not installed. " "Please install it using the following command: '${Python3_EXECUTABLE} -m pip install ${PLSSVM_PYTHON_LIB}'\n " "${error_string}" ) - endif() - endforeach() + endif () + endforeach () endfunction() \ No newline at end of file diff --git a/cmake/discover_tests_with_death_test_filter.cmake b/cmake/discover_tests_with_death_test_filter.cmake new file mode 100644 index 000000000..34b7efd59 --- /dev/null +++ b/cmake/discover_tests_with_death_test_filter.cmake @@ -0,0 +1,15 @@ +## Authors: Alexander Van Craen, Marcel Breyer +## Copyright (C): 2018-today The PLSSVM project - All Rights Reserved +## License: This file is part of the PLSSVM project which is released under the MIT license. +## See the LICENSE.md file in the project root for full license information. +######################################################################################################################## + +function(discover_tests_with_death_test_filter test_executable_name) + if (PLSSVM_ENABLE_DEATH_TESTS) + # assertions are enabled -> enable Google death tests + gtest_discover_tests(${test_executable_name} PROPERTIES DISCOVERY_TIMEOUT 600) + else () + # assertions are disabled -> disable Google death tests + gtest_discover_tests(${test_executable_name} TEST_FILTER -*DeathTest* PROPERTIES DISCOVERY_TIMEOUT 600) + endif () +endfunction() \ No newline at end of file diff --git a/cmake/git_watcher.cmake b/cmake/git_watcher.cmake new file mode 100644 index 000000000..3918dd2db --- /dev/null +++ b/cmake/git_watcher.cmake @@ -0,0 +1,367 @@ +# git_watcher.cmake +# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake +# +# Released under the MIT License. +# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE + + +# This file defines a target that monitors the state of a git repo. +# If the state changes (e.g. a commit is made), then a file gets reconfigured. +# Here are the primary variables that control script behavior: +# +# PRE_CONFIGURE_FILE (REQUIRED) +# -- The path to the file that'll be configured. +# +# POST_CONFIGURE_FILE (REQUIRED) +# -- The path to the configured PRE_CONFIGURE_FILE. +# +# GIT_STATE_FILE (OPTIONAL) +# -- The path to the file used to store the previous build's git state. +# Defaults to the current binary directory. +# +# GIT_WORKING_DIR (OPTIONAL) +# -- The directory from which git commands will be run. +# Defaults to the directory with the top level CMakeLists.txt. +# +# GIT_EXECUTABLE (OPTIONAL) +# -- The path to the git executable. It'll automatically be set if the +# user doesn't supply a path. +# +# GIT_FAIL_IF_NONZERO_EXIT (OPTIONAL) +# -- Raise a FATAL_ERROR if any of the git commands return a non-zero +# exit code. This is set to TRUE by default. You can set this to FALSE +# if you'd like the build to continue even if a git command fails. +# +# GIT_IGNORE_UNTRACKED (OPTIONAL) +# -- Ignore the presence of untracked files when detecting if the +# working tree is dirty. This is set to FALSE by default. +# +# DESIGN +# - This script was designed similar to a Python application +# with a Main() function. I wanted to keep it compact to +# simplify "copy + paste" usage. +# +# - This script is invoked under two CMake contexts: +# 1. Configure time (when build files are created). +# 2. Build time (called via CMake -P). +# The first invocation is what registers the script to +# be executed at build time. +# +# MODIFICATIONS +# You may wish to track other git properties like when the last +# commit was made. There are two sections you need to modify, +# and they're tagged with a ">>>" header. + +# Short hand for converting paths to absolute. +macro(PATH_TO_ABSOLUTE var_name) + get_filename_component(${var_name} "${${var_name}}" ABSOLUTE) +endmacro() + +# Check that a required variable is set. +macro(CHECK_REQUIRED_VARIABLE var_name) + if (NOT DEFINED ${var_name}) + message(FATAL_ERROR "The \"${var_name}\" variable must be defined.") + endif () + PATH_TO_ABSOLUTE(${var_name}) +endmacro() + +# Check that an optional variable is set, or, set it to a default value. +macro(CHECK_OPTIONAL_VARIABLE_NOPATH var_name default_value) + if (NOT DEFINED ${var_name}) + set(${var_name} ${default_value}) + endif () +endmacro() + +# Check that an optional variable is set, or, set it to a default value. +# Also converts that path to an abspath. +macro(CHECK_OPTIONAL_VARIABLE var_name default_value) + CHECK_OPTIONAL_VARIABLE_NOPATH(${var_name} ${default_value}) + PATH_TO_ABSOLUTE(${var_name}) +endmacro() + +CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE) +CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE) +CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_BINARY_DIR}/git-state-hash") +CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}") +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_FAIL_IF_NONZERO_EXIT TRUE) +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_IGNORE_UNTRACKED FALSE) + +# Check the optional git variable. +# If it's not set, we'll try to find it using the CMake packaging system. +if (NOT DEFINED GIT_EXECUTABLE) + find_package(Git QUIET REQUIRED) +endif () +CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE) + + +set(_state_variable_names + GIT_RETRIEVED_STATE + GIT_HEAD_SHA1 + GIT_IS_DIRTY + GIT_AUTHOR_NAME + GIT_AUTHOR_EMAIL + GIT_COMMIT_DATE_ISO8601 + GIT_COMMIT_SUBJECT + GIT_COMMIT_BODY + GIT_DESCRIBE + GIT_BRANCH + # >>> + # 1. Add the name of the additional git variable you're interested in monitoring + # to this list. + GIT_REMOTE_URL + ) + + +# Macro: RunGitCommand +# Description: short-hand macro for calling a git function. Outputs are the +# "exit_code" and "output" variables. The "_permit_git_failure" +# variable can locally override the exit code checking- use it +# with caution. +macro(RunGitCommand) + execute_process(COMMAND + "${GIT_EXECUTABLE}" ${ARGV} + WORKING_DIRECTORY "${_working_dir}" + RESULT_VARIABLE exit_code + OUTPUT_VARIABLE output + ERROR_VARIABLE stderr + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT exit_code EQUAL 0 AND NOT _permit_git_failure) + set(ENV{GIT_RETRIEVED_STATE} "false") + + # Issue 26: git info not properly set + # + # Check if we should fail if any of the exit codes are non-zero. + # Most methods have a fall-back default value that's used in case of non-zero + # exit codes. If you're feeling risky, disable this safety check and use + # those default values. + if (GIT_FAIL_IF_NONZERO_EXIT) + string(REPLACE ";" " " args_with_spaces "${ARGV}") + message(FATAL_ERROR "${stderr} (${GIT_EXECUTABLE} ${args_with_spaces})") + endif () + endif () +endmacro() + + +# Function: GetGitState +# Description: gets the current state of the git repo. +# Args: +# _working_dir (in) string; the directory from which git commands will be executed. +function(GetGitState _working_dir) + + # This is an error code that'll be set to FALSE if the + # RunGitCommand ever returns a non-zero exit code. + set(ENV{GIT_RETRIEVED_STATE} "true") + + # Get whether or not the working tree is dirty. + if (GIT_IGNORE_UNTRACKED) + set(untracked_flag "-uno") + else () + set(untracked_flag "-unormal") + endif () + RunGitCommand(status --porcelain ${untracked_flag}) + if (NOT exit_code EQUAL 0) + set(ENV{GIT_IS_DIRTY} "false") + else () + if (NOT "${output}" STREQUAL "") + set(ENV{GIT_IS_DIRTY} "true") + else () + set(ENV{GIT_IS_DIRTY} "false") + endif () + endif () + + # There's a long list of attributes grabbed from git show. + set(object HEAD) + RunGitCommand(show -s "--format=%H" ${object}) + if (exit_code EQUAL 0) + set(ENV{GIT_HEAD_SHA1} ${output}) + endif () + + RunGitCommand(show -s "--format=%an" ${object}) + if (exit_code EQUAL 0) + set(ENV{GIT_AUTHOR_NAME} "${output}") + endif () + + RunGitCommand(show -s "--format=%ae" ${object}) + if (exit_code EQUAL 0) + set(ENV{GIT_AUTHOR_EMAIL} "${output}") + endif () + + RunGitCommand(show -s "--format=%ci" ${object}) + if (exit_code EQUAL 0) + set(ENV{GIT_COMMIT_DATE_ISO8601} "${output}") + endif () + + RunGitCommand(show -s "--format=%s" ${object}) + if (exit_code EQUAL 0) + # Escape \ + string(REPLACE "\\" "\\\\" output "${output}") + # Escape quotes + string(REPLACE "\"" "\\\"" output "${output}") + set(ENV{GIT_COMMIT_SUBJECT} "${output}") + endif () + + RunGitCommand(show -s "--format=%b" ${object}) + if (exit_code EQUAL 0) + if (output) + # Escape \ + string(REPLACE "\\" "\\\\" output "${output}") + # Escape quotes + string(REPLACE "\"" "\\\"" output "${output}") + # Escape line breaks in the commit message. + string(REPLACE "\r\n" "\\r\\n\\\r\n" safe "${output}") + if (safe STREQUAL output) + # Didn't have windows lines - try unix lines. + string(REPLACE "\n" "\\n\\\n" safe "${output}") + endif () + else () + # There was no commit body - set the safe string to empty. + set(safe "") + endif () + set(ENV{GIT_COMMIT_BODY} "${safe}") + else () + set(ENV{GIT_COMMIT_BODY} "") # empty string. + endif () + + # Get output of git describe + RunGitCommand(describe --always ${object}) + if (NOT exit_code EQUAL 0) + set(ENV{GIT_DESCRIBE} "") # empty string. + else () + set(ENV{GIT_DESCRIBE} "${output}") + endif () + + # Convert HEAD to a symbolic ref. This can fail, in which case we just + # set that variable to HEAD. + set(_permit_git_failure ON) + RunGitCommand(symbolic-ref --short -q ${object}) + unset(_permit_git_failure) + if (NOT exit_code EQUAL 0) + set(ENV{GIT_BRANCH} "${object}") + else () + set(ENV{GIT_BRANCH} "${output}") + endif () + + # >>> + # 2. Additional git properties can be added here via the + # "execute_process()" command. Be sure to set them in + # the environment using the same variable name you added + # to the "_state_variable_names" list. + RunGitCommand(config --get remote.origin.url) + if (NOT exit_code EQUAL 0) + set(ENV{GIT_REMOTE_URL} "") # empty string. + else () + set(ENV{GIT_REMOTE_URL} "${output}") + endif () + +endfunction() + + +# Function: GitStateChangedAction +# Description: this function is executed when the state of the git +# repository changes (e.g. a commit is made). +function(GitStateChangedAction) + foreach (var_name ${_state_variable_names}) + set(${var_name} $ENV{${var_name}}) + endforeach () + configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY) +endfunction() + + +# Function: HashGitState +# Description: loop through the git state variables and compute a unique hash. +# Args: +# _state (out) string; a hash computed from the current git state. +function(HashGitState _state) + set(ans "") + foreach (var_name ${_state_variable_names}) + string(SHA256 ans "${ans}$ENV{${var_name}}") + endforeach () + set(${_state} ${ans} PARENT_SCOPE) +endfunction() + + +# Function: CheckGit +# Description: check if the git repo has changed. If so, update the state file. +# Args: +# _working_dir (in) string; the directory from which git commands will be ran. +# _state_changed (out) bool; whether or no the state of the repo has changed. +function(CheckGit _working_dir _state_changed) + + # Get the current state of the repo. + GetGitState("${_working_dir}") + + # Convert that state into a hash that we can compare against + # the hash stored on-disk. + HashGitState(state) + + # Issue 14: post-configure file isn't being regenerated. + # + # Update the state to include the SHA256 for the pre-configure file. + # This forces the post-configure file to be regenerated if the + # pre-configure file has changed. + file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash) + string(SHA256 state "${preconfig_hash}${state}") + + # Check if the state has changed compared to the backup on disk. + if (EXISTS "${GIT_STATE_FILE}") + file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS) + if (OLD_HEAD_CONTENTS STREQUAL "${state}") + # State didn't change. + set(${_state_changed} "false" PARENT_SCOPE) + return() + endif () + endif () + + # The state has changed. + # We need to update the state file on disk. + # Future builds will compare their state to this file. + file(WRITE "${GIT_STATE_FILE}" "${state}") + set(${_state_changed} "true" PARENT_SCOPE) +endfunction() + + +# Function: SetupGitMonitoring +# Description: this function sets up custom commands that make the build system +# check the state of git before every build. If the state has +# changed, then a file is configured. +function(SetupGitMonitoring) + add_custom_target(check_git + ALL + DEPENDS ${PRE_CONFIGURE_FILE} + BYPRODUCTS + ${POST_CONFIGURE_FILE} + ${GIT_STATE_FILE} + COMMENT "Checking the git repository for changes..." + COMMAND + ${CMAKE_COMMAND} + -D_BUILD_TIME_CHECK_GIT=TRUE + -DGIT_WORKING_DIR=${GIT_WORKING_DIR} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_STATE_FILE=${GIT_STATE_FILE} + -DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE} + -DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE} + -DGIT_FAIL_IF_NONZERO_EXIT=${GIT_FAIL_IF_NONZERO_EXIT} + -DGIT_IGNORE_UNTRACKED=${GIT_IGNORE_UNTRACKED} + -P "${CMAKE_CURRENT_LIST_FILE}") +endfunction() + + +# Function: Main +# Description: primary entry-point to the script. Functions are selected based +# on whether it's configure or build time. +function(Main) + if (_BUILD_TIME_CHECK_GIT) + # Check if the repo has changed. + # If so, run the change action. + CheckGit("${GIT_WORKING_DIR}" changed) + if (changed OR NOT EXISTS "${POST_CONFIGURE_FILE}") + GitStateChangedAction() + endif () + else () + # >> Executes at configure time. + SetupGitMonitoring() + endif () +endfunction() + +# And off we go... +Main() diff --git a/cmake/modules/Findcxxopts.cmake b/cmake/modules/Findcxxopts.cmake new file mode 100644 index 000000000..df5830b9f --- /dev/null +++ b/cmake/modules/Findcxxopts.cmake @@ -0,0 +1,36 @@ +## Authors: Alexander Van Craen, Marcel Breyer +## Copyright (C): 2018-today The PLSSVM project - All Rights Reserved +## License: This file is part of the PLSSVM project which is released under the MIT license. +## See the LICENSE.md file in the project root for full license information. +## Based on: https://gist.github.com/CodeFinder2/40864be863c887e0a6dabf4f3a1fa93b +######################################################################################################################## + +find_package(PkgConfig) + +# check if the include directory is given using an environment variable +if (DEFINED ENV{cxxopts_INCLUDE_DIR} AND NOT EXISTS "${cxxopts_INCLUDE_DIR}") + set(cxxopts_INCLUDE_DIR $ENV{cxxopts_INCLUDE_DIR}) +endif () + +# try to automatically find the header files in the standard directories +if (NOT EXISTS "${cxxopts_INCLUDE_DIR}") + find_path(cxxopts_INCLUDE_DIR + NAMES cxxopts.hpp + DOC "cxxopts header-only library header files" + ) +endif () + +# allow the user to specify the include directory in the CMake call (if provided, used instead of the environment variable) +if (EXISTS "${cxxopts_INCLUDE_DIR}") + include(FindPackageHandleStandardArgs) + mark_as_advanced(cxxopts_INCLUDE_DIR) +else () + #message(WARNING "Can't find package cxxopts!") +endif () + +# set the _FOUND variable to the correct value +if (EXISTS "${cxxopts_INCLUDE_DIR}") + set(cxxopts_FOUND 1) +else () + set(cxxopts_FOUND 0) +endif () diff --git a/cmake/modules/Findfast_float.cmake b/cmake/modules/Findfast_float.cmake new file mode 100644 index 000000000..8bc1feb61 --- /dev/null +++ b/cmake/modules/Findfast_float.cmake @@ -0,0 +1,36 @@ +## Authors: Alexander Van Craen, Marcel Breyer +## Copyright (C): 2018-today The PLSSVM project - All Rights Reserved +## License: This file is part of the PLSSVM project which is released under the MIT license. +## See the LICENSE.md file in the project root for full license information. +## Based on: https://gist.github.com/CodeFinder2/40864be863c887e0a6dabf4f3a1fa93b +######################################################################################################################## + +find_package(PkgConfig) + +# check if the include directory is given using an environment variable +if (DEFINED ENV{fast_float_INCLUDE_DIR} AND NOT EXISTS "${fast_float_INCLUDE_DIR}") + set(fast_float_INCLUDE_DIR $ENV{fast_float_INCLUDE_DIR}) +endif () + +# try to automatically find the header files in the standard directories +if (NOT EXISTS "${fast_float_INCLUDE_DIR}") + find_path(fast_float_INCLUDE_DIR + NAMES fast_float.hpp + DOC "fast_float header-only library header files" + ) +endif () + +# allow the user to specify the include directory in the CMake call (if provided, used instead of the environment variable) +if (EXISTS "${fast_float_INCLUDE_DIR}") + include(FindPackageHandleStandardArgs) + mark_as_advanced(fast_float_INCLUDE_DIR) +else () + #message(WARNING "Can't find required package fast_float!") +endif () + +# set the _FOUND variable to the correct value +if (EXISTS "${fast_float_INCLUDE_DIR}") + set(fast_float_FOUND 1) +else () + set(fast_float_FOUND 0) +endif () diff --git a/cmake/modules/Findigor.cmake b/cmake/modules/Findigor.cmake new file mode 100644 index 000000000..88a2e668e --- /dev/null +++ b/cmake/modules/Findigor.cmake @@ -0,0 +1,36 @@ +## Authors: Alexander Van Craen, Marcel Breyer +## Copyright (C): 2018-today The PLSSVM project - All Rights Reserved +## License: This file is part of the PLSSVM project which is released under the MIT license. +## See the LICENSE.md file in the project root for full license information. +## Based on: https://gist.github.com/CodeFinder2/40864be863c887e0a6dabf4f3a1fa93b +######################################################################################################################## + +find_package(PkgConfig) + +# check if the include directory is given using an environment variable +if (DEFINED ENV{igor_INCLUDE_DIR} AND NOT EXISTS "${igor_INCLUDE_DIR}") + set(igor_INCLUDE_DIR $ENV{igor_INCLUDE_DIR}) +endif () + +# try to automatically find the header files in the standard directories +if (NOT EXISTS "${igor_INCLUDE_DIR}") + find_path(igor_INCLUDE_DIR + NAMES igor.hpp + DOC "igor header-only library header files" + ) +endif () + +# allow the user to specify the include directory in the CMake call (if provided, used instead of the environment variable) +if (EXISTS "${igor_INCLUDE_DIR}") + include(FindPackageHandleStandardArgs) + mark_as_advanced(igor_INCLUDE_DIR) +else () + #message(WARNING "Can't find required package igor!") +endif () + +# set the _FOUND variable to the correct value +if (EXISTS "${igor_INCLUDE_DIR}") + set(igor_FOUND 1) +else () + set(igor_FOUND 0) +endif () diff --git a/cmake/parse_architecture_info.cmake b/cmake/parse_architecture_info.cmake index 585182e8c..0d92c2f9d 100644 --- a/cmake/parse_architecture_info.cmake +++ b/cmake/parse_architecture_info.cmake @@ -5,23 +5,23 @@ ######################################################################################################################## function(parse_architecture_info target_platform target_archs num_archs) - # transform platforms to list (e.g "nvidia:sm_70,sm_80" -> "nvidia;sm_70,sm_80") - string(REPLACE ":" ";" ARCH_LIST ${target_platform}) - # remove platform from list (e.g. "nvidia;sm_70,sm_80" -> "sm_70,sm_80") - list(POP_FRONT ARCH_LIST) + # transform platforms to list (e.g "nvidia:sm_70,sm_80" -> "nvidia;sm_70,sm_80") + string(REPLACE ":" ";" ARCH_LIST ${target_platform}) + # remove platform from list (e.g. "nvidia;sm_70,sm_80" -> "sm_70,sm_80") + list(POP_FRONT ARCH_LIST) - if(ARCH_LIST STREQUAL "") - # immediately return if architecture list is empty - set(${target_archs} "" PARENT_SCOPE) - set(${num_archs} 0 PARENT_SCOPE) - else() - # transform architectures to list and set output-variable (e.g. "sm_70,sm_80" -> "sm_70;sm_80") - string(REPLACE "," ";" ARCH_LIST ${ARCH_LIST}) - set(${target_archs} ${ARCH_LIST} PARENT_SCOPE) + if (ARCH_LIST STREQUAL "") + # immediately return if architecture list is empty + set(${target_archs} "" PARENT_SCOPE) + set(${num_archs} 0 PARENT_SCOPE) + else () + # transform architectures to list and set output-variable (e.g. "sm_70,sm_80" -> "sm_70;sm_80") + string(REPLACE "," ";" ARCH_LIST ${ARCH_LIST}) + set(${target_archs} ${ARCH_LIST} PARENT_SCOPE) - # get number of architectures and set output-variable (e.g. "sm_70;sm_80" -> 2) - list(LENGTH ARCH_LIST LEN) - set(${num_archs} ${LEN} PARENT_SCOPE) - endif() + # get number of architectures and set output-variable (e.g. "sm_70;sm_80" -> 2) + list(LENGTH ARCH_LIST LEN) + set(${num_archs} ${LEN} PARENT_SCOPE) + endif () endfunction() diff --git a/cmake/plssvmConfig.cmake.in b/cmake/plssvmConfig.cmake.in index 92f0d8a70..dec270441 100644 --- a/cmake/plssvmConfig.cmake.in +++ b/cmake/plssvmConfig.cmake.in @@ -7,39 +7,46 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) -find_dependency(OpenMP QUIET) -# check if the OpenMP backend is required +# check if the OpenMP is required (for the OpenMP backend or library utilities) +set(PLSSVM_HAS_OPENMP_UTILITY @PLSSVM_FOUND_OPENMP_FOR_UTILITY@) set(PLSSVM_HAS_OPENMP_BACKEND @PLSSVM_OPENMP_BACKEND_LIBRARY_NAME@) -if(PLSSVM_HAS_OPENMP_BACKEND) +if (PLSSVM_HAS_OPENMP_BACKEND OR PLSSVM_HAS_OPENMP_UTILITY) find_dependency(OpenMP REQUIRED) -endif() +endif () # check if fmt has been installed via FetchContent set(PLSSVM_FOUND_FMT @fmt_FOUND@) -if(PLSSVM_FOUND_FMT) +if (PLSSVM_FOUND_FMT) find_dependency(fmt REQUIRED) -endif() +endif () # check if the CUDA backend is required set(PLSSVM_HAS_CUDA_BACKEND @PLSSVM_CUDA_BACKEND_LIBRARY_NAME@) -if(PLSSVM_HAS_CUDA_BACKEND) +if (PLSSVM_HAS_CUDA_BACKEND) enable_language(CUDA) -endif() +endif () + +# check if the HIP backend is required +set(PLSSVM_HAS_HIP_BACKEND @PLSSVM_HIP_BACKEND_LIBRARY_NAME@) +if (PLSSVM_HAS_HIP_BACKEND) + enable_language(HIP) + find_package(HIP REQUIRED) +endif () # check if the OpenCL backend is required set(PLSSVM_HAS_OPENCL_BACKEND @PLSSVM_OPENCL_BACKEND_LIBRARY_NAME@) -if(PLSSVM_HAS_OPENCL_BACKEND) +if (PLSSVM_HAS_OPENCL_BACKEND) find_dependency(OpenCL REQUIRED) -endif() +endif () # check if the SYCL implementation hipSYCL backend is required set(PLSSVM_HAS_SYCL_BACKEND_HIPSYCL @PLSSVM_SYCL_BACKEND_HIPSYCL_LIBRARY_NAME@) -if(PLSSVM_HAS_SYCL_BACKEND_HIPSYCL) +if (PLSSVM_HAS_SYCL_BACKEND_HIPSYCL) set(HIPSYCL_TARGETS @HIPSYCL_TARGETS@) find_dependency(hipSYCL CONFIG REQUIRED) message(STATUS "Found hipSYCL with ${HIPSYCL_TARGETS}") -endif() +endif () include("${CMAKE_CURRENT_LIST_DIR}/plssvmTargets.cmake") check_required_components("plssvm") \ No newline at end of file diff --git a/cmake/set_local_and_parent.cmake b/cmake/utility_macros.cmake similarity index 80% rename from cmake/set_local_and_parent.cmake rename to cmake/utility_macros.cmake index 20f15da59..f2c4466ed 100644 --- a/cmake/set_local_and_parent.cmake +++ b/cmake/utility_macros.cmake @@ -8,4 +8,9 @@ macro(set_local_and_parent NAME VALUE) set(${ARGV0} ${ARGV1}) set(${ARGV0} ${ARGV1} PARENT_SCOPE) +endmacro() + +macro(append_local_and_parent LIST_NAME VALUE) + list(APPEND ${ARGV0} ${ARGV1}) + set(${ARGV0} ${${ARGV0}} PARENT_SCOPE) endmacro() \ No newline at end of file diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index b99d7c4d8..d669919e8 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -4,6 +4,8 @@ ## See the LICENSE.md file in the project root for full license information. ######################################################################################################################## +list(APPEND CMAKE_MESSAGE_INDENT "Documentation: ") + ######################################################################################################################## ## setup documentation generation with doxygen ## ######################################################################################################################## @@ -17,7 +19,7 @@ set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md") set(DOXYGEN_FILE_PATTERNS "*.hpp;*.cuh;*.cl;*.dox") set(DOXYGEN_EXTENSION_MAPPING "cu=c++;cuh=c++;cl=c++") set(DOXYGEN_STRIP_FROM_PATH "${PROJECT_SOURCE_DIR}") -set(DOXYGEN_EXCLUDE "${PROJECT_SOURCE_DIR}/src/main_train.cpp;${PROJECT_SOURCE_DIR}/src/main_predict.cpp;${PROJECT_SOURCE_DIR}/include/plssvm/backends/DPCPP/;${PROJECT_SOURCE_DIR}/include/plssvm/backends/hipSYCL/;${PROJECT_SOURCE_DIR}/src/plssvm/backends/DPCPP/;${PROJECT_SOURCE_DIR}/src/plssvm/backends/hipSYCL/") +set(DOXYGEN_EXCLUDE "${PROJECT_SOURCE_DIR}/src/main_train.cpp;${PROJECT_SOURCE_DIR}/src/main_predict.cpp;${PROJECT_SOURCE_DIR}/src/main_scale.cpp") set(DOXYGEN_ABBREVIATE_BRIEF "") set(DOXYGEN_QUIET "YES") set(DOXYGEN_HTML_TIMESTAMP "YES") @@ -27,6 +29,13 @@ set(DOXYGEN_SORT_MEMBER_DOCS "NO") set(DOXYGEN_INLINE_INHERITED_MEMB "YES") set(DOXYGEN_USE_MATHJAX "YES") set(DOXYGEN_PROJECT_LOGO "${PROJECT_SOURCE_DIR}/docs/resources/logo_90x55.png") +set(DOXYGEN_EXCLUDE_SYMBOLS "*_HPP_") + +set(DOXYGEN_DOT_IMAGE_FORMAT "svg") +set(DOXYGEN_INTERACTIVE_SVG "YES") +set(DOXYGEN_INCLUDE_GRAPH "NO") +set(DOXYGEN_EXTRACT_PRIVATE "YES") +set(DOXYGEN_EXAMPLE_PATH "${PROJECT_SOURCE_DIR}/docs/resources/examples") ## enable processing of specific attributes and macros set(DOXYGEN_ENABLE_PREPROCESSING "YES") @@ -54,4 +63,6 @@ add_custom_command( POST_BUILD COMMAND ${CMAKE_COMMAND} -P "${PROJECT_SOURCE_DIR}/cmake/create_documentation_shortcut.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" -) \ No newline at end of file +) + +list(POP_BACK CMAKE_MESSAGE_INDENT) \ No newline at end of file diff --git a/docs/plssvm-predict.1.in b/docs/plssvm-predict.1.in index f660e7e62..2fce30630 100644 --- a/docs/plssvm-predict.1.in +++ b/docs/plssvm-predict.1.in @@ -1,5 +1,5 @@ .\" Manpage for plssvm-predict. -.TH PLSSVM-PREDICT 1 "20 April 2022" "1.2.0" "plssvm-predict Manual" +.TH PLSSVM-PREDICT 1 "13 December 2022" "2.0.0" "plssvm-predict Manual" .SH NAME plssvm-predict - LS-SVM with multiple (GPU-)backends @@ -22,14 +22,32 @@ choose the target platform: @PLSSVM_PLATFORM_NAME_LIST@ (default: automatic) @PLSSVM_SYCL_MANPAGE_ENTRY@ +@PLSSVM_PERFORMANCE_TRACKER_MANPAGE_ENTRY@ + +.TP +.B --use_string_as_labels arg +must be specified if the labels should be interpreted as strings instead of integers + +.TP +.B --use_float_as_real_type arg +must be specified if float should be used instead of double as floating point type + +.TP +.B --verbosity arg +choose the level of verbosity: full|timing|libsvm|quiet (default: full) + .TP .B -q, --quiet -quiet mode (no outputs) +quiet mode (no outputs regardless the provided verbosity level!) .TP .B -h, --help print this helper message +.TP +.B -v, --version +print version information + .TP .B --test test_file the file containing the test data points diff --git a/docs/plssvm-scale.1.in b/docs/plssvm-scale.1.in new file mode 100644 index 000000000..c03d3870e --- /dev/null +++ b/docs/plssvm-scale.1.in @@ -0,0 +1,85 @@ +.\" Manpage for plssvm-scale. +.TH PLSSVM-SCALE 1 "13 December 2022" "2.0.0" "plssvm-predict Manual" + +.SH NAME +plssvm-scale - LS-SVM with multiple (GPU-)backends + +.SH SYNOPSIS +svm-scale [OPTION...] input_file [scaled_file] + +.SH DESCRIPTION +plssvm-scale is a utility to scale the data points in a data set to a specific range. + +.SH OPTIONS + +.TP +.B -l, --lower arg +the lowest (minimal) value allowed in each dimension of the data set (default: -1) + +.TP +.B -u, --upper arg +the highest (maximal) value allowed in each dimension of the data set (default: 1) + +.TP +.B -f, --format arg +the file format to output the scaled data to: libsvm|arff (default: libsvm) + +.TP +.B -s, --save_filename arg +the file name to which the scaling factors should be saved + +.TP +.B -r, --restore_filename arg +the file name from which previous scaling factors should be loaded + +@PLSSVM_PERFORMANCE_TRACKER_MANPAGE_ENTRY@ + +.TP +.B --use_string_as_labels arg +must be specified if the labels should be interpreted as strings instead of integers + +.TP +.B --use_float_as_real_type arg +must be specified if float should be used instead of double as floating point type + +.TP +.B --verbosity arg +choose the level of verbosity: full|timing|libsvm|quiet (default: full) + +.TP +.B -q, --quiet +quiet mode (no outputs regardless the provided verbosity level!) + +.TP +.B -h, --help +print this helper message + +.TP +.B -v, --version +print version information + +.TP +.B --input input_file +the file containing the data points that should be scaled + +.TP +.B --scaled scaled_file +the file containing the scaled data points + +.SH EXIT STATUS +EXIT_SUCCESS +.RS +if OK, +.RE +EXIT_FAILURE +.RS +if any error occurred (e.g., the input file couldn't be opened). + +.SH SEE ALSO +plssvm-train(1) +plssvm-predict(1) + +.SH AUTHOR +Alexander Van Craen +.br +Marcel Breyer diff --git a/docs/plssvm-train.1.in b/docs/plssvm-train.1.in index f2d5e4186..d97bd2d71 100644 --- a/docs/plssvm-train.1.in +++ b/docs/plssvm-train.1.in @@ -1,5 +1,5 @@ .\" Manpage for plssvm-train. -.TH PLSSVM-TRAIN 1 "20 April 2022" "1.2.0" "plssvm-train Manual" +.TH PLSSVM-TRAIN 1 "13 December 2022" "2.0.0" "plssvm-train Manual" .SH NAME plssvm-train - LS-SVM with multiple (GPU-)backends @@ -39,6 +39,10 @@ set the parameter C (default: 1) .B -e, --epsilon arg set the tolerance of termination criterion (default: 0.001) +.TP +.B -i, --max_iter arg +the maximum number of CG iterations (default: #features) + .TP .B -b, --backend arg choose the backend: @PLSSVM_BACKEND_NAME_LIST@ (default: automatic) @@ -49,14 +53,32 @@ choose the target platform: @PLSSVM_PLATFORM_NAME_LIST@ (default: automatic) @PLSSVM_SYCL_MANPAGE_ENTRY@ +@PLSSVM_PERFORMANCE_TRACKER_MANPAGE_ENTRY@ + +.TP +.B --use_string_as_labels arg +must be specified if the labels should be interpreted as strings instead of integers + +.TP +.B --use_float_as_real_type arg +must be specified if float should be used instead of double as floating point type + +.TP +.B --verbosity arg +choose the level of verbosity: full|timing|libsvm|quiet (default: full) + .TP .B -q, --quiet -quiet mode (no outputs) +quiet mode (no outputs regardless the provided verbosity level!) .TP .B -h, --help print this helper message +.TP +.B -v, --version +print version information + .TP .B --input training_set_file the file containing the training data points diff --git a/docs/resources/dirs.dox b/docs/resources/dirs.dox index 522a18bb5..977e0c63f 100644 --- a/docs/resources/dirs.dox +++ b/docs/resources/dirs.dox @@ -120,6 +120,50 @@ * @brief Directory containing implementation details for the SYCL backend. */ +/** + * @dir include/plssvm/backends/SYCL/DPCPP + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing the implementation for the SYCL backend using DPC++ as SYCL implementation. + */ + +/** + * @dir include/plssvm/backends/SYCL/DPCPP/detail + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing implementation details for the SYCL backend using DPC++ as SYCL implementation. + */ + +/** + * @dir include/plssvm/backends/SYCL/hipSYCL + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing the implementation for the SYCL backend using hipSYCL as SYCL implementation. + */ + +/** + * @dir include/plssvm/backends/SYCL/hipSYCL/detail + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing implementation details for the SYCL backend using hipSYCL as SYCL implementation. + */ + /** * @dir include/plssvm/detail @@ -132,6 +176,28 @@ * @brief Directory containing implementation details which **should not** be used by users. */ +/** + * @dir include/plssvm/detail/cmd + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing implementation details regarding the command line interface which **should not** be used by users. + */ + +/** + * @dir include/plssvm/detail/io + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing implementation details regarding the IO functionality which **should not** be used by users. + */ + /** * @dir include/plssvm/exceptions * @author Alexander Van Craen @@ -152,4 +218,15 @@ * See the LICENSE.md file in the project root for full license information. * * @brief Directory containing compile-time constant version information. + */ + +/** + * @dir include/plssvm/version/git_metadata + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Directory containing compile-time constant meta data for git specific information. */ \ No newline at end of file diff --git a/docs/resources/examples/csvm_examples.cpp b/docs/resources/examples/csvm_examples.cpp new file mode 100644 index 000000000..f2bcd101e --- /dev/null +++ b/docs/resources/examples/csvm_examples.cpp @@ -0,0 +1,47 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief A few examples for the plssvm::csvm classes. + */ + +#include "plssvm/core.hpp" + +#include +#include + +int main() { + // create a train data set from a file with int labels + const plssvm::data_set train_data_with_label{ "path/to/train/file.libsvm" }; + // create a test data set from a file without labels + const plssvm::data_set test_data{ "path/to/test/file.libsvm" }; + + // create a support vector machine + auto svm = plssvm::make_csvm(); + + // optional: update a parameter; can also be directly passed to the plssvm::make_csvm function! + svm->set_params(plssvm::kernel_type = plssvm::kernel_function_type::rbf, plssvm::gamma = 0.001); + + // fit the support vector machine + const plssvm::model model = svm->fit(train_data_with_label); + + // score the learned model + const double model_score = svm->score(model); + + // score a new, unseen data set + const double score = svm->score(model, test_data); + + // + // Note: the model is NOT bound to a specific support vector machine + // + // explicitly make an OpenCL support vector machine + const plssvm::opencl::csvm opencl_svm{}; + + // predict labels + const std::vector predicted_labels = opencl_svm.predict(model, test_data); + + return 0; +} \ No newline at end of file diff --git a/docs/resources/examples/csvm_factory_examples.cpp b/docs/resources/examples/csvm_factory_examples.cpp new file mode 100644 index 000000000..7de508e75 --- /dev/null +++ b/docs/resources/examples/csvm_factory_examples.cpp @@ -0,0 +1,37 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief A few examples for the plssvm::make_csvm function. + */ + +#include "plssvm/core.hpp" + +int main() { + // create a default support vector machine + // the used backend is determined by the plssvm::determine_default_backend function + const auto svm1 = plssvm::make_csvm(); + + // explicitly define a backend to use (using the default SVM parameters) + const auto svm2 = plssvm::make_csvm(plssvm::backend_type::cuda); + + // for SYCL, the SYCL implementation type can/must also be specified + const auto svm3 = plssvm::make_csvm(plssvm::backend_type::sycl, plssvm::sycl_implementation_type = plssvm::sycl::implementation_type::dpcpp); + + // explicitly define a backend and parameters to use + const plssvm::parameter params{ plssvm::kernel_type = plssvm::kernel_function_type::rbf, plssvm::gamma = 0.0001 }; + const auto svm4 = plssvm::make_csvm(plssvm::backend_type::openmp, params); + + // explicitly define a backend and named-parameters to use + const auto svm5 = plssvm::make_csvm(plssvm::backend_type::opencl, plssvm::kernel_type = plssvm::kernel_function_type::rbf, plssvm::gamma = 0.0001); + + // explicitly define a backend, parameters, and named-parameters to use + const auto svm6 = plssvm::make_csvm(plssvm::backend_type::sycl, params, plssvm::degree = 6); + // Note: in this case the plssvm::parameter object must be THE FIRST parameter, i.e., the following will not compiled + // const auto svm7 = plssvm::make_csvm(plssvm::backend_type::sycl, plssvm::degree = 6, params); + + return 0; +} \ No newline at end of file diff --git a/docs/resources/examples/data_set_examples.cpp b/docs/resources/examples/data_set_examples.cpp new file mode 100644 index 000000000..3d2d2242e --- /dev/null +++ b/docs/resources/examples/data_set_examples.cpp @@ -0,0 +1,37 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief A few examples for the plssvm::data_set class. + */ + +#include "plssvm/core.hpp" + +#include +#include + +int main() { + + // create a data set from a file with int labels + plssvm::data_set data1{ "path/to/train/file.libsvm" }; + // create a data set from a file with std::string labels + plssvm::data_set data2{ "path/to/train/file_string_labels.libsvm" }; + + // create a data set from a std::vector with labels + std::vector> data_vector{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + std::vector label_vector{ 0, 0, 1 }; + plssvm::data_set data3{ std::move(data_vector), std::move(label_vector) }; + // get the different labels + const bool has_label = data3.has_labels(); // will return true, since labels have explicitly been provided + auto different_labels = data3.different_labels().value(); // will return an optional vector containing { 0, 1 } + + // create a train data set and scale it to the range [-1, 1] + plssvm::data_set train_data{ "path/to/train/file.libsvm", { -1.0, 1.0 } }; + // scale a test data set and scale it according to the train data set + plssvm::data_set test_data{ "path/to/test/file.libsvm", train_data.scaling_factors()->get() }; + + return 0; +} \ No newline at end of file diff --git a/docs/resources/examples/model_examples.cpp b/docs/resources/examples/model_examples.cpp new file mode 100644 index 000000000..a375325f1 --- /dev/null +++ b/docs/resources/examples/model_examples.cpp @@ -0,0 +1,27 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief A few examples for the plssvm::model class. + */ + +#include "plssvm/core.hpp" + +int main() { + // create a data set from a file + const plssvm::data_set data{ "path/to/train/file.libsvm" }; + + // create a support vector machine + const auto svm = plssvm::make_csvm(); + + // fit the support vector machine + const plssvm::model model = svm->fit(data); + + // save the model file + model.save("path/to/model.libsvm"); + + return 0; +} \ No newline at end of file diff --git a/docs/resources/examples/parameter_examples.cpp b/docs/resources/examples/parameter_examples.cpp new file mode 100644 index 000000000..3f2c9b6ed --- /dev/null +++ b/docs/resources/examples/parameter_examples.cpp @@ -0,0 +1,36 @@ +/** + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief A few examples for the plssvm::parameter class. + */ + +#include "plssvm/core.hpp" + +int main() { + // create default parameter + const plssvm::parameter params1{}; + + // create parameters by explicitly specifying ALL values (not recommended) + const plssvm::parameter params2{ + plssvm::kernel_function_type::polynomial, // kernel function + 4, // degree + 0.001, // gamma + 1.2, // coef0 + 0.01 // cost + }; + + // create parameters by using name-parameters + const plssvm::parameter params3{ plssvm::kernel_type = plssvm::kernel_function_type::polynomial, plssvm::degree = 2, plssvm::gamma = 0.001 }; + + // create parameters by using another plssvm::parameter object together with named-parameters + // initializes params4 with the values of params2 and OVERRIDES all values provided using named-parameters + const plssvm::parameter params4{ params2, plssvm::degree = 5 }; + // Note: in this case the plssvm::parameter object must be THE FIRST parameter, i.e., the following will not compiled + // const plssvm::parameter params5{ plssvm::degree = 5, params2 }; + + return 0; +} \ No newline at end of file diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt new file mode 100644 index 000000000..5c2917bb2 --- /dev/null +++ b/examples/cpp/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.16) + +project(LibraryUsageExample LANGUAGES CXX) + +find_package(plssvm CONFIG REQUIRED) + +add_executable(prog main.cpp) + +target_compile_features(prog PUBLIC cxx_std_17) +target_link_libraries(prog PUBLIC plssvm::plssvm-all) \ No newline at end of file diff --git a/examples/cpp/main.cpp b/examples/cpp/main.cpp new file mode 100644 index 000000000..933d10c8e --- /dev/null +++ b/examples/cpp/main.cpp @@ -0,0 +1,40 @@ +#include "plssvm/core.hpp" + +#include +#include +#include + +int main() { + try { + // create a new C-SVM parameter set, explicitly overriding the default kernel function + const plssvm::parameter params{ plssvm::kernel_type = plssvm::kernel_function_type::polynomial }; + + // create two data sets: one with the training data scaled to [-1, 1] + // and one with the test data scaled like the training data + const plssvm::data_set train_data{ "train_file.libsvm", { -1.0, 1.0 } }; + const plssvm::data_set test_data{ "test_file.libsvm", train_data.scaling_factors()->get() }; + + // create C-SVM using the default backend and the previously defined parameter + const auto svm = plssvm::make_csvm(params); + + // fit using the training data, (optionally) set the termination criterion + const plssvm::model model = svm->fit(train_data, plssvm::epsilon = 10e-6); + + // get accuracy of the trained model + const double model_accuracy = svm->score(model); + std::cout << "model accuracy: " << model_accuracy << std::endl; + + // predict the labels + const std::vector label = svm->predict(model, test_data); + + // write model file to disk + model.save("model_file.libsvm"); + + } catch (const plssvm::exception &e) { + std::cerr << e.what_with_loc() << std::endl; + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + } + + return 0; +} \ No newline at end of file diff --git a/examples/python/main.py b/examples/python/main.py new file mode 100644 index 000000000..d27172332 --- /dev/null +++ b/examples/python/main.py @@ -0,0 +1,30 @@ +import plssvm + +try: + # create a new C-SVM parameter set, explicitly overriding the default kernel function + params = plssvm.Parameter(kernel_type=plssvm.KernelFunctionType.POLYNOMIAL) + + # create two data sets: one with the training data scaled to [-1, 1] + # and one with the test data scaled like the training data + train_data = plssvm.DataSet("train_data.libsvm", scaling=(-1.0, 1.0)) + test_data = plssvm.DataSet("test_data.libsvm", scaling=train_data.scaling_factors()) + + # create C-SVM using the default backend and the previously defined parameter + svm = plssvm.CSVM(params) + + # fit using the training data, (optionally) set the termination criterion + model = svm.fit(train_data, epsilon=10e-6) + + # get accuracy of the trained model + model_accuracy = svm.score(model) + print("model accuracy: {}".format(model_accuracy)) + + # predict labels + label = svm.predict(model, test_data) + + # write model file to disk + model.save("model_file.libsvm") +except plssvm.PLSSVMError as e: + print(e) +except RuntimeError as e: + print(e) diff --git a/examples/python/sklearn_like_svc.py b/examples/python/sklearn_like_svc.py new file mode 100644 index 000000000..01a6fccf0 --- /dev/null +++ b/examples/python/sklearn_like_svc.py @@ -0,0 +1,18 @@ +from sklearn.datasets import make_classification +import plssvm + +num_samples = 2**8 +num_features = 2**6 + +samples, labels = make_classification(n_samples=num_samples, n_features=num_features, n_redundant=0, + n_informative=2, n_clusters_per_class=1) + +# create C-SVM +svc = plssvm.SVC(kernel='linear', C=1.0, tol=10e-3, verbose=False) + +# fit the model +svc.fit(samples, labels) + +# score the data set +model_accuracy = svc.score(samples, labels) +print("model accuracy: {0:.2f}".format(model_accuracy * 100)) diff --git a/include/plssvm/backend_types.hpp b/include/plssvm/backend_types.hpp index 6b52acc0f..3e83c1bce 100644 --- a/include/plssvm/backend_types.hpp +++ b/include/plssvm/backend_types.hpp @@ -6,46 +6,57 @@ * @license This file is part of the PLSSVM project which is released under the MIT license. * See the LICENSE.md file in the project root for full license information. * - * @brief Defines all possible backends. Can also include backends not available on the current target platform. + * @brief Defines an enumeration holding all possible backends. + * Can also include backends not available on the current target platform. */ +#ifndef PLSSVM_BACKEND_TYPES_HPP_ +#define PLSSVM_BACKEND_TYPES_HPP_ #pragma once -#include // forward declare std::ostream and std::istream -#include // std::vector +#include "plssvm/backends/SYCL/implementation_type.hpp" // plssvm::sycl::implementation_type +#include "plssvm/detail/type_traits.hpp" // plssvm::detail::remove_cvref_t +#include "plssvm/target_platforms.hpp" // plssvm::list_available_target_platforms + +#include // forward declare std::ostream and std::istream +#include // std::vector namespace plssvm { /** * @brief Enum class for all possible backend types. + * @note All different SYCL implementations have the same backend type "sycl". */ enum class backend_type { - /** The default backend dependent on the specified target platforms. */ + /** The default backend. Depends on the specified target platform. */ automatic, - /** [OpenMP](https://www.openmp.org/) to target CPUs only. */ + /** [OpenMP](https://www.openmp.org/) to target CPUs only (currently no OpenMP target offloading support). */ openmp, /** [CUDA](https://developer.nvidia.com/cuda-zone) to target NVIDIA GPUs only. */ cuda, /** [HIP](https://github.com/ROCm-Developer-Tools/HIP) to target AMD and NVIDIA GPUs. */ hip, - /** [OpenCL](https://www.khronos.org/opencl/) to target GPUs from different vendors and CPUs. */ + /** [OpenCL](https://www.khronos.org/opencl/) to target CPUs and GPUs from different vendors. */ opencl, - /** [SYCL](https://www.khronos.org/sycl/) to target GPUs from different vendors and CPUs. Currently tested SYCL implementations are [DPC++](https://github.com/intel/llvm) and [hipSYCL](https://github.com/illuhad/hipSYCL). */ + /** [SYCL](https://www.khronos.org/sycl/) to target CPUs and GPUs from different vendors. Currently tested SYCL implementations are [DPC++](https://github.com/intel/llvm) and [hipSYCL](https://github.com/illuhad/hipSYCL). */ sycl }; /** * @brief Return a list of all currently available backends. * @details Only backends that where found during the CMake configuration are available. - * @return the available backends (`[[nodiscard]]`) + * @return a list of the available backends (`[[nodiscard]]`) */ [[nodiscard]] std::vector list_available_backends(); /** - * @brief Returns the default backend used given the specified target platforms during the CMake configuration. - * @return the default backend (`[[nodiscard]]`) + * @brief Returns the default backend (if plssvm::backend_type::automatic is used) given the backend and target platform lists. + * @param[in] available_backends list of backends; if no backends are provided, queries all available backends + * @param[in] available_target_platforms list of target platforms; if no target platforms are provided, queries all available target platforms + * @return the default backend given the backend and target platform lists (`[[nodiscard]]`) */ -[[nodiscard]] backend_type determine_default_backend(); +[[nodiscard]] backend_type determine_default_backend(const std::vector &available_backends = list_available_backends(), + const std::vector &available_target_platforms = list_available_target_platforms()); /** * @brief Output the @p backend to the given output-stream @p out. @@ -63,4 +74,98 @@ std::ostream &operator<<(std::ostream &out, backend_type backend); */ std::istream &operator>>(std::istream &in, backend_type &backend); +/// @cond Doxygen_suppress +// clang-format off +// Forward declare all possible C-SVMs. +namespace openmp { class csvm; } +namespace cuda { class csvm; } +namespace hip { class csvm; } +namespace opencl { class csvm; } +namespace hipsycl { class csvm; } +namespace dpcpp { class csvm; } +// clang-format on + +namespace detail { + +/** + * @brief No `value` member variable if anything other than a C-SVM has been provided. + */ +template +struct csvm_to_backend_type {}; + +/** + * @brief Sets the `value` to `plssvm::backend_type::openmp` for the OpenMP C-SVM. + */ +template <> +struct csvm_to_backend_type { + /// The enum value representing the OpenMP backend. + static constexpr backend_type value = backend_type::openmp; +}; +/** + * @brief Sets the `value` to `plssvm::backend_type::cuda` for the CUDA C-SVM. + */ +template <> +struct csvm_to_backend_type { + /// The enum value representing the CUDA backend. + static constexpr backend_type value = backend_type::cuda; +}; +/** + * @brief Sets the `value` to `plssvm::backend_type::hip` for the HIP C-SVM. + */ +template <> +struct csvm_to_backend_type { + /// The enum value representing the HIP backend. + static constexpr backend_type value = backend_type::hip; +}; +/** + * @brief Sets the `value` to `plssvm::backend_type::opencl` for the OpenCL C-SVM. + */ +template <> +struct csvm_to_backend_type { + /// The enum value representing the OpenCL backend. + static constexpr backend_type value = backend_type::opencl; +}; +/** + * @brief Sets the `value` to `plssvm::backend_type::sycl` for the SYCL C-SVM using hipSYCL as SYCL implementation. + * @details Also sets a member variable `impl` to the value `plssvm::sycl::implementation_type::hipsycl` (only present for SYCL backends!). + */ +template <> +struct csvm_to_backend_type { + /// The enum value representing the SYCL (hipSYCL) backend. + static constexpr backend_type value = backend_type::sycl; + /// The enum value representing the SYCL implementation for the (hipSYCL) SYCL backend. + static constexpr sycl::implementation_type impl = sycl::implementation_type::hipsycl; +}; +/** + * @brief Sets the `value` to `plssvm::backend_type::sycl` for the SYCL C-SVM using DPC++ as SYCL implementation. + * @details Also sets a member variable `impl` to the value `plssvm::sycl::implementation_type::dpcpp` (only present for SYCL backends!). + */ +template <> +struct csvm_to_backend_type { + /// The enum value representing the SYCL (DPC++) backend. + static constexpr backend_type value = backend_type::sycl; + /// The enum value representing the SYCL implementation for the (DPC++) SYCL backend. + static constexpr sycl::implementation_type impl = sycl::implementation_type::dpcpp; +}; + +} // namespace detail +/// @endcond + +/** + * @brief Get the plssvm::backend_type of the C-SVM class of type @p T. Ignores all top-level const, volatile, and reference qualifiers. + * @details Provides a member variable `value` if @p T is a valid C-SVM. + * If @p T is a SYCL C-SVM, an additional member variable `impl` is provided showing the used SYCL implementation. + * @tparam T the type of the C-SVM to get the backend type from + */ +template +struct csvm_to_backend_type : detail::csvm_to_backend_type> {}; +/** + * @copydoc plssvm::csvm_to_backend_type + * @details A shorthand for `plssvm::csvm_to_backend_type::value`. + */ +template +constexpr backend_type csvm_to_backend_type_v = csvm_to_backend_type::value; + } // namespace plssvm + +#endif // PLSSVM_BACKEND_TYPES_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/csvm.hpp b/include/plssvm/backends/CUDA/csvm.hpp index af30046eb..8e95cd873 100644 --- a/include/plssvm/backends/CUDA/csvm.hpp +++ b/include/plssvm/backends/CUDA/csvm.hpp @@ -9,19 +9,21 @@ * @brief Defines a C-SVM using the CUDA backend. */ +#ifndef PLSSVM_BACKENDS_CUDA_CSVM_HPP_ +#define PLSSVM_BACKENDS_CUDA_CSVM_HPP_ #pragma once #include "plssvm/backends/CUDA/detail/device_ptr.cuh" // plssvm::cuda::detail::device_ptr #include "plssvm/backends/gpu_csvm.hpp" // plssvm::detail::gpu_csvm +#include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform -#include // std::size_t +#include // std::size_t +#include // std::true_type +#include // std::forward namespace plssvm { -// forward declare parameter class -template -class parameter; - namespace detail { // forward declare execution_range class @@ -33,50 +35,81 @@ namespace cuda { /** * @brief A C-SVM implementation using CUDA as backend. - * @tparam T the type of the data */ -template -class csvm : public ::plssvm::detail::gpu_csvm, int> { +class csvm : public ::plssvm::detail::gpu_csvm { protected: // protected for the test mock class /// The template base type of the CUDA C-SVM class. - using base_type = ::plssvm::detail::gpu_csvm, int>; - - using base_type::coef0_; - using base_type::cost_; - using base_type::degree_; - using base_type::gamma_; - using base_type::kernel_; - using base_type::num_data_points_; - using base_type::num_features_; - using base_type::print_info_; - using base_type::QA_cost_; - using base_type::target_; - - using base_type::data_d_; - using base_type::data_last_d_; - using base_type::devices_; - using base_type::num_cols_; - using base_type::num_rows_; + using base_type = ::plssvm::detail::gpu_csvm; - using base_type::boundary_size_; - using base_type::dept_; + using base_type::devices_; public: - using typename base_type::real_type; - using typename base_type::device_ptr_type; + using base_type::device_ptr_type; using typename base_type::queue_type; /** * @brief Construct a new C-SVM using the CUDA backend with the parameters given through @p params. * @param[in] params struct encapsulating all possible parameters - * @throws plssvm::csvm::csvm() exceptions + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::cuda::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_nvidia + * @throws plssvm::cuda::backend_exception if the plssvm::target_platform::gpu_nvidia target isn't available + * @throws plssvm::cuda::backend_exception if no CUDA capable devices could be found + */ + explicit csvm(parameter params = {}); + /** + * @brief Construct a new C-SVM using the CUDA backend on the @p target platform with the parameters given through @p params. + * @param[in] target the target platform used for this C-SVM + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::cuda::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_nvidia + * @throws plssvm::cuda::backend_exception if the plssvm::target_platform::gpu_nvidia target isn't available + * @throws plssvm::cuda::backend_exception if no CUDA capable devices could be found + */ + explicit csvm(target_platform target, parameter params = {}); + + /** + * @brief Construct a new C-SVM using the CUDA backend and the optionally provided @p named_args. + * @param[in] named_args the additional optional named arguments + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::cuda::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_nvidia + * @throws plssvm::cuda::backend_exception if the plssvm::target_platform::gpu_nvidia target isn't available + * @throws plssvm::cuda::backend_exception if no CUDA capable devices could be found + */ + template )> + explicit csvm(Args &&...named_args) : + csvm{ plssvm::target_platform::automatic, std::forward(named_args)... } {} + /** + * @brief Construct a new C-SVM using the CUDA backend on the @p target platform and the optionally provided @p named_args. + * @param[in] target the target platform used for this C-SVM + * @param[in] named_args the additional optional named-parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor * @throws plssvm::cuda::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_nvidia * @throws plssvm::cuda::backend_exception if the plssvm::target_platform::gpu_nvidia target isn't available - * @throws plssvm::cuda::backend_exception if no CUDA devices could be found + * @throws plssvm::cuda::backend_exception if no CUDA capable devices could be found */ - explicit csvm(const parameter ¶ms); + template )> + explicit csvm(const target_platform target, Args &&...named_args) : + base_type{ std::forward(named_args)... } { + this->init(target); + } + /** + * @copydoc plssvm::csvm::csvm(const plssvm::csvm &) + */ + csvm(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::csvm(plssvm::csvm &&) noexcept + */ + csvm(csvm &&) noexcept = default; + /** + * @copydoc plssvm::csvm::operator=(const plssvm::csvm &) + */ + csvm &operator=(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::operator=(plssvm::csvm &&) noexcept + */ + csvm &operator=(csvm &&) noexcept = default; /** * @brief Wait for all operations on all CUDA devices to finish. * @details Terminates the program, if any exception is thrown. @@ -87,28 +120,84 @@ class csvm : public ::plssvm::detail::gpu_csvm ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + template + void run_q_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const; /** * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel */ - void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, real_type add, std::size_t num_features) final; + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, float QA_cost, float add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, double QA_cost, double add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + template + void run_svm_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, real_type QA_cost, real_type add, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } /** * @copydoc plssvm::detail::gpu_csvm::run_w_kernel */ - void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, std::size_t num_features) final; + template + void run_w_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const; /** * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel */ - void run_predict_kernel(const ::plssvm::detail::execution_range &range, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, std::size_t num_predict_points) final; -}; + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + template + void run_predict_kernel_impl(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const; -extern template class csvm; -extern template class csvm; + private: + /** + * @brief Initialize all important states related to the CUDA backend. + * @param[in] target the target platform to use + * @throws plssvm::cuda::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_nvidia + * @throws plssvm::cuda::backend_exception if the plssvm::target_platform::gpu_nvidia target isn't available + * @throws plssvm::cuda::backend_exception if no CUDA capable devices could be found + */ + void init(target_platform target); +}; } // namespace cuda + +namespace detail { + +/** + * @brief Sets the `value` to `true` since C-SVMs using the CUDA backend are available. + */ +template <> +struct csvm_backend_exists : std::true_type {}; + +} // namespace detail + } // namespace plssvm + +#endif // PLSSVM_BACKENDS_CUDA_CSVM_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/detail/atomics.cuh b/include/plssvm/backends/CUDA/detail/atomics.cuh index 2fc83f3bf..b538e2b3a 100644 --- a/include/plssvm/backends/CUDA/detail/atomics.cuh +++ b/include/plssvm/backends/CUDA/detail/atomics.cuh @@ -9,10 +9,10 @@ * @brief Defines an atomic add function for double precision floating point types for older CUDA architectures. */ +#ifndef PLSSVM_BACKENDS_CUDA_DETAIL_ATOMICS_HPP_ +#define PLSSVM_BACKENDS_CUDA_DETAIL_ATOMICS_HPP_ #pragma once -namespace plssvm::cuda::detail { - #if defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 600 /** * @brief Atomically add the double precision @p val to the value denoted by @p addr. @@ -32,4 +32,4 @@ __device__ __forceinline__ double atomicAdd(double *addr, const double val) { } #endif -} // namespace plssvm::cuda::detail \ No newline at end of file +#endif // PLSSVM_BACKENDS_CUDA_DETAIL_ATOMICS_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/detail/device_ptr.cuh b/include/plssvm/backends/CUDA/detail/device_ptr.cuh index 1e1408ed8..697f982c6 100644 --- a/include/plssvm/backends/CUDA/detail/device_ptr.cuh +++ b/include/plssvm/backends/CUDA/detail/device_ptr.cuh @@ -9,6 +9,8 @@ * @brief Small wrapper around a CUDA device pointer. */ +#ifndef PLSSVM_BACKENDS_CUDA_DETAIL_DEVICE_PTR_HPP_ +#define PLSSVM_BACKENDS_CUDA_DETAIL_DEVICE_PTR_HPP_ #pragma once #include "plssvm/backends/gpu_device_ptr.hpp" // plssvm::detail::gpu_device_ptr @@ -21,7 +23,7 @@ namespace plssvm::cuda::detail { */ template class device_ptr : public ::plssvm::detail::gpu_device_ptr { - /// The template base type of the OpenCL device_ptr class. + /// The template base type of the CUDA device_ptr class. using base_type = ::plssvm::detail::gpu_device_ptr; using base_type::data_; @@ -30,9 +32,10 @@ class device_ptr : public ::plssvm::detail::gpu_device_ptr { public: // Be able to use overloaded base class functions. - using base_type::memcpy_to_device; - using base_type::memcpy_to_host; using base_type::memset; + using base_type::fill; + using base_type::copy_to_device; + using base_type::copy_to_host; using typename base_type::const_host_pointer_type; using typename base_type::device_pointer_type; @@ -55,43 +58,49 @@ class device_ptr : public ::plssvm::detail::gpu_device_ptr { explicit device_ptr(size_type size, queue_type device = 0); /** - * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const gpu_device_ptr&) + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const plssvm::detail::gpu_device_ptr &) */ device_ptr(const device_ptr &) = delete; /** - * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(gpu_device_ptr&&) + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(plssvm::detail::gpu_device_ptr &&) */ device_ptr(device_ptr &&other) noexcept = default; /** - * @copydoc plssvm::detail::gpu_device_ptr::operator=(const gpu_device_ptr&) + * @copydoc plssvm::detail::gpu_device_ptr::operator=(const plssvm::detail::gpu_device_ptr &) */ device_ptr &operator=(const device_ptr &) = delete; /** - * @copydoc plssvm::detail::gpu_device_ptr::operator=(gpu_device_ptr&&) + * @copydoc plssvm::detail::gpu_device_ptr::operator=(plssvm::detail::gpu_device_ptr &&) */ device_ptr &operator=(device_ptr &&other) noexcept = default; /** * @copydoc plssvm::detail::gpu_device_ptr::~gpu_device_ptr() */ - ~device_ptr(); + ~device_ptr() override; /** * @copydoc plssvm::detail::gpu_device_ptr::memset(int, size_type, size_type) */ - void memset(int value, size_type pos, size_type count) override; + void memset(int pattern, size_type pos, size_type num_bytes) override; /** - * @copydoc plssvm::detail::gpu_device_ptr::memcpy_to_device(const_host_pointer_type, size_type, size_type) + * @copydoc plssvm::detail::gpu_device_ptr::fill(value_type, size_type, size_type) */ - void memcpy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; + void fill(value_type value, size_type pos, size_type count) override; /** - * @copydoc plssvm::detail::gpu_device_ptr::memcpy_to_host(host_pointer_type, size_type, size_type) const + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_device(const_host_pointer_type, size_type, size_type) */ - void memcpy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; + void copy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_host(host_pointer_type, size_type, size_type) const + */ + void copy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; }; extern template class device_ptr; extern template class device_ptr; -} // namespace plssvm::cuda::detail \ No newline at end of file +} // namespace plssvm::cuda::detail + +#endif // PLSSVM_BACKENDS_CUDA_DETAIL_DEVICE_PTR_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/detail/fill_kernel.cuh b/include/plssvm/backends/CUDA/detail/fill_kernel.cuh new file mode 100644 index 000000000..16413f682 --- /dev/null +++ b/include/plssvm/backends/CUDA/detail/fill_kernel.cuh @@ -0,0 +1,38 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines a CUDA function for filling a device pointer with a specific value. + */ + +#ifndef PLSSVM_BACKENDS_CUDA_DETAIL_FILL_KERNEL_HPP_ +#define PLSSVM_BACKENDS_CUDA_DETAIL_FILL_KERNEL_HPP_ +#pragma once + +namespace plssvm::cuda::detail { + +/** + * @brief Fill @p count values of the array @p data with the @p value starting at position @p pos. + * @tparam value_type the type of the array and fill value + * @tparam size_type the size type + * @param[out] data the array to fill + * @param[in] value the value to fill the array with + * @param[in] pos the position to start filling the array + * @param[in] count the number of values to fill + */ +template +__global__ void fill_array(value_type *data, const value_type value, const size_type pos, const size_type count) { + const unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x; + // fill the array + if (idx < count) { + data[pos + idx] = value; + } +} + +} // namespace plssvm::cuda::detail + +#endif // PLSSVM_BACKENDS_CUDA_DETAIL_FILL_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/detail/utility.cuh b/include/plssvm/backends/CUDA/detail/utility.cuh index 76a0262dc..ab7286657 100644 --- a/include/plssvm/backends/CUDA/detail/utility.cuh +++ b/include/plssvm/backends/CUDA/detail/utility.cuh @@ -9,6 +9,8 @@ * @brief Utility functions for the CUDA backend. */ +#ifndef PLSSVM_BACKENDS_CUDA_DETAIL_UTILITY_HPP_ +#define PLSSVM_BACKENDS_CUDA_DETAIL_UTILITY_HPP_ #pragma once /** @@ -21,7 +23,7 @@ namespace plssvm::cuda::detail { /** * @brief Check the CUDA error @p code. If @p code signals an error, throw a plssvm::cuda::backend_exception. - * @details The exception contains the error name and error string. + * @details The exception contains the following message: "CUDA assert 'CUDA_ERROR_NAME' (CUDA_ERROR_CODE): CUDA_ERROR_STRING". * @param[in] code the CUDA error code to check * @throws plssvm::cuda::backend_exception if the error code signals a failure */ @@ -34,8 +36,9 @@ void gpu_assert(cudaError_t code); [[nodiscard]] int get_device_count(); /** - * @brief Set the device @p device to the active CUDA device. + * @brief Set the @p device to the active CUDA device. * @param[in] device the now active device + * @throws plssvm::cuda::backend_exception if the given device ID is smaller than 0 or greater or equal than the available number of devices */ void set_device(int device); @@ -52,4 +55,6 @@ void peek_at_last_error(); */ void device_synchronize(int device); -} // namespace plssvm::cuda::detail \ No newline at end of file +} // namespace plssvm::cuda::detail + +#endif // PLSSVM_BACKENDS_CUDA_DETAIL_UTILITY_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/exceptions.hpp b/include/plssvm/backends/CUDA/exceptions.hpp index 510dafdfe..3f5d62171 100644 --- a/include/plssvm/backends/CUDA/exceptions.hpp +++ b/include/plssvm/backends/CUDA/exceptions.hpp @@ -9,12 +9,14 @@ * @brief Implements custom exception classes specific to the CUDA backend. */ +#ifndef PLSSVM_BACKENDS_CUDA_EXCEPTIONS_HPP_ +#define PLSSVM_BACKENDS_CUDA_EXCEPTIONS_HPP_ #pragma once #include "plssvm/exceptions/exceptions.hpp" // plssvm::exception #include "plssvm/exceptions/source_location.hpp" // plssvm::source_location -#include // std::string +#include // std::string namespace plssvm::cuda { @@ -31,4 +33,6 @@ class backend_exception : public exception { explicit backend_exception(const std::string &msg, source_location loc = source_location::current()); }; -} // namespace plssvm::cuda \ No newline at end of file +} // namespace plssvm::cuda + +#endif // PLSSVM_BACKENDS_CUDA_EXCEPTIONS_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/predict_kernel.cuh b/include/plssvm/backends/CUDA/predict_kernel.cuh index 894000d88..0063687d2 100644 --- a/include/plssvm/backends/CUDA/predict_kernel.cuh +++ b/include/plssvm/backends/CUDA/predict_kernel.cuh @@ -9,6 +9,8 @@ * @brief Defines the functions used for prediction for the C-SVM using the CUDA backend. */ +#ifndef PLSSVM_BACKENDS_CUDA_PREDICT_KERNEL_HPP_ +#define PLSSVM_BACKENDS_CUDA_PREDICT_KERNEL_HPP_ #pragma once #include "plssvm/constants.hpp" // plssvm::kernel_index_type @@ -33,7 +35,7 @@ __global__ void device_kernel_w_linear(real_type *w_d, const real_type *data_d, * @brief Predicts the labels for data points using the polynomial kernel function. * @details Currently only single GPU execution is supported. * @tparam real_type the type of the data - * @param[in] out_d the calculated predictions + * @param[out] out_d the calculated predictions * @param[in] data_d the one-dimension support vector matrix * @param[in] data_last_d the last row of the support vector matrix * @param[in] alpha_d the previously calculated weight for each data point @@ -46,13 +48,13 @@ __global__ void device_kernel_w_linear(real_type *w_d, const real_type *data_d, * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ template -__global__ void device_kernel_predict_poly(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0); +__global__ void device_kernel_predict_polynomial(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0); /** * @brief Predicts the labels for data points using the radial basis functions kernel function. * @details Currently only single GPU execution is supported. * @tparam real_type the type of the data - * @param[in] out_d the calculated predictions + * @param[out] out_d the calculated predictions * @param[in] data_d the one-dimension support vector matrix * @param[in] data_last_d the last row of the support vector matrix * @param[in] alpha_d the previously calculated weight for each data point @@ -63,6 +65,8 @@ __global__ void device_kernel_predict_poly(real_type *out_d, const real_type *da * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -__global__ void device_kernel_predict_radial(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma); +__global__ void device_kernel_predict_rbf(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma); -} // namespace plssvm::cuda \ No newline at end of file +} // namespace plssvm::cuda + +#endif // PLSSVM_BACKENDS_CUDA_PREDICT_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/q_kernel.cuh b/include/plssvm/backends/CUDA/q_kernel.cuh index 83ade808e..7a4cbdd27 100644 --- a/include/plssvm/backends/CUDA/q_kernel.cuh +++ b/include/plssvm/backends/CUDA/q_kernel.cuh @@ -9,6 +9,8 @@ * @brief Defines CUDA functions for generating the `q` vector. */ +#ifndef PLSSVM_BACKENDS_CUDA_Q_KERNEL_HPP_ +#define PLSSVM_BACKENDS_CUDA_Q_KERNEL_HPP_ #pragma once #include "plssvm/constants.hpp" // plssvm::kernel_index_type @@ -42,7 +44,7 @@ __global__ void device_kernel_q_linear(real_type *q, const real_type *data_d, co * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ template -__global__ void device_kernel_q_poly(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0); +__global__ void device_kernel_q_polynomial(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0); /** * @brief Calculates the `q` vector using the radial basis functions C-SVM kernel. @@ -56,6 +58,8 @@ __global__ void device_kernel_q_poly(real_type *q, const real_type *data_d, cons * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -__global__ void device_kernel_q_radial(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma); +__global__ void device_kernel_q_rbf(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma); -} // namespace plssvm::cuda \ No newline at end of file +} // namespace plssvm::cuda + +#endif // PLSSVM_BACKENDS_CUDA_Q_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/CUDA/svm_kernel.cuh b/include/plssvm/backends/CUDA/svm_kernel.cuh index 7935eac46..ed8eb7744 100644 --- a/include/plssvm/backends/CUDA/svm_kernel.cuh +++ b/include/plssvm/backends/CUDA/svm_kernel.cuh @@ -9,6 +9,8 @@ * @brief Defines the kernel functions for the C-SVM using the CUDA backend. */ +#ifndef PLSSVM_BACKENDS_CUDA_SVM_KERNEL_HPP_ +#define PLSSVM_BACKENDS_CUDA_SVM_KERNEL_HPP_ #pragma once #include "plssvm/constants.hpp" // plssvm::kernel_index_type @@ -51,7 +53,7 @@ __global__ void device_kernel_linear(const real_type *q, real_type *ret, const r * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ template -__global__ void device_kernel_poly(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0); +__global__ void device_kernel_polynomial(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0); /** * @brief Calculates the C-SVM kernel using the radial basis function kernel function. @@ -69,6 +71,8 @@ __global__ void device_kernel_poly(const real_type *q, real_type *ret, const rea * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -__global__ void device_kernel_radial(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma); +__global__ void device_kernel_rbf(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma); -} // namespace plssvm::cuda \ No newline at end of file +} // namespace plssvm::cuda + +#endif // PLSSVM_BACKENDS_CUDA_SVM_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/HIP/csvm.hpp b/include/plssvm/backends/HIP/csvm.hpp index 322e5c4b6..9f015e9aa 100644 --- a/include/plssvm/backends/HIP/csvm.hpp +++ b/include/plssvm/backends/HIP/csvm.hpp @@ -9,19 +9,21 @@ * @brief Defines a C-SVM using the HIP backend. */ +#ifndef PLSSVM_BACKENDS_HIP_CSVM_HPP_ +#define PLSSVM_BACKENDS_HIP_CSVM_HPP_ #pragma once #include "plssvm/backends/HIP/detail/device_ptr.hip.hpp" // plssvm::hip::detail::device_ptr #include "plssvm/backends/gpu_csvm.hpp" // plssvm::detail::gpu_csvm +#include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform -#include // std::size_t +#include // std::size_t +#include // std::true_type +#include // std::forward namespace plssvm { -// forward declare parameter class -template -class parameter; - namespace detail { // forward declare execution_range class @@ -33,50 +35,85 @@ namespace hip { /** * @brief A C-SVM implementation using HIP as backend. - * @tparam T the type of the data */ -template -class csvm : public ::plssvm::detail::gpu_csvm, int> { +class csvm : public ::plssvm::detail::gpu_csvm { protected: // protected for the test mock class /// The template base type of the HIP C-SVM class. - using base_type = ::plssvm::detail::gpu_csvm, int>; - - using base_type::coef0_; - using base_type::cost_; - using base_type::degree_; - using base_type::gamma_; - using base_type::kernel_; - using base_type::num_data_points_; - using base_type::num_features_; - using base_type::print_info_; - using base_type::QA_cost_; - using base_type::target_; - - using base_type::data_d_; - using base_type::data_last_d_; - using base_type::devices_; - using base_type::num_cols_; - using base_type::num_rows_; + using base_type = ::plssvm::detail::gpu_csvm; - using base_type::boundary_size_; - using base_type::dept_; + using base_type::devices_; public: - using typename base_type::real_type; - using typename base_type::device_ptr_type; + using base_type::device_ptr_type; using typename base_type::queue_type; /** * @brief Construct a new C-SVM using the HIP backend with the parameters given through @p params. + * @details Currently only tested with AMD GPUs. * @param[in] params struct encapsulating all possible parameters - * @throws plssvm::csvm::csvm() exceptions + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hip::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_amd + * @throws plssvm::hip::backend_exception if the plssvm::target_platform::gpu_amd target isn't available + * @throws plssvm::hip::backend_exception if no HIP capable devices could be found + */ + explicit csvm(parameter params = {}); + /** + * @brief Construct a new C-SVM using the HIP backend on the @p target platform with the parameters given through @p params. + * @details Currently only tested with AMD GPUs. + * @param[in] target the target platform used for this C-SVM + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hip::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_amd + * @throws plssvm::hip::backend_exception if the plssvm::target_platform::gpu_amd target isn't available + * @throws plssvm::hip::backend_exception if no HIP capable devices could be found + */ + explicit csvm(target_platform target, parameter params = {}); + + /** + * @brief Construct a new C-SVM using the HIP backend and the optionally provided @p named_args. + * @details Currently only tested with AMD GPUs. + * @param[in] named_args the additional optional named-parameter + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hip::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_amd + * @throws plssvm::hip::backend_exception if the plssvm::target_platform::gpu_amd target isn't available + * @throws plssvm::hip::backend_exception if no HIP capable devices could be found + */ + template )> + explicit csvm(Args &&...named_args) : + csvm{ plssvm::target_platform::automatic, std::forward(named_args)... } {} + /** + * @brief Construct a new C-SVM using the HIP backend on the @p target platform and the optionally provided @p named_args. + * @details Currently only tested with AMD GPUs. + * @param[in] target the target platform used for this C-SVM + * @param[in] named_args the additional optional named-parameter + * @throws plssvm::exception all exceptions thrown in the base class constructor * @throws plssvm::hip::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_amd * @throws plssvm::hip::backend_exception if the plssvm::target_platform::gpu_amd target isn't available - * @throws plssvm::hip::backend_exception if no HIP devices could be found + * @throws plssvm::hip::backend_exception if no HIP capable devices could be found */ - explicit csvm(const parameter ¶ms); + template )> + explicit csvm(const target_platform target, Args &&...named_args) : + base_type{ std::forward(named_args)... } { + this->init(target); + } + /** + * @copydoc plssvm::csvm::csvm(const plssvm::csvm &) + */ + csvm(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::csvm(plssvm::csvm &&) noexcept + */ + csvm(csvm &&) noexcept = default; + /** + * @copydoc plssvm::csvm::operator=(const plssvm::csvm &) + */ + csvm &operator=(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::operator=(plssvm::csvm &&) noexcept + */ + csvm &operator=(csvm &&) noexcept = default; /** * @brief Wait for all operations on all HIP devices to finish. * @details Terminates the program, if any exception is thrown. @@ -87,28 +124,84 @@ class csvm : public ::plssvm::detail::gpu_csvm ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + template + void run_q_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const; /** * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel */ - void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, real_type add, std::size_t num_features) final; + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, float QA_cost, float add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, double QA_cost, double add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + template + void run_svm_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, real_type QA_cost, real_type add, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } /** * @copydoc plssvm::detail::gpu_csvm::run_w_kernel */ - void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, std::size_t num_features) final; + template + void run_w_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const; /** * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel */ - void run_predict_kernel(const ::plssvm::detail::execution_range &range, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, std::size_t num_predict_points) final; -}; + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + template + void run_predict_kernel_impl(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const; -extern template class csvm; -extern template class csvm; + private: + /** + * @brief Initialize all important states related to the HIP backend. + * @param[in] target the target platform to use + * @throws plssvm::hip::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::gpu_amd + * @throws plssvm::hip::backend_exception if the plssvm::target_platform::gpu_amd target isn't available + * @throws plssvm::hip::backend_exception if no HIP capable devices could be found + */ + void init(target_platform target); +}; } // namespace hip + +namespace detail { + +/** + * @brief Sets the `value` to `true` since C-SVMs using the HIP backend are available. + */ +template <> +struct csvm_backend_exists : std::true_type {}; + +} // namespace detail + } // namespace plssvm + +#endif // PLSSVM_BACKENDS_HIP_CSVM_HPP_ diff --git a/include/plssvm/backends/HIP/detail/device_ptr.hip.hpp b/include/plssvm/backends/HIP/detail/device_ptr.hip.hpp index e2c23de3e..98e56f7fd 100644 --- a/include/plssvm/backends/HIP/detail/device_ptr.hip.hpp +++ b/include/plssvm/backends/HIP/detail/device_ptr.hip.hpp @@ -9,6 +9,8 @@ * @brief Small wrapper around a HIP device pointer. */ +#ifndef PLSSVM_BACKENDS_HIP_DETAIL_DEVICE_PTR_HPP_ +#define PLSSVM_BACKENDS_HIP_DETAIL_DEVICE_PTR_HPP_ #pragma once #include "plssvm/backends/gpu_device_ptr.hpp" // plssvm::detail::gpu_device_ptr @@ -21,7 +23,7 @@ namespace plssvm::hip::detail { */ template class device_ptr : public ::plssvm::detail::gpu_device_ptr { - /// The template base type of the OpenCL device_ptr class. + /// The template base type of the HIP device_ptr class. using base_type = ::plssvm::detail::gpu_device_ptr; using base_type::data_; @@ -30,9 +32,10 @@ class device_ptr : public ::plssvm::detail::gpu_device_ptr { public: // Be able to use overloaded base class functions. - using base_type::memcpy_to_device; - using base_type::memcpy_to_host; using base_type::memset; + using base_type::fill; + using base_type::copy_to_device; + using base_type::copy_to_host; using typename base_type::const_host_pointer_type; using typename base_type::device_pointer_type; @@ -55,43 +58,49 @@ class device_ptr : public ::plssvm::detail::gpu_device_ptr { explicit device_ptr(size_type size, queue_type device = 0); /** - * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const gpu_device_ptr&) + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const plssvm::detail::gpu_device_ptr &) */ device_ptr(const device_ptr &) = delete; /** - * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(gpu_device_ptr&&) + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(plssvm::detail::gpu_device_ptr &&) */ device_ptr(device_ptr &&other) noexcept = default; /** - * @copydoc plssvm::detail::gpu_device_ptr::operator=(const gpu_device_ptr&) + * @copydoc plssvm::detail::gpu_device_ptr::operator=(const plssvm::detail::gpu_device_ptr &) */ device_ptr &operator=(const device_ptr &) = delete; /** - * @copydoc plssvm::detail::gpu_device_ptr::operator=(gpu_device_ptr&&) + * @copydoc plssvm::detail::gpu_device_ptr::operator=(plssvm::detail::gpu_device_ptr &&) */ device_ptr &operator=(device_ptr &&other) noexcept = default; /** * @copydoc plssvm::detail::gpu_device_ptr::~gpu_device_ptr() */ - ~device_ptr(); + ~device_ptr() override; /** * @copydoc plssvm::detail::gpu_device_ptr::memset(int, size_type, size_type) */ - void memset(int value, size_type pos, size_type count) override; + void memset(int pattern, size_type pos, size_type num_bytes) override; /** - * @copydoc plssvm::detail::gpu_device_ptr::memcpy_to_device(const_host_pointer_type, size_type, size_type) + * @copydoc plssvm::detail::gpu_device_ptr::fill(value_type, size_type, size_type) */ - void memcpy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; + void fill(value_type value, size_type pos, size_type count) override; /** - * @copydoc plssvm::detail::gpu_device_ptr::memcpy_to_host(host_pointer_type, size_type, size_type) const + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_device(const_host_pointer_type, size_type, size_type) */ - void memcpy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; + void copy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_host(host_pointer_type, size_type, size_type) const + */ + void copy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; }; extern template class device_ptr; extern template class device_ptr; } // namespace plssvm::hip::detail + +#endif // PLSSVM_BACKENDS_HIP_DETAIL_DEVICE_PTR_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/HIP/detail/fill_kernel.hip.hpp b/include/plssvm/backends/HIP/detail/fill_kernel.hip.hpp new file mode 100644 index 000000000..8525beb07 --- /dev/null +++ b/include/plssvm/backends/HIP/detail/fill_kernel.hip.hpp @@ -0,0 +1,41 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines a HIP function for filling a device pointer with a specific value. + */ + +#ifndef PLSSVM_BACKENDS_HIP_DETAIL_FILL_KERNEL_HPP_ +#define PLSSVM_BACKENDS_HIP_DETAIL_FILL_KERNEL_HPP_ +#pragma once + +#include "hip/hip_runtime.h" // HIP runtime functions +#include "hip/hip_runtime_api.h" // HIP runtime functions + +namespace plssvm::hip::detail { + +/** + * @brief Fill the array @p data with @p count values @p value starting at position @p pos. + * @tparam value_type the type of the array + * @tparam size_type the unsigned size_type + * @param[in,out] data the array to fill + * @param[in] value the value to fill the array with + * @param[in] pos the position at which to start te filling + * @param[in] count the number of values to fill in the array starting at @p pos + */ +template +__global__ void fill_array(value_type *data, value_type value, size_type pos, size_type count) { + const unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x; + // fill the array + if (idx < count) { + data[pos + idx] = value; + } +} + +} // namespace plssvm::hip::detail + +#endif // PLSSVM_BACKENDS_HIP_DETAIL_FILL_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/HIP/detail/utility.hip.hpp b/include/plssvm/backends/HIP/detail/utility.hip.hpp index 8b57c245f..47d5f054c 100644 --- a/include/plssvm/backends/HIP/detail/utility.hip.hpp +++ b/include/plssvm/backends/HIP/detail/utility.hip.hpp @@ -9,6 +9,8 @@ * @brief Utility functions for the HIP backend. */ +#ifndef PLSSVM_BACKENDS_HIP_DETAIL_UTILITY_HPP_ +#define PLSSVM_BACKENDS_HIP_DETAIL_UTILITY_HPP_ #pragma once #include "hip/hip_runtime_api.h" // hipError_t @@ -23,7 +25,7 @@ namespace plssvm::hip::detail { /** * @brief Check the HIP error @p code. If @p code signals an error, throw a plssvm::hip::backend_exception. - * @details The exception contains the error name and error string. + * @details The exception contains the following message: "HIP assert 'HIP_ERROR_NAME' (HIP_ERROR_CODE): HIP_ERROR_STRING". * @param[in] code the HIP error code to check * @throws plssvm::hip::backend_exception if the error code signals a failure */ @@ -36,8 +38,9 @@ void gpu_assert(hipError_t code); [[nodiscard]] int get_device_count(); /** - * @brief Set the device @p device to the active HIP device. + * @brief Set the @p device to the active HIP device. * @param[in] device the now active device + * @throws plssvm::hip::backend_exception if the given device ID is smaller than 0 or greater or equal than the available number of devices */ void set_device(int device); @@ -55,3 +58,5 @@ void peek_at_last_error(); void device_synchronize(int device); } // namespace plssvm::hip::detail + +#endif // PLSSVM_BACKENDS_HIP_DETAIL_UTILITY_HPP_ diff --git a/include/plssvm/backends/HIP/exceptions.hpp b/include/plssvm/backends/HIP/exceptions.hpp index 0557fb405..7e95f3663 100644 --- a/include/plssvm/backends/HIP/exceptions.hpp +++ b/include/plssvm/backends/HIP/exceptions.hpp @@ -9,12 +9,14 @@ * @brief Implements custom exception classes specific to the HIP backend. */ +#ifndef PLSSVM_BACKENDS_HIP_EXCEPTIONS_HPP_ +#define PLSSVM_BACKENDS_HIP_EXCEPTIONS_HPP_ #pragma once #include "plssvm/exceptions/exceptions.hpp" // plssvm::exception #include "plssvm/exceptions/source_location.hpp" // plssvm::source_location -#include // std::string +#include // std::string namespace plssvm::hip { @@ -32,3 +34,5 @@ class backend_exception : public exception { }; } // namespace plssvm::hip + +#endif // PLSSVM_BACKENDS_HIP_EXCEPTIONS_HPP_ diff --git a/include/plssvm/backends/HIP/predict_kernel.hip.hpp b/include/plssvm/backends/HIP/predict_kernel.hip.hpp index 017925d7c..4e052e3b3 100644 --- a/include/plssvm/backends/HIP/predict_kernel.hip.hpp +++ b/include/plssvm/backends/HIP/predict_kernel.hip.hpp @@ -9,6 +9,8 @@ * @brief Defines the functions used for prediction for the C-SVM using the HIP backend. */ +#ifndef PLSSVM_BACKENDS_HIP_PREDICT_KERNEL_HPP_ +#define PLSSVM_BACKENDS_HIP_PREDICT_KERNEL_HPP_ #pragma once #include "hip/hip_runtime.h" @@ -59,14 +61,14 @@ __global__ void device_kernel_w_linear(real_type *w_d, const real_type *data_d, * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ template -__global__ void device_kernel_predict_poly(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0) { +__global__ void device_kernel_predict_polynomial(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0) { const kernel_index_type data_point_index = blockIdx.x * blockDim.x + threadIdx.x; const kernel_index_type predict_point_index = blockIdx.y * blockDim.y + threadIdx.y; real_type temp{ 0.0 }; if (predict_point_index < num_predict_points) { for (kernel_index_type feature_index = 0; feature_index < num_features; ++feature_index) { - if (data_point_index == num_data_points) { + if (data_point_index == num_data_points - 1) { temp += data_last_d[feature_index] * points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]; } else { temp += data_d[data_point_index + (num_data_points - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] * points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]; @@ -94,14 +96,14 @@ __global__ void device_kernel_predict_poly(real_type *out_d, const real_type *da * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -__global__ void device_kernel_predict_radial(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma) { +__global__ void device_kernel_predict_rbf(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma) { const kernel_index_type data_point_index = blockIdx.x * blockDim.x + threadIdx.x; const kernel_index_type predict_point_index = blockIdx.y * blockDim.y + threadIdx.y; real_type temp{ 0.0 }; if (predict_point_index < num_predict_points) { for (kernel_index_type feature_index = 0; feature_index < num_features; ++feature_index) { - if (data_point_index == num_data_points) { + if (data_point_index == num_data_points - 1) { temp += (data_last_d[feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]) * (data_last_d[feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]); } else { temp += (data_d[data_point_index + (num_data_points - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]) * (data_d[data_point_index + (num_data_points - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]); @@ -115,3 +117,5 @@ __global__ void device_kernel_predict_radial(real_type *out_d, const real_type * } } // namespace plssvm::hip + +#endif // PLSSVM_BACKENDS_HIP_PREDICT_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/HIP/q_kernel.hip.hpp b/include/plssvm/backends/HIP/q_kernel.hip.hpp index fd801bdb5..f16a49f65 100644 --- a/include/plssvm/backends/HIP/q_kernel.hip.hpp +++ b/include/plssvm/backends/HIP/q_kernel.hip.hpp @@ -9,6 +9,8 @@ * @brief Defines HIP functions for generating the `q` vector. */ +#ifndef PLSSVM_BACKENDS_HIP_Q_KERNEL_HPP_ +#define PLSSVM_BACKENDS_HIP_Q_KERNEL_HPP_ #pragma once #include "hip/hip_runtime.h" @@ -52,7 +54,7 @@ __global__ void device_kernel_q_linear(real_type *q, const real_type *data_d, co * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ template -__global__ void device_kernel_q_poly(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0) { +__global__ void device_kernel_q_polynomial(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0) { const kernel_index_type index = blockIdx.x * blockDim.x + threadIdx.x; real_type temp{ 0.0 }; for (kernel_index_type i = 0; i < num_cols; ++i) { @@ -73,7 +75,7 @@ __global__ void device_kernel_q_poly(real_type *q, const real_type *data_d, cons * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -__global__ void device_kernel_q_radial(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma) { +__global__ void device_kernel_q_rbf(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma) { const kernel_index_type index = blockIdx.x * blockDim.x + threadIdx.x; real_type temp{ 0.0 }; for (kernel_index_type i = 0; i < num_cols; ++i) { @@ -83,3 +85,5 @@ __global__ void device_kernel_q_radial(real_type *q, const real_type *data_d, co } } // namespace plssvm::hip + +#endif // PLSSVM_BACKENDS_HIP_Q_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/HIP/svm_kernel.hip.hpp b/include/plssvm/backends/HIP/svm_kernel.hip.hpp index 9f3fc00eb..ab5390e8b 100644 --- a/include/plssvm/backends/HIP/svm_kernel.hip.hpp +++ b/include/plssvm/backends/HIP/svm_kernel.hip.hpp @@ -9,6 +9,8 @@ * @brief Defines the kernel functions for the C-SVM using the HIP backend. */ +#ifndef PLSSVM_BACKENDS_HIP_SVM_KERNEL_HPP_ +#define PLSSVM_BACKENDS_HIP_SVM_KERNEL_HPP_ #pragma once #include "hip/hip_runtime.h" @@ -125,7 +127,7 @@ __global__ void device_kernel_linear(const real_type *q, real_type *ret, const r * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ template -__global__ void device_kernel_poly(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) { +__global__ void device_kernel_polynomial(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) { kernel_index_type i = blockIdx.x * blockDim.x * INTERNAL_BLOCK_SIZE; kernel_index_type j = blockIdx.y * blockDim.y * INTERNAL_BLOCK_SIZE; @@ -204,7 +206,7 @@ __global__ void device_kernel_poly(const real_type *q, real_type *ret, const rea * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -__global__ void device_kernel_radial(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) { +__global__ void device_kernel_rbf(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) { kernel_index_type i = blockIdx.x * blockDim.x * INTERNAL_BLOCK_SIZE; kernel_index_type j = blockIdx.y * blockDim.y * INTERNAL_BLOCK_SIZE; @@ -268,3 +270,5 @@ __global__ void device_kernel_radial(const real_type *q, real_type *ret, const r } } // namespace plssvm::hip + +#endif // PLSSVM_BACKENDS_HIP_SVM_KERNEL_HPP_ diff --git a/include/plssvm/backends/OpenCL/csvm.hpp b/include/plssvm/backends/OpenCL/csvm.hpp index 149bfaf0e..65a3fafb9 100644 --- a/include/plssvm/backends/OpenCL/csvm.hpp +++ b/include/plssvm/backends/OpenCL/csvm.hpp @@ -9,22 +9,24 @@ * @brief Defines a C-SVM using the OpenCL backend. */ +#ifndef PLSSVM_BACKENDS_OPENCL_CSVM_HPP_ +#define PLSSVM_BACKENDS_OPENCL_CSVM_HPP_ #pragma once #include "plssvm/backends/OpenCL/detail/command_queue.hpp" // plssvm::opencl::detail::command_queue #include "plssvm/backends/OpenCL/detail/context.hpp" // plssvm::opencl::detail::context #include "plssvm/backends/OpenCL/detail/device_ptr.hpp" // plssvm::opencl::detail::device_ptr #include "plssvm/backends/gpu_csvm.hpp" // plssvm::detail::gpu_csvm +#include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform -#include // std::size_t -#include // std::vector +#include // std::size_t +#include // std::true_type +#include // std::forward +#include // std::vector namespace plssvm { -// forward declare parameter class -template -class parameter; - namespace detail { // forward declare execution_range class @@ -36,48 +38,83 @@ namespace opencl { /** * @brief A C-SVM implementation using OpenCL as backend. - * @tparam T the type of the data */ -template -class csvm : public ::plssvm::detail::gpu_csvm, ::plssvm::opencl::detail::command_queue> { +class csvm : public ::plssvm::detail::gpu_csvm { protected: // protected for test MOCK class /// The template base type of the OpenCL C-SVM class. - using base_type = ::plssvm::detail::gpu_csvm, ::plssvm::opencl::detail::command_queue>; - - using base_type::coef0_; - using base_type::cost_; - using base_type::degree_; - using base_type::gamma_; - using base_type::kernel_; - using base_type::num_data_points_; - using base_type::num_features_; - using base_type::print_info_; - using base_type::QA_cost_; - using base_type::target_; - - using base_type::data_d_; - using base_type::data_last_d_; + using base_type = ::plssvm::detail::gpu_csvm; + using base_type::devices_; - using base_type::num_cols_; - using base_type::num_rows_; public: - using typename base_type::real_type; - using typename base_type::device_ptr_type; + using base_type::device_ptr_type; using typename base_type::queue_type; /** * @brief Construct a new C-SVM using the OpenCL backend with the parameters given through @p params. * @param[in] params struct encapsulating all possible parameters - * @throws plssvm::csvm::csvm() exceptions - * @throws plssvm::opencl::backend_exception if the requested plssvm::target_platform isn't available - * @throws plssvm::opencl::backend_exception if no possible OpenCL devices could be found + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::opencl::backend_exception if the requested target is not available + * @throws plssvm::opencl::backend_exception if more than one OpenCL context for the requested target was found + * @throws plssvm::opencl::backend_exception if no device for the requested target was found + */ + explicit csvm(parameter params = {}); + /** + * @brief Construct a new C-SVM using the OpenCL backend on the @p target platform with the parameters given through @p params. + * @param[in] target the target platform used for this C-SVM + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::opencl::backend_exception if the requested target is not available + * @throws plssvm::opencl::backend_exception if more than one OpenCL context for the requested target was found + * @throws plssvm::opencl::backend_exception if no device for the requested target was found + */ + explicit csvm(target_platform target, parameter params = {}); + + /** + * @brief Construct a new C-SVM using the OpenCL backend and the optionally provided @p named_args. + * @param[in] named_args the additional optional named-parameter + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::opencl::backend_exception if the requested target is not available + * @throws plssvm::opencl::backend_exception if more than one OpenCL context for the requested target was found + * @throws plssvm::opencl::backend_exception if no device for the requested target was found */ - explicit csvm(const parameter ¶ms); + template )> + explicit csvm(Args &&...named_args) : + csvm{ plssvm::target_platform::automatic, std::forward(named_args)... } {} + /** + * @brief Construct a new C-SVM using the OpenCL backend on the @p target platform and the optionally provided @p named_args. + * @param[in] target the target platform used for this C-SVM + * @param[in] named_args the additional optional named-parameter + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::opencl::backend_exception if the requested target is not available + * @throws plssvm::opencl::backend_exception if more than one OpenCL context for the requested target was found + * @throws plssvm::opencl::backend_exception if no device for the requested target was found + */ + template )> + explicit csvm(const target_platform target, Args &&...named_args) : + base_type{ std::forward(named_args)... } { + this->init(target); + } /** - * @brief Wait for all operations on all devices to finish. + * @copydoc plssvm::csvm::csvm(const plssvm::csvm &) + */ + csvm(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::csvm(plssvm::csvm &&) noexcept + */ + csvm(csvm &&) noexcept = default; + /** + * @copydoc plssvm::csvm::operator=(const plssvm::csvm &) + */ + csvm &operator=(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::operator=(plssvm::csvm &&) noexcept + */ + csvm &operator=(csvm &&) noexcept = default; + /** + * @brief Wait for all operations on all OpenCL devices to finish. * @details Terminates the program, if any exception is thrown. */ ~csvm() override; @@ -86,31 +123,87 @@ class csvm : public ::plssvm::detail::gpu_csvm ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + template + void run_q_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, float QA_cost, float add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, double QA_cost, double add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } /** * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel */ - void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, real_type add, std::size_t num_features) final; + template + void run_svm_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, real_type QA_cost, real_type add, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } /** * @copydoc plssvm::detail::gpu_csvm::run_w_kernel */ - void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, std::size_t num_features) final; + template + void run_w_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } /** * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel */ - void run_predict_kernel(const ::plssvm::detail::execution_range &range, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, std::size_t num_predict_points) final; + template + void run_predict_kernel_impl(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const; /// The available OpenCL contexts for the current target platform with the associated devices. std::vector contexts_{}; -}; -extern template class csvm; -extern template class csvm; + private: + /** + * @brief Initialize all important states related to the OpenCL backend. + * @param[in] target the target platform to use + * @throws plssvm::opencl::backend_exception if the requested target is not available + * @throws plssvm::opencl::backend_exception if more than one OpenCL context for the requested target was found + * @throws plssvm::opencl::backend_exception if no device for the requested target was found + */ + void init(target_platform target); +}; } // namespace opencl -} // namespace plssvm \ No newline at end of file + +namespace detail { + +/** + * @brief Sets the `value` to `true` since C-SVMs using the OpenCL backend are available. + */ +template <> +struct csvm_backend_exists : std::true_type {}; + +} // namespace detail + +} // namespace plssvm + +#endif // PLSSVM_BACKENDS_OPENCL_CSVM_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/atomics.cl b/include/plssvm/backends/OpenCL/detail/atomics.cl index 95afd63de..5185d44f5 100644 --- a/include/plssvm/backends/OpenCL/detail/atomics.cl +++ b/include/plssvm/backends/OpenCL/detail/atomics.cl @@ -14,6 +14,7 @@ /** * @brief Implementation of an atomic add function for double-precision floating point types. + * @details Uses the atomic compare-exchange idiom. * @param[in,out] addr the source value to add @p val to * @param[in] val the value to add to @p addr */ @@ -34,6 +35,7 @@ inline void __attribute__((overloadable)) atomicAdd(__global const double *addr, /** * @brief Implementation of an atomic add function for single-precision floating point types. + * @details Uses the atomic compare-exchange idiom. * @param[in,out] addr the source value to add @p val to * @param[in] val the value to add to @p addr */ diff --git a/include/plssvm/backends/OpenCL/detail/command_queue.hpp b/include/plssvm/backends/OpenCL/detail/command_queue.hpp index fd94c4a36..351454854 100644 --- a/include/plssvm/backends/OpenCL/detail/command_queue.hpp +++ b/include/plssvm/backends/OpenCL/detail/command_queue.hpp @@ -9,13 +9,15 @@ * @brief Defines a very small RAII wrapper around a cl_command_queue including information about its associated OpenCL context and device. */ +#ifndef PLSSVM_BACKENDS_OPENCL_DETAIL_COMMAND_QUEUE_HPP_ +#define PLSSVM_BACKENDS_OPENCL_DETAIL_COMMAND_QUEUE_HPP_ #pragma once #include "plssvm/backends/OpenCL/detail/kernel.hpp" // plssvm::opencl::detail::kernel -#include "CL/cl.h" // cl_context, cl_command_queue, cl_device_id +#include "CL/cl.h" // cl_context, cl_command_queue, cl_device_id -#include // std::map +#include // std::map namespace plssvm::opencl::detail { @@ -54,7 +56,7 @@ class command_queue { * @param[in,out] other the command_queue to move the resources from * @return `*this` */ - command_queue &operator=(command_queue &&other); + command_queue &operator=(command_queue &&other) noexcept; /** * @brief Release the cl_command_queue resources on destruction. @@ -74,15 +76,31 @@ class command_queue { /** * @brief Add a new OpenCL @p compute_kernel used for @p name to this command queue. + * @tparam real_type the floating point type used as type in the kernel (either `float` or `double`) * @param[in] name the name of the kernel that is to be added * @param[in] compute_kernel the kernel to add */ + template void add_kernel(compute_kernel_name name, kernel &&compute_kernel); + /** + * @brief Get the OpenCL kernel used for @p name. + * @tparam real_type the floating point type used as type in the kernel (either `float` or `double`) + * @param[in] name the name of the kernel + * @throws std::out_of_range if a kernel with @p name is requested that has not been compiled for this command queue + * @return the compiled kernel (`[[nodiscard]]`) + */ + template + [[nodiscard]] const kernel &get_kernel(compute_kernel_name name) const; + /// The wrapped cl_command_queue. cl_command_queue queue{}; - /// All OpenCL device kernel associated with the device corresponding to this command queue. - std::map kernels{}; + /// All OpenCL device kernel associated with the device corresponding to this command queue using `float` as `real_type`. + std::map float_kernels{}; + /// All OpenCL device kernel associated with the device corresponding to this command queue using `double` as `real_type`. + std::map double_kernels{}; }; -} // namespace plssvm::opencl::detail \ No newline at end of file +} // namespace plssvm::opencl::detail + +#endif // PLSSVM_BACKENDS_OPENCL_DETAIL_COMMAND_QUEUE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/context.hpp b/include/plssvm/backends/OpenCL/detail/context.hpp index 2b70be1a4..445be4d40 100644 --- a/include/plssvm/backends/OpenCL/detail/context.hpp +++ b/include/plssvm/backends/OpenCL/detail/context.hpp @@ -1,19 +1,21 @@ /** -* @file -* @author Alexander Van Craen -* @author Marcel Breyer -* @copyright 2018-today The PLSSVM project - All Rights Reserved -* @license This file is part of the PLSSVM project which is released under the MIT license. -* See the LICENSE.md file in the project root for full license information. -* -* @brief Defines a very small RAII wrapper around a cl_context including all associated devices and one command queue per device. -*/ + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines a very small RAII wrapper around a cl_context including all associated devices and one command queue per device. + */ +#ifndef PLSSVM_BACKENDS_OPENCL_DETAIL_CONTEXT_HPP_ +#define PLSSVM_BACKENDS_OPENCL_DETAIL_CONTEXT_HPP_ #pragma once #include "CL/cl.h" // cl_context, cl_platform_id, cl_device_id -#include // std::vector +#include // std::vector namespace plssvm::opencl::detail { @@ -79,4 +81,6 @@ class context { std::vector devices{}; }; -} // namespace plssvm::opencl::detail \ No newline at end of file +} // namespace plssvm::opencl::detail + +#endif // PLSSVM_BACKENDS_OPENCL_DETAIL_CONTEXT_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/device_ptr.hpp b/include/plssvm/backends/OpenCL/detail/device_ptr.hpp index 15158de05..4379daf1a 100644 --- a/include/plssvm/backends/OpenCL/detail/device_ptr.hpp +++ b/include/plssvm/backends/OpenCL/detail/device_ptr.hpp @@ -9,6 +9,8 @@ * @brief Small wrapper around a OpenCL device pointer. */ +#ifndef PLSSVM_BACKENDS_OPENCL_DETAIL_DEVICE_PTR_HPP_ +#define PLSSVM_BACKENDS_OPENCL_DETAIL_DEVICE_PTR_HPP_ #pragma once #include "plssvm/backends/OpenCL/detail/command_queue.hpp" // plssvm::opencl::detail::command_queue @@ -23,9 +25,9 @@ namespace plssvm::opencl::detail { * @tparam T the type of the kernel pointer to wrap */ template -class device_ptr : public ::plssvm::detail::gpu_device_ptr { +class device_ptr : public ::plssvm::detail::gpu_device_ptr { /// The template base type of the OpenCL device_ptr class. - using base_type = ::plssvm::detail::gpu_device_ptr; + using base_type = ::plssvm::detail::gpu_device_ptr; using base_type::data_; using base_type::queue_; @@ -33,9 +35,10 @@ class device_ptr : public ::plssvm::detail::gpu_device_ptr; extern template class device_ptr; -} // namespace plssvm::opencl::detail \ No newline at end of file +} // namespace plssvm::opencl::detail + +#endif // PLSSVM_BACKENDS_OPENCL_DETAIL_DEVICE_PTR_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/error_code.hpp b/include/plssvm/backends/OpenCL/detail/error_code.hpp index 0e7002d37..48138e4a0 100644 --- a/include/plssvm/backends/OpenCL/detail/error_code.hpp +++ b/include/plssvm/backends/OpenCL/detail/error_code.hpp @@ -9,9 +9,11 @@ * @brief Small wrapper around OpenCL's error codes. */ +#ifndef PLSSVM_BACKENDS_OPENCL_DETAIL_ERROR_CODE_HPP_ +#define PLSSVM_BACKENDS_OPENCL_DETAIL_ERROR_CODE_HPP_ #pragma once -#include "CL/cl.h" // cl_int, CL_SUCCESS +#include "CL/cl.h" // cl_int, CL_SUCCESS #include // forward declare std::ostream #include // std::string_view @@ -67,14 +69,13 @@ class error_code { */ [[nodiscard]] explicit operator bool() const noexcept; /** - * @brief Overloads the addressof operator to be able to set the wrapped error code value using an out-parameter - * in calls to OpenCL functions. + * @brief Overloads the addressof operator to be able to set the wrapped error code value using an out-parameter in calls to OpenCL functions. * @return pointer to the wrapped OpenCL error code (`[[nodiscard]]`) */ [[nodiscard]] cl_int *operator&() noexcept; private: - // The wrapped OpenCL error code. + /// The wrapped OpenCL error code. cl_int err_{ CL_SUCCESS }; }; @@ -104,4 +105,6 @@ std::ostream &operator<<(std::ostream &out, error_code ec); */ [[nodiscard]] bool operator!=(error_code lhs, error_code rhs) noexcept; -} // namespace plssvm::opencl::detail \ No newline at end of file +} // namespace plssvm::opencl::detail + +#endif // PLSSVM_BACKENDS_OPENCL_DETAIL_ERROR_CODE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/kernel.hpp b/include/plssvm/backends/OpenCL/detail/kernel.hpp index c088e85e2..b5201af09 100644 --- a/include/plssvm/backends/OpenCL/detail/kernel.hpp +++ b/include/plssvm/backends/OpenCL/detail/kernel.hpp @@ -9,6 +9,8 @@ * @brief Defines a very small RAII wrapper around a cl_kernel. */ +#ifndef PLSSVM_BACKENDS_OPENCL_DETAIL_KERNEL_HPP_ +#define PLSSVM_BACKENDS_OPENCL_DETAIL_KERNEL_HPP_ #pragma once #include "CL/cl.h" // cl_kernel @@ -17,7 +19,7 @@ namespace plssvm::opencl::detail { /** * @brief Enum class for all different OpenCL compute kernels. - * @details Used to distinguish kernels in the `plssvm::opencl::detail::command_queue` class. + * @details Used to distinguish kernels in the plssvm::opencl::detail::command_queue class. */ enum class compute_kernel_name { /// The kernels to generate the `q` vector. @@ -63,7 +65,7 @@ class kernel { * @param[in,out] other the kernel to move the resources from * @return `*this` */ - kernel &operator=(kernel &&other); + kernel &operator=(kernel &&other) noexcept; /** * @brief Release the cl_kernel resources on destruction. @@ -85,4 +87,6 @@ class kernel { cl_kernel compute_kernel; }; -} // namespace plssvm::opencl::detail \ No newline at end of file +} // namespace plssvm::opencl::detail + +#endif // PLSSVM_BACKENDS_OPENCL_DETAIL_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/kernel_source_string.hpp.in b/include/plssvm/backends/OpenCL/detail/kernel_source_string.hpp.in deleted file mode 100644 index 76693034b..000000000 --- a/include/plssvm/backends/OpenCL/detail/kernel_source_string.hpp.in +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @file - * @author Alexander Van Craen - * @author Marcel Breyer - * @copyright 2018-today The PLSSVM project - All Rights Reserved - * @license This file is part of the PLSSVM project which is released under the MIT license. - * See the LICENSE.md file in the project root for full license information. - * - * @brief File containing a single std::string which is propagated with all OpenCL kernels during CMake configuration. - */ - -#pragma once - -#include // std::string - -namespace plssvm::opencl::detail { - -/// An std::string containing all OpenCL kernel sources. Created and configured during CMake configuration. -constexpr const char* raw_kernel_src_string = R"( -@PLSSVM_OPENCL_KERNEL_SOURCE_STRING@; -)"; - -} \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/detail/utility.hpp b/include/plssvm/backends/OpenCL/detail/utility.hpp index 2813733a2..680eb095c 100644 --- a/include/plssvm/backends/OpenCL/detail/utility.hpp +++ b/include/plssvm/backends/OpenCL/detail/utility.hpp @@ -9,6 +9,8 @@ * @brief Utility functions specific to the OpenCL backend. */ +#ifndef PLSSVM_BACKENDS_OPENCL_DETAIL_UTILITY_HPP_ +#define PLSSVM_BACKENDS_OPENCL_DETAIL_UTILITY_HPP_ #pragma once #include "plssvm/backends/OpenCL/detail/command_queue.hpp" // plssvm::opencl::detail::command_queue @@ -16,18 +18,18 @@ #include "plssvm/backends/OpenCL/detail/error_code.hpp" // plssvm::opencl::detail::error_code #include "plssvm/backends/OpenCL/detail/kernel.hpp" // plssvm::opencl::detail::compute_kernel_name #include "plssvm/detail/assert.hpp" // PLSSVM_ASSERT -#include "plssvm/kernel_types.hpp" // plssvm::kernel_type +#include "plssvm/kernel_function_types.hpp" // plssvm::kernel_function_type #include "plssvm/target_platforms.hpp" // plssvm::target_platform -#include "CL/cl.h" // cl_uint, cl_int, clSetKernelArg, clEnqueueNDRangeKernel, clFinish +#include "CL/cl.h" // cl_uint, cl_int, clSetKernelArg, clEnqueueNDRangeKernel, clFinish -#include "fmt/core.h" // fmt::format +#include "fmt/core.h" // fmt::format -#include // std::size_t -#include // std::string -#include // std::string_view -#include // std::forward, std::pair -#include // std::vector +#include // std::size_t +#include // std::string +#include // std::string_view +#include // std::forward, std::pair +#include // std::vector /** * @def PLSSVM_OPENCL_ERROR_CHECK @@ -38,23 +40,18 @@ namespace plssvm::opencl::detail { /** - * @brief Check the OpenCL error @p code. If @p code signals an error, throw a `plssvm::opencl::backend_exception`. - * @details The exception contains the error name and additional debug information. + * @brief Check the OpenCL error @p code. If @p code signals an error, throw a plssvm::opencl::backend_exception. + * @details The exception contains the following message: "OpenCL assert 'OPENCL_ERROR_NAME' (OPENCL_ERROR_CODE): OPTIONAL_OPENCL_ERROR_STRING". * @param[in] code the OpenCL error code to check * @param[in] msg optional message printed if the error code check failed - * @throws `plssvm::opencl::backend_exception` if the error code signals a failure + * @throws plssvm::opencl::backend_exception if the error code signals a failure */ void device_assert(error_code code, std::string_view msg = ""); /** * @brief Returns the context listing all devices matching the target platform @p target and the actually used target platform * (only interesting if the provided @p target was automatic). - * @details If the selected target platform is `plssvm::target_platform::automatic` the selector tries to find devices in the following order: - * 1. NVIDIA GPUs - * 2. AMD GPUs - * 3. Intel GPUs - * 4. CPUs - * + * @details If the selected target platform is plssvm::target_platform::automatic the selector tries to find devices according to plssvm::determine_default_target_platform. * @param[in] target the target platform for which the devices must match * @return the command queues and used target platform (`[[nodiscard]]`) */ @@ -74,18 +71,19 @@ void device_synchronize(const command_queue &queue); [[nodiscard]] std::string get_device_name(const command_queue &queue); /** - * @brief Convert the kernel type @p kernel to the device function names and return the compute_kernel_name identifier. + * @brief Convert the kernel type @p kernel to the device function names and return the plssvm::opencl::detail::compute_kernel_name identifier. * @param[in] kernel the kernel type - * @return the kernel function names with the respective compute_kernel_name identifier (`[[nodiscard]]`) + * @return the kernel function names with the respective plssvm::opencl::detail::compute_kernel_name identifier (`[[nodiscard]]`) */ -[[nodiscard]] std::vector> kernel_type_to_function_names(kernel_type kernel); +[[nodiscard]] std::vector> kernel_type_to_function_names(kernel_function_type kernel); /** * @brief Create command queues for all devices in the OpenCL @p contexts with respect to @p target given and * the associated compute kernels with respect to the given source files and kernel function names. + * Always builds kernels for `float` and `double` precision floating point types. * @details Manually caches the OpenCL JIT compiled code in the current `$TEMP` directory (no special means to prevent race conditions are implemented). * The cached binaries are reused if: - * 1. cached files already exist + * 1. the cached files already exist * 2. the number of cached files match the number of needed files * 3. the kernel source checksum matches (no changes in the source files since the last caching) * @@ -93,16 +91,13 @@ void device_synchronize(const command_queue &queue); * Additionally, adds the path to the currently used OpenCL library as a comment to the kernel source string (before the checksum calculation) to detect * changes in the used OpenCL implementation and trigger a kernel rebuild. * - * @tparam real_type the floating point type used to replace the placeholders in the kernel file * @param[in] contexts the used OpenCL contexts * @param[in] target the target platform * @param[in] kernel_names all kernel name for which an OpenCL cl_kernel should be build - * @param[in] print_info if `true` prints additional info to the standard output * @throws plssvm::invalid_file_format_exception if the file couldn't be read using [`std::ifstream::read`](https://en.cppreference.com/w/cpp/io/basic_istream/read) * @return the command queues with all necessary kernels (`[[nodiscard]]`) */ -template -[[nodiscard]] std::vector create_command_queues(const std::vector &contexts, target_platform target, const std::vector> &kernel_names, bool print_info); +[[nodiscard]] std::vector create_command_queues(const std::vector &contexts, target_platform target, const std::vector> &kernel_names); /** * @brief Set all arguments in the parameter pack @p args for the kernel @p kernel. @@ -159,3 +154,5 @@ inline void run_kernel(const command_queue &queue, cl_kernel kernel, std::size_t } } // namespace plssvm::opencl::detail + +#endif // PLSSVM_BACKENDS_OPENCL_DETAIL_UTILITY_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/exceptions.hpp b/include/plssvm/backends/OpenCL/exceptions.hpp index f6bbb5ab8..b1f764293 100644 --- a/include/plssvm/backends/OpenCL/exceptions.hpp +++ b/include/plssvm/backends/OpenCL/exceptions.hpp @@ -9,12 +9,14 @@ * @brief Implements custom exception classes specific to the OpenCL backend. */ +#ifndef PLSSVM_BACKENDS_OPENCL_EXCEPTIONS_HPP_ +#define PLSSVM_BACKENDS_OPENCL_EXCEPTIONS_HPP_ #pragma once #include "plssvm/exceptions/exceptions.hpp" // plssvm::exception #include "plssvm/exceptions/source_location.hpp" // plssvm::source_location -#include // std::string +#include // std::string namespace plssvm::opencl { @@ -31,4 +33,6 @@ class backend_exception : public exception { explicit backend_exception(const std::string &msg, source_location loc = source_location::current()); }; -} // namespace plssvm::opencl \ No newline at end of file +} // namespace plssvm::opencl + +#endif // PLSSVM_BACKENDS_OPENCL_EXCEPTIONS_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenCL/predict_kernel.cl b/include/plssvm/backends/OpenCL/predict_kernel.cl index c9e6be852..0f60a8039 100644 --- a/include/plssvm/backends/OpenCL/predict_kernel.cl +++ b/include/plssvm/backends/OpenCL/predict_kernel.cl @@ -9,7 +9,7 @@ * @brief Defines the functions used for prediction for the C-SVM using the OpenCL backend. */ -//#include "detail/atomics.cl" // atomicAdd -> included via string concatenation when building the device kernels +// #include "detail/atomics.cl" // atomicAdd -> included via string concatenation when building the device kernels /** * @brief Calculate the `w` vector to speed up the prediction of the labels for data points using the linear kernel function. @@ -38,7 +38,7 @@ __kernel void device_kernel_w_linear(__global real_type *w_d, __global real_type * @brief Predicts the labels for data points using the polynomial kernel function. * @details Currently only single GPU execution is supported. * @tparam real_type the type of the data - * @param[in] out_d the calculated predictions + * @param[out] out_d the calculated predictions * @param[in] data_d the one-dimension support vector matrix * @param[in] data_last_d the last row of the support vector matrix * @param[in] alpha_d the previously calculated weight for each data point @@ -50,14 +50,14 @@ __kernel void device_kernel_w_linear(__global real_type *w_d, __global real_type * @param[in] gamma the gamma parameter used in the polynomial kernel function * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ -__kernel void device_kernel_predict_poly(__global real_type *out_d, __global const real_type *data_d, __global const real_type *data_last_d, __global const real_type *alpha_d, const kernel_index_type num_data_points, __global const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0) { +__kernel void device_kernel_predict_polynomial(__global real_type *out_d, __global const real_type *data_d, __global const real_type *data_last_d, __global const real_type *alpha_d, const kernel_index_type num_data_points, __global const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0) { const kernel_index_type data_point_index = get_global_id(0); const kernel_index_type predict_point_index = get_global_id(1); real_type temp = 0.0; if (predict_point_index < num_predict_points) { for (kernel_index_type feature_index = 0; feature_index < num_features; ++feature_index) { - if (data_point_index == num_data_points) { + if (data_point_index == num_data_points - 1) { temp += data_last_d[feature_index] * points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]; } else { temp += data_d[data_point_index + (num_data_points - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] * points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]; @@ -73,7 +73,7 @@ __kernel void device_kernel_predict_poly(__global real_type *out_d, __global con * @brief Predicts the labels for data points using the radial basis functions kernel function. * @details Currently only single GPU execution is supported. * @tparam real_type the type of the data - * @param[in] out_d the calculated predictions + * @param[out] out_d the calculated predictions * @param[in] data_d the one-dimension support vector matrix * @param[in] data_last_d the last row of the support vector matrix * @param[in] alpha_d the previously calculated weight for each data point @@ -83,14 +83,14 @@ __kernel void device_kernel_predict_poly(__global real_type *out_d, __global con * @param[in] num_features the number of features per support vector and point to predict * @param[in] gamma the gamma parameter used in the rbf kernel function */ -__kernel void device_kernel_predict_radial(__global real_type *out_d, __global const real_type *data_d, __global const real_type *data_last_d, __global const real_type *alpha_d, const kernel_index_type num_data_points, __global const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma) { +__kernel void device_kernel_predict_rbf(__global real_type *out_d, __global const real_type *data_d, __global const real_type *data_last_d, __global const real_type *alpha_d, const kernel_index_type num_data_points, __global const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma) { const kernel_index_type data_point_index = get_global_id(0); const kernel_index_type predict_point_index = get_global_id(1); real_type temp = 0.0; if (predict_point_index < num_predict_points) { for (kernel_index_type feature_index = 0; feature_index < num_features; ++feature_index) { - if (data_point_index == num_data_points) { + if (data_point_index == num_data_points - 1) { temp += (data_last_d[feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]) * (data_last_d[feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]); } else { temp += (data_d[data_point_index + (num_data_points - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]) * (data_d[data_point_index + (num_data_points - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] - points[predict_point_index + (num_predict_points + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]); diff --git a/include/plssvm/backends/OpenCL/q_kernel.cl b/include/plssvm/backends/OpenCL/q_kernel.cl index ecc0ba879..9a2e2b49c 100644 --- a/include/plssvm/backends/OpenCL/q_kernel.cl +++ b/include/plssvm/backends/OpenCL/q_kernel.cl @@ -43,13 +43,13 @@ __kernel void device_kernel_q_linear(__global real_type *q, __global real_type * * @param[in] gamma the gamma parameter used in the polynomial kernel function * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ -__kernel void device_kernel_q_poly(__global real_type *q, __global real_type *data_d, __global real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0) { +__kernel void device_kernel_q_polynomial(__global real_type *q, __global real_type *data_d, __global real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0) { const kernel_index_type index = get_global_id(0); real_type temp = 0.0; for (int i = 0; i < num_cols; ++i) { temp += data_d[i * num_rows + index] * data_last[i]; } - q[index] = pown(gamma * temp + coef0, degree); + q[index] = pow(gamma * temp + coef0, degree); } /** @@ -63,7 +63,7 @@ __kernel void device_kernel_q_poly(__global real_type *q, __global real_type *da * @param[in] num_cols the number of columns in the data matrix * @param[in] gamma the gamma parameter used in the rbf kernel function */ -__kernel void device_kernel_q_radial(__global real_type *q, __global real_type *data_d, __global real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma) { +__kernel void device_kernel_q_rbf(__global real_type *q, __global real_type *data_d, __global real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma) { const kernel_index_type index = get_global_id(0); real_type temp = 0.0; for (kernel_index_type i = 0; i < num_cols; ++i) { diff --git a/include/plssvm/backends/OpenCL/svm_kernel.cl b/include/plssvm/backends/OpenCL/svm_kernel.cl index 02e08689e..dedf53306 100644 --- a/include/plssvm/backends/OpenCL/svm_kernel.cl +++ b/include/plssvm/backends/OpenCL/svm_kernel.cl @@ -9,7 +9,7 @@ * @brief Defines the kernel functions for the C-SVM using the CUDA backend. */ -//#include "detail/atomics.cl" // atomicAdd -> included via string concatenation when building the device kernels +// #include "detail/atomics.cl" // atomicAdd -> included via string concatenation when building the device kernels /** * @brief Calculates the C-SVM kernel using the linear kernel function. @@ -41,7 +41,7 @@ __kernel void device_kernel_linear(__global const real_type *q, __global real_ty // cache data for (kernel_index_type vec_index = 0; vec_index < feature_range * num_rows; vec_index += num_rows) { barrier(CLK_LOCAL_MEM_FENCE); - #pragma unroll INTERNAL_BLOCK_SIZE +#pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type block_id = 0; block_id < INTERNAL_BLOCK_SIZE; ++block_id) { const kernel_index_type idx = block_id % THREAD_BLOCK_SIZE; if (get_local_id(1) == idx) { @@ -54,25 +54,25 @@ __kernel void device_kernel_linear(__global const real_type *q, __global real_ty } barrier(CLK_LOCAL_MEM_FENCE); - #pragma unroll INTERNAL_BLOCK_SIZE +#pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type data_index = 0; data_index < INTERNAL_BLOCK_SIZE; ++data_index) { data_j[data_index] = data_intern_j[get_local_id(1)][data_index]; } - #pragma unroll INTERNAL_BLOCK_SIZE +#pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type l = 0; l < INTERNAL_BLOCK_SIZE; ++l) { const real_type data_i = data_intern_i[get_local_id(0)][l]; - #pragma unroll INTERNAL_BLOCK_SIZE +#pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type k = 0; k < INTERNAL_BLOCK_SIZE; ++k) { matr[k][l] += data_i * data_j[k]; } } } - #pragma unroll INTERNAL_BLOCK_SIZE +#pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type x = 0; x < INTERNAL_BLOCK_SIZE; ++x) { real_type ret_jx = 0.0; - #pragma unroll INTERNAL_BLOCK_SIZE +#pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type y = 0; y < INTERNAL_BLOCK_SIZE; ++y) { real_type temp; if (id == 0) { @@ -115,7 +115,7 @@ __kernel void device_kernel_linear(__global const real_type *q, __global real_ty * @param[in] gamma the gamma parameter used in the polynomial kernel function * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ -__kernel void device_kernel_poly(__global const real_type *q, __global real_type *ret, __global const real_type *d, __global const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) { +__kernel void device_kernel_polynomial(__global const real_type *q, __global real_type *ret, __global const real_type *d, __global const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) { kernel_index_type i = get_group_id(0) * get_local_size(0) * INTERNAL_BLOCK_SIZE; kernel_index_type j = get_group_id(1) * get_local_size(1) * INTERNAL_BLOCK_SIZE; @@ -163,7 +163,7 @@ __kernel void device_kernel_poly(__global const real_type *q, __global real_type real_type ret_jx = 0.0; #pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type y = 0; y < INTERNAL_BLOCK_SIZE; ++y) { - const real_type temp = (pown(gamma * matr[x][y] + coef0, degree) + QA_cost - q[i + y] - q[j + x]) * add; + const real_type temp = (pow(gamma * matr[x][y] + coef0, degree) + QA_cost - q[i + y] - q[j + x]) * add; if (i + x > j + y) { // upper triangular matrix atomicAdd(&ret[i + y], temp * d[j + x]); @@ -193,7 +193,7 @@ __kernel void device_kernel_poly(__global const real_type *q, __global real_type * @param[in] add denotes whether the values are added or subtracted from the result vector * @param[in] gamma the gamma parameter used in the rbf kernel function */ -__kernel void device_kernel_radial(__global const real_type *q, __global real_type *ret, __global const real_type *d, __global const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) { +__kernel void device_kernel_rbf(__global const real_type *q, __global real_type *ret, __global const real_type *d, __global const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) { kernel_index_type i = get_group_id(0) * get_local_size(0) * INTERNAL_BLOCK_SIZE; kernel_index_type j = get_group_id(1) * get_local_size(1) * INTERNAL_BLOCK_SIZE; diff --git a/include/plssvm/backends/OpenMP/csvm.hpp b/include/plssvm/backends/OpenMP/csvm.hpp index 163290a6c..7f4d29b1d 100644 --- a/include/plssvm/backends/OpenMP/csvm.hpp +++ b/include/plssvm/backends/OpenMP/csvm.hpp @@ -9,98 +9,178 @@ * @brief Defines a C-SVM using the OpenMP backend. */ +#ifndef PLSSVM_BACKENDS_OPENMP_CSVM_HPP_ +#define PLSSVM_BACKENDS_OPENMP_CSVM_HPP_ #pragma once -#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/detail/type_traits.hpp" // PLSSVM_REQUIRES +#include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::{parameter, has_only_parameter_named_args_v} +#include "plssvm/target_platforms.hpp" // plssvm::target_platform -#include // std::vector +#include // std::true_type +#include // std::forward, std::pair +#include // std::vector namespace plssvm { -// forward declare parameter class -template -class parameter; - namespace openmp { /** * @brief A C-SVM implementation using OpenMP as backend. - * @tparam T the type of the data */ -template -class csvm : public ::plssvm::csvm { - protected: - // protected for test mock class - /// The template base type of the OpenMP C-SVM class. - using base_type = ::plssvm::csvm; - using base_type::alpha_ptr_; - using base_type::bias_; - using base_type::coef0_; - using base_type::cost_; - using base_type::data_ptr_; - using base_type::degree_; - using base_type::gamma_; - using base_type::kernel_; - using base_type::num_data_points_; - using base_type::num_features_; - using base_type::print_info_; - using base_type::QA_cost_; - using base_type::target_; - using base_type::w_; - +class csvm : public ::plssvm::csvm { public: - // Be able to use the predict overload from the csvm base class. - using base_type::predict; - /// The type of the data. Must be either `float` or `double`. - using real_type = typename base_type::real_type; - /** * @brief Construct a new C-SVM using the OpenMP backend with the parameters given through @p params. - * @param[in] params struct encapsulating all possible parameters - * @throws plssvm::csvm::csvm() exceptions + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor * @throws plssvm::openmp::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::cpu * @throws plssvm::openmp::backend_exception if the plssvm::target_platform::cpu target isn't available */ - explicit csvm(const parameter ¶ms); + explicit csvm(parameter params = {}); + /** + * @brief Construct a new C-SVM using the OpenMP backend on the @p target platform with the parameters given through @p params. + * @param[in] target the target platform used for this C-SVM + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::openmp::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::cpu + * @throws plssvm::openmp::backend_exception if the plssvm::target_platform::cpu target isn't available + */ + explicit csvm(target_platform target, parameter params = {}); /** - * @copydoc plssvm::csvm::predict(const std::vector>&) + * @brief Construct a new C-SVM using the OpenMP backend and the optionally provided @p named_args. + * @param[in] named_args the additional optional named-parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::openmp::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::cpu + * @throws plssvm::openmp::backend_exception if the plssvm::target_platform::cpu target isn't available + */ + template )> + explicit csvm(Args &&...named_args) : + ::plssvm::csvm{ std::forward(named_args)... } { + // the default target is the automatic one + this->init(plssvm::target_platform::automatic); + } + /** + * @brief Construct a new C-SVM using the OpenMP backend on the @p target platform and the optionally provided @p named_args. + * @param[in] target the target platform used for this C-SVM + * @param[in] named_args the additional optional named-parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::openmp::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::cpu + * @throws plssvm::openmp::backend_exception if the plssvm::target_platform::cpu target isn't available */ - [[nodiscard]] std::vector predict(const std::vector> &points) override; + template )> + explicit csvm(const target_platform target, Args &&...named_args) : + ::plssvm::csvm{ std::forward(named_args)... } { + this->init(target); + } + + /** + * @copydoc plssvm::csvm::csvm(const plssvm::csvm &) + */ + csvm(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::csvm(plssvm::csvm &&) noexcept + */ + csvm(csvm &&) noexcept = default; + /** + * @copydoc plssvm::csvm::operator=(const plssvm::csvm &) + */ + csvm &operator=(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::operator=(plssvm::csvm &&) noexcept + */ + csvm &operator=(csvm &&) noexcept = default; + /** + * @brief Default destructor since the copy and move constructors and copy- and move-assignment operators are defined. + */ + ~csvm() override = default; protected: /** - * @copydoc plssvm::csvm::setup_data_on_device + * @copydoc plssvm::csvm::solve_system_of_linear_equations */ - void setup_data_on_device() override { - // OpenMP device is the CPU -> no special load functions - } + [[nodiscard]] std::pair, float> solve_system_of_linear_equations(const detail::parameter ¶ms, const std::vector> &A, std::vector b, float eps, unsigned long long max_iter) const override { return this->solve_system_of_linear_equations_impl(params, A, b, eps, max_iter); } + /** + * @copydoc plssvm::csvm::solve_system_of_linear_equations + */ + [[nodiscard]] std::pair, double> solve_system_of_linear_equations(const detail::parameter ¶ms, const std::vector> &A, std::vector b, double eps, unsigned long long max_iter) const override { return this->solve_system_of_linear_equations_impl(params, A, b, eps, max_iter); } + /** + * @copydoc plssvm::csvm::solve_system_of_linear_equations + */ + template + [[nodiscard]] std::pair, real_type> solve_system_of_linear_equations_impl(const detail::parameter ¶ms, const std::vector> &A, std::vector b, real_type eps, unsigned long long max_iter) const; + /** - * @copydoc plssvm::csvm::generate_q + * @copydoc plssvm::csvm::predict_values */ - [[nodiscard]] std::vector generate_q() override; + [[nodiscard]] std::vector predict_values(const detail::parameter ¶ms, const std::vector> &support_vectors, const std::vector &alpha, float rho, std::vector &w, const std::vector> &predict_points) const override { return this->predict_values_impl(params, support_vectors, alpha, rho, w, predict_points); } /** - * @copydoc plssvm::csvm::solver_CG + * @copydoc plssvm::csvm::predict_values */ - std::vector solver_CG(const std::vector &b, std::size_t imax, real_type eps, const std::vector &q) override; + [[nodiscard]] std::vector predict_values(const detail::parameter ¶ms, const std::vector> &support_vectors, const std::vector &alpha, double rho, std::vector &w, const std::vector> &predict_points) const override { return this->predict_values_impl(params, support_vectors, alpha, rho, w, predict_points); } /** - * @copydoc plssvm::csvm::update_w + * @copydoc plssvm::csvm::predict_values */ - void update_w() override; + template + [[nodiscard]] std::vector predict_values_impl(const detail::parameter ¶ms, const std::vector> &support_vectors, const std::vector &alpha, real_type rho, std::vector &w, const std::vector> &predict_points) const; /** - * @brief Select the correct kernel based on the value of @p kernel_ and run it on the CPU using OpenMP. - * @param[in] q the `q` vector + * @brief Calculate the `q` vector used in the dimensional reduction. + * @details The template parameter `real_type` represents the type of the data points (either `float` or `double`). + * @param[in] params the SVM parameter used to calculate `q` (e.g., kernel_type) + * @param[in] data the data points used in the dimensional reduction. + * @return the `q` vector (`[[nodiscard]]`) + */ + template + [[nodiscard]] std::vector generate_q(const detail::parameter ¶ms, const std::vector> &data) const; + /** + * @brief Precalculate the `w` vector to speedup up the prediction using the linear kernel function. + * @details The template parameter `real_type` represents the type of the data points (either `float` or `double`). + * @param[in] support_vectors the previously learned support vectors + * @param[in] alpha the previously learned weights + * @return the `w` vector (`[[nodiscard]]`) + */ + template + [[nodiscard]] std::vector calculate_w(const std::vector> &support_vectors, const std::vector &alpha) const; + + /** + * @brief Select the correct kernel based on the value of plssvm::parameter::kernel_type and run it on the CPU using OpenMP. + * @details The template parameter `real_type` represents the type of the data points (either `float` or `double`). + * @param[in] params the SVM parameter used to calculate `q` (e.g., kernel_type) + * @param[in] q the `q` vector used in the dimensional reduction * @param[out] ret the result vector * @param[in] d the right-hand side of the equation - * @param[in] data the data + * @param[in] data the data points + * @param[in] QA_cost a value used in the dimensional reduction * @param[in] add denotes whether the values are added or subtracted from the result vector */ - void run_device_kernel(const std::vector &q, std::vector &ret, const std::vector &d, const std::vector> &data, real_type add); -}; + template + void run_device_kernel(const detail::parameter ¶ms, const std::vector &q, std::vector &ret, const std::vector &d, const std::vector> &data, real_type QA_cost, real_type add) const; -extern template class csvm; -extern template class csvm; + private: + /** + * @brief Initializes the OpenMP backend and performs some sanity checks. + * @param[in] target the target platform to use + * @throws plssvm::openmp::backend_exception if the target platform isn't plssvm::target_platform::automatic or plssvm::target_platform::cpu + * @throws plssvm::openmp::backend_exception if the plssvm::target_platform::cpu target isn't available + */ + void init(target_platform target); +}; } // namespace openmp -} // namespace plssvm \ No newline at end of file + +namespace detail { + +/** + * @brief Sets the `value` to `true` since C-SVMs using the OpenMP backend are available. + */ +template <> +struct csvm_backend_exists : std::true_type {}; + +} // namespace detail + +} // namespace plssvm + +#endif // PLSSVM_BACKENDS_OPENMP_CSVM_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenMP/exceptions.hpp b/include/plssvm/backends/OpenMP/exceptions.hpp index b5ccf67c5..91f25ead2 100644 --- a/include/plssvm/backends/OpenMP/exceptions.hpp +++ b/include/plssvm/backends/OpenMP/exceptions.hpp @@ -9,12 +9,14 @@ * @brief Implements custom exception classes specific to the OpenMP backend. */ +#ifndef PLSSVM_BACKENDS_OPENMP_EXCEPTIONS_HPP_ +#define PLSSVM_BACKENDS_OPENMP_EXCEPTIONS_HPP_ #pragma once #include "plssvm/exceptions/exceptions.hpp" // plssvm::exception #include "plssvm/exceptions/source_location.hpp" // plssvm::source_location -#include // std::string +#include // std::string namespace plssvm::openmp { @@ -31,4 +33,6 @@ class backend_exception : public exception { explicit backend_exception(const std::string &msg, source_location loc = source_location::current()); }; -} // namespace plssvm::openmp \ No newline at end of file +} // namespace plssvm::openmp + +#endif // PLSSVM_BACKENDS_OPENMP_EXCEPTIONS_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenMP/q_kernel.hpp b/include/plssvm/backends/OpenMP/q_kernel.hpp index aeb800c6a..8d7e2031c 100644 --- a/include/plssvm/backends/OpenMP/q_kernel.hpp +++ b/include/plssvm/backends/OpenMP/q_kernel.hpp @@ -9,6 +9,8 @@ * @brief Defines OpenMP functions for generating the `q` vector. */ +#ifndef PLSSVM_BACKENDS_OPENMP_Q_KERNEL_HPP_ +#define PLSSVM_BACKENDS_OPENMP_Q_KERNEL_HPP_ #pragma once #include // std::vector @@ -34,7 +36,7 @@ void device_kernel_q_linear(std::vector &q, const std::vector -void device_kernel_q_poly(std::vector &q, const std::vector> &data, int degree, real_type gamma, real_type coef0); +void device_kernel_q_polynomial(std::vector &q, const std::vector> &data, int degree, real_type gamma, real_type coef0); /** * @brief Calculates the `q` vector using the radial basis functions C-SVM kernel. @@ -44,6 +46,8 @@ void device_kernel_q_poly(std::vector &q, const std::vector -void device_kernel_q_radial(std::vector &q, const std::vector> &data, real_type gamma); +void device_kernel_q_rbf(std::vector &q, const std::vector> &data, real_type gamma); -} // namespace plssvm::openmp \ No newline at end of file +} // namespace plssvm::openmp + +#endif // PLSSVM_BACKENDS_OPENMP_Q_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/OpenMP/svm_kernel.hpp b/include/plssvm/backends/OpenMP/svm_kernel.hpp index 0a09cdf62..fc2601ac0 100644 --- a/include/plssvm/backends/OpenMP/svm_kernel.hpp +++ b/include/plssvm/backends/OpenMP/svm_kernel.hpp @@ -9,6 +9,8 @@ * @brief Defines the kernel functions for the C-SVM using the OpenMP backend. */ +#ifndef PLSSVM_BACKENDS_OPENMP_SVM_KERNEL_HPP_ +#define PLSSVM_BACKENDS_OPENMP_SVM_KERNEL_HPP_ #pragma once #include // std::vector @@ -19,7 +21,7 @@ namespace plssvm::openmp { * @brief Calculates the C-SVM kernel using the linear kernel function. * @tparam real_type the type of the data * @param[in] q the `q` vector - * @param[in] ret the result vector + * @param[out] ret the result vector * @param[in] d the right-hand side of the equation * @param[in] data the data matrix * @param[in] QA_cost he bottom right matrix entry multiplied by cost @@ -33,7 +35,7 @@ void device_kernel_linear(const std::vector &q, std::vector &q, std::vector -void device_kernel_poly(const std::vector &q, std::vector &ret, const std::vector &d, const std::vector> &data, real_type QA_cost, real_type cost, real_type add, int degree, real_type gamma, real_type coef0); +void device_kernel_polynomial(const std::vector &q, std::vector &ret, const std::vector &d, const std::vector> &data, real_type QA_cost, real_type cost, real_type add, int degree, real_type gamma, real_type coef0); /** * @brief Calculates the C-SVM kernel using the radial basis function kernel function. * @tparam real_type the type of the data * @param[in] q the `q` vector - * @param[in] ret the result vector + * @param[out] ret the result vector * @param[in] d the right-hand side of the equation * @param[in] data the data matrix * @param[in] QA_cost he bottom right matrix entry multiplied by cost @@ -59,6 +61,8 @@ void device_kernel_poly(const std::vector &q, std::vector * @param[in] gamma the gamma parameter used in the rbf kernel function */ template -void device_kernel_radial(const std::vector &q, std::vector &ret, const std::vector &d, const std::vector> &data, real_type QA_cost, real_type cost, real_type add, real_type gamma); +void device_kernel_rbf(const std::vector &q, std::vector &ret, const std::vector &d, const std::vector> &data, real_type QA_cost, real_type cost, real_type add, real_type gamma); -} // namespace plssvm::openmp \ No newline at end of file +} // namespace plssvm::openmp + +#endif // PLSSVM_BACKENDS_OPENMP_SVM_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/DPCPP/csvm.hpp b/include/plssvm/backends/SYCL/DPCPP/csvm.hpp new file mode 100644 index 000000000..d04ff4f90 --- /dev/null +++ b/include/plssvm/backends/SYCL/DPCPP/csvm.hpp @@ -0,0 +1,221 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines a base C-SVM used for the different SYCL backends using DPC++ as SYCL implementation. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_DPCPP_CSVM_HPP_ +#define PLSSVM_BACKENDS_SYCL_DPCPP_CSVM_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/DPCPP/detail/device_ptr.hpp" // plssvm::dpcpp::detail::device_ptr +#include "plssvm/backends/SYCL/DPCPP/detail/queue.hpp" // plssvm::dpcpp::detail::queue (PImpl) + +#include "plssvm/backends/SYCL/kernel_invocation_type.hpp" // plssvm::sycl::kernel_invocation_type +#include "plssvm/backends/gpu_csvm.hpp" // plssvm::detail::gpu_csvm +#include "plssvm/detail/type_traits.hpp" // PLSSVM_REQUIRES, plssvm::detail::remove_cvref_t +#include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "igor/igor.hpp" // igor::parser + +#include // std::size_t +#include // std::is_same_v, std::true_type +#include // std::forward + +namespace plssvm { + +namespace detail { + +// forward declare execution_range class +class execution_range; + +} // namespace detail + +namespace dpcpp { + +/** + * @brief A C-SVM implementation using DPC++ as SYCL backend. + */ +class csvm : public ::plssvm::detail::gpu_csvm { + protected: + // protected for the test MOCK class + /// The template base type of the DPC++ SYCL C-SVM class. + using base_type = ::plssvm::detail::gpu_csvm; + + using base_type::devices_; + + public: + using base_type::device_ptr_type; + using typename base_type::queue_type; + + /** + * @brief Construct a new C-SVM using the SYCL backend with the parameters given through @p params. + * @param[in] params struct encapsulating all possible parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::dpcpp::backend_exception if the requested target is not available + * @throws plssvm::dpcpp::backend_exception if no device for the requested target was found + */ + explicit csvm(parameter params = {}); + /** + * @brief Construct a new C-SVM using the SYCL backend on the @p target platform with the parameters given through @p params. + * @param[in] target the target platform used for this C-SVM + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::dpcpp::backend_exception if the requested target is not available + * @throws plssvm::dpcpp::backend_exception if no device for the requested target was found + */ + explicit csvm(target_platform target, parameter params = {}); + + /** + * @brief Construct a new C-SVM using the SYCL backend and the optionally provided @p named_args. + * @param[in] named_args the additional optional named arguments + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::dpcpp::backend_exception if the requested target is not available + * @throws plssvm::dpcpp::backend_exception if no device for the requested target was found + */ + template )> + explicit csvm(Args &&...named_args) : + csvm{ plssvm::target_platform::automatic, std::forward(named_args)... } {} + /** + * @brief Construct a new C-SVM using the SYCL backend on the @p target platform and the optionally provided @p named_args. + * @param[in] target the target platform used for this C-SVM + * @param[in] named_args the additional optional named arguments + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::dpcpp::backend_exception if the requested target is not available + * @throws plssvm::dpcpp::backend_exception if no device for the requested target was found + */ + template )> + explicit csvm(const target_platform target, Args &&...named_args) : + base_type{ named_args... } { + // check igor parameter + igor::parser parser{ std::forward(named_args)... }; + + // check whether a specific SYCL kernel invocation type has been requested + if constexpr (parser.has(sycl_kernel_invocation_type)) { + // compile time check: the value must have the correct type + static_assert(std::is_same_v<::plssvm::detail::remove_cvref_t, sycl::kernel_invocation_type>, "Provided sycl_kernel_invocation_type must be convertible to a plssvm::sycl::kernel_invocation_type!"); + invocation_type_ = static_cast(parser(sycl_kernel_invocation_type)); + } + this->init(target); + } + + /** + * @copydoc plssvm::csvm::csvm(const plssvm::csvm &) + */ + csvm(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::csvm(plssvm::csvm &&) noexcept + */ + csvm(csvm &&) noexcept = default; + /** + * @copydoc plssvm::csvm::operator=(const plssvm::csvm &) + */ + csvm &operator=(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::operator=(plssvm::csvm &&) noexcept + */ + csvm &operator=(csvm &&) noexcept = default; + /** + * @brief Wait for all operations in all [`sycl::queue`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:interface.queue.class) to finish. + * @details Terminates the program, if any asynchronous exception is thrown. + */ + ~csvm() override; + + /** + * @brief Return the kernel invocation type (nd_range or the SYCL specific hierarchical kernel) used in this SYCL SVM. + * @return the SYCL kernel invocation type (`[[nodiscard]]`) + */ + [[nodiscard]] sycl::kernel_invocation_type get_kernel_invocation_type() const noexcept { return invocation_type_; } + + protected: + /** + * @copydoc plssvm::detail::gpu_csvm::device_synchronize + */ + void device_synchronize(const queue_type &queue) const final; + + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + template + void run_q_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, float QA_cost, float add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, double QA_cost, double add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + template + void run_svm_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, real_type QA_cost, real_type add, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + template + void run_w_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + template + void run_predict_kernel_impl(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const; + + private: + /** + * @brief Initialize all important states related to the SYCL backend. + * @param[in] target the target platform to use + * @throws plssvm::dpcpp::backend_exception if the requested target is not available + * @throws plssvm::dpcpp::backend_exception if no device for the requested target was found + */ + void init(target_platform target); + + /// The SYCL kernel invocation type for the svm kernel. Either nd_range or hierarchical. + sycl::kernel_invocation_type invocation_type_{ sycl::kernel_invocation_type::automatic }; +}; +} // namespace dpcpp + +namespace detail { + +/** + * @brief Sets the `value` to `true` since C-SVMs using the SYCL backend with DPC++ as SYCL implementation are available. + */ +template <> +struct csvm_backend_exists : std::true_type {}; + +} // namespace detail + +} // namespace plssvm + +#endif // PLSSVM_BACKENDS_SYCL_DPCPP_CSVM_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/DPCPP/detail/device_ptr.hpp b/include/plssvm/backends/SYCL/DPCPP/detail/device_ptr.hpp new file mode 100644 index 000000000..0270362bc --- /dev/null +++ b/include/plssvm/backends/SYCL/DPCPP/detail/device_ptr.hpp @@ -0,0 +1,105 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Small wrapper around a SYCL device pointer for the DPC++ SYCL implementation. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_DEVICE_PTR_HPP_ +#define PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_DEVICE_PTR_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/DPCPP/detail/queue.hpp" // plssvm::sycl::detail::queue (PImpl) +#include "plssvm/backends/gpu_device_ptr.hpp" // plssvm::detail::gpu_device_ptr + +namespace plssvm::dpcpp::detail { + +/** + * @brief Small wrapper class around a DPC++ device pointer together with commonly used device functions. + * @tparam T the type of the kernel pointer to wrap + */ +template +class device_ptr : public ::plssvm::detail::gpu_device_ptr { + /// The template base type of the DPC++ device_ptr class. + using base_type = ::plssvm::detail::gpu_device_ptr; + + using base_type::data_; + using base_type::queue_; + using base_type::size_; + + public: + // Be able to use overloaded base class functions. + using base_type::memset; + using base_type::fill; + using base_type::copy_to_device; + using base_type::copy_to_host; + + using typename base_type::const_host_pointer_type; + using typename base_type::device_pointer_type; + using typename base_type::host_pointer_type; + using typename base_type::queue_type; + using typename base_type::size_type; + using typename base_type::value_type; + + /** + * @brief Default construct a device_ptr with a size of 0. + */ + device_ptr() = default; + /** + * @brief Allocates `size * sizeof(T)` bytes on the device associated with @p q. + * @param[in] size the number of elements represented by the device_ptr + * @param[in] q the associated SYCL queue + */ + device_ptr(size_type size, const queue &q); + + /** + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const plssvm::detail::gpu_device_ptr &) + */ + device_ptr(const device_ptr &) = delete; + /** + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(plssvm::detail::gpu_device_ptr &&) + */ + device_ptr(device_ptr &&other) noexcept = default; + + /** + * @copydoc plssvm::detail::gpu_device_ptr::operator=(const plssvm::detail::gpu_device_ptr &) + */ + device_ptr &operator=(const device_ptr &) = delete; + /** + * @copydoc plssvm::detail::gpu_device_ptr::operator=(plssvm::detail::gpu_device_ptr &&) + */ + device_ptr &operator=(device_ptr &&other) noexcept = default; + + /** + * @copydoc plssvm::detail::gpu_device_ptr::~gpu_device_ptr() + */ + ~device_ptr() override; + + /** + * @copydoc plssvm::detail::gpu_device_ptr::memset(int, size_type, size_type) + */ + void memset(int pattern, size_type pos, size_type num_bytes) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::fill(value_type, size_type, size_type) + */ + void fill(value_type value, size_type pos, size_type count) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_device(const_host_pointer_type, size_type, size_type) + */ + void copy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_host(host_pointer_type, size_type, size_type) const + */ + void copy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; +}; + +extern template class device_ptr; +extern template class device_ptr; + +} // namespace plssvm::dpcpp::detail + +#endif // PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_DEVICE_PTR_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/DPCPP/detail/queue.hpp b/include/plssvm/backends/SYCL/DPCPP/detail/queue.hpp new file mode 100644 index 000000000..030abc8c0 --- /dev/null +++ b/include/plssvm/backends/SYCL/DPCPP/detail/queue.hpp @@ -0,0 +1,33 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief PImpl class used to hide the "sycl/sycl.hpp" header from the public interface. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_QUEUE_HPP_ +#define PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_QUEUE_HPP_ +#pragma once + +#include // std::shared_ptr + +namespace plssvm::dpcpp::detail { + +/** + * @brief PImpl class to hide the SYCL queue class from the public interface (and, therefore, the "sycl/sycl.hpp" header). + */ +class queue { + public: + /// The struct used in the PImpl idiom (encapsulates the actual SYCL queue). + struct queue_impl; + /// A pointer to the implementation hidden in a private header. + std::shared_ptr impl{}; +}; + +} // namespace plssvm::dpcpp::detail + +#endif // PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_QUEUE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/DPCPP/detail/queue_impl.hpp b/include/plssvm/backends/SYCL/DPCPP/detail/queue_impl.hpp new file mode 100644 index 000000000..3ed242d09 --- /dev/null +++ b/include/plssvm/backends/SYCL/DPCPP/detail/queue_impl.hpp @@ -0,0 +1,43 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief PImpl implementation encapsulating a single SYCL queue. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_QUEUE_IMPL_HPP_ +#define PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_QUEUE_IMPL_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/DPCPP/detail/queue.hpp" // plssvm::dpcpp::detail::queue + +#include "sycl/sycl.hpp" // sycl::queue + +#include // std::forward + +namespace plssvm::dpcpp::detail { + +/** + * @brief The PImpl implementation struct encapsulating a single SYCL queue. + */ +struct queue::queue_impl { + /** + * @brief Construct a SYCL queue by forwarding all parameters in @p args. + * @tparam Args the type of the parameters + * @param[in] args the parameters to construct a SYCL queue + */ + template + explicit queue_impl(Args... args) : + sycl_queue{ std::forward(args)... } {} + + /// The wrapped SYCL queue. + ::sycl::queue sycl_queue; +}; + +} // namespace plssvm::dpcpp::detail + +#endif // PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_QUEUE_IMPL_HPP_ diff --git a/include/plssvm/backends/SYCL/DPCPP/detail/utility.hpp b/include/plssvm/backends/SYCL/DPCPP/detail/utility.hpp new file mode 100644 index 000000000..56f707bbd --- /dev/null +++ b/include/plssvm/backends/SYCL/DPCPP/detail/utility.hpp @@ -0,0 +1,53 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Utility functions specific to the SYCL backend using DPC++ as SYCL implementation. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_UTILITY_HPP_ +#define PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_UTILITY_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/DPCPP/detail/queue.hpp" // plssvm::dpcpp::detail::queue (PImpl) +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include // std::pair +#include // std::vector + +namespace plssvm::dpcpp::detail { + +/** + * @brief Returns the list devices matching the target platform @p target and the actually used target platform + * (only interesting if the provided @p target was automatic). + * @details If the selected target platform is `plssvm::target_platform::automatic` the selector tries to find devices in the following order: + * 1. NVIDIA GPUs + * 2. AMD GPUs + * 3. Intel GPUs + * 4. CPUs + * + * @param[in] target the target platform for which the devices must match + * @return the devices and used target platform (`[[nodiscard]]`) + */ +[[nodiscard]] std::pair, target_platform> get_device_list(target_platform target); + +/** + * @brief Wait for the compute device associated with @p q to finish. + * @param[in] q the SYCL queue to synchronize + */ +void device_synchronize(const queue &q); + +/** + * @brief Get the default SYCL queue. + * @details Only used in the tests, but **must** be defined and implemented here! + * @return the default queue (`[[nodiscard]]`) + */ +[[nodiscard]] queue get_default_queue(); + +} // namespace plssvm::dpcpp::detail + +#endif // PLSSVM_BACKENDS_SYCL_DPCPP_DETAIL_UTILITY_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/csvm.hpp b/include/plssvm/backends/SYCL/csvm.hpp deleted file mode 100644 index 2cb010163..000000000 --- a/include/plssvm/backends/SYCL/csvm.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @file - * @author Alexander Van Craen - * @author Marcel Breyer - * @copyright 2018-today The PLSSVM project - All Rights Reserved - * @license This file is part of the PLSSVM project which is released under the MIT license. - * See the LICENSE.md file in the project root for full license information. - * - * @brief Defines a C-SVM using the SYCL backend. - */ - -#pragma once - -#include "plssvm/backends/autogenerated/@PLSSVM_SYCL_BACKEND_INCLUDE_NAME@/detail/constants.hpp" // forward declaration and namespace alias -#include "plssvm/backends/autogenerated/@PLSSVM_SYCL_BACKEND_INCLUDE_NAME@/detail/device_ptr.hpp" // plssvm::@PLSSVM_SYCL_BACKEND_INCLUDE_NAME@::detail::device_ptr -#include "plssvm/backends/SYCL/kernel_invocation_type.hpp" // plssvm::sycl_generic::kernel_invocation_type -#include "plssvm/backends/gpu_csvm.hpp" // plssvm::detail::gpu_csvm - -#include // std::unique_ptr - -namespace plssvm { - -using namespace sycl_generic; - -// forward declare parameter class -template -class parameter; - -namespace detail { - -// forward declare execution_range class -class execution_range; - -} // namespace detail - -namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ { - -/** - * @brief A C-SVM implementation using SYCL as backend. - * @details If DPC++ is available, this class also exists in the `plssvm::dpcpp` namespace. - * If hipSYCL is available, this class also exists in the `plssvm::hipsycl` namespace. - * @tparam T the type of the data - */ -template - class csvm : public ::plssvm::detail::gpu_csvm, std::unique_ptr> { - protected: - // protected for the test MOCK class - /// The template base type of the SYCL C-SVM class. - using base_type = ::plssvm::detail::gpu_csvm, std::unique_ptr>; - - using base_type::coef0_; - using base_type::cost_; - using base_type::degree_; - using base_type::dept_; - using base_type::gamma_; - using base_type::kernel_; - using base_type::num_data_points_; - using base_type::num_features_; - using base_type::print_info_; - using base_type::QA_cost_; - using base_type::target_; - - using base_type::data_d_; - using base_type::data_last_d_; - using base_type::devices_; - using base_type::num_cols_; - using base_type::num_rows_; - - public: - using typename base_type::real_type; - using typename base_type::device_ptr_type; - using typename base_type::queue_type; - - /** - * @brief Construct a new C-SVM using the SYCL backend with the parameters given through @p params. - * @param[in] params struct encapsulating all possible parameters - * @throws plssvm::csvm::csvm() exceptions - * @throws plssvm::sycl::backend_exception if the requested plssvm::target_platform isn't available - * @throws plssvm::sycl::backend_exception if no possible OpenCL devices could be found - */ - explicit csvm(const parameter ¶ms); - - /** - * @brief Wait for all operations in all [`sycl::queue`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:interface.queue.class) to finish. - * @details Terminates the program, if any asynchronous exception is thrown. - */ - ~csvm() override; - - protected: - /** - * @copydoc plssvm::detail::gpu_csvm::device_synchronize - */ - void device_synchronize(queue_type &queue) final; - - /** - * @copydoc plssvm::detail::gpu_csvm::run_q_kernel - */ - void run_q_kernel(std::size_t device, [[maybe_unused]] const ::plssvm::detail::execution_range &range, device_ptr_type &q_d, std::size_t num_features) final; - /** - * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel - */ - void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const real_type add, std::size_t num_features) final; - /** - * @copydoc plssvm::detail::gpu_csvm::run_w_kernel - */ - void run_w_kernel(std::size_t device, [[maybe_unused]] const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, std::size_t num_features) final; - /** - * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel - */ - void run_predict_kernel(const ::plssvm::detail::execution_range &range, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, std::size_t num_predict_points) final; - - private: - /// The SYCL kernel invocation type for the svm kernel. Either nd_range or hierarchical. - kernel_invocation_type invocation_type_; -}; - -extern template class csvm; -extern template class csvm; - -} // namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ -} // namespace plssvm \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/detail/atomics.hpp b/include/plssvm/backends/SYCL/detail/atomics.hpp index 5fea8d705..82c5d0364 100644 --- a/include/plssvm/backends/SYCL/detail/atomics.hpp +++ b/include/plssvm/backends/SYCL/detail/atomics.hpp @@ -1,19 +1,21 @@ /** -* @file -* @author Alexander Van Craen -* @author Marcel Breyer -* @copyright 2018-today The PLSSVM project - All Rights Reserved -* @license This file is part of the PLSSVM project which is released under the MIT license. -* See the LICENSE.md file in the project root for full license information. -* -* @brief Defines an atomic_ref wrapper for the SYCL backend. -*/ + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines an atomic_ref wrapper for the SYCL backend. + */ +#ifndef PLSSVM_BACKENDS_SYCL_DETAIL_ATOMICS_HPP_ +#define PLSSVM_BACKENDS_SYCL_DETAIL_ATOMICS_HPP_ #pragma once #include "sycl/sycl.hpp" // sycl::atomic_ref, sycl::memory_order, sycl::memory_scope, sycl::access::address_space -namespace plssvm::sycl_generic { +namespace plssvm::sycl::detail { /** * @brief Shortcut alias for a [`sycl::atomic_ref`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:atomic-references) targeting global memory. @@ -22,4 +24,6 @@ namespace plssvm::sycl_generic { template using atomic_op = ::sycl::atomic_ref; -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl + +#endif // PLSSVM_BACKENDS_SYCL_DETAIL_ATOMICS_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/detail/constants.hpp b/include/plssvm/backends/SYCL/detail/constants.hpp index 1cd980c81..6b72d55fa 100644 --- a/include/plssvm/backends/SYCL/detail/constants.hpp +++ b/include/plssvm/backends/SYCL/detail/constants.hpp @@ -9,6 +9,8 @@ * @brief Global compile-time constants specific to the SYCL backend. */ +#ifndef PLSSVM_BACKENDS_SYCL_DETAIL_CONSTANTS_HPP_ +#define PLSSVM_BACKENDS_SYCL_DETAIL_CONSTANTS_HPP_ #pragma once /** @@ -21,25 +23,16 @@ */ #define PLSSVM_SYCL_BACKEND_COMPILER_DPCPP 0 - -// forward declare sycl::queue from hipsycl namespace and create global ::hipsycl namespace -#if defined(PLSSVM_SYCL_BACKEND_HAS_HIPSYCL) -namespace hipsycl::sycl { -class queue; +#if defined(PLSSVM_HAS_SYCL_BACKEND) +// define the default used SYCL implementation +namespace plssvm::sycl { +using namespace plssvm::PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION; } -namespace plssvm::hipsycl::detail { - using namespace ::hipsycl; +#else +// define dpcpp as default SYCL namespace if no SYCL backend is available (to prevent compiler errors) +namespace plssvm::sycl { +using namespace plssvm::dpcpp; } #endif -// forward declare sycl::queue from DPC++ namespace and create global ::dpcpp namespace -#if defined(PLSSVM_SYCL_BACKEND_HAS_DPCPP) -inline namespace cl { -namespace sycl { -class queue; -} -} -namespace plssvm::dpcpp::detail { - using namespace cl; -} -#endif \ No newline at end of file +#endif // PLSSVM_BACKENDS_SYCL_DETAIL_CONSTANTS_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/detail/device_ptr.hpp b/include/plssvm/backends/SYCL/detail/device_ptr.hpp deleted file mode 100644 index e2e3ca9f9..000000000 --- a/include/plssvm/backends/SYCL/detail/device_ptr.hpp +++ /dev/null @@ -1,104 +0,0 @@ -/** - * @file - * @author Alexander Van Craen - * @author Marcel Breyer - * @copyright 2018-today The PLSSVM project - All Rights Reserved - * @license This file is part of the PLSSVM project which is released under the MIT license. - * See the LICENSE.md file in the project root for full license information. - * - * @brief Small wrapper around a SYCL device pointer. - */ - -#pragma once - -#include "plssvm/backends/autogenerated/@PLSSVM_SYCL_BACKEND_INCLUDE_NAME@/detail/constants.hpp" // forward declaration and namespace alias -#include "plssvm/backends/gpu_device_ptr.hpp" // plssvm::detail::gpu_device_ptr - -#include "sycl/sycl.hpp" // sycl::queue - -namespace plssvm { - -namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ { - -namespace detail { - -/** - * @brief Small wrapper class around a SYCL device pointer together with commonly used device functions. - * @tparam T the type of the kernel pointer to wrap - */ -template -class device_ptr : public ::plssvm::detail::gpu_device_ptr { - /// The template base type of the OpenCL device_ptr class. - using base_type = ::plssvm::detail::gpu_device_ptr; - - using base_type::data_; - using base_type::queue_; - using base_type::size_; - - public: - // Be able to use overloaded base class functions. - using base_type::memcpy_to_device; - using base_type::memcpy_to_host; - using base_type::memset; - - using typename base_type::const_host_pointer_type; - using typename base_type::device_pointer_type; - using typename base_type::host_pointer_type; - using typename base_type::queue_type; - using typename base_type::size_type; - using typename base_type::value_type; - - /** - * @brief Default construct a device_ptr with a size of 0. - */ - device_ptr() = default; - /** - * @brief Allocates `size * sizeof(T)` bytes on the device associated with @p queue. - * @param[in] size the number of elements represented by the device_ptr - * @param[in] queue the associated SYCL queue - */ - device_ptr(size_type size, std::unique_ptr& queue); - - /** - * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const gpu_device_ptr&) - */ - device_ptr(const device_ptr &) = delete; - /** - * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(gpu_device_ptr&&) - */ - device_ptr(device_ptr &&other) noexcept = default; - - /** - * @copydoc plssvm::detail::gpu_device_ptr::operator=(const gpu_device_ptr&) - */ - device_ptr &operator=(const device_ptr &) = delete; - /** - * @copydoc plssvm::detail::gpu_device_ptr::operator=(gpu_device_ptr&&) - */ - device_ptr &operator=(device_ptr &&other) noexcept = default; - - /** - * @copydoc plssvm::detail::gpu_device_ptr::~gpu_device_ptr() - */ - ~device_ptr(); - - /** - * @copydoc plssvm::detail::gpu_device_ptr::memset(int, size_type, size_type) - */ - void memset(int value, size_type pos, size_type count) override; - /** - * @copydoc plssvm::detail::gpu_device_ptr::memcpy_to_device(const_host_pointer_type, size_type, size_type) - */ - void memcpy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; - /** - * @copydoc plssvm::detail::gpu_device_ptr::memcpy_to_host(host_pointer_type, size_type, size_type) const - */ - void memcpy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; -}; - -extern template class device_ptr; -extern template class device_ptr; - -} // namespace detail -} // namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ -} // namespace plssvm \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/detail/utility.hpp b/include/plssvm/backends/SYCL/detail/utility.hpp deleted file mode 100644 index 261e3aeb7..000000000 --- a/include/plssvm/backends/SYCL/detail/utility.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @file - * @author Alexander Van Craen - * @author Marcel Breyer - * @copyright 2018-today The PLSSVM project - All Rights Reserved - * @license This file is part of the PLSSVM project which is released under the MIT license. - * See the LICENSE.md file in the project root for full license information. - * - * @brief Utility functions specific to the SYCL backend. - */ - -#pragma once - -#include "plssvm/backends/autogenerated/@PLSSVM_SYCL_BACKEND_INCLUDE_NAME@/detail/constants.hpp" // forward declaration and namespace alias -#include "plssvm/target_platforms.hpp" // plssvm::target_platform - -#include // std::unique_ptr -#include // std::pair -#include // std::vector - -namespace plssvm { - -namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ { - -namespace detail { - -/** - * @brief Returns the list devices matching the target platform @p target and the actually used target platform - * (only interesting if the provided @p target was automatic). - * @details If the selected target platform is `plssvm::target_platform::automatic` the selector tries to find devices in the following order: - * 1. NVIDIA GPUs - * 2. AMD GPUs - * 3. Intel GPUs - * 4. CPUs - * - * @param[in] target the target platform for which the devices must match - * @return the devices and used target platform (`[[nodiscard]]`) - */ -[[nodiscard]] std::pair>, target_platform> get_device_list(target_platform target); -/** - * @brief Wait for the compute device associated with @p queue to finish. - * @param[in] queue the SYCL queue to synchronize - */ -void device_synchronize(detail::sycl::queue &queue); - -} // namespace detail -} // namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ -} // namespace plssvm \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/exceptions.hpp b/include/plssvm/backends/SYCL/exceptions.hpp index 98b57f1a6..9b8ea3e51 100644 --- a/include/plssvm/backends/SYCL/exceptions.hpp +++ b/include/plssvm/backends/SYCL/exceptions.hpp @@ -9,16 +9,18 @@ * @brief Implements custom exception classes specific to the SYCL backend. */ +#ifndef PLSSVM_BACKENDS_SYCL_CSVM_HPP_ +#define PLSSVM_BACKENDS_SYCL_CSVM_HPP_ #pragma once #include "plssvm/exceptions/exceptions.hpp" // plssvm::exception #include "plssvm/exceptions/source_location.hpp" // plssvm::source_location -#include // std::string +#include // std::string namespace plssvm { -namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ { +namespace sycl { /** * @brief Exception type thrown if a problem with the SYCL backend occurs. @@ -31,7 +33,51 @@ class backend_exception : public exception { * @param[in] loc the exception's call side information */ explicit backend_exception(const std::string &msg, source_location loc = source_location::current()); + /** + * @brief Construct a new exception forwarding the exception message and source location to plssvm::exception. + * @param[in] msg the exception's `what()` message + * @param[in] class_name the name of the thrown exception class + * @param[in] loc the exception's call side information + */ + explicit backend_exception(const std::string &msg, std::string_view class_name, source_location loc = source_location::current()); }; -} // namespace @PLSSVM_SYCL_BACKEND_NAMESPACE_NAME@ -} // namespace plssvm \ No newline at end of file +} // namespace sycl + +namespace hipsycl { + +/** + * @brief Exception type thrown if a problem with the hipSYCL SYCL backend occurs. + */ +class backend_exception : public sycl::backend_exception { + public: + /** + * @brief Construct a new exception forwarding the exception message and source location to plssvm::sycl::backend_exception. + * @param[in] msg the exception's `what()` message + * @param[in] loc the exception's call side information + */ + explicit backend_exception(const std::string &msg, source_location loc = source_location::current()); +}; + +} // namespace hipsycl + +namespace dpcpp { + +/** + * @brief Exception type thrown if a problem with the DPC++ SYCL backend occurs. + */ +class backend_exception : public sycl::backend_exception { + public: + /** + * @brief Construct a new exception forwarding the exception message and source location to plssvm::sycl::backend_exception. + * @param[in] msg the exception's `what()` message + * @param[in] loc the exception's call side information + */ + explicit backend_exception(const std::string &msg, source_location loc = source_location::current()); +}; + +} // namespace dpcpp + +} // namespace plssvm + +#endif // PLSSVM_BACKENDS_SYCL_CSVM_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/hipSYCL/csvm.hpp b/include/plssvm/backends/SYCL/hipSYCL/csvm.hpp new file mode 100644 index 000000000..0e84d5fc6 --- /dev/null +++ b/include/plssvm/backends/SYCL/hipSYCL/csvm.hpp @@ -0,0 +1,223 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines a base C-SVM used for the different SYCL backends using hipSYCL as SYCL implementation. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_HIPSYCL_CSVM_HPP_ +#define PLSSVM_BACKENDS_SYCL_HIPSYCL_CSVM_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/hipSYCL/detail/device_ptr.hpp" // plssvm::hipsycl::detail::device_ptr +#include "plssvm/backends/SYCL/hipSYCL/detail/queue.hpp" // plssvm::hipsycl::detail::queue (PImpl) + +#include "plssvm/backends/SYCL/kernel_invocation_type.hpp" // plssvm::sycl::kernel_invocation_type +#include "plssvm/backends/gpu_csvm.hpp" // plssvm::detail::gpu_csvm +#include "plssvm/detail/type_traits.hpp" // PLSSVM_REQUIRES, plssvm::detail::remove_cvref_t +#include "plssvm/parameter.hpp" // plssvm::parameter, plssvm::detail::parameter +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include "igor/igor.hpp" // igor::parser + +#include // std::size_t +#include // std::is_same_v, std::true_type +#include // std::forward + +namespace plssvm { + +namespace detail { + +// forward declare execution_range class +class execution_range; + +} // namespace detail + +namespace hipsycl { + +/** + * @brief A C-SVM implementation using hipSYCL as SYCL backend. + */ +class csvm : public ::plssvm::detail::gpu_csvm { + protected: + // protected for the test MOCK class + /// The template base type of the hipSYCL SYCL C-SVM class. + using base_type = ::plssvm::detail::gpu_csvm; + + using base_type::devices_; + + public: + using base_type::device_ptr_type; + using typename base_type::queue_type; + + /** + * @brief Construct a new C-SVM using the SYCL backend with the parameters given through @p params. + * @param[in] params struct encapsulating all possible parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hipsycl::backend_exception if the requested target is not available + * @throws plssvm::hipsycl::backend_exception if no device for the requested target was found + */ + explicit csvm(parameter params = {}); + /** + * @brief Construct a new C-SVM using the SYCL backend on the @p target platform with the parameters given through @p params. + * @param[in] target the target platform used for this C-SVM + * @param[in] params struct encapsulating all possible SVM parameters + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hipsycl::backend_exception if the requested target is not available + * @throws plssvm::hipsycl::backend_exception if no device for the requested target was found + */ + explicit csvm(target_platform target, parameter params = {}); + + /** + * @brief Construct a new C-SVM using the SYCL backend and the optionally provided @p named_args. + * @details Additionally sets the SYCL specific kernel invocation type. + * @param[in] named_args the additional optional named arguments + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hipsycl::backend_exception if the requested target is not available + * @throws plssvm::hipsycl::backend_exception if no device for the requested target was found + */ + template )> + explicit csvm(Args &&...named_args) : + csvm{ plssvm::target_platform::automatic, std::forward(named_args)... } {} + /** + * @brief Construct a new C-SVM using the SYCL backend on the @p target platform and the optionally provided @p named_args. + * @details Additionally sets the SYCL specific kernel invocation type. + * @param[in] target the target platform used for this C-SVM + * @param[in] named_args the additional optional named arguments + * @throws plssvm::exception all exceptions thrown in the base class constructor + * @throws plssvm::hipsycl::backend_exception if the requested target is not available + * @throws plssvm::hipsycl::backend_exception if no device for the requested target was found + */ + template )> + explicit csvm(const target_platform target, Args &&...named_args) : + base_type{ named_args... } { + // check igor parameter + igor::parser parser{ std::forward(named_args)... }; + + // check whether a specific SYCL kernel invocation type has been requested + if constexpr (parser.has(sycl_kernel_invocation_type)) { + // compile time check: the value must have the correct type + static_assert(std::is_same_v<::plssvm::detail::remove_cvref_t, sycl::kernel_invocation_type>, "Provided sycl_kernel_invocation_type must be convertible to a plssvm::sycl::kernel_invocation_type!"); + invocation_type_ = static_cast(parser(sycl_kernel_invocation_type)); + } + this->init(target); + } + + /** + * @copydoc plssvm::csvm::csvm(const plssvm::csvm &) + */ + csvm(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::csvm(plssvm::csvm &&) noexcept + */ + csvm(csvm &&) noexcept = default; + /** + * @copydoc plssvm::csvm::operator=(const plssvm::csvm &) + */ + csvm &operator=(const csvm &) = delete; + /** + * @copydoc plssvm::csvm::operator=(plssvm::csvm &&) noexcept + */ + csvm &operator=(csvm &&) noexcept = default; + /** + * @brief Wait for all operations in all [`sycl::queue`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:interface.queue.class) to finish. + * @details Terminates the program, if any asynchronous exception is thrown. + */ + ~csvm() override; + + /** + * @brief Return the kernel invocation type (nd_range or the SYCL specific hierarchical kernel) used in this SYCL SVM. + * @return the SYCL kernel invocation type (`[[nodiscard]]`) + */ + [[nodiscard]] sycl::kernel_invocation_type get_kernel_invocation_type() const noexcept { return invocation_type_; } + + protected: + /** + * @copydoc plssvm::detail::gpu_csvm::device_synchronize + */ + void device_synchronize(const queue_type &queue) const final; + + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + void run_q_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_q_kernel_impl(device, range, params, q_d, data_d, data_last_d, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_q_kernel + */ + template + void run_q_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &q_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, float QA_cost, float add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + void run_svm_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, double QA_cost, double add, std::size_t num_data_points_padded, std::size_t num_features) const final { this->run_svm_kernel_impl(device, range, params, q_d, r_d, x_d, data_d, QA_cost, add, num_data_points_padded, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_svm_kernel + */ + template + void run_svm_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, const device_ptr_type &q_d, device_ptr_type &r_d, const device_ptr_type &x_d, const device_ptr_type &data_d, real_type QA_cost, real_type add, std::size_t num_data_points_padded, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + void run_w_kernel(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const final { this->run_w_kernel_impl(device, range, w_d, alpha_d, data_d, data_last_d, num_data_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_w_kernel + */ + template + void run_w_kernel_impl(std::size_t device, const ::plssvm::detail::execution_range &range, device_ptr_type &w_d, const device_ptr_type &alpha_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_data_points, std::size_t num_features) const; + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + void run_predict_kernel(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const final { this->run_predict_kernel_impl(range, params, out_d, alpha_d, point_d, data_d, data_last_d, num_support_vectors, num_predict_points, num_features); } + /** + * @copydoc plssvm::detail::gpu_csvm::run_predict_kernel + */ + template + void run_predict_kernel_impl(const ::plssvm::detail::execution_range &range, const ::plssvm::detail::parameter ¶ms, device_ptr_type &out_d, const device_ptr_type &alpha_d, const device_ptr_type &point_d, const device_ptr_type &data_d, const device_ptr_type &data_last_d, std::size_t num_support_vectors, std::size_t num_predict_points, std::size_t num_features) const; + + private: + /** + * @brief Initialize all important states related to the SYCL backend. + * @param[in] target the target platform to use + * @throws plssvm::hipsycl::backend_exception if the requested target is not available + * @throws plssvm::hipsycl::backend_exception if no device for the requested target was found + */ + void init(target_platform target); + + /// The SYCL kernel invocation type for the svm kernel. Either nd_range or hierarchical. + sycl::kernel_invocation_type invocation_type_{ sycl::kernel_invocation_type::automatic }; +}; +} // namespace hipsycl + +namespace detail { + +/** + * @brief Sets the `value` to `true` since C-SVMs using the SYCL backend with hipSYCL as SYCL implementation are available. + */ +template <> +struct csvm_backend_exists : std::true_type {}; + +} // namespace detail + +} // namespace plssvm + +#endif // PLSSVM_BACKENDS_SYCL_HIPSYCL_CSVM_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/hipSYCL/detail/device_ptr.hpp b/include/plssvm/backends/SYCL/hipSYCL/detail/device_ptr.hpp new file mode 100644 index 000000000..b877aea4c --- /dev/null +++ b/include/plssvm/backends/SYCL/hipSYCL/detail/device_ptr.hpp @@ -0,0 +1,105 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Small wrapper around a SYCL device pointer for the hipSYCL SYCL implementation. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_DEVICE_PTR_HPP_ +#define PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_DEVICE_PTR_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/hipSYCL/detail/queue.hpp" // plssvm::hipsycl::detail::queue (PImpl) +#include "plssvm/backends/gpu_device_ptr.hpp" // plssvm::detail::gpu_device_ptr + +namespace plssvm::hipsycl::detail { + +/** + * @brief Small wrapper class around a hipSYCL device pointer together with commonly used device functions. + * @tparam T the type of the kernel pointer to wrap + */ +template +class device_ptr : public ::plssvm::detail::gpu_device_ptr { + /// The template base type of the hipSYCL device_ptr class. + using base_type = ::plssvm::detail::gpu_device_ptr; + + using base_type::data_; + using base_type::queue_; + using base_type::size_; + + public: + // Be able to use overloaded base class functions. + using base_type::memset; + using base_type::fill; + using base_type::copy_to_device; + using base_type::copy_to_host; + + using typename base_type::const_host_pointer_type; + using typename base_type::device_pointer_type; + using typename base_type::host_pointer_type; + using typename base_type::queue_type; + using typename base_type::size_type; + using typename base_type::value_type; + + /** + * @brief Default construct a device_ptr with a size of 0. + */ + device_ptr() = default; + /** + * @brief Allocates `size * sizeof(T)` bytes on the device associated with @p q. + * @param[in] size the number of elements represented by the device_ptr + * @param[in] q the associated SYCL queue + */ + device_ptr(size_type size, const queue &q); + + /** + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(const plssvm::detail::gpu_device_ptr &) + */ + device_ptr(const device_ptr &) = delete; + /** + * @copydoc plssvm::detail::gpu_device_ptr::gpu_device_ptr(plssvm::detail::gpu_device_ptr &&) + */ + device_ptr(device_ptr &&other) noexcept = default; + + /** + * @copydoc plssvm::detail::gpu_device_ptr::operator=(const plssvm::detail::gpu_device_ptr &) + */ + device_ptr &operator=(const device_ptr &) = delete; + /** + * @copydoc plssvm::detail::gpu_device_ptr::operator=(plssvm::detail::gpu_device_ptr &&) + */ + device_ptr &operator=(device_ptr &&other) noexcept = default; + + /** + * @copydoc plssvm::detail::gpu_device_ptr::~gpu_device_ptr() + */ + ~device_ptr() override; + + /** + * @copydoc plssvm::detail::gpu_device_ptr::memset(int, size_type, size_type) + */ + void memset(int pattern, size_type pos, size_type num_bytes) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::fill(value_type, size_type, size_type) + */ + void fill(value_type value, size_type pos, size_type count) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_device(const_host_pointer_type, size_type, size_type) + */ + void copy_to_device(const_host_pointer_type data_to_copy, size_type pos, size_type count) override; + /** + * @copydoc plssvm::detail::gpu_device_ptr::copy_to_host(host_pointer_type, size_type, size_type) const + */ + void copy_to_host(host_pointer_type buffer, size_type pos, size_type count) const override; +}; + +extern template class device_ptr; +extern template class device_ptr; + +} // namespace plssvm::hipsycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_DEVICE_PTR_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/hipSYCL/detail/queue.hpp b/include/plssvm/backends/SYCL/hipSYCL/detail/queue.hpp new file mode 100644 index 000000000..4dea93b68 --- /dev/null +++ b/include/plssvm/backends/SYCL/hipSYCL/detail/queue.hpp @@ -0,0 +1,33 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief PImpl class used to hide the "sycl/sycl.hpp" header from the public interface. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_QUEUE_HPP_ +#define PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_QUEUE_HPP_ +#pragma once + +#include // std::shared_ptr + +namespace plssvm::hipsycl::detail { + +/** + * @brief PImpl class to hide the SYCL queue class from the public interface (and, therefore, the "sycl/sycl.hpp" header). + */ +class queue { + public: + /// The struct used in the PImpl idiom (encapsulates the actual SYCL queue). + struct queue_impl; + /// A pointer to the implementation hidden in a private header. + std::shared_ptr impl{}; +}; + +} // namespace plssvm::hipsycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_QUEUE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/hipSYCL/detail/queue_impl.hpp b/include/plssvm/backends/SYCL/hipSYCL/detail/queue_impl.hpp new file mode 100644 index 000000000..dc8c85dd7 --- /dev/null +++ b/include/plssvm/backends/SYCL/hipSYCL/detail/queue_impl.hpp @@ -0,0 +1,43 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief PImpl implementation encapsulating a single SYCL queue. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_QUEUE_IMPL_HPP_ +#define PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_QUEUE_IMPL_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/hipSYCL/detail/queue.hpp" // plssvm::sycl::detail::queue + +#include "sycl/sycl.hpp" // sycl::queue + +#include // std::forward + +namespace plssvm::hipsycl::detail { + +/** + * @brief The PImpl implementation struct encapsulating a single SYCL queue. + */ +struct queue::queue_impl { + /** + * @brief Construct a SYCL queue by forwarding all parameters in @p args. + * @tparam Args the type of the parameters + * @param[in] args the parameters to construct a SYCL queue + */ + template + explicit queue_impl(Args... args) : + sycl_queue{ std::forward(args)... } {} + + /// The wrapped SYCL queue. + ::sycl::queue sycl_queue; +}; + +} // namespace plssvm::hipsycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_QUEUE_IMPL_HPP_ diff --git a/include/plssvm/backends/SYCL/hipSYCL/detail/utility.hpp b/include/plssvm/backends/SYCL/hipSYCL/detail/utility.hpp new file mode 100644 index 000000000..6ee8e563c --- /dev/null +++ b/include/plssvm/backends/SYCL/hipSYCL/detail/utility.hpp @@ -0,0 +1,53 @@ +/** + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Utility functions specific to the SYCL backend using hipSYCL as SYCL implementation. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_UTILITY_HPP_ +#define PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_UTILITY_HPP_ +#pragma once + +#include "plssvm/backends/SYCL/hipSYCL/detail/queue.hpp" // plssvm::hipsycl::detail::queue (PImpl) +#include "plssvm/target_platforms.hpp" // plssvm::target_platform + +#include // std::pair +#include // std::vector + +namespace plssvm::hipsycl::detail { + +/** + * @brief Returns the list devices matching the target platform @p target and the actually used target platform + * (only interesting if the provided @p target was automatic). + * @details If the selected target platform is `plssvm::target_platform::automatic` the selector tries to find devices in the following order: + * 1. NVIDIA GPUs + * 2. AMD GPUs + * 3. Intel GPUs + * 4. CPUs + * + * @param[in] target the target platform for which the devices must match + * @return the devices and used target platform (`[[nodiscard]]`) + */ +[[nodiscard]] std::pair, target_platform> get_device_list(target_platform target); + +/** + * @brief Wait for the compute device associated with @p q to finish. + * @param[in] q the SYCL queue to synchronize + */ +void device_synchronize(const queue &q); + +/** + * @brief Get the default SYCL queue. + * @details Only used in the tests, but **must** be defined and implemented here! + * @return the default queue (`[[nodiscard]]`) + */ +[[nodiscard]] queue get_default_queue(); + +} // namespace plssvm::hipsycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_HIPSYCL_DETAIL_UTILITY_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/implementation_type.hpp b/include/plssvm/backends/SYCL/implementation_type.hpp index bbf189a3c..e99bb4d48 100644 --- a/include/plssvm/backends/SYCL/implementation_type.hpp +++ b/include/plssvm/backends/SYCL/implementation_type.hpp @@ -1,26 +1,28 @@ /** -* @file -* @author Alexander Van Craen -* @author Marcel Breyer -* @copyright 2018-today The PLSSVM project - All Rights Reserved -* @license This file is part of the PLSSVM project which is released under the MIT license. -* See the LICENSE.md file in the project root for full license information. -* -* @brief Defines all currently supported SYCL implementations. -*/ + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines an enumeration holding all possible SYCL implementations. + */ +#ifndef PLSSVM_BACKENDS_SYCL_IMPLEMENTATION_TYPE_HPP_ +#define PLSSVM_BACKENDS_SYCL_IMPLEMENTATION_TYPE_HPP_ #pragma once #include // forward declare std::ostream and std::istream #include // std::vector -namespace plssvm::sycl_generic { +namespace plssvm::sycl { /** -* @brief Enum class for all possible SYCL kernel invocation types. -*/ + * @brief Enum class for all possible SYCL implementation types. + */ enum class implementation_type { - /** Use the available SYCL implementation. If more than one implementation is available, use PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION. */ + /** Use the available SYCL implementation. If more than one implementation is available, the macro PLSSVM_SYCL_BACKEND_PREFERRED_IMPLEMENTATION must be defined. */ automatic, /** Use [DPC++](https://github.com/intel/llvm) as SYCL implementation. */ dpcpp, @@ -36,19 +38,21 @@ enum class implementation_type { [[nodiscard]] std::vector list_available_sycl_implementations(); /** -* @brief Output the @p impl type to the given output-stream @p out. -* @param[in,out] out the output-stream to write the backend type to -* @param[in] impl the SYCL implementation type -* @return the output-stream -*/ + * @brief Output the @p impl type to the given output-stream @p out. + * @param[in,out] out the output-stream to write the backend type to + * @param[in] impl the SYCL implementation type + * @return the output-stream + */ std::ostream &operator<<(std::ostream &out, implementation_type impl); /** -* @brief Use the input-stream @p in to initialize the @p impl type. -* @param[in,out] in input-stream to extract the backend type from -* @param[in] impl the SYCL implementation type -* @return the input-stream -*/ + * @brief Use the input-stream @p in to initialize the @p impl type. + * @param[in,out] in input-stream to extract the backend type from + * @param[in] impl the SYCL implementation type + * @return the input-stream + */ std::istream &operator>>(std::istream &in, implementation_type &impl); -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl + +#endif // PLSSVM_BACKENDS_SYCL_IMPLEMENTATION_TYPE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/kernel_invocation_type.hpp b/include/plssvm/backends/SYCL/kernel_invocation_type.hpp index e609e484b..d632a4363 100644 --- a/include/plssvm/backends/SYCL/kernel_invocation_type.hpp +++ b/include/plssvm/backends/SYCL/kernel_invocation_type.hpp @@ -6,14 +6,16 @@ * @license This file is part of the PLSSVM project which is released under the MIT license. * See the LICENSE.md file in the project root for full license information. * - * @brief Defines all available kernel invoke types when using SYCL. + * @brief Defines an enumeration holding all possible SYCL kernel invocation types. */ +#ifndef PLSSVM_BACKENDS_SYCL_KERNEL_INVOCATION_TYPE_HPP_ +#define PLSSVM_BACKENDS_SYCL_KERNEL_INVOCATION_TYPE_HPP_ #pragma once #include // forward declare std::ostream and std::istream -namespace plssvm::sycl_generic { +namespace plssvm::sycl { /** * @brief Enum class for all possible SYCL kernel invocation types. @@ -21,9 +23,9 @@ namespace plssvm::sycl_generic { enum class kernel_invocation_type { /** Use the best kernel invocation type for the current SYCL implementation and target hardware platform. */ automatic, - /** Use the [*nd_range* invocation type](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#_parallel_for_invoke). */ + /** Use the [`nd_range` invocation type](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#_parallel_for_invoke). */ nd_range, - /** Use the SYCL specific [hierarchical invocation type](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#_parallel_for_hierarchical_invoke). */ + /** Use the SYCL specific [`hierarchical` invocation type](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#_parallel_for_hierarchical_invoke). */ hierarchical }; @@ -43,4 +45,6 @@ std::ostream &operator<<(std::ostream &out, kernel_invocation_type invocation); */ std::istream &operator>>(std::istream &in, kernel_invocation_type &invocation); -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl + +#endif // PLSSVM_BACKENDS_SYCL_KERNEL_INVOCATION_TYPE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/predict_kernel.hpp b/include/plssvm/backends/SYCL/predict_kernel.hpp index 98031ca52..739097e64 100644 --- a/include/plssvm/backends/SYCL/predict_kernel.hpp +++ b/include/plssvm/backends/SYCL/predict_kernel.hpp @@ -9,14 +9,16 @@ * @brief Defines the functions used for prediction for the C-SVM using the SYCL backend. */ +#ifndef PLSSVM_BACKENDS_SYCL_PREDICT_KERNEL_HPP_ +#define PLSSVM_BACKENDS_SYCL_PREDICT_KERNEL_HPP_ #pragma once -#include "plssvm/backends/SYCL/detail/atomics.hpp" // plssvm::sycl::atomic_op +#include "plssvm/backends/SYCL/detail/atomics.hpp" // plssvm::sycl::detail::atomic_op #include "plssvm/constants.hpp" // plssvm::kernel_index_type, plssvm::THREAD_BLOCK_SIZE, plssvm::INTERNAL_BLOCK_SIZE -#include "sycl/sycl.hpp" // sycl::nd_item, sycl::range, sycl::pow, sycl::exp +#include "sycl/sycl.hpp" // sycl::nd_item, sycl::range, sycl::pow, sycl::exp -namespace plssvm::sycl_generic { +namespace plssvm::sycl::detail { /** * @brief Calculate the `w` vector to speed up the prediction of the labels for data points using the linear kernel function. @@ -59,12 +61,14 @@ class device_kernel_w_linear { } private: + /// @cond Doxygen_suppress real_type *w_d_; const real_type *data_d_; const real_type *data_last_d_; const real_type *alpha_d_; const kernel_index_type num_data_points_; const kernel_index_type num_features_; + /// @endcond }; /** @@ -73,7 +77,7 @@ class device_kernel_w_linear { * @tparam T the type of the data points */ template -class device_kernel_predict_poly { +class device_kernel_predict_polynomial { public: /// The type of the data. using real_type = T; @@ -93,7 +97,7 @@ class device_kernel_predict_poly { * @param[in] gamma the gamma parameter used in the polynomial kernel function * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ - device_kernel_predict_poly(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0) : + device_kernel_predict_polynomial(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const int degree, const real_type gamma, const real_type coef0) : out_d_{ out_d }, data_d_{ data_d }, data_last_d_{ data_last_d }, alpha_d_{ alpha_d }, num_data_points_{ num_data_points }, points_{ points }, num_predict_points_{ num_predict_points }, num_features_{ num_features }, degree_{ degree }, gamma_{ gamma }, coef0_{ coef0 } {} /** @@ -107,7 +111,7 @@ class device_kernel_predict_poly { real_type temp = 0; if (predict_point_index < num_predict_points_) { for (kernel_index_type feature_index = 0; feature_index < num_features_; ++feature_index) { - if (data_point_index == num_data_points_) { + if (data_point_index == num_data_points_ - 1) { temp += data_last_d_[feature_index] * points_[predict_point_index + (num_predict_points_ + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]; } else { temp += data_d_[data_point_index + (num_data_points_ - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] * points_[predict_point_index + (num_predict_points_ + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]; @@ -116,11 +120,12 @@ class device_kernel_predict_poly { temp = alpha_d_[data_point_index] * ::sycl::pow(gamma_ * temp + coef0_, static_cast(degree_)); - atomic_op{ out_d_[predict_point_index] } += temp; + detail::atomic_op{ out_d_[predict_point_index] } += temp; } } private: + /// @cond Doxygen_suppress real_type *out_d_; const real_type *data_d_; const real_type *data_last_d_; @@ -132,6 +137,7 @@ class device_kernel_predict_poly { const int degree_; const real_type gamma_; const real_type coef0_; + /// @endcond }; /** @@ -140,7 +146,7 @@ class device_kernel_predict_poly { * @tparam T the type of the data points */ template -class device_kernel_predict_radial { +class device_kernel_predict_rbf { public: /// The type of the data. using real_type = T; @@ -158,7 +164,7 @@ class device_kernel_predict_radial { * @param[in] num_features the number of features per support vector and point to predict * @param[in] gamma the gamma parameter used in the rbf kernel function */ - device_kernel_predict_radial(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma) : + device_kernel_predict_rbf(real_type *out_d, const real_type *data_d, const real_type *data_last_d, const real_type *alpha_d, const kernel_index_type num_data_points, const real_type *points, const kernel_index_type num_predict_points, const kernel_index_type num_features, const real_type gamma) : out_d_{ out_d }, data_d_{ data_d }, data_last_d_{ data_last_d }, alpha_d_{ alpha_d }, num_data_points_{ num_data_points }, points_{ points }, num_predict_points_{ num_predict_points }, num_features_{ num_features }, gamma_{ gamma } {} /** @@ -172,7 +178,7 @@ class device_kernel_predict_radial { real_type temp = 0; if (predict_point_index < num_predict_points_) { for (kernel_index_type feature_index = 0; feature_index < num_features_; ++feature_index) { - if (data_point_index == num_data_points_) { + if (data_point_index == num_data_points_ - 1) { temp += (data_last_d_[feature_index] - points_[predict_point_index + (num_predict_points_ + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]) * (data_last_d_[feature_index] - points_[predict_point_index + (num_predict_points_ + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]); } else { temp += (data_d_[data_point_index + (num_data_points_ - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] - points_[predict_point_index + (num_predict_points_ + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]) * (data_d_[data_point_index + (num_data_points_ - 1 + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index] - points_[predict_point_index + (num_predict_points_ + THREAD_BLOCK_SIZE * INTERNAL_BLOCK_SIZE) * feature_index]); @@ -181,11 +187,12 @@ class device_kernel_predict_radial { temp = alpha_d_[data_point_index] * ::sycl::exp(-gamma_ * temp); - atomic_op{ out_d_[predict_point_index] } += temp; + detail::atomic_op{ out_d_[predict_point_index] } += temp; } } private: + /// @cond Doxygen_suppress real_type *out_d_; const real_type *data_d_; const real_type *data_last_d_; @@ -195,6 +202,9 @@ class device_kernel_predict_radial { const kernel_index_type num_predict_points_; const kernel_index_type num_features_; const real_type gamma_; + /// @endcond }; -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_PREDICT_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/q_kernel.hpp b/include/plssvm/backends/SYCL/q_kernel.hpp index 78e0830e5..b650c4c8a 100644 --- a/include/plssvm/backends/SYCL/q_kernel.hpp +++ b/include/plssvm/backends/SYCL/q_kernel.hpp @@ -9,13 +9,15 @@ * @brief Defines SYCL functions for generating the `q` vector. */ +#ifndef PLSSVM_BACKENDS_SYCL_Q_KERNEL_HPP_ +#define PLSSVM_BACKENDS_SYCL_Q_KERNEL_HPP_ #pragma once #include "plssvm/constants.hpp" // plssvm::kernel_index_type -#include "sycl/sycl.hpp" // sycl::nd_item, sycl::pow, sycl::exp +#include "sycl/sycl.hpp" // sycl::nd_item, sycl::pow, sycl::exp -namespace plssvm::sycl_generic { +namespace plssvm::sycl::detail { /** * @brief Functor to calculate the `q` vector using the linear C-SVM kernel. @@ -53,11 +55,13 @@ class device_kernel_q_linear { } private: + /// @cond Doxygen_suppress real_type *q_; const real_type *data_d_; const real_type *data_last_; const kernel_index_type num_rows_; const kernel_index_type feature_range_; + /// @endcond }; /** @@ -66,7 +70,7 @@ class device_kernel_q_linear { * @tparam T the type of the data */ template -class device_kernel_q_poly { +class device_kernel_q_polynomial { public: /// The type of the data. using real_type = T; @@ -82,7 +86,7 @@ class device_kernel_q_poly { * @param[in] gamma the gamma parameter used in the polynomial kernel function * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ - device_kernel_q_poly(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0) : + device_kernel_q_polynomial(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const int degree, const real_type gamma, const real_type coef0) : q_{ q }, data_d_{ data_d }, data_last_{ data_last }, num_rows_{ num_rows }, num_cols_{ num_cols }, degree_{ degree }, gamma_{ gamma }, coef0_{ coef0 } {} /** @@ -99,6 +103,7 @@ class device_kernel_q_poly { } private: + /// @cond Doxygen_suppress real_type *q_; const real_type *data_d_; const real_type *data_last_; @@ -107,6 +112,7 @@ class device_kernel_q_poly { const int degree_; const real_type gamma_; const real_type coef0_; + /// @endcond }; /** @@ -115,7 +121,7 @@ class device_kernel_q_poly { * @tparam T the type of the data */ template -class device_kernel_q_radial { +class device_kernel_q_rbf { public: /// The type of the data. using real_type = T; @@ -129,7 +135,7 @@ class device_kernel_q_radial { * @param[in] num_cols the number of columns in the data matrix * @param[in] gamma the gamma parameter used in the rbf kernel function */ - device_kernel_q_radial(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma) : + device_kernel_q_rbf(real_type *q, const real_type *data_d, const real_type *data_last, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type gamma) : q_{ q }, data_d_{ data_d }, data_last_{ data_last }, num_rows_{ num_rows }, num_cols_{ num_cols }, gamma_{ gamma } {} /** @@ -146,12 +152,16 @@ class device_kernel_q_radial { } private: + /// @cond Doxygen_suppress real_type *q_; const real_type *data_d_; const real_type *data_last_; const kernel_index_type num_rows_; const kernel_index_type num_cols_; const real_type gamma_; + /// @endcond }; -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_Q_KERNEL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/svm_kernel_hierarchical.hpp b/include/plssvm/backends/SYCL/svm_kernel_hierarchical.hpp index c50b38ea6..9410674ff 100644 --- a/include/plssvm/backends/SYCL/svm_kernel_hierarchical.hpp +++ b/include/plssvm/backends/SYCL/svm_kernel_hierarchical.hpp @@ -9,17 +9,18 @@ * @brief Defines the kernel functions for the C-SVM using the SYCL backend. */ +#ifndef PLSSVM_BACKENDS_SYCL_SVM_KERNEL_HIERARCHICAL_HPP_ +#define PLSSVM_BACKENDS_SYCL_SVM_KERNEL_HIERARCHICAL_HPP_ #pragma once -#include "plssvm/backends/SYCL/detail/atomics.hpp" // plssvm::sycl::atomic_op -#include "plssvm/constants.hpp" // plssvm::kernel_index_type, plssvm::THREAD_BLOCK_SIZE, plssvm::INTERNAL_BLOCK_SIZE -#include "plssvm/detail/execution_range.hpp" // plssvm::detail::execution_range +#include "plssvm/backends/SYCL/detail/atomics.hpp" // plssvm::sycl::detail::atomic_op +#include "plssvm/constants.hpp" // plssvm::kernel_index_type, plssvm::THREAD_BLOCK_SIZE, plssvm::INTERNAL_BLOCK_SIZE -#include "sycl/sycl.hpp" // sycl::queue, sycl::handler, sycl::h_item, sycl::range, sycl::private_memory, sycl::pow, sycl::exp +#include "sycl/sycl.hpp" // sycl::queue, sycl::handler, sycl::h_item, sycl::range, sycl::private_memory, sycl::pow, sycl::exp -#include // std::size_t +#include // std::size_t (cant' use kernel_index_type because of comparisons with unsigned long values) -namespace plssvm::sycl_generic { +namespace plssvm::sycl::detail { /** * @brief Calculates the C-SVM kernel using the hierarchical formulation and the linear kernel function. @@ -147,7 +148,7 @@ class hierarchical_device_kernel_linear { } if (private_i(idx) + x > private_j(idx) + y) { // upper triangular matrix - atomic_op{ ret_[private_i(idx) + y] } += temp * d_[private_j(idx) + x]; + detail::atomic_op{ ret_[private_i(idx) + y] } += temp * d_[private_j(idx) + x]; ret_jx += temp * d_[private_i(idx) + y]; } else if (private_i(idx) + x == private_j(idx) + y) { // diagonal @@ -158,13 +159,14 @@ class hierarchical_device_kernel_linear { } } } - atomic_op{ ret_[private_j(idx) + x] } += ret_jx; + detail::atomic_op{ ret_[private_j(idx) + x] } += ret_jx; } } }); } private: + /// @cond Doxygen_suppress const real_type *q_; real_type *ret_; const real_type *d_; @@ -175,6 +177,7 @@ class hierarchical_device_kernel_linear { const kernel_index_type feature_range_; const real_type add_; const kernel_index_type device_; + /// @endcond }; /** @@ -183,7 +186,7 @@ class hierarchical_device_kernel_linear { * @tparam T the type of the data */ template -class hierarchical_device_kernel_poly { +class hierarchical_device_kernel_polynomial { public: /// The type of the data. using real_type = T; @@ -203,7 +206,7 @@ class hierarchical_device_kernel_poly { * @param[in] gamma the gamma parameter used in the polynomial kernel function * @param[in] coef0 the coef0 parameter used in the polynomial kernel function */ - hierarchical_device_kernel_poly(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) : + hierarchical_device_kernel_polynomial(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) : q_{ q }, ret_{ ret }, d_{ d }, data_d_{ data_d }, QA_cost_{ QA_cost }, cost_{ cost }, num_rows_{ num_rows }, num_cols_{ num_cols }, add_{ add }, degree_{ degree }, gamma_{ gamma }, coef0_{ coef0 } {} /** @@ -300,20 +303,21 @@ class hierarchical_device_kernel_poly { const real_type temp = (::sycl::pow(gamma_ * private_matr(idx)[x][y] + coef0_, static_cast(degree_)) + QA_cost_ - q_[private_i(idx) + y] - q_[private_j(idx) + x]) * add_; if (private_i(idx) + x > private_j(idx) + y) { // upper triangular matrix - atomic_op{ ret_[private_i(idx) + y] } += temp * d_[private_j(idx) + x]; + detail::atomic_op{ ret_[private_i(idx) + y] } += temp * d_[private_j(idx) + x]; ret_jx += temp * d_[private_i(idx) + y]; } else if (private_i(idx) + x == private_j(idx) + y) { // diagonal ret_jx += (temp + cost_ * add_) * d_[private_i(idx) + y]; } } - atomic_op{ ret_[private_j(idx) + x] } += ret_jx; + detail::atomic_op{ ret_[private_j(idx) + x] } += ret_jx; } } }); } private: + /// @cond Doxygen_suppress const real_type *q_; real_type *ret_; const real_type *d_; @@ -326,6 +330,7 @@ class hierarchical_device_kernel_poly { const int degree_; const real_type gamma_; const real_type coef0_; + /// @endcond }; /** @@ -334,7 +339,7 @@ class hierarchical_device_kernel_poly { * @tparam T the type of the data */ template -class hierarchical_device_kernel_radial { +class hierarchical_device_kernel_rbf { public: /// The type of the data. using real_type = T; @@ -352,7 +357,7 @@ class hierarchical_device_kernel_radial { * @param[in] add denotes whether the values are added or subtracted from the result vector * @param[in] gamma the gamma parameter used in the rbf kernel function */ - hierarchical_device_kernel_radial(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) : + hierarchical_device_kernel_rbf(const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) : q_{ q }, ret_{ ret }, d_{ d }, data_d_{ data_d }, QA_cost_{ QA_cost }, cost_{ cost }, num_rows_{ num_rows }, num_cols_{ num_cols }, add_{ add }, gamma_{ gamma } {} /** @@ -449,20 +454,21 @@ class hierarchical_device_kernel_radial { const real_type temp = (::sycl::exp(-gamma_ * private_matr(idx)[x][y]) + QA_cost_ - q_[private_i(idx) + y] - q_[private_j(idx) + x]) * add_; if (private_i(idx) + x > private_j(idx) + y) { // upper triangular matrix - atomic_op{ ret_[private_i(idx) + y] } += temp * d_[private_j(idx) + x]; + detail::atomic_op{ ret_[private_i(idx) + y] } += temp * d_[private_j(idx) + x]; ret_jx += temp * d_[private_i(idx) + y]; } else if (private_i(idx) + x == private_j(idx) + y) { // diagonal ret_jx += (temp + cost_ * add_) * d_[private_i(idx) + y]; } } - atomic_op{ ret_[private_j(idx) + x] } += ret_jx; + detail::atomic_op{ ret_[private_j(idx) + x] } += ret_jx; } } }); } private: + /// @cond Doxygen_suppress const real_type *q_; real_type *ret_; const real_type *d_; @@ -473,6 +479,9 @@ class hierarchical_device_kernel_radial { const kernel_index_type num_cols_; const real_type add_; const real_type gamma_; + /// @endcond }; -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_SVM_KERNEL_HIERARCHICAL_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/SYCL/svm_kernel_nd_range.hpp b/include/plssvm/backends/SYCL/svm_kernel_nd_range.hpp index 6f66ca3a3..9d2f01cec 100644 --- a/include/plssvm/backends/SYCL/svm_kernel_nd_range.hpp +++ b/include/plssvm/backends/SYCL/svm_kernel_nd_range.hpp @@ -1,39 +1,32 @@ /** -* @file -* @author Alexander Van Craen -* @author Marcel Breyer -* @copyright 2018-today The PLSSVM project - All Rights Reserved -* @license This file is part of the PLSSVM project which is released under the MIT license. -* See the LICENSE.md file in the project root for full license information. -* -* @brief Defines the kernel functions for the C-SVM in the nd_range formulation using the SYCL backend. -*/ - + * @file + * @author Alexander Van Craen + * @author Marcel Breyer + * @copyright 2018-today The PLSSVM project - All Rights Reserved + * @license This file is part of the PLSSVM project which is released under the MIT license. + * See the LICENSE.md file in the project root for full license information. + * + * @brief Defines the kernel functions for the C-SVM in the nd_range formulation using the SYCL backend. + */ + +#ifndef PLSSVM_BACKENDS_SYCL_SVM_KERNEL_ND_RANGE_HPP_ +#define PLSSVM_BACKENDS_SYCL_SVM_KERNEL_ND_RANGE_HPP_ #pragma once -#include "plssvm/backends/SYCL/detail/atomics.hpp" // plssvm::sycl::atomic_op -#include "plssvm/constants.hpp" // plssvm::kernel_index_type, plssvm::THREAD_BLOCK_SIZE, plssvm::INTERNAL_BLOCK_SIZE +#include "plssvm/backends/SYCL/detail/atomics.hpp" // plssvm::sycl::detail::atomic_op +#include "plssvm/constants.hpp" // plssvm::kernel_index_type, plssvm::THREAD_BLOCK_SIZE, plssvm::INTERNAL_BLOCK_SIZE -#include "sycl/sycl.hpp" // sycl::nd_item, sycl::handler, sycl::accessor, sycl::access::mode, sycl::access::target, sycl::range, sycl::group_barrier, sycl::pow, - // sycl::exp, sycl::atomic_ref, sycl::memory_order, sycl::memory_scope, sycl::access::address_space +#include "sycl/sycl.hpp" // sycl::nd_item, sycl::local_accessor, sycl::range, sycl::group_barrier, sycl::pow, sycl::exp, sycl::atomic_ref -#include // std::size_t +#include // std::size_t (cant' use kernel_index_type because of comparisons with unsigned long values) -namespace plssvm::sycl_generic { +namespace plssvm::sycl::detail { -// TODO: change to ::sycl::local_accessor once implemented in the SYCL implementations /** -* @brief Shortcut alias for a SYCL local accessor. -* @tparam T the type of the accessed values -*/ -template -using local_accessor = ::sycl::accessor; - -/** -* @brief Calculates the C-SVM kernel using the nd_range formulation and the linear kernel function. -* @details Supports multi-GPU execution. -* @tparam T the type of the data -*/ + * @brief Calculates the C-SVM kernel using the nd_range formulation and the linear kernel function. + * @details Supports multi-GPU execution. + * @tparam T the type of the data + */ template class nd_range_device_kernel_linear { public: @@ -41,27 +34,27 @@ class nd_range_device_kernel_linear { using real_type = T; /** - * @brief Construct a new device kernel calculating the C-SVM kernel using the linear C-SVM kernel. - * @param[in] cgh [`sycl::handler`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:handlerClass) used to allocate the local memory - * @param[in] q the `q` vector - * @param[out] ret the result vector - * @param[in] d the right-hand side of the equation - * @param[in] data_d the one-dimension data matrix - * @param[in] QA_cost he bottom right matrix entry multiplied by cost - * @param[in] cost 1 / the cost parameter in the C-SVM - * @param[in] num_rows the number of columns in the data matrix - * @param[in] feature_range number of features used for the calculation on the device @p id - * @param[in] add denotes whether the values are added or subtracted from the result vector - * @param[in] id the id of the device - */ + * @brief Construct a new device kernel calculating the C-SVM kernel using the linear C-SVM kernel. + * @param[in] cgh [`sycl::handler`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:handlerClass) used to allocate the local memory + * @param[in] q the `q` vector + * @param[out] ret the result vector + * @param[in] d the right-hand side of the equation + * @param[in] data_d the one-dimension data matrix + * @param[in] QA_cost he bottom right matrix entry multiplied by cost + * @param[in] cost 1 / the cost parameter in the C-SVM + * @param[in] num_rows the number of columns in the data matrix + * @param[in] feature_range number of features used for the calculation on the device @p id + * @param[in] add denotes whether the values are added or subtracted from the result vector + * @param[in] id the id of the device + */ nd_range_device_kernel_linear(::sycl::handler &cgh, const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type feature_range, const real_type add, const kernel_index_type id) : data_intern_i_{ ::sycl::range<2>{ THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE }, cgh }, data_intern_j_{ ::sycl::range<2>{ THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE }, cgh }, q_{ q }, ret_{ ret }, d_{ d }, data_d_{ data_d }, QA_cost_{ QA_cost }, cost_{ cost }, num_rows_{ num_rows }, feature_range_{ feature_range }, add_{ add }, device_{ id } {} /** - * @brief Function call operator overload performing the actual calculation. - * @param[in] nd_idx the [`sycl::nd_item`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#nditem-class) - * identifying an instance of the functor executing at each point in a [`sycl::range`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#range-class) - */ + * @brief Function call operator overload performing the actual calculation. + * @param[in] nd_idx the [`sycl::nd_item`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#nditem-class) + * identifying an instance of the functor executing at each point in a [`sycl::range`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#range-class) + */ void operator()(::sycl::nd_item<2> nd_idx) const { kernel_index_type i = nd_idx.get_group(0) * nd_idx.get_local_range(0) * INTERNAL_BLOCK_SIZE; kernel_index_type j = nd_idx.get_group(1) * nd_idx.get_local_range(1) * INTERNAL_BLOCK_SIZE; @@ -69,7 +62,6 @@ class nd_range_device_kernel_linear { real_type matr[INTERNAL_BLOCK_SIZE][INTERNAL_BLOCK_SIZE] = { { 0.0 } }; real_type data_j[INTERNAL_BLOCK_SIZE] = { { 0.0 } }; - // TODO: profile: warp access optimal? if (nd_idx.get_local_range(0) < THREAD_BLOCK_SIZE && nd_idx.get_local_range(1) == 0) { #pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type block_id = 0; block_id < INTERNAL_BLOCK_SIZE; ++block_id) { @@ -127,7 +119,7 @@ class nd_range_device_kernel_linear { } if (i + x > j + y) { // upper triangular matrix - atomic_op{ ret_[i + y] } += temp * d_[j + x]; + detail::atomic_op{ ret_[i + y] } += temp * d_[j + x]; ret_jx += temp * d_[i + y]; } else if (i + x == j + y) { // diagonal @@ -138,15 +130,18 @@ class nd_range_device_kernel_linear { } } } - atomic_op{ ret_[j + x] } += ret_jx; + detail::atomic_op{ ret_[j + x] } += ret_jx; } } } private: - local_accessor data_intern_i_; - local_accessor data_intern_j_; + /// Local memory used for internal memory access optimizations. + ::sycl::local_accessor data_intern_i_; + /// Local memory used for internal memory access optimizations. + ::sycl::local_accessor data_intern_j_; + /// @cond Doxygen_suppress const real_type *q_; real_type *ret_; const real_type *d_; @@ -157,43 +152,44 @@ class nd_range_device_kernel_linear { const kernel_index_type feature_range_; const real_type add_; const kernel_index_type device_; + /// @endcond }; /** -* @brief Calculates the C-SVM kernel using the nd_range formulation and the polynomial kernel function. -* @details Currently only single GPU execution is supported. -* @tparam T the type of the data -*/ + * @brief Calculates the C-SVM kernel using the nd_range formulation and the polynomial kernel function. + * @details Currently only single GPU execution is supported. + * @tparam T the type of the data + */ template -class nd_range_device_kernel_poly { +class nd_range_device_kernel_polynomial { public: /// The type of the data. using real_type = T; /** - * @brief Construct a new device kernel calculating the C-SVM kernel using the polynomial C-SVM kernel. - * @param[in] cgh [`sycl::handler`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:handlerClass) used to allocate the local memory - * @param[in] q the `q` vector - * @param[out] ret the result vector - * @param[in] d the right-hand side of the equation - * @param[in] data_d the one-dimension data matrix - * @param[in] QA_cost he bottom right matrix entry multiplied by cost - * @param[in] cost 1 / the cost parameter in the C-SVM - * @param[in] num_rows the number of columns in the data matrix - * @param[in] num_cols the number of rows in the data matrix - * @param[in] add denotes whether the values are added or subtracted from the result vector - * @param[in] degree the degree parameter used in the polynomial kernel function - * @param[in] gamma the gamma parameter used in the polynomial kernel function - * @param[in] coef0 the coef0 parameter used in the polynomial kernel function - */ - nd_range_device_kernel_poly(::sycl::handler &cgh, const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) : + * @brief Construct a new device kernel calculating the C-SVM kernel using the polynomial C-SVM kernel. + * @param[in] cgh [`sycl::handler`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:handlerClass) used to allocate the local memory + * @param[in] q the `q` vector + * @param[out] ret the result vector + * @param[in] d the right-hand side of the equation + * @param[in] data_d the one-dimension data matrix + * @param[in] QA_cost he bottom right matrix entry multiplied by cost + * @param[in] cost 1 / the cost parameter in the C-SVM + * @param[in] num_rows the number of columns in the data matrix + * @param[in] num_cols the number of rows in the data matrix + * @param[in] add denotes whether the values are added or subtracted from the result vector + * @param[in] degree the degree parameter used in the polynomial kernel function + * @param[in] gamma the gamma parameter used in the polynomial kernel function + * @param[in] coef0 the coef0 parameter used in the polynomial kernel function + */ + nd_range_device_kernel_polynomial(::sycl::handler &cgh, const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const int degree, const real_type gamma, const real_type coef0) : data_intern_i_{ ::sycl::range<2>{ THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE }, cgh }, data_intern_j_{ ::sycl::range<2>{ THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE }, cgh }, q_{ q }, ret_{ ret }, d_{ d }, data_d_{ data_d }, QA_cost_{ QA_cost }, cost_{ cost }, num_rows_{ num_rows }, num_cols_{ num_cols }, add_{ add }, degree_{ degree }, gamma_{ gamma }, coef0_{ coef0 } {} /** - * @brief Function call operator overload performing the actual calculation. - * @param[in] nd_idx the [`sycl::nd_item`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#nditem-class) - * identifying an instance of the functor executing at each point in a [`sycl::range`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#range-class) - */ + * @brief Function call operator overload performing the actual calculation. + * @param[in] nd_idx the [`sycl::nd_item`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#nditem-class) + * identifying an instance of the functor executing at each point in a [`sycl::range`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#range-class) + */ void operator()(::sycl::nd_item<2> nd_idx) const { kernel_index_type i = nd_idx.get_group(0) * nd_idx.get_local_range(0) * INTERNAL_BLOCK_SIZE; kernel_index_type j = nd_idx.get_group(1) * nd_idx.get_local_range(1) * INTERNAL_BLOCK_SIZE; @@ -201,7 +197,6 @@ class nd_range_device_kernel_poly { real_type matr[INTERNAL_BLOCK_SIZE][INTERNAL_BLOCK_SIZE] = { { 0.0 } }; real_type data_j[INTERNAL_BLOCK_SIZE] = { { 0.0 } }; - // TODO: profile: warp access optimal? if (nd_idx.get_local_range(0) < THREAD_BLOCK_SIZE && nd_idx.get_local_range(1) == 0) { #pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type block_id = 0; block_id < INTERNAL_BLOCK_SIZE; ++block_id) { @@ -254,22 +249,25 @@ class nd_range_device_kernel_poly { const real_type temp = (::sycl::pow(gamma_ * matr[x][y] + coef0_, static_cast(degree_)) + QA_cost_ - q_[i + y] - q_[j + x]) * add_; if (i + x > j + y) { // upper triangular matrix - atomic_op{ ret_[i + y] } += temp * d_[j + x]; + detail::atomic_op{ ret_[i + y] } += temp * d_[j + x]; ret_jx += temp * d_[i + y]; } else if (i + x == j + y) { // diagonal ret_jx += (temp + cost_ * add_) * d_[i + y]; } } - atomic_op{ ret_[j + x] } += ret_jx; + detail::atomic_op{ ret_[j + x] } += ret_jx; } } } private: - local_accessor data_intern_i_; - local_accessor data_intern_j_; + /// Local memory used for internal memory access optimizations. + ::sycl::local_accessor data_intern_i_; + /// Local memory used for internal memory access optimizations. + ::sycl::local_accessor data_intern_j_; + /// @cond Doxygen_suppress const real_type *q_; real_type *ret_; const real_type *d_; @@ -282,41 +280,42 @@ class nd_range_device_kernel_poly { const int degree_; const real_type gamma_; const real_type coef0_; + /// @endcond }; /** -* @brief Calculates the C-SVM kernel using the nd_range formulation and the radial basis functions kernel function. -* @details Currently only single GPU execution is supported. -* @tparam T the type of the data -*/ + * @brief Calculates the C-SVM kernel using the nd_range formulation and the radial basis functions kernel function. + * @details Currently only single GPU execution is supported. + * @tparam T the type of the data + */ template -class nd_range_device_kernel_radial { +class nd_range_device_kernel_rbf { public: /// The type of the data. using real_type = T; /** - * @brief Construct a new device kernel calculating the C-SVM kernel using the radial basis functions C-SVM kernel. - * @param[in] cgh [`sycl::handler`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:handlerClass) used to allocate the local memory - * @param[in] q the `q` vector - * @param[out] ret the result vector - * @param[in] d the right-hand side of the equation - * @param[in] data_d the one-dimension data matrix - * @param[in] QA_cost he bottom right matrix entry multiplied by cost - * @param[in] cost 1 / the cost parameter in the C-SVM - * @param[in] num_rows the number of columns in the data matrix - * @param[in] num_cols the number of rows in the data matrix - * @param[in] add denotes whether the values are added or subtracted from the result vector - * @param[in] gamma the gamma parameter used in the rbf kernel function - */ - nd_range_device_kernel_radial(::sycl::handler &cgh, const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) : + * @brief Construct a new device kernel calculating the C-SVM kernel using the radial basis functions C-SVM kernel. + * @param[in] cgh [`sycl::handler`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:handlerClass) used to allocate the local memory + * @param[in] q the `q` vector + * @param[out] ret the result vector + * @param[in] d the right-hand side of the equation + * @param[in] data_d the one-dimension data matrix + * @param[in] QA_cost he bottom right matrix entry multiplied by cost + * @param[in] cost 1 / the cost parameter in the C-SVM + * @param[in] num_rows the number of columns in the data matrix + * @param[in] num_cols the number of rows in the data matrix + * @param[in] add denotes whether the values are added or subtracted from the result vector + * @param[in] gamma the gamma parameter used in the rbf kernel function + */ + nd_range_device_kernel_rbf(::sycl::handler &cgh, const real_type *q, real_type *ret, const real_type *d, const real_type *data_d, const real_type QA_cost, const real_type cost, const kernel_index_type num_rows, const kernel_index_type num_cols, const real_type add, const real_type gamma) : data_intern_i_{ ::sycl::range<2>{ THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE }, cgh }, data_intern_j_{ ::sycl::range<2>{ THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE }, cgh }, q_{ q }, ret_{ ret }, d_{ d }, data_d_{ data_d }, QA_cost_{ QA_cost }, cost_{ cost }, num_rows_{ num_rows }, num_cols_{ num_cols }, add_{ add }, gamma_{ gamma } {} /** - * @brief Function call operator overload performing the actual calculation. - * @param[in] nd_idx the [`sycl::nd_item`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#nditem-class) - * identifying an instance of the functor executing at each point in a [`sycl::range`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#range-class) - */ + * @brief Function call operator overload performing the actual calculation. + * @param[in] nd_idx the [`sycl::nd_item`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#nditem-class) + * identifying an instance of the functor executing at each point in a [`sycl::range`](https://www.khronos.org/registry/SYCL/specs/sycl-2020/html/sycl-2020.html#range-class) + */ void operator()(::sycl::nd_item<2> nd_idx) const { kernel_index_type i = nd_idx.get_group(0) * nd_idx.get_local_range(0) * INTERNAL_BLOCK_SIZE; kernel_index_type j = nd_idx.get_group(1) * nd_idx.get_local_range(1) * INTERNAL_BLOCK_SIZE; @@ -324,7 +323,6 @@ class nd_range_device_kernel_radial { real_type matr[INTERNAL_BLOCK_SIZE][INTERNAL_BLOCK_SIZE] = { { 0.0 } }; real_type data_j[INTERNAL_BLOCK_SIZE] = { { 0.0 } }; - // TODO: profile: warp access optimal? if (nd_idx.get_local_range(0) < THREAD_BLOCK_SIZE && nd_idx.get_local_range(1) == 0) { #pragma unroll INTERNAL_BLOCK_SIZE for (kernel_index_type block_id = 0; block_id < INTERNAL_BLOCK_SIZE; ++block_id) { @@ -377,22 +375,25 @@ class nd_range_device_kernel_radial { const real_type temp = (::sycl::exp(-gamma_ * matr[x][y]) + QA_cost_ - q_[i + y] - q_[j + x]) * add_; if (i + x > j + y) { // upper triangular matrix - atomic_op{ ret_[i + y] } += temp * d_[j + x]; + detail::atomic_op{ ret_[i + y] } += temp * d_[j + x]; ret_jx += temp * d_[i + y]; } else if (i + x == j + y) { // diagonal ret_jx += (temp + cost_ * add_) * d_[i + y]; } } - atomic_op{ ret_[j + x] } += ret_jx; + detail::atomic_op{ ret_[j + x] } += ret_jx; } } } private: - local_accessor data_intern_i_; - local_accessor data_intern_j_; + /// Local memory used for internal memory access optimizations. + ::sycl::local_accessor data_intern_i_; + /// Local memory used for internal memory access optimizations. + ::sycl::local_accessor data_intern_j_; + /// @cond Doxygen_suppress const real_type *q_; real_type *ret_; const real_type *d_; @@ -403,6 +404,9 @@ class nd_range_device_kernel_radial { const kernel_index_type num_cols_; const real_type add_; const real_type gamma_; + /// @endcond }; -} // namespace plssvm::sycl_generic +} // namespace plssvm::sycl::detail + +#endif // PLSSVM_BACKENDS_SYCL_SVM_KERNEL_ND_RANGE_HPP_ \ No newline at end of file diff --git a/include/plssvm/backends/autogenerated/README.md b/include/plssvm/backends/autogenerated/README.md deleted file mode 100644 index 4e4242b2a..000000000 --- a/include/plssvm/backends/autogenerated/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# 'autogenerated' directory - -Since this directory only contains autogenerated files, these files **must not** be changed manually! - -If you want to change the content of the `OpenCL/detail/kernel_source_string.hpp` file, have a look the the -`*.cl` kernels inside the main `include/plssvm/backends/OpenCL/` directory. - -If you want to change the content of any file inside the `DPCPP` or `hipSYLC` directories, have a look at the -files inside the main `include/plssvm/backends/SYCL` directory. \ No newline at end of file diff --git a/include/plssvm/backends/gpu_csvm.hpp b/include/plssvm/backends/gpu_csvm.hpp index 4a81d8564..60f33bcbf 100644 --- a/include/plssvm/backends/gpu_csvm.hpp +++ b/include/plssvm/backends/gpu_csvm.hpp @@ -9,185 +9,726 @@ * @brief Defines the base class for all C-SVM backends using a GPU. Used for code duplication reduction. */ +#ifndef PLSSVM_BACKENDS_GPU_CSVM_HPP_ +#define PLSSVM_BACKENDS_GPU_CSVM_HPP_ #pragma once -#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/constants.hpp" // plssvm::{THREAD_BLOCK_SIZE, INTERNAL_BLOCK_SIZE} +#include "plssvm/csvm.hpp" // plssvm::csvm +#include "plssvm/detail/execution_range.hpp" // plssvm::detail::execution_range +#include "plssvm/detail/layout.hpp" // plssvm::detail::{transform_to_layout, layout_type} +#include "plssvm/detail/logger.hpp" // plssvm::detail::log, plssvm::verbosity_level +#include "plssvm/detail/performance_tracker.hpp" // plssvm::detail::tracking_entry, PLSSVM_DETAIL_PERFORMANCE_TRACKER_ADD_TRACKING_ENTRY +#include "plssvm/parameter.hpp" // plssvm::parameter -#include // std::size_t -#include // std::vector +#include "fmt/chrono.h" // output std::chrono times using {fmt} +#include "fmt/core.h" // fmt::format -namespace plssvm { +#include // std::min, std::all_of, std::adjacent_find +#include // std::chrono::{milliseconds, steady_clock, duration_cast} +#include // std::ceil +#include // std::size_t +#include // std::less_equal +#include // std::clog, std::cout, std::endl +#include // std::tuple, std::make_tuple +#include // std::forward, std::pair, std::move, std::make_pair +#include // std::vector -// forward declare parameter class -template -class parameter; - -namespace detail { - -// forward declare execution_range class -class execution_range; +namespace plssvm::detail { /** * @brief A C-SVM implementation for all GPU backends to reduce code duplication. - * @details Implements all virtual functions defined in plssvm::csvm. The GPU backends only have to implement the actual kernel launches. - * @tparam T the type of the data + * @details Implements all virtual functions defined in plssvm::csvm. The GPU backends only have to implement the actual kernel (launches). * @tparam device_ptr_t the type of the device pointer (dependent on the used backend) * @tparam queue_t the type of the device queue (dependent on the used backend) */ -template -class gpu_csvm : public csvm { - protected: - /// The template base type of the C-SVM class. - using base_type = ::plssvm::csvm; - - using base_type::alpha_ptr_; - using base_type::bias_; - using base_type::coef0_; - using base_type::cost_; - using base_type::data_ptr_; - using base_type::degree_; - using base_type::epsilon_; - using base_type::gamma_; - using base_type::kernel_; - using base_type::num_data_points_; - using base_type::num_features_; - using base_type::print_info_; - using base_type::QA_cost_; - using base_type::target_; - using base_type::value_ptr_; - using base_type::w_; - +template