diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..c9124643 --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +# MATAR style: Google base with adjustments for MATAR/Kokkos idioms +BasedOnStyle: Google + +# --- deviations from stock Google, matching existing MATAR code --- +IndentWidth: 4 +ColumnLimit: 150 +AccessModifierOffset: -4 + +# Never reorder #includes: preserves author-intended ordering +SortIncludes: Never + +# --- MATAR / Kokkos macro handling --- +# Complete-statement macros: prevents mis-indentation of following lines +StatementMacros: + - MATAR_INITIALIZE + - MATAR_FINALIZE + - MATAR_FENCE + - MATAR_MPI_INIT + - MATAR_MPI_FINALIZE + - MATAR_MPI_BARRIER + +# Declaration decorators +AttributeMacros: + - KOKKOS_INLINE_FUNCTION + - KOKKOS_FUNCTION + - KOKKOS_LAMBDA + - KOKKOS_CLASS_LAMBDA + +# Keep the braced loop-body argument of parallel macros formatted as a block: +# penalize packing so each index triple stays on its own line in multi-dim loops +AlignAfterOpenBracket: Align +AllowAllArgumentsOnNextLine: false +BinPackArguments: false + +# Don't let clang-format merge short loop bodies onto one line inside macros +AllowShortBlocksOnASingleLine: Never +AllowShortLambdasOnASingleLine: None + + +# MATAR convention: aligned consecutive assignments (deviation from Google) +AlignConsecutiveAssignments: Consecutive diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..9d583016 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,5 @@ +# Files clang-format must not touch (gitignore-style globs, clang-format >= 18) + +# Heavy preprocessor code: #define continuations and variadic macro dispatch +# tables that clang-format mangles badly. Format by hand. +src/include/macros.h diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a826c860..470dc21d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -6,82 +6,140 @@ on: pull_request: branches: [ "main" ] -#env: -# # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) -# BUILD_TYPE: Debug +# The Kokkos release the bundled submodule is pinned to. Bump here and in the +# submodule together; the verify step below fails the build if they disagree. +env: + KOKKOS_VERSION: "5.2.1" jobs: - # This workflow contains a single job called "build" - build: - # - name: ${{ matrix.config.name }} - runs-on: ${{ matrix.config.os }} + # MATAR without Kokkos: host-only header library (double and float are the + # only precisions supported there). + build-no-kokkos: + name: Ubuntu_GCC_NO_KOKKOS_${{ matrix.real }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - config: - - {name: "Ubuntu_Latest_GCC_SERIAL", os: ubuntu-latest, build_type: "Debug", cc: "gcc", cxx: "g++", matar_config_args: "", kokkos_config_args: ""} - - {name: "Ubuntu_Latest_GCC_KOKKOS_SERIAL", os: ubuntu-latest, build_type: "Debug", cc: "gcc", cxx: "g++", matar_config_args: "-DKOKKOS=ON", kokkos_config_args: "-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17 -DKokkos_ENABLE_SERIAL=ON -DKokkos_ENABLE_TESTS=OFF -DBUILD_TESTING=OFF"} + real: [double, float] + steps: + - uses: actions/checkout@v3 + + - name: Configure + run: | + cmake -B build/no-kokkos \ + -DCMAKE_BUILD_TYPE=Debug \ + -DMATAR_ENABLE_KOKKOS=OFF \ + -DMATAR_REAL=${{ matrix.real }} \ + -DMATAR_BUILD_TESTS=ON \ + -DMATAR_BUILD_EXAMPLES=ON + + - name: Build + run: cmake --build build/no-kokkos -j2 + + - name: Test + run: ctest --test-dir build/no-kokkos --output-on-failure + + # Bundled Kokkos submodule (the default path), plus an install round-trip + # verifying a downstream find_package(Matar) consumer. + build-bundled-kokkos: + name: Ubuntu_GCC_BUNDLED_KOKKOS + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Checkout submodules + run: git submodule update --init --recursive + + - name: Verify bundled Kokkos version + run: | + ver=$(awk '/^set\(Kokkos_VERSION_MAJOR/{m=$2} /^set\(Kokkos_VERSION_MINOR/{n=$2} /^set\(Kokkos_VERSION_PATCH/{p=$2} \ + END{gsub(/\)/,"",m);gsub(/\)/,"",n);gsub(/\)/,"",p);print m"."n"."p}' \ + src/Kokkos/kokkos/CMakeLists.txt) + echo "Bundled Kokkos version: $ver (expected ${KOKKOS_VERSION})" + test "$ver" = "${KOKKOS_VERSION}" || { + echo "::error::Submodule is Kokkos $ver but ${KOKKOS_VERSION} is required; run 'git submodule update --init --recursive'" + exit 1 + } + - name: Configure + run: cmake --preset serial + + - name: Build + run: cmake --build --preset serial -j2 + + - name: Test + run: ctest --preset serial + + - name: Install + run: cmake --install build/serial --prefix ${{ github.workspace }}/install + + - name: Downstream find_package(Matar) smoke test + run: | + mkdir -p consumer + cat > consumer/main.cpp << 'EOF' + #include + int main(int argc, char* argv[]) { + MATAR_INITIALIZE(argc, argv); + { mtr::CArrayDevice a(3, 3); } + MATAR_FINALIZE(); + return 0; + } + EOF + cat > consumer/CMakeLists.txt << 'EOF' + cmake_minimum_required(VERSION 3.22) + project(MatarConsumer LANGUAGES CXX) + find_package(Matar REQUIRED) + add_executable(consumer main.cpp) + target_link_libraries(consumer matar::matar) + EOF + cmake -S consumer -B consumer/build -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install + cmake --build consumer/build -j2 + ./consumer/build/consumer + + # External (pre-installed) Kokkos via MATAR_USE_EXTERNAL_KOKKOS. + build-external-kokkos: + name: Ubuntu_GCC_EXTERNAL_KOKKOS + runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v3 - + - uses: actions/checkout@v3 + - name: Checkout submodules run: git submodule update --init --recursive - - # Kokkos configure, build, and install------------------------------------------ - - if: contains(matrix.config.name, 'KOKKOS') - name: Configure Kokkos CMake - shell: bash + + - name: Verify bundled Kokkos version run: | - cmake \ - -S ${{ github.workspace }}/src/Kokkos/kokkos \ - -B ${{ github.workspace }}/build-kokkos-serial/kokkos \ - -D CMAKE_INSTALL_PREFIX=${{ github.workspace }}/build-kokkos-serial/kokkos \ - ${{ matrix.config.kokkos_config_args }} - - - if: contains(matrix.config.name, 'KOKKOS') - name: Build Kokkos - shell: bash - run: cmake --build ${{ github.workspace }}/build-kokkos-serial/kokkos - - - if: contains(matrix.config.name, 'KOKKOS') - name: Install Kokkos - shell: bash - run: cmake --install ${{ github.workspace }}/build-kokkos-serial/kokkos - - # End Kokkos configure, build, and install------------------------------------------ - - - # MATAR configure and build------------------------------------------ - - if: false == contains(matrix.config.name, 'KOKKOS') - name: Configure MATAR CMake Without Kokkos - shell: bash + ver=$(awk '/^set\(Kokkos_VERSION_MAJOR/{m=$2} /^set\(Kokkos_VERSION_MINOR/{n=$2} /^set\(Kokkos_VERSION_PATCH/{p=$2} \ + END{gsub(/\)/,"",m);gsub(/\)/,"",n);gsub(/\)/,"",p);print m"."n"."p}' \ + src/Kokkos/kokkos/CMakeLists.txt) + echo "Bundled Kokkos version: $ver (expected ${KOKKOS_VERSION})" + test "$ver" = "${KOKKOS_VERSION}" || { + echo "::error::Submodule is Kokkos $ver but ${KOKKOS_VERSION} is required; run 'git submodule update --init --recursive'" + exit 1 + } + + - name: Install Kokkos standalone run: | - cmake \ - -B ${{ github.workspace }}/build-kokkos-serial/matar \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - ${{ matrix.config.matar_config_args }} - - - if: contains(matrix.config.name, 'KOKKOS') - name: Configure MATAR CMake With Kokkos - shell: bash + cmake -S src/Kokkos/kokkos -B build/kokkos \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=20 \ + -DKokkos_ENABLE_SERIAL=ON \ + -DKokkos_ENABLE_TESTS=OFF \ + -DBUILD_TESTING=OFF \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/kokkos-install + cmake --build build/kokkos -j2 + cmake --install build/kokkos + + - name: Configure MATAR against external Kokkos run: | - cmake \ - -B ${{ github.workspace }}/build-kokkos-serial/matar \ - -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ - ${{ matrix.config.matar_config_args }} \ - -DKokkos_DIR=${{ github.workspace }}/build-kokkos-serial/kokkos/lib/cmake/Kokkos - - - name: Build MATAR - shell: bash - run: cmake --build ${{github.workspace}}/build-kokkos-serial/matar - # End MATAR configure and build------------------------------------------ - - #- name: Test - # working-directory: ${{github.workspace}}/build - # # Execute tests defined by the CMake configuration. - # # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - # run: ctest -C ${{env.BUILD_TYPE}} - + cmake -B build/external \ + -DCMAKE_BUILD_TYPE=Debug \ + -DMATAR_USE_EXTERNAL_KOKKOS=ON \ + -DKokkos_ROOT=${{ github.workspace }}/kokkos-install \ + -DMATAR_BUILD_TESTS=ON \ + -DMATAR_BUILD_EXAMPLES=ON + + - name: Build + run: cmake --build build/external -j2 + + - name: Test + run: ctest --test-dir build/external --output-on-failure diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 542c2a70..4c5f834c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,38 +7,63 @@ on: pull_request: branches: [ "main" ] -#env: -# # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - +# The Kokkos release the bundled submodule is pinned to. Bump here and in the +# submodule together; the verify step below fails the build if they disagree. +env: + KOKKOS_VERSION: "5.2.1" jobs: - # This workflow contains a single job called "build" build: - # name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - - {name: "TEST_UBUNTU_SERIAL_DEBUG", os: ubuntu-latest, debug: "enabled", cc: "gcc", cxx: "g++", kokkos_backend: "serial"} - - {name: "TEST_UBUNTU_OPENMP_DEBUG", os: ubuntu-latest, debug: "enabled", cc: "gcc", cxx: "g++", kokkos_backend: "openmp"} - - {name: "TEST_UBUNTU_SERIAL_RELEASE", os: ubuntu-latest, debug: "disabled", cc: "gcc", cxx: "g++", kokkos_backend: "serial"} - - {name: "TEST_UBUNTU_OPENMP_RELEASE", os: ubuntu-latest, debug: "disabled", cc: "gcc", cxx: "g++", kokkos_backend: "openmp"} - # Kokkos + MPI: builds MATAR with MPI, registers MPICArrayKokkos_mpi_suite (mpirun from CTest). - - {name: "TEST_UBUNTU_SERIAL_MPI_DEBUG", os: ubuntu-latest, debug: "enabled", cc: "gcc", cxx: "g++", kokkos_backend: "serial_mpi"} - - {name: "TEST_MAC_SERIAL_DEBUG", os: macos-14, debug: "enabled", cc: "clang", cxx: "clang++", kokkos_backend: "serial"} - - {name: "TEST_MAC_SERIAL_RELEASE", os: macos-14, debug: "disabled", cc: "clang", cxx: "clang++", kokkos_backend: "serial"} + - {name: "TEST_UBUNTU_SERIAL_DEBUG", os: ubuntu-latest, preset: "serial-debug"} + - {name: "TEST_UBUNTU_OPENMP_DEBUG", os: ubuntu-latest, preset: "openmp-debug"} + - {name: "TEST_UBUNTU_SERIAL_RELEASE", os: ubuntu-latest, preset: "serial"} + - {name: "TEST_UBUNTU_OPENMP_RELEASE", os: ubuntu-latest, preset: "openmp"} + # Kokkos + MPI: registers MPICArrayKokkos_mpi_suite (mpirun from CTest). + - {name: "TEST_UBUNTU_SERIAL_MPI_DEBUG", os: ubuntu-latest, preset: "serial-mpi-debug"} + # Precision-tier matrix: every real_t definition testable without a GPU + # (half/bfloat16 run float-emulated on CPU backends) + - {name: "TEST_UBUNTU_SERIAL_FP32", os: ubuntu-latest, preset: "serial-fp32"} + - {name: "TEST_UBUNTU_SERIAL_FP16", os: ubuntu-latest, preset: "serial-fp16"} + - {name: "TEST_UBUNTU_SERIAL_BF16", os: ubuntu-latest, preset: "serial-bf16"} + - {name: "TEST_UBUNTU_SERIAL_QUAD", os: ubuntu-latest, preset: "serial-quad"} + - {name: "TEST_UBUNTU_SERIAL_MIXED", os: ubuntu-latest, preset: "serial-mixed"} + # MPI at non-default tiers: fp16 proves tier plumbing (float-emulated); + # quad exercises the custom MPI datatype + custom MPI_Op reduction paths + - {name: "TEST_UBUNTU_SERIAL_MPI_FP16", os: ubuntu-latest, preset: "serial-mpi-fp16"} + - {name: "TEST_UBUNTU_SERIAL_MPI_QUAD", os: ubuntu-latest, preset: "serial-mpi-quad"} + # Host-side parallel macros via the MATAR_*_BACKEND front door. + # NOTE: CI has no GPU, so this verifies host-macro correctness and the + # option plumbing, not actual host/device overlap. + - {name: "TEST_UBUNTU_HOST_OPENMP", os: ubuntu-latest, preset: "hostomp"} + - {name: "TEST_MAC_SERIAL_DEBUG", os: macos-14, preset: "serial-debug"} + - {name: "TEST_MAC_SERIAL_RELEASE", os: macos-14, preset: "serial"} steps: - name: Checkout repository uses: actions/checkout@v3 - + - name: Checkout submodules run: git submodule update --init --recursive + - name: Verify bundled Kokkos version + run: | + ver=$(awk '/^set\(Kokkos_VERSION_MAJOR/{m=$2} /^set\(Kokkos_VERSION_MINOR/{n=$2} /^set\(Kokkos_VERSION_PATCH/{p=$2} \ + END{gsub(/\)/,"",m);gsub(/\)/,"",n);gsub(/\)/,"",p);print m"."n"."p}' \ + src/Kokkos/kokkos/CMakeLists.txt) + echo "Bundled Kokkos version: $ver (expected ${KOKKOS_VERSION})" + test "$ver" = "${KOKKOS_VERSION}" || { + echo "::error::Submodule is Kokkos $ver but ${KOKKOS_VERSION} is required; run 'git submodule update --init --recursive'" + exit 1 + } + - name: Install Open MPI - if: contains(matrix.config.kokkos_backend, 'mpi') && matrix.config.os == 'ubuntu-latest' + if: contains(matrix.config.preset, 'mpi') && matrix.config.os == 'ubuntu-latest' env: DEBIAN_FRONTEND: noninteractive run: | @@ -49,23 +74,11 @@ jobs: mpi-default-bin \ mpi-default-dev - # Build MATAR tests using the build-matar.sh script - - name: Build MATAR Tests - shell: bash - run: | - cd ${{ github.workspace }}/scripts - source build-matar.sh \ - --execution=test \ - --kokkos_build_type=${{ matrix.config.kokkos_backend }} \ - --build_action=full-app \ - --machine=linux \ - --debug=${{ matrix.config.debug }} \ - --build_cores=1 + - name: Configure + run: cmake --preset ${{ matrix.config.preset }} - # Run the tests - - name: Run Tests - shell: bash - run: | - cd ${{ github.workspace }}/build-matar-${{ matrix.config.kokkos_backend }} - ctest --output-on-failure + - name: Build + run: cmake --build --preset ${{ matrix.config.preset }} -j2 + - name: Run Tests + run: ctest --preset ${{ matrix.config.preset }} diff --git a/.gitignore b/.gitignore index 87400105..1cad4d2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +build/ +CMakeUserPresets.json install-* benchmark/benchmark* build-matar-* @@ -5,6 +7,7 @@ install/* heffte/ docs_doxygen/ docs_sphinx/ -tutorial/getting_started/Example0/build_* -tutorial/getting_started/Example0/install* -examples/mesh_decomp/lib/* \ No newline at end of file +tutorial/getting_started/Example*/build_* +tutorial/getting_started/Example*/install* +examples/mesh_decomp/lib/* +test/googletest/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e6ba416..5c93623e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,111 +1,359 @@ -# Usage of C++17 standard requires CMake version >= 3.8 -cmake_minimum_required(VERSION 3.8) +# 3.22 is the minimum required by the bundled Kokkos 5.x. +# (CUDA builds additionally need CMake >= 3.25.2 for C++20 in the CUDA language.) +cmake_minimum_required(VERSION 3.22) -# Current usage of shared_ptr in MATAR requires C++17 standard -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +project(Matar VERSION 1.0.0 LANGUAGES CXX) +# ----------------------------------------------------------------------------- +# Options +# ----------------------------------------------------------------------------- +option(MATAR_ENABLE_KOKKOS "Enable the Kokkos-backed device/dual MATAR types" ON) +option(MATAR_ENABLE_MPI "Enable the MPI-aware MATAR types (MPICArrayKokkos, CommunicationPlan)" OFF) +option(MATAR_ENABLE_GPU_AWARE_MPI "Assume the MPI implementation is GPU-aware (device buffers passed to MPI)" OFF) +option(MATAR_USE_EXTERNAL_KOKKOS "Use an already-installed Kokkos (find_package) instead of building the bundled submodule" OFF) +option(MATAR_BUILD_EXAMPLES "Build the MATAR example programs" OFF) +option(MATAR_BUILD_TESTS "Build the MATAR unit tests" OFF) +option(MATAR_BUILD_BENCHMARKS "Build the MATAR benchmarks" OFF) +option(MATAR_INSTALL "Generate install/export rules" ${PROJECT_IS_TOP_LEVEL}) -project (MATAR) -set (CMAKE_CXX_STANDARD 17) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# CMAKE_BUILD_TYPE: -# 1. Release: `-O3 -DNDEBUG` -# 2. Debug: `-O0 -g` -# 3. RelWithDebInfo: `-O2 -g -DNDEBUG` -# 4. MinSizeRel: `-Os -DNDEBUG` -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "set default to Release") -endif(NOT CMAKE_BUILD_TYPE) +# Trilinos support has been removed; the old Trilinos-based build lives in legacy/. +if(DEFINED Matar_ENABLE_TRILINOS OR DEFINED Matar_KOKKOS_PACKAGE) + message(FATAL_ERROR + "Trilinos support has been removed from MATAR. " + "See legacy/CMakeLists.txt for the retired Trilinos build.") +endif() +# Map the old option names used by existing consumers (ELEMENTS, Fierro). +if(DEFINED Matar_ENABLE_KOKKOS) + message(DEPRECATION "Matar_ENABLE_KOKKOS is deprecated; use MATAR_ENABLE_KOKKOS") + set(MATAR_ENABLE_KOKKOS ${Matar_ENABLE_KOKKOS}) +endif() +if(DEFINED Matar_ENABLE_MPI) + message(DEPRECATION "Matar_ENABLE_MPI is deprecated; use MATAR_ENABLE_MPI") + set(MATAR_ENABLE_MPI ${Matar_ENABLE_MPI}) +endif() -# Macros and packages -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules/") +if(PROJECT_IS_TOP_LEVEL) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) + endif() +endif() +# ----------------------------------------------------------------------------- +# Vectorization flags (cmake/Modules/FindVector.cmake) +# +# Detects per-compiler auto-vectorization flags and appends them to the Release +# flags for everything compiled in this tree (examples, tests, benchmarks, and +# the bundled Kokkos). Toggles: +# -DCMAKE_VECTOR_NOVEC=ON same flags but with vectorization disabled +# -DCMAKE_VECTOR_VERBOSE=ON compiler vectorization reports +# ----------------------------------------------------------------------------- +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") find_package(Vector) -if (CMAKE_VECTOR_NOVEC) - set(VECTOR_C_FLAGS "${VECTOR_NOVEC_C_FLAGS}") - set(VECTOR_CXX_FLAGS "${VECTOR_NOVEC_CXX_FLAGS}") -endif (CMAKE_VECTOR_NOVEC) -if (CMAKE_VECTOR_VERBOSE) - set(VECTOR_C_FLAGS "${VECTOR_C_FLAGS} ${VECTOR_C_VERBOSE}") - set(VECTOR_CXX_FLAGS "${VECTOR_CXX_FLAGS} ${VECTOR_CXX_VERBOSE}") - set(VECTOR_Fortran_FLAGS "${VECTOR_Fortran_FLAGS} ${VECTOR_Fortran_VERBOSE}") -endif (CMAKE_VECTOR_VERBOSE) - - -# Compiler flags -set(CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_Fortran_FLAGS_RELEASE} ${VECTOR_Fortran_FLAGS}") +if(CMAKE_VECTOR_NOVEC) + set(VECTOR_C_FLAGS "${VECTOR_NOVEC_C_FLAGS}") + set(VECTOR_CXX_FLAGS "${VECTOR_NOVEC_CXX_FLAGS}") +endif() +if(CMAKE_VECTOR_VERBOSE) + set(VECTOR_C_FLAGS "${VECTOR_C_FLAGS} ${VECTOR_C_VERBOSE}") + set(VECTOR_CXX_FLAGS "${VECTOR_CXX_FLAGS} ${VECTOR_CXX_VERBOSE}") +endif() set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${VECTOR_C_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${VECTOR_CXX_FLAGS}") - +# ----------------------------------------------------------------------------- +# The matar target (header-only) +# ----------------------------------------------------------------------------- add_library(matar INTERFACE) -target_include_directories(matar INTERFACE - $ +add_library(matar::matar ALIAS matar) + +# Kokkos 5 requires C++20; MATAR itself is C++17-clean but must match. +target_compile_features(matar INTERFACE cxx_std_20) +target_include_directories(matar INTERFACE + $ $ ) +# ----------------------------------------------------------------------------- +# Floating-point precision tiers +# +# Three independently swappable tiers, fixed at configure time: +# real_t default working precision (MATAR_REAL) +# high_real_t must-stay-accurate fields (MATAR_HIGH_REAL) +# low_real_t tolerant / bulk-storage fields (MATAR_LOW_REAL) +# Values: double (default) | float | half | bfloat16 | quad. +# No-Kokkos builds support only double and float. quad (__float128) is +# host-backend-only and needs Kokkos_ENABLE_LIBQUADMATH. +# ----------------------------------------------------------------------------- +set(MATAR_REAL "double" CACHE STRING "Precision of the real_t tier") +set(MATAR_HIGH_REAL "double" CACHE STRING "Precision of the high_real_t tier") +set(MATAR_LOW_REAL "double" CACHE STRING "Precision of the low_real_t tier") +set_property(CACHE MATAR_REAL PROPERTY STRINGS double float half bfloat16 quad) +set_property(CACHE MATAR_HIGH_REAL PROPERTY STRINGS double float quad) +set_property(CACHE MATAR_LOW_REAL PROPERTY STRINGS double float half bfloat16) + +function(matar_precision_token tier value out_token) + string(TOLOWER "${value}" v) + set(map_double MATAR_FP64) + set(map_float MATAR_FP32) + set(map_half MATAR_FP16) + set(map_bfloat16 MATAR_BF16) + set(map_quad MATAR_FP128) + if(NOT DEFINED map_${v}) + message(FATAL_ERROR "MATAR_${tier}=${value} is not a recognized precision " + "(valid values: double, float, half, bfloat16, quad)") + endif() + if(NOT v MATCHES "^(double|float)$" AND NOT MATAR_ENABLE_KOKKOS) + message(FATAL_ERROR "MATAR_${tier}=${value} requires a Kokkos build; " + "no-Kokkos builds support only double and float") + endif() + if(tier STREQUAL "HIGH_REAL" AND v MATCHES "^(half|bfloat16)$") + message(FATAL_ERROR "MATAR_HIGH_REAL=${value}: the high-precision tier " + "must be double, float, or quad") + endif() + if(v STREQUAL "quad" AND (Kokkos_ENABLE_CUDA OR Kokkos_ENABLE_HIP OR Kokkos_ENABLE_SYCL)) + message(FATAL_ERROR "MATAR_${tier}=quad: __float128 is host-only and " + "cannot be combined with a GPU backend") + endif() + if(v STREQUAL "bfloat16" AND Kokkos_ENABLE_HIP) + message(WARNING "MATAR_${tier}=bfloat16 with the HIP backend may be " + "float-emulated depending on the Kokkos version " + "(check MATAR_BF16_IS_EMULATED at compile time)") + endif() + set(${out_token} ${map_${v}} PARENT_SCOPE) +endfunction() + +matar_precision_token(REAL "${MATAR_REAL}" MATAR_REAL_TOKEN) +matar_precision_token(HIGH_REAL "${MATAR_HIGH_REAL}" MATAR_HIGH_REAL_TOKEN) +matar_precision_token(LOW_REAL "${MATAR_LOW_REAL}" MATAR_LOW_REAL_TOKEN) + +# Emit definitions only for non-default tiers (existing consumers see +# unchanged command lines; precision.h defaults undefined tiers to double). +if(NOT MATAR_REAL STREQUAL "double") + target_compile_definitions(matar INTERFACE MATAR_REAL_TYPE=${MATAR_REAL_TOKEN}) +endif() +if(NOT MATAR_HIGH_REAL STREQUAL "double") + target_compile_definitions(matar INTERFACE MATAR_HIGH_REAL_TYPE=${MATAR_HIGH_REAL_TOKEN}) +endif() +if(NOT MATAR_LOW_REAL STREQUAL "double") + target_compile_definitions(matar INTERFACE MATAR_LOW_REAL_TYPE=${MATAR_LOW_REAL_TOKEN}) +endif() + +if(MATAR_REAL STREQUAL "quad" OR MATAR_HIGH_REAL STREQUAL "quad" OR MATAR_LOW_REAL STREQUAL "quad") + set(MATAR_ANY_QUAD ON) + # Must be in the cache before add_subdirectory(src/Kokkos/kokkos) below + set(Kokkos_ENABLE_LIBQUADMATH ON CACHE BOOL "Required for MATAR quad-precision tiers" FORCE) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_libraries(matar INTERFACE quadmath) + endif() +endif() + +# ----------------------------------------------------------------------------- +# Host / device backend selection +# +# The device backend runs the standard macros (FOR_ALL, DO_ALL, ...); the host +# backend runs the _HOST twins (FOR_ALL_HOST, ...). Choosing different +# backends for the two lets CPU work overlap in-flight GPU kernels. +# +# These are a convenience front door: leaving them empty keeps the historical +# behavior of setting Kokkos_ENABLE_* directly, which existing presets and +# downstream projects (ELEMENTS, Fierro) rely on. +# ----------------------------------------------------------------------------- +set(MATAR_HOST_BACKEND "" CACHE STRING "Host-side parallel backend: serial|openmp|pthreads") +set(MATAR_DEVICE_BACKEND "" CACHE STRING "Device-side parallel backend: serial|openmp|pthreads|cuda|hip|sycl") +set_property(CACHE MATAR_HOST_BACKEND PROPERTY STRINGS serial openmp pthreads) +set_property(CACHE MATAR_DEVICE_BACKEND PROPERTY STRINGS serial openmp pthreads cuda hip sycl) + +function(matar_backend_kokkos_var role value out_var) + string(TOLOWER "${value}" v) + set(map_serial Kokkos_ENABLE_SERIAL) + set(map_openmp Kokkos_ENABLE_OPENMP) + set(map_pthreads Kokkos_ENABLE_THREADS) + set(map_cuda Kokkos_ENABLE_CUDA) + set(map_hip Kokkos_ENABLE_HIP) + set(map_sycl Kokkos_ENABLE_SYCL) + if(NOT DEFINED map_${v}) + message(FATAL_ERROR "MATAR_${role}_BACKEND=${value} is not a recognized backend " + "(valid values: serial, openmp, pthreads" + "${ARGN})") + endif() + if(role STREQUAL "HOST" AND v MATCHES "^(cuda|hip|sycl)$") + message(FATAL_ERROR "MATAR_HOST_BACKEND=${value} is a device backend; the host " + "backend must be serial, openmp, or pthreads") + endif() + if(NOT MATAR_ENABLE_KOKKOS AND NOT v STREQUAL "serial") + message(FATAL_ERROR "MATAR_${role}_BACKEND=${value} requires a Kokkos build " + "(-DMATAR_ENABLE_KOKKOS=ON)") + endif() + set(${out_var} ${map_${v}} PARENT_SCOPE) +endfunction() + +if(MATAR_HOST_BACKEND OR MATAR_DEVICE_BACKEND) + string(TOLOWER "${MATAR_HOST_BACKEND}" _matar_host) + string(TOLOWER "${MATAR_DEVICE_BACKEND}" _matar_dev) + + # Kokkos permits only one non-Serial HOST-parallel space per build, so a + # host/device pair drawn from {openmp, pthreads} cannot coexist. Catch it + # here with an explanation rather than letting Kokkos error out later. + if(_matar_host AND _matar_dev AND NOT _matar_host STREQUAL _matar_dev + AND _matar_host MATCHES "^(openmp|pthreads)$" AND _matar_dev MATCHES "^(openmp|pthreads)$") + message(FATAL_ERROR + "MATAR_HOST_BACKEND=${_matar_host} with MATAR_DEVICE_BACKEND=${_matar_dev}: Kokkos " + "allows only one host-parallel execution space per build, so openmp and pthreads " + "cannot both be enabled. Pair a host backend with a GPU device backend " + "(cuda/hip/sycl), or use the same backend for both.") + endif() + + if(_matar_host) + matar_backend_kokkos_var(HOST "${_matar_host}" _matar_host_var) + set(${_matar_host_var} ON CACHE BOOL "Enabled by MATAR_HOST_BACKEND" FORCE) + endif() + if(_matar_dev) + matar_backend_kokkos_var(DEVICE "${_matar_dev}" _matar_dev_var ", cuda, hip, sycl") + set(${_matar_dev_var} ON CACHE BOOL "Enabled by MATAR_DEVICE_BACKEND" FORCE) + endif() -if(Matar_ENABLE_KOKKOS) - if(Matar_CUDA_BUILD) - find_package(CUDAToolkit REQUIRED) + # Same space on both sides is valid and correct, but the _HOST macros then + # contend with device work instead of overlapping it. + if(_matar_host AND _matar_dev AND _matar_host STREQUAL _matar_dev) + message(WARNING + "MATAR_HOST_BACKEND and MATAR_DEVICE_BACKEND are both '${_matar_host}': the _HOST " + "macros will run correctly but share one execution space with the device macros, " + "so there is no host/device concurrency. Pair a host backend (openmp/pthreads) " + "with a GPU device backend (cuda/hip/sycl) to overlap them.") endif() - if("${Matar_KOKKOS_PACKAGE}" STREQUAL "Trilinos") - find_package(Trilinos REQUIRED) - add_definitions(-DTRILINOS_INTERFACE=1) - elseif(Matar_ENABLE_TRILINOS) - find_package(Trilinos REQUIRED) - add_definitions(-DTRILINOS_INTERFACE=1) +endif() + +# ----------------------------------------------------------------------------- +# Kokkos +# +# Backend selection is done with the standard Kokkos cache variables +# (Kokkos_ENABLE_OPENMP, Kokkos_ENABLE_CUDA, Kokkos_ARCH_*, ...), which the +# bundled submodule build consumes directly. An external Kokkos is used only +# when explicitly requested, so higher-level projects get a Kokkos built to +# match the backend they asked for. +# ----------------------------------------------------------------------------- +if(MATAR_ENABLE_KOKKOS) + if(TARGET Kokkos::kokkos) + # A parent project already provides Kokkos; adding the submodule again + # would define duplicate targets. + set(MATAR_KOKKOS_PROVIDER "parent") + elseif(MATAR_USE_EXTERNAL_KOKKOS) + find_package(Kokkos CONFIG REQUIRED) + set(MATAR_KOKKOS_PROVIDER "external") else() - find_package(Kokkos REQUIRED) - endif() - if (Matar_ENABLE_MPI) - find_package(MPI REQUIRED) - add_definitions(-DHAVE_MPI=1) - if(Matar_ENABLE_TRILINOS) - target_link_libraries(matar INTERFACE Trilinos::all_selected_libs MPI::MPI_CXX) - else() - target_link_libraries(matar INTERFACE Kokkos::kokkos MPI::MPI_CXX) + if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/Kokkos/kokkos/CMakeLists.txt) + message(FATAL_ERROR + "The bundled Kokkos submodule is missing. Run:\n" + " git submodule update --init --recursive\n" + "or point at an installed Kokkos with " + "-DMATAR_USE_EXTERNAL_KOKKOS=ON -DKokkos_ROOT=") endif() - else() - target_link_libraries(matar INTERFACE Kokkos::kokkos) + add_subdirectory(src/Kokkos/kokkos) + set(MATAR_KOKKOS_PROVIDER "bundled") + endif() + + target_link_libraries(matar INTERFACE Kokkos::kokkos) + target_compile_definitions(matar INTERFACE HAVE_KOKKOS=1) + + # Device space: exactly one HAVE_* here, since kokkos_types.h selects + # DefaultExecSpace from this chain. + if(Kokkos_ENABLE_CUDA) + target_compile_definitions(matar INTERFACE HAVE_CUDA=1) + # Silence CUDA compiler warnings + target_compile_options(matar INTERFACE + "$<$:SHELL:-Xcudafe --diag_suppress=177>" + "$<$:SHELL:-Xcudafe --diag_suppress=550>") + elseif(Kokkos_ENABLE_HIP) + target_compile_definitions(matar INTERFACE HAVE_HIP=1) + elseif(Kokkos_ENABLE_SYCL) + target_compile_definitions(matar INTERFACE HAVE_SYCL=1) + elseif(Kokkos_ENABLE_OPENMP) + target_compile_definitions(matar INTERFACE HAVE_OPENMP=1) + elseif(Kokkos_ENABLE_THREADS) + target_compile_definitions(matar INTERFACE HAVE_THREADS=1) + endif() + + # Host space, independent of the device chain above: on a GPU build this + # is what the _HOST macros run in. The macros themselves resolve + # Kokkos::DefaultHostExecutionSpace; these defines are for introspection + # and tests. + if(Kokkos_ENABLE_OPENMP) + target_compile_definitions(matar INTERFACE HAVE_HOST_OPENMP=1) + elseif(Kokkos_ENABLE_THREADS) + target_compile_definitions(matar INTERFACE HAVE_HOST_THREADS=1) endif() - add_definitions(-DHAVE_KOKKOS=1) -elseif(Matar_ENABLE_MPI) - find_package(MPI REQUIRED) + + # quad tiers force LIBQUADMATH on for the bundled build; an external or + # parent-provided Kokkos must already have it. + if(MATAR_ANY_QUAD AND NOT MATAR_KOKKOS_PROVIDER STREQUAL "bundled" AND NOT Kokkos_ENABLE_LIBQUADMATH) + message(FATAL_ERROR + "A MATAR quad-precision tier requires Kokkos built with " + "Kokkos_ENABLE_LIBQUADMATH=ON; the ${MATAR_KOKKOS_PROVIDER} Kokkos lacks it") + endif() +endif() + +# ----------------------------------------------------------------------------- +# MPI +# ----------------------------------------------------------------------------- +if(MATAR_ENABLE_MPI) + find_package(MPI REQUIRED COMPONENTS CXX) target_link_libraries(matar INTERFACE MPI::MPI_CXX) - add_definitions(-DHAVE_MPI=1) + target_compile_definitions(matar INTERFACE HAVE_MPI=1) + if(MATAR_ENABLE_GPU_AWARE_MPI) + target_compile_definitions(matar INTERFACE HAVE_GPU_AWARE_MPI) + endif() endif() -include(CMakePackageConfigHelpers) -write_basic_package_version_file( - "${PROJECT_BINARY_DIR}/MatarConfigVersion.cmake" - VERSION 1.0 - COMPATIBILITY AnyNewerVersion -) +# ----------------------------------------------------------------------------- +# Install / export +# +# When the bundled Kokkos is used, its own install rules run too, so Kokkos is +# installed into the same prefix and MatarConfig's find_dependency(Kokkos) +# resolves against the sibling KokkosConfig.cmake. +# ----------------------------------------------------------------------------- +if(MATAR_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) -install(TARGETS matar - EXPORT MatarTargets - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime -) + install(TARGETS matar EXPORT MatarTargets) + install(DIRECTORY src/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h") -include(CMakePackageConfigHelpers) -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/MatarConfig.cmake.in" - "${PROJECT_BINARY_DIR}/MatarConfig.cmake" - INSTALL_DESTINATION lib/cmake/matar -) + install(EXPORT MatarTargets + NAMESPACE matar:: + FILE MatarTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Matar) + + configure_package_config_file( + cmake/MatarConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/MatarConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Matar) -install(EXPORT MatarTargets DESTINATION lib/cmake/matar) -install(FILES "${PROJECT_BINARY_DIR}/MatarConfigVersion.cmake" - "${PROJECT_BINARY_DIR}/MatarConfig.cmake" - DESTINATION lib/cmake/matar) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/include/ DESTINATION include) + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/MatarConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ARCH_INDEPENDENT) + + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/MatarConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/MatarConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Matar) +endif() +# ----------------------------------------------------------------------------- +# Examples / tests / benchmarks +# ----------------------------------------------------------------------------- +if(MATAR_BUILD_TESTS) + enable_testing() + add_subdirectory(test) +endif() +if(MATAR_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() +if(MATAR_BUILD_BENCHMARKS) + add_subdirectory(benchmark) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..4ee0beac --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,199 @@ +{ + "version": 3, + "cmakeMinimumRequired": { "major": 3, "minor": 22, "patch": 0 }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "MATAR_BUILD_TESTS": "ON", + "MATAR_BUILD_EXAMPLES": "ON" + } + }, + { + "name": "kokkos", + "hidden": true, + "inherits": "base", + "cacheVariables": { + "MATAR_ENABLE_KOKKOS": "ON", + "Kokkos_ENABLE_SERIAL": "ON", + "Kokkos_ARCH_NATIVE": "ON" + } + }, + { + "name": "mpi", + "hidden": true, + "cacheVariables": { "MATAR_ENABLE_MPI": "ON" } + }, + { + "name": "debug", + "hidden": true, + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + + { "name": "serial", "displayName": "Kokkos serial backend", "inherits": "kokkos" }, + { + "name": "openmp", + "displayName": "Kokkos OpenMP backend", + "inherits": "kokkos", + "cacheVariables": { "Kokkos_ENABLE_OPENMP": "ON" } + }, + { + "name": "pthreads", + "displayName": "Kokkos std::threads backend", + "inherits": "kokkos", + "cacheVariables": { "Kokkos_ENABLE_THREADS": "ON" } + }, + { + "name": "cuda", + "displayName": "Kokkos CUDA backend", + "inherits": "kokkos", + "cacheVariables": { + "Kokkos_ENABLE_CUDA": "ON", + "Kokkos_ENABLE_CUDA_CONSTEXPR": "ON", + "Kokkos_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE": "ON" + } + }, + { + "name": "hip", + "displayName": "Kokkos HIP backend", + "inherits": "kokkos", + "cacheVariables": { + "CMAKE_CXX_COMPILER": "hipcc", + "Kokkos_ENABLE_HIP": "ON", + "Kokkos_ENABLE_HIP_RELOCATABLE_DEVICE_CODE": "ON" + } + }, + + { "name": "serial-mpi", "displayName": "Kokkos serial backend + MPI", "inherits": ["serial", "mpi"] }, + { "name": "openmp-mpi", "displayName": "Kokkos OpenMP backend + MPI", "inherits": ["openmp", "mpi"] }, + { "name": "cuda-mpi", "displayName": "Kokkos CUDA backend + MPI", "inherits": ["cuda", "mpi"] }, + { "name": "hip-mpi", "displayName": "Kokkos HIP backend + MPI", "inherits": ["hip", "mpi"] }, + + { "name": "serial-debug", "inherits": ["debug", "serial"] }, + { "name": "openmp-debug", "inherits": ["debug", "openmp"] }, + { "name": "cuda-debug", "inherits": ["debug", "cuda"] }, + { "name": "hip-debug", "inherits": ["debug", "hip"] }, + { "name": "serial-mpi-debug", "inherits": ["debug", "serial-mpi"] }, + + { + "name": "serial-fp32", + "displayName": "Kokkos serial backend, real_t = float", + "inherits": "serial", + "cacheVariables": { "MATAR_REAL": "float" } + }, + { + "name": "serial-fp16", + "displayName": "Kokkos serial backend, real_t = half (float-emulated on CPU)", + "inherits": "serial", + "cacheVariables": { "MATAR_REAL": "half" } + }, + { + "name": "serial-bf16", + "displayName": "Kokkos serial backend, real_t = bfloat16 (float-emulated on CPU)", + "inherits": "serial", + "cacheVariables": { "MATAR_REAL": "bfloat16" } + }, + { + "name": "serial-quad", + "displayName": "Kokkos serial backend, real_t = quad (__float128)", + "inherits": "serial", + "cacheVariables": { "MATAR_REAL": "quad" } + }, + { + "name": "serial-mixed", + "displayName": "Kokkos serial backend, mixed tiers (float/double/half)", + "inherits": "serial", + "cacheVariables": { + "MATAR_REAL": "float", + "MATAR_HIGH_REAL": "double", + "MATAR_LOW_REAL": "half" + } + }, + { + "name": "serial-mpi-fp16", + "displayName": "Kokkos serial backend + MPI, real_t = half (float-emulated on CPU)", + "inherits": "serial-mpi", + "cacheVariables": { "MATAR_REAL": "half" } + }, + { + "name": "serial-mpi-quad", + "displayName": "Kokkos serial backend + MPI, real_t = quad (custom MPI datatype/op)", + "inherits": "serial-mpi", + "cacheVariables": { "MATAR_REAL": "quad" } + }, + { + "name": "hostomp", + "displayName": "OpenMP host + device backends via the MATAR_*_BACKEND front door", + "inherits": "base", + "cacheVariables": { + "MATAR_ENABLE_KOKKOS": "ON", + "Kokkos_ARCH_NATIVE": "ON", + "MATAR_HOST_BACKEND": "openmp", + "MATAR_DEVICE_BACKEND": "openmp" + } + }, + { + "name": "cuda-hostomp", + "displayName": "CUDA device + OpenMP host: concurrent host/device execution", + "inherits": "base", + "cacheVariables": { + "MATAR_ENABLE_KOKKOS": "ON", + "Kokkos_ARCH_NATIVE": "ON", + "MATAR_HOST_BACKEND": "openmp", + "MATAR_DEVICE_BACKEND": "cuda", + "Kokkos_ENABLE_CUDA_CONSTEXPR": "ON", + "Kokkos_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE": "ON" + } + } + ], + "buildPresets": [ + { "name": "serial", "configurePreset": "serial" }, + { "name": "openmp", "configurePreset": "openmp" }, + { "name": "pthreads", "configurePreset": "pthreads" }, + { "name": "cuda", "configurePreset": "cuda" }, + { "name": "hip", "configurePreset": "hip" }, + { "name": "serial-mpi", "configurePreset": "serial-mpi" }, + { "name": "openmp-mpi", "configurePreset": "openmp-mpi" }, + { "name": "cuda-mpi", "configurePreset": "cuda-mpi" }, + { "name": "hip-mpi", "configurePreset": "hip-mpi" }, + { "name": "serial-debug", "configurePreset": "serial-debug" }, + { "name": "openmp-debug", "configurePreset": "openmp-debug" }, + { "name": "cuda-debug", "configurePreset": "cuda-debug" }, + { "name": "hip-debug", "configurePreset": "hip-debug" }, + { "name": "serial-mpi-debug", "configurePreset": "serial-mpi-debug" }, + { "name": "serial-fp32", "configurePreset": "serial-fp32" }, + { "name": "serial-fp16", "configurePreset": "serial-fp16" }, + { "name": "serial-bf16", "configurePreset": "serial-bf16" }, + { "name": "serial-quad", "configurePreset": "serial-quad" }, + { "name": "serial-mixed", "configurePreset": "serial-mixed" }, + { "name": "serial-mpi-fp16", "configurePreset": "serial-mpi-fp16" }, + { "name": "serial-mpi-quad", "configurePreset": "serial-mpi-quad" }, + { "name": "hostomp", "configurePreset": "hostomp" }, + { "name": "cuda-hostomp", "configurePreset": "cuda-hostomp" } + ], + "testPresets": [ + { "name": "serial", "configurePreset": "serial", "output": { "outputOnFailure": true } }, + { "name": "openmp", "configurePreset": "openmp", "output": { "outputOnFailure": true } }, + { "name": "pthreads", "configurePreset": "pthreads", "output": { "outputOnFailure": true } }, + { "name": "cuda", "configurePreset": "cuda", "output": { "outputOnFailure": true } }, + { "name": "hip", "configurePreset": "hip", "output": { "outputOnFailure": true } }, + { "name": "serial-mpi", "configurePreset": "serial-mpi", "output": { "outputOnFailure": true } }, + { "name": "openmp-mpi", "configurePreset": "openmp-mpi", "output": { "outputOnFailure": true } }, + { "name": "cuda-mpi", "configurePreset": "cuda-mpi", "output": { "outputOnFailure": true } }, + { "name": "hip-mpi", "configurePreset": "hip-mpi", "output": { "outputOnFailure": true } }, + { "name": "serial-debug", "configurePreset": "serial-debug", "output": { "outputOnFailure": true } }, + { "name": "openmp-debug", "configurePreset": "openmp-debug", "output": { "outputOnFailure": true } }, + { "name": "serial-mpi-debug", "configurePreset": "serial-mpi-debug", "output": { "outputOnFailure": true } }, + { "name": "serial-fp32", "configurePreset": "serial-fp32", "output": { "outputOnFailure": true } }, + { "name": "serial-fp16", "configurePreset": "serial-fp16", "output": { "outputOnFailure": true } }, + { "name": "serial-bf16", "configurePreset": "serial-bf16", "output": { "outputOnFailure": true } }, + { "name": "serial-quad", "configurePreset": "serial-quad", "output": { "outputOnFailure": true } }, + { "name": "serial-mixed", "configurePreset": "serial-mixed", "output": { "outputOnFailure": true } }, + { "name": "serial-mpi-fp16", "configurePreset": "serial-mpi-fp16", "output": { "outputOnFailure": true } }, + { "name": "serial-mpi-quad", "configurePreset": "serial-mpi-quad", "output": { "outputOnFailure": true } }, + { "name": "hostomp", "configurePreset": "hostomp", "output": { "outputOnFailure": true } } + ] +} diff --git a/MATAR_LLM_CONTEXT.md b/MATAR_LLM_CONTEXT.md index d66a31fd..fd2c8fe0 100644 --- a/MATAR_LLM_CONTEXT.md +++ b/MATAR_LLM_CONTEXT.md @@ -1177,10 +1177,47 @@ FOR_ALL(i, 0, num_nodes, { ### Global Type Aliases ```cpp -using real_t = double; -using u_int = unsigned int; +// precision.h — meaning fixed at configure time by CMake flags (see below) +using real_t = mtr::real_t; // default working tier (MATAR_REAL, default double) +using high_real_t = mtr::high_real_t; // must-stay-accurate tier (MATAR_HIGH_REAL, default double) +using low_real_t = mtr::low_real_t; // tolerant/bulk tier (MATAR_LOW_REAL, default double) +using u_int = unsigned int; ``` +### Precision System (precision.h) + +Three compile-time-swappable tiers selected per build with CMake flags; user +code only ever writes the tier names. + +| CMake flag | Values | Tier | +|---|---|---| +| `MATAR_REAL` | `double float half bfloat16 quad` | `real_t` | +| `MATAR_HIGH_REAL` | `double float quad` | `high_real_t` | +| `MATAR_LOW_REAL` | `double float half bfloat16` | `low_real_t` | + +Rules (hard rules for generated code): + +- **User-facing code uses ONLY the three tier names** `real_t` / + `high_real_t` / `low_real_t` — no other precision types, traits, or helper + functions exist in the public API. Declare fields with a tier + (`CArrayDevice`), write constants by constructing the tier type + (`real_t(0.5)`, `real_t(0)`), and let the CMake flags fix the meaning. +- `half`/`bfloat16` are native 16-bit on CUDA/HIP/SYCL and float-backed on + CPU backends (`MATAR_FP16_IS_EMULATED`/`MATAR_BF16_IS_EMULATED` report + which at compile time). `quad` = `__float128`, host-only. Non-Kokkos + builds allow only `double`/`float` (configure error otherwise). +- Solver internals (and any MATAR-internal math) compute in `real_t`; + storage arrays of other tiers convert implicitly on read/write. +- Reductions (`FOR_REDUCE_*`) work at every tier: precision.h supplies the + `Kokkos::reduction_identity` specializations Kokkos lacks (half types on + Kokkos < 5.2, `__float128` everywhere). +- MPI types work at every tier with no user-visible machinery: + `MPICArrayKokkos` communicate()/all_reduce() just work. Internally + (mpi_types.h): native 16-bit types get a contiguous-byte MPI datatype and + reduce by promoting to double on the wire; `__float128` gets a 16-byte + datatype plus custom MPI_Ops. Native-16-bit MPI paths compile on GPU builds + but CI verifies them only in float-emulated form (no GPU runners). + ### Choosing the Right Type Use this decision tree: @@ -1599,6 +1636,23 @@ Use these constraints as hard rules when generating MATAR code: - **Class-member variants** (use `KOKKOS_CLASS_LAMBDA`): `FOR_ALL_CLASS`, `RUN_CLASS`, `FOR_REDUCE_SUM_CLASS`, `FOR_REDUCE_MAX_CLASS`, `FOR_REDUCE_MIN_CLASS` - **Hierarchical (team/thread/vector):** `FOR_FIRST`, `FOR_SECOND`, `FOR_THIRD`, `DO_FIRST`, `DO_SECOND`, `DO_THIRD` - **Hierarchical reductions:** `FOR_REDUCE_SUM_SECOND`, `FOR_REDUCE_SUM_THIRD`, `DO_REDUCE_SUM_SECOND`, `DO_REDUCE_SUM_THIRD`, `FOR_REDUCE_MAX_SECOND`, `DO_REDUCE_MAX_THIRD`, `FOR_REDUCE_MIN_SECOND`, `DO_REDUCE_MIN_THIRD` +1b. **Host-side twins: `FOR_ALL_HOST`, `DO_ALL_HOST`, `RUN_HOST`, + `FOR_REDUCE_{SUM,MAX,MIN,PRODUCT}_HOST`, `DO_REDUCE_{SUM,MAX,MIN}_HOST`** + (same argument forms, including the optional trailing kernel-name string). + These run in the host execution space so CPU work overlaps in-flight + device kernels. Two hard rules: + - They capture **by reference**, so `std::` containers/streams are allowed + inside them (that is their purpose: file I/O and CPU-faster algorithms). + - MATAR is **device-centric**: `*Device` types always live on the device + and their execution space is never overridden. For host-parallel work + over device-resident data, declare the field as a **Dual** type and use + the `.host()` accessor inside the host macro, then `update_device()`. + - They may **only** touch host-accessible data — `*Host` types, the + `.host()` side of a Dual type, or plain `std::` data. Never a device + array: that compiles but aborts at run time. + Fence one side without stalling the other via `MATAR_FENCE_HOST()` / + `MATAR_FENCE_DEVICE()`. There are no hierarchical `_HOST` variants. + 2. `**FOR_ALL` and `DO_ALL` parallelize all listed dimensions.** - If a dimension must remain sequential, keep it as a plain inner `for` loop inside the macro body, or use hierarchical macros where appropriate. 3. **Indexing is always `()` for MATAR arrays.** diff --git a/README.md b/README.md index afaff73e..516c6d8b 100644 --- a/README.md +++ b/README.md @@ -102,90 +102,182 @@ The code can also be cloned using git clone --recursive https://github.com/lanl/MATAR.git ``` -## Basic build -The basic build is for users only interested in the serial CPU only MATAR data types. For this build, we recommend making a folder perhaps called build then go into the build folder and type +## Building MATAR +MATAR is built entirely with CMake. Kokkos is bundled as a git submodule (`src/Kokkos/kokkos`), pinned to the Kokkos **5.2.1** release, and is built automatically with the backend you select, so no separate Kokkos install is needed. Kokkos 5 requires **C++20** and CMake >= 3.22 (CUDA builds additionally need CMake >= 3.25.2 for C++20 in the CUDA language), so `matar::matar` requests `cxx_std_20`. + +The provided CMake presets configure, build, and test MATAR with a given Kokkos backend: ``` -cmake .. -make +cmake --preset serial # also: openmp, pthreads, cuda, hip, +cmake --build --preset serial # serial-mpi, openmp-mpi, cuda-mpi, hip-mpi, +ctest --preset serial # plus -debug variants (e.g. serial-debug) ``` -The compiled code will be in the build folder. - -## Debug basic build +The unit tests are built with the presets but only execute when `ctest` is invoked. +Each preset builds into `build/`, with example executables in `build//bin`. Debug presets include checks on array and matrix dimensions and index bounds. On HPC machines, load your compiler/MPI/CUDA modules first, then run the preset; site-specific toolchains can be layered with a `CMakeUserPresets.json`. -To build serial CPU only MATAR data types in the debug mode, please use +Configuring manually instead of with presets works the same way: ``` -cmake -DCMAKE_BUILD_TYPE=Debug .. -make +cmake -B build -DMATAR_BUILD_EXAMPLES=ON -DKokkos_ENABLE_OPENMP=ON +cmake --build build -j ``` -The debug flag includes checks on array and matrix dimensions and index bounds. +The CMake options are: -## Building MATAR with Kokkos -A building script is provided to build the MATAR examples and tests, with or without Kokkos. The simplest build with all defaults can be run with -``` -source {path-to-repo}/scripts/build-matar.sh -``` -Running with the argument ```--help``` will give a full list of all possible arguments. -If an argument is not changed, it will be set to the default action, which can all be found from the help command -If the scripts fail to build, then carefully review the modules used and the computer architecture settings. +| Option | Default | Description | +|---|---|---| +| `MATAR_ENABLE_KOKKOS` | ON | Build the Kokkos-backed device/dual types (builds the bundled Kokkos submodule) | +| `MATAR_ENABLE_MPI` | OFF | Enable the MPI-aware types (`MPICArrayKokkos`, `CommunicationPlan`) | +| `MATAR_ENABLE_GPU_AWARE_MPI` | OFF | Assume the MPI implementation is GPU-aware | +| `MATAR_USE_EXTERNAL_KOKKOS` | OFF | Use an installed Kokkos (`-DKokkos_ROOT=`) instead of the submodule | +| `MATAR_BUILD_EXAMPLES` | OFF | Build the example programs | +| `MATAR_BUILD_TESTS` | OFF | Build the unit tests (`ctest` to run) | +| `MATAR_BUILD_BENCHMARKS` | OFF | Build the benchmarks | +| `MATAR_REAL` | double | Precision of the `real_t` tier: `double`, `float`, `half`, `bfloat16`, `quad` | +| `MATAR_HIGH_REAL` | double | Precision of the `high_real_t` tier: `double`, `float`, `quad` | +| `MATAR_LOW_REAL` | double | Precision of the `low_real_t` tier: `double`, `float`, `half`, `bfloat16` | -## Building MATAR with Anaconda -The recommended way to build **MATAR** is inside an Anaconda environment. As a starting place, follow the steps for your platform to install [anaconda](https://docs.anaconda.com/free/anaconda/install/index.html)/[miniconda](https://docs.conda.io/en/latest/miniconda.html)/[mamba](https://mamba.readthedocs.io/en/latest/installation.html). +## Host-side parallelism -Open a terminal on your machine and go to a folder where you want to run the **MATAR** code. Activate a bash terminal by typing: -``` -bash -``` -Then create and activate an Anaconda environment by typing: -``` -conda create -n MATAR -conda activate MATAR -``` -In this example, the enviroment is called MATAR, but any name can be used. In some cases, the text to activate an enviroment is `source activate MATAR`. Likewise, if an enviroment already exists, then just activate the desired environment. +Every MATAR macro (`FOR_ALL`, `DO_ALL`, `FOR_REDUCE_*`, ...) has a `_HOST` twin that runs in the **host** execution space instead of the device space. Because device kernels are asynchronous and host kernels only block the calling thread, CPU work can proceed while the GPU is still busy — useful for file I/O and for algorithms that are simply faster on a CPU: -Now install a compiler and cmake, which are needed to build the MATAR library. -``` -conda install -c conda-forge "cxx-compiler=1.5.2" -conda install -c conda-forge "fortran-compiler=1.5.2" -conda install cmake -``` -By using cxx-compiler=1.5.2., it install gcc 11. Omit the version number and gcc 12 will be installed (at this time). If building for a GPU, it is recommended to use an older gcc version. For example, we have success using gcc 11 with CUDA 12. +```c++ +FOR_ALL(i, 0, n, { device_field(i) = compute(i); }); // GPU, returns immediately -If running on an Nvidia GPU, install cudatoolkit by typing: -``` -conda install -c conda-forge cudatoolkit -conda install -c conda-forge cudatoolkit-dev +FOR_ALL_HOST(i, 0, n, { // CPU, runs concurrently + out_lines[i] = format_record(host_field(i)); +}); +MATAR_FENCE_HOST(); // wait for the host work +MATAR_FENCE_DEVICE(); // wait for the GPU work ``` -This installs CUDA 12 (at this time). -The build script is located at -``` -source {path-to-repo}/scripts/build-matar.sh +The `_HOST` macros differ from their device counterparts in two ways: the loop body captures **by reference**, so `std::` containers, file streams, and other non-device-copyable objects can be used directly; and no `_CLASS` variants are needed (`_CLASS` spellings exist as aliases). + +**MATAR is device-centric: the `*Device` types always live on the device.** To run host-parallel work over data that also lives on the device, declare it as a **Dual** type and go through the `.host()` accessor inside the host macro: + +```c++ +CArrayDual field(n, "field"); +FOR_ALL_HOST(i, 0, n, { // CPU-side pass over the host mirror + field.host(i) = read_from_file(i); +}); +field.update_device(); // publish to the device ``` -To build the MATAR library and examples with CUDA, type: +A host kernel may otherwise only touch host-accessible data — the `*Host` types or plain `std::` data. Passing a `*Device` array to a host macro compiles but aborts at run time with `attempt to access inaccessible memory space`. + +Select the two backends independently: + +| Option | Values | Description | +|---|---|---| +| `MATAR_DEVICE_BACKEND` | `serial`, `openmp`, `pthreads`, `cuda`, `hip`, `sycl` | Backend for `FOR_ALL` and friends | +| `MATAR_HOST_BACKEND` | `serial`, `openmp`, `pthreads` | Backend for the `_HOST` macros | + ``` -source build-matar.sh --kokkos_build_type=cuda --build_cores=16 +cmake --preset cuda-hostomp # CUDA device + OpenMP host: genuinely concurrent +cmake -B build -DMATAR_DEVICE_BACKEND=cuda -DMATAR_HOST_BACKEND=openmp ``` -The executables for the examples that run in parallel Nvidia GPUs using CUDA are located in: + +Both options are optional; leaving them unset keeps the historical behavior of setting `Kokkos_ENABLE_*` directly. Note that Kokkos permits only one host-parallel backend per build, so `openmp` and `pthreads` cannot be paired with each other, and selecting the same backend on both sides is valid but gives no concurrency (the two macro families then share one execution space — MATAR warns at configure time). + +## Precision tiers + +MATAR provides three floating-point type names whose meaning is fixed at configure time — code just uses the names, and the CMake flags decide what they are per build/architecture: + +* `real_t` — the default working precision for field data +* `high_real_t` — fields that must stay accurate (coordinates, conserved-quantity sums) +* `low_real_t` — tolerant or bulk-storage fields (history buffers, gradients, output) + ``` -MATAR/build-matar-cuda/bin +cmake --preset serial-fp32 # real_t = float +cmake -B build -DMATAR_REAL=half # real_t = Kokkos half_t (native on GPU backends) +cmake -B build -DMATAR_REAL=float -DMATAR_HIGH_REAL=double # mixed ``` -To build the MATAR library and examples with OpenMP, type: -``` -source build-matar.sh --kokkos_build_type=openmp --build_cores=16 +Notes: +* `half`/`bfloat16` map to the native 16-bit types on CUDA/HIP/SYCL and are transparently float-backed on CPU backends (the macros `MATAR_FP16_IS_EMULATED`/`MATAR_BF16_IS_EMULATED` report which at compile time). `quad` is `__float128`, host backends only. Non-Kokkos builds support only `double` and `float`; anything else fails at configure. +* User code only ever writes the three tier names — declare fields as `CArrayDevice` (or `high_real_t`/`low_real_t`) and write constants as `real_t(0.5)`; the build flags decide what those names mean. The solvers in `solvers/` compute in `real_t`, so they run at whatever working precision the build selects. +* The MPI-aware types work at every tier: `MPICArrayKokkos` halo exchange and `all_reduce` need nothing extra from the user. Internally, native 16-bit types travel as bytes and reduce by promoting to double on the wire (exact), and quad uses a custom MPI datatype and reduction op. + +The Kokkos backend is selected with the standard Kokkos CMake variables (`Kokkos_ENABLE_OPENMP`, `Kokkos_ENABLE_CUDA`, `Kokkos_ENABLE_HIP`, `Kokkos_ARCH_*`, ...), which are passed through to the bundled Kokkos build. With `MATAR_ENABLE_KOKKOS=OFF`, MATAR is a dependency-free serial header-only library. + +## Using MATAR as a third-party library +MATAR is header-only and exports a single CMake target, **`matar::matar`**. Linking it propagates everything a consumer needs: the include paths, the C++20 requirement, the `HAVE_KOKKOS`/`HAVE_CUDA`/`HAVE_HIP`/`HAVE_OPENMP`/`HAVE_THREADS`/`HAVE_MPI` compile definitions, and the Kokkos/MPI link dependencies. There is nothing to link manually and no MATAR library to build. + +Consumers need **CMake >= 3.22** and a **C++20** compiler; both come from Kokkos 5. Whichever method you choose, set the Kokkos backend variables *before* MATAR is added, because the bundled Kokkos is configured as part of that step. + +### Option 1: FetchContent +CMake downloads MATAR at configure time. Nothing needs to be vendored into your repository. + +```cmake +cmake_minimum_required(VERSION 3.22) +project(myapp LANGUAGES CXX) + +# Pick the Kokkos backend BEFORE MATAR is made available. +set(Kokkos_ENABLE_OPENMP ON CACHE BOOL "" FORCE) + +include(FetchContent) +FetchContent_Declare( + matar + GIT_REPOSITORY https://github.com/lanl/MATAR.git + GIT_TAG # pin a release; avoid tracking a branch +) +FetchContent_MakeAvailable(matar) # also configures the bundled Kokkos + +add_executable(myapp main.cpp) +target_link_libraries(myapp PRIVATE matar::matar) ``` -The executables for the examples that run in parallel on multi-core CPUs using OpenMP are located in: +MATAR carries Kokkos as its own submodule, and FetchContent updates submodules recursively by default, so the bundled Kokkos is fetched and built automatically. No extra steps are needed. + +### Option 2: git submodule +Vendors MATAR into your repository, which pins the exact commit and allows offline builds. + +```bash +git submodule add https://github.com/lanl/MATAR.git external/MATAR +git submodule update --init --recursive ``` -MATAR/build-matar-openmp/bin + +`--recursive` is required: MATAR contains Kokkos as a nested submodule, and without it `external/MATAR/src/Kokkos/kokkos` is left empty and the MATAR build stops with an error telling you to re-run the command. Anyone cloning your project afterwards needs `git clone --recursive`, or the same `git submodule update --init --recursive` after cloning. + +```cmake +cmake_minimum_required(VERSION 3.22) +project(myapp LANGUAGES CXX) + +set(Kokkos_ENABLE_OPENMP ON CACHE BOOL "" FORCE) +add_subdirectory(external/MATAR) + +add_executable(myapp main.cpp) +target_link_libraries(myapp PRIVATE matar::matar) ``` -Using the main_kokkos.cpp executable as an example, it can be run by typing: + +### Option 3: an installed MATAR +Against a MATAR installed with `cmake --install build/ --prefix `: + +```cmake +find_package(Matar REQUIRED) # configure with -DCMAKE_PREFIX_PATH= +target_link_libraries(myapp PRIVATE matar::matar) ``` -./mtestkokkos + +When the bundled Kokkos was used, it is installed into the same prefix and `MatarConfig.cmake` resolves it through `find_dependency(Kokkos)`. + +### Selecting a backend, and reusing your own Kokkos +The backend is chosen with the standard Kokkos cache variables (`Kokkos_ENABLE_SERIAL`, `Kokkos_ENABLE_OPENMP`, `Kokkos_ENABLE_CUDA`, `Kokkos_ENABLE_HIP`, `Kokkos_ARCH_*`, ...), which are passed straight through to the bundled Kokkos build. Set them from your own `CMakeLists.txt` as shown above, or on the command line with `-DKokkos_ENABLE_CUDA=ON`. + +If your project already provides Kokkos — through `find_package(Kokkos)` or an `add_subdirectory` of its own Kokkos copy — do that **before** adding MATAR. MATAR detects the existing `Kokkos::kokkos` target and links against it instead of building its bundled submodule, so only one Kokkos ends up in the build: + +```cmake +find_package(Kokkos REQUIRED) # your Kokkos wins +FetchContent_MakeAvailable(matar) # MATAR reuses it ``` +Other options worth knowing when embedding MATAR: + +| Option | Effect | +|---|---| +| `MATAR_ENABLE_KOKKOS=OFF` | Host-only types; MATAR becomes a dependency-free header library and the parallel macros compile to plain serial loops | +| `MATAR_ENABLE_MPI=ON` | Adds `MPICArrayKokkos` and `CommunicationPlan` (requires MPI) | +| `MATAR_INSTALL` | Defaults to off when MATAR is embedded in another project, so it contributes no install rules to your package | + +Note that while an embedded MATAR installs nothing, the *bundled Kokkos* still adds its own install rules, so `cmake --install` on your project will also deposit the Kokkos headers and CMake package files into your prefix. Use an external or parent-provided Kokkos if you need to keep it out of your install tree. + ## Running codes in parallel The openMP and pthread Kokkos backends require the user to specify the number of threads used to run the code in parallel. To specify the number of threads with the Kokkos pthread backend, add the following command line argument when executing the code, @@ -196,15 +288,15 @@ in otherwords, ``` ./mycode --kokkos-threads=4 ``` -The above command runs the code with fine grained parallelism using 4 threads. In your code, ensure you pass the command line argument variables to Kokkos::initialize function as shown below here. +The above command runs the code with fine grained parallelism using 4 threads. In your code, ensure you pass the command line argument variables to the MATAR_INITIALIZE macro (which wraps Kokkos::initialize) as shown below here. ``` int main(int argc, char* argv[]) { - Kokkos::initialize(argc, argv); + MATAR_INITIALIZE(argc, argv); // coding goes here - Kokkos::finalize(); + MATAR_FINALIZE(); return 0; } diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 0a548973..81ffd0d6 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,54 +1,19 @@ -cmake_minimum_required(VERSION 3.5) - -project (matarbenchmark) - -set(benchmark_DIR "benchmark/build") - -find_package(benchmark REQUIRED) -find_package(Matar REQUIRED) - - - - - -if (NOT KOKKOS) - add_executable(BM_Carray src/CArray_benchmark.cpp) - target_link_libraries(BM_Carray matar benchmark::benchmark) +include(FetchContent) + +FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.8.3 +) +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(benchmark) + +add_executable(BM_CArray src/CArray_benchmark.cpp) +target_link_libraries(BM_CArray matar::matar benchmark::benchmark) + +if(MATAR_ENABLE_KOKKOS) + add_executable(BM_CArrayDevice src/CArrayDevice_benchmark.cpp) + target_link_libraries(BM_CArrayDevice matar::matar benchmark::benchmark) endif() - -if (KOKKOS) - find_package(Kokkos REQUIRED) #new - - add_definitions(-DHAVE_KOKKOS=1) - - add_executable(BM_CArray src/CArray_benchmark.cpp) - target_link_libraries(BM_CArray matar Kokkos::kokkos benchmark::benchmark) - - add_executable(BM_CArrayDevice src/CArrayDevice_benchmark.cpp) - target_link_libraries(BM_CArrayDevice matar Kokkos::kokkos benchmark::benchmark) - - if (CUDA) - add_definitions(-DHAVE_CUDA=1) - elseif (HIP) - add_definitions(-DHAVE_HIP=1) - elseif (OPENMP) - add_definitions(-DHAVE_OPENMP=1) - elseif (THREADS) - add_definitions(-DHAVE_THREADS=1) - endif() -endif() - -# find_package(Kokkos REQUIRED) #new - -# set(This matar_benchmark) - -# set(Sources -# src/serial_types_benchmark.cpp -# ) - - - - -# if (KOKKOKS) -# target_link_libraries(${This} matar Kokkos::kokkos benchmark::benchmark) -# endif () diff --git a/benchmark/src/CArrayDevice_benchmark.cpp b/benchmark/src/CArrayDevice_benchmark.cpp index 71462125..313e1546 100644 --- a/benchmark/src/CArrayDevice_benchmark.cpp +++ b/benchmark/src/CArrayDevice_benchmark.cpp @@ -57,7 +57,7 @@ static void BM_CArrayDevice_vec_vec_dot(benchmark::State& state) double loc_sum = 0; double C = 0; - REDUCE_SUM(i, 0, size, + FOR_REDUCE_SUM(i, 0, size, loc_sum, { loc_sum += A(i)*B(i); }, C); diff --git a/cmake/MatarConfig.cmake.in b/cmake/MatarConfig.cmake.in index a673ed41..688359c5 100644 --- a/cmake/MatarConfig.cmake.in +++ b/cmake/MatarConfig.cmake.in @@ -1,4 +1,19 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) + +if(@MATAR_ENABLE_KOKKOS@) + find_dependency(Kokkos) +endif() +if(@MATAR_ENABLE_MPI@) + find_dependency(MPI COMPONENTS CXX) +endif() + include("${CMAKE_CURRENT_LIST_DIR}/MatarTargets.cmake") -check_required_components("@Matar@") + +# Older consumers link against plain `matar` rather than matar::matar. +if(NOT TARGET matar) + add_library(matar ALIAS matar::matar) +endif() + +check_required_components(Matar) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index aecbe314..c5ea9b95 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,100 +1,25 @@ -cmake_minimum_required(VERSION 3.8) +set(LINKING_LIBRARIES matar::matar) -# --- custom targets: --- +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../solvers) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -if (NOT TARGET distclean) - # Only include distclean if it has not already been defined (by any - # other package that defines distclean and uses MATAR as a submodule) - INCLUDE(../cmake/Modules/TargetDistclean.cmake OPTIONAL) -endif (NOT TARGET distclean) - -find_package(Matar REQUIRED) -set(LINKING_LIBRARIES matar) - -include_directories(../solvers) - -if (MPI) - find_package(MPI REQUIRED) - add_definitions(-DHAVE_MPI=1) - list(APPEND LINKING_LIBRARIES MPI::MPI_CXX) - if (GPU_AWARE_MPI) - add_definitions(-DHAVE_GPU_AWARE_MPI) -# elseif (GPU_SHARED_MEM) -# add_definitions(-DHAVE_GPU_SHARED_MEM) - endif() -endif() - -if (NOT KOKKOS) +if (NOT MATAR_ENABLE_KOKKOS) add_executable(mtest main.cpp) target_link_libraries(mtest ${LINKING_LIBRARIES}) - add_executable(test_for test_for.cpp) - target_link_libraries(test_for ${LINKING_LIBRARIES}) + add_executable(test_for test_for.cpp) + target_link_libraries(test_for ${LINKING_LIBRARIES}) - add_executable(test_shared_ptr test_shared_ptr.cpp) - target_link_libraries(test_shared_ptr ${LINKING_LIBRARIES}) + add_executable(test_shared_ptr test_shared_ptr.cpp) + target_link_libraries(test_shared_ptr ${LINKING_LIBRARIES}) endif() -if (KOKKOS) - if (Matar_ENABLE_TRILINOS) - find_package(Trilinos REQUIRED) #new - # Assume if the CXX compiler exists, the rest do too. - if (EXISTS ${Trilinos_CXX_COMPILER}) - set(CMAKE_CXX_COMPILER ${Trilinos_CXX_COMPILER}) - set(CMAKE_C_COMPILER ${Trilinos_C_COMPILER}) - set(CMAKE_Fortran_COMPILER ${Trilinos_Fortran_COMPILER}) - endif() - if(NOT DISTRIBUTION) - # Make sure to use same compilers and flags as Trilinos - set(CMAKE_CXX_FLAGS "${Trilinos_CXX_COMPILER_FLAGS} ${CMAKE_CXX_FLAGS}") - set(CMAKE_C_FLAGS "${Trilinos_C_COMPILER_FLAGS} ${CMAKE_C_FLAGS}") - set(CMAKE_Fortran_FLAGS "${Trilinos_Fortran_COMPILER_FLAGS} ${CMAKE_Fortran_FLAGS}") - endif() - - message("\nFound Trilinos! Here are the details: ") - message(" Trilinos_DIR = ${Trilinos_DIR}") - message(" Trilinos_VERSION = ${Trilinos_VERSION}") - message(" Trilinos_PACKAGE_LIST = ${Trilinos_PACKAGE_LIST}") - message(" Trilinos_LIBRARIES = ${Trilinos_LIBRARIES}") - message(" Trilinos_INCLUDE_DIRS = ${Trilinos_INCLUDE_DIRS}") - message(" Trilinos_LIBRARY_DIRS = ${Trilinos_LIBRARY_DIRS}") - message(" Trilinos_TPL_LIST = ${Trilinos_TPL_LIST}") - message(" Trilinos_TPL_INCLUDE_DIRS = ${Trilinos_TPL_INCLUDE_DIRS}") - message(" Trilinos_TPL_LIBRARIES = ${Trilinos_TPL_LIBRARIES}") - message(" Trilinos_TPL_LIBRARY_DIRS = ${Trilinos_TPL_LIBRARY_DIRS}") - message(" Trilinos_BUILD_SHARED_LIBS = ${Trilinos_BUILD_SHARED_LIBS}") - message("End of Trilinos details\n") - - include_directories(${Trilinos_INCLUDE_DIRS} ${Trilinos_TPL_INCLUDE_DIRS}) - list(APPEND LINKING_LIBRARIES Trilinos::all_selected_libs) - add_definitions(-DTRILINOS_INTERFACE=1) - else() - find_package(Kokkos REQUIRED) #new - list(APPEND LINKING_LIBRARIES Kokkos::kokkos) - endif() - - - add_definitions(-DHAVE_KOKKOS=1) - - if (CUDA) - add_definitions(-DHAVE_CUDA=1) - elseif (HIP) - add_definitions(-DHAVE_HIP=1) - elseif (OPENMP) - add_definitions(-DHAVE_OPENMP=1) - elseif (THREADS) - add_definitions(-DHAVE_THREADS=1) - endif() - +if (MATAR_ENABLE_KOKKOS) add_executable(testsetval test_set_values.cpp) target_link_libraries(testsetval ${LINKING_LIBRARIES}) add_executable(mtestkokkos main_kokkos.cpp) target_link_libraries(mtestkokkos ${LINKING_LIBRARIES}) - add_executable(drrak_test test_drrak.cpp) target_link_libraries(drrak_test ${LINKING_LIBRARIES}) @@ -119,9 +44,6 @@ if (KOKKOS) add_executable(annkokkos_compare ann_kokkos_compare.cpp) target_link_libraries(annkokkos_compare ${LINKING_LIBRARIES}) - #add_executable(ompperftest ompperftest.cpp) - #target_link_libraries(ompperftest ${LINKING_LIBRARIES}) - add_executable(lu_test test_lu_solve.cpp) target_link_libraries(lu_test ${LINKING_LIBRARIES}) @@ -131,85 +53,38 @@ if (KOKKOS) add_executable(cramers_rule_test test_cramers_rule.cpp) target_link_libraries(cramers_rule_test ${LINKING_LIBRARIES}) - include_directories(pointcloud) add_subdirectory(pointcloud) - - if (Matar_ENABLE_TRILINOS) - add_executable(anndistributed ann_distributed.cpp) - target_link_libraries(anndistributed ${LINKING_LIBRARIES}) - - add_executable(anndistributed_crs ann_distributed_crs.cpp) - target_link_libraries(anndistributed_crs ${LINKING_LIBRARIES}) - - add_executable(test_tpetra_farray test_tpetra_farray.cpp) - target_link_libraries(test_tpetra_farray ${LINKING_LIBRARIES}) - - add_executable(test_tpetra_carray test_tpetra_carray.cpp) - target_link_libraries(test_tpetra_carray ${LINKING_LIBRARIES}) - - add_executable(test_tpetra_crs test_tpetra_crs.cpp) - target_link_libraries(test_tpetra_crs ${LINKING_LIBRARIES}) - - add_executable(test_tpetra_mesh test_tpetra_mesh.cpp) - target_link_libraries(test_tpetra_mesh ${LINKING_LIBRARIES}) - endif() - - if (OPENMP) + if (Kokkos_ENABLE_OPENMP) add_executable(parallel_hello_world parallel_hello_world.cpp) target_link_libraries(parallel_hello_world ${LINKING_LIBRARIES}) endif() - if (MPI) - include_directories(laplaceMPI) + if (MATAR_ENABLE_MPI) add_subdirectory(laplaceMPI) - - include_directories(mesh_decomp) add_subdirectory(mesh_decomp) add_executable(matar_mpi matar_mpi.cpp) target_link_libraries(matar_mpi ${LINKING_LIBRARIES}) endif() - endif() ### HIP Linking error, will add back in after fixed -if (NOT HIP) - include_directories(virtualFcnKokkos) +if (NOT Kokkos_ENABLE_HIP) add_subdirectory(virtualFcnKokkos) endif() # In testing, not working -#include_directories(gArrayofgArrays) #add_subdirectory(gArrayofgArrays) -include_directories(virtualFcnMATAR) add_subdirectory(virtualFcnMATAR) - -include_directories(laplace) add_subdirectory(laplace) - -include_directories(halfspace_cooling) add_subdirectory(halfspace_cooling) - -include_directories(watt-graph) add_subdirectory(watt-graph) - -#include_directories(matar_fortran) #add_subdirectory(matar_fortran) - -include_directories(sparsetests) add_subdirectory(sparsetests) - -include_directories(test_rocm) add_subdirectory(test_rocm) - -# include_directories(phaseField/srcKokkosVerbose) # add_subdirectory(phaseField/srcKokkosVerbose) - -# include_directories(phaseField/srcMacros) # add_subdirectory(phaseField/srcMacros) - -# include_directories(phaseFieldMPI) # add_subdirectory(phaseFieldMPI) diff --git a/examples/CSCKokkos.cpp b/examples/CSCKokkos.cpp index f3f95beb..7cfb40fe 100644 --- a/examples/CSCKokkos.cpp +++ b/examples/CSCKokkos.cpp @@ -36,17 +36,17 @@ #include #include -using namespace mtr; // matar namespace +using namespace mtr; // matar namespace -int main(int argc, char* argv[]) -{ - Kokkos::initialize(); { +int main(int argc, char* argv[]) { + Kokkos::initialize(); + { size_t nnz = 6; size_t dim1 = 3; size_t dim2 = 10; CArrayKokkos starts(dim2 + 1); CArrayKokkos rows(nnz); - CArrayKokkos array(nnz + 1); + CArrayKokkos array(nnz + 1); RUN({ starts(1) = 1; starts(2) = 2; @@ -86,37 +86,34 @@ int main(int argc, char* argv[]) auto A = pre_A; int* values = A.pointer(); auto a_start = A.get_starts(); - int total = 0; + int total = 0; - RUN({ - printf("This matix is %ld x %ld \n", A.dim1(), A.dim2()); - }); + RUN({ printf("This matix is %ld x %ld \n", A.dim1(), A.dim2()); }); - RUN({ - printf("nnz : %ld \n", A.nnz()); - }); + RUN({ printf("nnz : %ld \n", A.nnz()); }); int loc_total = 0; - loc_total += 0; // Get rid of warning + loc_total += 0; // Get rid of warning FOR_REDUCE_SUM(i, 0, nnz, loc_total, { - loc_total += values[i]; + loc_total += values[i]; }, total); printf("Sum of nnz from pointer method %d\n", total); total = 0; FOR_REDUCE_SUM(i, 0, nnz, loc_total, { - loc_total += a_start[i]; + loc_total += a_start[i]; }, total); printf("Sum of start indices form .get_starts() %d\n", total); total = 0; FOR_REDUCE_SUM(i, 0, dim1, j, 0, dim2 - 1, - loc_total, { - loc_total += A(i, j); + loc_total, { + loc_total += A(i, j); }, total); printf("Sum of nnz in array notation %d\n", total); - } Kokkos::finalize(); + } + Kokkos::finalize(); return 0; } diff --git a/examples/CSRKokkos.cpp b/examples/CSRKokkos.cpp index 7dab444d..286398fb 100644 --- a/examples/CSRKokkos.cpp +++ b/examples/CSRKokkos.cpp @@ -36,28 +36,32 @@ #include #include -using namespace mtr; // matar namespace +using namespace mtr; // matar namespace -int main(int argc, char* argv[]) -{ - Kokkos::initialize(); { +int main(int argc, char* argv[]) { + Kokkos::initialize(); + { size_t nnz = 6; size_t dim1 = 3; size_t dim2 = 10; CArrayKokkos starts(dim1 + 1); CArrayKokkos columns(nnz); - CArrayKokkos array(nnz); + CArrayKokkos array(nnz); RUN({ - for (int i = 0; i < 4; i++) { + // starts has dim1+1 entries (the trailing one is the total nnz), + // while columns/array hold exactly nnz = 2 entries per row. + for (size_t i = 0; i <= dim1; i++) { starts(i) = 2 * i; - for (int j = 0; j < 2; j++) { + } + for (size_t i = 0; i < dim1; i++) { + for (size_t j = 0; j < 2; j++) { columns(2 * i + j) = i + j; array(2 * i + j) = 2 * i + j; } } }); - int column_arr[] = { 0, 2, 2, 0, 1, 2 }; + int column_arr[] = {0, 2, 2, 0, 1, 2}; CArrayKokkos data(6); CArrayKokkos row(4); CArrayKokkos column(6); @@ -116,6 +120,7 @@ int main(int argc, char* argv[]) printf("Sum of nnz in array notation %d\n", total); auto ss = A.begin(0); */ - } Kokkos::finalize(); + } + Kokkos::finalize(); return 0; } diff --git a/examples/ann_kokkos.cpp b/examples/ann_kokkos.cpp index 9d7b1ffd..030f16a5 100644 --- a/examples/ann_kokkos.cpp +++ b/examples/ann_kokkos.cpp @@ -39,35 +39,30 @@ #include "matar.h" -using namespace mtr; // matar namespace - - +using namespace mtr; // matar namespace // ================================================================= // Artificial Neural Network (ANN) // -// For a single layer, we have x_i inputs with weights_{ij}, +// For a single layer, we have x_i inputs with weights_{ij}, // creating y_j outputs. We have // y_j = Fcn(b_j) = Fcn( Sum_i {x_i w_{ij}} ) -// where the activation function Fcn is applied to b_j, creating +// where the activation function Fcn is applied to b_j, creating // outputs y_j. For multiple layers, we have // b_j^l = Sum_i (x_i^{l-1} w_{ij}^l) -// where l is a layer, and as before, an activation function is +// where l is a layer, and as before, an activation function is // applied to b_j^l, creating outputs y_j^l. -// +// // ================================================================= - // ================================================================= // // Number of nodes in each layer including inputs and outputs // // ================================================================= -std::vector num_nodes_in_layer = {64000, 30000, 8000, 4000, 2000, 1000, 100} ; +std::vector num_nodes_in_layer = {64000, 30000, 8000, 4000, 2000, 1000, 100}; // {9, 50, 100, 300, 200, 100, 20, 6} - - // ================================================================= // // data types and classes @@ -75,82 +70,67 @@ std::vector num_nodes_in_layer = {64000, 30000, 8000, 4000, 2000, 1000, // ================================================================= // array of ANN structs -struct ANNLayer_t{ - - DCArrayKokkos outputs; // dims = [layer] - DFArrayKokkos weights; // dims = [layer-1, layer] - DCArrayKokkos biases; // dims = [layer] - -}; // end struct - +struct ANNLayer_t { + DCArrayKokkos outputs; // dims = [layer] + DFArrayKokkos weights; // dims = [layer-1, layer] + DCArrayKokkos biases; // dims = [layer] +}; // end struct // ================================================================= // // functions // // ================================================================= -void vec_mat_multiply(DCArrayKokkos &inputs, - DCArrayKokkos &outputs, - DFArrayKokkos &matrix){ - +void vec_mat_multiply(DCArrayKokkos& inputs, DCArrayKokkos& outputs, DFArrayKokkos& matrix) { const size_t num_i = inputs.size(); const size_t num_j = outputs.size(); using team_t = typename Kokkos::TeamPolicy<>::member_type; - Kokkos::parallel_for ("MatVec", Kokkos::TeamPolicy<> (num_j, Kokkos::AUTO), - KOKKOS_LAMBDA (const team_t& team_h) { - - float sum = 0; - int j = team_h.league_rank(); - Kokkos::parallel_reduce (Kokkos::TeamThreadRange (team_h, num_i), - [&] (int i, float& lsum) { - lsum += inputs(i)*matrix(i,j); - }, sum); // end parallel reduce - - outputs(j) = sum; - - }); // end parallel for - - - FOR_ALL(j,0,num_j, { - if(fabs(outputs(j) - num_i)>= 1e-15){ - printf("error in vec mat multiply test \n"); - } + Kokkos::parallel_for( + "MatVec", + Kokkos::TeamPolicy<>(num_j, Kokkos::AUTO), + KOKKOS_LAMBDA(const team_t& team_h) { + float sum = 0; + int j = team_h.league_rank(); + Kokkos::parallel_reduce( + Kokkos::TeamThreadRange(team_h, num_i), + [&](int i, float& lsum) { + lsum += inputs(i) * matrix(i, j); + }, + sum); // end parallel reduce + + outputs(j) = sum; + }); // end parallel for + + FOR_ALL(j, 0, num_j, { + if (fabs(outputs(j) - num_i) >= 1e-15) { + printf("error in vec mat multiply test \n"); + } }); - - return; -}; // end function + return; -KOKKOS_INLINE_FUNCTION -float sigmoid(const float value){ - return 1.0/(1.0 + exp(-value)); // exp2f doesn't work with CUDA -}; // end function +}; // end function +KOKKOS_INLINE_FUNCTION float sigmoid(const float value) { + return 1.0 / (1.0 + exp(-value)); // exp2f doesn't work with CUDA +}; // end function -KOKKOS_INLINE_FUNCTION -float sigmoid_derivative(const float value){ +KOKKOS_INLINE_FUNCTION float sigmoid_derivative(const float value) { float sigval = sigmoid(value); - return sigval*(1.0 - sigval); // exp2f doesn't work with CUDA -}; // end function - - - + return sigval * (1.0 - sigval); // exp2f doesn't work with CUDA +}; // end function -void forward_propagate_layer(DCArrayKokkos &inputs, - DCArrayKokkos &outputs, - DFArrayKokkos &weights, - const DCArrayKokkos &biases){ - +void forward_propagate_layer(DCArrayKokkos& inputs, DCArrayKokkos& outputs, DFArrayKokkos& weights, + const DCArrayKokkos& biases) { const size_t num_i = inputs.size(); const size_t num_j = outputs.size(); - /* FOR_ALL(j, 0, num_j,{ - //printf("thread = %d \n", omp_get_thread_num()); + //printf("thread = %d \n", omp_get_thread_num()); float value = 0.0; for(int i=0; i &inputs, }); // end parallel for */ - // For a GPU, use the nested parallelism below here - - using team_t = typename Kokkos::TeamPolicy<>::member_type; - Kokkos::parallel_for ("MatVec", Kokkos::TeamPolicy<> (num_j, Kokkos::AUTO), - KOKKOS_LAMBDA (const team_t& team_h) { - - float sum = 0; - int j = team_h.league_rank(); - Kokkos::parallel_reduce (Kokkos::TeamThreadRange (team_h, num_i), - [&] (int i, float& lsum) { - lsum += inputs(i)*weights(i,j) + biases(j); - }, sum); // end parallel reduce - - outputs(j) = 1.0/(1.0 + exp(-sum)); - - }); // end parallel for - + using team_t = typename Kokkos::TeamPolicy<>::member_type; + Kokkos::parallel_for( + "MatVec", + Kokkos::TeamPolicy<>(num_j, Kokkos::AUTO), + KOKKOS_LAMBDA(const team_t& team_h) { + float sum = 0; + int j = team_h.league_rank(); + Kokkos::parallel_reduce( + Kokkos::TeamThreadRange(team_h, num_i), + [&](int i, float& lsum) { + lsum += inputs(i) * weights(i, j) + biases(j); + }, + sum); // end parallel reduce + + outputs(j) = 1.0 / (1.0 + exp(-sum)); + }); // end parallel for return; -}; // end function - +}; // end function -void set_biases(const DCArrayKokkos &biases){ +void set_biases(const DCArrayKokkos& biases) { const size_t num_j = biases.size(); - FOR_ALL(j,0,num_j, { - biases(j) = 0.0; - }); // end parallel for - -}; // end function - + FOR_ALL(j, 0, num_j, { + biases(j) = 0.0; + }); // end parallel for -void set_weights(const DFArrayKokkos &weights){ +}; // end function +void set_weights(const DFArrayKokkos& weights) { const size_t num_i = weights.dims(0); const size_t num_j = weights.dims(1); - - FOR_ALL(i,0,num_i, - j,0,num_j, { - - weights(i,j) = 1.0; - }); // end parallel for -}; // end function + FOR_ALL(i, 0, num_i, + j, 0, num_j, { + weights(i, j) = 1.0; + }); // end parallel for +}; // end function // ================================================================= // // Main function // // ================================================================= -int main(int argc, char* argv[]) -{ +int main(int argc, char* argv[]) { Kokkos::initialize(argc, argv); { - // ================================================================= // allocate arrays // ================================================================= // note: the num_nodes_in_layer has the inputs into the ANN, so subtract 1 for the layers - size_t num_layers = num_nodes_in_layer.size()-1; + size_t num_layers = num_nodes_in_layer.size() - 1; - CMatrix ANNLayers(num_layers); // starts at 1 and goes to num_layers + CMatrix ANNLayers(num_layers); // starts at 1 and goes to num_layers // input and ouput values to ANN - DCArrayKokkos inputs(num_nodes_in_layer[0]); - + DCArrayKokkos inputs(num_nodes_in_layer[0]); // set the strides // layer 0 are the inputs to the ANN // layer n-1 are the outputs from the ANN - for (size_t layer=1; layer<=num_layers; layer++){ - + for (size_t layer = 1; layer <= num_layers; layer++) { // dimensions - size_t num_i = num_nodes_in_layer[layer-1]; + size_t num_i = num_nodes_in_layer[layer - 1]; size_t num_j = num_nodes_in_layer[layer]; // allocate the weights in this layer - ANNLayers(layer).weights = DFArrayKokkos (num_i, num_j); - ANNLayers(layer).outputs = DCArrayKokkos (num_j); - ANNLayers(layer).biases = DCArrayKokkos (num_j); - - } // end for + ANNLayers(layer).weights = DFArrayKokkos(num_i, num_j); + ANNLayers(layer).outputs = DCArrayKokkos(num_j); + ANNLayers(layer).biases = DCArrayKokkos(num_j); + } // end for // ================================================================= // set weights, biases, and inputs // ================================================================= - + // inputs to ANN - for (size_t i=0; i