diff --git a/CMakeLists.txt b/CMakeLists.txt index b71b3352..c777da70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,12 @@ # select backend -set(PSZ_BACKEND "CUDA" CACHE STRING "Select the desired backend: CUDA/cuda and ONEAPI/1api.") +set(PSZ_BACKEND "CUDA" CACHE STRING "Select the desired backend: CUDA/cuda, HIP/hip, ONEAPI/1api.") if(PSZ_BACKEND STREQUAL "CUDA" OR PSZ_BACKEND STREQUAL "cuda") cmake_minimum_required(VERSION 3.18) project(CUSZ LANGUAGES CUDA CXX C VERSION 0.10.0) +elseif(PSZ_BACKEND STREQUAL "HIP" OR PSZ_BACKEND STREQUAL "hip") + cmake_minimum_required(VERSION 3.21) + project(HIPSZ LANGUAGES HIP CXX C VERSION 0.10.0) elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") cmake_minimum_required(VERSION 3.23) project(DPSZ LANGUAGES CXX C VERSION 0.6.0) @@ -14,7 +17,15 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(BUILD_SHARED_LIBS "prefer shared libaries" ON) # conditionally enable functionality -option(PSZ_ACTIVATE_LC "build LC as a submodule" ON) +# The bundled LC-framework (third_party/lc) is an external CUDA codebase that is +# not yet ported to HIP; default it OFF on the HIP backend so the core +# Lorenzo/spline + Huffman/HFR + FZG pipeline builds. It can be force-enabled +# once LC-framework gains HIP support. +if(PSZ_BACKEND STREQUAL "HIP" OR PSZ_BACKEND STREQUAL "hip") + option(PSZ_ACTIVATE_LC "build LC as a submodule" OFF) +else() + option(PSZ_ACTIVATE_LC "build LC as a submodule" ON) +endif() # example and testing option(PSZ_BUILD_EXAMPLES "build example codes" ON) @@ -25,6 +36,9 @@ option(PSZ_BUILD_PYBINDING "enable Python binding for specific backend" OFF) if(PSZ_BACKEND STREQUAL "CUDA" OR PSZ_BACKEND STREQUAL "cuda") message("[psz::info] CUDA backend has been selected.") include(cmake/cuda.cmake) +elseif(PSZ_BACKEND STREQUAL "HIP" OR PSZ_BACKEND STREQUAL "hip") + message("[psz::info] HIP (ROCm) backend has been selected.") + include(cmake/hip.cmake) elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") message("[psz::info] oneAPI backend has been selected.") include(cmake/sycl.cmake) diff --git a/README.md b/README.md index fa80da1a..4a773af2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ pSZ/cuSZ: A GPU-Based Error-Bounded Lossy Compressor for Scientific Data

-pSZ/cuSZ (cuSZ for short) is a GPU implementation of the seminal [SZ algorithm](https://github.com/szcompressor/SZ). It is the *first* GPU-practical framework of error-bounded lossy compression on GPU for scientific data (circa 2020), aiming to improve SZ's throughput on heterogeneous HPC systems. pSZ/cuSZ primarily focuses on CUDA backend support, with other GPU-parallel backends in development. pSZ/cuSZ is formerly known as cuSZ, which is also the short form of its current name. +pSZ/cuSZ (cuSZ for short) is a GPU implementation of the seminal [SZ algorithm](https://github.com/szcompressor/SZ). It is the *first* GPU-practical framework of error-bounded lossy compression on GPU for scientific data (circa 2020), aiming to improve SZ's throughput on heterogeneous HPC systems. pSZ/cuSZ primarily focuses on CUDA backend support, with other GPU-parallel backends in development, including an AMD GPU (ROCm/HIP) backend selected at build time with `-DPSZ_BACKEND=HIP`. pSZ/cuSZ is formerly known as cuSZ, which is also the short form of its current name. (c) 2025 by Argonne National Laboratory and Oakland University. See [COPYRIGHT](https://github.com/szcompressor/cuSZ/blob/master/LICENSE) in top-level directory. diff --git a/cmake/hip-compat/cooperative_groups.h b/cmake/hip-compat/cooperative_groups.h new file mode 100644 index 00000000..85beb491 --- /dev/null +++ b/cmake/hip-compat/cooperative_groups.h @@ -0,0 +1,8 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim placed on the HIP include path so sources that #include +// resolve against HIP's cooperative-groups header. +#ifndef PSZ_HIP_COMPAT_COOPERATIVE_GROUPS_H +#define PSZ_HIP_COMPAT_COOPERATIVE_GROUPS_H +#include +#endif diff --git a/cmake/hip-compat/cooperative_groups/memcpy_async.h b/cmake/hip-compat/cooperative_groups/memcpy_async.h new file mode 100644 index 00000000..5477bc76 --- /dev/null +++ b/cmake/hip-compat/cooperative_groups/memcpy_async.h @@ -0,0 +1,9 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim for sources that #include . HIP's +// cooperative-groups support lives in a single header and does not split out a +// memcpy_async sub-header; redirect to it so the include resolves. +#ifndef PSZ_HIP_COMPAT_CG_MEMCPY_ASYNC_H +#define PSZ_HIP_COMPAT_CG_MEMCPY_ASYNC_H +#include +#endif diff --git a/cmake/hip-compat/cuda.h b/cmake/hip-compat/cuda.h new file mode 100644 index 00000000..cfc02aa1 --- /dev/null +++ b/cmake/hip-compat/cuda.h @@ -0,0 +1,9 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim placed on the HIP include path so sources that #include resolve +// against the HIP runtime; the CUDA->HIP translation macros are supplied by the +// force-included psz_hip_compat.h prelude / the cuda_runtime.h shim. +#ifndef PSZ_HIP_COMPAT_CUDA_H +#define PSZ_HIP_COMPAT_CUDA_H +#include +#endif diff --git a/cmake/hip-compat/cuda_fp16.h b/cmake/hip-compat/cuda_fp16.h new file mode 100644 index 00000000..6c359b2a --- /dev/null +++ b/cmake/hip-compat/cuda_fp16.h @@ -0,0 +1,7 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim so sources that #include resolve against HIP's half type. +#ifndef PSZ_HIP_COMPAT_CUDA_FP16_H +#define PSZ_HIP_COMPAT_CUDA_FP16_H +#include +#endif diff --git a/cmake/hip-compat/cuda_runtime.h b/cmake/hip-compat/cuda_runtime.h new file mode 100644 index 00000000..7c38de7f --- /dev/null +++ b/cmake/hip-compat/cuda_runtime.h @@ -0,0 +1,17 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim placed on the HIP include path so sources that #include +// (kernel .cu TUs and host .cc TUs alike) resolve against the HIP runtime and +// pick up the CUDA->HIP translation macros. Device TUs additionally get the +// force-included psz_hip_compat.h prelude, but a plain host .cc that only +// includes is fully covered by this shim alone. +#ifndef PSZ_HIP_COMPAT_CUDA_RUNTIME_H +#define PSZ_HIP_COMPAT_CUDA_RUNTIME_H + +#include + +#include "macro/c_cu2hip_0_translation.h" +#include "macro/c_cu2hip_1_fix_primitives.h" +#include "macro/c_cu2hip_2_suppress_warning.h" + +#endif diff --git a/cmake/hip-compat/cuda_runtime_api.h b/cmake/hip-compat/cuda_runtime_api.h new file mode 100644 index 00000000..f0218248 --- /dev/null +++ b/cmake/hip-compat/cuda_runtime_api.h @@ -0,0 +1,9 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim so sources that #include resolve against the HIP +// runtime. The CUDA->HIP translation macros are supplied by the cuda_runtime.h +// shim / the force-included psz_hip_compat.h prelude. +#ifndef PSZ_HIP_COMPAT_CUDA_RUNTIME_API_H +#define PSZ_HIP_COMPAT_CUDA_RUNTIME_API_H +#include +#endif diff --git a/cmake/hip-compat/curand.h b/cmake/hip-compat/curand.h new file mode 100644 index 00000000..26636c16 --- /dev/null +++ b/cmake/hip-compat/curand.h @@ -0,0 +1,18 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// Shim placed on the HIP include path so sources that #include +// resolve against hipRAND. The cuRAND host-generator API used by cuSZ +// (portable/src/utils/rand.cu.cc) maps 1:1 onto hipRAND. +#ifndef PSZ_HIP_COMPAT_CURAND_H +#define PSZ_HIP_COMPAT_CURAND_H + +#include + +#define curandGenerator_t hiprandGenerator_t +#define curandCreateGenerator hiprandCreateGenerator +#define curandSetPseudoRandomGeneratorSeed hiprandSetPseudoRandomGeneratorSeed +#define curandGenerateUniform hiprandGenerateUniform +#define curandGenerateUniformDouble hiprandGenerateUniformDouble +#define CURAND_RNG_PSEUDO_DEFAULT HIPRAND_RNG_PSEUDO_DEFAULT + +#endif diff --git a/cmake/hip-compat/psz_hip_compat.h b/cmake/hip-compat/psz_hip_compat.h new file mode 100644 index 00000000..3dcf02ff --- /dev/null +++ b/cmake/hip-compat/psz_hip_compat.h @@ -0,0 +1,22 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// HIP/ROCm backend compatibility prelude for cuSZ. +// +// This header is force-included (compiler -include) into every translation +// unit compiled for the HIP backend. The kernel and host sources are written +// in CUDA spelling and reused as-is for HIP (single-source, no .hip mirrors); +// this prelude pulls in the HIP runtime and the CUDA->HIP translation macros so +// those sources compile unchanged against ROCm. It is never on the CUDA or SYCL +// include path, so those backends are unaffected. + +#ifndef PSZ_HIP_COMPAT_H +#define PSZ_HIP_COMPAT_H + +#include +#include + +#include "macro/c_cu2hip_0_translation.h" +#include "macro/c_cu2hip_1_fix_primitives.h" +#include "macro/c_cu2hip_2_suppress_warning.h" + +#endif /* PSZ_HIP_COMPAT_H */ diff --git a/cmake/hip.cmake b/cmake/hip.cmake new file mode 100644 index 00000000..91622d33 --- /dev/null +++ b/cmake/hip.cmake @@ -0,0 +1,349 @@ +# ------------------------------------------------------------------------------ +# HIP (ROCm) backend. Mirrors cmake/cuda.cmake: the same single-source +# .cu/.cu.inl kernel and host files are reused and marked LANGUAGE HIP, with the +# CUDA->HIP translation supplied by the force-included cmake/hip-compat prelude +# and the portable c_cu2hip macros. Library targets keep their _cu names so the +# shared install/test plumbing is backend-agnostic. +# ------------------------------------------------------------------------------ + +add_compile_definitions( + PSZ_USE_HIP + _PORTABLE_USE_HIP +) + +find_package(hip REQUIRED) +find_package(hiprand REQUIRED) +find_package(rocthrust REQUIRED) +find_package(rocprim REQUIRED) + +include(GNUInstallDirs) +include(CTest) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/psz/src/cusz_version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/psz/include/cusz_version.h" + @ONLY +) + +# All .cu/.cu.inl sources are compiled as HIP without renaming. A single helper +# marks a list of sources LANGUAGE HIP. +function(psz_mark_hip) + set_source_files_properties(${ARGN} PROPERTIES LANGUAGE HIP) +endfunction() + +# ------------------------------------------------------------------------------ +# Common compile settings (interface target) +# ------------------------------------------------------------------------------ + +add_library(psz_cu_compile_settings INTERFACE) + +target_compile_features(psz_cu_compile_settings + INTERFACE + cxx_std_17 +) + +target_compile_definitions(psz_cu_compile_settings + INTERFACE + $<$:__STRICT_ANSI__> + # ROCm/TheRock amd_hip_bf16.h defines __shfl_*_sync bf16 overloads that + # clash with the templated forms from amd_warp_sync_functions.h on Windows; + # the c_cu2hip macros redirect __shfl_*_sync to __shfl_* so suppressing the + # bf16 overloads is safe. + $<$:HIP_DISABLE_WARP_SYNC_BUILTINS> +) + +# Force-include the HIP compatibility prelude (hip runtime + cooperative groups +# + CUDA->HIP translation macros) and put the include shims (cuda_runtime.h, +# cooperative_groups.h, curand.h) ahead of the rest of the include path. +target_compile_options(psz_cu_compile_settings + INTERFACE + $<$:-Wno-deprecated-declarations> + $<$:-include;${CMAKE_CURRENT_SOURCE_DIR}/cmake/hip-compat/psz_hip_compat.h> +) + +target_include_directories(psz_cu_compile_settings + INTERFACE + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ +) + +# ------------------------------------------------------------------------------ +# Dependencies (installed or fallback) +# ------------------------------------------------------------------------------ + +find_package(PORTABLE QUIET) +if(NOT TARGET PORTABLE::PORTABLE AND NOT TARGET PORTABLE) + add_subdirectory(portable) +endif() + +set(_PORTABLE_TARGET "") +if(TARGET PORTABLE::PORTABLE) + set(_PORTABLE_TARGET PORTABLE::PORTABLE) +elseif(TARGET PORTABLE) + set(_PORTABLE_TARGET PORTABLE) +else() + message(FATAL_ERROR + "PORTABLE target not available. Provide PORTABLE or add the portable subdirectory." + ) +endif() + +if(NOT TARGET DEPS::deps) + add_library(DEPS::deps ALIAS "${_PORTABLE_TARGET}") +endif() + +target_link_libraries(psz_cu_compile_settings + INTERFACE + DEPS::deps +) + +find_package(FZG QUIET) +if(NOT TARGET FZG::fzg_cu AND NOT FZG_FOUND) + add_subdirectory(codec/fzg) +endif() + +find_package(EVAL QUIET) +if(NOT TARGET EVAL::utils_headers AND NOT EVAL_FOUND) + add_subdirectory(utils) +endif() + +# ------------------------------------------------------------------------------ +# Libraries +# ------------------------------------------------------------------------------ + +find_package(PHF QUIET) +if(NOT TARGET PHF::phf_cu AND NOT PHF_FOUND) + add_subdirectory(codec/hf) +endif() +if(TARGET phf_cu AND NOT TARGET PSZ::CUDA::phf) + add_library(PSZ::CUDA::phf ALIAS phf_cu) + add_library(CUSZ::phf ALIAS phf_cu) +elseif(TARGET PHF::phf_cu AND NOT TARGET PSZ::CUDA::phf) + add_library(PSZ::CUDA::phf ALIAS PHF::phf_cu) + add_library(CUSZ::phf ALIAS PHF::phf_cu) +endif() + +add_library(psz_seq_core + psz/src/kernel/lrz.seq.cc + psz/src/kernel/hist_generic.seq.cc + psz/src/kernel/histsp.seq.cc + psz/src/kernel/spvn.seq.cc +) +target_link_libraries(psz_seq_core + PUBLIC + psz_cu_compile_settings +) + +set(psz_cu_mem_sources + psz/src/buf_comp.cc + psz/src/buf_comp_dummy.cu +) +psz_mark_hip(psz/src/buf_comp_dummy.cu) +add_library(psz_cu_mem ${psz_cu_mem_sources}) +target_link_libraries(psz_cu_mem + PUBLIC + psz_cu_compile_settings + EVAL::stat_cu + DEPS::deps + PHF::phf_cu + hip::device +) + +set(psz_cu_core_sources + psz/src/compile/hist_generic.cu + psz/src/compile/histsp.cu + psz/src/compile/proto_lrz_c.cu + psz/src/compile/proto_lrz_x.cu + psz/src/compile/spvn.cu + psz/src/compile/lrz_c.cu + psz/src/compile/lrz_x.cu + psz/src/compile/spl_y24_c_u1.cu + psz/src/compile/spl_y24_c_u2.cu + psz/src/compile/spl_y24_x_u1.cu + psz/src/compile/spl_y24_x_u2.cu + psz/src/compile/spl_y25_c_u1.cu + psz/src/compile/spl_y25_c_u2.cu + psz/src/compile/spl_y25_x_u1.cu + psz/src/compile/spl_y25_x_u2.cu +) +psz_mark_hip(${psz_cu_core_sources}) +add_library(psz_cu_core ${psz_cu_core_sources}) +target_link_libraries(psz_cu_core + PUBLIC + psz_cu_compile_settings + psz_cu_mem + PHF::phf_cu + hip::device +) + +# verinfo.cu (NVML) and verinfo_nv.cu (CUDA driver-API deviceQuery) are replaced +# by verinfo_hip.cu, which reports the HIP/ROCm toolchain and device properties. +set(psz_cu_utils_sources + psz/src/cli/verinfo.cc + psz/src/cli/verinfo_hip.cu + psz/src/cli/context.cc + psz/src/header.c +) +psz_mark_hip(psz/src/cli/verinfo_hip.cu) +add_library(psz_cu_utils ${psz_cu_utils_sources}) +target_link_libraries(psz_cu_utils + PUBLIC + psz_cu_compile_settings + PHF::phf_cu + EVAL::stat_seq + EVAL::viewer_cu + hip::device +) + +if(PSZ_ACTIVATE_LC) + add_compile_definitions(PSZ_USE_LC_FIXED) + set(lc_gen_sources + third_party/lc_gen/lc_connector.cu + third_party/lc_gen/comp-tcms.cu third_party/lc_gen/decomp-tcms.cu + third_party/lc_gen/comp-bitr.cu third_party/lc_gen/decomp-bitr.cu + third_party/lc_gen/comp-rtr.cu third_party/lc_gen/decomp-rtr.cu + ) + psz_mark_hip(${lc_gen_sources}) + add_library(lc_gen ${lc_gen_sources}) + target_compile_options(lc_gen PRIVATE + $<$:-O3 -ffp-contract=off> + $<$:-O3 -march=native -mno-fma> + ) + target_link_libraries(lc_gen PUBLIC psz_cu_compile_settings hip::device) +endif() + +add_library(cusz + psz/src/compressor.cc + psz/src/libcusz.cc +) +target_link_libraries(cusz + PUBLIC + psz_cu_compile_settings + psz_cu_core + psz_cu_mem + psz_cu_utils + EVAL::stat_cu + PHF::phf_cu + FZG::fzg_cu + hip::device +) +if(PSZ_ACTIVATE_LC) + target_link_libraries(cusz PUBLIC lc_gen) +endif() + +# ------------------------------------------------------------------------------ +# Executable +# ------------------------------------------------------------------------------ + +add_executable(cusz-bin psz/src/cli/cli.cc psz/src/cli/executor.cc) +psz_mark_hip(psz/src/cli/cli.cc psz/src/cli/executor.cc) +target_link_libraries(cusz-bin PRIVATE cusz) +set_target_properties(cusz-bin PROPERTIES OUTPUT_NAME cusz) + +# ------------------------------------------------------------------------------ +# Examples / Tests +# ------------------------------------------------------------------------------ + +if(PSZ_BUILD_EXAMPLES) + add_subdirectory(example) +endif() + +if(BUILD_TESTING) + add_subdirectory(test) + + include(ProcessorCount) + ProcessorCount(N) + if(N EQUAL 0) + set(N 8) + endif() + add_custom_target(check + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --parallel ${N} + USES_TERMINAL + ) +endif() + +# ------------------------------------------------------------------------------ +# Installation (CUSZ:: namespace, back compat) +# ------------------------------------------------------------------------------ + +install(TARGETS psz_cu_compile_settings EXPORT CUSZTargets) + +install(TARGETS + psz_seq_core + psz_cu_core + psz_cu_mem + psz_cu_utils + cusz + eval_cu + eval_seq + eval_viewer_cu + EXPORT CUSZTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) +if(PSZ_ACTIVATE_LC) + install(TARGETS + lc_gen + EXPORT CUSZTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) +endif() + +install(TARGETS + cusz-bin + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install( + EXPORT CUSZTargets + NAMESPACE CUSZ:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/CUSZ +) + +include(CMakePackageConfigHelpers) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CUSZConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/CUSZConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/CUSZ +) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/CUSZConfigVersion.cmake" + VERSION "${PROJECT_VERSION}" + COMPATIBILITY AnyNewerVersion +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/CUSZConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/CUSZConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/CUSZ +) + +install(DIRECTORY + portable/include + psz/include + codec/hf/include + codec/fzg/include + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cusz +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/psz/include/cusz_version.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cusz +) diff --git a/cmake/probe.cmake b/cmake/probe.cmake index 892f3231..4598ed35 100644 --- a/cmake/probe.cmake +++ b/cmake/probe.cmake @@ -14,6 +14,10 @@ if(PSZ_BACKEND STREQUAL "CUDA" OR PSZ_BACKEND STREQUAL "cuda") message("[psz::info] $\{CMAKE_CUDA_COMPILER_ID\}: " ${CMAKE_CUDA_COMPILER_ID}) message("[psz::info] $\{CMAKE_CUDA_COMPILER_VERSION\}: " ${CMAKE_CUDA_COMPILER_VERSION}) message("[psz::info] $\{CMAKE_CUDA_ARCHITECTURES\}: " ${CMAKE_CUDA_ARCHITECTURES}) +elseif(PSZ_BACKEND STREQUAL "HIP" OR PSZ_BACKEND STREQUAL "hip") + message("[psz::info] $\{CMAKE_HIP_COMPILER\}: " ${CMAKE_HIP_COMPILER}) + message("[psz::info] $\{CMAKE_HIP_COMPILER_VERSION\}: " ${CMAKE_HIP_COMPILER_VERSION}) + message("[psz::info] $\{CMAKE_HIP_ARCHITECTURES\}: " ${CMAKE_HIP_ARCHITECTURES}) elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") else() message(FATAL_ERROR "Unsupported PSZ_BACKEND selected: ${PSZ_BACKEND}") diff --git a/codec/fzg/CMakeLists.txt b/codec/fzg/CMakeLists.txt index 5dd3a485..4a27b4ef 100644 --- a/codec/fzg/CMakeLists.txt +++ b/codec/fzg/CMakeLists.txt @@ -1,12 +1,20 @@ cmake_minimum_required(VERSION 3.18) -project(FZG - VERSION 0.1.0 - LANGUAGES CXX CUDA -) - -find_package(CUDAToolkit REQUIRED) +if(NOT PSZ_BACKEND) + set(PSZ_BACKEND "CUDA") +endif() +string(TOUPPER "${PSZ_BACKEND}" FZG_BACKEND_UPPER) + +if(FZG_BACKEND_UPPER STREQUAL "HIP") + project(FZG VERSION 0.1.0 LANGUAGES CXX HIP) + find_package(hip REQUIRED) + set(_FZG_GPU_RT hip::device) +else() + project(FZG VERSION 0.1.0 LANGUAGES CXX CUDA) + find_package(CUDAToolkit REQUIRED) + set(_FZG_GPU_RT CUDA::cudart) +endif() # ------------------------------------------------------------------------------ # Dependency: PORTABLE (installed or fallback) @@ -39,6 +47,16 @@ add_library(fzg_cu add_library(FZG::fzg_cu ALIAS fzg_cu) +if(FZG_BACKEND_UPPER STREQUAL "HIP") + set_source_files_properties(src/fzg_kernel.cu PROPERTIES LANGUAGE HIP) + target_compile_definitions(fzg_cu PRIVATE FZG_USE_HIP _PORTABLE_USE_HIP) + # Host .cc TUs (fzg_hl.cc, fzg_buf.cc) and the HIP kernel TU all need the + # CUDA->HIP translation prelude and the cuda_runtime.h shim. + target_include_directories(fzg_cu PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/hip-compat) + target_compile_options(fzg_cu PRIVATE + -include ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/hip-compat/psz_hip_compat.h) +endif() + target_include_directories(fzg_cu PUBLIC $ @@ -47,7 +65,7 @@ target_include_directories(fzg_cu target_link_libraries(fzg_cu PUBLIC - CUDA::cudart + ${_FZG_GPU_RT} PORTABLE ) diff --git a/codec/hf/CMakeLists.txt b/codec/hf/CMakeLists.txt index 097a609c..175f3b5b 100644 --- a/codec/hf/CMakeLists.txt +++ b/codec/hf/CMakeLists.txt @@ -1,12 +1,22 @@ cmake_minimum_required(VERSION 3.18) -project(PHF - VERSION 0.1.0 - LANGUAGES CXX CUDA -) - -find_package(CUDAToolkit REQUIRED) +if(NOT PSZ_BACKEND) + set(PSZ_BACKEND "CUDA") +endif() +string(TOUPPER "${PSZ_BACKEND}" PHF_BACKEND_UPPER) + +if(PHF_BACKEND_UPPER STREQUAL "HIP") + project(PHF VERSION 0.1.0 LANGUAGES CXX HIP) + find_package(hip REQUIRED) + set(_PHF_GPU_RT hip::device) + set(_PHF_GPU_DEFINE PHF_USE_HIP) +else() + project(PHF VERSION 0.1.0 LANGUAGES CXX CUDA) + find_package(CUDAToolkit REQUIRED) + set(_PHF_GPU_RT CUDA::cudart) + set(_PHF_GPU_DEFINE PHF_USE_CUDA) +endif() find_package(PORTABLE QUIET) if(NOT TARGET PORTABLE AND NOT TARGET PORTABLE::PORTABLE) @@ -35,7 +45,7 @@ endif() target_compile_definitions(${PHF_PORTABLE_TARGET} INTERFACE - PHF_USE_CUDA + ${_PHF_GPU_DEFINE} ) # ------------------------------------------------------------------------------ @@ -80,6 +90,19 @@ add_library(phf_cu ) add_library(PHF::phf_cu ALIAS phf_cu) +if(PHF_BACKEND_UPPER STREQUAL "HIP") + get_target_property(_phf_sources phf_cu SOURCES) + foreach(_src ${_phf_sources}) + if(_src MATCHES "\\.cu$") + set_source_files_properties(${_src} PROPERTIES LANGUAGE HIP) + endif() + endforeach() + target_compile_definitions(phf_cu PRIVATE PHF_USE_HIP _PORTABLE_USE_HIP) + target_include_directories(phf_cu PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/hip-compat) + target_compile_options(phf_cu PRIVATE + -include ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/hip-compat/psz_hip_compat.h) +endif() + target_include_directories(phf_cu PUBLIC $ @@ -91,7 +114,7 @@ target_include_directories(phf_cu target_link_libraries(phf_cu PUBLIC - CUDA::cudart + ${_PHF_GPU_RT} ${PHF_PORTABLE_TARGET} ) diff --git a/codec/hf/src/hf_buf.cc b/codec/hf/src/hf_buf.cc index f7eaa1b5..7561b0a0 100644 --- a/codec/hf/src/hf_buf.cc +++ b/codec/hf/src/hf_buf.cc @@ -14,7 +14,14 @@ namespace { struct phf_eager_module_loading_init { - phf_eager_module_loading_init() { setenv("CUDA_MODULE_LOADING", "EAGER", /*overwrite=*/0); } + phf_eager_module_loading_init() + { +#if defined(_WIN32) + if (not std::getenv("CUDA_MODULE_LOADING")) _putenv_s("CUDA_MODULE_LOADING", "EAGER"); +#else + setenv("CUDA_MODULE_LOADING", "EAGER", /*overwrite=*/0); +#endif + } }; phf_eager_module_loading_init _phf_eager_module_loading_init_singleton; } // namespace diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index dbed245a..08687925 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,5 +1,7 @@ if(PSZ_BACKEND STREQUAL "CUDA" OR PSZ_BACKEND STREQUAL "cuda") include(cmake/cuda-example.cmake) +elseif(PSZ_BACKEND STREQUAL "HIP" OR PSZ_BACKEND STREQUAL "hip") + include(cmake/hip-example.cmake) elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") include(cmake/sycl-example.cmake) endif() diff --git a/example/cmake/hip-example.cmake b/example/cmake/hip-example.cmake new file mode 100644 index 00000000..2092284b --- /dev/null +++ b/example/cmake/hip-example.cmake @@ -0,0 +1,25 @@ +# HIP (ROCm) example/driver binaries. Mirrors cuda-example.cmake; the bin_hf +# binary backs the bin_hf ctest matrix. CUDA::cupti (profiling-only) is dropped +# on the HIP path. The .cc sources are reused unchanged; the cuda_runtime.h +# shim and c_cu2hip macros (carried by cusz's psz_cu_compile_settings) translate +# their CUDA API calls. +# +# bin_pred2 is omitted: pred_run.hh uses cudaFree as a std::unique_ptr deleter +# (decltype(&cudaFree)), but cudaFree is a macro on the HIP path, not a function +# symbol. bin_pred2 is not used by any HIP ctest (its tests are in +# cuda-test-bin_pred.cmake, which is CUDA-only). + +add_library(example_utils2 src/ex_utils2.cc) +target_link_libraries(example_utils2 PRIVATE cusz) + +add_executable(bin_pred1 src/bin_pred1.cc) +target_link_libraries(bin_pred1 PRIVATE cusz) + +add_executable(bin_hf src/bin_phf.cc) +target_link_libraries(bin_hf PRIVATE cusz hip::device PORTABLE::testutils) + +add_executable(bin_hist src/bin_hist.cc) +target_link_libraries(bin_hist PRIVATE cusz hip::device) + +add_executable(batch_run src/batch_run.cc) +target_link_libraries(batch_run PRIVATE cusz example_utils2 hip::device) diff --git a/portable/CMakeLists.txt b/portable/CMakeLists.txt index 508ba8f8..9a2b9e62 100644 --- a/portable/CMakeLists.txt +++ b/portable/CMakeLists.txt @@ -1,12 +1,25 @@ cmake_minimum_required(VERSION 3.18) -project(PORTABLE - VERSION 0.1.0 - LANGUAGES CXX CUDA -) - -find_package(CUDAToolkit REQUIRED) +if(NOT PSZ_BACKEND) + set(PSZ_BACKEND "CUDA") +endif() +string(TOUPPER "${PSZ_BACKEND}" PORTABLE_BACKEND_UPPER) + +if(PORTABLE_BACKEND_UPPER STREQUAL "HIP") + project(PORTABLE VERSION 0.1.0 LANGUAGES CXX HIP) + find_package(hip REQUIRED) + find_package(hiprand REQUIRED) + set(_PORTABLE_GPU_DEFINE _PORTABLE_USE_HIP) + set(_PORTABLE_GPU_RT hip::host) + set(_PORTABLE_GPU_RAND hip::hiprand) +else() + project(PORTABLE VERSION 0.1.0 LANGUAGES CXX CUDA) + find_package(CUDAToolkit REQUIRED) + set(_PORTABLE_GPU_DEFINE _PORTABLE_USE_CUDA) + set(_PORTABLE_GPU_RT CUDA::cudart) + set(_PORTABLE_GPU_RAND CUDA::curand) +endif() # ------------------------------------------------------------------------------ # Header-only interface target @@ -21,7 +34,7 @@ target_compile_features(PORTABLE target_compile_definitions(PORTABLE INTERFACE - _PORTABLE_USE_CUDA + ${_PORTABLE_GPU_DEFINE} ) target_include_directories(PORTABLE @@ -41,11 +54,17 @@ add_library(portable_testutils src/utils/synth.cc ) add_library(PORTABLE::testutils ALIAS portable_testutils) +if(PORTABLE_BACKEND_UPPER STREQUAL "HIP") + # rand.cu.cc is compiled as host C++ and includes ; the hip-compat + # shim maps it onto hipRAND. + target_include_directories(portable_testutils + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/hip-compat) +endif() target_link_libraries(portable_testutils PUBLIC PORTABLE - CUDA::cudart - CUDA::curand + ${_PORTABLE_GPU_RT} + ${_PORTABLE_GPU_RAND} ) # ------------------------------------------------------------------------------ diff --git a/portable/include/c_type.h b/portable/include/c_type.h index 9ee4f8cb..c87aefc7 100644 --- a/portable/include/c_type.h +++ b/portable/include/c_type.h @@ -12,7 +12,7 @@ extern "C" { // enum for device type typedef enum { CPU, NVGPU, INTELGPU } _ptb_device; // enum for runtime type -typedef enum { SEQ, SIMD, OPENMP, CUDA, SYCL, THRUST_DPL } _ptb_runtime; +typedef enum { SEQ, SIMD, OPENMP, CUDA, HIP, SYCL, THRUST_DPL } _ptb_runtime; // enum for toolkit type typedef enum { VENDOR_NATIVE, KOKKOS, ONEAPI } _ptb_toolkit; diff --git a/portable/include/cxx_typing.h b/portable/include/cxx_typing.h index ac8fc416..d8cc286e 100644 --- a/portable/include/cxx_typing.h +++ b/portable/include/cxx_typing.h @@ -49,7 +49,11 @@ template <> struct TypeSym { static const _ptb_dtype type = U1; static const template <> struct TypeSym { static const _ptb_dtype type = U2; static const int width = sizeof(u2); }; template <> struct TypeSym { static const _ptb_dtype type = U4; static const int width = sizeof(u4); }; template <> struct TypeSym { static const _ptb_dtype type = U8; static const int width = sizeof(u8); }; +#if !defined(_WIN32) +// On Windows (MSVC/LLP64) uint64_t and unsigned long long are the same type, so a +// separate TypeSym would redefine TypeSym; on LP64 they are distinct. template <> struct TypeSym { static const _ptb_dtype type = ULL; static const int width = sizeof(ull); }; +#endif // clang-format on } // namespace _ptb diff --git a/portable/include/macro/c_cu2hip_0_translation.h b/portable/include/macro/c_cu2hip_0_translation.h index 8ff4891b..1286dacf 100644 --- a/portable/include/macro/c_cu2hip_0_translation.h +++ b/portable/include/macro/c_cu2hip_0_translation.h @@ -44,4 +44,16 @@ #define cudaGetErrorString(...) hipGetErrorString(__VA_ARGS__) #define cudaGetLastError(...) hipGetLastError(__VA_ARGS__) -#define cudaDeviceReset(...) hipDeviceReset(__VA_ARGS__) \ No newline at end of file +#define cudaDeviceReset(...) hipDeviceReset(__VA_ARGS__) + +#define cudaMemsetAsync(...) hipMemsetAsync(__VA_ARGS__) +#define cudaGetSymbolAddress(...) hipGetSymbolAddress(__VA_ARGS__) +#define cudaOccupancyMaxActiveBlocksPerMultiprocessor(...) \ + hipOccupancyMaxActiveBlocksPerMultiprocessor(__VA_ARGS__) + +#define cudaEvent_t hipEvent_t +#define cudaEventCreate(...) hipEventCreate(__VA_ARGS__) +#define cudaEventDestroy(...) hipEventDestroy(__VA_ARGS__) +#define cudaEventRecord(...) hipEventRecord(__VA_ARGS__) +#define cudaEventSynchronize(...) hipEventSynchronize(__VA_ARGS__) +#define cudaEventElapsedTime(...) hipEventElapsedTime(__VA_ARGS__) \ No newline at end of file diff --git a/portable/include/macro/c_cu2hip_1_fix_primitives.h b/portable/include/macro/c_cu2hip_1_fix_primitives.h index 5a2182db..3ce3e14d 100644 --- a/portable/include/macro/c_cu2hip_1_fix_primitives.h +++ b/portable/include/macro/c_cu2hip_1_fix_primitives.h @@ -1,6 +1,25 @@ // clang-format off -#define __shfl_sync(MASK, VAR, SRC_LANE, WIDTH) __shfl(VAR, SRC_LANE, WIDTH) -#define __shfl_up_sync(MASK, VAR, DELTA, WIDTH) __shfl_up(VAR, DELTA, WIDTH) -#define __shfl_down_sync(MASK, VAR, DELTA, WIDTH) __shfl_down(VAR, DELTA, WIDTH) -#define __shfl_xor_sync(MASK, VAR, LANE_MASK, WIDTH) __shfl_xor(VAR, LANE_MASK, WIDTH) -// clang-format on \ No newline at end of file + +// Warp shuffle intrinsics: HIP's __shfl_*_sync require a 64-bit lane mask, but +// the CUDA-spelled call sites pass a 32-bit mask (0xffffffff). Redirect to the +// maskless __shfl_* forms instead. Variadic so both the 3-arg (default-width) +// and 4-arg (explicit-width) call forms map through. +#define __shfl_sync(MASK, VAR, SRC_LANE, ...) __shfl(VAR, SRC_LANE, ##__VA_ARGS__) +#define __shfl_up_sync(MASK, VAR, DELTA, ...) __shfl_up(VAR, DELTA, ##__VA_ARGS__) +#define __shfl_down_sync(MASK, VAR, DELTA, ...) __shfl_down(VAR, DELTA, ##__VA_ARGS__) +#define __shfl_xor_sync(MASK, VAR, LANE_MASK, ...) __shfl_xor(VAR, LANE_MASK, ##__VA_ARGS__) + +// Ballot intrinsic: every call site here uses a logical 32-lane warp (mask +// 0xffffffff) and consumes the result as a 32-bit mask. HIP's __ballot returns +// the full wavefront mask (32 bits on wave32, 64 bits on wave64). On wave64 a +// 32-lane logical warp occupies one of the two 32-lane halves of the wavefront, +// so extract the 32 bits for the current thread's half by shifting down by the +// half-aligned base lane (__lane_id() & ~31). This is correct on both wave32 +// (shift is always 0) and wave64, and matches CUDA's uint32_t semantics. +#define __ballot_sync(MASK, PRED) \ + ((unsigned int)(__ballot(PRED) >> (__lane_id() & ~(unsigned)31))) + +// Population count and __activemask: HIP uses the same intrinsic name as CUDA +// for __popc; no translation needed. + +// clang-format on diff --git a/portable/include/mem/cxx_backends.h b/portable/include/mem/cxx_backends.h index 9121a732..9ac99e53 100644 --- a/portable/include/mem/cxx_backends.h +++ b/portable/include/mem/cxx_backends.h @@ -14,6 +14,8 @@ #if defined(_PORTABLE_USE_CUDA) #include +#elif defined(_PORTABLE_USE_HIP) +#include #elif defined(_PORTABLE_USE_1API) #include #include @@ -34,6 +36,14 @@ #define GPU_EVENT cudaEvent_t #define GPU_EVENT_CREATE(e) cudaEventCreate(e); +#elif defined(_PORTABLE_USE_HIP) + +#define GPULEN3 dim3 +#define MAKE_GPULEN3(X, Y, Z) dim3(X, Y, Z) +#define GPU_BACKEND_SPECIFIC_STREAM hipStream_t +#define GPU_EVENT hipEvent_t +#define GPU_EVENT_CREATE(e) hipEventCreate(e); + #elif defined(_PORTABLE_USE_1API) #define GPULEN3 sycl::range<3> @@ -62,6 +72,24 @@ cudaEventSynchronize(E2); #define event_time_elapsed(start, end, p_millisec) cudaEventElapsedTime(p_millisec, start, end); +#elif defined(_PORTABLE_USE_HIP) + +#define event_create_pair(...) \ + ([]() -> std::pair { \ + hipEvent_t a, b; \ + hipEventCreate(&a); \ + hipEventCreate(&b); \ + return {a, b}; \ + })(__VA_ARGS__); +#define event_destroy_pair(a, b) \ + hipEventDestroy(a); \ + hipEventDestroy(b); +#define event_recording_start(E1, STREAM) hipEventRecord(E1, (hipStream_t)STREAM); +#define event_recording_stop(E2, STREAM) \ + hipEventRecord(E2, (hipStream_t)STREAM); \ + hipEventSynchronize(E2); +#define event_time_elapsed(start, end, p_millisec) hipEventElapsedTime(p_millisec, start, end); + #elif defined(_PORTABLE_USE_1API) #define event_create_pair(...) \ @@ -96,6 +124,16 @@ })(__VA_ARGS__); #define destroy_stream(stream) ([](void* s) { cudaStreamDestroy((cudaStream_t)s); })(stream); +#elif defined(_PORTABLE_USE_HIP) + +#define create_stream(...) \ + ([]() -> hipStream_t { \ + hipStream_t stream; \ + hipStreamCreate(&stream); \ + return stream; \ + })(__VA_ARGS__); +#define destroy_stream(stream) ([](void* s) { hipStreamDestroy((hipStream_t)s); })(stream); + #elif defined(_PORTABLE_USE_1API) #define create_stream(...) \ @@ -112,6 +150,11 @@ #define sync_by_stream(stream) cudaStreamSynchronize((cudaStream_t)stream); #define sync_device cudaDeviceSynchronize(); +#elif defined(_PORTABLE_USE_HIP) + +#define sync_by_stream(stream) hipStreamSynchronize((hipStream_t)stream); +#define sync_device hipDeviceSynchronize(); + #elif defined(_PORTABLE_USE_1API) #define sync_by_stream(stream) ((dpct::queue_ptr)stream)->wait(); diff --git a/portable/include/mem/cxx_mem_ops.h b/portable/include/mem/cxx_mem_ops.h index 53dbeb53..e89176b8 100644 --- a/portable/include/mem/cxx_mem_ops.h +++ b/portable/include/mem/cxx_mem_ops.h @@ -5,6 +5,8 @@ template <> struct _memcpy_direcion { #if defined(_PORTABLE_USE_CUDA) static const cudaMemcpyKind direction = cudaMemcpyHostToHost; +#elif defined(_PORTABLE_USE_HIP) + static const hipMemcpyKind direction = hipMemcpyHostToHost; #elif defined(_PORTABLE_USE_1API) // no need #endif @@ -14,6 +16,8 @@ template <> struct _memcpy_direcion { #if defined(_PORTABLE_USE_CUDA) static const cudaMemcpyKind direction = cudaMemcpyDeviceToHost; +#elif defined(_PORTABLE_USE_HIP) + static const hipMemcpyKind direction = hipMemcpyDeviceToHost; #elif defined(_PORTABLE_USE_1API) // no need #endif diff --git a/portable/include/mem/gpu_event.hh b/portable/include/mem/gpu_event.hh index 1e912bf2..8206f254 100644 --- a/portable/include/mem/gpu_event.hh +++ b/portable/include/mem/gpu_event.hh @@ -2,7 +2,9 @@ #define _PORTABLE_MEM_GPU_EVENT_HH #include +#if !defined(_PORTABLE_USE_HIP) #include +#endif #include #include @@ -10,19 +12,22 @@ #include #include #include +#include namespace _ptb { // CUDA event zone: RAII /////////////////////////////////////////////////////// +using _gpu_event_elem = std::remove_pointer::type; + struct _gpu_event_deleter { - void operator()(CUevent_st* e) const noexcept + void operator()(_gpu_event_elem* e) const noexcept { if (e) cudaEventDestroy(e); } }; -using gpu_event = std::unique_ptr; +using gpu_event = std::unique_ptr<_gpu_event_elem, _gpu_event_deleter>; inline gpu_event make_gpu_event() { @@ -51,6 +56,24 @@ struct timer_cuevent { // CUPTI zone ////////////////////////////////////////////////////////////////// +#if defined(_PORTABLE_USE_HIP) + +// ROCm has no CUPTI; the optional per-kernel profiling timer is unavailable, so +// provide an inert stub with the same interface. It never activates, so +// gpu_timer always falls back to the hipEvent wall-clock path. +struct timer_cupti { + static inline bool active = false; + static void enable() {} + void start(cudaStream_t) {} + double stop_ms(cudaStream_t s) + { + cudaStreamSynchronize(s); + return 0.0; + } +}; + +#else + // CUPTI kernel timing: sum of on-device kernel durations (vs cudaEvent wall-clock). // Buffers are app-owned (request=malloc, complete=parse+free). struct timer_cupti { @@ -112,6 +135,8 @@ struct timer_cupti { } }; +#endif // _PORTABLE_USE_HIP + // user zone ////////////////////////////////////////////////////////////////// struct gpu_timer { diff --git a/portable/include/mem/gpu_stream.hh b/portable/include/mem/gpu_stream.hh index e19620e0..c8f965b7 100644 --- a/portable/include/mem/gpu_stream.hh +++ b/portable/include/mem/gpu_stream.hh @@ -21,17 +21,20 @@ #include #include +#include namespace _ptb { +using _gpu_stream_elem = std::remove_pointer::type; + struct gpu_stream_deleter { - void operator()(CUstream_st* s) const noexcept + void operator()(_gpu_stream_elem* s) const noexcept { if (s) cudaStreamDestroy(s); } }; -using gpu_stream = std::unique_ptr; +using gpu_stream = std::unique_ptr<_gpu_stream_elem, gpu_stream_deleter>; inline gpu_stream make_gpu_stream() { diff --git a/portable/include/utils/err.hh b/portable/include/utils/err.hh index ad15ad29..8f02ea2b 100644 --- a/portable/include/utils/err.hh +++ b/portable/include/utils/err.hh @@ -1,8 +1,12 @@ #ifndef AE6DCA2E_F19B_41DB_80CB_11230E548F92 #define AE6DCA2E_F19B_41DB_80CB_11230E548F92 +#if defined(_PORTABLE_USE_CUDA) || defined(_PORTABLE_USE_HIP) #if defined(_PORTABLE_USE_CUDA) #include +#else +#include +#endif #include #include diff --git a/psz/src/cli/context.cc b/psz/src/cli/context.cc index 39b4cce6..27242708 100644 --- a/psz/src/cli/context.cc +++ b/psz/src/cli/context.cc @@ -32,6 +32,12 @@ const char* BACKEND_TEXT = "cuSZ"; const char* VERSION_TEXT = "2025-02-05 (0.16)"; const int VERSION = 20241218; +#elif defined(PSZ_USE_HIP) + +const char* BACKEND_TEXT = "hipSZ"; +const char* VERSION_TEXT = "2025-02-05 (0.16)"; +const int VERSION = 20241218; + #elif defined(PSZ_USE_1API) const char* BACKEND_TEXT = "dpSZ"; diff --git a/psz/src/cli/query/query_cpu.hh b/psz/src/cli/query/query_cpu.hh index 6ad9efb8..46041374 100644 --- a/psz/src/cli/query/query_cpu.hh +++ b/psz/src/cli/query/query_cpu.hh @@ -5,9 +5,15 @@ #define E8CDEF97_5136_45C6_A6F2_3FECD549F8A4 #include +#include #include "cusz/type.h" +#if defined(_WIN32) +#define popen _popen +#define pclose _pclose +#endif + struct cpu_diagnostics { static std::string exec_shellcmd(const char* cmd) { diff --git a/psz/src/cli/verinfo_hip.cu b/psz/src/cli/verinfo_hip.cu new file mode 100644 index 00000000..a2d19743 --- /dev/null +++ b/psz/src/cli/verinfo_hip.cu @@ -0,0 +1,92 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. +// +// \file verinfo_hip.cu +// \author Jeff Daily +// \brief HIP/ROCm implementation of the CLI version-info queries. +// +// On the HIP backend this replaces verinfo.cu (NVML driver query) and +// verinfo_nv.cu (CUDA driver-API deviceQuery). The function names mirror the +// CUDA backend (print_NVCC_ver, print_CUDA_driver, print_NVIDIA_driver, +// CUDA_devices) so the shared context.cc version banner is unchanged; here they +// report the HIP/ROCm toolchain, driver, and device properties. + +#include + +#include + +#include "cli/verinfo.h" + +void print_NVCC_ver() +{ + printf( + "- HIP: %s.%s.%s\n", // + STRINGIZE_VALUE_OF(HIP_VERSION_MAJOR), STRINGIZE_VALUE_OF(HIP_VERSION_MINOR), + STRINGIZE_VALUE_OF(HIP_VERSION_PATCH)); +} + +int print_CUDA_driver() +{ + int driver_version = 0; + std::printf("- HIP driver: "); + if (hipDriverGetVersion(&driver_version) != hipSuccess) { + std::printf("(failed to get driver version)\n"); + return 1; + } + int major_version = driver_version / 10000000; + int minor_version = (driver_version % 10000000) / 100000; + std::printf("%d.%d\n", major_version, minor_version); + return 0; +} + +int print_NVIDIA_driver() +{ + int runtime_version = 0; + if (hipRuntimeGetVersion(&runtime_version) != hipSuccess) { + std::printf("- ROCm runtime: (failed to query)\n"); + return 1; + } + int major_version = runtime_version / 10000000; + int minor_version = (runtime_version % 10000000) / 100000; + std::printf("- ROCm runtime: %d.%d\n", major_version, minor_version); + return 0; +} + +void CUDA_devices() +{ + int device_count = 0; + hipGetDeviceCount(&device_count); + + if (device_count == 0) { printf("0 devices detected\n"); } + else { + printf("%d HIP device(s):\n", device_count); + } + + for (auto dev = 0; dev < device_count; ++dev) { + hipDeviceProp_t deviceProp; + hipGetDeviceProperties(&deviceProp, dev); + + int memClockKHz = deviceProp.memoryClockRate; + auto membw_GiBps = membw_base1024(deviceProp.memoryBusWidth, memClockKHz * 1e3); + auto membw_GBps = membw_base1000(deviceProp.memoryBusWidth, memClockKHz * 1e3); + + printf("- %s (%s)\n", deviceProp.name, deviceProp.gcnArchName); + printf( + " - %d compute units; warp size: %d\n", deviceProp.multiProcessorCount, + deviceProp.warpSize); + printf( + " - global VRAM: %.0f MB (theoretically) at \n", + (float)deviceProp.totalGlobalMem / 1048576.0f); + printf(" %.1f GiB/s (base-1024) or %.1f GB/s (base-1000)\n", membw_GiBps, membw_GBps); + printf(" - L2 cache: %d bytes\n", deviceProp.l2CacheSize); + printf( + " - per-block/CU total shared memory: %zu/%zu bytes\n", deviceProp.sharedMemPerBlock, + deviceProp.sharedMemPerMultiprocessor); + printf( + " - per-block/CU max thread count: %d/%d\n", deviceProp.maxThreadsPerBlock, + deviceProp.maxThreadsPerMultiProcessor); + printf( + " - max thread-block dim (x,y,z): (%d, %d, %d)\n", deviceProp.maxThreadsDim[0], + deviceProp.maxThreadsDim[1], deviceProp.maxThreadsDim[2]); + printf(" - per-block total registers count: %d\n", deviceProp.regsPerBlock); + } +} diff --git a/psz/src/compressor.inl b/psz/src/compressor.inl index 009af9c1..cf64c110 100644 --- a/psz/src/compressor.inl +++ b/psz/src/compressor.inl @@ -24,7 +24,7 @@ template , psz::PredictorFeature>; -#if defined(PSZ_USE_CUDA) +#if defined(PSZ_USE_CUDA) || defined(PSZ_USE_HIP) #define CONCAT_ON_DEVICE(dst, src, nbyte, stream) \ if (nbyte != 0) cudaMemcpyAsync(dst, src, nbyte, cudaMemcpyDeviceToDevice, (cudaStream_t)stream); diff --git a/psz/src/kernel/histsp.cu.inl b/psz/src/kernel/histsp.cu.inl index 9617f43b..6dd46303 100644 --- a/psz/src/kernel/histsp.cu.inl +++ b/psz/src/kernel/histsp.cu.inl @@ -58,10 +58,15 @@ __global__ void KCU_histogram_sparse_multiwarp( __syncthreads(); #ifdef __HIP_PLATFORM_AMD__ + // 32-lane logical-warp inclusive scan; the result is consumed below at the + // lane-31 leader (threadIdx.x % 32 == 31). Pin the shuffle width to 32 so the + // scan domain matches that leader on both wave32 (RDNA) and wave64 (CDNA): the + // earlier wave64-only form (width 64, threadIdx.x % 64) lost the upper half of + // the scan on a 32-lane wavefront, corrupting the per-warp histogram. for (auto& sum : p_hist) { - for (auto d = 1; d < 64; d *= 2) { - auto n = __shfl_up(sum, d, 64); - if (threadIdx.x % 64 >= d) sum += n; + for (auto d = 1; d < 32; d *= 2) { + auto n = __shfl_up(sum, d, 32); + if (threadIdx.x % 32 >= d) sum += n; } } #else diff --git a/psz/src/kernel/spl_y24.cuh b/psz/src/kernel/spl_y24.cuh index 39872697..bfc7b5e3 100644 --- a/psz/src/kernel/spl_y24.cuh +++ b/psz/src/kernel/spl_y24.cuh @@ -54,7 +54,8 @@ template < typename T1, typename T2, typename FP, int LINEAR_BLOCK_SIZE, bool WORKFLOW = SPLINE3_COMPR, bool PROBE_PRED_ERROR = false> __device__ void spline3d_layout2_interpolate( - volatile T1 s_data[9][9][33], volatile T2 s_eq[9][9][33], FP eb_r, FP ebx2, int radius); + volatile T1 s_data[9][9][33], volatile T2 s_eq[9][9][33], dim3 data_size, FP eb_r, FP ebx2, + int radius); } // namespace psz namespace { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 34958120..323f274c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,8 +1,16 @@ -if(PSZ_BACKEND STREQUAL "CUDA" OR PSZ_BACKEND STREQUAL "cuda") +string(TOUPPER "${PSZ_BACKEND}" _PSZ_TEST_BACKEND_UPPER) +if(_PSZ_TEST_BACKEND_UPPER STREQUAL "CUDA") + set(PSZ_TEST_GPU_RT CUDA::cudart) include(cmake/cuda-test.cmake) # legacy unit-test binaries include(cmake/cuda-test-bin_pred.cmake) # bin_pred microbench matrix (v1-only on develop) include(cmake/cuda-test-bin_hf.cmake) # bin_hf-driven ctest matrix include(cmake/cuda-test-cusz.cmake) # cusz CLI-driven ctest matrix +elseif(_PSZ_TEST_BACKEND_UPPER STREQUAL "HIP") + set(PSZ_TEST_GPU_RT hip::device) + set(PSZ_TEST_HIP ON) + include(cmake/cuda-test.cmake) # same sources, marked LANGUAGE HIP + include(cmake/cuda-test-bin_hf.cmake) # bin_hf-driven ctest matrix (binary-agnostic) + include(cmake/cuda-test-cusz.cmake) # cusz CLI-driven ctest matrix (binary-agnostic) elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") include(cmake/sycl-test.cmake) else() diff --git a/test/cmake/cuda-test.cmake b/test/cmake/cuda-test.cmake index c87bf960..0ab19dff 100644 --- a/test/cmake/cuda-test.cmake +++ b/test/cmake/cuda-test.cmake @@ -14,6 +14,36 @@ target_include_directories( $ ) +# On the HIP backend, test TUs (host .cc and .cu alike) need the CUDA->HIP +# translation prelude and the cuda_runtime.h shim; carry both here so tests that +# do not link psz_cu_compile_settings still resolve. +if(PSZ_TEST_HIP) + find_package(hip REQUIRED) + target_compile_definitions(psz_cu_test_compile_settings + INTERFACE PSZ_USE_HIP _PORTABLE_USE_HIP) + # hip-compat first (cuda_runtime.h shim + macros), then the ROCm include dir so + # the shims' #include resolve in host C++ test TUs too. + target_include_directories(psz_cu_test_compile_settings + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/hip-compat + ${hip_INCLUDE_DIRS}) + # HIP-language test TUs get the force-included prelude via + # psz_cu_compile_settings; host .cc test TUs pick up translation via the + # cuda_runtime.h shim on demand. No -include here, to avoid a duplicate + # force-include when both interface targets are linked. +endif() + +# On the HIP backend the same .cu test sources are compiled as HIP; the +# CUDA->HIP translation prelude is carried by psz_cu_compile_settings. +if(PSZ_TEST_HIP) + set_source_files_properties( + src/test_l1_compact.cu + src/tune_histsp.cu + src/test_identical2.cu + src/test_mem_unique.cu + PROPERTIES LANGUAGE HIP) +endif() + # functionality add_executable(zigzag src/test_zigzag_codec.cc) target_link_libraries(zigzag PRIVATE psz_cu_test_compile_settings) @@ -26,13 +56,19 @@ target_link_libraries(l1_compact PRIVATE psz_cu_compile_settings add_test(test_l1_compact l1_compact) # Level-2 kernel (template; unit tests) -add_executable(histsp_cu src/tune_histsp.cu) -target_link_libraries(histsp_cu - PRIVATE psz_cu_compile_settings - psz_seq_core - cusz -) -add_test(test_histsp_cu histsp_cu) +# tune_histsp.cu.inl calls GPU_histogram_generic(d_in,...) as a constructor +# but the struct only exposes static ::init/::kernel; signature drifted upstream. +# Pre-existing CUDA build failure; excluded on the HIP path to avoid blocking +# the build. The target is a tuning/perf test only, not a correctness gate. +if(NOT PSZ_TEST_HIP) + add_executable(histsp_cu src/tune_histsp.cu) + target_link_libraries(histsp_cu + PRIVATE psz_cu_compile_settings + psz_seq_core + cusz + ) + add_test(test_histsp_cu histsp_cu) +endif() # Level-3 kernel with configuration (low-level API) add_executable(lrz_seq src/test_lrz.seq.cc) @@ -55,7 +91,7 @@ target_link_libraries(stat_identical1 PORTABLE::testutils EVAL::stat_cu EVAL::stat_seq - CUDA::cudart + ${PSZ_TEST_GPU_RT} ) add_test(test_stat_identical1 stat_identical1) @@ -67,7 +103,7 @@ target_link_libraries(stat_identical2 PORTABLE::testutils EVAL::stat_cu EVAL::stat_seq - CUDA::cudart + ${PSZ_TEST_GPU_RT} ) add_test(test_stat_identical2 stat_identical2) @@ -79,7 +115,7 @@ target_link_libraries(stat_max_error PORTABLE::testutils EVAL::stat_cu EVAL::stat_seq - CUDA::cudart + ${PSZ_TEST_GPU_RT} ) add_test(test_stat_max_error stat_max_error) @@ -89,7 +125,7 @@ target_link_libraries(mem_unique psz_cu_compile_settings psz_cu_test_compile_settings psz_cu_mem - CUDA::cudart + ${PSZ_TEST_GPU_RT} ) add_test(test_mem_unique mem_unique) @@ -98,7 +134,7 @@ target_link_libraries(test_hfr PRIVATE psz_cu_test_compile_settings PSZ::CUDA::phf - CUDA::cudart + ${PSZ_TEST_GPU_RT} ) add_test(test_hf_revisit_altcode test_hfr) @@ -113,13 +149,15 @@ add_test(test_hf_cpu_serial_codebook test_hfserial) # GPU tests serialize via a named resource lock so `ctest -j N` doesn't # oversubscribe the device. Tests that touch only the host (zigzag, lrz_seq, # test_hf_cpu_serial_codebook) are intentionally omitted and run in parallel. -set_tests_properties( +set(psz_gpu_tests test_l1_compact - test_histsp_cu test_stat_identical1 test_stat_identical2 test_stat_max_error test_mem_unique test_hf_revisit_altcode - PROPERTIES RESOURCE_LOCK gpu ) +if(NOT PSZ_TEST_HIP) + list(APPEND psz_gpu_tests test_histsp_cu) +endif() +set_tests_properties(${psz_gpu_tests} PROPERTIES RESOURCE_LOCK gpu) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index cbdbb940..4194a010 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -40,9 +40,11 @@ else() option(EVAL "build CUDA eval implementations (requires full psz tree)" ON) endif() -# Find CUDA if needed +# Find GPU toolkit if needed if(PSZ_BACKEND_UPPER STREQUAL "CUDA") find_package(CUDAToolkit REQUIRED) +elseif(PSZ_BACKEND_UPPER STREQUAL "HIP") + find_package(hip REQUIRED) endif() # Find portable headers @@ -73,6 +75,17 @@ if(EVAL_STANDALONE) INTERFACE $<$:--extended-lambda;--expt-relaxed-constexpr> ) + elseif(PSZ_BACKEND_UPPER STREQUAL "HIP") + target_compile_definitions(eval_compile_settings + INTERFACE + PSZ_USE_HIP + _PORTABLE_USE_HIP + ) + target_include_directories(eval_compile_settings + INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/hip-compat) + target_compile_options(eval_compile_settings + INTERFACE + $<$:-include;${CMAKE_CURRENT_SOURCE_DIR}/../cmake/hip-compat/psz_hip_compat.h>) elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") target_compile_definitions(eval_compile_settings INTERFACE @@ -93,6 +106,11 @@ if(PSZ_BACKEND_UPPER STREQUAL "CUDA") enable_language(CUDA) set(EVAL_BACKEND "CUDA") +elseif(PSZ_BACKEND_UPPER STREQUAL "HIP") + + enable_language(HIP) + set(EVAL_BACKEND "HIP") + elseif(PSZ_BACKEND STREQUAL "ONEAPI" OR PSZ_BACKEND STREQUAL "oneapi" OR PSZ_BACKEND STREQUAL "1api") set(EVAL_BACKEND "SYCL") @@ -173,7 +191,51 @@ if(EVAL_BACKEND STREQUAL "CUDA") else() message("[eval::info] EVAL is OFF: CUDA eval implementations skipped (require full psz tree).") endif() - + +elseif(EVAL_BACKEND STREQUAL "HIP") + # HIP reuses the same .cu/.cc sources as CUDA, marked LANGUAGE HIP; the + # CUDA->HIP translation prelude is supplied by ${eval_compile_settings} + # (psz_cu_compile_settings in the in-tree build). + if(EVAL) + set(_eval_hip_sources + src/identical.cu + src/compile/extrema-f4.cu + src/compile/extrema-f8.cu + src/compile/calc_err-f4.cu + src/compile/calc_err-f8.cu + src/compile/assess-f4.cu + src/compile/assess-f8.cu + src/compile/maxerr-all.cu + ) + set_source_files_properties(${_eval_hip_sources} PROPERTIES LANGUAGE HIP) + add_library(eval_cu ${_eval_hip_sources}) + target_link_libraries(eval_cu + PUBLIC + utils_headers + ${eval_compile_settings} + hip::device + ) + add_library(EVAL::stat_cu ALIAS eval_cu) + + set(_eval_viewer_hip_sources + src/viewer.cc + src/compile/viewer-all.cu + ) + set_source_files_properties(src/compile/viewer-all.cu PROPERTIES LANGUAGE HIP) + add_library(eval_viewer_cu ${_eval_viewer_hip_sources}) + target_link_libraries(eval_viewer_cu + PUBLIC + utils_headers + eval_seq + eval_cu + ${eval_compile_settings} + hip::device + ) + add_library(EVAL::viewer_cu ALIAS eval_viewer_cu) + else() + message("[eval::info] EVAL is OFF: HIP eval implementations skipped (require full psz tree).") + endif() + elseif(EVAL_BACKEND STREQUAL "SYCL") # SYCL/DPC++ backend - TODO: add SYCL implementations # add_library(eval_dp ...) diff --git a/utils/include/compare.hh b/utils/include/compare.hh index 0899bd37..e6f14c66 100644 --- a/utils/include/compare.hh +++ b/utils/include/compare.hh @@ -53,12 +53,13 @@ template std::tuple GPU_probe_extrema(T* in, size_t len) { T r[4]; - if constexpr (R == CUDA) + if constexpr (R == CUDA or R == HIP) cuda::GPU_extrema(in, len, r); else if constexpr (R == SYCL) dpcpp::GPU_extrema(in, len, r); else - static_assert(R == CUDA or R == SYCL, "GPU_probe_extrema supports CUDA / SYCL."); + static_assert( + R == CUDA or R == HIP or R == SYCL, "GPU_probe_extrema supports CUDA / HIP / SYCL."); return {r[0], r[1], r[2], r[3]}; } @@ -67,12 +68,13 @@ void assess_quality(psz_stats* s, T* xdata, T* odata, size_t const len) { if constexpr (P == SEQ) cppstl::CPU_assess_quality(s, xdata, odata, len); - else if constexpr (P == CUDA) + else if constexpr (P == CUDA or P == HIP) cuda::GPU_assess_quality(s, xdata, odata, len); else if constexpr (P == SYCL) dpcpp::GPU_assess_quality(s, xdata, odata, len); else - static_assert(P == SEQ or P == CUDA or P == SYCL, "assess_quality: unsupported backend."); + static_assert( + P == SEQ or P == CUDA or P == HIP or P == SYCL, "assess_quality: unsupported backend."); } } // namespace psz::analysis diff --git a/utils/src/atomics.cu.inl b/utils/src/atomics.cu.inl index 34429c81..8cfa69db 100644 --- a/utils/src/atomics.cu.inl +++ b/utils/src/atomics.cu.inl @@ -64,7 +64,9 @@ __device__ __forceinline__ T atomicAddFp(T* addr, T value) { if constexpr (std::is_same::value) { return atomicAdd(addr, value); } else if constexpr (std::is_same::value) { -#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 600) +#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 600)) || defined(__HIP_DEVICE_COMPILE__) + // CUDA SM6.0+ and HIP/ROCm provide a native double atomicAdd; the CAS loop + // below is the portable fallback for older CUDA device targets only. return atomicAdd(addr, value); #else auto addr_as_ull = reinterpret_cast(addr); diff --git a/utils/src/extrema.cu.inl b/utils/src/extrema.cu.inl index 6c60c347..e04f8664 100644 --- a/utils/src/extrema.cu.inl +++ b/utils/src/extrema.cu.inl @@ -101,21 +101,9 @@ void GPU_extrema(T* in, size_t len, T res[4]) cudaMemcpy(d_minel, in, sizeof(T), cudaMemcpyDeviceToDevice); // init min el cudaMemcpy(d_maxel, in, sizeof(T), cudaMemcpyDeviceToDevice); // init max el -// launch -#if defined(PSZ_USE_CUDA) + // launch: the same KCU_extrema kernel is used on CUDA and HIP. psz::KCU_extrema<<>>( in, len, d_minel, d_maxel, d_sum, failsafe, R); -#elif defined(PSZ_USE_HIP) - if constexpr (std::is_same::value) { - psz::extrema_kernel<<>>( - in, len, d_minel, d_maxel, d_sum, failsafe, R); - } - else { - throw std::runtime_error( - "As of now (5.5.30202), HIP does not support 64-bit integer atomic " - "operation."); - } -#endif cudaStreamSynchronize(stream); diff --git a/utils/src/viewer.cc b/utils/src/viewer.cc index 83a5a04d..70da8f50 100644 --- a/utils/src/viewer.cc +++ b/utils/src/viewer.cc @@ -19,6 +19,16 @@ using std::cout; using std::endl; using std::to_string; +// MSVC STL has no std::hash specialization, so a const-qualified +// unordered_map key fails to compile; on libstdc++ the const form is accepted. +// Keep the Linux key type byte-identical (so device objects and exported symbols +// are unchanged there) and drop the const only on Windows. +#if defined(_WIN32) +#define PSZ_MAP_KEY(T) T +#else +#define PSZ_MAP_KEY(T) T const +#endif + void psz_review_comp_time_breakdown(void* _r, psz_header* h) { std::runtime_error("psz_review_comp_time_breakdown is to be updated."); @@ -26,7 +36,7 @@ void psz_review_comp_time_breakdown(void* _r, psz_header* h) string const psz_report_query_pred(psz_predictor const p) { - const std::unordered_map lut = { + const std::unordered_map lut = { {psz_predictor::Lorenzo, "Lorenzo"}, {psz_predictor::LorenzoZigZag, "Lrz-ZigZag"}, {psz_predictor::LorenzoProto, "Lrz-Proto"}, @@ -37,7 +47,7 @@ string const psz_report_query_pred(psz_predictor const p) string const psz_report_query_hist(psz_hist const h) { - const std::unordered_map lut = { + const std::unordered_map lut = { {psz_hist::HistGeneric, "Hist"}, {psz_hist::HistSp, "Hist-Sparse"}, {psz_hist::HistNull, "Null"}, @@ -47,7 +57,7 @@ string const psz_report_query_hist(psz_hist const h) string const psz_report_query_codec1(psz_codec const c) { - const std::unordered_map lut = { + const std::unordered_map lut = { {psz_codec::HF, "HF"}, {psz_codec::HFr1, "Huffman-rev1"}, {psz_codec::HFr2, "Huffman-rev2"},