diff --git a/.appveyor.yml b/.appveyor.yml
index e0ca042a93..320acde8e1 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -51,7 +51,7 @@ build_script:
- "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%MINICONDA%\\Library\\bin;%PATH%"
# install parallelproj and Python stuff
# don't do numpy here due to https://github.com/conda-forge/numpy-feedstock/issues/350
- - conda create --name stirbuild -c conda-forge -yq libparallelproj=1 swig pytest ccache ninja cmake
+ - conda create --name stirbuild -c conda-forge -yq libparallelproj=2 swig pytest ccache ninja cmake
- CALL conda.bat activate stirbuild
- python --version
- pip install numpy matplotlib
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 1022d054ee..ad2ad0e87b 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -140,7 +140,9 @@ jobs:
compiler: clang
compiler_version: 21
cuda_version: "0"
+ # openMP off as problems again with brew
BUILD_FLAGS: "-DSTIR_OPENMP=OFF"
+ # parallelproj off as it needs openMP
parallelproj: "OFF"
BUILD_TYPE: "Release"
ROOT: "OFF"
@@ -303,16 +305,20 @@ jobs:
esac
if test "${{matrix.parallelproj}}XX" == "ONXX"; then
- git clone --depth 1 --branch v1.7.3 https://github.com/gschramm/parallelproj
- mkdir parallelproj/build
- cd parallelproj/build
+ git clone --depth 20 https://github.com/KUL-recon-lab/libparallelproj/
+ cd libparallelproj
+ git checkout v2.0.3
+ mkdir build
+ cd build
if test "${{matrix.cuda_version}}" == "0"; then
- extra_args="-DSKIP_CUDA_LIB:BOOL=ON"
+ extra_args="-DUSE_CUDA:BOOL=OFF"
+ else
+ extra_args="-DUSE_CUDA:BOOL=ON"
fi
cmake .. -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} ${extra_args}
cmake --build . --target install --config Release
cd ../..
- rm -rf parallelproj
+ rm -rf libparallelproj
fi
# Install ROOT (warning: brittle due to OS versions etc)
diff --git a/.gitmodules b/.gitmodules
index 5d1ac04565..2e6f41619f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "external_helpers/fmt"]
path = external_helpers/fmt
url = https://github.com/fmtlib/fmt.git
+[submodule "cuvec"]
+ path = external_helpers/CuVec
+ url = https://github.com/AMYPAD/CuVec
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 773c3a5986..f6f6d3c724 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -261,17 +261,18 @@ endif()
# Parallelproj
if(NOT DISABLE_Parallelproj_PROJECTOR)
- find_package(parallelproj 1.3.4 CONFIG)
+ find_package(parallelproj CONFIG)
if (parallelproj_FOUND)
set(STIR_WITH_Parallelproj_PROJECTOR ON)
- if (parallelproj_built_with_CUDA)
+ if (PARALLELPROJ_VERSION VERSION_LESS 2.0)
+ message(WARNING "Found parallelproj ${parallelproj_VERSION}. This will not be supported anymore in the near future.")
+ endif()
+ if (PARALLELPROJ_CUDA)
message(STATUS "Found parallelproj ${parallelproj_VERSION} (will use its CUDA support)")
+ set(parallelproj_built_with_CUDA ON)
else()
message(STATUS "Found parallelproj ${parallelproj_VERSION} (but using its OpenMP version as it wasn't built with CUDA)")
endif()
- if (parallelproj_VERSION VERSION_LESS 1.0.1)
- message(STATUS "If the above parallelproj info looks incorrect, upgrade it to at least 1.0.1 (but 1.2.13 or later is recommended)")
- endif()
endif()
endif()
if (STIR_WITH_Parallelproj_PROJECTOR)
diff --git a/documentation/release_6.4.htm b/documentation/release_6.4.htm
index 996ed7120e..dd36474707 100644
--- a/documentation/release_6.4.htm
+++ b/documentation/release_6.4.htm
@@ -12,15 +12,14 @@
Summary of changes in STIR release 6.4
Overall summary
- This version is 100% backwards compatible with STIR 6.3, aside from three important bug fixes,
- which will change results (see below).
+ This version is 100% backwards compatible with STIR 6.3.
This is a release with many new features, as listed below. Highlights are
- But there are many other changes from other contibutors as well, see below.
+ But there are many other changes from other contributors as well, see below.
Of course, there is also the usual code-cleanup and some improvements to the documentation.
Overall overview and release management was by Kris Thielemans (UCL) with help
from Daniel Deidda (NPL).
@@ -29,8 +28,8 @@ Overall summary
Patch release info
@@ -61,7 +60,25 @@ Changed functionality
PR #1667.
+
+ Several optimisations were contributed as part of the SyneRBI AI-RBI hackathon:
+
+ -
+ Compatibility with libparallelproj 2.0 and usage of CUDA managed pointers via
+ CuVec for internal variables in our parallelproj interface and
CudaGibbsPenalty.
+ This results in a ~20% speed-up, but also code simplification.
+
+ PR #1689.
+
+ -
+ Extra constructors for array, image and projdata classes that allow
std::move for input arrays.
+
+ PR #1693 and PR #1694.
+
+
+
+
Changes to examples
diff --git a/external_helpers/CMakeLists.txt b/external_helpers/CMakeLists.txt
index 49a4ea2437..f03c498d27 100644
--- a/external_helpers/CMakeLists.txt
+++ b/external_helpers/CMakeLists.txt
@@ -39,3 +39,11 @@ else()
set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIRS} PARENT_SCOPE)
endif()
+################ CuVec
+if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CuVec/Makefile")
+ message(FATAL_ERROR "The CuVec submodule was not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
+endif()
+set(CUVEC_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/CuVec/cuvec/include")
+set(CUVEC_INCLUDE_DIR ${CUVEC_INCLUDE_DIR} PARENT_SCOPE)
+
+install(FILES "${CUVEC_INCLUDE_DIR}/cuvec.cuh" DESTINATION "${STIR_INCLUDE_INSTALL_DIR}/")
diff --git a/external_helpers/CuVec b/external_helpers/CuVec
new file mode 160000
index 0000000000..f27ececc00
--- /dev/null
+++ b/external_helpers/CuVec
@@ -0,0 +1 @@
+Subproject commit f27ececc00088db944a30ea8080016b9bdaa8e39
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 14db4ab2a1..aecf58f1e5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -269,6 +269,14 @@ target_link_libraries(stir_registries)
# TODO, really should use stir_libs.cmake
target_include_directories(stir_registries PRIVATE ${STIR_INCLUDE_DIR})
target_include_directories(stir_registries PRIVATE ${Boost_INCLUDE_DIR})
+target_include_directories(stir_registries PUBLIC
+ $
+ $)
+if (NOT STIR_WITH_CUDA)
+ target_compile_definitions(stir_registries PUBLIC CUVEC_DISABLE_CUDA)
+else()
+ target_link_libraries(stir_registries PUBLIC CUDA::cudart)
+endif()
# go and look for CMakeLists.txt files in all those directories
foreach(STIR_DIR ${STIR_DIRS} ${STIR_TEST_DIRS})
diff --git a/src/include/stir/cuda_utilities.h b/src/include/stir/cuda_utilities.h
index cf17eb4321..be7f11e88e 100644
--- a/src/include/stir/cuda_utilities.h
+++ b/src/include/stir/cuda_utilities.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2024, University College London
+ Copyright (C) 2024, 2026, University College London
Copyright (C) 2025, University of Milano-Bicocca
This file is part of STIR.
@@ -22,6 +22,10 @@
#include "stir/Array.h"
#include "stir/info.h"
#include "stir/error.h"
+#ifdef __CUDACC__
+# include
+# include "cuvec.cuh"
+#endif
#include
START_NAMESPACE_STIR
@@ -45,12 +49,16 @@ struct cuda_int3
int x = 0, y = 0, z = 0;
};
#else
-# include
typedef dim3 cuda_dim3;
typedef int3 cuda_int3;
#endif
#ifdef __CUDACC__
+
+//! copy an `Array` to pre-allocated device memory
+/*!
+ \ingroup CUDA
+*/
template
inline void
array_to_device(elemT* dev_data, const Array& stir_array)
@@ -71,9 +79,25 @@ array_to_device(elemT* dev_data, const Array& stir_array)
}
}
+//! copy an `Array` to pre-allocated CuVec
+/*!
+ \ingroup CUDA
+*/
template
inline void
-array_to_host(Array& stir_array, const elemT* dev_data)
+array_to_device(CuVec& dev_data, const Array& stir_array)
+{
+ std::copy(stir_array.begin_all(), stir_array.end_all(), dev_data.begin());
+}
+
+//! copy CUDA pointer to `Array`
+/*!
+ \ingroup CUDA
+ The third argument is ignored, as `cudaMemcpy` always syncs device and host.
+*/
+template
+inline void
+array_to_host(Array& stir_array, const elemT* dev_data, bool /* sync */ = true)
{
if (stir_array.is_contiguous())
{
@@ -92,6 +116,20 @@ array_to_host(Array& stir_array, const elemT* dev_data)
}
}
+//! copy CuVec to `Array`
+/*!
+ \ingroup CUDA
+ If \a sync = \c true, the function will call `cudaDeviceSynchronize()` before copying.
+*/
+template
+inline void
+array_to_host(Array& stir_array, const CuVec& dev_data, bool sync = true)
+{
+ if (sync)
+ cudaDeviceSynchronize();
+ std::copy(dev_data.begin(), dev_data.end(), stir_array.begin_all());
+}
+
//! \brief Performs a parallel reduction sum on shared memory within a CUDA thread block, final value stored in shared_mem[0].
template
__device__ inline void
diff --git a/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.cuh b/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.cuh
index 4f94800651..cd760448aa 100644
--- a/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.cuh
+++ b/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.cuh
@@ -451,35 +451,14 @@ CudaGibbsPenalty_Hessian_Times_Input_kernel(elemT* output,
template
CudaGibbsPenalty::CudaGibbsPenalty()
- : base_type(), // Call parent constructor
- d_weights_data(nullptr),
- d_kappa_data(nullptr)
+ : base_type() // Call parent constructor
{}
template
CudaGibbsPenalty::CudaGibbsPenalty(const bool only_2D, float penalization_factor)
- : base_type(only_2D, penalization_factor), // Call parent constructor
- d_weights_data(nullptr),
- d_kappa_data(nullptr)
+ : base_type(only_2D, penalization_factor) // Call parent constructor
{}
-template
-CudaGibbsPenalty::~CudaGibbsPenalty()
-{
- if (d_weights_data)
- cudaFree(d_weights_data);
- if (d_kappa_data)
- cudaFree(d_kappa_data);
- if (d_image_data)
- cudaFree(d_image_data);
- if (d_scalar)
- cudaFree(d_scalar);
- if (d_output_data)
- cudaFree(d_output_data);
- if (d_input_data)
- cudaFree(d_input_data);
-}
-
template
double
CudaGibbsPenalty::compute_value(const DiscretisedDensity<3, elemT>& current_image_estimate)
@@ -491,17 +470,17 @@ CudaGibbsPenalty::compute_value(const DiscretisedDensity<3, e
if (this->_already_set_up == false)
error("CudaGibbsPenalty: set_up has not been called");
- cudaMemset(d_scalar, 0, sizeof(double));
+ d_scalar[0] = 0.0;
array_to_device(d_image_data, current_image_estimate);
const bool do_kappa = !is_null_ptr(this->get_kappa_sptr());
- if (do_kappa != (!is_null_ptr(d_kappa_data)))
+ if (do_kappa != (!d_kappa_data.empty()))
error("CudaGibbsPenalty internal error: inconsistent CPU and device kappa");
- CudaGibbsPenalty_value_kernel<<>>(d_scalar,
- d_image_data,
- d_weights_data,
- do_kappa ? d_kappa_data : nullptr,
+ CudaGibbsPenalty_value_kernel<<>>(d_scalar.data(),
+ d_image_data.data(),
+ d_weights_data.data(),
+ do_kappa ? d_kappa_data.data() : nullptr,
do_kappa,
d_image_dim,
d_image_max_indices,
@@ -511,9 +490,9 @@ CudaGibbsPenalty::compute_value(const DiscretisedDensity<3, e
this->potential);
checkCudaError("compute_value kernel");
-
+ cudaDeviceSynchronize();
double prior_value;
- cudaMemcpy(&prior_value, d_scalar, sizeof(double), cudaMemcpyDeviceToHost);
+ prior_value = d_scalar[0];
return this->penalisation_factor * prior_value;
}
@@ -538,13 +517,13 @@ CudaGibbsPenalty::compute_gradient(DiscretisedDensity<3, elem
array_to_device(d_image_data, current_image_estimate);
const bool do_kappa = !is_null_ptr(this->get_kappa_sptr());
- if (do_kappa != (!is_null_ptr(d_kappa_data)))
+ if (do_kappa != (!d_kappa_data.empty()))
error("CudaGibbsPenalty internal error: inconsistent CPU and device kappa");
- CudaGibbsPenalty_gradient_kernel<<>>(d_output_data,
- d_image_data,
- d_weights_data,
- do_kappa ? d_kappa_data : nullptr,
+ CudaGibbsPenalty_gradient_kernel<<>>(d_output_data.data(),
+ d_image_data.data(),
+ d_weights_data.data(),
+ do_kappa ? d_kappa_data.data() : nullptr,
do_kappa,
this->penalisation_factor,
d_image_dim,
@@ -555,7 +534,6 @@ CudaGibbsPenalty::compute_gradient(DiscretisedDensity<3, elem
this->potential);
checkCudaError("compute_gradient kernel");
- cudaDeviceSynchronize();
array_to_host(prior_gradient, d_output_data);
// Optional: write gradient to file
@@ -576,21 +554,21 @@ CudaGibbsPenalty::compute_gradient_times_input(const Discreti
if (this->penalisation_factor == 0)
return 0.;
- cudaMemset(d_scalar, 0, sizeof(double));
+ d_scalar[0] = 0.0;
array_to_device(d_image_data, current_image_estimate);
array_to_device(d_input_data, input);
const bool do_kappa = !is_null_ptr(this->get_kappa_sptr());
- if (do_kappa != (!is_null_ptr(this->d_kappa_data)))
+ if (do_kappa != (!d_kappa_data.empty()))
error("CudaGibbsPenalty internal error: inconsistent CPU and device kappa");
CudaGibbsPenalty_gradient_dot_input_kernel
- <<>>(d_scalar,
- d_input_data,
- d_image_data,
- d_weights_data,
- do_kappa ? d_kappa_data : nullptr,
+ <<>>(d_scalar.data(),
+ d_input_data.data(),
+ d_image_data.data(),
+ d_weights_data.data(),
+ do_kappa ? d_kappa_data.data() : nullptr,
do_kappa,
d_image_dim,
d_image_max_indices,
@@ -602,7 +580,7 @@ CudaGibbsPenalty::compute_gradient_times_input(const Discreti
checkCudaError("compute_gradient_times_input kernel");
cudaDeviceSynchronize();
double result;
- cudaMemcpy(&result, d_scalar, sizeof(double), cudaMemcpyDeviceToHost);
+ result = d_scalar[0];
return this->penalisation_factor * result;
}
@@ -626,13 +604,13 @@ CudaGibbsPenalty::compute_Hessian_diagonal(DiscretisedDensity
array_to_device(d_image_data, current_image_estimate);
const bool do_kappa = !is_null_ptr(this->get_kappa_sptr());
- if (do_kappa != (!is_null_ptr(this->d_kappa_data)))
+ if (do_kappa != (!d_kappa_data.empty()))
error("CudaGibbsPenalty internal error: inconsistent CPU and device kappa");
- CudaGibbsPenalty_Hessian_diagonal_kernel<<>>(d_output_data,
- d_image_data,
- d_weights_data,
- do_kappa ? d_kappa_data : nullptr,
+ CudaGibbsPenalty_Hessian_diagonal_kernel<<>>(d_output_data.data(),
+ d_image_data.data(),
+ d_weights_data.data(),
+ do_kappa ? d_kappa_data.data() : nullptr,
do_kappa,
this->penalisation_factor,
d_image_dim,
@@ -643,7 +621,6 @@ CudaGibbsPenalty::compute_Hessian_diagonal(DiscretisedDensity
this->potential);
checkCudaError("compute_hessian_diagonal kernel");
-
array_to_host(Hessian_diag, d_output_data);
}
@@ -669,14 +646,14 @@ CudaGibbsPenalty::accumulate_Hessian_times_input(DiscretisedD
array_to_device(d_output_data, output);
const bool do_kappa = !is_null_ptr(this->get_kappa_sptr());
- if (do_kappa != (!is_null_ptr(d_kappa_data)))
+ if (do_kappa != (!d_kappa_data.empty()))
error("CudaGibbsPenalty internal error: inconsistent CPU and device kappa");
- CudaGibbsPenalty_Hessian_Times_Input_kernel<<>>(d_output_data,
- d_image_data,
- d_input_data,
- d_weights_data,
- do_kappa ? d_kappa_data : nullptr,
+ CudaGibbsPenalty_Hessian_Times_Input_kernel<<>>(d_output_data.data(),
+ d_image_data.data(),
+ d_input_data.data(),
+ d_weights_data.data(),
+ do_kappa ? d_kappa_data.data() : nullptr,
do_kappa,
this->penalisation_factor,
d_image_dim,
@@ -687,7 +664,6 @@ CudaGibbsPenalty::accumulate_Hessian_times_input(DiscretisedD
this->potential);
checkCudaError("accumulate_Hessian_times_input kernel");
-
array_to_host(output, d_output_data);
}
@@ -722,41 +698,23 @@ CudaGibbsPenalty::set_up(shared_ptrsize_all() * sizeof(elemT));
- checkCudaError("CudaGibbsPenalty: cudaMalloc for d_image_data");
+ d_image_data.resize(target_sptr->size_all());
// Pre-allocate GPU memory for input data
- if (d_input_data)
- cudaFree(d_input_data);
- cudaMalloc(&d_input_data, target_sptr->size_all() * sizeof(elemT));
- checkCudaError("CudaGibbsPenalty: cudaMalloc for d_input_data");
+ d_input_data.resize(target_sptr->size_all());
// Pre-allocate GPU memory for outputs
- if (d_scalar)
- cudaFree(d_scalar);
- cudaMalloc(&d_scalar, sizeof(double));
- checkCudaError("CudaGibbsPenalty: cudaMalloc for d_scalar");
+ d_scalar.resize(1);
- if (d_output_data)
- cudaFree(d_output_data);
- cudaMalloc(&d_output_data, target_sptr->size_all() * sizeof(elemT));
- checkCudaError("CudaGibbsPenalty: cudaMalloc for d_output_data");
+ d_output_data.resize(target_sptr->size_all());
// Copy CPU weights to GPU (weights should already be set up by parent)
if (this->weights.get_length() > 0)
{
- if (d_weights_data)
- cudaFree(d_weights_data);
- cudaMalloc(&d_weights_data, this->weights.size_all() * sizeof(float));
+ d_weights_data.resize(this->weights.size_all());
array_to_device(d_weights_data, this->weights);
- checkCudaError("CudaGibbsPenalty: cudaMalloc for d_weights_data");
}
-
- // Copy CPU kappa image to GPU
- if (d_kappa_data)
- cudaFree(d_kappa_data);
+
auto kappa_ptr = this->get_kappa_sptr();
const bool do_kappa = !is_null_ptr(kappa_ptr);
@@ -764,7 +722,7 @@ CudaGibbsPenalty::set_up(shared_ptrhas_same_characteristics(*target_sptr))
error("CudaGibbsPenalty: kappa image does not have the same index range as the reconstructed image");
- cudaMalloc(&d_kappa_data, kappa_ptr->size_all() * sizeof(elemT));
+ d_kappa_data.resize(kappa_ptr->size_all());
array_to_device(d_kappa_data, *kappa_ptr);
}
@@ -777,9 +735,7 @@ void
CudaGibbsPenalty::set_weights(const Array<3, float>& w)
{
base_type::set_weights(w);
- if (d_weights_data)
- cudaFree(d_weights_data);
- cudaMalloc(&d_weights_data, w.size_all() * sizeof(float));
+ d_weights_data.resize(w.size_all());
array_to_device(d_weights_data, w);
}
@@ -788,15 +744,16 @@ void
CudaGibbsPenalty::set_kappa_sptr(const shared_ptr>& k)
{
base_type::set_kappa_sptr(k);
- if (d_kappa_data)
- cudaFree(d_kappa_data);
if (!is_null_ptr(k))
{
- cudaMalloc(&d_kappa_data, k->size_all() * sizeof(elemT));
+ d_kappa_data.resize(k->size_all());
array_to_device(d_kappa_data, *k);
}
else
- d_kappa_data = nullptr;
+ {
+ d_kappa_data.resize(0);
+ d_kappa_data.shrink_to_fit();
+ }
}
END_NAMESPACE_STIR
diff --git a/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.h b/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.h
index 7154082bfa..265b702550 100644
--- a/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.h
+++ b/src/include/stir/recon_buildblock/CUDA/CudaGibbsPenalty.h
@@ -24,6 +24,7 @@
#include "stir/cuda_utilities.h"
#include "stir/recon_buildblock/GibbsPenalty.h"
+#include "cuvec.cuh"
#include "stir/shared_ptr.h"
#include
@@ -62,7 +63,7 @@ class CudaGibbsPenalty : public GibbsPenalty
int threads_per_block;
size_t shared_mem_bytes;
- elemT* d_image_data = nullptr;
+ mutable CuVec d_image_data;
// Currently stir:CartesianCoordinate3D is not supported on GPU, we need a simple structure to store boundaries.
cuda_int3 d_image_dim;
cuda_int3 d_image_max_indices;
@@ -71,22 +72,21 @@ class CudaGibbsPenalty : public GibbsPenalty
cuda_int3 d_weight_min_indices;
// GPU pointers to weights and kappa data
- float* d_weights_data = nullptr;
- elemT* d_kappa_data = nullptr;
+ CuVec d_weights_data;
+ CuVec d_kappa_data;
// Buffers for GPU input/output to avoid reallocating memory on each call see usage in set_up() and ~CudaGibbsPenalty()
- mutable double* d_scalar = nullptr;
+ mutable CuVec d_scalar;
// d_scalar is used for compute_value and compute_gradient_times_input as output variable
- mutable elemT* d_input_data = nullptr;
+ mutable CuVec d_input_data;
// d_input_data is used for storing input image for compute_gradient_times_input and accumulate_Hessian_times_input
- mutable elemT* d_output_data = nullptr;
+ mutable CuVec d_output_data;
// d_output_data is used for storing output image for compute_gradient, accumulate_Hessian_times_input and
// compute_Hessian_diagonal
public:
CudaGibbsPenalty();
CudaGibbsPenalty(const bool only_2D, float penalization_factor);
- ~CudaGibbsPenalty();
//! Override CPU version to set up CUDA resources on GPU and call parent set_up
Succeeded set_up(shared_ptr> const& target_sptr) override;
diff --git a/src/include/stir/recon_buildblock/Parallelproj_projector/ParallelprojHelper.h b/src/include/stir/recon_buildblock/Parallelproj_projector/ParallelprojHelper.h
index 47afa9a919..5b369e2603 100644
--- a/src/include/stir/recon_buildblock/Parallelproj_projector/ParallelprojHelper.h
+++ b/src/include/stir/recon_buildblock/Parallelproj_projector/ParallelprojHelper.h
@@ -21,6 +21,7 @@
#define __stir_recon_buildblock_ParallelprojHelper_h__
#include "stir/common.h"
+#include "cuvec.cuh"
#include
#include
@@ -47,8 +48,8 @@ class ParallelprojHelper
std::array voxsize;
std::array imgdim;
std::array origin;
- std::vector xstart;
- std::vector xend;
+ CuVec xstart;
+ CuVec xend;
long long num_image_voxel;
long long num_lors;
diff --git a/src/include/stir/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.h b/src/include/stir/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.h
index bd83489c0f..47c928bca7 100644
--- a/src/include/stir/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.h
+++ b/src/include/stir/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.h
@@ -53,6 +53,9 @@ class ProjectorByBinPairUsingParallelproj
/// Set verbosity
void set_verbosity(const bool verbosity);
+ /// Set number of GPU chunks used by the forward and back projectors.
+ void set_num_gpu_chunks(const int num_gpu_chunks);
+
private:
shared_ptr _helper;
@@ -60,6 +63,7 @@ class ProjectorByBinPairUsingParallelproj
void initialise_keymap() override;
bool post_processing() override;
bool _verbosity;
+ int _num_gpu_chunks;
};
END_NAMESPACE_STIR
diff --git a/src/recon_buildblock/CMakeLists.txt b/src/recon_buildblock/CMakeLists.txt
index b0d6466df1..3ddde2de2b 100644
--- a/src/recon_buildblock/CMakeLists.txt
+++ b/src/recon_buildblock/CMakeLists.txt
@@ -167,9 +167,17 @@ endif()
endif()
if (STIR_WITH_Parallelproj_PROJECTOR)
- target_link_libraries(${TARGET} PRIVATE parallelproj::parallelproj_c)
- if (parallelproj_built_with_CUDA)
- target_link_libraries(${TARGET} PRIVATE parallelproj::parallelproj_cuda)
+ if (PARALLELPROJ_VERSION VERSION_LESS 2.0)
+ # variable to enable work-arounds in .cxx
+ target_compile_definitions(${TARGET} PRIVATE parallelproj1)
+ target_link_libraries(${TARGET} PRIVATE parallelproj::parallelproj_c)
+ if (parallelproj_built_with_CUDA)
+ target_link_libraries(${TARGET} PRIVATE parallelproj::parallelproj_cuda)
+ # variable to enable work-arounds in .cxx
+ target_compile_definitions(${TARGET} PRIVATE parallelproj1_CUDA)
+ endif()
+ else()
+ target_link_libraries(${TARGET} PRIVATE parallelproj::parallelproj)
endif()
endif()
@@ -183,5 +191,14 @@ if (HAVE_HDF5)
endif()
if (STIR_WITH_CUDA)
- target_link_libraries(${TARGET} PRIVATE CUDA::cudart)
+ target_link_libraries(${TARGET} PUBLIC CUDA::cudart)
+endif()
+
+target_include_directories(${TARGET} PUBLIC #${CUVEC_INCLUDE_DIR})
+ $
+ $)
+
+
+if (NOT STIR_WITH_CUDA)
+ target_compile_definitions(${TARGET} PUBLIC CUVEC_DISABLE_CUDA)
endif()
diff --git a/src/recon_buildblock/Parallelproj_projector/BackProjectorByBinParallelproj.cxx b/src/recon_buildblock/Parallelproj_projector/BackProjectorByBinParallelproj.cxx
index fd8ee5d9c6..0107bf251f 100644
--- a/src/recon_buildblock/Parallelproj_projector/BackProjectorByBinParallelproj.cxx
+++ b/src/recon_buildblock/Parallelproj_projector/BackProjectorByBinParallelproj.cxx
@@ -9,8 +9,10 @@
\author Richard Brown
\author Kris Thielemans
\author Nicole Jurjew
+ \author Markus Jehl
- Copyright (C) 2019, 2021, 2024 University College London
+ Copyright (C) 2019, 2021, 2024, 2026 University College London
+ Copyright (C) 2026 Positrigo
This file is part of STIR.
SPDX-License-Identifier: Apache-2.0
@@ -30,13 +32,23 @@
#include "stir/ProjDataInMemory.h"
#include "stir/LORCoordinates.h"
#include "stir/recon_array_functions.h"
-#ifdef parallelproj_built_with_CUDA
-# include "parallelproj_cuda.h"
+
+#ifdef parallelproj1
+# ifdef parallelproj_built_with_CUDA
+# include "parallelproj_cuda.h"
+# define parallelproj1_CUDA
+# define joseph3d_back joseph3d_back_cuda
+# define joseph3d_tof_sino_back joseph3d_back_tof_sino_cuda
+# else
+# include "parallelproj_c.h"
+# define joseph3d_tof_sino_back joseph3d_back_tof_sino
+# endif
#else
-# include "parallelproj_c.h"
+# include "parallelproj.h"
#endif
-// for debugging, remove later
+
#include "stir/info.h"
+#include "stir/format.h"
#include "stir/error.h"
#include "stir/stream.h"
#include
@@ -120,11 +132,12 @@ static void
TOF_transpose(std::vector& mem_for_PP_back,
const float* STIR_mem,
const shared_ptr _helper,
- const long long offset)
+ const long long offset,
+ const long long num_lors_per_chunk)
{
const auto num_tof_bins = static_cast(_helper->num_tof_bins);
for (unsigned tof_idx = 0; tof_idx < num_tof_bins; ++tof_idx)
- for (long long lor_idx = 0; lor_idx < _helper->num_lors; ++lor_idx)
+ for (long long lor_idx = 0; lor_idx < num_lors_per_chunk; ++lor_idx)
{
mem_for_PP_back[lor_idx * num_tof_bins + tof_idx] = STIR_mem[offset + tof_idx * _helper->num_lors + lor_idx];
}
@@ -158,9 +171,7 @@ BackProjectorByBinParallelproj::get_output(DiscretisedDensity<3, float>& density
// create an alias for the projection data
const ProjDataInMemory& p(*_proj_data_to_backproject_sptr);
- info("Calling parallelproj backprojector", 2);
-
-#ifdef parallelproj_built_with_CUDA
+ info(format("Calling parallelproj backprojector with {} chunks", _num_gpu_chunks), 2);
long long num_lors_per_chunk_floor = _helper->num_lors / _num_gpu_chunks;
long long remainder = _helper->num_lors % _num_gpu_chunks;
@@ -168,10 +179,12 @@ BackProjectorByBinParallelproj::get_output(DiscretisedDensity<3, float>& density
long long num_lors_per_chunk;
long long offset = 0;
+#ifdef parallelproj1_CUDA
// send image to all visible CUDA devices
float** image_on_cuda_devices = copy_float_array_to_all_devices(image_ptr, _helper->num_image_voxel);
+#endif
- // do (chuck-wise) back projection on the CUDA devices
+ // do (chunck-wise) back projection
for (int chunk_num = 0; chunk_num < _num_gpu_chunks; chunk_num++)
{
if (chunk_num < remainder)
@@ -184,52 +197,63 @@ BackProjectorByBinParallelproj::get_output(DiscretisedDensity<3, float>& density
}
if (p.get_proj_data_info_sptr()->is_tof_data())
{
- info("running the CUDA version of parallelproj, about to call function joseph3d_back_tof_sino_cuda for one chunk", 2);
-
std::vector mem_for_PP_back(num_lors_per_chunk * _helper->num_tof_bins);
const float* STIR_mem = p.get_const_data_ptr();
- TOF_transpose(mem_for_PP_back, STIR_mem, _helper, offset);
-
- // info("created object mem_for_PP_img", 2);
- joseph3d_back_tof_sino_cuda(_helper->xend.data() + 3 * offset,
- _helper->xstart.data() + 3 * offset,
- image_on_cuda_devices,
- _helper->origin.data(),
- _helper->voxsize.data(),
- mem_for_PP_back.data(), // p.get_const_data_ptr() + offset* num_tof_bins,
- num_lors_per_chunk,
- _helper->imgdim.data(),
- _helper->tofbin_width,
- &_helper->sigma_tof,
- &_helper->tofcenter_offset,
- 4, // float n_sigmas
- _helper->num_tof_bins,
- 0, // unsigned char lor_dependent_sigma_tof
- 0, // unsigned char lor_dependent_tofcenter_offset
- 64 // threadsperblock
+ TOF_transpose(mem_for_PP_back, STIR_mem, _helper, offset, num_lors_per_chunk);
+
+ joseph3d_tof_sino_back(_helper->xend.data() + 3 * offset,
+ _helper->xstart.data() + 3 * offset,
+#ifdef parallelproj1_CUDA
+ image_on_cuda_devices,
+#else
+ image_ptr,
+#endif
+ _helper->origin.data(),
+ _helper->voxsize.data(),
+ mem_for_PP_back.data(),
+ num_lors_per_chunk,
+ _helper->imgdim.data(),
+ _helper->tofbin_width,
+ &_helper->sigma_tof,
+ &_helper->tofcenter_offset,
+ 4, // float n_sigmas
+ _helper->num_tof_bins,
+ 0, // unsigned char lor_dependent_sigma_tof
+ 0 // unsigned char lor_dependent_tofcenter_offset
+#ifdef parallelproj1_CUDA
+ ,
+ /* num_threads_per_block */ 64
+#endif
);
if (chunk_num != _num_gpu_chunks - 1)
p.release_const_data_ptr();
}
else
{
- info("running the CUDA version of parallelproj, about to call function joseph3d_back_cuda for one chunk", 2);
-
- joseph3d_back_cuda(_helper->xstart.data() + 3 * offset,
- _helper->xend.data() + 3 * offset,
- image_on_cuda_devices,
- _helper->origin.data(),
- _helper->voxsize.data(),
- p.get_const_data_ptr() + offset,
- num_lors_per_chunk,
- _helper->imgdim.data(),
- /*threadsperblock*/ 64);
+ joseph3d_back(_helper->xstart.data() + 3 * offset,
+ _helper->xend.data() + 3 * offset,
+#ifdef parallelproj1_CUDA
+ image_on_cuda_devices,
+#else
+ image_ptr,
+#endif
+ _helper->origin.data(),
+ _helper->voxsize.data(),
+ p.get_const_data_ptr() + offset,
+ num_lors_per_chunk,
+ _helper->imgdim.data()
+#ifdef parallelproj1_CUDA
+ ,
+ /* num_threads_per_block */ 64
+#endif
+ );
}
info("done", 2);
offset += num_lors_per_chunk;
}
+#ifdef parallelproj1_CUDA
// sum backprojected images on the first CUDA device
sum_float_arrays_on_first_device(image_on_cuda_devices, _helper->num_image_voxel);
@@ -238,46 +262,7 @@ BackProjectorByBinParallelproj::get_output(DiscretisedDensity<3, float>& density
// free image array from CUDA devices
free_float_array_on_all_devices(image_on_cuda_devices);
-
-#else
- info("Calling parallelproj backprojector (CPU)", 2);
- if (this->_proj_data_info_sptr->is_tof_data() == 1)
- {
- std::vector mem_for_PP_back(_helper->num_lors * _helper->num_tof_bins);
- const float* STIR_mem = p.get_const_data_ptr();
-
- TOF_transpose(mem_for_PP_back, STIR_mem, _helper, 0);
-
- joseph3d_back_tof_sino(_helper->xend.data(),
- _helper->xstart.data(),
- image_ptr,
- _helper->origin.data(),
- _helper->voxsize.data(),
- mem_for_PP_back.data(),
- _helper->num_lors,
- _helper->imgdim.data(),
- _helper->tofbin_width,
- &_helper->sigma_tof,
- &_helper->tofcenter_offset,
- 4, // float n_sigmas,
- _helper->num_tof_bins,
- 0, // unsigned char lor_dependent_sigma_tof
- 0 // unsigned char lor_dependent_tofcenter_offset
- );
- }
- else
- {
- joseph3d_back(_helper->xstart.data(),
- _helper->xend.data(),
- image_ptr,
- _helper->origin.data(),
- _helper->voxsize.data(),
- p.get_const_data_ptr(),
- static_cast(p.get_proj_data_info_sptr()->size_all()),
- _helper->imgdim.data());
- }
#endif
- info("PP backprojecting done. Finishing up", 2);
p.release_const_data_ptr();
diff --git a/src/recon_buildblock/Parallelproj_projector/ForwardProjectorByBinParallelproj.cxx b/src/recon_buildblock/Parallelproj_projector/ForwardProjectorByBinParallelproj.cxx
index e47862b277..d5a458e403 100644
--- a/src/recon_buildblock/Parallelproj_projector/ForwardProjectorByBinParallelproj.cxx
+++ b/src/recon_buildblock/Parallelproj_projector/ForwardProjectorByBinParallelproj.cxx
@@ -10,7 +10,10 @@
\author Richard Brown
\author Kris Thielemans
\author Nicole Jurjew
- Copyright (C) 2019, 2021, 2024 University College London
+ \author Markus Jehl
+
+ Copyright (C) 2019, 2021, 2024, 2026 University College London
+ Copyright (C) 2026 Positrigo
This file is part of STIR.
SPDX-License-Identifier: Apache-2.0
@@ -25,15 +28,24 @@
#include "stir/ProjDataInfoCylindricalNoArcCorr.h"
#include "stir/recon_buildblock/TrivialDataSymmetriesForBins.h"
#include "stir/info.h"
+#include "stir/format.h"
#include "stir/error.h"
#include "stir/recon_array_functions.h"
#include "stir/utilities.h"
#include "stir/TOF_conversions.h"
#include
-#ifdef parallelproj_built_with_CUDA
-# include "parallelproj_cuda.h"
+
+#ifdef parallelproj1
+# ifdef parallelproj_built_with_CUDA
+# include "parallelproj_cuda.h"
+# define joseph3d_fwd joseph3d_fwd_cuda
+# define joseph3d_tof_sino_fwd joseph3d_fwd_tof_sino_cuda
+# else
+# include "parallelproj_c.h"
+# define joseph3d_tof_sino_fwd joseph3d_fwd_tof_sino
+# endif
#else
-# include "parallelproj_c.h"
+# include "parallelproj.h"
#endif
START_NAMESPACE_STIR
@@ -85,14 +97,6 @@ ForwardProjectorByBinParallelproj::set_up(const shared_ptr&
check(*proj_data_info_sptr, *_density_sptr);
_symmetries_sptr.reset(new TrivialDataSymmetriesForBins(proj_data_info_sptr));
-#if 0
- shared_ptr
- proj_data_info_cy_no_ar_cor_sptr(
- dynamic_pointer_cast(
- proj_data_info_sptr));
- if (is_null_ptr(proj_data_info_cy_no_ar_cor_sptr))
- error("ForwardProjectorByBinParallelproj: Failed casting to ProjDataInfoCylindricalNoArcCorr");
-#endif
// Initialise projected_data_sptr from this->_proj_data_info_sptr
_projected_data_sptr.reset(new ProjDataInMemory(this->_density_sptr->get_exam_info_sptr(), proj_data_info_sptr));
if (!this->_do_not_setup_helper)
@@ -167,14 +171,7 @@ ForwardProjectorByBinParallelproj::set_input(const DiscretisedDensity<3, float>&
image_ptr = image_vec.data();
}
-#if 0
- // needed to set output to zero as parallelproj accumulates but is no longer the case
- _projected_data_sptr->fill(0.F);
-#endif
-
-#ifdef parallelproj_built_with_CUDA
-
- info("Calling parallelproj forward (CUDA)", 2);
+ info(format("Calling parallelproj forward with {} chunks", _num_gpu_chunks), 2);
long long num_lors_per_chunk_floor
= _helper->num_lors
@@ -184,10 +181,12 @@ ForwardProjectorByBinParallelproj::set_input(const DiscretisedDensity<3, float>&
long long num_lors_per_chunk;
long long offset = 0;
+#ifdef parallelproj1_CUDA
// send image to all visible CUDA devices
float** image_on_cuda_devices = copy_float_array_to_all_devices(image_ptr, _helper->num_image_voxel);
+#endif
- // do (chuck-wise) projection on the CUDA devices
+ // do (chunk-wise) projection
for (int chunk_num = 0; chunk_num < _num_gpu_chunks; chunk_num++)
{
if (chunk_num < remainder)
@@ -203,22 +202,29 @@ ForwardProjectorByBinParallelproj::set_input(const DiscretisedDensity<3, float>&
{
std::vector mem_for_PP(num_lors_per_chunk * _helper->num_tof_bins);
- joseph3d_fwd_tof_sino_cuda(_helper->xend.data() + 3 * offset,
- _helper->xstart.data() + 3 * offset,
- image_on_cuda_devices,
- _helper->origin.data(),
- _helper->voxsize.data(),
- mem_for_PP.data(), // this is where the data is written to
- num_lors_per_chunk, // PP docu: "number of geometrical LORs";
- _helper->imgdim.data(),
- _helper->tofbin_width,
- &_helper->sigma_tof,
- &_helper->tofcenter_offset,
- 4, // float n_sigmas
- _helper->num_tof_bins, // short n_tofbins
- 0, // unsigned char lor_dependent_sigma_tof
- 0, // unsigned char lor_dependent_tofcenter_offset
- 64 // threadsperblock
+ joseph3d_tof_sino_fwd(_helper->xend.data() + 3 * offset,
+ _helper->xstart.data() + 3 * offset,
+#ifdef parallelproj1_CUDA
+ image_on_cuda_devices,
+#else
+ image_ptr,
+#endif
+ _helper->origin.data(),
+ _helper->voxsize.data(),
+ mem_for_PP.data(), // this is where the data is written to
+ num_lors_per_chunk, // PP docu: "number of geometrical LORs";
+ _helper->imgdim.data(),
+ _helper->tofbin_width,
+ &_helper->sigma_tof,
+ &_helper->tofcenter_offset,
+ 4, // float n_sigmas
+ _helper->num_tof_bins, // short n_tofbins
+ 0, // unsigned char lor_dependent_sigma_tof
+ 0 // unsigned char lor_dependent_tofcenter_offset
+#ifdef parallelproj1_CUDA
+ ,
+ 64 // threadsperblock
+#endif
);
float* STIR_mem = _projected_data_sptr->get_data_ptr();
@@ -227,69 +233,36 @@ ForwardProjectorByBinParallelproj::set_input(const DiscretisedDensity<3, float>&
if (chunk_num != _num_gpu_chunks - 1)
_projected_data_sptr->release_data_ptr();
- info("current proj max: "
- + std::to_string(*std::max_element(_projected_data_sptr->begin(), _projected_data_sptr->end())));
}
else
{
- joseph3d_fwd_cuda(_helper->xstart.data() + 3 * offset,
- _helper->xend.data() + 3 * offset,
- image_on_cuda_devices,
- _helper->origin.data(),
- _helper->voxsize.data(),
- _projected_data_sptr->get_data_ptr() + offset,
- num_lors_per_chunk,
- _helper->imgdim.data(),
- /*threadsperblock*/ 64);
+ joseph3d_fwd(_helper->xstart.data() + 3 * offset,
+ _helper->xend.data() + 3 * offset,
+#ifdef parallelproj1_CUDA
+ image_on_cuda_devices,
+#else
+ image_ptr,
+#endif
+ _helper->origin.data(),
+ _helper->voxsize.data(),
+ _projected_data_sptr->get_data_ptr() + offset,
+ num_lors_per_chunk,
+ _helper->imgdim.data()
+#ifdef parallelproj1_CUDA
+ ,
+ 64 // threadsperblock
+#endif
+ );
if (chunk_num != _num_gpu_chunks - 1)
_projected_data_sptr->release_data_ptr();
}
offset += num_lors_per_chunk;
}
+#ifdef parallelproj1_CUDA
// free image array from CUDA devices
free_float_array_on_all_devices(image_on_cuda_devices);
-
-#else
- info("Calling parallelproj forward (CPU)", 2);
-
- if (this->_proj_data_info_sptr->is_tof_data() == 1)
- {
-
- std::vector mem_for_PP(_helper->num_lors * _helper->num_tof_bins);
- joseph3d_fwd_tof_sino(_helper->xend.data(),
- _helper->xstart.data(),
- image_ptr,
- _helper->origin.data(),
- _helper->voxsize.data(),
- mem_for_PP.data(),
- _helper->num_lors,
- _helper->imgdim.data(),
- _helper->tofbin_width,
- &_helper->sigma_tof,
- &_helper->tofcenter_offset,
- 4, // float n_sigmas,
- _helper->num_tof_bins,
- 0, // unsigned char lor_dependent_sigma_tof
- 0 // unsigned char lor_dependent_tofcenter_offset
- );
-
- float* STIR_mem = _projected_data_sptr->get_data_ptr();
- TOF_transpose(STIR_mem, mem_for_PP, _helper, 0, _helper->num_lors);
- }
- else
- {
- joseph3d_fwd(_helper->xstart.data(),
- _helper->xend.data(),
- image_ptr,
- _helper->origin.data(),
- _helper->voxsize.data(),
- _projected_data_sptr->get_data_ptr(),
- static_cast(_projected_data_sptr->get_proj_data_info_sptr()->size_all()),
- _helper->imgdim.data());
- }
#endif
- info("done", 2);
if (_density_sptr->is_contiguous())
{
diff --git a/src/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.cxx b/src/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.cxx
index ea35717f35..c656ef3cf3 100644
--- a/src/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.cxx
+++ b/src/recon_buildblock/Parallelproj_projector/ProjectorByBinPairUsingParallelproj.cxx
@@ -34,6 +34,7 @@ ProjectorByBinPairUsingParallelproj::initialise_keymap()
parser.add_start_key("Projector Pair Using Parallelproj Parameters");
parser.add_stop_key("End Projector Pair Using Parallelproj Parameters");
parser.add_key("verbosity", &_verbosity);
+ parser.add_key("num_gpu_chunks", &_num_gpu_chunks);
}
void
@@ -41,12 +42,14 @@ ProjectorByBinPairUsingParallelproj::set_defaults()
{
base_type::set_defaults();
this->set_verbosity(true);
+ this->set_num_gpu_chunks(1);
}
bool
ProjectorByBinPairUsingParallelproj::post_processing()
{
this->set_verbosity(this->_verbosity);
+ this->set_num_gpu_chunks(this->_num_gpu_chunks);
if (base_type::post_processing())
return true;
@@ -98,4 +101,20 @@ ProjectorByBinPairUsingParallelproj::set_verbosity(const bool verbosity)
bck_prj_downcast_sptr->set_verbosity(_verbosity);
}
+void
+ProjectorByBinPairUsingParallelproj::set_num_gpu_chunks(const int num_gpu_chunks)
+{
+ _num_gpu_chunks = num_gpu_chunks;
+
+ shared_ptr fwd_prj_downcast_sptr
+ = dynamic_pointer_cast(this->forward_projector_sptr);
+ if (fwd_prj_downcast_sptr)
+ fwd_prj_downcast_sptr->set_num_gpu_chunks(_num_gpu_chunks);
+
+ shared_ptr bck_prj_downcast_sptr
+ = dynamic_pointer_cast(this->back_projector_sptr);
+ if (bck_prj_downcast_sptr)
+ bck_prj_downcast_sptr->set_num_gpu_chunks(_num_gpu_chunks);
+}
+
END_NAMESPACE_STIR