From f772bb1579c56aa1304c93e126ccdef95978819a Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 18 Mar 2025 10:23:47 -0700 Subject: [PATCH 01/49] Begin refactor --- src/chai/expt/ManagedArray.hpp | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/chai/expt/ManagedArray.hpp diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp new file mode 100644 index 00000000..666204be --- /dev/null +++ b/src/chai/expt/ManagedArray.hpp @@ -0,0 +1,61 @@ +#ifndef CHAI_MANAGED_ARRAY_HPP +#define CHAI_MANAGED_ARRAY_HPP + +namespace chai { + template + class ManagedArray { + public: + ManagedArray() = default; + + ManagedArray(ArrayManager* manager) : + m_manager{manager} + { + if (m_manager) { + m_manager->update(m_data, m_size); + } + } + + void resize(size_t count) { + if (count == 0) { + free(); + } + else { + if (!m_manager) { + makeDefaultArrayManager(count); + } + else { + m_manager->resize(count); + } + + m_manager->update(m_data, m_size); + } + } + + void free() { + m_data = nullptr; + m_size = 0; + delete m_manager; + m_manager = nullptr; + } + + CHAI_HOST_DEVICE T& operator[](size_t i) const { + return m_data[i]; + } + + CHAI_HOST_DEVICE size_t size() const { + return m_size; + } + + private: + T* m_data = nullptr; + size_t m_size = 0; + ArrayManager* m_manager = nullptr; + }; // class ManagedArray + + template + ManagedArray makeManagedArray(Args&&... args) { + return ManagedArray(new Manager(std::forward(args)...)); + } +} // namespace chai + +#endif // CHAI_MANAGED_ARRAY_HPP From c91d68ad000e1cfe0e357363f46bf1b54ee8039c Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 12:58:10 -0700 Subject: [PATCH 02/49] Set up skeleton of ManagedArray --- src/chai/expt/ManagedArray.hpp | 95 ++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 22 deletions(-) diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index 666204be..9a94cc13 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -5,53 +5,104 @@ namespace chai { template class ManagedArray { public: + /*! + * \brief Constructs an empty array without an array manager. + * + * \note The array takes ownership of the manager. + */ ManagedArray() = default; + /*! + * \brief Constructs an array from a manager. + * + * \param manager The array manager controls the coherence of the array. + * + * \note The array takes ownership of the manager. + */ ManagedArray(ArrayManager* manager) : m_manager{manager} { - if (m_manager) { - m_manager->update(m_data, m_size); - } } - void resize(size_t count) { - if (count == 0) { - free(); - } - else { - if (!m_manager) { - makeDefaultArrayManager(count); - } - else { - m_manager->resize(count); - } - - m_manager->update(m_data, m_size); + /*! + * \brief Constructs a shallow copy of an array from another and makes + * the data coherent in the current execution space. + * + * \param other The other array. + * + * \note This is a shallow copy. + */ + CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) : + m_size{other.m_size}, + m_data{other.m_data}, + m_manager{other.m_manager} + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_manager->update(m_size, m_data); } } + /*! + * \brief Frees the resources associated with this array. + * + * \note Once free has been called, it is invalid to use any other copies + * of this array (since copies are shallow). + */ void free() { - m_data = nullptr; m_size = 0; + m_data = nullptr; delete m_manager; m_manager = nullptr; } - CHAI_HOST_DEVICE T& operator[](size_t i) const { - return m_data[i]; - } - + /*! + * \brief Get the number of elements in the array. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ CHAI_HOST_DEVICE size_t size() const { return m_size; } + /*! + * \brief Get the ith element in the array. + * + * \param i The index of the element to retrieve. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE T& operator[](size_t i) const { + return m_data[i]; + } + private: - T* m_data = nullptr; + /*! + * The number of elements in the array. + */ size_t m_size = 0; + + /*! + * The array that is coherent in the current execution space. + */ + T* m_data = nullptr; + + /*! + * The array manager controls the coherence of the array. + */ ArrayManager* m_manager = nullptr; }; // class ManagedArray + /*! + * \brief Constructs an array by creating a new manager object. + * + * \tparam Manager The type of array manager. + * \tparam Args The type of the arguments used to construct the array manager. + * + * \param args The arguments to construct an array manager. + */ template ManagedArray makeManagedArray(Args&&... args) { return ManagedArray(new Manager(std::forward(args)...)); From 570d799547f744e40ae429b73c54d8c05bd4c5fd Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 13:13:40 -0700 Subject: [PATCH 03/49] Add an ArrayManager class --- src/chai/expt/ArrayManager.hpp | 31 +++++++++++++++++++++++++++++++ src/chai/expt/ManagedArray.hpp | 12 ++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/chai/expt/ArrayManager.hpp diff --git a/src/chai/expt/ArrayManager.hpp b/src/chai/expt/ArrayManager.hpp new file mode 100644 index 00000000..80d6075f --- /dev/null +++ b/src/chai/expt/ArrayManager.hpp @@ -0,0 +1,31 @@ +#ifndef CHAI_ARRAY_MANAGER_HPP +#define CHAI_ARRAY_MANAGER_HPP + +namespace chai { + /*! + * \class ArrayManager + * + * \brief Controls the coherence of an array. + * + * \tparam T The type of element in the array. + */ + template + class ArrayManager { + public: + /*! + * \brief Virtual destructor. + */ + virtual ~ArrayManager() = default; + + /*! + * \brief Updates the size and data to be coherent in the current + * execution space. + * + * \param size [out] The number of elements in the coherent array. + * \param data [out] A coherent array in the current execution space. + */ + virtual void update(size_t& size, T*& data) = 0; + }; // class ArrayManager +} // namespace chai + +#endif // CHAI_ARRAY_MANAGER_HPP diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index 9a94cc13..1205361f 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -1,14 +1,22 @@ #ifndef CHAI_MANAGED_ARRAY_HPP #define CHAI_MANAGED_ARRAY_HPP +#include "chai/ArrayManager.hpp" + namespace chai { + /*! + * \class ManagedArray + * + * \brief An array class that manages coherency across the CPU and GPU. + * How the coherence is obtained is controlled by the array manager. + * + * \tparam T The type of element in the array. + */ template class ManagedArray { public: /*! * \brief Constructs an empty array without an array manager. - * - * \note The array takes ownership of the manager. */ ManagedArray() = default; From c02e4c1dd94a8d76c5bb65a66afdd0e74f98ee44 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 14:56:14 -0700 Subject: [PATCH 04/49] Clean up --- src/chai/expt/{ManagedArray.hpp => Array.hpp} | 28 +++++----- src/chai/expt/ArrayManager.hpp | 31 ---------- src/chai/expt/ExecutionContext.hpp | 22 ++++++++ src/chai/expt/HostManager.hpp | 56 +++++++++++++++++++ src/chai/expt/Manager.cpp | 10 ++++ src/chai/expt/Manager.hpp | 35 ++++++++++++ 6 files changed, 138 insertions(+), 44 deletions(-) rename src/chai/expt/{ManagedArray.hpp => Array.hpp} (84%) delete mode 100644 src/chai/expt/ArrayManager.hpp create mode 100644 src/chai/expt/ExecutionContext.hpp create mode 100644 src/chai/expt/HostManager.hpp create mode 100644 src/chai/expt/Manager.cpp create mode 100644 src/chai/expt/Manager.hpp diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/Array.hpp similarity index 84% rename from src/chai/expt/ManagedArray.hpp rename to src/chai/expt/Array.hpp index 1205361f..a73a1efe 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/Array.hpp @@ -1,11 +1,12 @@ -#ifndef CHAI_MANAGED_ARRAY_HPP -#define CHAI_MANAGED_ARRAY_HPP +#ifndef CHAI_ARRAY_HPP +#define CHAI_ARRAY_HPP -#include "chai/ArrayManager.hpp" +#include "chai/Manager.hpp" namespace chai { +namespace expt { /*! - * \class ManagedArray + * \class Array * * \brief An array class that manages coherency across the CPU and GPU. * How the coherence is obtained is controlled by the array manager. @@ -13,12 +14,12 @@ namespace chai { * \tparam T The type of element in the array. */ template - class ManagedArray { + class Array { public: /*! * \brief Constructs an empty array without an array manager. */ - ManagedArray() = default; + Array() = default; /*! * \brief Constructs an array from a manager. @@ -27,7 +28,7 @@ namespace chai { * * \note The array takes ownership of the manager. */ - ManagedArray(ArrayManager* manager) : + Array(Manager* manager) : m_manager{manager} { } @@ -40,7 +41,7 @@ namespace chai { * * \note This is a shallow copy. */ - CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) : + CHAI_HOST_DEVICE Array(const Array& other) : m_size{other.m_size}, m_data{other.m_data}, m_manager{other.m_manager} @@ -100,8 +101,8 @@ namespace chai { /*! * The array manager controls the coherence of the array. */ - ArrayManager* m_manager = nullptr; - }; // class ManagedArray + Manager* m_manager = nullptr; + }; // class Array /*! * \brief Constructs an array by creating a new manager object. @@ -112,9 +113,10 @@ namespace chai { * \param args The arguments to construct an array manager. */ template - ManagedArray makeManagedArray(Args&&... args) { - return ManagedArray(new Manager(std::forward(args)...)); + Array makeArray(Args&&... args) { + return Array(new Manager(std::forward(args)...)); } +} // namespace expt } // namespace chai -#endif // CHAI_MANAGED_ARRAY_HPP +#endif // CHAI_ARRAY_HPP diff --git a/src/chai/expt/ArrayManager.hpp b/src/chai/expt/ArrayManager.hpp deleted file mode 100644 index 80d6075f..00000000 --- a/src/chai/expt/ArrayManager.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CHAI_ARRAY_MANAGER_HPP -#define CHAI_ARRAY_MANAGER_HPP - -namespace chai { - /*! - * \class ArrayManager - * - * \brief Controls the coherence of an array. - * - * \tparam T The type of element in the array. - */ - template - class ArrayManager { - public: - /*! - * \brief Virtual destructor. - */ - virtual ~ArrayManager() = default; - - /*! - * \brief Updates the size and data to be coherent in the current - * execution space. - * - * \param size [out] The number of elements in the coherent array. - * \param data [out] A coherent array in the current execution space. - */ - virtual void update(size_t& size, T*& data) = 0; - }; // class ArrayManager -} // namespace chai - -#endif // CHAI_ARRAY_MANAGER_HPP diff --git a/src/chai/expt/ExecutionContext.hpp b/src/chai/expt/ExecutionContext.hpp new file mode 100644 index 00000000..ca66233a --- /dev/null +++ b/src/chai/expt/ExecutionContext.hpp @@ -0,0 +1,22 @@ +#ifndef CHAI_EXECUTION_CONTEXT_HPP +#define CHAI_EXECUTION_CONTEXT_HPP + +namespace chai { +namespace expt { + /*! + * \brief Enum listing possible execution contexts. + */ + enum class ExecutionContext { + /*! Default, no execution space. */ + NONE = 0, + /*! Executing in CPU space */ + CPU, +#if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) + /*! Executing in GPU space */ + GPU +#endif + }; // enum class ExecutionContext +} // namespace expt +} // namespace chai + +#endif // CHAI_EXECUTION_CONTEXT_HPP diff --git a/src/chai/expt/HostManager.hpp b/src/chai/expt/HostManager.hpp new file mode 100644 index 00000000..4410c901 --- /dev/null +++ b/src/chai/expt/HostManager.hpp @@ -0,0 +1,56 @@ +#ifndef CHAI_HOST_MANAGER_HPP +#define CHAI_HOST_MANAGER_HPP + +namespace chai { +namespace expt { + /*! + * \class HostManager + * + * \brief Controls the coherence of an array on the CPU. + */ + template + class HostManager : public Manager { + public: + /*! + * \brief Constructs a host array manager. + */ + HostManager(size_t size, const Allocator& allocator) : + m_allocator_id{allocatorID}, + m_size{size} + { + m_data = m_allocator.allocate(size); + } + + HostManager(const HostManager&) = delete; + HostManager& operator=(const HostManager&) = delete; + + /*! + * \brief Virtual destructor. + */ + virtual ~HostManager() { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + * + * \param data [out] A coherent array in the current execution space. + */ + virtual void update(void*& data) { + if (execution_space() == ExecutionSpace::CPU) { + data = m_data; + } + else { + data = nullptr; + } + } + + private: + Allocator m_allocator{}; + size_t m_size{0}; + T* m_data{nullptr}; + }; // class HostManager +} // namespace expt +} // namespace chai + +#endif // CHAI_HOST_MANAGER_HPP diff --git a/src/chai/expt/Manager.cpp b/src/chai/expt/Manager.cpp new file mode 100644 index 00000000..7da30c80 --- /dev/null +++ b/src/chai/expt/Manager.cpp @@ -0,0 +1,10 @@ +#include "chai/expt/Manager.hpp" + +namespace chai { +namespace expt { + ExecutionContext& Manager::execution_space() { + static thread_local ExecutionContext s_space = ExecutionContext::NONE; + return s_space; + } +} // namespace expt +} // namespace chai diff --git a/src/chai/expt/Manager.hpp b/src/chai/expt/Manager.hpp new file mode 100644 index 00000000..320b621c --- /dev/null +++ b/src/chai/expt/Manager.hpp @@ -0,0 +1,35 @@ +#ifndef CHAI_MANAGER_HPP +#define CHAI_MANAGER_HPP + +#include "chai/expt/ExecutionContext.hpp" + +namespace chai { +namespace expt { + /*! + * \class Manager + * + * \brief Controls the coherence of an array. + */ + class Manager { + public: + /*! + * \brief Virtual destructor. + */ + virtual ~Manager() = default; + + /*! + * \brief Updates the data to be coherent in the current execution context. + * + * \param data [out] A coherent array in the current execution context. + */ + virtual void update(void*& data) = 0; + + /*! + * \brief Returns a modifiable reference to the current execution context. + */ + static ExecutionContext& execution_context(); + }; // class Manager +} // namespace expt +} // namespace chai + +#endif // CHAI_MANAGER_HPP From 8372534eaf05aa1a4ab11da5a1890dcbb202188d Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 15:44:38 -0700 Subject: [PATCH 05/49] Add CopyHidingManager --- src/chai/expt/CopyHidingManager.hpp | 109 ++++++++++++++++++++++++++++ src/chai/expt/HostManager.hpp | 2 +- 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/chai/expt/CopyHidingManager.hpp diff --git a/src/chai/expt/CopyHidingManager.hpp b/src/chai/expt/CopyHidingManager.hpp new file mode 100644 index 00000000..8849eb7c --- /dev/null +++ b/src/chai/expt/CopyHidingManager.hpp @@ -0,0 +1,109 @@ +#ifndef CHAI_COPY_HIDING_MANAGER_HPP +#define CHAI_COPY_HIDING_MANAGER_HPP + +namespace chai { +namespace expt { + /*! + * \class CopyHidingManager + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class CopyHidingManager : public Manager { + public: + /*! + * \brief Constructs a host array manager. + */ + CopyHidingManager(size_t size, + const HostAllocator& hostAllocator = HostAllocator(), + const DeviceAllocator& deviceAllocator = DeviceAllocator()) + m_size{size}, + m_host_allocator{hostAllocator}, + m_device_allocator{deviceAllocator} + { + } + + CopyHidingManager(const CopyHidingManager&) = delete; + CopyHidingManager& operator=(const CopyHidingManager&) = delete; + + /*! + * \brief Virtual destructor. + */ + virtual ~CopyHidingManager() { + m_device_allocator.deallocate(m_device_data); + m_host_allocator.deallocate(m_host_data); + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + * + * \param data [out] A coherent array in the current execution space. + */ + virtual void update(void*& data, bool touch) { + ExecutionContext context = execution_context(); + + if (context == ExecutionContext::Host) { + if (!m_host_data) { + m_host_data = m_host_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::Device) { +#if defined(CHAI_ENABLE_CUDA) + cudaMemcpy(m_host_data, m_device_data, m_size, cudaMemcpyDtoH); +#elif defined(CHAI_ENABLE_HIP) + hipMemcpy(m_host_data, m_device_data, m_size, hipMemcpyDtoH); +#else + memcpy(m_host_data, m_device_data, m_size); +#endif + + // Reset touch + m_touch = ExecutionContext::None; + } + + if (touch) { + m_touch = ExecutionContext::Host; + } + + data = m_host_data; + } + else if (context == ExecutionContext::Device) { + if (!m_device_data) { + m_device_data = m_device_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::Host) { +#if defined(CHAI_ENABLE_CUDA) + cudaMemcpy(m_device_data, m_host_data, m_size); +#elif defined(CHAI_ENABLE_HIP) + hipMemcpy(m_device_data, m_host_data, m_size); +#else + memcpy(m_device_data, m_host_data, m_size); +#endif + + // Reset touch + m_touch = ExecutionContext::None; + } + + if (touch) { + m_touch = ExecutionContext::Device; + } + + data = m_device_data; + } + else { + data = nullptr; + } + } + + private: + size_t m_size{0}; + T* m_host_data{nullptr}; + T* m_device_data{nullptr}; + HostAllocator m_host_allocator{}; + DeviceAllocator m_device_allocator{}; + ExecutionContext m_touch{ExecutionContext::None}; + }; // class CopyHidingManager +} // namespace expt +} // namespace chai + +#endif // CHAI_COPY_HIDING_MANAGER_HPP diff --git a/src/chai/expt/HostManager.hpp b/src/chai/expt/HostManager.hpp index 4410c901..edc1570f 100644 --- a/src/chai/expt/HostManager.hpp +++ b/src/chai/expt/HostManager.hpp @@ -15,7 +15,7 @@ namespace expt { * \brief Constructs a host array manager. */ HostManager(size_t size, const Allocator& allocator) : - m_allocator_id{allocatorID}, + m_allocator{allocator}, m_size{size} { m_data = m_allocator.allocate(size); From 7fc63b23bd7c9b2204f9a417fee3d3f6298fc56f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 16:02:50 -0700 Subject: [PATCH 06/49] Add PinnedManager --- src/chai/expt/PinnedManager.hpp | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/chai/expt/PinnedManager.hpp diff --git a/src/chai/expt/PinnedManager.hpp b/src/chai/expt/PinnedManager.hpp new file mode 100644 index 00000000..0bb1173c --- /dev/null +++ b/src/chai/expt/PinnedManager.hpp @@ -0,0 +1,64 @@ +#ifndef CHAI_PINNED_MANAGER_HPP +#define CHAI_PINNED_MANAGER_HPP + +namespace chai { +namespace expt { + /*! + * \class PinnedManager + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class PinnedManager : public Manager { + public: + /*! + * \brief Constructs a host array manager. + */ + PinnedManager(size_t size, + const Allocator& allocator = Allocator()) + m_size{size}, + m_data{allocator.allocate(size)}, + m_allocator{allocator} + { + } + + PinnedManager(const PinnedManager&) = delete; + PinnedManager& operator=(const PinnedManager&) = delete; + + /*! + * \brief Virtual destructor. + */ + virtual ~PinnedManager() { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + * + * \param data [out] A coherent array in the current execution space. + */ + virtual void update(void*& data, bool touch) { + ExecutionContext context = execution_context(); + + if (context == ExecutionContext::None) { + data = nullptr; + } + else { + if (context == ExecutionContext::Host) { + // TODO: Only sync if last touched on device + synchronizeDeviceIfNeeded(); + } + + data = m_data; + } + } + + private: + size_t m_size{0}; + T* m_data{nullptr}; + Allocator m_allocator{}; + }; // class PinnedManager +} // namespace expt +} // namespace chai + +#endif // CHAI_PINNED_MANAGER_HPP From 123a437c90dbd64e0c9f23c1c3699e1a1ad896a8 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 16:06:14 -0700 Subject: [PATCH 07/49] Update method to include touch argument --- src/chai/expt/Array.hpp | 2 +- src/chai/expt/HostManager.hpp | 2 +- src/chai/expt/Manager.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chai/expt/Array.hpp b/src/chai/expt/Array.hpp index a73a1efe..e61a57f8 100644 --- a/src/chai/expt/Array.hpp +++ b/src/chai/expt/Array.hpp @@ -48,7 +48,7 @@ namespace expt { { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_manager->update(m_size, m_data); + m_manager->update(m_data, !std::is_const::value); } } diff --git a/src/chai/expt/HostManager.hpp b/src/chai/expt/HostManager.hpp index edc1570f..9ea924e4 100644 --- a/src/chai/expt/HostManager.hpp +++ b/src/chai/expt/HostManager.hpp @@ -36,7 +36,7 @@ namespace expt { * * \param data [out] A coherent array in the current execution space. */ - virtual void update(void*& data) { + virtual void update(void*& data, bool touch) { if (execution_space() == ExecutionSpace::CPU) { data = m_data; } diff --git a/src/chai/expt/Manager.hpp b/src/chai/expt/Manager.hpp index 320b621c..d506e2e1 100644 --- a/src/chai/expt/Manager.hpp +++ b/src/chai/expt/Manager.hpp @@ -22,7 +22,7 @@ namespace expt { * * \param data [out] A coherent array in the current execution context. */ - virtual void update(void*& data) = 0; + virtual void update(void*& data, bool touch) = 0; /*! * \brief Returns a modifiable reference to the current execution context. From 6d7abd6d0d83c8dbe34d4663509bcf2f5154c8c5 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 16:17:57 -0700 Subject: [PATCH 08/49] Add PageableManager --- src/chai/expt/PageableManager.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/chai/expt/PageableManager.hpp diff --git a/src/chai/expt/PageableManager.hpp b/src/chai/expt/PageableManager.hpp new file mode 100644 index 00000000..1408b83c --- /dev/null +++ b/src/chai/expt/PageableManager.hpp @@ -0,0 +1,18 @@ +#ifndef CHAI_PAGEABLE_MANAGER_HPP +#define CHAI_PAGEABLE_MANAGER_HPP + +#include "chai/expt/PinnedManager.hpp" + +namespace chai { +namespace expt { + /*! + * \alias PageableManager + * + * \brief Controls the coherence of an array on the host and device. + */ + template + using PageableManager = PinnedManager; +} // namespace expt +} // namespace chai + +#endif // CHAI_PAGEABLE_MANAGER_HPP From 283fbf84565e95dbdc0c66a483b1f33f761cd3d6 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 21 Mar 2025 16:22:14 -0700 Subject: [PATCH 09/49] Add new files to build system --- src/chai/CMakeLists.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index faab5481..4a32e3db 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -20,7 +20,14 @@ set (chai_headers ManagedArray.inl managed_ptr.hpp PointerRecord.hpp - Types.hpp) + Types.hpp + expt/Array.hpp + expt/ExecutionContext.hpp + expt/Manager.hpp + expt/CopyHidingManager.hpp + expt/HostManager.hpp + expt/PageableManager.hpp + expt/PinnedManager.hpp) if(CHAI_DISABLE_RM) set(chai_headers @@ -29,7 +36,8 @@ if(CHAI_DISABLE_RM) endif () set (chai_sources - ArrayManager.cpp) + ArrayManager.cpp + expt/Manager.cpp) set (chai_depends umpire) From 7262fe04520db5aabc7eb497c4f2f5e5d84da888 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 31 Mar 2025 11:36:46 -0700 Subject: [PATCH 10/49] Get HostManager tests functioning --- src/chai/CMakeLists.txt | 3 +- src/chai/expt/CopyHidingManager.hpp | 7 +++ src/chai/expt/ExecutionContext.hpp | 4 +- src/chai/expt/HostManager.cpp | 35 +++++++++++++ src/chai/expt/HostManager.hpp | 46 ++++++++++-------- src/chai/expt/Manager.cpp | 6 +-- src/chai/expt/Manager.hpp | 7 +++ src/chai/expt/PinnedManager.hpp | 7 +++ tests/integration/CMakeLists.txt | 16 ++++++ tests/integration/expt/TestArray.cpp | 73 ++++++++++++++++++++++++++++ tests/unit/CMakeLists.txt | 13 +++++ tests/unit/expt/TestHostManager.cpp | 54 ++++++++++++++++++++ 12 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 src/chai/expt/HostManager.cpp create mode 100644 tests/integration/expt/TestArray.cpp create mode 100644 tests/unit/expt/TestHostManager.cpp diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index 4a32e3db..9a344cf6 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -37,7 +37,8 @@ endif () set (chai_sources ArrayManager.cpp - expt/Manager.cpp) + expt/Manager.cpp + expt/HostManager.cpp) set (chai_depends umpire) diff --git a/src/chai/expt/CopyHidingManager.hpp b/src/chai/expt/CopyHidingManager.hpp index 8849eb7c..19ad9ef1 100644 --- a/src/chai/expt/CopyHidingManager.hpp +++ b/src/chai/expt/CopyHidingManager.hpp @@ -34,6 +34,13 @@ namespace expt { m_host_allocator.deallocate(m_host_data); } + /*! + * \brief Get the number of elements. + */ + virtual size_t size() const { + return m_size; + } + /*! * \brief Updates the data to be coherent in the current execution space. * diff --git a/src/chai/expt/ExecutionContext.hpp b/src/chai/expt/ExecutionContext.hpp index ca66233a..3a1209d4 100644 --- a/src/chai/expt/ExecutionContext.hpp +++ b/src/chai/expt/ExecutionContext.hpp @@ -10,10 +10,10 @@ namespace expt { /*! Default, no execution space. */ NONE = 0, /*! Executing in CPU space */ - CPU, + HOST, #if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) /*! Executing in GPU space */ - GPU + DEVICE #endif }; // enum class ExecutionContext } // namespace expt diff --git a/src/chai/expt/HostManager.cpp b/src/chai/expt/HostManager.cpp new file mode 100644 index 00000000..cdceefca --- /dev/null +++ b/src/chai/expt/HostManager.cpp @@ -0,0 +1,35 @@ +#include "chai/expt/HostManager.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + HostManager::HostManager(int allocatorID, std::size_t size) : + Manager{}, + m_allocator_id{allocatorID}, + m_size{size} + { + m_data = umpire::ResourceManager::getInstance().getAllocator(m_allocator_id).allocate(size); + } + + HostManager::~HostManager() { + umpire::ResourceManager::getInstance().getAllocator(m_allocator_id).deallocate(m_data); + } + + std::size_t HostManager::size() const { + return m_size; + } + + void HostManager::update(void*& data, bool /* touch */) { + if (execution_context() == ExecutionContext::HOST) { + data = m_data; + } + else { + data = nullptr; + } + } + + int HostManager::getAllocatorID() const { + return m_allocator_id; + } +} // namespace expt +} // namespace chai diff --git a/src/chai/expt/HostManager.hpp b/src/chai/expt/HostManager.hpp index 9ea924e4..f07b93a6 100644 --- a/src/chai/expt/HostManager.hpp +++ b/src/chai/expt/HostManager.hpp @@ -1,6 +1,8 @@ #ifndef CHAI_HOST_MANAGER_HPP #define CHAI_HOST_MANAGER_HPP +#include "chai/expt/Manager.hpp" + namespace chai { namespace expt { /*! @@ -8,47 +10,49 @@ namespace expt { * * \brief Controls the coherence of an array on the CPU. */ - template class HostManager : public Manager { public: /*! * \brief Constructs a host array manager. */ - HostManager(size_t size, const Allocator& allocator) : - m_allocator{allocator}, - m_size{size} - { - m_data = m_allocator.allocate(size); - } + HostManager(int allocatorID, std::size_t size); + /*! + * \brief Deleted copy constructor. + */ HostManager(const HostManager&) = delete; + + /*! + * \brief Deleted copy assignment operator. + */ HostManager& operator=(const HostManager&) = delete; /*! * \brief Virtual destructor. */ - virtual ~HostManager() { - m_allocator.deallocate(m_data); - } + virtual ~HostManager(); + + /*! + * \brief Get the number of elements. + */ + virtual std::size_t size() const override; /*! * \brief Updates the data to be coherent in the current execution space. * * \param data [out] A coherent array in the current execution space. */ - virtual void update(void*& data, bool touch) { - if (execution_space() == ExecutionSpace::CPU) { - data = m_data; - } - else { - data = nullptr; - } - } + virtual void update(void*& data, bool touch) override; + + /*! + * \brief Get the allocator ID. + */ + int getAllocatorID() const; private: - Allocator m_allocator{}; - size_t m_size{0}; - T* m_data{nullptr}; + int m_allocator_id{-1}; + std::size_t m_size{0}; + void* m_data{nullptr}; }; // class HostManager } // namespace expt } // namespace chai diff --git a/src/chai/expt/Manager.cpp b/src/chai/expt/Manager.cpp index 7da30c80..a369f08c 100644 --- a/src/chai/expt/Manager.cpp +++ b/src/chai/expt/Manager.cpp @@ -2,9 +2,9 @@ namespace chai { namespace expt { - ExecutionContext& Manager::execution_space() { - static thread_local ExecutionContext s_space = ExecutionContext::NONE; - return s_space; + ExecutionContext& Manager::execution_context() { + static thread_local ExecutionContext s_context = ExecutionContext::NONE; + return s_context; } } // namespace expt } // namespace chai diff --git a/src/chai/expt/Manager.hpp b/src/chai/expt/Manager.hpp index d506e2e1..01c84db6 100644 --- a/src/chai/expt/Manager.hpp +++ b/src/chai/expt/Manager.hpp @@ -3,6 +3,8 @@ #include "chai/expt/ExecutionContext.hpp" +#include + namespace chai { namespace expt { /*! @@ -17,6 +19,11 @@ namespace expt { */ virtual ~Manager() = default; + /*! + * \brief Get the number of elements. + */ + virtual std::size_t size() const = 0; + /*! * \brief Updates the data to be coherent in the current execution context. * diff --git a/src/chai/expt/PinnedManager.hpp b/src/chai/expt/PinnedManager.hpp index 0bb1173c..49c13cfe 100644 --- a/src/chai/expt/PinnedManager.hpp +++ b/src/chai/expt/PinnedManager.hpp @@ -32,6 +32,13 @@ namespace expt { m_allocator.deallocate(m_data); } + /*! + * \brief Get the number of elements. + */ + virtual size_t size() const { + return m_size; + } + /*! * \brief Updates the data to be coherent in the current execution space. * diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index ca5dbb88..f861606b 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -25,6 +25,22 @@ blt_add_test( NAME managed_array_test COMMAND managed_array_tests) +if (FALSE) +# Array tests +blt_add_executable( + NAME TestArray + SOURCES TestArray.cpp + DEPENDS_ON ${chai_integration_test_depends}) + +target_include_directories( + TestArray + PUBLIC ${PROJECT_BINARY_DIR}/include) + +blt_add_test( + NAME TestArray + COMMAND TestArray) +endif() + if (CHAI_ENABLE_MANAGED_PTR) blt_add_executable( NAME managed_ptr_tests diff --git a/tests/integration/expt/TestArray.cpp b/tests/integration/expt/TestArray.cpp new file mode 100644 index 00000000..70c392ba --- /dev/null +++ b/tests/integration/expt/TestArray.cpp @@ -0,0 +1,73 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" +#define GPU_TEST(X, Y) \ + static void gpu_test_##X##Y(); \ + TEST(X, Y) { gpu_test_##X##Y(); } \ + static void gpu_test_##X##Y() + +#ifdef NDEBUG + +#ifdef CHAI_ENABLE_CUDA +#define device_assert(EXP) if( !(EXP) ) asm ("trap;") +#else +#define device_assert(EXP) if( !(EXP) ) asm ("s_trap 1;") +#endif + +#else +#define device_assert(EXP) assert(EXP) +#endif + +#ifdef CHAI_DISABLE_RM +#define assert_empty_map(IGNORED) +#else +#define assert_empty_map(IGNORED) ASSERT_EQ(chai::ArrayManager::getInstance()->getPointerMap().size(),0) +#endif + + +#include "chai/config.hpp" + +#include "../src/util/forall.hpp" + +#include "chai/ManagedArray.hpp" + +#include "umpire/ResourceManager.hpp" + + +TEST(Array, HostManager) +{ + chai::Array a = chai::makeArray(10, allocator); + + forall(sequential(), 0, 10, [=](int i) { array[i] = i; }); + + forall(sequential(), 0, 10, [=](int i) { ASSERT_EQ(array[i], i); }); + + array.free(); + + assert_empty_map(true); +} + + +GPU_TEST(ManagedArray, PickandSetDeviceToDeviceUM) +{ + chai::ManagedArray array1(10, chai::UM); + chai::ManagedArray array2(10, chai::UM); + + forall(gpu(), 0, 10, [=] __device__(int i) { array1[i] = i; }); + + forall(gpu(), 0, 10, [=] __device__(int i) { + int temp = array1.pick(i); + array2.set(i, temp); + }); + + forall(sequential(), 0, 10, [=](int i) { ASSERT_EQ(array2[i], i); }); + + array1.free(); + array2.free(); + assert_empty_map(true); +} + diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 1831dfe9..8e6ed770 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -50,3 +50,16 @@ if (CHAI_ENABLE_MANAGED_PTR) NAME managed_ptr_unit_test COMMAND managed_ptr_unit_tests) endif () + +blt_add_executable( + NAME TestHostManager + SOURCES expt/TestHostManager.cpp + DEPENDS_ON ${chai_unit_test_depends}) + +target_include_directories( + TestHostManager + PUBLIC ${PROJECT_BINARY_DIR}/include) + +blt_add_test( + NAME TestHostManager + COMMAND TestHostManager) diff --git a/tests/unit/expt/TestHostManager.cpp b/tests/unit/expt/TestHostManager.cpp new file mode 100644 index 00000000..bdfd4c3a --- /dev/null +++ b/tests/unit/expt/TestHostManager.cpp @@ -0,0 +1,54 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" + +#include "chai/config.hpp" + +#include "chai/expt/HostManager.hpp" + +#include "umpire/ResourceManager.hpp" + +TEST(HostManager, Constructor) +{ + size_t size = 100; + int allocatorID = umpire::ResourceManager::getInstance().getAllocator("HOST").getId(); + + chai::expt::HostManager manager(allocatorID, size); + + ASSERT_EQ(allocatorID, manager.getAllocatorID()); + ASSERT_EQ(size, manager.size()); + + void* data; + + /* + * Test chai::expt::ExecutionContext::NONE + */ + manager.execution_context() = chai::expt::ExecutionContext::NONE; + manager.update(data, false); + ASSERT_EQ(nullptr, data); + + /* + * Test chai::expt::ExecutionContext::HOST + */ + manager.execution_context() = chai::expt::ExecutionContext::HOST; + manager.update(data, false); + ASSERT_NE(nullptr, data); + +#if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) + /* + * Test chai::expt::ExecutionContext::DEVICE + */ + manager.execution_context() = chai::expt::ExecutionContext::DEVICE; + manager.update(data, false); + ASSERT_EQ(nullptr, data); +#endif + + /* + * Reset chai::expt::ExecutionContext + */ + manager.execution_context() = chai::expt::ExecutionContext::NONE; +} From 5ae9eff3787861366268c84f892629a8b8c10f92 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 31 Mar 2025 12:49:09 -0700 Subject: [PATCH 11/49] Refactor HostManager tests --- src/chai/expt/HostManager.cpp | 8 ++-- src/chai/expt/HostManager.hpp | 2 +- src/chai/expt/Manager.hpp | 2 +- tests/unit/expt/TestHostManager.cpp | 60 +++++++++++++---------------- 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/chai/expt/HostManager.cpp b/src/chai/expt/HostManager.cpp index cdceefca..68053494 100644 --- a/src/chai/expt/HostManager.cpp +++ b/src/chai/expt/HostManager.cpp @@ -19,12 +19,12 @@ namespace expt { return m_size; } - void HostManager::update(void*& data, bool /* touch */) { - if (execution_context() == ExecutionContext::HOST) { - data = m_data; + void* HostManager::data(ExecutionContext context, bool /* touch */) { + if (context == ExecutionContext::HOST) { + return m_data; } else { - data = nullptr; + return nullptr; } } diff --git a/src/chai/expt/HostManager.hpp b/src/chai/expt/HostManager.hpp index f07b93a6..802500b3 100644 --- a/src/chai/expt/HostManager.hpp +++ b/src/chai/expt/HostManager.hpp @@ -42,7 +42,7 @@ namespace expt { * * \param data [out] A coherent array in the current execution space. */ - virtual void update(void*& data, bool touch) override; + virtual void* data(ExecutionContext context, bool touch) override; /*! * \brief Get the allocator ID. diff --git a/src/chai/expt/Manager.hpp b/src/chai/expt/Manager.hpp index 01c84db6..d7f7db7c 100644 --- a/src/chai/expt/Manager.hpp +++ b/src/chai/expt/Manager.hpp @@ -29,7 +29,7 @@ namespace expt { * * \param data [out] A coherent array in the current execution context. */ - virtual void update(void*& data, bool touch) = 0; + virtual void* data(ExecutionContext context, bool touch) = 0; /*! * \brief Returns a modifiable reference to the current execution context. diff --git a/tests/unit/expt/TestHostManager.cpp b/tests/unit/expt/TestHostManager.cpp index bdfd4c3a..7abf605a 100644 --- a/tests/unit/expt/TestHostManager.cpp +++ b/tests/unit/expt/TestHostManager.cpp @@ -7,48 +7,40 @@ #include "gtest/gtest.h" #include "chai/config.hpp" - #include "chai/expt/HostManager.hpp" - #include "umpire/ResourceManager.hpp" -TEST(HostManager, Constructor) -{ - size_t size = 100; - int allocatorID = umpire::ResourceManager::getInstance().getAllocator("HOST").getId(); +class HostManagerTest : public testing::Test { + protected: + int m_allocator_id{umpire::ResourceManager::getInstance().getAllocator("HOST").getId()}; + std::size_t m_size{100}; + chai::expt::HostManager m_manager{m_allocator_id, m_size}; +}; - chai::expt::HostManager manager(allocatorID, size); - ASSERT_EQ(allocatorID, manager.getAllocatorID()); - ASSERT_EQ(size, manager.size()); +TEST_F(HostManagerTest, AllocatorID) +{ + EXPECT_EQ(m_allocator_id, m_manager.getAllocatorID()); +} - void* data; +TEST_F(HostManagerTest, Size) +{ + EXPECT_EQ(m_size, m_manager.size()); +} - /* - * Test chai::expt::ExecutionContext::NONE - */ - manager.execution_context() = chai::expt::ExecutionContext::NONE; - manager.update(data, false); - ASSERT_EQ(nullptr, data); +TEST_F(HostManagerTest, DataExecutionContextNone) +{ + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, false)); +} - /* - * Test chai::expt::ExecutionContext::HOST - */ - manager.execution_context() = chai::expt::ExecutionContext::HOST; - manager.update(data, false); - ASSERT_NE(nullptr, data); +TEST_F(HostManagerTest, DataExecutionContextHost) +{ + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, false)); +} #if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) - /* - * Test chai::expt::ExecutionContext::DEVICE - */ - manager.execution_context() = chai::expt::ExecutionContext::DEVICE; - manager.update(data, false); - ASSERT_EQ(nullptr, data); -#endif - - /* - * Reset chai::expt::ExecutionContext - */ - manager.execution_context() = chai::expt::ExecutionContext::NONE; +TEST_F(HostManagerTest, DataExecutionContextNone) +{ + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); } +#endif From 844d8860afda207bd36b3f87f5a1a4ee680244d2 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 31 Mar 2025 16:48:14 -0700 Subject: [PATCH 12/49] Update CopyHidingManager and tests --- .../blueos_3_ppc64le_ib_p9/nvcc_clang.cmake | 2 + src/chai/CMakeLists.txt | 11 +- src/chai/expt/CopyHidingManager.cpp | 75 ++++++++++++ src/chai/expt/CopyHidingManager.hpp | 107 ++++++------------ src/chai/expt/ExecutionContext.hpp | 2 + src/chai/expt/HostManager.hpp | 4 +- tests/unit/CMakeLists.txt | 15 +++ tests/unit/expt/TestCopyHidingManager.cpp | 60 ++++++++++ tests/unit/expt/TestHostManager.cpp | 2 +- 9 files changed, 200 insertions(+), 78 deletions(-) create mode 100644 src/chai/expt/CopyHidingManager.cpp create mode 100644 tests/unit/expt/TestCopyHidingManager.cpp diff --git a/host-configs/lc/blueos_3_ppc64le_ib_p9/nvcc_clang.cmake b/host-configs/lc/blueos_3_ppc64le_ib_p9/nvcc_clang.cmake index 4b2ae845..fdd69246 100644 --- a/host-configs/lc/blueos_3_ppc64le_ib_p9/nvcc_clang.cmake +++ b/host-configs/lc/blueos_3_ppc64le_ib_p9/nvcc_clang.cmake @@ -30,3 +30,5 @@ set(CMAKE_CUDA_COMPILER "${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc" CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") set(CMAKE_CUDA_ARCHITECTURES "70" CACHE STRING "") set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=--gcc-toolchain=${GCC_HOME}" CACHE STRING "") + +set(UMPIRE_FMT_TARGET "fmt::fmt" CACHE STRING "") diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index 9a344cf6..e1eacb3a 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -24,7 +24,6 @@ set (chai_headers expt/Array.hpp expt/ExecutionContext.hpp expt/Manager.hpp - expt/CopyHidingManager.hpp expt/HostManager.hpp expt/PageableManager.hpp expt/PinnedManager.hpp) @@ -40,6 +39,16 @@ set (chai_sources expt/Manager.cpp expt/HostManager.cpp) +if(CHAI_ENABLE_CUDA OR CHAI_ENABLE_HIP OR CHAI_ENABLE_GPU_SIMULATION_MODE) + set(chai_headers + ${chai_headers} + expt/CopyHidingManager.hpp) + + set(chai_sources + ${chai_sources} + expt/CopyHidingManager.cpp) +endif() + set (chai_depends umpire) diff --git a/src/chai/expt/CopyHidingManager.cpp b/src/chai/expt/CopyHidingManager.cpp new file mode 100644 index 00000000..92b00b29 --- /dev/null +++ b/src/chai/expt/CopyHidingManager.cpp @@ -0,0 +1,75 @@ +#include "chai/expt/CopyHidingManager.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + CopyHidingManager::CopyHidingManager(int hostAllocatorID, + int deviceAllocatorID, + std::size_t size) : + Manager{}, + m_host_allocator_id{hostAllocatorID}, + m_device_allocator_id{deviceAllocatorID}, + m_size{size} + { + } + + CopyHidingManager::~CopyHidingManager() { + umpire::ResourceManager::getInstance().getAllocator(m_device_allocator_id).deallocate(m_device_data); + umpire::ResourceManager::getInstance().getAllocator(m_host_allocator_id).deallocate(m_host_data); + } + + size_t CopyHidingManager::size() const { + return m_size; + } + + void* CopyHidingManager::data(ExecutionContext context, bool touch) { + if (context == ExecutionContext::HOST) { + if (!m_host_data) { + m_host_data = umpire::ResourceManager::getInstance().getAllocator(m_host_allocator_id).allocate(m_size); + } + + if (m_touch == ExecutionContext::DEVICE) { + umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size); + m_touch = ExecutionContext::NONE; + } + + if (touch) { + m_touch = ExecutionContext::HOST; + } + + return m_host_data; + } + else if (context == ExecutionContext::DEVICE) { + if (!m_device_data) { + m_device_data = umpire::ResourceManager::getInstance().getAllocator(m_device_allocator_id).allocate(m_size); + } + + if (m_touch == ExecutionContext::HOST) { + umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size); + m_touch = ExecutionContext::NONE; + } + + if (touch) { + m_touch = ExecutionContext::DEVICE; + } + + return m_device_data; + } + else { + return nullptr; + } + } + + int CopyHidingManager::getHostAllocatorID() const { + return m_host_allocator_id; + } + + int CopyHidingManager::getDeviceAllocatorID() const { + return m_device_allocator_id; + } + + ExecutionContext CopyHidingManager::getTouch() const { + return m_touch; + } +} // namespace expt +} // namespace chai diff --git a/src/chai/expt/CopyHidingManager.hpp b/src/chai/expt/CopyHidingManager.hpp index 19ad9ef1..99385b0b 100644 --- a/src/chai/expt/CopyHidingManager.hpp +++ b/src/chai/expt/CopyHidingManager.hpp @@ -1,6 +1,8 @@ #ifndef CHAI_COPY_HIDING_MANAGER_HPP #define CHAI_COPY_HIDING_MANAGER_HPP +#include "chai/expt/Manager.hpp" + namespace chai { namespace expt { /*! @@ -8,107 +10,64 @@ namespace expt { * * \brief Controls the coherence of an array on the host and device. */ - template class CopyHidingManager : public Manager { public: /*! * \brief Constructs a host array manager. */ - CopyHidingManager(size_t size, - const HostAllocator& hostAllocator = HostAllocator(), - const DeviceAllocator& deviceAllocator = DeviceAllocator()) - m_size{size}, - m_host_allocator{hostAllocator}, - m_device_allocator{deviceAllocator} - { - } + CopyHidingManager(int hostAllocatorID, + int deviceAllocatorID, + std::size_t size); + /*! + * \brief Copy constructor is deleted. + */ CopyHidingManager(const CopyHidingManager&) = delete; + + /*! + * \brief Copy assignment operator is deleted. + */ CopyHidingManager& operator=(const CopyHidingManager&) = delete; /*! * \brief Virtual destructor. */ - virtual ~CopyHidingManager() { - m_device_allocator.deallocate(m_device_data); - m_host_allocator.deallocate(m_host_data); - } + virtual ~CopyHidingManager(); /*! * \brief Get the number of elements. */ - virtual size_t size() const { - return m_size; - } + virtual std::size_t size() const override; /*! * \brief Updates the data to be coherent in the current execution space. * * \param data [out] A coherent array in the current execution space. */ - virtual void update(void*& data, bool touch) { - ExecutionContext context = execution_context(); - - if (context == ExecutionContext::Host) { - if (!m_host_data) { - m_host_data = m_host_allocator.allocate(m_size); - } - - if (m_touch == ExecutionContext::Device) { -#if defined(CHAI_ENABLE_CUDA) - cudaMemcpy(m_host_data, m_device_data, m_size, cudaMemcpyDtoH); -#elif defined(CHAI_ENABLE_HIP) - hipMemcpy(m_host_data, m_device_data, m_size, hipMemcpyDtoH); -#else - memcpy(m_host_data, m_device_data, m_size); -#endif - - // Reset touch - m_touch = ExecutionContext::None; - } + virtual void* data(ExecutionContext context, bool touch) override; - if (touch) { - m_touch = ExecutionContext::Host; - } - - data = m_host_data; - } - else if (context == ExecutionContext::Device) { - if (!m_device_data) { - m_device_data = m_device_allocator.allocate(m_size); - } - - if (m_touch == ExecutionContext::Host) { -#if defined(CHAI_ENABLE_CUDA) - cudaMemcpy(m_device_data, m_host_data, m_size); -#elif defined(CHAI_ENABLE_HIP) - hipMemcpy(m_device_data, m_host_data, m_size); -#else - memcpy(m_device_data, m_host_data, m_size); -#endif - - // Reset touch - m_touch = ExecutionContext::None; - } + /*! + * \brief Get the host allocator ID. + */ + int getHostAllocatorID() const; - if (touch) { - m_touch = ExecutionContext::Device; - } + /*! + * \brief Get the device allocator ID. + */ + int getDeviceAllocatorID() const; - data = m_device_data; - } - else { - data = nullptr; - } - } + /*! + * \brief Get the last touch. + */ + ExecutionContext getTouch() const; private: - size_t m_size{0}; - T* m_host_data{nullptr}; - T* m_device_data{nullptr}; - HostAllocator m_host_allocator{}; - DeviceAllocator m_device_allocator{}; - ExecutionContext m_touch{ExecutionContext::None}; + int m_host_allocator_id{-1}; + int m_device_allocator_id{-1}; + std::size_t m_size{0}; + void* m_host_data{nullptr}; + void* m_device_data{nullptr}; + ExecutionContext m_touch{ExecutionContext::NONE}; }; // class CopyHidingManager } // namespace expt } // namespace chai diff --git a/src/chai/expt/ExecutionContext.hpp b/src/chai/expt/ExecutionContext.hpp index 3a1209d4..d01af8d4 100644 --- a/src/chai/expt/ExecutionContext.hpp +++ b/src/chai/expt/ExecutionContext.hpp @@ -1,6 +1,8 @@ #ifndef CHAI_EXECUTION_CONTEXT_HPP #define CHAI_EXECUTION_CONTEXT_HPP +#include "chai/config.hpp" + namespace chai { namespace expt { /*! diff --git a/src/chai/expt/HostManager.hpp b/src/chai/expt/HostManager.hpp index 802500b3..51fd9eeb 100644 --- a/src/chai/expt/HostManager.hpp +++ b/src/chai/expt/HostManager.hpp @@ -18,12 +18,12 @@ namespace expt { HostManager(int allocatorID, std::size_t size); /*! - * \brief Deleted copy constructor. + * \brief Copy constructor is deleted. */ HostManager(const HostManager&) = delete; /*! - * \brief Deleted copy assignment operator. + * \brief Copy assignment operator is deleted. */ HostManager& operator=(const HostManager&) = delete; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 8e6ed770..e4cb579f 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -63,3 +63,18 @@ target_include_directories( blt_add_test( NAME TestHostManager COMMAND TestHostManager) + +if(CHAI_ENABLE_CUDA OR CHAI_ENABLE_HIP OR CHAI_ENABLE_GPU_SIMULATION_MODE) + blt_add_executable( + NAME TestCopyHidingManager + SOURCES expt/TestCopyHidingManager.cpp + DEPENDS_ON ${chai_unit_test_depends}) + + target_include_directories( + TestCopyHidingManager + PUBLIC ${PROJECT_BINARY_DIR}/include) + + blt_add_test( + NAME TestCopyHidingManager + COMMAND TestCopyHidingManager) +endif() diff --git a/tests/unit/expt/TestCopyHidingManager.cpp b/tests/unit/expt/TestCopyHidingManager.cpp new file mode 100644 index 00000000..28b35b33 --- /dev/null +++ b/tests/unit/expt/TestCopyHidingManager.cpp @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" + +#include "chai/config.hpp" +#include "chai/expt/CopyHidingManager.hpp" +#include "umpire/ResourceManager.hpp" + +class CopyHidingManagerTest : public testing::Test { + protected: + int m_host_allocator_id{umpire::ResourceManager::getInstance().getAllocator("HOST").getId()}; + int m_device_allocator_id{umpire::ResourceManager::getInstance().getAllocator("DEVICE").getId()}; + std::size_t m_size{100}; + chai::expt::CopyHidingManager m_manager{m_host_allocator_id, + m_device_allocator_id, + m_size}; +}; + + +TEST_F(CopyHidingManagerTest, HostAllocatorID) +{ + EXPECT_EQ(m_host_allocator_id, m_manager.getHostAllocatorID()); +} + +TEST_F(CopyHidingManagerTest, DeviceAllocatorID) +{ + EXPECT_EQ(m_device_allocator_id, m_manager.getDeviceAllocatorID()); +} + +TEST_F(CopyHidingManagerTest, Size) +{ + EXPECT_EQ(m_size, m_manager.size()); +} + +TEST_F(CopyHidingManagerTest, DataExecutionContextNone) +{ + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, false)); + EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); +} + +TEST_F(CopyHidingManagerTest, DataExecutionContextHost) +{ + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, false)); + EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); +} + +TEST_F(CopyHidingManagerTest, DataExecutionContextDevice) +{ + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); + EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); +} + +TEST_F(CopyHidingManagerTest, Touch) +{ + EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); +} diff --git a/tests/unit/expt/TestHostManager.cpp b/tests/unit/expt/TestHostManager.cpp index 7abf605a..ede568b8 100644 --- a/tests/unit/expt/TestHostManager.cpp +++ b/tests/unit/expt/TestHostManager.cpp @@ -39,7 +39,7 @@ TEST_F(HostManagerTest, DataExecutionContextHost) } #if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) -TEST_F(HostManagerTest, DataExecutionContextNone) +TEST_F(HostManagerTest, DataExecutionContextDevice) { EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); } From 4676ed28c3ce09b0ce1e565c7ad8bc71aa2a8a1a Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 31 Mar 2025 16:55:51 -0700 Subject: [PATCH 13/49] Add more test cases --- tests/unit/expt/TestCopyHidingManager.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit/expt/TestCopyHidingManager.cpp b/tests/unit/expt/TestCopyHidingManager.cpp index 28b35b33..64daac67 100644 --- a/tests/unit/expt/TestCopyHidingManager.cpp +++ b/tests/unit/expt/TestCopyHidingManager.cpp @@ -58,3 +58,21 @@ TEST_F(CopyHidingManagerTest, Touch) { EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); } + +TEST_F(CopyHidingManagerTest, DataExecutionContextNoneTouch) +{ + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, true)); + EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); +} + +TEST_F(CopyHidingManagerTest, DataExecutionContextHostTouch) +{ + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, true)); + EXPECT_EQ(chai::expt::ExecutionContext::HOST, m_manager.getTouch()); +} + +TEST_F(CopyHidingManagerTest, DataExecutionContextDeviceTouch) +{ + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, true)); + EXPECT_EQ(chai::expt::ExecutionContext::DEVICE, m_manager.getTouch()); +} From d72a12c4f821fc56f1bdc383f3f5c0023099ac15 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 20 May 2025 15:07:22 -0700 Subject: [PATCH 14/49] Update tests --- tests/unit/expt/TestCopyHidingManager.cpp | 54 ++++++++++++++--------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/tests/unit/expt/TestCopyHidingManager.cpp b/tests/unit/expt/TestCopyHidingManager.cpp index 64daac67..8c5c9e7f 100644 --- a/tests/unit/expt/TestCopyHidingManager.cpp +++ b/tests/unit/expt/TestCopyHidingManager.cpp @@ -10,7 +10,18 @@ #include "chai/expt/CopyHidingManager.hpp" #include "umpire/ResourceManager.hpp" -class CopyHidingManagerTest : public testing::Test { +/*! + * CopyHidingManager has many states and transitions. A test fixture is created + * for each state, and a test case is created for each transition. + */ + +/*! + * \class CopyHidingManager_StateBothUnallocated_Test + * + * \brief Test fixture for the state where both host and device data are + * unallocated (and untouched) + */ +class CopyHidingManager_StateBothUnallocated_Test : public testing::Test { protected: int m_host_allocator_id{umpire::ResourceManager::getInstance().getAllocator("HOST").getId()}; int m_device_allocator_id{umpire::ResourceManager::getInstance().getAllocator("DEVICE").getId()}; @@ -20,59 +31,58 @@ class CopyHidingManagerTest : public testing::Test { m_size}; }; +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Size) +{ + EXPECT_EQ(m_size, m_manager.size()); +} -TEST_F(CopyHidingManagerTest, HostAllocatorID) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, HostAllocatorID) { EXPECT_EQ(m_host_allocator_id, m_manager.getHostAllocatorID()); } -TEST_F(CopyHidingManagerTest, DeviceAllocatorID) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, DeviceAllocatorID) { EXPECT_EQ(m_device_allocator_id, m_manager.getDeviceAllocatorID()); } -TEST_F(CopyHidingManagerTest, Size) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Touch) { - EXPECT_EQ(m_size, m_manager.size()); + EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); } -TEST_F(CopyHidingManagerTest, DataExecutionContextNone) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Data_None) { EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, false)); EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); } -TEST_F(CopyHidingManagerTest, DataExecutionContextHost) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Data_None_Touch) { - EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, false)); + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, true)); EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); } -TEST_F(CopyHidingManagerTest, DataExecutionContextDevice) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Data_Host) { - EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, false)); EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); } -TEST_F(CopyHidingManagerTest, Touch) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Data_Host_Touch) { - EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, true)); + EXPECT_EQ(chai::expt::ExecutionContext::HOST, m_manager.getTouch()); } -TEST_F(CopyHidingManagerTest, DataExecutionContextNoneTouch) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Data_Device) { - EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, true)); + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); EXPECT_EQ(chai::expt::ExecutionContext::NONE, m_manager.getTouch()); } -TEST_F(CopyHidingManagerTest, DataExecutionContextHostTouch) -{ - EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, true)); - EXPECT_EQ(chai::expt::ExecutionContext::HOST, m_manager.getTouch()); -} - -TEST_F(CopyHidingManagerTest, DataExecutionContextDeviceTouch) +TEST_F(CopyHidingManager_StateBothUnallocated_Test, Data_Device_Touch) { - EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, true)); + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); EXPECT_EQ(chai::expt::ExecutionContext::DEVICE, m_manager.getTouch()); } From 99aa35200273ce709cfaa6fc8113dc9872820281 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Thu, 29 May 2025 13:40:19 -0700 Subject: [PATCH 15/49] More updates --- src/chai/expt/PinnedArray.hpp | 102 ++++++++++++++++++++ src/chai/expt/PinnedManager.hpp | 35 ++++++- tests/unit/expt/TestPinnedManager.cpp | 128 ++++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 src/chai/expt/PinnedArray.hpp create mode 100644 tests/unit/expt/TestPinnedManager.cpp diff --git a/src/chai/expt/PinnedArray.hpp b/src/chai/expt/PinnedArray.hpp new file mode 100644 index 00000000..176805ad --- /dev/null +++ b/src/chai/expt/PinnedArray.hpp @@ -0,0 +1,102 @@ +#ifndef CHAI_PINNED_ARRAY_HPP +#define CHAI_PINNED_ARRAY_HPP + +namespace chai { +namespace expt { + /*! + * \class PinnedArray + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class PinnedArray : public Manager { + public: + PinnedArray() noexcept(noexcept(Allocator())) : + PinnedArray(Allocator()) + { + } + + explicit PinnedArray(const Allocator& allocator) : + m_allocator{allocator} + { + } + + /*! + * \brief Constructs a host array manager. + */ + PinnedArray(size_t size, + const Allocator& allocator = Allocator()) : + m_size{size}, + m_data{allocator.allocate(size)}, + m_allocator{allocator} + { + } + + PinnedArray(const PinnedArray& other) : + m_size{other.m_size}, + m_data{other.m_allocator.allocate(size)}, + m_allocator{other.m_allocator} + { + // Copy data from other array + } + + PinnedArray(PinnedArray&& other) : + m_size{other.m_size}, + m_data{other.m_data}, + m_allocator{other.m_allocator} + { + other.m_size = 0; + other.m_data = nullptr; + other.m_allocator = Allocator(); + } + + PinnedArray& operator=(const PinnedArray&) = delete; + + /*! + * \brief Virtual destructor. + */ + virtual ~PinnedArray() { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Get the number of elements. + */ + virtual size_t size() const { + return m_size; + } + + virtual T* data(ExecutionContext context, bool touch) { + + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + * + * \param data [out] A coherent array in the current execution space. + */ + virtual void update(void*& data, bool touch) { + ExecutionContext context = execution_context(); + + if (context == ExecutionContext::None) { + data = nullptr; + } + else { + if (context == ExecutionContext::Host) { + // TODO: Only sync if last touched on device + synchronizeDeviceIfNeeded(); + } + + data = m_data; + } + } + + private: + size_t m_size{0}; + T* m_data{nullptr}; + Allocator m_allocator{}; + }; // class PinnedArray +} // namespace expt +} // namespace chai + +#endif // CHAI_PINNED_ARRAY_HPP diff --git a/src/chai/expt/PinnedManager.hpp b/src/chai/expt/PinnedManager.hpp index 49c13cfe..295e6cd0 100644 --- a/src/chai/expt/PinnedManager.hpp +++ b/src/chai/expt/PinnedManager.hpp @@ -11,18 +11,45 @@ namespace expt { template class PinnedManager : public Manager { public: + PinnedManager() noexcept(noexcept(Allocator())) : + PinnedManager(Allocator()) + { + } + + explicit PinnedManager(const Allocator& allocator) : + m_allocator{allocator} + { + } + /*! * \brief Constructs a host array manager. */ PinnedManager(size_t size, - const Allocator& allocator = Allocator()) + const Allocator& allocator = Allocator()) : m_size{size}, m_data{allocator.allocate(size)}, m_allocator{allocator} { } - PinnedManager(const PinnedManager&) = delete; + PinnedManager(const PinnedManager& other) : + m_size{other.m_size}, + m_data{other.m_allocator.allocate(size)}, + m_allocator{other.m_allocator} + { + // Copy data from other array + } + + PinnedManager(PinnedManager&& other) : + m_size{other.m_size}, + m_data{other.m_data}, + m_allocator{other.m_allocator} + { + other.m_size = 0; + other.m_data = nullptr; + other.m_allocator = Allocator(); + } + PinnedManager& operator=(const PinnedManager&) = delete; /*! @@ -39,6 +66,10 @@ namespace expt { return m_size; } + virtual T* data(ExecutionContext context, bool touch) { + + } + /*! * \brief Updates the data to be coherent in the current execution space. * diff --git a/tests/unit/expt/TestPinnedManager.cpp b/tests/unit/expt/TestPinnedManager.cpp new file mode 100644 index 00000000..6cc9ee62 --- /dev/null +++ b/tests/unit/expt/TestPinnedManager.cpp @@ -0,0 +1,128 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" + +#include "chai/config.hpp" +#include "chai/expt/HostManager.hpp" +#include "umpire/ResourceManager.hpp" + +class HostManagerTest : public testing::Test { + protected: + int m_allocator_id{umpire::ResourceManager::getInstance().getAllocator("HOST").getId()}; + std::size_t m_size{100}; + chai::expt::HostManager m_manager{m_allocator_id, m_size}; +}; + + +TEST_F(HostManagerTest, AllocatorID) +{ + EXPECT_EQ(m_allocator_id, m_manager.getAllocatorID()); +} + +TEST_F(HostManagerTest, Size) +{ + EXPECT_EQ(m_size, m_manager.size()); +} + +TEST_F(HostManagerTest, DataExecutionContextNone) +{ + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::NONE, false)); +} + +TEST_F(HostManagerTest, DataExecutionContextHost) +{ + EXPECT_NE(nullptr, m_manager.data(chai::expt::ExecutionContext::HOST, false)); +} + +#if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) +TEST_F(HostManagerTest, DataExecutionContextDevice) +{ + EXPECT_EQ(nullptr, m_manager.data(chai::expt::ExecutionContext::DEVICE, false)); +} +#endif + +TEST(PinnedManagerTest, Container1) +{ + // Could have accessor parameter to control whether operator[] is defined. + chai::ManagedArray myArray(10); + + { + ManagedView myView(myArray); + + RAJA_LOOP(i, 0, 10) { + // Could accidentally do a deep copy of myArray if didn't cast to a view + myView[i]++; + } RAJA_LOOP_END + } + + ManagedArray myArray2 = myArray; // Deep copy +} + +TEST(PinnedManagerTest, Container2) +{ + ManagedArray myArray(10); + + RAJA_LOOP(i, 0, 10, myView = ManagedView(myArray)) { + // Could accidentally do a deep copy of myArray if didn't cast to a view + myView[i]++; + } RAJA_LOOP_END + + ManagedArray myArray2 = myArray; // Deep copy +} + + +TEST(PinnedManagerTest, UniquePtr1) +{ + ManagedArray myArray(10); + + { + ManagedView myView(myArray); + + // Can't accidentally copy myArray into loop + RAJA_LOOP(i, 0, 10) { + myView[i]++; + } RAJA_LOOP_END + } + + ManagedArray& myArray2 = myArray; // Can't do a deep copy +} + +TEST(PinnedManagerTest, UniqueArray) +{ + chai::UniqueArray myArray = chai::makeUnique(10); + + // Can't accidentally copy myArray into loop + RAJA_LOOP(i, 0, 10, myView = ManagedView(myArray)) { + myView[i]++; + } RAJA_LOOP_END + + // Can't do a deep copy + ManagedArray myArray2 = myArray; // Deep copy +} + +TEST(PinnedManagerTest, SharedArray) +{ + chai::SharedArray myArray = chai::makeShared(10); + + RAJA_LOOP(i, 0, 10) { + // Can be used directly in RAJA loop + myArray[i]++; + } RAJA_LOOP_END +} + +TEST(PinnedManagerTest, NonOwnedArray) +{ + chai::NonOwnedArray myArray = chai::makeNonOwned(10); + + RAJA_LOOP(i, 0, 10) { + // Can be used directly in RAJA loop + myArray[i]++; + } RAJA_LOOP_END + + // Have to explicitly manage lifetime + myArray.free(); +} From ef548c57a5e03e951ca374e6d6361129d013d1ce Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 23 Jun 2025 13:50:19 -0700 Subject: [PATCH 16/49] Updates --- .../lc/toss_4_x86_64_ib_cray/amdclang.cmake | 4 +- src/chai/expt/PinnedArray.hpp | 106 ++++++++++-------- src/chai/expt/PinnedManager.hpp | 7 +- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake b/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake index db1dafbd..ae0e9bc1 100644 --- a/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake +++ b/host-configs/lc/toss_4_x86_64_ib_cray/amdclang.cmake @@ -6,8 +6,8 @@ ############################################################################## # Set up software versions -set(ROCM_VERSION "6.2.0" CACHE PATH "") -set(GCC_VERSION "12.2.1" CACHE PATH "") +set(ROCM_VERSION "6.4.1" CACHE PATH "") +set(GCC_VERSION "13.3.1" CACHE PATH "") # Set up compilers set(COMPILER_BASE "/usr/tce/packages/rocmcc/rocmcc-${ROCM_VERSION}-magic" CACHE PATH "") diff --git a/src/chai/expt/PinnedArray.hpp b/src/chai/expt/PinnedArray.hpp index 176805ad..67bf1f3b 100644 --- a/src/chai/expt/PinnedArray.hpp +++ b/src/chai/expt/PinnedArray.hpp @@ -1,6 +1,8 @@ #ifndef CHAI_PINNED_ARRAY_HPP #define CHAI_PINNED_ARRAY_HPP +#include "umpire/ResourceManager.hpp" + namespace chai { namespace expt { /*! @@ -8,93 +10,103 @@ namespace expt { * * \brief Controls the coherence of an array on the host and device. */ - template - class PinnedArray : public Manager { + template + class PinnedArray public: - PinnedArray() noexcept(noexcept(Allocator())) : - PinnedArray(Allocator()) - { - } + PinnedArray() = default; - explicit PinnedArray(const Allocator& allocator) : - m_allocator{allocator} + explicit PinnedArray(int allocatorID) : + m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} { } - /*! - * \brief Constructs a host array manager. - */ - PinnedArray(size_t size, - const Allocator& allocator = Allocator()) : - m_size{size}, - m_data{allocator.allocate(size)}, - m_allocator{allocator} + explicit PinnedArray(std::size_t size, int allocatorID) : + PinnedArray(allocatorID), + m_size{size} { + m_data = m_allocator.allocate(m_size); + + // TODO: Default initialize on host or device? + for (std::size_t i = 0; i < size; ++i) { + new (m_data[i]) T(); + } } PinnedArray(const PinnedArray& other) : m_size{other.m_size}, - m_data{other.m_allocator.allocate(size)}, m_allocator{other.m_allocator} { - // Copy data from other array + m_data = m_allocator.allocate(m_size); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size); } PinnedArray(PinnedArray&& other) : - m_size{other.m_size}, m_data{other.m_data}, + m_size{other.m_size}, m_allocator{other.m_allocator} { - other.m_size = 0; other.m_data = nullptr; - other.m_allocator = Allocator(); + other.m_size = 0; + other.m_allocator = umpire::Allocator(); } - PinnedArray& operator=(const PinnedArray&) = delete; + PinnedArray& operator=(const PinnedArray& other) { + if (this != &other) { // Prevent self-assignment + m_allocator.deallocate(m_data); + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = m_allocator.allocate(m_size); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size); + } + + return *this; + } + + PinnedArray& operator=(PinnedArray&& other) { + if (this != &other) { // Prevent self-move + m_allocator.deallocate(m_data); + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = other.m_data; + + other.m_data = nullptr; + other.m_size = nullptr; + other.m_allocator = umpire::Allocator(); + } + + return *this; + } /*! * \brief Virtual destructor. */ - virtual ~PinnedArray() { + ~PinnedArray() { m_allocator.deallocate(m_data); } /*! * \brief Get the number of elements. */ - virtual size_t size() const { + size_t size() const { return m_size; } - virtual T* data(ExecutionContext context, bool touch) { - - } - - /*! - * \brief Updates the data to be coherent in the current execution space. - * - * \param data [out] A coherent array in the current execution space. - */ - virtual void update(void*& data, bool touch) { - ExecutionContext context = execution_context(); - - if (context == ExecutionContext::None) { - data = nullptr; + T* data(ExecutionSpace space) { + if (space == CPU) { +#if (__CUDACC__) + cudaDeviceSynchronize(); +#elif(__HIPCC__) + hipDeviceSynchronize(); +#endif } - else { - if (context == ExecutionContext::Host) { - // TODO: Only sync if last touched on device - synchronizeDeviceIfNeeded(); - } - data = m_data; - } + return m_data; } private: - size_t m_size{0}; T* m_data{nullptr}; - Allocator m_allocator{}; + size_t m_size{0}; + umpire::Allocator m_allocator{}; }; // class PinnedArray } // namespace expt } // namespace chai diff --git a/src/chai/expt/PinnedManager.hpp b/src/chai/expt/PinnedManager.hpp index 295e6cd0..2c04a7b1 100644 --- a/src/chai/expt/PinnedManager.hpp +++ b/src/chai/expt/PinnedManager.hpp @@ -24,11 +24,12 @@ namespace expt { /*! * \brief Constructs a host array manager. */ - PinnedManager(size_t size, - const Allocator& allocator = Allocator()) : + explicit PinnedManager(size_t size, + const Allocator& allocator = Allocator()) : + PinnedManager(allocator), m_size{size}, + // TODO: Investigate if allocate should be in body of constructor m_data{allocator.allocate(size)}, - m_allocator{allocator} { } From 5a4d58fb3e4646e406b5267c443cb33048b55adf Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 11 Aug 2025 15:48:42 -0700 Subject: [PATCH 17/49] Updates to CopyHidingManager --- src/chai/expt/CopyHidingManager.cpp | 282 ++++++++++++++++++++++++---- src/chai/expt/CopyHidingManager.hpp | 84 ++++++--- 2 files changed, 301 insertions(+), 65 deletions(-) diff --git a/src/chai/expt/CopyHidingManager.cpp b/src/chai/expt/CopyHidingManager.cpp index 92b00b29..8cdc2ffd 100644 --- a/src/chai/expt/CopyHidingManager.cpp +++ b/src/chai/expt/CopyHidingManager.cpp @@ -3,73 +3,275 @@ namespace chai { namespace expt { - CopyHidingManager::CopyHidingManager(int hostAllocatorID, - int deviceAllocatorID, - std::size_t size) : - Manager{}, - m_host_allocator_id{hostAllocatorID}, - m_device_allocator_id{deviceAllocatorID}, - m_size{size} + CopyHidingManager::CopyHidingManager(const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator) + : Manager{}, + m_cpu_allocator{cpuAllocator}, + m_gpu_allocator{gpuAllocator} { } + CopyHidingManager::CopyHidingManager(int cpuAllocatorID, + int gpuAllocatorID) + : Manager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_cpu_allocator{m_resource_manager.getAllocator(cpuAllocatorID)}, + m_gpu_allocator{m_resource_manager.getAllocator(gpuAllocatorID)} + { + } + + CopyHidingManager::CopyHidingManager(size_type size) : + : Manager{}, + m_size{size} + { + // TODO: Exception handling + m_cpu_data = m_cpu_allocator.allocate(size); + m_gpu_data = m_gpu_allocator.allocate(size); + } + + CopyHidingManager::CopyHidingManager(size_type size, + const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator) : + : Manager{}, + m_cpu_allocator{cpuAllocator}, + m_gpu_allocator{gpuAllocator}, + m_size{size} + { + // TODO: Exception handling + m_cpu_data = m_cpu_allocator.allocate(size); + m_gpu_data = m_gpu_allocator.allocate(size); + } + + CopyHidingManager::CopyHidingManager(size_type size, + int cpuAllocatorID, + int gpuAllocatorID) : + : Manager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_cpu_allocator{m_resource_manager.getAllocator(cpuAllocatorID)}, + m_gpu_allocator{m_resource_manager.getAllocator(gpuAllocatorID)}, + m_size{size} + { + // TODO: Exception handling + m_cpu_data = m_cpu_allocator.allocate(size); + m_gpu_data = m_gpu_allocator.allocate(size); + } + + CopyHidingManager::CopyHidingManager(const CopyHidingManager& other) + : Manager{}, + m_cpu_allocator{other.m_cpu_allocator}, + m_gpu_allocator{other.m_gpu_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch} + { + if (other.m_cpu_data) + { + m_cpu_data = m_cpu_allocator.allocate(m_size); + m_resourceManager.copy(m_cpu_data, other.m_cpu_data, m_size); + } + + if (other.m_gpu_data) + { + m_gpu_data = m_gpu_allocator.allocate(m_size); + m_resourceManager.copy(m_gpu_data, other.m_gpu_data, m_size); + } + } + + CopyHidingManager::CopyHidingManager(CopyHidingManager&& other) noexcept + : Manager{}, + m_cpu_allocator{other.m_cpu_allocator}, + m_gpu_allocator{other.m_gpu_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch}, + m_cpu_data{other.m_cpu_data}, + m_gpu_data{other.m_gpu_data} + { + other.m_size = 0; + other.m_cpu_data = nullptr; + other.m_gpu_data = nullptr; + other.m_touch = ExecutionContext::NONE; + } + CopyHidingManager::~CopyHidingManager() { - umpire::ResourceManager::getInstance().getAllocator(m_device_allocator_id).deallocate(m_device_data); - umpire::ResourceManager::getInstance().getAllocator(m_host_allocator_id).deallocate(m_host_data); + m_cpu_allocator.deallocate(m_cpu_data); + m_gpu_allocator.deallocate(m_gpu_data); } - size_t CopyHidingManager::size() const { + CopyHidingManager& CopyHidingManager::operator=(const CopyHidingManager& other) + { + if (this != &other) + { + // Copy-assign base class if needed (uncomment if Manager is copy-assignable) + // Manager::operator=(other); + + // Copy-assign or copy members + m_cpu_allocator = other.m_cpu_allocator; + m_gpu_allocator = other.m_gpu_allocator; + m_touch = other.m_touch; + + // Allocate new resources before releasing old ones for strong exception safety + void* new_cpu_data = nullptr; + void* new_gpu_data = nullptr; + + if (other.m_cpu_data) + { + new_cpu_data = m_cpu_allocator.allocate(other.m_size); + m_resourceManager.copy(new_cpu_data, other.m_cpu_data, other.m_size); + } + + if (other.m_gpu_data) + { + new_gpu_data = m_gpu_allocator.allocate(other.m_size); + m_resourceManager.copy(new_gpu_data, other.m_gpu_data, other.m_size); + } + + // Clean up old resources + if (m_cpu_data) + { + m_cpu_allocator.deallocate(m_cpu_data, m_size); + } + + if (m_gpu_data) + { + m_gpu_allocator.deallocate(m_gpu_data, m_size); + } + + // Assign new resources and size + m_cpu_data = new_cpu_data; + m_gpu_data = new_gpu_data; + m_size = other.m_size; + } + + return *this; + } + + CopyHidingManager& CopyHidingManager::operator=(CopyHidingManager&& other) noexcept + { + if (this != &other) + { + // Release any resources currently held + if (m_cpu_data) + { + m_cpu_allocator.deallocate(m_cpu_data, m_size); + m_cpu_data = nullptr; + } + if (m_gpu_data) + { + m_gpu_allocator.deallocate(m_gpu_data, m_size); + m_gpu_data = nullptr; + } + + // Move-assign base class if needed (uncomment if Manager is move-assignable) + // Manager::operator=(std::move(other)); + + // Move-assign or copy members + m_cpu_allocator = other.m_cpu_allocator; + m_gpu_allocator = other.m_gpu_allocator; + m_size = other.m_size; + m_cpu_data = other.m_cpu_data; + m_gpu_data = other.m_gpu_data; + m_touch = other.m_touch; + + // Null out other's pointers and reset size + other.m_cpu_data = nullptr; + other.m_gpu_data = nullptr; + other.m_size = 0; + other.m_touch = ExecutionContext::NONE; + } + return *this; + } + + void CopyHidingManager::resize(size_type newSize) + { + if (newSize != m_size) + { + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.reallocate(m_cpu_pointer, newSize); + + if (m_gpu_pointer) + { + m_resource_manager.deallocate(m_gpu_pointer); + m_gpu_pointer = m_gpu_allocator.allocate(newSize); + } + } + else if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.reallocate(m_gpu_pointer, newSize); + + if (m_cpu_pointer) + { + m_resource_manager.deallocate(m_cpu_pointer); + m_cpu_pointer = m_cpu_allocator.allocate(newSize); + } + } + else + { + if (m_gpu_pointer) + { + m_resource_manager.reallocate(m_gpu_pointer, newSize); + } + + if (m_cpu_pointer) + { + m_resource_manager.reallocate(m_cpu_pointer, newSize); + } + } + } + } + + size_type CopyHidingManager::size() const + { return m_size; } - void* CopyHidingManager::data(ExecutionContext context, bool touch) { - if (context == ExecutionContext::HOST) { - if (!m_host_data) { - m_host_data = umpire::ResourceManager::getInstance().getAllocator(m_host_allocator_id).allocate(m_size); + void* CopyHidingManager::data(bool touch) + { + ExecutionContext context = ExecutionContextManager::getCurrentContext(); + + if (context == ExecutionContext::CPU) + { + if (!m_cpu_data) + { + m_cpu_data = m_cpu_allocator.allocate(m_size); } - if (m_touch == ExecutionContext::DEVICE) { - umpire::ResourceManager::getInstance().copy(m_host_data, m_device_data, m_size); + if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.copy(m_cpu_data, m_gpu_data, m_size); m_touch = ExecutionContext::NONE; } - if (touch) { - m_touch = ExecutionContext::HOST; + if (touch) + { + m_touch = ExecutionContext::CPU; } - return m_host_data; + return m_cpu_data; } - else if (context == ExecutionContext::DEVICE) { - if (!m_device_data) { - m_device_data = umpire::ResourceManager::getInstance().getAllocator(m_device_allocator_id).allocate(m_size); + else if (context == ExecutionContext::GPU) + { + if (!m_gpu_data) + { + m_gpu_data = m_gpu_allocator.allocate(m_size); } - if (m_touch == ExecutionContext::HOST) { - umpire::ResourceManager::getInstance().copy(m_device_data, m_host_data, m_size); + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.copy(m_gpu_data, m_cpu_data, m_size); m_touch = ExecutionContext::NONE; } - if (touch) { - m_touch = ExecutionContext::DEVICE; + if (touch) + { + m_touch = ExecutionContext::GPU; } - return m_device_data; + return m_gpu_data; } - else { + else + { return nullptr; } } - - int CopyHidingManager::getHostAllocatorID() const { - return m_host_allocator_id; - } - - int CopyHidingManager::getDeviceAllocatorID() const { - return m_device_allocator_id; - } - - ExecutionContext CopyHidingManager::getTouch() const { - return m_touch; - } } // namespace expt } // namespace chai diff --git a/src/chai/expt/CopyHidingManager.hpp b/src/chai/expt/CopyHidingManager.hpp index 99385b0b..60eba2ae 100644 --- a/src/chai/expt/CopyHidingManager.hpp +++ b/src/chai/expt/CopyHidingManager.hpp @@ -2,6 +2,7 @@ #define CHAI_COPY_HIDING_MANAGER_HPP #include "chai/expt/Manager.hpp" +#include "umpire/ResourceManager.hpp" namespace chai { namespace expt { @@ -13,21 +14,55 @@ namespace expt { class CopyHidingManager : public Manager { public: /*! - * \brief Constructs a host array manager. + * Constructs a CopyHidingManager with default allocators from Umpire + * for the "HOST" and "DEVICE" resources. */ - CopyHidingManager(int hostAllocatorID, - int deviceAllocatorID, - std::size_t size); + CopyHidingManager() = default; /*! - * \brief Copy constructor is deleted. + * Constructs a CopyHidingManager with the given Umpire allocators. */ - CopyHidingManager(const CopyHidingManager&) = delete; + CopyHidingManager(const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator); /*! - * \brief Copy assignment operator is deleted. + * Constructs a CopyHidingManager with the given Umpire allocator IDs. */ - CopyHidingManager& operator=(const CopyHidingManager&) = delete; + CopyHidingManager(int cpuAllocatorID, + int gpuAllocatorID); + + /*! + * Constructs a CopyHidingManager with the given size using default allocators + * from Umpire for the "HOST" and "DEVICE" resources. + */ + CopyHidingManager(size_type size); + + /*! + * Constructs a CopyHidingManager with the given size using the given Umpire + * allocators. + */ + CopyHidingManager(size_type size, + const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator); + + /*! + * Constructs a CopyHidingManager with the given size using the given Umpire + * allocator IDs. + */ + CopyHidingManager(size_type size, + int cpuAllocatorID, + int gpuAllocatorID); + + /*! + * Constructs a deep copy of the given CopyHidingManager. + */ + CopyHidingManager(const CopyHidingManager& other); + + /*! + * Constructs a CopyHidingManager that takes ownership of the + * resources from the given CopyHidingManager. + */ + CopyHidingManager(CopyHidingManager&& other) noexcept; /*! * \brief Virtual destructor. @@ -35,38 +70,37 @@ namespace expt { virtual ~CopyHidingManager(); /*! - * \brief Get the number of elements. + * \brief Copy assignment operator. */ - virtual std::size_t size() const override; + CopyHidingManager& operator=(const CopyHidingManager& other); /*! - * \brief Updates the data to be coherent in the current execution space. - * - * \param data [out] A coherent array in the current execution space. + * \brief Move assignment operator. */ - virtual void* data(ExecutionContext context, bool touch) override; + CopyHidingManager& operator=(CopyHidingManager&& other); /*! - * \brief Get the host allocator ID. + * \brief Resize the underlying arrays. */ - int getHostAllocatorID() const; + virtual void resize(size_type newSize) override; /*! - * \brief Get the device allocator ID. + * \brief Get the size of the underlying arrays. */ - int getDeviceAllocatorID() const; + virtual void size() const override; /*! - * \brief Get the last touch. + * \brief Updates the data to be coherent in the current execution space. */ - ExecutionContext getTouch() const; + virtual void* data(bool touch) override; private: - int m_host_allocator_id{-1}; - int m_device_allocator_id{-1}; - std::size_t m_size{0}; - void* m_host_data{nullptr}; - void* m_device_data{nullptr}; + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_cpu_allocator{m_resource_manager.getAllocator("HOST")}; + umpire::Allocator m_gpu_allocator{m_resource_manager.getAllocator("DEVICE")}; + size_type m_size{0}; + void* m_cpu_data{nullptr}; + void* m_gpu_data{nullptr}; ExecutionContext m_touch{ExecutionContext::NONE}; }; // class CopyHidingManager } // namespace expt From e9aa9eb97371206645323d71e448a79f0cf1efb2 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 11 Aug 2025 15:51:03 -0700 Subject: [PATCH 18/49] Rename CopyHidingManager to CopyHidingArrayManager --- ...Manager.cpp => CopyHidingArrayManager.cpp} | 0 ...Manager.hpp => CopyHidingArrayManager.hpp} | 52 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) rename src/chai/expt/{CopyHidingManager.cpp => CopyHidingArrayManager.cpp} (100%) rename src/chai/expt/{CopyHidingManager.hpp => CopyHidingArrayManager.hpp} (54%) diff --git a/src/chai/expt/CopyHidingManager.cpp b/src/chai/expt/CopyHidingArrayManager.cpp similarity index 100% rename from src/chai/expt/CopyHidingManager.cpp rename to src/chai/expt/CopyHidingArrayManager.cpp diff --git a/src/chai/expt/CopyHidingManager.hpp b/src/chai/expt/CopyHidingArrayManager.hpp similarity index 54% rename from src/chai/expt/CopyHidingManager.hpp rename to src/chai/expt/CopyHidingArrayManager.hpp index 60eba2ae..01c86e67 100644 --- a/src/chai/expt/CopyHidingManager.hpp +++ b/src/chai/expt/CopyHidingArrayManager.hpp @@ -1,5 +1,5 @@ -#ifndef CHAI_COPY_HIDING_MANAGER_HPP -#define CHAI_COPY_HIDING_MANAGER_HPP +#ifndef CHAI_COPY_HIDING_ARRAY_MANAGER_HPP +#define CHAI_COPY_HIDING_ARRAY_MANAGER_HPP #include "chai/expt/Manager.hpp" #include "umpire/ResourceManager.hpp" @@ -7,77 +7,77 @@ namespace chai { namespace expt { /*! - * \class CopyHidingManager + * \class CopyHidingArrayManager * * \brief Controls the coherence of an array on the host and device. */ - class CopyHidingManager : public Manager { + class CopyHidingArrayManager : public Manager { public: /*! - * Constructs a CopyHidingManager with default allocators from Umpire + * Constructs a CopyHidingArrayManager with default allocators from Umpire * for the "HOST" and "DEVICE" resources. */ - CopyHidingManager() = default; + CopyHidingArrayManager() = default; /*! - * Constructs a CopyHidingManager with the given Umpire allocators. + * Constructs a CopyHidingArrayManager with the given Umpire allocators. */ - CopyHidingManager(const umpire::Allocator& cpuAllocator, + CopyHidingArrayManager(const umpire::Allocator& cpuAllocator, const umpire::Allocator& gpuAllocator); /*! - * Constructs a CopyHidingManager with the given Umpire allocator IDs. + * Constructs a CopyHidingArrayManager with the given Umpire allocator IDs. */ - CopyHidingManager(int cpuAllocatorID, + CopyHidingArrayManager(int cpuAllocatorID, int gpuAllocatorID); /*! - * Constructs a CopyHidingManager with the given size using default allocators + * Constructs a CopyHidingArrayManager with the given size using default allocators * from Umpire for the "HOST" and "DEVICE" resources. */ - CopyHidingManager(size_type size); + CopyHidingArrayManager(size_type size); /*! - * Constructs a CopyHidingManager with the given size using the given Umpire + * Constructs a CopyHidingArrayManager with the given size using the given Umpire * allocators. */ - CopyHidingManager(size_type size, + CopyHidingArrayManager(size_type size, const umpire::Allocator& cpuAllocator, const umpire::Allocator& gpuAllocator); /*! - * Constructs a CopyHidingManager with the given size using the given Umpire + * Constructs a CopyHidingArrayManager with the given size using the given Umpire * allocator IDs. */ - CopyHidingManager(size_type size, + CopyHidingArrayManager(size_type size, int cpuAllocatorID, int gpuAllocatorID); /*! - * Constructs a deep copy of the given CopyHidingManager. + * Constructs a deep copy of the given CopyHidingArrayManager. */ - CopyHidingManager(const CopyHidingManager& other); + CopyHidingArrayManager(const CopyHidingArrayManager& other); /*! - * Constructs a CopyHidingManager that takes ownership of the - * resources from the given CopyHidingManager. + * Constructs a CopyHidingArrayManager that takes ownership of the + * resources from the given CopyHidingArrayManager. */ - CopyHidingManager(CopyHidingManager&& other) noexcept; + CopyHidingArrayManager(CopyHidingArrayManager&& other) noexcept; /*! * \brief Virtual destructor. */ - virtual ~CopyHidingManager(); + virtual ~CopyHidingArrayManager(); /*! * \brief Copy assignment operator. */ - CopyHidingManager& operator=(const CopyHidingManager& other); + CopyHidingArrayManager& operator=(const CopyHidingArrayManager& other); /*! * \brief Move assignment operator. */ - CopyHidingManager& operator=(CopyHidingManager&& other); + CopyHidingArrayManager& operator=(CopyHidingArrayManager&& other); /*! * \brief Resize the underlying arrays. @@ -102,8 +102,8 @@ namespace expt { void* m_cpu_data{nullptr}; void* m_gpu_data{nullptr}; ExecutionContext m_touch{ExecutionContext::NONE}; - }; // class CopyHidingManager + }; // class CopyHidingArrayManager } // namespace expt } // namespace chai -#endif // CHAI_COPY_HIDING_MANAGER_HPP +#endif // CHAI_COPY_HIDING_ARRAY_MANAGER_HPP From c2f28066178304b2ca433c52d4846799c72ad1e3 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 11 Aug 2025 15:58:13 -0700 Subject: [PATCH 19/49] Update Array implementation --- src/chai/expt/Array.hpp | 45 ++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/chai/expt/Array.hpp b/src/chai/expt/Array.hpp index e61a57f8..9107f990 100644 --- a/src/chai/expt/Array.hpp +++ b/src/chai/expt/Array.hpp @@ -28,7 +28,7 @@ namespace expt { * * \note The array takes ownership of the manager. */ - Array(Manager* manager) : + explicit Array(Manager* manager) : m_manager{manager} { } @@ -42,13 +42,30 @@ namespace expt { * \note This is a shallow copy. */ CHAI_HOST_DEVICE Array(const Array& other) : - m_size{other.m_size}, m_data{other.m_data}, + m_size{other.m_size}, m_manager{other.m_manager} { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_manager->update(m_data, !std::is_const::value); + m_data = static_cast(m_manager->data(!std::is_const::value)); + } +#endif + } + + void setManager(Manager* manager) + { + delete m_manager; + m_manager = manager; + } + + void resize(size_t newSize) { + if (m_manager) { + m_size = newSize; + m_manager->resize(newSize); + } + else { + throw std::runtime_exception("Unable to resize"); } } @@ -59,12 +76,13 @@ namespace expt { * of this array (since copies are shallow). */ void free() { - m_size = 0; m_data = nullptr; + m_size = 0; delete m_manager; m_manager = nullptr; } + /*! * \brief Get the number of elements in the array. * @@ -75,6 +93,15 @@ namespace expt { return m_size; } + CHAI_HOST_DEVICE T* data() const { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(!std::is_const::value)); + } +#endif + return m_data; + } + /*! * \brief Get the ith element in the array. * @@ -89,14 +116,14 @@ namespace expt { private: /*! - * The number of elements in the array. + * The array that is coherent in the current execution space. */ - size_t m_size = 0; + T* m_data = nullptr; /*! - * The array that is coherent in the current execution space. + * The number of elements in the array. */ - T* m_data = nullptr; + size_t m_size = 0; /*! * The array manager controls the coherence of the array. @@ -112,7 +139,7 @@ namespace expt { * * \param args The arguments to construct an array manager. */ - template + template Array makeArray(Args&&... args) { return Array(new Manager(std::forward(args)...)); } From 7cd54a3d7857beb16978cd7471080872e36f2c9f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 11 Aug 2025 16:01:51 -0700 Subject: [PATCH 20/49] Simplify Manager interface --- src/chai/expt/Manager.cpp | 10 ---------- src/chai/expt/Manager.hpp | 15 +++++---------- 2 files changed, 5 insertions(+), 20 deletions(-) delete mode 100644 src/chai/expt/Manager.cpp diff --git a/src/chai/expt/Manager.cpp b/src/chai/expt/Manager.cpp deleted file mode 100644 index a369f08c..00000000 --- a/src/chai/expt/Manager.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "chai/expt/Manager.hpp" - -namespace chai { -namespace expt { - ExecutionContext& Manager::execution_context() { - static thread_local ExecutionContext s_context = ExecutionContext::NONE; - return s_context; - } -} // namespace expt -} // namespace chai diff --git a/src/chai/expt/Manager.hpp b/src/chai/expt/Manager.hpp index d7f7db7c..c70e1f0e 100644 --- a/src/chai/expt/Manager.hpp +++ b/src/chai/expt/Manager.hpp @@ -1,8 +1,6 @@ #ifndef CHAI_MANAGER_HPP #define CHAI_MANAGER_HPP -#include "chai/expt/ExecutionContext.hpp" - #include namespace chai { @@ -14,27 +12,24 @@ namespace expt { */ class Manager { public: + using size_type = std::size_t; + /*! * \brief Virtual destructor. */ virtual ~Manager() = default; /*! - * \brief Get the number of elements. + * \brief Resize the contained array. */ - virtual std::size_t size() const = 0; + virtual void resize(size_type newSize) = 0; /*! * \brief Updates the data to be coherent in the current execution context. * * \param data [out] A coherent array in the current execution context. */ - virtual void* data(ExecutionContext context, bool touch) = 0; - - /*! - * \brief Returns a modifiable reference to the current execution context. - */ - static ExecutionContext& execution_context(); + virtual void* data(bool touch) = 0; }; // class Manager } // namespace expt } // namespace chai From 829fbb8aaaca46c3b4451ad4d087992c18e13618 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 12 Aug 2025 11:07:03 -0700 Subject: [PATCH 21/49] Clean up CopyHidingArrayManager --- src/chai/expt/CopyHidingArrayManager.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/chai/expt/CopyHidingArrayManager.hpp b/src/chai/expt/CopyHidingArrayManager.hpp index 01c86e67..95faed6a 100644 --- a/src/chai/expt/CopyHidingArrayManager.hpp +++ b/src/chai/expt/CopyHidingArrayManager.hpp @@ -23,13 +23,13 @@ namespace expt { * Constructs a CopyHidingArrayManager with the given Umpire allocators. */ CopyHidingArrayManager(const umpire::Allocator& cpuAllocator, - const umpire::Allocator& gpuAllocator); + const umpire::Allocator& gpuAllocator); /*! * Constructs a CopyHidingArrayManager with the given Umpire allocator IDs. */ CopyHidingArrayManager(int cpuAllocatorID, - int gpuAllocatorID); + int gpuAllocatorID); /*! * Constructs a CopyHidingArrayManager with the given size using default allocators @@ -42,16 +42,16 @@ namespace expt { * allocators. */ CopyHidingArrayManager(size_type size, - const umpire::Allocator& cpuAllocator, - const umpire::Allocator& gpuAllocator); + const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator); /*! * Constructs a CopyHidingArrayManager with the given size using the given Umpire * allocator IDs. */ CopyHidingArrayManager(size_type size, - int cpuAllocatorID, - int gpuAllocatorID); + int cpuAllocatorID, + int gpuAllocatorID); /*! * Constructs a deep copy of the given CopyHidingArrayManager. From 46d7f40670a8f2f12e83c170e4c6b93d85a2e803 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 12 Aug 2025 11:09:51 -0700 Subject: [PATCH 22/49] Add test file for CopyHidingArrayManager --- tests/unit/expt/TestCopyHidingArrayManager.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/unit/expt/TestCopyHidingArrayManager.cpp diff --git a/tests/unit/expt/TestCopyHidingArrayManager.cpp b/tests/unit/expt/TestCopyHidingArrayManager.cpp new file mode 100644 index 00000000..f4217177 --- /dev/null +++ b/tests/unit/expt/TestCopyHidingArrayManager.cpp @@ -0,0 +1,11 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" + +#include "chai/config.hpp" +#include "chai/expt/CopyHidingManager.hpp" +#include "umpire/ResourceManager.hpp" \ No newline at end of file From 1f89b4c47374ba0a7db55ab31ba64bc3999bc4a5 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 14:19:38 -0700 Subject: [PATCH 23/49] Add more functionality to Array --- src/chai/expt/Array.hpp | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/chai/expt/Array.hpp b/src/chai/expt/Array.hpp index 9107f990..48c94159 100644 --- a/src/chai/expt/Array.hpp +++ b/src/chai/expt/Array.hpp @@ -2,6 +2,7 @@ #define CHAI_ARRAY_HPP #include "chai/Manager.hpp" +#include namespace chai { namespace expt { @@ -31,6 +32,23 @@ namespace expt { explicit Array(Manager* manager) : m_manager{manager} { + if (m_manager) + { + m_size = m_manager->size(); + } + } + + Array(std::size_t size, Manager* manager) : + m_size{size} + m_manager{manager} + { + if (m_manager) { + m_size = newSize; + m_manager->resize(newSize); + } + else { + throw std::runtime_exception("Unable to resize"); + } } /*! @@ -48,7 +66,7 @@ namespace expt { { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_data = static_cast(m_manager->data(!std::is_const::value)); + m_data = static_cast(m_manager->data(ContextManager::getInstance()::getContext(), !std::is_const::value)); } #endif } @@ -59,7 +77,7 @@ namespace expt { m_manager = manager; } - void resize(size_t newSize) { + void resize(std::size_t newSize) { if (m_manager) { m_size = newSize; m_manager->resize(newSize); @@ -89,14 +107,23 @@ namespace expt { * \pre The copy constructor has been called with the execution space * set to CPU or GPU (e.g. by the RAJA plugin). */ - CHAI_HOST_DEVICE size_t size() const { + CHAI_HOST_DEVICE std::size_t size() const { return m_size; } CHAI_HOST_DEVICE T* data() const { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_data = static_cast(m_manager->data(!std::is_const::value)); + m_data = static_cast(m_manager->data(ExecutionContext::HOST, !std::is_const::value)); + } +#endif + return m_data; + } + + CHAI_HOST_DEVICE T* data(ExecutionContext context) const { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(context, !std::is_const::value)); } #endif return m_data; @@ -110,7 +137,7 @@ namespace expt { * \pre The copy constructor has been called with the execution space * set to CPU or GPU (e.g. by the RAJA plugin). */ - CHAI_HOST_DEVICE T& operator[](size_t i) const { + CHAI_HOST_DEVICE T& operator[](std::size_t i) const { return m_data[i]; } @@ -123,7 +150,7 @@ namespace expt { /*! * The number of elements in the array. */ - size_t m_size = 0; + std::size_t m_size = 0; /*! * The array manager controls the coherence of the array. From 5233f7b2218fcbf7b496ad3111d779172e2afbb9 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 15:10:30 -0700 Subject: [PATCH 24/49] Add host device implementation of std::span --- src/chai/expt/span.hpp | 96 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/chai/expt/span.hpp diff --git a/src/chai/expt/span.hpp b/src/chai/expt/span.hpp new file mode 100644 index 00000000..cd9eec59 --- /dev/null +++ b/src/chai/expt/span.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include + +namespace custom { + +template +class span { +public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = pointer; + using const_iterator = const_pointer; + + // Constructors + __host__ __device__ constexpr span() noexcept : data_(nullptr), size_(0) {} + + __host__ __device__ constexpr span(pointer ptr, size_type count) noexcept + : data_(ptr), size_(count) {} + + template + __host__ __device__ constexpr span(element_type (&arr)[N]) noexcept + : data_(arr), size_(N) {} + + // Element access + __host__ __device__ constexpr reference operator[](size_type idx) const noexcept { + return data_[idx]; + } + + __host__ __device__ constexpr reference front() const noexcept { + return data_[0]; + } + + __host__ __device__ constexpr reference back() const noexcept { + return data_[size_ - 1]; + } + + __host__ __device__ constexpr pointer data() const noexcept { + return data_; + } + + // Iterators + __host__ __device__ constexpr iterator begin() const noexcept { + return data_; + } + + __host__ __device__ constexpr iterator end() const noexcept { + return data_ + size_; + } + + // Capacity + __host__ __device__ constexpr bool empty() const noexcept { + return size_ == 0; + } + + __host__ __device__ constexpr size_type size() const noexcept { + return size_; + } + + __host__ __device__ constexpr size_type size_bytes() const noexcept { + return size_ * sizeof(element_type); + } + + // Subviews + __host__ __device__ constexpr span first(size_type count) const { + return {data_, count}; + } + + __host__ __device__ constexpr span last(size_type count) const { + return {data_ + (size_ - count), count}; + } + + __host__ __device__ constexpr span subspan(size_type offset, size_type count) const { + return {data_ + offset, count}; + } + +private: + pointer data_; + size_type size_; +}; + +// Deduction guides +template +span(T (&)[N]) -> span; + +template +span(T*, std::size_t) -> span; + +} // namespace custom \ No newline at end of file From e133d05ea2350c222c13a292c11425a47ee5e42b Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 15:30:31 -0700 Subject: [PATCH 25/49] Rename Manager to ArrayManager --- src/chai/expt/ArrayManager.hpp | 52 ++++++++++++++++++++++++++++++++++ src/chai/expt/Manager.hpp | 37 ------------------------ 2 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 src/chai/expt/ArrayManager.hpp delete mode 100644 src/chai/expt/Manager.hpp diff --git a/src/chai/expt/ArrayManager.hpp b/src/chai/expt/ArrayManager.hpp new file mode 100644 index 00000000..25ea6f39 --- /dev/null +++ b/src/chai/expt/ArrayManager.hpp @@ -0,0 +1,52 @@ +#ifndef CHAI_ARRAY_MANAGER_HPP +#define CHAI_ARRAY_MANAGER_HPP + +#include + +namespace chai { +namespace expt { + /*! + * \class ArrayManager + * + * \brief Controls the coherence of an array. + */ + template + class ArrayManager { + public: + /*! + * \brief Virtual destructor. + */ + virtual ~ArrayManager() = default; + + /*! + * \brief Creates a clone of this ArrayManager. + * + * \return A new ArrayManager object that is a clone of this instance. + */ + virtual ArrayManager* clone() const = 0; + + /*! + * \brief Resizes the array to the specified new size. + * + * \param newSize The new size to resize the array to. + */ + virtual void resize(std::size_t newSize) = 0; + + /*! + * \brief Returns the size of the contained array. + * + * \return The size of the contained array. + */ + virtual std::size_t size() const = 0; + + /*! + * \brief Updates the data to be coherent in the current execution context. + * + * \param data [out] A coherent array in the current execution context. + */ + virtual void* data(ExecutionSpace space, bool touch) const = 0; + }; // class ArrayManager +} // namespace expt +} // namespace chai + +#endif // CHAI_ARRAY_MANAGER_HPP diff --git a/src/chai/expt/Manager.hpp b/src/chai/expt/Manager.hpp deleted file mode 100644 index c70e1f0e..00000000 --- a/src/chai/expt/Manager.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef CHAI_MANAGER_HPP -#define CHAI_MANAGER_HPP - -#include - -namespace chai { -namespace expt { - /*! - * \class Manager - * - * \brief Controls the coherence of an array. - */ - class Manager { - public: - using size_type = std::size_t; - - /*! - * \brief Virtual destructor. - */ - virtual ~Manager() = default; - - /*! - * \brief Resize the contained array. - */ - virtual void resize(size_type newSize) = 0; - - /*! - * \brief Updates the data to be coherent in the current execution context. - * - * \param data [out] A coherent array in the current execution context. - */ - virtual void* data(bool touch) = 0; - }; // class Manager -} // namespace expt -} // namespace chai - -#endif // CHAI_MANAGER_HPP From 88b2c950ff73e688c978400a2d77740aaefac839 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 16:24:00 -0700 Subject: [PATCH 26/49] Add Context enum --- src/chai/expt/Context.hpp | 28 ++++++++++++++++++++++++++++ src/chai/expt/ExecutionContext.hpp | 24 ------------------------ 2 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 src/chai/expt/Context.hpp delete mode 100644 src/chai/expt/ExecutionContext.hpp diff --git a/src/chai/expt/Context.hpp b/src/chai/expt/Context.hpp new file mode 100644 index 00000000..5f2323d1 --- /dev/null +++ b/src/chai/expt/Context.hpp @@ -0,0 +1,28 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_CONTEXT_HPP +#define CHAI_CONTEXT_HPP + +namespace chai { +namespace expt { + /*! + * \enum Context + * + * \brief Represents the state of a program. ArrayManagers update coherence based on the context. + */ + enum Context { + NONE = 0, ///< Represents no context. + HOST ///< Represents the host context (i.e. the CPU). +#if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) + , DEVICE ///< Represents the device context (i.e. the GPU). +#endif + }; +} // namespace expt +} // namespace chai + +#endif // CHAI_CONTEXT_HPP \ No newline at end of file diff --git a/src/chai/expt/ExecutionContext.hpp b/src/chai/expt/ExecutionContext.hpp deleted file mode 100644 index d01af8d4..00000000 --- a/src/chai/expt/ExecutionContext.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef CHAI_EXECUTION_CONTEXT_HPP -#define CHAI_EXECUTION_CONTEXT_HPP - -#include "chai/config.hpp" - -namespace chai { -namespace expt { - /*! - * \brief Enum listing possible execution contexts. - */ - enum class ExecutionContext { - /*! Default, no execution space. */ - NONE = 0, - /*! Executing in CPU space */ - HOST, -#if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) - /*! Executing in GPU space */ - DEVICE -#endif - }; // enum class ExecutionContext -} // namespace expt -} // namespace chai - -#endif // CHAI_EXECUTION_CONTEXT_HPP From 7e10182c6750a9e454427c86ac8b2dfc1be59703 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 16:24:52 -0700 Subject: [PATCH 27/49] Add ContextManager class --- src/chai/expt/ContextManager.hpp | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/chai/expt/ContextManager.hpp diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ContextManager.hpp new file mode 100644 index 00000000..9ea553db --- /dev/null +++ b/src/chai/expt/ContextManager.hpp @@ -0,0 +1,82 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_CONTEXT_MANAGER_HPP +#define CHAI_CONTEXT_MANAGER_HPP + +#include "chai/expt/Context.hpp" + +namespace chai { +namespace expt { + /*! + * \class ContextManager + * + * \brief Singleton class for managing the current execution context. + * + * This class provides a centralized way to get and set the current execution + * context across the application. + */ + class ContextManager { + public: + /*! + * \brief Get the singleton instance of ContextManager. + * + * \return The singleton instance. + */ + static ContextManager& getInstance() { + static inline ContextManager s_instance; + return s_instance; + } + + /*! + * \brief Private copy constructor to prevent copying. + */ + ContextManager(const ContextManager&) = delete; + + /*! + * \brief Private assignment operator to prevent assignment. + */ + ContextManager& operator=(const ContextManager&) = delete; + + /*! + * \brief Get the current execution context. + * + * \return The current context. + */ + Context getContext() const { + return m_current_context; + } + + /*! + * \brief Set the current execution context. + * + * \param context The new context to set. + */ + void setContext(Context context) { + m_current_context = context; + } + + private: + /*! + * \brief Private constructor for singleton pattern. + */ + constexpr ContextManager() noexcept = default; + + /*! + * \brief The current execution context. + */ + Context m_current_context = NONE; + }; +} // namespace expt +} // namespace chai + +#endif // CHAI_CONTEXT_HPP + +} // namespace expt +} // namespace chai + +#endif // CHAI_CONTEXT_MANAGER_HPP From cbd69e2bbf0747f9f008d10b4b27b526463e4293 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 16:56:56 -0700 Subject: [PATCH 28/49] Update ArrayManager interface --- src/chai/expt/ArrayManager.hpp | 30 +++++++++- src/chai/expt/CopyHidingArrayManager.hpp | 71 +++++++++++++++++------- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/chai/expt/ArrayManager.hpp b/src/chai/expt/ArrayManager.hpp index 25ea6f39..6a237a00 100644 --- a/src/chai/expt/ArrayManager.hpp +++ b/src/chai/expt/ArrayManager.hpp @@ -1,6 +1,14 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + #ifndef CHAI_ARRAY_MANAGER_HPP #define CHAI_ARRAY_MANAGER_HPP +#include "chai/expt/Context.hpp" #include namespace chai { @@ -44,7 +52,27 @@ namespace expt { * * \param data [out] A coherent array in the current execution context. */ - virtual void* data(ExecutionSpace space, bool touch) const = 0; + virtual T* data(Context context, bool touch) = 0; + + /*! + * \brief Returns the value at index i. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to get. + * \return The value at index i. + */ + virtual T get(std::size_t i) const = 0; + + /*! + * \brief Sets the value at index i to the specified value. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to set. + * \param value The value to set at index i. + */ + virtual void set(std::size_t i, const T& value) = 0; }; // class ArrayManager } // namespace expt } // namespace chai diff --git a/src/chai/expt/CopyHidingArrayManager.hpp b/src/chai/expt/CopyHidingArrayManager.hpp index 95faed6a..3ef3753f 100644 --- a/src/chai/expt/CopyHidingArrayManager.hpp +++ b/src/chai/expt/CopyHidingArrayManager.hpp @@ -1,7 +1,15 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + #ifndef CHAI_COPY_HIDING_ARRAY_MANAGER_HPP #define CHAI_COPY_HIDING_ARRAY_MANAGER_HPP -#include "chai/expt/Manager.hpp" +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ContextManager.hpp" #include "umpire/ResourceManager.hpp" namespace chai { @@ -11,7 +19,8 @@ namespace expt { * * \brief Controls the coherence of an array on the host and device. */ - class CopyHidingArrayManager : public Manager { + template + class CopyHidingArrayManager : public ArrayManager { public: /*! * Constructs a CopyHidingArrayManager with default allocators from Umpire @@ -22,36 +31,36 @@ namespace expt { /*! * Constructs a CopyHidingArrayManager with the given Umpire allocators. */ - CopyHidingArrayManager(const umpire::Allocator& cpuAllocator, - const umpire::Allocator& gpuAllocator); + CopyHidingArrayManager(const umpire::Allocator& hostAllocator, + const umpire::Allocator& deviceAllocator); /*! * Constructs a CopyHidingArrayManager with the given Umpire allocator IDs. */ - CopyHidingArrayManager(int cpuAllocatorID, - int gpuAllocatorID); + CopyHidingArrayManager(int hostAllocatorID, + int deviceAllocatorID); /*! * Constructs a CopyHidingArrayManager with the given size using default allocators * from Umpire for the "HOST" and "DEVICE" resources. */ - CopyHidingArrayManager(size_type size); + CopyHidingArrayManager(std::size_t size); /*! * Constructs a CopyHidingArrayManager with the given size using the given Umpire * allocators. */ - CopyHidingArrayManager(size_type size, - const umpire::Allocator& cpuAllocator, - const umpire::Allocator& gpuAllocator); + CopyHidingArrayManager(std::size_t size, + const umpire::Allocator& hostAllocator, + const umpire::Allocator& deviceAllocator); /*! * Constructs a CopyHidingArrayManager with the given size using the given Umpire * allocator IDs. */ - CopyHidingArrayManager(size_type size, - int cpuAllocatorID, - int gpuAllocatorID); + CopyHidingArrayManager(std::size_t size, + int hostAllocatorID, + int deviceAllocatorID); /*! * Constructs a deep copy of the given CopyHidingArrayManager. @@ -82,25 +91,45 @@ namespace expt { /*! * \brief Resize the underlying arrays. */ - virtual void resize(size_type newSize) override; + virtual void resize(std::size_t newSize) override; /*! * \brief Get the size of the underlying arrays. */ - virtual void size() const override; + virtual std::size_t size() const override; /*! * \brief Updates the data to be coherent in the current execution space. */ - virtual void* data(bool touch) override; + virtual T* data(Context context, bool touch) override; + + /*! + * \brief Returns the value at index i. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to get. + * \return The value at index i. + */ + virtual T get(std::size_t i) const override; + + /*! + * \brief Sets the value at index i to the specified value. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to set. + * \param value The value to set at index i. + */ + virtual void set(std::size_t i, const T& value) override; private: umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; - umpire::Allocator m_cpu_allocator{m_resource_manager.getAllocator("HOST")}; - umpire::Allocator m_gpu_allocator{m_resource_manager.getAllocator("DEVICE")}; - size_type m_size{0}; - void* m_cpu_data{nullptr}; - void* m_gpu_data{nullptr}; + umpire::Allocator m_host_allocator{m_resource_manager.getAllocator("HOST")}; + umpire::Allocator m_device_allocator{m_resource_manager.getAllocator("DEVICE")}; + std::size_t m_size{0}; + void* m_host_data{nullptr}; + void* m_device_data{nullptr}; ExecutionContext m_touch{ExecutionContext::NONE}; }; // class CopyHidingArrayManager } // namespace expt From d438e7dd6bba7fd002859c99731097c176adb2a1 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 17:05:53 -0700 Subject: [PATCH 29/49] Inline CopyHidingArrayManager implementation --- src/chai/expt/CopyHidingArrayManager.hpp | 275 +++++++++++++++++++++-- 1 file changed, 260 insertions(+), 15 deletions(-) diff --git a/src/chai/expt/CopyHidingArrayManager.hpp b/src/chai/expt/CopyHidingArrayManager.hpp index 3ef3753f..f4241fd1 100644 --- a/src/chai/expt/CopyHidingArrayManager.hpp +++ b/src/chai/expt/CopyHidingArrayManager.hpp @@ -32,19 +32,37 @@ namespace expt { * Constructs a CopyHidingArrayManager with the given Umpire allocators. */ CopyHidingArrayManager(const umpire::Allocator& hostAllocator, - const umpire::Allocator& deviceAllocator); + const umpire::Allocator& deviceAllocator) + : ArrayManager{}, + m_host_allocator{hostAllocator}, + m_device_allocator{deviceAllocator} + { + } /*! * Constructs a CopyHidingArrayManager with the given Umpire allocator IDs. */ CopyHidingArrayManager(int hostAllocatorID, - int deviceAllocatorID); + int deviceAllocatorID) + : ArrayManager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_host_allocator{m_resource_manager.getAllocator(hostAllocatorID)}, + m_device_allocator{m_resource_manager.getAllocator(deviceAllocatorID)} + { + } /*! * Constructs a CopyHidingArrayManager with the given size using default allocators * from Umpire for the "HOST" and "DEVICE" resources. */ - CopyHidingArrayManager(std::size_t size); + CopyHidingArrayManager(std::size_t size) + : ArrayManager{}, + m_size{size} + { + // TODO: Exception handling + m_host_data = m_host_allocator.allocate(size); + m_device_data = m_device_allocator.allocate(size); + } /*! * Constructs a CopyHidingArrayManager with the given size using the given Umpire @@ -52,7 +70,16 @@ namespace expt { */ CopyHidingArrayManager(std::size_t size, const umpire::Allocator& hostAllocator, - const umpire::Allocator& deviceAllocator); + const umpire::Allocator& deviceAllocator) + : ArrayManager{}, + m_host_allocator{hostAllocator}, + m_device_allocator{deviceAllocator}, + m_size{size} + { + // TODO: Exception handling + m_host_data = m_host_allocator.allocate(size); + m_device_data = m_device_allocator.allocate(size); + } /*! * Constructs a CopyHidingArrayManager with the given size using the given Umpire @@ -60,48 +87,258 @@ namespace expt { */ CopyHidingArrayManager(std::size_t size, int hostAllocatorID, - int deviceAllocatorID); + int deviceAllocatorID) + : ArrayManager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_host_allocator{m_resource_manager.getAllocator(hostAllocatorID)}, + m_device_allocator{m_resource_manager.getAllocator(deviceAllocatorID)}, + m_size{size} + { + // TODO: Exception handling + m_host_data = m_host_allocator.allocate(size); + m_device_data = m_device_allocator.allocate(size); + } /*! * Constructs a deep copy of the given CopyHidingArrayManager. */ - CopyHidingArrayManager(const CopyHidingArrayManager& other); + CopyHidingArrayManager(const CopyHidingArrayManager& other) + : ArrayManager{}, + m_host_allocator{other.m_host_allocator}, + m_device_allocator{other.m_device_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch} + { + if (other.m_host_data) + { + m_host_data = m_host_allocator.allocate(m_size); + m_resource_manager.copy(m_host_data, other.m_host_data, m_size); + } + + if (other.m_device_data) + { + m_device_data = m_device_allocator.allocate(m_size); + m_resource_manager.copy(m_device_data, other.m_device_data, m_size); + } + } /*! * Constructs a CopyHidingArrayManager that takes ownership of the * resources from the given CopyHidingArrayManager. */ - CopyHidingArrayManager(CopyHidingArrayManager&& other) noexcept; + CopyHidingArrayManager(CopyHidingArrayManager&& other) noexcept + : ArrayManager{}, + m_host_allocator{other.m_host_allocator}, + m_device_allocator{other.m_device_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch}, + m_host_data{other.m_host_data}, + m_device_data{other.m_device_data} + { + other.m_size = 0; + other.m_host_data = nullptr; + other.m_device_data = nullptr; + other.m_touch = ExecutionContext::NONE; + } /*! * \brief Virtual destructor. */ - virtual ~CopyHidingArrayManager(); + virtual ~CopyHidingArrayManager() + { + if (m_host_data) { + m_host_allocator.deallocate(m_host_data); + } + if (m_device_data) { + m_device_allocator.deallocate(m_device_data); + } + } /*! * \brief Copy assignment operator. */ - CopyHidingArrayManager& operator=(const CopyHidingArrayManager& other); + CopyHidingArrayManager& operator=(const CopyHidingArrayManager& other) + { + if (this != &other) + { + // Copy-assign or copy members + m_host_allocator = other.m_host_allocator; + m_device_allocator = other.m_device_allocator; + m_touch = other.m_touch; + + // Allocate new resources before releasing old ones for strong exception safety + void* new_host_data = nullptr; + void* new_device_data = nullptr; + + if (other.m_host_data) + { + new_host_data = m_host_allocator.allocate(other.m_size); + m_resource_manager.copy(new_host_data, other.m_host_data, other.m_size); + } + + if (other.m_device_data) + { + new_device_data = m_device_allocator.allocate(other.m_size); + m_resource_manager.copy(new_device_data, other.m_device_data, other.m_size); + } + + // Clean up old resources + if (m_host_data) + { + m_host_allocator.deallocate(m_host_data); + } + + if (m_device_data) + { + m_device_allocator.deallocate(m_device_data); + } + + // Assign new resources and size + m_host_data = new_host_data; + m_device_data = new_device_data; + m_size = other.m_size; + } + + return *this; + } /*! * \brief Move assignment operator. */ - CopyHidingArrayManager& operator=(CopyHidingArrayManager&& other); + CopyHidingArrayManager& operator=(CopyHidingArrayManager&& other) noexcept + { + if (this != &other) + { + // Release any resources currently held + if (m_host_data) + { + m_host_allocator.deallocate(m_host_data); + m_host_data = nullptr; + } + if (m_device_data) + { + m_device_allocator.deallocate(m_device_data); + m_device_data = nullptr; + } + + // Move-assign or copy members + m_host_allocator = other.m_host_allocator; + m_device_allocator = other.m_device_allocator; + m_size = other.m_size; + m_host_data = other.m_host_data; + m_device_data = other.m_device_data; + m_touch = other.m_touch; + + // Null out other's pointers and reset size + other.m_host_data = nullptr; + other.m_device_data = nullptr; + other.m_size = 0; + other.m_touch = ExecutionContext::NONE; + } + return *this; + } /*! * \brief Resize the underlying arrays. */ - virtual void resize(std::size_t newSize) override; + virtual void resize(std::size_t newSize) override + { + if (newSize != m_size) + { + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.reallocate(m_host_data, newSize); + + if (m_device_data) + { + m_resource_manager.deallocate(m_device_data); + m_device_data = m_device_allocator.allocate(newSize); + } + } + else if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.reallocate(m_device_data, newSize); + + if (m_host_data) + { + m_resource_manager.deallocate(m_host_data); + m_host_data = m_host_allocator.allocate(newSize); + } + } + else + { + if (m_device_data) + { + m_resource_manager.reallocate(m_device_data, newSize); + } + + if (m_host_data) + { + m_resource_manager.reallocate(m_host_data, newSize); + } + } + m_size = newSize; + } + } /*! * \brief Get the size of the underlying arrays. */ - virtual std::size_t size() const override; + virtual std::size_t size() const override + { + return m_size; + } /*! * \brief Updates the data to be coherent in the current execution space. */ - virtual T* data(Context context, bool touch) override; + virtual ElementT* data(Context context, bool touch) override + { + if (context == ExecutionContext::CPU) + { + if (!m_host_data) + { + m_host_data = m_host_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.copy(m_host_data, m_device_data, m_size); + m_touch = ExecutionContext::NONE; + } + + if (touch) + { + m_touch = ExecutionContext::CPU; + } + + return static_cast(m_host_data); + } + else if (context == ExecutionContext::GPU) + { + if (!m_device_data) + { + m_device_data = m_device_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.copy(m_device_data, m_host_data, m_size); + m_touch = ExecutionContext::NONE; + } + + if (touch) + { + m_touch = ExecutionContext::GPU; + } + + return static_cast(m_device_data); + } + else + { + return nullptr; + } + } /*! * \brief Returns the value at index i. @@ -111,7 +348,12 @@ namespace expt { * \param i The index of the element to get. * \return The value at index i. */ - virtual T get(std::size_t i) const override; + virtual ElementT get(std::size_t i) const override + { + // Implementation needed + // For now, just returning a default value + return ElementT{}; + } /*! * \brief Sets the value at index i to the specified value. @@ -121,7 +363,10 @@ namespace expt { * \param i The index of the element to set. * \param value The value to set at index i. */ - virtual void set(std::size_t i, const T& value) override; + virtual void set(std::size_t i, const ElementT& value) override + { + // Implementation needed + } private: umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; From eaf272ae08365a6a051f6a91f27ef3a101664005 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 17:24:29 -0700 Subject: [PATCH 30/49] Rename Array to ManagedArray --- src/chai/expt/{Array.hpp => ManagedArray.hpp} | 105 ++++++++++-------- 1 file changed, 61 insertions(+), 44 deletions(-) rename src/chai/expt/{Array.hpp => ManagedArray.hpp} (56%) diff --git a/src/chai/expt/Array.hpp b/src/chai/expt/ManagedArray.hpp similarity index 56% rename from src/chai/expt/Array.hpp rename to src/chai/expt/ManagedArray.hpp index 48c94159..7912349b 100644 --- a/src/chai/expt/Array.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -1,26 +1,27 @@ -#ifndef CHAI_ARRAY_HPP -#define CHAI_ARRAY_HPP +#ifndef CHAI_MANAGED_ARRAY_HPP +#define CHAI_MANAGED_ARRAY_HPP -#include "chai/Manager.hpp" +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ContextManager.hpp" #include namespace chai { namespace expt { /*! - * \class Array + * \class ManagedArray * * \brief An array class that manages coherency across the CPU and GPU. * How the coherence is obtained is controlled by the array manager. * - * \tparam T The type of element in the array. + * \tparam ElementT The type of element in the array. */ - template - class Array { + template + class ManagedArray { public: /*! * \brief Constructs an empty array without an array manager. */ - Array() = default; + ManagedArray() = default; /*! * \brief Constructs an array from a manager. @@ -29,22 +30,22 @@ namespace expt { * * \note The array takes ownership of the manager. */ - explicit Array(Manager* manager) : - m_manager{manager} + explicit ManagedArray(ArrayManager* manager) : + m_array_manager{manager} { - if (m_manager) + if (m_array_manager) { - m_size = m_manager->size(); + m_size = m_array_manager->size(); } } - Array(std::size_t size, Manager* manager) : + ManagedArray(std::size_t size, ArrayManager* manager) : m_size{size} - m_manager{manager} + m_array_manager{manager} { - if (m_manager) { + if (m_array_manager) { m_size = newSize; - m_manager->resize(newSize); + m_array_manager->resize(newSize); } else { throw std::runtime_exception("Unable to resize"); @@ -59,28 +60,28 @@ namespace expt { * * \note This is a shallow copy. */ - CHAI_HOST_DEVICE Array(const Array& other) : + CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) : m_data{other.m_data}, m_size{other.m_size}, - m_manager{other.m_manager} + m_array_manager{other.m_array_manager} { #if !defined(CHAI_DEVICE_COMPILE) - if (m_manager) { - m_data = static_cast(m_manager->data(ContextManager::getInstance()::getContext(), !std::is_const::value)); + if (m_array_manager) { + m_data = m_array_manager->data(ContextManager::getInstance()::getContext(), !std::is_const::value)); } #endif } - void setManager(Manager* manager) + void setManager(ArrayManager* manager) { - delete m_manager; - m_manager = manager; + delete m_array_manager; + m_array_manager = manager; } void resize(std::size_t newSize) { - if (m_manager) { + if (m_array_manager) { m_size = newSize; - m_manager->resize(newSize); + m_array_manager->resize(newSize); } else { throw std::runtime_exception("Unable to resize"); @@ -96,8 +97,8 @@ namespace expt { void free() { m_data = nullptr; m_size = 0; - delete m_manager; - m_manager = nullptr; + delete m_array_manager; + m_array_manager = nullptr; } @@ -111,24 +112,36 @@ namespace expt { return m_size; } - CHAI_HOST_DEVICE T* data() const { + CHAI_HOST_DEVICE ElementT* data() const { #if !defined(CHAI_DEVICE_COMPILE) - if (m_manager) { - m_data = static_cast(m_manager->data(ExecutionContext::HOST, !std::is_const::value)); - } + return data(HOST); #endif return m_data; } - CHAI_HOST_DEVICE T* data(ExecutionContext context) const { + CHAI_HOST_DEVICE const ElementT* cdata() const { #if !defined(CHAI_DEVICE_COMPILE) - if (m_manager) { - m_data = static_cast(m_manager->data(context, !std::is_const::value)); - } + return cdata(HOST); #endif return m_data; } + ElementT* data(Context context) const { + if (m_array_manager) { + m_data = m_array_manager->data(context, !std::is_const::value); + } + + return m_data; + } + + const ElementT* cdata(Context context) const { + if (m_array_manager) { + m_data = m_array_manager->data(context, false); + } + + return m_data; + } + /*! * \brief Get the ith element in the array. * @@ -137,15 +150,19 @@ namespace expt { * \pre The copy constructor has been called with the execution space * set to CPU or GPU (e.g. by the RAJA plugin). */ - CHAI_HOST_DEVICE T& operator[](std::size_t i) const { + CHAI_HOST_DEVICE ElementT& operator[](std::size_t i) const { return m_data[i]; } + ArrayManager* manager() const { + return m_array_manager; + } + private: /*! * The array that is coherent in the current execution space. */ - T* m_data = nullptr; + ElementT* m_data = nullptr; /*! * The number of elements in the array. @@ -155,22 +172,22 @@ namespace expt { /*! * The array manager controls the coherence of the array. */ - Manager* m_manager = nullptr; - }; // class Array + ArrayManager* m_array_manager = nullptr; + }; // class ManagedArray /*! * \brief Constructs an array by creating a new manager object. * - * \tparam Manager The type of array manager. + * \tparam ArrayManager The type of array manager. * \tparam Args The type of the arguments used to construct the array manager. * * \param args The arguments to construct an array manager. */ - template - Array makeArray(Args&&... args) { - return Array(new Manager(std::forward(args)...)); + template , typename... Args> + ManagedArray makeArray(Args&&... args) { + return ManagedArray(new ArrayManager(std::forward(args)...)); } } // namespace expt } // namespace chai -#endif // CHAI_ARRAY_HPP +#endif // CHAI_MANAGED_ARRAY_HPP From f6e6d351cedcd52e3055a63323c5669c62949b24 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 17:32:38 -0700 Subject: [PATCH 31/49] Add get and set methods --- src/chai/expt/ManagedArray.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index 7912349b..8add1722 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -154,6 +154,24 @@ namespace expt { return m_data[i]; } + ElementT get(std::size_t i) const { + if (m_array_manager) { + return m_array_manager->get(i); + } + else { + throw std::runtime_exception("Unable to get element"); + } + } + + void set(std::size_t i, const ElementT& value) { + if (m_array_manager) { + m_array_manager->set(i, value); + } + else { + throw std::runtime_exception("Unable to set element"); + } + } + ArrayManager* manager() const { return m_array_manager; } From 2d1981bb7a3f1f0c49a35b9f088b354a7cb5581a Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 17:34:04 -0700 Subject: [PATCH 32/49] Remove CopyHidingArrayManager.cpp --- src/chai/expt/CopyHidingArrayManager.cpp | 277 ----------------------- 1 file changed, 277 deletions(-) delete mode 100644 src/chai/expt/CopyHidingArrayManager.cpp diff --git a/src/chai/expt/CopyHidingArrayManager.cpp b/src/chai/expt/CopyHidingArrayManager.cpp deleted file mode 100644 index 8cdc2ffd..00000000 --- a/src/chai/expt/CopyHidingArrayManager.cpp +++ /dev/null @@ -1,277 +0,0 @@ -#include "chai/expt/CopyHidingManager.hpp" -#include "umpire/ResourceManager.hpp" - -namespace chai { -namespace expt { - CopyHidingManager::CopyHidingManager(const umpire::Allocator& cpuAllocator, - const umpire::Allocator& gpuAllocator) - : Manager{}, - m_cpu_allocator{cpuAllocator}, - m_gpu_allocator{gpuAllocator} - { - } - - CopyHidingManager::CopyHidingManager(int cpuAllocatorID, - int gpuAllocatorID) - : Manager{}, - m_resource_manager{umpire::ResourceManager::getInstance()}, - m_cpu_allocator{m_resource_manager.getAllocator(cpuAllocatorID)}, - m_gpu_allocator{m_resource_manager.getAllocator(gpuAllocatorID)} - { - } - - CopyHidingManager::CopyHidingManager(size_type size) : - : Manager{}, - m_size{size} - { - // TODO: Exception handling - m_cpu_data = m_cpu_allocator.allocate(size); - m_gpu_data = m_gpu_allocator.allocate(size); - } - - CopyHidingManager::CopyHidingManager(size_type size, - const umpire::Allocator& cpuAllocator, - const umpire::Allocator& gpuAllocator) : - : Manager{}, - m_cpu_allocator{cpuAllocator}, - m_gpu_allocator{gpuAllocator}, - m_size{size} - { - // TODO: Exception handling - m_cpu_data = m_cpu_allocator.allocate(size); - m_gpu_data = m_gpu_allocator.allocate(size); - } - - CopyHidingManager::CopyHidingManager(size_type size, - int cpuAllocatorID, - int gpuAllocatorID) : - : Manager{}, - m_resource_manager{umpire::ResourceManager::getInstance()}, - m_cpu_allocator{m_resource_manager.getAllocator(cpuAllocatorID)}, - m_gpu_allocator{m_resource_manager.getAllocator(gpuAllocatorID)}, - m_size{size} - { - // TODO: Exception handling - m_cpu_data = m_cpu_allocator.allocate(size); - m_gpu_data = m_gpu_allocator.allocate(size); - } - - CopyHidingManager::CopyHidingManager(const CopyHidingManager& other) - : Manager{}, - m_cpu_allocator{other.m_cpu_allocator}, - m_gpu_allocator{other.m_gpu_allocator}, - m_size{other.m_size}, - m_touch{other.m_touch} - { - if (other.m_cpu_data) - { - m_cpu_data = m_cpu_allocator.allocate(m_size); - m_resourceManager.copy(m_cpu_data, other.m_cpu_data, m_size); - } - - if (other.m_gpu_data) - { - m_gpu_data = m_gpu_allocator.allocate(m_size); - m_resourceManager.copy(m_gpu_data, other.m_gpu_data, m_size); - } - } - - CopyHidingManager::CopyHidingManager(CopyHidingManager&& other) noexcept - : Manager{}, - m_cpu_allocator{other.m_cpu_allocator}, - m_gpu_allocator{other.m_gpu_allocator}, - m_size{other.m_size}, - m_touch{other.m_touch}, - m_cpu_data{other.m_cpu_data}, - m_gpu_data{other.m_gpu_data} - { - other.m_size = 0; - other.m_cpu_data = nullptr; - other.m_gpu_data = nullptr; - other.m_touch = ExecutionContext::NONE; - } - - CopyHidingManager::~CopyHidingManager() { - m_cpu_allocator.deallocate(m_cpu_data); - m_gpu_allocator.deallocate(m_gpu_data); - } - - CopyHidingManager& CopyHidingManager::operator=(const CopyHidingManager& other) - { - if (this != &other) - { - // Copy-assign base class if needed (uncomment if Manager is copy-assignable) - // Manager::operator=(other); - - // Copy-assign or copy members - m_cpu_allocator = other.m_cpu_allocator; - m_gpu_allocator = other.m_gpu_allocator; - m_touch = other.m_touch; - - // Allocate new resources before releasing old ones for strong exception safety - void* new_cpu_data = nullptr; - void* new_gpu_data = nullptr; - - if (other.m_cpu_data) - { - new_cpu_data = m_cpu_allocator.allocate(other.m_size); - m_resourceManager.copy(new_cpu_data, other.m_cpu_data, other.m_size); - } - - if (other.m_gpu_data) - { - new_gpu_data = m_gpu_allocator.allocate(other.m_size); - m_resourceManager.copy(new_gpu_data, other.m_gpu_data, other.m_size); - } - - // Clean up old resources - if (m_cpu_data) - { - m_cpu_allocator.deallocate(m_cpu_data, m_size); - } - - if (m_gpu_data) - { - m_gpu_allocator.deallocate(m_gpu_data, m_size); - } - - // Assign new resources and size - m_cpu_data = new_cpu_data; - m_gpu_data = new_gpu_data; - m_size = other.m_size; - } - - return *this; - } - - CopyHidingManager& CopyHidingManager::operator=(CopyHidingManager&& other) noexcept - { - if (this != &other) - { - // Release any resources currently held - if (m_cpu_data) - { - m_cpu_allocator.deallocate(m_cpu_data, m_size); - m_cpu_data = nullptr; - } - if (m_gpu_data) - { - m_gpu_allocator.deallocate(m_gpu_data, m_size); - m_gpu_data = nullptr; - } - - // Move-assign base class if needed (uncomment if Manager is move-assignable) - // Manager::operator=(std::move(other)); - - // Move-assign or copy members - m_cpu_allocator = other.m_cpu_allocator; - m_gpu_allocator = other.m_gpu_allocator; - m_size = other.m_size; - m_cpu_data = other.m_cpu_data; - m_gpu_data = other.m_gpu_data; - m_touch = other.m_touch; - - // Null out other's pointers and reset size - other.m_cpu_data = nullptr; - other.m_gpu_data = nullptr; - other.m_size = 0; - other.m_touch = ExecutionContext::NONE; - } - return *this; - } - - void CopyHidingManager::resize(size_type newSize) - { - if (newSize != m_size) - { - if (m_touch == ExecutionContext::CPU) - { - m_resource_manager.reallocate(m_cpu_pointer, newSize); - - if (m_gpu_pointer) - { - m_resource_manager.deallocate(m_gpu_pointer); - m_gpu_pointer = m_gpu_allocator.allocate(newSize); - } - } - else if (m_touch == ExecutionContext::GPU) - { - m_resource_manager.reallocate(m_gpu_pointer, newSize); - - if (m_cpu_pointer) - { - m_resource_manager.deallocate(m_cpu_pointer); - m_cpu_pointer = m_cpu_allocator.allocate(newSize); - } - } - else - { - if (m_gpu_pointer) - { - m_resource_manager.reallocate(m_gpu_pointer, newSize); - } - - if (m_cpu_pointer) - { - m_resource_manager.reallocate(m_cpu_pointer, newSize); - } - } - } - } - - size_type CopyHidingManager::size() const - { - return m_size; - } - - void* CopyHidingManager::data(bool touch) - { - ExecutionContext context = ExecutionContextManager::getCurrentContext(); - - if (context == ExecutionContext::CPU) - { - if (!m_cpu_data) - { - m_cpu_data = m_cpu_allocator.allocate(m_size); - } - - if (m_touch == ExecutionContext::GPU) - { - m_resource_manager.copy(m_cpu_data, m_gpu_data, m_size); - m_touch = ExecutionContext::NONE; - } - - if (touch) - { - m_touch = ExecutionContext::CPU; - } - - return m_cpu_data; - } - else if (context == ExecutionContext::GPU) - { - if (!m_gpu_data) - { - m_gpu_data = m_gpu_allocator.allocate(m_size); - } - - if (m_touch == ExecutionContext::CPU) - { - m_resource_manager.copy(m_gpu_data, m_cpu_data, m_size); - m_touch = ExecutionContext::NONE; - } - - if (touch) - { - m_touch = ExecutionContext::GPU; - } - - return m_gpu_data; - } - else - { - return nullptr; - } - } -} // namespace expt -} // namespace chai From 7dfa1feae039f4fac77177b18b480db499856b33 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 15 Aug 2025 17:54:56 -0700 Subject: [PATCH 33/49] Some clean up --- src/chai/expt/CopyHidingArray.hpp | 418 +++++++++++++++++++++++ src/chai/expt/CopyHidingArrayManager.hpp | 118 ++++--- src/chai/expt/HostArray.hpp | 279 +++++++++++++++ src/chai/expt/ManagedArrayView.hpp | 151 ++++++++ 4 files changed, 921 insertions(+), 45 deletions(-) create mode 100644 src/chai/expt/CopyHidingArray.hpp create mode 100644 src/chai/expt/HostArray.hpp create mode 100644 src/chai/expt/ManagedArrayView.hpp diff --git a/src/chai/expt/CopyHidingArray.hpp b/src/chai/expt/CopyHidingArray.hpp new file mode 100644 index 00000000..b22e27be --- /dev/null +++ b/src/chai/expt/CopyHidingArray.hpp @@ -0,0 +1,418 @@ +#ifndef CHAI_COPY_HIDING_ARRAY_HPP +#define CHAI_COPY_HIDING_ARRAY_HPP + +#include "umpire/ResourceManager.hpp" + +// TODO: Determine how to specify starting execution space + +namespace chai { +namespace expt { + /*! + * \class CopyHidingArray + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class CopyHidingArray + public: + /*! + * Constructs a CopyHidingArray with default allocators from Umpire + * for the "HOST" and "DEVICE" resources. + */ + CopyHidingArray() = default; + + /*! + * Constructs a CopyHidingArray with the given Umpire allocators. + */ + CopyHidingArray(const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator) : + m_cpu_allocator{cpuAllocator}, + m_gpu_allocator{gpuAllocator} + { + } + + /*! + * Constructs a CopyHidingArray with the given Umpire allocator IDs. + */ + CopyHidingArray(int cpuAllocatorID, + int gpuAllocatorID) : + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_cpu_allocator{m_resource_manager.getAllocator(cpuAllocatorID)}, + m_gpu_allocator{m_resource_manager.getAllocator(gpuAllocatorID)} + { + } + + /*! + * Constructs a CopyHidingArray with the given size using default allocators + * from Umpire for the "HOST" and "DEVICE" resources. + */ + CopyHidingArray(size_type size) : + m_size{size} + { + // TODO: Exception handling + m_cpu_data = m_cpu_allocator.allocate(size); + m_gpu_data = m_gpu_allocator.allocate(size); + } + + /*! + * Constructs a CopyHidingArray with the given size using the given Umpire + * allocators. + */ + CopyHidingArray(size_type size, + const umpire::Allocator& cpuAllocator, + const umpire::Allocator& gpuAllocator) : + m_cpu_allocator{cpuAllocator}, + m_gpu_allocator{gpuAllocator}, + m_size{size} + { + // TODO: Exception handling + m_cpu_data = m_cpu_allocator.allocate(size); + m_gpu_data = m_gpu_allocator.allocate(size); + } + + /*! + * Constructs a CopyHidingArray with the given size using the given Umpire + * allocator IDs. + */ + CopyHidingArray(size_type size, + int cpuAllocatorID, + int gpuAllocatorID) : + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_cpu_allocator{m_resource_manager.getAllocator(cpuAllocatorID)}, + m_gpu_allocator{m_resource_manager.getAllocator(gpuAllocatorID)}, + m_size{size} + { + // TODO: Exception handling + m_cpu_data = m_cpu_allocator.allocate(size); + m_gpu_data = m_gpu_allocator.allocate(size); + } + + /*! + * Constructs a deep copy of the given CopyHidingArray. + */ + CopyHidingArray(const CopyHidingArray& other) : + m_cpu_allocator{other.m_cpu_allocator}, + m_gpu_allocator{other.m_gpu_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch} + { + if (other.m_cpu_data) + { + m_cpu_data = m_cpu_allocator.allocate(m_size); + m_resourceManager.copy(m_cpu_data, other.m_cpu_data, m_size); + } + + if (other.m_gpu_data) + { + m_gpu_data = m_gpu_allocator.allocate(m_size); + m_resourceManager.copy(m_gpu_data, other.m_gpu_data, m_size); + } + } + + /*! + * Constructs a CopyHidingArray that takes ownership of the + * resources from the given CopyHidingArray. + */ + CopyHidingArray(CopyHidingArray&& other) : + m_cpu_allocator{other.m_cpu_allocator}, + m_gpu_allocator{other.m_gpu_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch}, + m_cpu_data{other.m_cpu_data}, + m_gpu_data{other.m_gpu_data} + { + other.m_size = 0; + other.m_cpu_data = nullptr; + other.m_gpu_data = nullptr; + other.m_touch = ExecutionContext::NONE; + } + + /*! + * \brief Virtual destructor. + */ + ~CopyHidingArray() + { + m_cpu_allocator.deallocate(m_cpu_data); + m_gpu_allocator.deallocate(m_gpu_data); + } + + /*! + * \brief Copy assignment operator. + */ + CopyHidingArray& operator=(const CopyHidingArray& other) + { + if (this != &other) + { + // Copy-assign base class if needed (uncomment if Manager is copy-assignable) + // Manager::operator=(other); + + // Copy-assign or copy members + m_cpu_allocator = other.m_cpu_allocator; + m_gpu_allocator = other.m_gpu_allocator; + m_touch = other.m_touch; + + // Allocate new resources before releasing old ones for strong exception safety + void* new_cpu_data = nullptr; + void* new_gpu_data = nullptr; + + if (other.m_cpu_data) + { + new_cpu_data = m_cpu_allocator.allocate(other.m_size); + m_resourceManager.copy(new_cpu_data, other.m_cpu_data, other.m_size); + } + + if (other.m_gpu_data) + { + new_gpu_data = m_gpu_allocator.allocate(other.m_size); + m_resourceManager.copy(new_gpu_data, other.m_gpu_data, other.m_size); + } + + // Clean up old resources + if (m_cpu_data) + { + m_cpu_allocator.deallocate(m_cpu_data, m_size); + } + + if (m_gpu_data) + { + m_gpu_allocator.deallocate(m_gpu_data, m_size); + } + + // Assign new resources and size + m_cpu_data = new_cpu_data; + m_gpu_data = new_gpu_data; + m_size = other.m_size; + } + + return *this; + } + + /*! + * \brief Move assignment operator. + */ + CopyHidingArray& operator=(CopyHidingArray&& other) + { + if (this != &other) + { + // Release any resources currently held + if (m_cpu_data) + { + m_cpu_allocator.deallocate(m_cpu_data, m_size); + m_cpu_data = nullptr; + } + + if (m_gpu_data) + { + m_gpu_allocator.deallocate(m_gpu_data, m_size); + m_gpu_data = nullptr; + } + + // Move-assign base class if needed (uncomment if Manager is move-assignable) + // Manager::operator=(std::move(other)); + + // Move-assign or copy members + m_cpu_allocator = other.m_cpu_allocator; + m_gpu_allocator = other.m_gpu_allocator; + m_size = other.m_size; + m_cpu_data = other.m_cpu_data; + m_gpu_data = other.m_gpu_data; + m_touch = other.m_touch; + + // Null out other's pointers and reset size + other.m_cpu_data = nullptr; + other.m_gpu_data = nullptr; + other.m_size = 0; + other.m_touch = ExecutionContext::NONE; + } + + return *this; + } + + /*! + * \brief Resize the underlying arrays. + */ + void resize(size_type newSize) + { + if (newSize != m_size) + { + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.reallocate(m_cpu_pointer, newSize); + + if (m_gpu_pointer) + { + m_resource_manager.deallocate(m_gpu_pointer); + m_gpu_pointer = m_gpu_allocator.allocate(newSize); + } + } + else if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.reallocate(m_gpu_pointer, newSize); + + if (m_cpu_pointer) + { + m_resource_manager.deallocate(m_cpu_pointer); + m_cpu_pointer = m_cpu_allocator.allocate(newSize); + } + } + else + { + if (m_gpu_pointer) + { + m_resource_manager.reallocate(m_gpu_pointer, newSize); + } + + if (m_cpu_pointer) + { + m_resource_manager.reallocate(m_cpu_pointer, newSize); + } + } + } + } + + /*! + * \brief Get the size of the underlying arrays. + */ + size_type size() const + { + return m_size; + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + */ + T* data(ExecutionContext context) + { + if (context == ExecutionContext::CPU) + { + if (!m_cpu_data) + { + m_cpu_data = m_cpu_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.copy(m_cpu_data, m_gpu_data, m_size); + m_touch = ExecutionContext::NONE; + } + + if (touch) + { + m_touch = ExecutionContext::CPU; + } + + return m_cpu_data; + } + else if (context == ExecutionContext::GPU) + { + if (!m_gpu_data) + { + m_gpu_data = m_gpu_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.copy(m_gpu_data, m_cpu_data, m_size); + m_touch = ExecutionContext::NONE; + } + + if (touch) + { + m_touch = ExecutionContext::GPU; + } + + return m_gpu_data; + } + else + { + return nullptr; + } + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + */ + const T* data(ExecutionContext context) const + { + if (context == ExecutionContext::CPU) + { + if (!m_cpu_data) + { + m_cpu_data = m_cpu_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::GPU) + { + m_resource_manager.copy(m_cpu_data, m_gpu_data, m_size); + m_touch = ExecutionContext::NONE; + } + + return m_cpu_data; + } + else if (context == ExecutionContext::GPU) + { + if (!m_gpu_data) + { + m_gpu_data = m_gpu_allocator.allocate(m_size); + } + + if (m_touch == ExecutionContext::CPU) + { + m_resource_manager.copy(m_gpu_data, m_cpu_data, m_size); + m_touch = ExecutionContext::NONE; + } + + return m_gpu_data; + } + else + { + return nullptr; + } + } + +#if 0 + /*! + * \brief Get the i-th element. + * + * \warning Use sparingly, as coherence must be checked. + */ + ElementType getElement(size_type i) const + { + if (m_touch == ExecutionContext::GPU) + { + // Copy m_gpu_data[i] to host + } + else + { + return m_cpu_data[i]; + } + + return m_cpu_data[i]; + } + + void setElement(size_type i, const ElementType& value) + { + if (m_touch == ExecutionContext::GPU) + { + // Copy value to m_gpu_data[i] + } + else + { + m_cpu_data[i] = value; + } + } +#endif + + private: + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_cpu_allocator{m_resource_manager.getAllocator("HOST")}; + umpire::Allocator m_gpu_allocator{m_resource_manager.getAllocator("DEVICE")}; + size_type m_size{0}; + ElementType* m_cpu_data{nullptr}; + ElementType* m_gpu_data{nullptr}; + ExecutionContext m_touch{ExecutionContext::NONE}; + }; // class CopyHidingArray +} // namespace expt +} // namespace chai + +#endif // CHAI_COPY_HIDING_ARRAY_HPP diff --git a/src/chai/expt/CopyHidingArrayManager.hpp b/src/chai/expt/CopyHidingArrayManager.hpp index f4241fd1..802fe90a 100644 --- a/src/chai/expt/CopyHidingArrayManager.hpp +++ b/src/chai/expt/CopyHidingArrayManager.hpp @@ -60,8 +60,8 @@ namespace expt { m_size{size} { // TODO: Exception handling - m_host_data = m_host_allocator.allocate(size); - m_device_data = m_device_allocator.allocate(size); + m_host_data = static_cast(m_host_allocator.allocate(size*sizeof(ElementT)); + m_device_data = static_cast(m_device_allocator.allocate(size*sizeof(ElementT)); } /*! @@ -112,13 +112,13 @@ namespace expt { if (other.m_host_data) { m_host_data = m_host_allocator.allocate(m_size); - m_resource_manager.copy(m_host_data, other.m_host_data, m_size); + m_resource_manager.copy(m_host_data, other.m_host_data, m_size*sizeof(ElementT)); } if (other.m_device_data) { m_device_data = m_device_allocator.allocate(m_size); - m_resource_manager.copy(m_device_data, other.m_device_data, m_size); + m_resource_manager.copy(m_device_data, other.m_device_data, m_size*sizeof(ElementT)); } } @@ -138,7 +138,7 @@ namespace expt { other.m_size = 0; other.m_host_data = nullptr; other.m_device_data = nullptr; - other.m_touch = ExecutionContext::NONE; + other.m_touch = NONE; } /*! @@ -173,13 +173,13 @@ namespace expt { if (other.m_host_data) { new_host_data = m_host_allocator.allocate(other.m_size); - m_resource_manager.copy(new_host_data, other.m_host_data, other.m_size); + m_resource_manager.copy(new_host_data, other.m_host_data, other.m_size*sizeof(ElementT)); } if (other.m_device_data) { new_device_data = m_device_allocator.allocate(other.m_size); - m_resource_manager.copy(new_device_data, other.m_device_data, other.m_size); + m_resource_manager.copy(new_device_data, other.m_device_data, other.m_size*sizeof(ElementT)); } // Clean up old resources @@ -233,7 +233,7 @@ namespace expt { other.m_host_data = nullptr; other.m_device_data = nullptr; other.m_size = 0; - other.m_touch = ExecutionContext::NONE; + other.m_touch = NONE; } return *this; } @@ -245,7 +245,7 @@ namespace expt { { if (newSize != m_size) { - if (m_touch == ExecutionContext::CPU) + if (m_touch == HOST) { m_resource_manager.reallocate(m_host_data, newSize); @@ -255,7 +255,7 @@ namespace expt { m_device_data = m_device_allocator.allocate(newSize); } } - else if (m_touch == ExecutionContext::GPU) + else if (m_touch == DEVICE) { m_resource_manager.reallocate(m_device_data, newSize); @@ -294,45 +294,45 @@ namespace expt { */ virtual ElementT* data(Context context, bool touch) override { - if (context == ExecutionContext::CPU) + if (context == HOST) { if (!m_host_data) { - m_host_data = m_host_allocator.allocate(m_size); + m_host_data = static_cast(m_host_allocator.allocate(m_size*sizeof(ElementT))); } - if (m_touch == ExecutionContext::GPU) + if (m_touch == DEVICE) { - m_resource_manager.copy(m_host_data, m_device_data, m_size); - m_touch = ExecutionContext::NONE; + m_resource_manager.copy(m_host_data, m_device_data, m_size*sizeof(ElementT)); + m_touch = NONE; } if (touch) { - m_touch = ExecutionContext::CPU; + m_touch = HOST; } - return static_cast(m_host_data); + return m_host_data; } - else if (context == ExecutionContext::GPU) + else if (context == DEVICE) { if (!m_device_data) { m_device_data = m_device_allocator.allocate(m_size); } - if (m_touch == ExecutionContext::CPU) + if (m_touch == HOST) { - m_resource_manager.copy(m_device_data, m_host_data, m_size); - m_touch = ExecutionContext::NONE; + m_resource_manager.copy(m_device_data, m_host_data, m_size*sizeof(ElementT)); + m_touch = NONE; } if (touch) { - m_touch = ExecutionContext::GPU; + m_touch = DEVICE; } - return static_cast(m_device_data); + return m_device_data; } else { @@ -348,34 +348,62 @@ namespace expt { * \param i The index of the element to get. * \return The value at index i. */ - virtual ElementT get(std::size_t i) const override - { - // Implementation needed - // For now, just returning a default value - return ElementT{}; - } - - /*! - * \brief Sets the value at index i to the specified value. - * - * Note: Use this function sparingly as it may be slow. - * - * \param i The index of the element to set. - * \param value The value to set at index i. - */ - virtual void set(std::size_t i, const ElementT& value) override - { - // Implementation needed - } + virtual ElementT get(std::size_t i) const override { + ElementT result; + + if (m_touch == HOST) { + return m_host_data[i]; + } + else if (m_touch == DEVICE) { + m_resource_manager.copy(&result, m_device_data + i, sizeof(ElementT)); + } + else { + if (m_host_data) { + return m_host_data[i]; + } + else { + throw std::runtime_exception("Reading uninitialized memory"); + } + } + + return ElementT{}; + } + + /*! + * \brief Sets the value at index i to the specified value. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to set. + * \param value The value to set at index i. + */ + virtual void set(std::size_t i, const ElementT& value) override + { + if (m_touch == HOST) { + m_host_data[i] = value; + } + else if (m_touch == DEVICE) { + m_resource_manager.copy(m_device_data + i, &value, sizeof(ElementT)); + } + else { + if (m_host_data) { + m_host_data[i] = value; + } + + if (m_device_data) { + m_resource_manager.copy(m_device_data + i, &value, sizeof(ElementT)); + } + } + } private: umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; umpire::Allocator m_host_allocator{m_resource_manager.getAllocator("HOST")}; umpire::Allocator m_device_allocator{m_resource_manager.getAllocator("DEVICE")}; std::size_t m_size{0}; - void* m_host_data{nullptr}; - void* m_device_data{nullptr}; - ExecutionContext m_touch{ExecutionContext::NONE}; + ElementT* m_host_data{nullptr}; + ElementT* m_device_data{nullptr}; + ExecutionContext m_touch{NONE}; }; // class CopyHidingArrayManager } // namespace expt } // namespace chai diff --git a/src/chai/expt/HostArray.hpp b/src/chai/expt/HostArray.hpp new file mode 100644 index 00000000..8ca03c31 --- /dev/null +++ b/src/chai/expt/HostArray.hpp @@ -0,0 +1,279 @@ +#ifndef CHAI_HOST_ARRAY_HPP +#define CHAI_HOST_ARRAY_HPP + +#include "umpire/ResourceManager.hpp" +#include // for size_t + +namespace chai { +namespace expt { + /*! + * \class HostArray + * + * \brief Manages an array in host memory with RAII semantics. + * + * This class provides a simpler alternative to CopyHidingArray + * when only host memory access is needed. + */ + template + class HostArray { + public: + using size_type = std::size_t; + + /*! + * \brief Default constructor creates an empty array. + */ + HostArray() = default; + + /*! + * \brief Constructs a HostArray with the given Umpire allocator. + */ + HostArray(const umpire::Allocator& allocator) : + m_allocator{allocator} + { + } + + /*! + * \brief Constructs a HostArray with the given Umpire allocator ID. + */ + HostArray(int allocatorID) : + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_allocator{m_resource_manager.getAllocator(allocatorID)} + { + } + + /*! + * \brief Constructs a HostArray with the given size using the default allocator. + */ + HostArray(size_type size) : + m_size{size} + { + if (size > 0) { + m_data = static_cast(m_allocator.allocate(size * sizeof(ElementT))); + } + } + + /*! + * \brief Constructs a HostArray with the given size using the specified allocator. + */ + HostArray(size_type size, const umpire::Allocator& allocator) : + m_allocator{allocator}, + m_size{size} + { + if (size > 0) { + m_data = static_cast(m_allocator.allocate(size * sizeof(ElementT))); + } + } + + /*! + * \brief Constructs a HostArray with the given size using the specified allocator ID. + */ + HostArray(size_type size, int allocatorID) : + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_allocator{m_resource_manager.getAllocator(allocatorID)}, + m_size{size} + { + if (size > 0) { + m_data = static_cast(m_allocator.allocate(size * sizeof(ElementT))); + } + } + + /*! + * \brief Constructs a deep copy of the given HostArray. + */ + HostArray(const HostArray& other) : + m_allocator{other.m_allocator}, + m_size{other.m_size} + { + if (m_size > 0) { + m_data = static_cast(m_allocator.allocate(m_size * sizeof(ElementT))); + + for (size_type i = 0; i < m_size; ++i) { + m_data[i] = other.m_data[i]; + } + } + } + + /*! + * \brief Constructs a HostArray that takes ownership of the resources from the given HostArray. + */ + HostArray(HostArray&& other) noexcept : + m_allocator{other.m_allocator}, + m_size{other.m_size}, + m_data{other.m_data} + { + other.m_size = 0; + other.m_data = nullptr; + } + + /*! + * \brief Destructor releases allocated memory. + */ + ~HostArray() + { + if (m_data) { + m_allocator.deallocate(m_data); + } + } + + /*! + * \brief Copy assignment operator. + */ + HostArray& operator=(const HostArray& other) + { + if (this != &other) { + // Allocate new data before releasing old data + ElementT* new_data = nullptr; + + if (other.m_size > 0) { + new_data = static_cast(other.m_allocator.allocate(other.m_size * sizeof(ElementT))); + + for (size_type i = 0; i < other.m_size; ++i) { + new_data[i] = other.m_data[i]; + } + } + + // Clean up old data + if (m_data) { + m_allocator.deallocate(m_data); + } + + // Update allocator, size, and data + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = new_data; + } + + return *this; + } + + /*! + * \brief Move assignment operator. + */ + HostArray& operator=(HostArray&& other) noexcept + { + if (this != &other) { + // Clean up current resources + if (m_data) { + m_allocator.deallocate(m_data); + } + + // Move resources from other + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = other.m_data; + + // Reset other + other.m_size = 0; + other.m_data = nullptr; + } + + return *this; + } + + /*! + * \brief Resize the array. + * + * If the new size is larger, the existing content is preserved and + * new elements are default-initialized. If the new size is smaller, + * only the first newSize elements are preserved. + */ + void resize(size_type newSize) + { + if (newSize != m_size) { + ElementT* new_data = nullptr; + + if (newSize > 0) { + new_data = static_cast(m_allocator.allocate(newSize * sizeof(ElementT))); + + // Copy existing data, up to the smaller of m_size and newSize + size_type copy_size = (newSize < m_size) ? newSize : m_size; + for (size_type i = 0; i < copy_size; ++i) { + new_data[i] = m_data[i]; + } + + // Initialize new elements if expanding + for (size_type i = m_size; i < newSize; ++i) { + new_data[i] = ElementT(); + } + } + + // Clean up old data + if (m_data) { + m_allocator.deallocate(m_data); + } + + m_data = new_data; + m_size = newSize; + } + } + + /*! + * \brief Get the size of the array. + */ + size_type size() const + { + return m_size; + } + + /*! + * \brief Get access to the underlying data. + */ + ElementT* data() + { + return m_data; + } + + /*! + * \brief Get const access to the underlying data. + */ + const ElementT* data() const + { + return m_data; + } + + /*! + * \brief Array subscript operator for element access. + */ + ElementT& operator[](size_type index) + { + return m_data[index]; + } + + /*! + * \brief Const array subscript operator for element access. + */ + const ElementT& operator[](size_type index) const + { + return m_data[index]; + } + + /*! + * \brief Check if the array is empty. + */ + bool empty() const + { + return m_size == 0; + } + + /*! + * \brief Clear the array by deallocating memory and setting size to 0. + */ + void clear() + { + if (m_data) { + m_allocator.deallocate(m_data); + m_data = nullptr; + } + m_size = 0; + } + + private: + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_allocator{m_resource_manager.getAllocator("HOST")}; + size_type m_size{0}; + ElementT* m_data{nullptr}; + }; // class HostArray +} // namespace expt +} // namespace chai + +#endif // CHAI_HOST_ARRAY_HPP \ No newline at end of file diff --git a/src/chai/expt/ManagedArrayView.hpp b/src/chai/expt/ManagedArrayView.hpp new file mode 100644 index 00000000..c6d31ffa --- /dev/null +++ b/src/chai/expt/ManagedArrayView.hpp @@ -0,0 +1,151 @@ +#ifndef CHAI_ARRAY_VIEW_HPP +#define CHAI_ARRAY_VIEW_HPP + +#include "chai/Manager.hpp" +#include + +namespace chai { +namespace expt { + /*! + * \class ArrayView + * + * \brief A view into an existing Array without taking ownership of the data. + * + * \tparam T The type of element in the array view. + */ + template + class ArrayView { + public: + /*! + * \brief Constructs an empty array view. + */ + ArrayView() = default; + + /*! + * \brief Constructs an array view from a manager. + * + * \param manager The array manager controls the coherence of the array. + */ + explicit ArrayView(Manager* manager) : + m_manager{manager} + { + if (m_manager) + { + m_size = m_manager->size(); + } + } + + /*! + * \brief Constructs an array view with specified size and manager. + * + * \param size The number of elements + * \param manager The array manager + */ + ArrayView(std::size_t offset, std::size_t size, Manager* manager) : + m_offset{offset}, + m_size{size}, + m_manager{manager} + { + } + + /*! + * \brief Constructs a shallow copy of an array view from another and makes + * the data coherent in the current execution space. + * + * \param other The other array view. + * + * \note This is a shallow copy. + */ + CHAI_HOST_DEVICE ArrayView(const ArrayView& other) : + m_data{other.m_data}, + m_offset{other.m_offset}, + m_size{other.m_size}, + m_manager{other.m_manager} + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(ContextManager::getInstance()::getContext(), !std::is_const::value)) + m_offset; + } +#endif + } + + CHAI_HOST_DEVICE std::size_t offset() const { + return m_offset; + } + + /*! + * \brief Get the number of elements in the array view. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE std::size_t size() const { + return m_size; + } + + CHAI_HOST_DEVICE T* data() const { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(ExecutionContext::HOST, !std::is_const::value)) + m_offset; + } +#endif + return m_data; + } + + CHAI_HOST_DEVICE T* data(ExecutionContext context) const { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(context, !std::is_const::value)) + m_offset; + } +#endif + return m_data; + } + + /*! + * \brief Get the ith element in the array view. + * + * \param i The index of the element to retrieve. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE T& operator[](std::size_t i) const { + return m_data[i]; + } + + private: + /*! + * The array that is coherent in the current execution space. + */ + T* m_data = nullptr; + + /*! + * The starting element in the array view. + */ + std::size_t m_offset = 0; + + /*! + * The number of elements in the array view. + */ + std::size_t m_size = 0; + + /*! + * The array manager controls the coherence of the array. + * ArrayView doesn't own the manager + */ + Manager* m_manager = nullptr; + }; // class ArrayView + + /*! + * \brief Constructs an array view by using an existing manager object. + * + * \tparam Manager The type of array manager. + */ + template + ArrayView makeArrayView(Manager* manager) { + return ArrayView(manager); + } +} // namespace expt +} // namespace chai + +#endif // CHAI_ARRAY_VIEW_HPP \ No newline at end of file From f3ec2bfecf6e2b8de9504825f725ed40295c40a4 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 18 Aug 2025 09:57:04 -0700 Subject: [PATCH 34/49] Clean up ManagedArray --- src/chai/expt/ManagedArray.hpp | 119 +++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 36 deletions(-) diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index 8add1722..e6094c73 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -39,19 +39,6 @@ namespace expt { } } - ManagedArray(std::size_t size, ArrayManager* manager) : - m_size{size} - m_array_manager{manager} - { - if (m_array_manager) { - m_size = newSize; - m_array_manager->resize(newSize); - } - else { - throw std::runtime_exception("Unable to resize"); - } - } - /*! * \brief Constructs a shallow copy of an array from another and makes * the data coherent in the current execution space. @@ -72,12 +59,36 @@ namespace expt { #endif } - void setManager(ArrayManager* manager) - { - delete m_array_manager; - m_array_manager = manager; + /*! + * \brief Sets the array manager for this ManagedArray. + * + * \param manager The new array manager to be set. + * + * \post The ManagedArray takes ownership of the new manager objet. + */ + void setManager(ArrayManager* manager) + { + delete m_array_manager; + m_array_manager = manager; + } + + /*! + * \brief Get the array manager associated with this ManagedArray. + * + * \return A pointer to the array manager. + */ + ArrayManager* getManager() const { + return m_array_manager; } + /*! + * \brief Resizes the array to the specified new size. + * + * \param newSize The new size to resize the array to. + * + * \note This method updates the size of the array and triggers a resize operation in the array manager if it exists. + * If no array manager is associated, an exception is thrown. + */ void resize(std::size_t newSize) { if (m_array_manager) { m_size = newSize; @@ -101,7 +112,6 @@ namespace expt { m_array_manager = nullptr; } - /*! * \brief Get the number of elements in the array. * @@ -112,20 +122,13 @@ namespace expt { return m_size; } - CHAI_HOST_DEVICE ElementT* data() const { -#if !defined(CHAI_DEVICE_COMPILE) - return data(HOST); -#endif - return m_data; - } - - CHAI_HOST_DEVICE const ElementT* cdata() const { -#if !defined(CHAI_DEVICE_COMPILE) - return cdata(HOST); -#endif - return m_data; - } - + /*! + * \brief Get a pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the element data. + * + * \return A pointer to the element data in the specified context. + */ ElementT* data(Context context) const { if (m_array_manager) { m_data = m_array_manager->data(context, !std::is_const::value); @@ -134,6 +137,13 @@ namespace expt { return m_data; } + /*! + * \brief Get a const pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the const element data. + * + * \return A const pointer to the element data in the specified context. + */ const ElementT* cdata(Context context) const { if (m_array_manager) { m_data = m_array_manager->data(context, false); @@ -142,6 +152,30 @@ namespace expt { return m_data; } + /*! + * \brief Get a pointer to the element data in the current execution space. + * + * \return A pointer to the element data in the current execution space. + */ + CHAI_HOST_DEVICE ElementT* data() const { +#if !defined(CHAI_DEVICE_COMPILE) + return data(HOST); +#endif + return m_data; + } + + /*! + * \brief Get a const pointer to the element data in the current execution space. + * + * \return A const pointer to the element data in the current execution space. + */ + CHAI_HOST_DEVICE const ElementT* cdata() const { +#if !defined(CHAI_DEVICE_COMPILE) + return cdata(HOST); +#endif + return m_data; + } + /*! * \brief Get the ith element in the array. * @@ -154,6 +188,15 @@ namespace expt { return m_data[i]; } + /*! + * \brief Get the value of the element at the specified index. + * + * \param i The index of the element to retrieve. + * + * \return The value of the element at the specified index. + * + * \throw std::runtime_exception if unable to retrieve the element. + */ ElementT get(std::size_t i) const { if (m_array_manager) { return m_array_manager->get(i); @@ -163,6 +206,14 @@ namespace expt { } } + /*! + * \brief Set a value at a specified index in the array. + * + * \param i The index where the value is to be set. + * \param value The value to set at the specified index. + * + * \throw std::runtime_exception if the array manager is not associated with the ManagedArray. + */ void set(std::size_t i, const ElementT& value) { if (m_array_manager) { m_array_manager->set(i, value); @@ -172,10 +223,6 @@ namespace expt { } } - ArrayManager* manager() const { - return m_array_manager; - } - private: /*! * The array that is coherent in the current execution space. From 2c1def234ddfe69569cfb69980764054fdf93132 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 18 Aug 2025 11:58:15 -0700 Subject: [PATCH 35/49] Rename to ExecutionContext and ExecutionContextManager --- .../{Context.hpp => ExecutionContext.hpp} | 10 +- ...anager.hpp => ExecutionContextManager.hpp} | 37 +-- src/chai/expt/ManagedArray.hpp | 8 +- src/chai/expt/PinnedArrayManager.hpp | 295 ++++++++++++++++++ 4 files changed, 320 insertions(+), 30 deletions(-) rename src/chai/expt/{Context.hpp => ExecutionContext.hpp} (83%) rename src/chai/expt/{ContextManager.hpp => ExecutionContextManager.hpp} (63%) create mode 100644 src/chai/expt/PinnedArrayManager.hpp diff --git a/src/chai/expt/Context.hpp b/src/chai/expt/ExecutionContext.hpp similarity index 83% rename from src/chai/expt/Context.hpp rename to src/chai/expt/ExecutionContext.hpp index 5f2323d1..44819004 100644 --- a/src/chai/expt/Context.hpp +++ b/src/chai/expt/ExecutionContext.hpp @@ -5,17 +5,17 @@ // SPDX-License-Identifier: BSD-3-Clause ////////////////////////////////////////////////////////////////////////////// -#ifndef CHAI_CONTEXT_HPP -#define CHAI_CONTEXT_HPP +#ifndef CHAI_EXECUTION_CONTEXT_HPP +#define CHAI_EXECUTION_CONTEXT_HPP namespace chai { namespace expt { /*! - * \enum Context + * \enum ExecutionContext * * \brief Represents the state of a program. ArrayManagers update coherence based on the context. */ - enum Context { + enum ExecutionContext { NONE = 0, ///< Represents no context. HOST ///< Represents the host context (i.e. the CPU). #if defined(CHAI_ENABLE_CUDA) || defined(CHAI_ENABLE_HIP) || defined(CHAI_ENABLE_GPU_SIMULATION_MODE) @@ -25,4 +25,4 @@ namespace expt { } // namespace expt } // namespace chai -#endif // CHAI_CONTEXT_HPP \ No newline at end of file +#endif // CHAI_EXECUTION_CONTEXT_HPP \ No newline at end of file diff --git a/src/chai/expt/ContextManager.hpp b/src/chai/expt/ExecutionContextManager.hpp similarity index 63% rename from src/chai/expt/ContextManager.hpp rename to src/chai/expt/ExecutionContextManager.hpp index 9ea553db..2fedeb27 100644 --- a/src/chai/expt/ContextManager.hpp +++ b/src/chai/expt/ExecutionContextManager.hpp @@ -5,49 +5,49 @@ // SPDX-License-Identifier: BSD-3-Clause ////////////////////////////////////////////////////////////////////////////// -#ifndef CHAI_CONTEXT_MANAGER_HPP -#define CHAI_CONTEXT_MANAGER_HPP +#ifndef CHAI_EXECUTION_CONTEXT_MANAGER_HPP +#define CHAI_EXECUTION_CONTEXT_MANAGER_HPP -#include "chai/expt/Context.hpp" +#include "chai/expt/ExecutionContext.hpp" namespace chai { namespace expt { /*! - * \class ContextManager + * \class ExecutionContextManager * * \brief Singleton class for managing the current execution context. * * This class provides a centralized way to get and set the current execution * context across the application. */ - class ContextManager { + class ExecutionContextManager { public: /*! - * \brief Get the singleton instance of ContextManager. + * \brief Get the singleton instance of ExecutionContextManager. * * \return The singleton instance. */ - static ContextManager& getInstance() { - static inline ContextManager s_instance; + static ExecutionContextManager& getInstance() { + static inline ExecutionContextManager s_instance; return s_instance; } /*! * \brief Private copy constructor to prevent copying. */ - ContextManager(const ContextManager&) = delete; + ExecutionContextManager(const ExecutionContextManager&) = delete; /*! * \brief Private assignment operator to prevent assignment. */ - ContextManager& operator=(const ContextManager&) = delete; + ExecutionContextManager& operator=(const ExecutionContextManager&) = delete; /*! * \brief Get the current execution context. * * \return The current context. */ - Context getContext() const { + ExecutionContext getContext() const { return m_current_context; } @@ -56,7 +56,7 @@ namespace expt { * * \param context The new context to set. */ - void setContext(Context context) { + void setContext(ExecutionContext context) { m_current_context = context; } @@ -64,19 +64,14 @@ namespace expt { /*! * \brief Private constructor for singleton pattern. */ - constexpr ContextManager() noexcept = default; + constexpr ExecutionContextManager() noexcept = default; /*! * \brief The current execution context. */ - Context m_current_context = NONE; - }; + ExecutionContext m_current_context = NONE; + }; // class ExecutionContextManager } // namespace expt } // namespace chai -#endif // CHAI_CONTEXT_HPP - -} // namespace expt -} // namespace chai - -#endif // CHAI_CONTEXT_MANAGER_HPP +#endif // CHAI_EXECUTION_CONTEXT_MANAGER_HPP diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index e6094c73..dd30b108 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -2,7 +2,7 @@ #define CHAI_MANAGED_ARRAY_HPP #include "chai/expt/ArrayManager.hpp" -#include "chai/expt/ContextManager.hpp" +#include "chai/expt/ExecutionContextManager.hpp" #include namespace chai { @@ -54,7 +54,7 @@ namespace expt { { #if !defined(CHAI_DEVICE_COMPILE) if (m_array_manager) { - m_data = m_array_manager->data(ContextManager::getInstance()::getContext(), !std::is_const::value)); + m_data = m_array_manager->data(ExecutionContextManager::getInstance()::getContext(), !std::is_const::value)); } #endif } @@ -129,7 +129,7 @@ namespace expt { * * \return A pointer to the element data in the specified context. */ - ElementT* data(Context context) const { + ElementT* data(ExecutionContext context) const { if (m_array_manager) { m_data = m_array_manager->data(context, !std::is_const::value); } @@ -144,7 +144,7 @@ namespace expt { * * \return A const pointer to the element data in the specified context. */ - const ElementT* cdata(Context context) const { + const ElementT* cdata(ExecutionContext context) const { if (m_array_manager) { m_data = m_array_manager->data(context, false); } diff --git a/src/chai/expt/PinnedArrayManager.hpp b/src/chai/expt/PinnedArrayManager.hpp new file mode 100644 index 00000000..a568c5b6 --- /dev/null +++ b/src/chai/expt/PinnedArrayManager.hpp @@ -0,0 +1,295 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_PINNED_ARRAY_MANAGER_HPP +#define CHAI_PINNED_ARRAY_MANAGER_HPP + +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + /*! + * \class PinnedArrayManager + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class PinnedArrayManager : public ArrayManager { + public: + /*! + * Constructs a PinnedArrayManager with default allocators from Umpire + * for the "HOST" and "DEVICE" resources. + */ + PinnedArrayManager() = default; + + /*! + * Constructs a PinnedArrayManager with the given Umpire allocators. + */ + PinnedArrayManager(const umpire::Allocator& allocator) + : ArrayManager{}, + m_allocator{allocator} + { + } + + /*! + * Constructs a PinnedArrayManager with the given Umpire allocator IDs. + */ + PinnedArrayManager(int allocatorID) + : ArrayManager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_allocator{m_resource_manager.getAllocator(allocatorID)} + { + } + + /*! + * Constructs a PinnedArrayManager with the given size using default allocators + * from Umpire for the "HOST" and "DEVICE" resources. + */ + PinnedArrayManager(std::size_t size) + : ArrayManager{}, + m_size{size} + { + // TODO: Exception handling + m_data = static_cast(m_allocator.allocate(size*sizeof(ElementT)); + } + + /*! + * Constructs a PinnedArrayManager with the given size using the given Umpire + * allocators. + */ + PinnedArrayManager(std::size_t size, + const umpire::Allocator& allocator) + : ArrayManager{}, + m_allocator{allocator}, + m_size{size} + { + // TODO: Exception handling + m_data = static_cast(m_allocator.allocate(size*sizeof(ElementT)); + } + + /*! + * Constructs a PinnedArrayManager with the given size using the given Umpire + * allocator IDs. + */ + PinnedArrayManager(std::size_t size, + int allocatorID) + : ArrayManager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_allocator{m_resource_manager.getAllocator(allocatorID)}, + m_size{size} + { + // TODO: Exception handling + static_cast(m_allocator.allocate(size*sizeof(ElementT)); + } + + /*! + * Constructs a deep copy of the given PinnedArrayManager. + */ + PinnedArrayManager(const PinnedArrayManager& other) + : ArrayManager{}, + m_allocator{other.m_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch} + { + if (other.m_data) + { + m_data = m_allocator.allocate(m_size); + m_resource_manager.copy(m_data, other.m_data, m_size*sizeof(ElementT)); + // TODO: The copy could potentially change in which space the last touch occurs + } + } + + /*! + * Constructs a PinnedArrayManager that takes ownership of the + * resources from the given PinnedArrayManager. + */ + PinnedArrayManager(PinnedArrayManager&& other) noexcept + : ArrayManager{}, + m_allocator{other.m_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch}, + m_data{other.m_data} + { + other.m_size = 0; + other.m_data = nullptr; + other.m_touch = NONE; + } + + /*! + * \brief Virtual destructor. + */ + virtual ~PinnedArrayManager() + { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Copy assignment operator. + */ + PinnedArrayManager& operator=(const PinnedArrayManager& other) + { + if (this != &other) + { + // Copy-assign or copy members + m_allocator = other.m_allocator; + m_touch = other.m_touch; + + // Allocate new resources before releasing old ones for strong exception safety + void* new_data = nullptr; + + if (other.m_data) + { + new_data = static_cast(m_allocator.allocate(other.m_size*sizeof(ElementT))); + m_resource_manager.copy(new_data, other.m_data, other.m_size*sizeof(ElementT)); + // TODO: The copy operation could change m_touch + } + + // Clean up old resources + if (m_data) + { + m_allocator.deallocate(m_data); + } + + // Assign new resources and size + m_data = new_data; + m_size = other.m_size; + } + + return *this; + } + + /*! + * \brief Move assignment operator. + */ + PinnedArrayManager& operator=(PinnedArrayManager&& other) noexcept + { + if (this != &other) + { + // Release any resources currently held + if (m_data) + { + m_allocator.deallocate(m_data); + } + + // Move-assign or copy members + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = other.m_data; + m_touch = other.m_touch; + + // Null out other's pointers and reset size + other.m_data = nullptr; + other.m_size = 0; + other.m_touch = NONE; + } + return *this; + } + + /*! + * \brief Resize the underlying arrays. + */ + virtual void resize(std::size_t newSize) override + { + if (newSize != m_size) + { + // TODO: Is any synchronization needed? + m_resource_manager.reallocate(m_data, newSize); + m_size = newSize; + } + } + + /*! + * \brief Get the size of the underlying arrays. + */ + virtual std::size_t size() const override + { + return m_size; + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + */ + virtual ElementT* data(Context context, bool touch) override + { + ElementT* result{nullptr}; + + if (context == HOST) + { + if (m_touch == DEVICE) + { + m_context_manager.synchronize(DEVICE); + m_touch = NONE; + } + + if (touch) + { + m_touch = HOST; + } + + result = m_data; + } + else if (context == DEVICE) + { + if (m_touch == HOST) + { + // TODO: Should we call m_context_manager.synchronize(HOST)? Would support host openmp. + m_touch = NONE; + } + + if (touch) + { + m_touch = DEVICE; + } + + result = m_data; + } + + return result; + } + + /*! + * \brief Returns the value at index i. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to get. + * \return The value at index i. + */ + virtual ElementT get(std::size_t i) const override { + m_context_manager.synchronize(m_touch); + m_touch = NONE; + return m_data[i]; + } + + /*! + * \brief Sets the value at index i to the specified value. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to set. + * \param value The value to set at index i. + */ + virtual void set(std::size_t i, const ElementT& value) override + { + m_context_manager.synchronize(m_touch); + m_touch = HOST; + m_data[i] = value; + } + + private: + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_allocator{m_resource_manager.getAllocator("DEVICE")}; + std::size_t m_size{0}; + ElementT* m_data{nullptr}; + ExecutionContext m_touch{NONE}; + }; // class PinnedArrayManager +} // namespace expt +} // namespace chai + +#endif // CHAI_PINNED_ARRAY_MANAGER_HPP From 4e3eb54d6c7012f34f52b216301b66eceb518e90 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 18 Aug 2025 13:38:12 -0700 Subject: [PATCH 36/49] Add ExecutionContextManager tests --- src/chai/expt/ExecutionContextManager.hpp | 10 +++--- tests/unit/CMakeLists.txt | 34 +++++-------------- .../expt/ExecutionContextManagerTests.cpp | 30 ++++++++++++++++ 3 files changed, 44 insertions(+), 30 deletions(-) create mode 100644 tests/unit/expt/ExecutionContextManagerTests.cpp diff --git a/src/chai/expt/ExecutionContextManager.hpp b/src/chai/expt/ExecutionContextManager.hpp index 2fedeb27..4a91a4bc 100644 --- a/src/chai/expt/ExecutionContextManager.hpp +++ b/src/chai/expt/ExecutionContextManager.hpp @@ -47,8 +47,8 @@ namespace expt { * * \return The current context. */ - ExecutionContext getContext() const { - return m_current_context; + ExecutionContext getExecutionContext() const { + return m_execution_context; } /*! @@ -56,8 +56,8 @@ namespace expt { * * \param context The new context to set. */ - void setContext(ExecutionContext context) { - m_current_context = context; + void setExecutionContext(ExecutionContext context) { + m_execution_context = context; } private: @@ -69,7 +69,7 @@ namespace expt { /*! * \brief The current execution context. */ - ExecutionContext m_current_context = NONE; + ExecutionContext m_execution_context = NONE; }; // class ExecutionContextManager } // namespace expt } // namespace chai diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index e4cb579f..0ad34688 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -51,30 +51,14 @@ if (CHAI_ENABLE_MANAGED_PTR) COMMAND managed_ptr_unit_tests) endif () -blt_add_executable( - NAME TestHostManager - SOURCES expt/TestHostManager.cpp - DEPENDS_ON ${chai_unit_test_depends}) - -target_include_directories( - TestHostManager - PUBLIC ${PROJECT_BINARY_DIR}/include) - -blt_add_test( - NAME TestHostManager - COMMAND TestHostManager) +if (CHAI_ENABLE_EXPT) + blt_add_executable(NAME ExecutionContextManagerTests + SOURCES ExecutionContextManagerTests.cpp + DEPENDS_ON chai) -if(CHAI_ENABLE_CUDA OR CHAI_ENABLE_HIP OR CHAI_ENABLE_GPU_SIMULATION_MODE) - blt_add_executable( - NAME TestCopyHidingManager - SOURCES expt/TestCopyHidingManager.cpp - DEPENDS_ON ${chai_unit_test_depends}) + target_include_directories(ExecutionContextManagerTests + PUBLIC ${PROJECT_BINARY_DIR}/include) - target_include_directories( - TestCopyHidingManager - PUBLIC ${PROJECT_BINARY_DIR}/include) - - blt_add_test( - NAME TestCopyHidingManager - COMMAND TestCopyHidingManager) -endif() + blt_add_test(NAME ExecutionContextManagerTests + COMMAND ExecutionContextManagerTests) +endif () diff --git a/tests/unit/expt/ExecutionContextManagerTests.cpp b/tests/unit/expt/ExecutionContextManagerTests.cpp new file mode 100644 index 00000000..35802a66 --- /dev/null +++ b/tests/unit/expt/ExecutionContextManagerTests.cpp @@ -0,0 +1,30 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/expt/ExecutionContextManager.hpp" +#include "gtest/gtest.h" + +// Test that getInstance returns the same object at the same place in memory +TEST(ExecutionContextManager, SingletonInstance) { + chai::expt::ExecutionContextManager& executionContextManager1 = chai::expt::ExecutionContextManager::getInstance(); + chai::expt::ExecutionContextManager& executionContextManager2 = chai::expt::ExecutionContextManager::getInstance(); + EXPECT_EQ(&executionContextManager1, &executionContextManager2); +} + +// Test that the default execution context is NONE +TEST(ExecutionContextManager, DefaultExecutionContext) { + chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); + EXPECT_EQ(executionContextManager.getExecutionContext(), chai::expt::ExecutionContext::NONE); +} + +// Test setting and getting the execution context +TEST(ExecutionContextManager, ExecutionContext) { + chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); + chai::expt::ExecutionContext executionContext = chai::expt::ExecutionContext::HOST; + executionContextManager.setExecutionContext(executionContext); + EXPECT_EQ(executionContextManager.getExecutionContext(), executionContext); +} \ No newline at end of file From fdd748233b5f7c16c9d9bb52e46939d1028ad19f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 18 Aug 2025 13:39:16 -0700 Subject: [PATCH 37/49] Clean up --- src/chai/expt/ExecutionContextManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chai/expt/ExecutionContextManager.hpp b/src/chai/expt/ExecutionContextManager.hpp index 4a91a4bc..7c72eb9f 100644 --- a/src/chai/expt/ExecutionContextManager.hpp +++ b/src/chai/expt/ExecutionContextManager.hpp @@ -69,7 +69,7 @@ namespace expt { /*! * \brief The current execution context. */ - ExecutionContext m_execution_context = NONE; + ExecutionContext m_execution_context = ExecutionContext::NONE; }; // class ExecutionContextManager } // namespace expt } // namespace chai From 671615d4e70580a345808b58dd38d94fe9f04492 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 18 Aug 2025 13:45:12 -0700 Subject: [PATCH 38/49] Clean up cmake --- cmake/SetupChaiOptions.cmake | 1 + src/chai/CMakeLists.txt | 29 +++++++++-------------------- tests/unit/CMakeLists.txt | 2 +- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/cmake/SetupChaiOptions.cmake b/cmake/SetupChaiOptions.cmake index d7b69ac6..3ead3f31 100644 --- a/cmake/SetupChaiOptions.cmake +++ b/cmake/SetupChaiOptions.cmake @@ -20,6 +20,7 @@ option(CHAI_ENABLE_MANAGED_PTR "Enable managed_ptr" On) option(CHAI_DEBUG "Enable Debug Logging." Off) option(CHAI_ENABLE_RAJA_NESTED_TEST "Enable raja-chai-nested-tests, which fails to build on Debug CUDA builds." On) option(CHAI_ENABLE_MANAGED_PTR_ON_GPU "Enable managed_ptr on GPU" On) +option(CHAI_ENABLE_EXPERIMENTAL "Enable experimental features" On) option(CHAI_ENABLE_TESTS "Enable CHAI tests" On) option(CHAI_ENABLE_BENCHMARKS "Enable benchmarks" Off) diff --git a/src/chai/CMakeLists.txt b/src/chai/CMakeLists.txt index e1eacb3a..c8443eeb 100644 --- a/src/chai/CMakeLists.txt +++ b/src/chai/CMakeLists.txt @@ -20,13 +20,14 @@ set (chai_headers ManagedArray.inl managed_ptr.hpp PointerRecord.hpp - Types.hpp - expt/Array.hpp - expt/ExecutionContext.hpp - expt/Manager.hpp - expt/HostManager.hpp - expt/PageableManager.hpp - expt/PinnedManager.hpp) + Types.hpp) + +if(CHAI_ENABLE_EXPERIMENTAL) + set(chai_headers + ${chai_headers} + expt/ExecutionContext.hpp + expt/ExecutionContextManager.hpp) +endif() if(CHAI_DISABLE_RM) set(chai_headers @@ -35,19 +36,7 @@ if(CHAI_DISABLE_RM) endif () set (chai_sources - ArrayManager.cpp - expt/Manager.cpp - expt/HostManager.cpp) - -if(CHAI_ENABLE_CUDA OR CHAI_ENABLE_HIP OR CHAI_ENABLE_GPU_SIMULATION_MODE) - set(chai_headers - ${chai_headers} - expt/CopyHidingManager.hpp) - - set(chai_sources - ${chai_sources} - expt/CopyHidingManager.cpp) -endif() + ArrayManager.cpp) set (chai_depends umpire) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 0ad34688..c3394989 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -51,7 +51,7 @@ if (CHAI_ENABLE_MANAGED_PTR) COMMAND managed_ptr_unit_tests) endif () -if (CHAI_ENABLE_EXPT) +if (CHAI_ENABLE_EXPERIMENTAL) blt_add_executable(NAME ExecutionContextManagerTests SOURCES ExecutionContextManagerTests.cpp DEPENDS_ON chai) From d6109b2426892f016ebc1755be1cbb9a58e427d2 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 18 Aug 2025 15:20:26 -0700 Subject: [PATCH 39/49] Get ExecutionContextManager tests to build --- src/chai/expt/ExecutionContextManager.hpp | 2 +- tests/unit/CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chai/expt/ExecutionContextManager.hpp b/src/chai/expt/ExecutionContextManager.hpp index 7c72eb9f..937e5d70 100644 --- a/src/chai/expt/ExecutionContextManager.hpp +++ b/src/chai/expt/ExecutionContextManager.hpp @@ -28,7 +28,7 @@ namespace expt { * \return The singleton instance. */ static ExecutionContextManager& getInstance() { - static inline ExecutionContextManager s_instance; + static ExecutionContextManager s_instance; return s_instance; } diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index c3394989..14b8f161 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -53,8 +53,8 @@ endif () if (CHAI_ENABLE_EXPERIMENTAL) blt_add_executable(NAME ExecutionContextManagerTests - SOURCES ExecutionContextManagerTests.cpp - DEPENDS_ON chai) + SOURCES expt/ExecutionContextManagerTests.cpp + DEPENDS_ON chai gtest) target_include_directories(ExecutionContextManagerTests PUBLIC ${PROJECT_BINARY_DIR}/include) From 7f09c14065ac29b4aa24ec64c85fee8303945521 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 19 Aug 2025 10:35:54 -0700 Subject: [PATCH 40/49] Add testing --- .../unit/expt/CopyHidingArrayManagerTests.cpp | 31 +++ tests/unit/expt/TestCopyHidingArray.cpp | 181 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 tests/unit/expt/CopyHidingArrayManagerTests.cpp create mode 100644 tests/unit/expt/TestCopyHidingArray.cpp diff --git a/tests/unit/expt/CopyHidingArrayManagerTests.cpp b/tests/unit/expt/CopyHidingArrayManagerTests.cpp new file mode 100644 index 00000000..c8702cb5 --- /dev/null +++ b/tests/unit/expt/CopyHidingArrayManagerTests.cpp @@ -0,0 +1,31 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/expt/CopyHidingArrayManager.hpp" +#include "gtest/gtest.h" + +TEST(CopyHidingArrayManager, DefaultConstructor) { + chai::expt::CopyingHidingArrayManager arrayManager{}; + EXPECT_EQ(arrayManager.size(), 0); + EXPECT_EQ(arrayManager.data(chai::expt::ExecutionContext::NONE), nullptr); + EXPECT_EQ(arrayManager.data(chai::expt::ExecutionContext::HOST), nullptr); + EXPECT_EQ(arrayManager.data(chai::expt::ExecutionContext::DEVICE), nullptr); +} + +// Test that the default execution context is NONE +TEST(ExecutionContextManager, DefaultExecutionContext) { + chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); + EXPECT_EQ(executionContextManager.getExecutionContext(), chai::expt::ExecutionContext::NONE); +} + +// Test setting and getting the execution context +TEST(ExecutionContextManager, ExecutionContext) { + chai::expt::ExecutionContextManager& executionContextManager = chai::expt::ExecutionContextManager::getInstance(); + chai::expt::ExecutionContext executionContext = chai::expt::ExecutionContext::HOST; + executionContextManager.setExecutionContext(executionContext); + EXPECT_EQ(executionContextManager.getExecutionContext(), executionContext); +} \ No newline at end of file diff --git a/tests/unit/expt/TestCopyHidingArray.cpp b/tests/unit/expt/TestCopyHidingArray.cpp new file mode 100644 index 00000000..04005e51 --- /dev/null +++ b/tests/unit/expt/TestCopyHidingArray.cpp @@ -0,0 +1,181 @@ +#include "gtest/gtest.h" +#include "chai/expt/CopyHidingArray.hpp" +#include + +namespace chai { +namespace expt { + +// Fixture class for CopyHidingArray tests +class CopyHidingArrayTest : public ::testing::Test { +protected: + void SetUp() override { + // Get ResourceManager instance + m_resource_manager = &umpire::ResourceManager::getInstance(); + + // Get default allocators + m_cpu_allocator = m_resource_manager->getAllocator("HOST"); + m_gpu_allocator = m_resource_manager->getAllocator("DEVICE"); + } + + umpire::ResourceManager* m_resource_manager; + umpire::Allocator m_cpu_allocator; + umpire::Allocator m_gpu_allocator; + + // Helper to fill array with test data + void fillWithTestData(CopyHidingArray& arr, int start_val = 1) { + int* data = arr.data(ExecutionContext::CPU); + for (size_t i = 0; i < arr.size(); ++i) { + data[i] = start_val + i; + } + } + + // Helper to verify array contents + void verifyArrayContents(const CopyHidingArray& arr, int start_val = 1) { + const int* data = arr.data(ExecutionContext::CPU); + for (size_t i = 0; i < arr.size(); ++i) { + EXPECT_EQ(data[i], start_val + i); + } + } +}; + +// Test default constructor +TEST_F(CopyHidingArrayTest, DefaultConstructor) { + CopyHidingArray arr; + EXPECT_EQ(arr.size(), 0); + EXPECT_EQ(arr.data(ExecutionContext::NONE), nullptr); +} + +// Test constructor with allocators +TEST_F(CopyHidingArrayTest, AllocatorConstructor) { + CopyHidingArray arr(m_cpu_allocator, m_gpu_allocator); + EXPECT_EQ(arr.size(), 0); +} + +// Test size constructor +TEST_F(CopyHidingArrayTest, SizeConstructor) { + const size_t size = 100; + CopyHidingArray arr(size); + EXPECT_EQ(arr.size(), size); +} + +// Test size and allocator constructor +TEST_F(CopyHidingArrayTest, SizeAndAllocatorConstructor) { + const size_t size = 100; + CopyHidingArray arr(size, m_cpu_allocator, m_gpu_allocator); + EXPECT_EQ(arr.size(), size); +} + +// Test copy constructor +TEST_F(CopyHidingArrayTest, CopyConstructor) { + const size_t size = 100; + CopyHidingArray arr1(size); + fillWithTestData(arr1); + + CopyHidingArray arr2(arr1); + EXPECT_EQ(arr2.size(), size); + verifyArrayContents(arr2); +} + +// Test move constructor +TEST_F(CopyHidingArrayTest, MoveConstructor) { + const size_t size = 100; + CopyHidingArray arr1(size); + fillWithTestData(arr1); + + CopyHidingArray arr2(std::move(arr1)); + EXPECT_EQ(arr2.size(), size); + EXPECT_EQ(arr1.size(), 0); + verifyArrayContents(arr2); +} + +// Test copy assignment +TEST_F(CopyHidingArrayTest, CopyAssignment) { + const size_t size = 100; + CopyHidingArray arr1(size); + fillWithTestData(arr1); + + CopyHidingArray arr2; + arr2 = arr1; + EXPECT_EQ(arr2.size(), size); + verifyArrayContents(arr2); +} + +// Test move assignment +TEST_F(CopyHidingArrayTest, MoveAssignment) { + const size_t size = 100; + CopyHidingArray arr1(size); + fillWithTestData(arr1); + + CopyHidingArray arr2; + arr2 = std::move(arr1); + EXPECT_EQ(arr2.size(), size); + EXPECT_EQ(arr1.size(), 0); + verifyArrayContents(arr2); +} + +// Test data access and coherence +TEST_F(CopyHidingArrayTest, DataCoherence) { + const size_t size = 100; + CopyHidingArray arr(size); + + // Fill on CPU + int* cpu_data = arr.data(ExecutionContext::CPU); + for (size_t i = 0; i < size; ++i) { + cpu_data[i] = i + 1; + } + + // Access on GPU (this will cause a copy) + int* gpu_data = arr.data(ExecutionContext::GPU); + // Here we would run a GPU kernel, but for testing we'll just verify the copy happens + + // Modify on GPU (simulation for test) + // In a real test, this would be done via a GPU kernel + for (size_t i = 0; i < size; ++i) { + gpu_data[i] *= 2; + } + + // Access back on CPU (should trigger a copy back) + cpu_data = arr.data(ExecutionContext::CPU); + + // Verify data was copied back correctly + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(cpu_data[i], (i + 1) * 2); + } +} + +// Test resize functionality +TEST_F(CopyHidingArrayTest, Resize) { + const size_t initial_size = 50; + const size_t new_size = 100; + + CopyHidingArray arr(initial_size); + fillWithTestData(arr); + + EXPECT_EQ(arr.size(), initial_size); + + arr.resize(new_size); + EXPECT_EQ(arr.size(), new_size); + + // First initial_size elements should still have their values + int* data = arr.data(ExecutionContext::CPU); + for (size_t i = 0; i < initial_size; ++i) { + EXPECT_EQ(data[i], i + 1); + } +} + +// Test const data access +TEST_F(CopyHidingArrayTest, ConstDataAccess) { + const size_t size = 100; + CopyHidingArray arr(size); + fillWithTestData(arr); + + const CopyHidingArray& const_arr = arr; + const int* const_data = const_arr.data(ExecutionContext::CPU); + + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(const_data[i], i + 1); + } +} + +} // namespace expt +} // namespace chai \ No newline at end of file From 4de13667d401320262778e3bd174e7efa2049fd9 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 29 Aug 2025 08:54:13 -0700 Subject: [PATCH 41/49] Clean up PinnedArrayContainer class --- src/chai/expt/ExecutionContextManager.hpp | 72 +++++++++- src/chai/expt/PinnedArray.hpp | 114 --------------- src/chai/expt/PinnedArrayContainer.hpp | 161 ++++++++++++++++++++++ 3 files changed, 231 insertions(+), 116 deletions(-) delete mode 100644 src/chai/expt/PinnedArray.hpp create mode 100644 src/chai/expt/PinnedArrayContainer.hpp diff --git a/src/chai/expt/ExecutionContextManager.hpp b/src/chai/expt/ExecutionContextManager.hpp index 937e5d70..e6f5abec 100644 --- a/src/chai/expt/ExecutionContextManager.hpp +++ b/src/chai/expt/ExecutionContextManager.hpp @@ -33,12 +33,12 @@ namespace expt { } /*! - * \brief Private copy constructor to prevent copying. + * \brief Deleted copy constructor to prevent copying. */ ExecutionContextManager(const ExecutionContextManager&) = delete; /*! - * \brief Private assignment operator to prevent assignment. + * \brief Deleted assignment operator to prevent assignment. */ ExecutionContextManager& operator=(const ExecutionContextManager&) = delete; @@ -58,6 +58,69 @@ namespace expt { */ void setExecutionContext(ExecutionContext context) { m_execution_context = context; + m_synchronized[context] = false; + } + + /*! + * \brief Synchronize the given execution context. + * + * \param context The execution context that needs synchronization. + */ + void synchronize(ExecutionContext context) { + auto it = m_synchronized.find(context); + + if (it != m_synchronized.end()) { + #if defined(CHAI_ENABLE_DEVICE) + if (context == ExecutionContext::DEVICE) { +#if defined(CHAI_ENABLE_CUDA) + cudaDeviceSynchronize(); +#elif defined(CHAI_ENABLE_HIP) + hipDeviceSynchronize(); +#endif + } + } + bool& unsynchronized = m_unsynchronized[context]; + + if (unsynchronized) { +#if defined(CHAI_ENABLE_DEVICE) + if (context == ExecutionContext::DEVICE) { +#if defined(CHAI_ENABLE_CUDA) + cudaDeviceSynchronize(); +#elif defined(CHAI_ENABLE_HIP) + hipDeviceSynchronize(); +#endif + } + + unsynchronized = false; + } + } + + /*! + * \brief Check if a specific execution context needs synchronization. + * + * \param context The execution context to check. + * \return True if the context needs synchronization, false otherwise. + */ + bool isSynchronized(ExecutionContext context) const { + auto it = m_synchronized.find(context); + + if (it == m_synchronized.end()) { + return true; + } + else { + return it->second; + } + } + + /*! + * \brief Mark the given execution context as synchronized. + * + * This should only be called after synchronization has been performed. + * + * \param context The execution context to clear the synchronization flag for. + */ + void markSynchronized(ExecutionContext context) { + m_synchronized[context] = true; } private: @@ -70,6 +133,11 @@ namespace expt { * \brief The current execution context. */ ExecutionContext m_execution_context = ExecutionContext::NONE; + + /*! + * \brief Map for tracking which execution contexts are synchronized. + */ + std::unordered_map m_synchronized; }; // class ExecutionContextManager } // namespace expt } // namespace chai diff --git a/src/chai/expt/PinnedArray.hpp b/src/chai/expt/PinnedArray.hpp deleted file mode 100644 index 67bf1f3b..00000000 --- a/src/chai/expt/PinnedArray.hpp +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef CHAI_PINNED_ARRAY_HPP -#define CHAI_PINNED_ARRAY_HPP - -#include "umpire/ResourceManager.hpp" - -namespace chai { -namespace expt { - /*! - * \class PinnedArray - * - * \brief Controls the coherence of an array on the host and device. - */ - template - class PinnedArray - public: - PinnedArray() = default; - - explicit PinnedArray(int allocatorID) : - m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} - { - } - - explicit PinnedArray(std::size_t size, int allocatorID) : - PinnedArray(allocatorID), - m_size{size} - { - m_data = m_allocator.allocate(m_size); - - // TODO: Default initialize on host or device? - for (std::size_t i = 0; i < size; ++i) { - new (m_data[i]) T(); - } - } - - PinnedArray(const PinnedArray& other) : - m_size{other.m_size}, - m_allocator{other.m_allocator} - { - m_data = m_allocator.allocate(m_size); - umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size); - } - - PinnedArray(PinnedArray&& other) : - m_data{other.m_data}, - m_size{other.m_size}, - m_allocator{other.m_allocator} - { - other.m_data = nullptr; - other.m_size = 0; - other.m_allocator = umpire::Allocator(); - } - - PinnedArray& operator=(const PinnedArray& other) { - if (this != &other) { // Prevent self-assignment - m_allocator.deallocate(m_data); - m_allocator = other.m_allocator; - m_size = other.m_size; - m_data = m_allocator.allocate(m_size); - umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size); - } - - return *this; - } - - PinnedArray& operator=(PinnedArray&& other) { - if (this != &other) { // Prevent self-move - m_allocator.deallocate(m_data); - m_allocator = other.m_allocator; - m_size = other.m_size; - m_data = other.m_data; - - other.m_data = nullptr; - other.m_size = nullptr; - other.m_allocator = umpire::Allocator(); - } - - return *this; - } - - /*! - * \brief Virtual destructor. - */ - ~PinnedArray() { - m_allocator.deallocate(m_data); - } - - /*! - * \brief Get the number of elements. - */ - size_t size() const { - return m_size; - } - - T* data(ExecutionSpace space) { - if (space == CPU) { -#if (__CUDACC__) - cudaDeviceSynchronize(); -#elif(__HIPCC__) - hipDeviceSynchronize(); -#endif - } - - return m_data; - } - - private: - T* m_data{nullptr}; - size_t m_size{0}; - umpire::Allocator m_allocator{}; - }; // class PinnedArray -} // namespace expt -} // namespace chai - -#endif // CHAI_PINNED_ARRAY_HPP diff --git a/src/chai/expt/PinnedArrayContainer.hpp b/src/chai/expt/PinnedArrayContainer.hpp new file mode 100644 index 00000000..1b842c6c --- /dev/null +++ b/src/chai/expt/PinnedArrayContainer.hpp @@ -0,0 +1,161 @@ +#ifndef CHAI_PINNED_ARRAY_CONTAINER_HPP +#define CHAI_PINNED_ARRAY_CONTAINER_HPP + +#include "chai/expt/ExecutionContext.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + /*! + * \class PinnedArrayContainer + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class PinnedArrayContainer + public: + PinnedArrayContainer() = default; + + explicit PinnedArrayContainer(const umpire::Allocator& allocator) : + m_allocator{allocator} + { + } + + PinnedArrayContainer(std::size_t size, const umpire::Allocator& allocator) : + m_size{size}, + m_allocator{allocator} + { + m_data = m_allocator.allocate(m_size * sizeof(T)); + // TODO: Initialization + } + + explicit PinnedArrayContainer(int allocatorID) : + m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} + { + } + + PinnedArrayContainer(std::size_t size, int allocatorID) : + m_size{size}, + m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} + { + m_data = m_allocator.allocate(m_size * sizeof(T)); + // TODO: Initialization + } + + PinnedArrayContainer(const PinnedArrayContainer& other) : + m_size{other.m_size}, + m_allocator{other.m_allocator} + { + m_data = m_allocator.allocate(m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + m_last_execution_context = ExecutionContext::DEVICE; + } + + PinnedArrayContainer(PinnedArrayContainer&& other) : + m_data{other.m_data}, + m_size{other.m_size}, + m_last_execution_context{other.m_last_execution_context}, + m_allocator{other.m_allocator} + { + other.m_data = nullptr; + other.m_size = 0; + other.m_last_execution_context = NONE; + other.m_allocator = umpire::Allocator(); + } + + PinnedArrayContainer& operator=(const PinnedArrayContainer& other) { + if (&other != this) { // Prevent self-assignment + m_allocator.deallocate(m_data); + + m_size = other.m_size; + m_allocator = other.m_allocator; + m_data = m_allocator.allocate(m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + m_last_execution_context = ExecutionContext::DEVICE; + } + + return *this; + } + + PinnedArrayContainer& operator=(PinnedArrayContainer&& other) { + if (&other != this) { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_last_execution_context = other.m_last_execution_context; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + other.m_last_execution_context = ExecutionContext::NONE; + other.m_allocator = umpire::Allocator(); + } + + return *this; + } + + /*! + * \brief Destructor. + */ + ~PinnedArrayContainer() { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Get the number of elements. + */ + size_t size() const { + return m_size; + } + + T* data(ExecutionContext executionContext) { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = executionContext; + } + + return m_data; + } + + const T* data(ExecutionContext executionContext) const { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = ExecutionContext::NONE; + } + + return m_data; + } + + T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = executionContext; + } + + return m_data[i]; + } + + const T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = ExecutionContext::NONE; + } + + return m_data[i]; + } + + private: + T* m_data{nullptr}; + size_t m_size{0}; + ExecutionContext m_last_execution_context{ExecutionContext::NONE}; + umpire::Allocator m_allocator{}; + }; // class PinnedArrayContainer +} // namespace expt +} // namespace chai + +#endif // CHAI_PINNED_ARRAY_CONTAINER_HPP From 2fb4b4b00768b8e719e0f48162f5d0975734267f Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 29 Aug 2025 16:33:13 -0700 Subject: [PATCH 42/49] Add UnifiedMemoryArray --- src/chai/expt/UnifiedMemoryArray.hpp | 302 +++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 src/chai/expt/UnifiedMemoryArray.hpp diff --git a/src/chai/expt/UnifiedMemoryArray.hpp b/src/chai/expt/UnifiedMemoryArray.hpp new file mode 100644 index 00000000..36cdad5f --- /dev/null +++ b/src/chai/expt/UnifiedMemoryArray.hpp @@ -0,0 +1,302 @@ +#ifndef CHAI_UNIFIED_MEMORY_ARRAY_HPP +#define CHAI_UNIFIED_MEMORY_ARRAY_HPP + +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ExecutionContextManager.hpp" + +#include +#include + +namespace chai { +namespace expt { + /*! + * \class UnifiedMemoryArray + * + * \brief A data structure for managing the lifetime and coherence of a + * unified memory array, meaning an array with a single address + * that is accessible from all processors/devices in a system. + * + * This data structure is designed for use with the ExecutionContextManager. + * When the execution context is set and the copy constructor is triggered, + * the underlying data is made coherent in the current execution context. + * + * This lends itself particularly well to an elegant programming model + * where loop bodies are replaced with lambda expressions that capture + * variables in the current scope by copy. If the execution context has + * already been set, the lambda expression can be executed in that context. + * Otherwise, the execution context must be set and then a copy of the + * lambda expression (which triggers a copy of all the captured variables) + * can be executed in that context. This latter case is how CHAI works + * with RAJA via a RAJA plugin. + * + * This model works well for APUs where the CPU and GPU have the same + * physical memory. It also works for pinned (i.e. page-locked) memory. + * + * Example using RAJA and CHAI: + * + * \code + * #include "chai/UnifiedMemoryArray.hpp" + * #include "RAJA/RAJA.hpp" + * + * constexpr int CUDA_BLOCK_SIZE = 256; + * constexpr int ASYNCHRONOUS = true; + * + * int size = 10000; + * chai::UnifiedMemoryArray a(size); + * + * int offset = 42; + * + * // Both `a` and `offset` are captured by copy into the lambda expression. + * // The execution context is not set, so the copy constructor of `a` + * // results in a shallow copy with nothing done related to coherence + * // management. RAJA then calls the CHAI plugin, which sets the current + * // execution context. At that point, the lambda expression is then copied, + * // which again triggers the copy constructor of `a`. This time, the data + * // is made coherent on the device (which is essentially a no-op because it + * // has not been accessed in any other execution context yet). After this, + * // RAJA calls the CHAI plugin to reset the execution context and launches + * // a CUDA kernel that executes the lambda expression for each index in + * // [0, size). + * RAJA::forall>(RAJA::TypedRangeSegment(0, size), [=] __device__ (int i) { + * a[i] = offset + i; + * }); + * + * // The same process as described above happens, except that now `a` is + * // made coherent on the host by synchronizing the device and the lambda + * // expression is evaluated on the host. + * RAJA::forall(RAJA::TypedRangeSegment(0, size), [=] __device__ (int i) { + * a[i] = i - offset; + * }); + * \endcode + */ + template + class UnifiedMemoryArray { + public: + /*! + * \brief Constructs an empty array. + */ + constexpr UnifiedMemoryArray() noexcept = default; + + explicit UnifiedMemoryArray(const umpire::Allocator& allocator) + : m_manager{new UnifiedMemoryManager(allocator)} + { + } + + UnifiedMemoryArray(std::size_t size, const umpire::Allocator& allocator) + : m_size{size}, + m_manager{new UnifiedMemoryManager(size * sizeof(T), allocator)} + { + } + + explicit UnifiedMemoryArray(int allocatorID) + : m_manager{new UnifiedMemoryManager(allocatorID)} + { + } + + UnifiedMemoryArray(std::size_t size, int allocatorID) + : m_size{size}, + m_manager{new UnifiedMemoryManager(size * sizeof(T), allocatorID)} + { + } + + explicit UnifiedMemoryArray(UnifiedMemoryManager* manager) + : m_manager{manager} + { + if (m_manager) + { + m_size = m_manager->size() / sizeof(T); + } + } + + /*! + * \brief Constructs a shallow copy of an array from another and makes + * the data coherent in the current execution space. + * + * \param other The other array. + * + * \note This is a shallow copy. + */ + CHAI_HOST_DEVICE UnifiedMemoryArray(const UnifiedMemoryArray& other) : + m_data{other.m_data}, + m_size{other.m_size} +#if !defined(CHAI_DEVICE_COMPILE) + , m_manager{other.m_manager} +#endif + { + update(); + } + + /*! + * \brief Sets the array manager for this UnifiedMemoryArray. + * + * \param manager The new array manager to be set. + * + * \post The UnifiedMemoryArray takes ownership of the new manager objet. + */ + void setManager(UnifiedMemoryManager* manager) + { + delete m_manager; + m_manager = manager; + + if (m_manager) + { + m_size = m_manager->size() / sizeof(T); + } + } + + /*! + * \brief Get the array manager associated with this UnifiedMemoryArray. + * + * \return A pointer to the array manager. + */ + UnifiedMemoryManager* getManager() const + { + return m_manager; + } + + /*! + * \brief Resizes the array to the specified new size. + * + * \param newSize The new size to resize the array to. + * + * \note This method updates the size of the array and triggers a resize operation in the array manager if it exists. + * If no array manager is associated, an exception is thrown. + */ + void resize(std::size_t newSize) + { + if (m_manager) { + m_size = newSize; + m_manager->resize(newSize * sizeof(T)); + } + else { + m_manager = new UnifiedMemoryManager(newSize * sizeof(T)); + } + } + + /*! + * \brief Frees the resources associated with this array. + * + * \note Once free has been called, it is invalid to use any other copies + * of this array (since copies are shallow). + */ + void free() { + m_data = nullptr; + m_size = 0; + delete m_manager; + m_manager = nullptr; + } + + /*! + * \brief Get the number of elements in the array. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE std::size_t size() const + { + return m_size; + } + + CHAI_HOST_DEVICE update() const + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(!std::is_const::value)); + m_size = m_manager->size() / sizeof(T); + } +#endif + } + + /*! + * \brief Get a pointer to the element data in the current execution context. + * + * \return A pointer to the element data in the current execution context. + */ + CHAI_HOST_DEVICE T* data() const + { + update(); + return m_data; + } + + /*! + * \brief Get a const pointer to the element data in the current execution context. + * + * \return A const pointer to the element data in the current execution context. + */ + CHAI_HOST_DEVICE const T* cdata() const { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(false)); + m_size = m_manager->size() / sizeof(T); + } +#endif + return m_data; + } + + /*! + * \brief Get the ith element in the array. + * + * \param i The index of the element to retrieve. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE T& operator[](std::size_t i) const { + return m_data[i]; + } + + /*! + * \brief Get the value of the element at the specified index. + * + * \param i The index of the element to retrieve. + * + * \return The value of the element at the specified index. + * + * \throw std::runtime_exception if unable to retrieve the element. + */ + T get(std::size_t i) const { + if (m_manager) { + return m_manager->get(i); + } + else { + throw std::out_of_range(); + } + } + + /*! + * \brief Set a value at a specified index in the array. + * + * \param i The index where the value is to be set. + * \param value The value to set at the specified index. + * + * \throw std::runtime_exception if the array manager is not associated with the UnifiedMemoryArray. + */ + void set(std::size_t i, const T& value) { + if (m_manager) { + m_manager->set(i, value); + } + else { + throw std::out_of_range(); + } + } + + private: + /*! + * The array that is coherent in the current execution space. + */ + T* m_data{nullptr}; + + /*! + * The number of elements in the array. + */ + std::size_t m_size{0}; + + /*! + * The array manager controls the coherence of the array. + */ + UnifiedMemoryManager* m_manager{nullptr}; + }; // class UnifiedMemoryArray +} // namespace expt +} // namespace chai + +#endif // CHAI_UNIFIED_MEMORY_ARRAY_HPP From 5387d312876b52a8a9cce2f935ddfc93e48abee5 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Fri, 29 Aug 2025 17:01:37 -0700 Subject: [PATCH 43/49] Rename types --- src/chai/expt/UnifiedMemoryArray.hpp | 338 +++++++++---------------- src/chai/expt/UnifiedMemoryPointer.hpp | 302 ++++++++++++++++++++++ 2 files changed, 421 insertions(+), 219 deletions(-) create mode 100644 src/chai/expt/UnifiedMemoryPointer.hpp diff --git a/src/chai/expt/UnifiedMemoryArray.hpp b/src/chai/expt/UnifiedMemoryArray.hpp index 36cdad5f..4d76a436 100644 --- a/src/chai/expt/UnifiedMemoryArray.hpp +++ b/src/chai/expt/UnifiedMemoryArray.hpp @@ -1,300 +1,200 @@ #ifndef CHAI_UNIFIED_MEMORY_ARRAY_HPP #define CHAI_UNIFIED_MEMORY_ARRAY_HPP -#include "chai/expt/ArrayManager.hpp" -#include "chai/expt/ExecutionContextManager.hpp" - -#include -#include +#include "chai/expt/ExecutionContext.hpp" +#include "umpire/ResourceManager.hpp" namespace chai { namespace expt { /*! * \class UnifiedMemoryArray * - * \brief A data structure for managing the lifetime and coherence of a + * \brief A container for managing the lifetime and coherence of a * unified memory array, meaning an array with a single address * that is accessible from all processors/devices in a system. * - * This data structure is designed for use with the ExecutionContextManager. - * When the execution context is set and the copy constructor is triggered, - * the underlying data is made coherent in the current execution context. - * - * This lends itself particularly well to an elegant programming model - * where loop bodies are replaced with lambda expressions that capture - * variables in the current scope by copy. If the execution context has - * already been set, the lambda expression can be executed in that context. - * Otherwise, the execution context must be set and then a copy of the - * lambda expression (which triggers a copy of all the captured variables) - * can be executed in that context. This latter case is how CHAI works - * with RAJA via a RAJA plugin. + * This container should be used in tandem with the ExecutionContextManager. + * Together, they provide a programming model where work (e.g. a kernel) + * is generally performed asynchronously, with synchronization occurring + * only as needed for coherence of the array. For example, if the array is + * written to in an asynchronize kernel on a GPU, then the GPU will be + * synchronized if the array needs to be accessed on the CPU. * * This model works well for APUs where the CPU and GPU have the same - * physical memory. It also works for pinned (i.e. page-locked) memory. + * physical memory. It also works for pinned (i.e. page-locked) memory + * and in some cases for pageable memory, though no pre-fetching is + * performed. * - * Example using RAJA and CHAI: + * Example: * * \code - * #include "chai/UnifiedMemoryArray.hpp" - * #include "RAJA/RAJA.hpp" + * // Create a UnifiedMemoryArray with size 100 and default allocator + * int size = 10000; + * UnifiedMemoryArray array(size); * - * constexpr int CUDA_BLOCK_SIZE = 256; - * constexpr int ASYNCHRONOUS = true; + * // Access elements on the device + * std::span device_view(array.data(ExecutionContext::DEVICE, array.size()); * - * int size = 10000; - * chai::UnifiedMemoryArray a(size); + * // Launch a kernel that modifies device_view. + * // Note that this example relies on c++20 and the ability to use constexpr + * // host code on the device. * - * int offset = 42; + * // Access elements on the host. This will synchronize the device. + * std::span host_view(array.data(ExecutionContext::HOST), array.size()); * - * // Both `a` and `offset` are captured by copy into the lambda expression. - * // The execution context is not set, so the copy constructor of `a` - * // results in a shallow copy with nothing done related to coherence - * // management. RAJA then calls the CHAI plugin, which sets the current - * // execution context. At that point, the lambda expression is then copied, - * // which again triggers the copy constructor of `a`. This time, the data - * // is made coherent on the device (which is essentially a no-op because it - * // has not been accessed in any other execution context yet). After this, - * // RAJA calls the CHAI plugin to reset the execution context and launches - * // a CUDA kernel that executes the lambda expression for each index in - * // [0, size). - * RAJA::forall>(RAJA::TypedRangeSegment(0, size), [=] __device__ (int i) { - * a[i] = offset + i; - * }); + * for (int i = 0; i < size; ++i) { + * std::cout << host_view[i] << "\n"; + * } * - * // The same process as described above happens, except that now `a` is - * // made coherent on the host by synchronizing the device and the lambda - * // expression is evaluated on the host. - * RAJA::forall(RAJA::TypedRangeSegment(0, size), [=] __device__ (int i) { - * a[i] = i - offset; - * }); + * // Access and modify individual elements in the container. + * // This should be used sparingly or it will tank performance. + * // Getting the last element after performing a scan is one use case. + * array.get(ExecutionContext::HOST, size - 1) = 10; * \endcode */ template - class UnifiedMemoryArray { + class UnifiedMemoryArray public: - /*! - * \brief Constructs an empty array. - */ - constexpr UnifiedMemoryArray() noexcept = default; + UnifiedMemoryArray() = default; - explicit UnifiedMemoryArray(const umpire::Allocator& allocator) - : m_manager{new UnifiedMemoryManager(allocator)} + explicit UnifiedMemoryArray(const umpire::Allocator& allocator) : + m_allocator{allocator} { } - UnifiedMemoryArray(std::size_t size, const umpire::Allocator& allocator) - : m_size{size}, - m_manager{new UnifiedMemoryManager(size * sizeof(T), allocator)} + UnifiedMemoryArray(std::size_t size, const umpire::Allocator& allocator) : + m_size{size}, + m_allocator{allocator} { + m_data = m_allocator.allocate(m_size * sizeof(T)); + // TODO: Investigate if/when to do initialization } - explicit UnifiedMemoryArray(int allocatorID) - : m_manager{new UnifiedMemoryManager(allocatorID)} + explicit UnifiedMemoryArray(int allocatorID) : + m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} { } - UnifiedMemoryArray(std::size_t size, int allocatorID) - : m_size{size}, - m_manager{new UnifiedMemoryManager(size * sizeof(T), allocatorID)} + UnifiedMemoryArray(std::size_t size, int allocatorID) : + m_size{size}, + m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} { + m_data = m_allocator.allocate(m_size * sizeof(T)); + // TODO: Investigate if/when to do initialization } - explicit UnifiedMemoryArray(UnifiedMemoryManager* manager) - : m_manager{manager} + UnifiedMemoryArray(const UnifiedMemoryArray& other) : + m_size{other.m_size}, + m_allocator{other.m_allocator} { - if (m_manager) - { - m_size = m_manager->size() / sizeof(T); - } + m_data = m_allocator.allocate(m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + m_last_execution_context = ExecutionContext::DEVICE; } - /*! - * \brief Constructs a shallow copy of an array from another and makes - * the data coherent in the current execution space. - * - * \param other The other array. - * - * \note This is a shallow copy. - */ - CHAI_HOST_DEVICE UnifiedMemoryArray(const UnifiedMemoryArray& other) : + UnifiedMemoryArray(UnifiedMemoryArray&& other) : m_data{other.m_data}, - m_size{other.m_size} -#if !defined(CHAI_DEVICE_COMPILE) - , m_manager{other.m_manager} -#endif - { - update(); - } - - /*! - * \brief Sets the array manager for this UnifiedMemoryArray. - * - * \param manager The new array manager to be set. - * - * \post The UnifiedMemoryArray takes ownership of the new manager objet. - */ - void setManager(UnifiedMemoryManager* manager) + m_size{other.m_size}, + m_last_execution_context{other.m_last_execution_context}, + m_allocator{other.m_allocator} { - delete m_manager; - m_manager = manager; - - if (m_manager) - { - m_size = m_manager->size() / sizeof(T); + other.m_data = nullptr; + other.m_size = 0; + other.m_last_execution_context = NONE; + other.m_allocator = umpire::Allocator(); + } + + UnifiedMemoryArray& operator=(const UnifiedMemoryArray& other) { + if (&other != this) { // Prevent self-assignment + m_allocator.deallocate(m_data); + + m_size = other.m_size; + m_allocator = other.m_allocator; + m_data = m_allocator.allocate(m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::DEVICE); + umpire::ResourceManager::getInstance().copy(other.m_data, m_data, m_size * sizeof(T)); + ExecutionContextManager::getInstance().setExecutionContext(ExecutionContext::NONE); + m_last_execution_context = ExecutionContext::DEVICE; } - } - /*! - * \brief Get the array manager associated with this UnifiedMemoryArray. - * - * \return A pointer to the array manager. - */ - UnifiedMemoryManager* getManager() const - { - return m_manager; + return *this; } - /*! - * \brief Resizes the array to the specified new size. - * - * \param newSize The new size to resize the array to. - * - * \note This method updates the size of the array and triggers a resize operation in the array manager if it exists. - * If no array manager is associated, an exception is thrown. - */ - void resize(std::size_t newSize) - { - if (m_manager) { - m_size = newSize; - m_manager->resize(newSize * sizeof(T)); - } - else { - m_manager = new UnifiedMemoryManager(newSize * sizeof(T)); + UnifiedMemoryArray& operator=(UnifiedMemoryArray&& other) { + if (&other != this) { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_last_execution_context = other.m_last_execution_context; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + other.m_last_execution_context = ExecutionContext::NONE; + other.m_allocator = umpire::Allocator(); } + + return *this; } /*! - * \brief Frees the resources associated with this array. - * - * \note Once free has been called, it is invalid to use any other copies - * of this array (since copies are shallow). + * \brief Destructor. */ - void free() { - m_data = nullptr; - m_size = 0; - delete m_manager; - m_manager = nullptr; + ~UnifiedMemoryArray() { + m_allocator.deallocate(m_data); } /*! - * \brief Get the number of elements in the array. - * - * \pre The copy constructor has been called with the execution space - * set to CPU or GPU (e.g. by the RAJA plugin). + * \brief Get the number of elements. */ - CHAI_HOST_DEVICE std::size_t size() const - { + size_t size() const { return m_size; } - CHAI_HOST_DEVICE update() const - { -#if !defined(CHAI_DEVICE_COMPILE) - if (m_manager) { - m_data = static_cast(m_manager->data(!std::is_const::value)); - m_size = m_manager->size() / sizeof(T); + T* data(ExecutionContext executionContext) { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = executionContext; } -#endif - } - /*! - * \brief Get a pointer to the element data in the current execution context. - * - * \return A pointer to the element data in the current execution context. - */ - CHAI_HOST_DEVICE T* data() const - { - update(); return m_data; } - /*! - * \brief Get a const pointer to the element data in the current execution context. - * - * \return A const pointer to the element data in the current execution context. - */ - CHAI_HOST_DEVICE const T* cdata() const { -#if !defined(CHAI_DEVICE_COMPILE) - if (m_manager) { - m_data = static_cast(m_manager->data(false)); - m_size = m_manager->size() / sizeof(T); + const T* data(ExecutionContext executionContext) const { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = ExecutionContext::NONE; } -#endif + return m_data; } - /*! - * \brief Get the ith element in the array. - * - * \param i The index of the element to retrieve. - * - * \pre The copy constructor has been called with the execution space - * set to CPU or GPU (e.g. by the RAJA plugin). - */ - CHAI_HOST_DEVICE T& operator[](std::size_t i) const { + T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = executionContext; + } + return m_data[i]; } - /*! - * \brief Get the value of the element at the specified index. - * - * \param i The index of the element to retrieve. - * - * \return The value of the element at the specified index. - * - * \throw std::runtime_exception if unable to retrieve the element. - */ - T get(std::size_t i) const { - if (m_manager) { - return m_manager->get(i); - } - else { - throw std::out_of_range(); + const T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_last_execution_context) { + ExecutionContextManager::getInstance().synchronize(m_last_execution_context); + m_last_execution_context = ExecutionContext::NONE; } - } - /*! - * \brief Set a value at a specified index in the array. - * - * \param i The index where the value is to be set. - * \param value The value to set at the specified index. - * - * \throw std::runtime_exception if the array manager is not associated with the UnifiedMemoryArray. - */ - void set(std::size_t i, const T& value) { - if (m_manager) { - m_manager->set(i, value); - } - else { - throw std::out_of_range(); - } + return m_data[i]; } private: - /*! - * The array that is coherent in the current execution space. - */ T* m_data{nullptr}; - - /*! - * The number of elements in the array. - */ - std::size_t m_size{0}; - - /*! - * The array manager controls the coherence of the array. - */ - UnifiedMemoryManager* m_manager{nullptr}; + size_t m_size{0}; + ExecutionContext m_last_execution_context{ExecutionContext::NONE}; + umpire::Allocator m_allocator{}; }; // class UnifiedMemoryArray } // namespace expt } // namespace chai diff --git a/src/chai/expt/UnifiedMemoryPointer.hpp b/src/chai/expt/UnifiedMemoryPointer.hpp new file mode 100644 index 00000000..a1f8530f --- /dev/null +++ b/src/chai/expt/UnifiedMemoryPointer.hpp @@ -0,0 +1,302 @@ +#ifndef CHAI_UNIFIED_MEMORY_POINTER_HPP +#define CHAI_UNIFIED_MEMORY_POINTER_HPP + +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ExecutionContextManager.hpp" + +#include +#include + +namespace chai { +namespace expt { + /*! + * \class UnifiedMemoryPointer + * + * \brief A data structure for managing the lifetime and coherence of a + * unified memory array, meaning an array with a single address + * that is accessible from all processors/devices in a system. + * + * This data structure is designed for use with the ExecutionContextManager. + * When the execution context is set and the copy constructor is triggered, + * the underlying data is made coherent in the current execution context. + * + * This lends itself particularly well to an elegant programming model + * where loop bodies are replaced with lambda expressions that capture + * variables in the current scope by copy. If the execution context has + * already been set, the lambda expression can be executed in that context. + * Otherwise, the execution context must be set and then a copy of the + * lambda expression (which triggers a copy of all the captured variables) + * can be executed in that context. This latter case is how CHAI works + * with RAJA via a RAJA plugin. + * + * This model works well for APUs where the CPU and GPU have the same + * physical memory. It also works for pinned (i.e. page-locked) memory. + * + * Example using RAJA and CHAI: + * + * \code + * #include "chai/UnifiedMemoryPointer.hpp" + * #include "RAJA/RAJA.hpp" + * + * constexpr int CUDA_BLOCK_SIZE = 256; + * constexpr int ASYNCHRONOUS = true; + * + * int size = 10000; + * chai::UnifiedMemoryPointer a(size); + * + * int offset = 42; + * + * // Both `a` and `offset` are captured by copy into the lambda expression. + * // The execution context is not set, so the copy constructor of `a` + * // results in a shallow copy with nothing done related to coherence + * // management. RAJA then calls the CHAI plugin, which sets the current + * // execution context. At that point, the lambda expression is then copied, + * // which again triggers the copy constructor of `a`. This time, the data + * // is made coherent on the device (which is essentially a no-op because it + * // has not been accessed in any other execution context yet). After this, + * // RAJA calls the CHAI plugin to reset the execution context and launches + * // a CUDA kernel that executes the lambda expression for each index in + * // [0, size). + * RAJA::forall>(RAJA::TypedRangeSegment(0, size), [=] __device__ (int i) { + * a[i] = offset + i; + * }); + * + * // The same process as described above happens, except that now `a` is + * // made coherent on the host by synchronizing the device and the lambda + * // expression is evaluated on the host. + * RAJA::forall(RAJA::TypedRangeSegment(0, size), [=] __device__ (int i) { + * a[i] = i - offset; + * }); + * \endcode + */ + template + class UnifiedMemoryPointer { + public: + /*! + * \brief Constructs an empty array. + */ + constexpr UnifiedMemoryPointer() noexcept = default; + + explicit UnifiedMemoryPointer(const umpire::Allocator& allocator) + : m_manager{new UnifiedMemoryArray(allocator)} + { + } + + UnifiedMemoryPointer(std::size_t size, const umpire::Allocator& allocator) + : m_size{size}, + m_manager{new UnifiedMemoryArray(size * sizeof(T), allocator)} + { + } + + explicit UnifiedMemoryPointer(int allocatorID) + : m_manager{new UnifiedMemoryArray(allocatorID)} + { + } + + UnifiedMemoryPointer(std::size_t size, int allocatorID) + : m_size{size}, + m_manager{new UnifiedMemoryArray(size * sizeof(T), allocatorID)} + { + } + + explicit UnifiedMemoryPointer(UnifiedMemoryArray* manager) + : m_manager{manager} + { + if (m_manager) + { + m_size = m_manager->size() / sizeof(T); + } + } + + /*! + * \brief Constructs a shallow copy of an array from another and makes + * the data coherent in the current execution space. + * + * \param other The other array. + * + * \note This is a shallow copy. + */ + CHAI_HOST_DEVICE UnifiedMemoryPointer(const UnifiedMemoryPointer& other) : + m_data{other.m_data}, + m_size{other.m_size} +#if !defined(CHAI_DEVICE_COMPILE) + , m_manager{other.m_manager} +#endif + { + update(); + } + + /*! + * \brief Sets the array manager for this UnifiedMemoryPointer. + * + * \param manager The new array manager to be set. + * + * \post The UnifiedMemoryPointer takes ownership of the new manager objet. + */ + void setManager(UnifiedMemoryArray* manager) + { + delete m_manager; + m_manager = manager; + + if (m_manager) + { + m_size = m_manager->size() / sizeof(T); + } + } + + /*! + * \brief Get the array manager associated with this UnifiedMemoryPointer. + * + * \return A pointer to the array manager. + */ + UnifiedMemoryArray* getManager() const + { + return m_manager; + } + + /*! + * \brief Resizes the array to the specified new size. + * + * \param newSize The new size to resize the array to. + * + * \note This method updates the size of the array and triggers a resize operation in the array manager if it exists. + * If no array manager is associated, an exception is thrown. + */ + void resize(std::size_t newSize) + { + if (m_manager) { + m_size = newSize; + m_manager->resize(newSize * sizeof(T)); + } + else { + m_manager = new UnifiedMemoryArray(newSize * sizeof(T)); + } + } + + /*! + * \brief Frees the resources associated with this array. + * + * \note Once free has been called, it is invalid to use any other copies + * of this array (since copies are shallow). + */ + void free() { + m_data = nullptr; + m_size = 0; + delete m_manager; + m_manager = nullptr; + } + + /*! + * \brief Get the number of elements in the array. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE std::size_t size() const + { + return m_size; + } + + CHAI_HOST_DEVICE update() const + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(!std::is_const::value)); + m_size = m_manager->size() / sizeof(T); + } +#endif + } + + /*! + * \brief Get a pointer to the element data in the current execution context. + * + * \return A pointer to the element data in the current execution context. + */ + CHAI_HOST_DEVICE T* data() const + { + update(); + return m_data; + } + + /*! + * \brief Get a const pointer to the element data in the current execution context. + * + * \return A const pointer to the element data in the current execution context. + */ + CHAI_HOST_DEVICE const T* cdata() const { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = static_cast(m_manager->data(false)); + m_size = m_manager->size() / sizeof(T); + } +#endif + return m_data; + } + + /*! + * \brief Get the ith element in the array. + * + * \param i The index of the element to retrieve. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE T& operator[](std::size_t i) const { + return m_data[i]; + } + + /*! + * \brief Get the value of the element at the specified index. + * + * \param i The index of the element to retrieve. + * + * \return The value of the element at the specified index. + * + * \throw std::runtime_exception if unable to retrieve the element. + */ + T get(std::size_t i) const { + if (m_manager) { + return m_manager->get(i); + } + else { + throw std::out_of_range(); + } + } + + /*! + * \brief Set a value at a specified index in the array. + * + * \param i The index where the value is to be set. + * \param value The value to set at the specified index. + * + * \throw std::runtime_exception if the array manager is not associated with the UnifiedMemoryPointer. + */ + void set(std::size_t i, const T& value) { + if (m_manager) { + m_manager->set(i, value); + } + else { + throw std::out_of_range(); + } + } + + private: + /*! + * The array that is coherent in the current execution space. + */ + T* m_data{nullptr}; + + /*! + * The number of elements in the array. + */ + std::size_t m_size{0}; + + /*! + * The array manager controls the coherence of the array. + */ + UnifiedMemoryArray* m_manager{nullptr}; + }; // class UnifiedMemoryPointer +} // namespace expt +} // namespace chai + +#endif // CHAI_UNIFIED_MEMORY_POINTER_HPP From f7c639104c5d0b31d8919053d678303c6e81a6f3 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 2 Sep 2025 15:59:52 -0700 Subject: [PATCH 44/49] Update UnifiedMemoryPointer --- src/chai/expt/UnifiedMemoryPointer.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/chai/expt/UnifiedMemoryPointer.hpp b/src/chai/expt/UnifiedMemoryPointer.hpp index a1f8530f..4011c201 100644 --- a/src/chai/expt/UnifiedMemoryPointer.hpp +++ b/src/chai/expt/UnifiedMemoryPointer.hpp @@ -78,28 +78,28 @@ namespace expt { constexpr UnifiedMemoryPointer() noexcept = default; explicit UnifiedMemoryPointer(const umpire::Allocator& allocator) - : m_manager{new UnifiedMemoryArray(allocator)} + : m_manager{new UnifiedMemoryManager(allocator)} { } UnifiedMemoryPointer(std::size_t size, const umpire::Allocator& allocator) : m_size{size}, - m_manager{new UnifiedMemoryArray(size * sizeof(T), allocator)} + m_manager{new UnifiedMemoryManager(size * sizeof(T), allocator)} { } explicit UnifiedMemoryPointer(int allocatorID) - : m_manager{new UnifiedMemoryArray(allocatorID)} + : m_manager{new UnifiedMemoryManager(allocatorID)} { } UnifiedMemoryPointer(std::size_t size, int allocatorID) : m_size{size}, - m_manager{new UnifiedMemoryArray(size * sizeof(T), allocatorID)} + m_manager{new UnifiedMemoryManager(size * sizeof(T), allocatorID)} { } - explicit UnifiedMemoryPointer(UnifiedMemoryArray* manager) + explicit UnifiedMemoryPointer(UnifiedMemoryManager* manager) : m_manager{manager} { if (m_manager) @@ -133,7 +133,7 @@ namespace expt { * * \post The UnifiedMemoryPointer takes ownership of the new manager objet. */ - void setManager(UnifiedMemoryArray* manager) + void setManager(UnifiedMemoryManager* manager) { delete m_manager; m_manager = manager; @@ -149,7 +149,7 @@ namespace expt { * * \return A pointer to the array manager. */ - UnifiedMemoryArray* getManager() const + UnifiedMemoryManager* getManager() const { return m_manager; } @@ -169,7 +169,7 @@ namespace expt { m_manager->resize(newSize * sizeof(T)); } else { - m_manager = new UnifiedMemoryArray(newSize * sizeof(T)); + m_manager = new UnifiedMemoryManager(newSize * sizeof(T)); } } @@ -202,7 +202,7 @@ namespace expt { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { m_data = static_cast(m_manager->data(!std::is_const::value)); - m_size = m_manager->size() / sizeof(T); + // m_size = m_manager->size() / sizeof(T); } #endif } @@ -294,7 +294,7 @@ namespace expt { /*! * The array manager controls the coherence of the array. */ - UnifiedMemoryArray* m_manager{nullptr}; + UnifiedMemoryManager* m_manager{nullptr}; }; // class UnifiedMemoryPointer } // namespace expt } // namespace chai From aa06e0da0b1b790a7cb146b3c6a47dce03a11eb7 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 2 Sep 2025 17:18:39 -0700 Subject: [PATCH 45/49] Add UnifiedMemoryManager --- src/chai/expt/ExecutionContextGuard.hpp | 33 ++++ src/chai/expt/UnifiedMemoryManager.hpp | 158 ++++++++++++++++ src/chai/expt/UnifiedMemoryPointer.hpp | 3 +- tests/expt/UnifiedMemoryManagerTests.cpp | 224 +++++++++++++++++++++++ 4 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 src/chai/expt/ExecutionContextGuard.hpp create mode 100644 src/chai/expt/UnifiedMemoryManager.hpp create mode 100644 tests/expt/UnifiedMemoryManagerTests.cpp diff --git a/src/chai/expt/ExecutionContextGuard.hpp b/src/chai/expt/ExecutionContextGuard.hpp new file mode 100644 index 00000000..cfaab279 --- /dev/null +++ b/src/chai/expt/ExecutionContextGuard.hpp @@ -0,0 +1,33 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#include "chai/expt/ExecutionContext.hpp" +#include "chai/expt/ExectuionContextManager.hpp" + +#ifndef CHAI_EXECUTION_CONTEXT_GUARD_HPP +#define CHAI_EXECUTION_CONTEXT_GUARD_HPP + +namespace chai { +namespace expt { + class ExecutionContextGuard { + public: + explicit ExecutionContextGuard(ExecutionContext executionContext) { + m_execution_context_manager.setExecutionContext(executionContext); + } + + ~ExecutionContextGuard() { + m_execution_context_manager.setExecutionContext(m_last_execution_context); + } + + private: + ExecutionContextManager& m_execution_context_manager{ExecutionContextManager::getInstance()}; + ExecutionContext m_last_execution_context{m_execution_context_manager.getExecutionContext()}; + }; +} // namespace expt +} // namespace chai + +#endif // CHAI_EXECUTION_CONTEXT_GUARD_HPP \ No newline at end of file diff --git a/src/chai/expt/UnifiedMemoryManager.hpp b/src/chai/expt/UnifiedMemoryManager.hpp new file mode 100644 index 00000000..385de46b --- /dev/null +++ b/src/chai/expt/UnifiedMemoryManager.hpp @@ -0,0 +1,158 @@ +#ifndef CHAI_UNIFIED_MEMORY_MANAGER_HPP +#define CHAI_UNIFIED_MEMORY_MANAGER_HPP + +#include "chai/expt/ExecutionContext.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + class UnifiedMemoryManager + public: + UnifiedMemoryManager() = default; + + explicit UnifiedMemoryManager(const umpire::Allocator& allocator) : + m_allocator{allocator} + { + } + + UnifiedMemoryManager(std::size_t size, const umpire::Allocator& allocator) : + m_allocator{allocator}, + m_size{size}, + m_data{m_allocator.allocate(m_size)} + { + } + + explicit UnifiedMemoryManager(int allocatorID) : + m_allocator{m_resource_manager.getAllocator(allocatorID)} + { + } + + UnifiedMemoryManager(std::size_t size, int allocatorID) : + m_allocator{m_resource_manager.getAllocator(allocatorID)}, + m_size{size}, + m_data{m_allocator.allocate(m_size)} + { + m_data = m_allocator.allocate(size); + } + + UnifiedMemoryManager(const UnifiedMemoryManager& other) : + m_size{other.m_size}, + m_allocator{other.m_allocator} + { + m_data = m_allocator.allocate(m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::DEVICE); + m_resource_manager.copy(other.m_data, m_data, m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::NONE); + m_last_execution_context = ExecutionContext::DEVICE; + } + + UnifiedMemoryManager(UnifiedMemoryManager&& other) : + m_data{other.m_data}, + m_size{other.m_size}, + m_last_execution_context{other.m_last_execution_context}, + m_allocator{other.m_allocator} + { + other.m_data = nullptr; + other.m_size = 0; + other.m_last_execution_context = NONE; + other.m_allocator = umpire::Allocator(); + } + + UnifiedMemoryManager& operator=(const UnifiedMemoryManager& other) { + if (&other != this) { // Prevent self-assignment + m_allocator.deallocate(m_data); + + m_size = other.m_size; + m_allocator = other.m_allocator; + m_data = m_allocator.allocate(m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::DEVICE); + m_resource_manager.copy(other.m_data, m_data, m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::NONE); + m_last_execution_context = ExecutionContext::DEVICE; + } + + return *this; + } + + UnifiedMemoryManager& operator=(UnifiedMemoryManager&& other) { + if (&other != this) { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_last_execution_context = other.m_last_execution_context; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + other.m_last_execution_context = ExecutionContext::NONE; + other.m_allocator = umpire::Allocator(); + } + + return *this; + } + + /*! + * \brief Destructor. + */ + ~UnifiedMemoryManager() { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Get the number of elements. + */ + size_t size() const { + return m_size; + } + + T* data(ExecutionContext executionContext) { + if (executionContext != m_last_execution_context) { + m_execution_context_manager.synchronize(m_last_execution_context); + m_last_execution_context = executionContext; + } + + return m_data; + } + + const T* data(ExecutionContext executionContext) const { + if (executionContext != m_last_execution_context) { + m_execution_context_manager.synchronize(m_last_execution_context); + m_last_execution_context = ExecutionContext::NONE; + } + + return m_data; + } + + T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_last_execution_context) { + m_execution_context_manager.synchronize(m_last_execution_context); + m_last_execution_context = executionContext; + } + + return m_data[i]; + } + + const T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_last_execution_context) { + m_execution_context_manager.synchronize(m_last_execution_context); + m_last_execution_context = ExecutionContext::NONE; + } + + return m_data[i]; + } + + private: + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_allocator{}; + T* m_data{nullptr}; + size_t m_size{0}; + ExecutionContext m_last_execution_context{ExecutionContext::NONE}; + + ExecutionContextManager& m_execution_context_manager{ExecutionContextManager::getInstance()}; + + }; // class UnifiedMemoryManager +} // namespace expt +} // namespace chai + +#endif // CHAI_UNIFIED_MEMORY_MANAGER_HPP diff --git a/src/chai/expt/UnifiedMemoryPointer.hpp b/src/chai/expt/UnifiedMemoryPointer.hpp index 4011c201..f4ea3691 100644 --- a/src/chai/expt/UnifiedMemoryPointer.hpp +++ b/src/chai/expt/UnifiedMemoryPointer.hpp @@ -1,8 +1,7 @@ #ifndef CHAI_UNIFIED_MEMORY_POINTER_HPP #define CHAI_UNIFIED_MEMORY_POINTER_HPP -#include "chai/expt/ArrayManager.hpp" -#include "chai/expt/ExecutionContextManager.hpp" +#include "chai/expt/UnifiedMemoryManager.hpp" #include #include diff --git a/tests/expt/UnifiedMemoryManagerTests.cpp b/tests/expt/UnifiedMemoryManagerTests.cpp new file mode 100644 index 00000000..baa5c59e --- /dev/null +++ b/tests/expt/UnifiedMemoryManagerTests.cpp @@ -0,0 +1,224 @@ +#include "gtest/gtest.h" +#include "chai/expt/UnifiedMemoryManager.hpp" + +class UnifiedMemoryManagerTest : public ::testing::Test { +protected: + void SetUp() override { + // Get a basic allocator for testing + m_allocator = umpire::ResourceManager::getInstance().getAllocator("HOST"); + m_execution_context_manager = ExecutionContextManager::getInstance(); + } + + umpire::Allocator m_allocator; + ExecutionContextManager& m_execution_context_manager; +}; + +TEST_F(UnifiedMemoryManagerTest, DefaultConstructor) { + chai::expt::UnifiedMemoryManager manager; + + { + EXPECT_EQ(manager.size(), 0); + EXPECT_EQ(manager.data(), nullptr); + } + + { + chai::expt::ExecutionContextGuard executionContextGuard(ExecutionContext::NONE); + EXPECT_EQ(manager.size(), 0); + EXPECT_EQ(manager.data(), nullptr); + } + + { + chai::expt::ExecutionContextGuard executionContextGuard(ExecutionContext::HOST); + EXPECT_EQ(manager.size(), 0); + EXPECT_EQ(manager.data(), nullptr); + } + +#if defined(CHAI_ENABLE_DEVICE) + { + chai::expt::ExecutionContextGuard executionContextGuard(ExecutionContext::DEVICE); + EXPECT_EQ(manager.size(), 0); + EXPECT_EQ(manager.data(), nullptr); + } +#endif +} + +TEST_F(UnifiedMemoryManagerTest, AllocatorConstructor) { + chai::expt::UnifiedMemoryManager manager(m_allocator); + EXPECT_EQ(manager.size(), 0); + EXPECT_EQ(manager.data(chai::expt::ExecutionContext::HOST), nullptr); +} + +TEST_F(UnifiedMemoryManagerTest, SizeAndAllocatorConstructor) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager manager(size, m_allocator); + EXPECT_EQ(manager.size(), size); + EXPECT_NE(manager.data(chai::expt::ExecutionContext::HOST), nullptr); +} + +TEST_F(UnifiedMemoryManagerTest, AllocatorIDConstructor) { + chai::expt::UnifiedMemoryManager manager(0); // 0 typically corresponds to HOST + EXPECT_EQ(manager.size(), 0); + EXPECT_EQ(manager.data(chai::expt::ExecutionContext::HOST), nullptr); +} + +TEST_F(UnifiedMemoryManagerTest, SizeAndAllocatorIDConstructor) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager manager(size, 0); // 0 typically corresponds to HOST + EXPECT_EQ(manager.size(), size); + EXPECT_NE(manager.data(chai::expt::ExecutionContext::HOST), nullptr); +} + +TEST_F(UnifiedMemoryManagerTest, CopyConstructor) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager original(size, m_allocator); + + // Initialize data + for (size_t i = 0; i < size; ++i) { + original.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Copy + chai::expt::UnifiedMemoryManager copy(original); + + // Verify + EXPECT_EQ(copy.size(), original.size()); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(copy.get(chai::expt::ExecutionContext::HOST, i), + original.get(chai::expt::ExecutionContext::HOST, i)); + } +} + +TEST_F(UnifiedMemoryManagerTest, MoveConstructor) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager original(size, m_allocator); + + // Initialize data + for (size_t i = 0; i < size; ++i) { + original.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Store data pointer for comparison + int* originalData = original.data(chai::expt::ExecutionContext::HOST); + + // Move + chai::expt::UnifiedMemoryManager moved(std::move(original)); + + // Verify + EXPECT_EQ(moved.size(), size); + EXPECT_EQ(moved.data(chai::expt::ExecutionContext::HOST), originalData); + EXPECT_EQ(original.size(), 0); + EXPECT_EQ(original.data(chai::expt::ExecutionContext::HOST), nullptr); +} + +TEST_F(UnifiedMemoryManagerTest, CopyAssignment) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager original(size, m_allocator); + + // Initialize data + for (size_t i = 0; i < size; ++i) { + original.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Copy assignment + chai::expt::UnifiedMemoryManager copy; + copy = original; + + // Verify + EXPECT_EQ(copy.size(), original.size()); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(copy.get(chai::expt::ExecutionContext::HOST, i), + original.get(chai::expt::ExecutionContext::HOST, i)); + } +} + +TEST_F(UnifiedMemoryManagerTest, MoveAssignment) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager original(size, m_allocator); + + // Initialize data + for (size_t i = 0; i < size; ++i) { + original.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Store data pointer for comparison + int* originalData = original.data(chai::expt::ExecutionContext::HOST); + + // Move assignment + chai::expt::UnifiedMemoryManager moved; + moved = std::move(original); + + // Verify + EXPECT_EQ(moved.size(), size); + EXPECT_EQ(moved.data(chai::expt::ExecutionContext::HOST), originalData); + EXPECT_EQ(original.size(), 0); + EXPECT_EQ(original.data(chai::expt::ExecutionContext::HOST), nullptr); +} + +TEST_F(UnifiedMemoryManagerTest, DataAccess) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager manager(size, m_allocator); + + // Initialize and verify data access + for (size_t i = 0; i < size; ++i) { + manager.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Verify using get() + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(manager.get(chai::expt::ExecutionContext::HOST, i), static_cast(i)); + } + + // Verify using data() + int* data = manager.data(chai::expt::ExecutionContext::HOST); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(data[i], static_cast(i)); + } +} + +TEST_F(UnifiedMemoryManagerTest, ConstDataAccess) { + const size_t size = 10; + chai::expt::UnifiedMemoryManager manager(size, m_allocator); + + // Initialize data + for (size_t i = 0; i < size; ++i) { + manager.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Create const reference and verify data access + const chai::expt::UnifiedMemoryManager& constManager = manager; + + // Verify using get() + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(constManager.get(chai::expt::ExecutionContext::HOST, i), static_cast(i)); + } + + // Verify using data() + const int* constData = constManager.data(chai::expt::ExecutionContext::HOST); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(constData[i], static_cast(i)); + } +} + +TEST_F(UnifiedMemoryManagerTest, ExecutionContextSwitching) { + // Note: This test assumes a system with both HOST and DEVICE execution contexts + // For systems without a device (e.g., GPU), this test may need to be modified + + const size_t size = 10; + chai::expt::UnifiedMemoryManager manager(size, m_allocator); + + // Initialize data on HOST + for (size_t i = 0; i < size; ++i) { + manager.get(chai::expt::ExecutionContext::HOST, i) = static_cast(i); + } + + // Access data on DEVICE (should trigger synchronization) + int* deviceData = manager.data(chai::expt::ExecutionContext::DEVICE); + + // Access data back on HOST (should trigger synchronization again) + int* hostData = manager.data(chai::expt::ExecutionContext::HOST); + + // Verify data is still correct + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(hostData[i], static_cast(i)); + } +} \ No newline at end of file From 3de1ea7d132da6aa510b4feaacf53a3a9315dd23 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 8 Sep 2025 14:33:37 -0700 Subject: [PATCH 46/49] Add NullArrayManager --- src/chai/expt/NullArrayManager.hpp | 124 +++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/chai/expt/NullArrayManager.hpp diff --git a/src/chai/expt/NullArrayManager.hpp b/src/chai/expt/NullArrayManager.hpp new file mode 100644 index 00000000..62d2a42c --- /dev/null +++ b/src/chai/expt/NullArrayManager.hpp @@ -0,0 +1,124 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_NULL_ARRAY_MANAGER_HPP +#define CHAI_NULL_ARRAY_MANAGER_HPP + +#include "chai/expt/ArrayManager.hpp" +#include + +namespace chai { +namespace expt { + +/*! + * \class NullArrayManager + * + * \brief A null implementation of ArrayManager that doesn't actually store data. + * + * This class implements the ArrayManager interface but doesn't actually + * store any data. It's implemented as a singleton. + */ +template +class NullArrayManager : public ArrayManager { +public: + /*! + * \brief Get the singleton instance of NullArrayManager. + * + * \return Reference to the singleton instance. + */ + static NullArrayManager& getInstance() { + static NullArrayManager instance; + return instance; + } + + /*! + * \brief Virtual destructor. + */ + virtual ~NullArrayManager() = default; + + /*! + * \brief Creates a clone of this NullArrayManager. + * + * \return Pointer to the singleton instance. + */ + virtual ArrayManager* clone() const override { + return &getInstance(); + } + + /*! + * \brief Throws an exception when attempting to resize. + * + * \param newSize The new size (ignored). + * \throws std::runtime_error Always throws this exception. + */ + virtual void resize(std::size_t newSize) override { + throw std::runtime_error("Cannot resize NullArrayManager"); + } + + /*! + * \brief Returns 0 as the size. + * + * \return Always returns 0. + */ + virtual std::size_t size() const override { + return 0; + } + + /*! + * \brief Returns a nullptr for data access. + * + * \param context The execution context (ignored). + * \param touch Whether to mark data as touched (ignored). + * \return Always returns nullptr. + */ + virtual T* data(Context context, bool touch) override { + return nullptr; + } + + /*! + * \brief Throws an exception when attempting to get a value. + * + * \param i The index (ignored). + * \return Never returns. + * \throws std::runtime_error Always throws this exception. + */ + virtual T get(std::size_t i) const override { + throw std::runtime_error("Cannot get value from NullArrayManager"); + } + + /*! + * \brief Throws an exception when attempting to set a value. + * + * \param i The index (ignored). + * \param value The value (ignored). + * \throws std::runtime_error Always throws this exception. + */ + virtual void set(std::size_t i, const T& value) override { + throw std::runtime_error("Cannot set value in NullArrayManager"); + } + +private: + /*! + * \brief Private constructor for singleton pattern. + */ + NullArrayManager() {} + + /*! + * \brief Delete copy constructor. + */ + NullArrayManager(const NullArrayManager&) = delete; + + /*! + * \brief Delete assignment operator. + */ + NullArrayManager& operator=(const NullArrayManager&) = delete; +}; + +} // namespace expt +} // namespace chai + +#endif // CHAI_NULL_ARRAY_MANAGER_HPP \ No newline at end of file From 4f795fd75b268ab4ea406d89318aa5199dd6e0b2 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 8 Sep 2025 14:42:28 -0700 Subject: [PATCH 47/49] Clean up ManagedArray --- src/chai/expt/ManagedArray.hpp | 78 +++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index dd30b108..e6ebabf9 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -13,9 +13,9 @@ namespace expt { * \brief An array class that manages coherency across the CPU and GPU. * How the coherence is obtained is controlled by the array manager. * - * \tparam ElementT The type of element in the array. + * \tparam ElementType The type of element in the array. */ - template + template class ManagedArray { public: /*! @@ -30,12 +30,12 @@ namespace expt { * * \note The array takes ownership of the manager. */ - explicit ManagedArray(ArrayManager* manager) : - m_array_manager{manager} + explicit ManagedArray(ArrayManager* manager) + : m_manager{manager} { - if (m_array_manager) + if (m_manager) { - m_size = m_array_manager->size(); + m_size = m_manager->size(); } } @@ -50,11 +50,11 @@ namespace expt { CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) : m_data{other.m_data}, m_size{other.m_size}, - m_array_manager{other.m_array_manager} + m_manager{other.m_manager} { #if !defined(CHAI_DEVICE_COMPILE) - if (m_array_manager) { - m_data = m_array_manager->data(ExecutionContextManager::getInstance()::getContext(), !std::is_const::value)); + if (m_manager) { + m_data = m_manager->data(ExecutionContextManager::getInstance()::getContext(), !std::is_const::value)); } #endif } @@ -66,10 +66,10 @@ namespace expt { * * \post The ManagedArray takes ownership of the new manager objet. */ - void setManager(ArrayManager* manager) + void setManager(ArrayManager* manager) { - delete m_array_manager; - m_array_manager = manager; + delete m_manager; + m_manager = manager; } /*! @@ -77,8 +77,8 @@ namespace expt { * * \return A pointer to the array manager. */ - ArrayManager* getManager() const { - return m_array_manager; + ArrayManager* getManager() const { + return m_manager; } /*! @@ -90,9 +90,9 @@ namespace expt { * If no array manager is associated, an exception is thrown. */ void resize(std::size_t newSize) { - if (m_array_manager) { + if (m_manager) { m_size = newSize; - m_array_manager->resize(newSize); + m_manager->resize(newSize); } else { throw std::runtime_exception("Unable to resize"); @@ -108,8 +108,8 @@ namespace expt { void free() { m_data = nullptr; m_size = 0; - delete m_array_manager; - m_array_manager = nullptr; + delete m_manager; + m_manager = nullptr; } /*! @@ -129,9 +129,9 @@ namespace expt { * * \return A pointer to the element data in the specified context. */ - ElementT* data(ExecutionContext context) const { - if (m_array_manager) { - m_data = m_array_manager->data(context, !std::is_const::value); + ElementType* data(ExecutionContext context) const { + if (m_manager) { + m_data = m_manager->data(context, !std::is_const::value); } return m_data; @@ -144,9 +144,9 @@ namespace expt { * * \return A const pointer to the element data in the specified context. */ - const ElementT* cdata(ExecutionContext context) const { - if (m_array_manager) { - m_data = m_array_manager->data(context, false); + const ElementType* cdata(ExecutionContext context) const { + if (m_manager) { + m_data = m_manager->data(context, false); } return m_data; @@ -157,7 +157,7 @@ namespace expt { * * \return A pointer to the element data in the current execution space. */ - CHAI_HOST_DEVICE ElementT* data() const { + CHAI_HOST_DEVICE ElementType* data() const { #if !defined(CHAI_DEVICE_COMPILE) return data(HOST); #endif @@ -169,7 +169,7 @@ namespace expt { * * \return A const pointer to the element data in the current execution space. */ - CHAI_HOST_DEVICE const ElementT* cdata() const { + CHAI_HOST_DEVICE const ElementType* cdata() const { #if !defined(CHAI_DEVICE_COMPILE) return cdata(HOST); #endif @@ -184,7 +184,7 @@ namespace expt { * \pre The copy constructor has been called with the execution space * set to CPU or GPU (e.g. by the RAJA plugin). */ - CHAI_HOST_DEVICE ElementT& operator[](std::size_t i) const { + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const { return m_data[i]; } @@ -197,9 +197,9 @@ namespace expt { * * \throw std::runtime_exception if unable to retrieve the element. */ - ElementT get(std::size_t i) const { - if (m_array_manager) { - return m_array_manager->get(i); + ElementType get(std::size_t i) const { + if (m_manager) { + return m_manager->get(i); } else { throw std::runtime_exception("Unable to get element"); @@ -214,9 +214,9 @@ namespace expt { * * \throw std::runtime_exception if the array manager is not associated with the ManagedArray. */ - void set(std::size_t i, const ElementT& value) { - if (m_array_manager) { - m_array_manager->set(i, value); + void set(std::size_t i, const ElementType& value) { + if (m_manager) { + m_manager->set(i, value); } else { throw std::runtime_exception("Unable to set element"); @@ -227,7 +227,7 @@ namespace expt { /*! * The array that is coherent in the current execution space. */ - ElementT* m_data = nullptr; + ElementType* m_data = nullptr; /*! * The number of elements in the array. @@ -237,20 +237,20 @@ namespace expt { /*! * The array manager controls the coherence of the array. */ - ArrayManager* m_array_manager = nullptr; + ArrayManager* m_manager = nullptr; }; // class ManagedArray /*! * \brief Constructs an array by creating a new manager object. * - * \tparam ArrayManager The type of array manager. + * \tparam ArrayManager The type of array manager. * \tparam Args The type of the arguments used to construct the array manager. * * \param args The arguments to construct an array manager. */ - template , typename... Args> - ManagedArray makeArray(Args&&... args) { - return ManagedArray(new ArrayManager(std::forward(args)...)); + template , typename... Args> + ManagedArray makeArray(Args&&... args) { + return ManagedArray(new ArrayManager(std::forward(args)...)); } } // namespace expt } // namespace chai From 9241899f10d223fe4731f397d4d71b6988db3709 Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Mon, 8 Sep 2025 17:13:48 -0700 Subject: [PATCH 48/49] Add converting constructor to ManagedArray --- src/chai/expt/ManagedArray.hpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index e6ebabf9..db452a37 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -54,11 +54,28 @@ namespace expt { { #if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_data = m_manager->data(ExecutionContextManager::getInstance()::getContext(), !std::is_const::value)); + m_data = m_manager->data(!std::is_const::value)); } #endif } + /*! + * \brief Constructs a ManagedArray from a ManagedArray. + * + * \param other The non-const array to convert from. + * + * \note This is a converting constructor that enables implicit conversion + * from ManagedArray to ManagedArray. + */ + template * = nullptr> + CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_manager{other.m_manager} + { + } + /*! * \brief Sets the array manager for this ManagedArray. * From 099070b51cd1dbe2d51092603649479b5497553e Mon Sep 17 00:00:00 2001 From: Alan Dayton Date: Tue, 9 Sep 2025 17:23:37 -0700 Subject: [PATCH 49/49] More changes --- src/chai/expt/Allocator.hpp | 77 +++++ src/chai/expt/Array.hpp | 275 ++++++++++++++++ src/chai/expt/ArrayManager.hpp | 7 +- ...yContainer.hpp => DiscreteMemoryArray.hpp} | 79 +++-- src/chai/expt/ManagedArray.hpp | 80 ++--- src/chai/expt/MemoryManager.hpp | 79 +++++ src/chai/expt/PinnedArrayView.hpp | 73 +++++ src/chai/expt/PinnedMemoryManager.hpp | 295 ++++++++++++++++++ src/chai/expt/UnifiedArrayManager.hpp | 169 ++++++++++ ...nter.hpp => UnifiedMemoryArrayPointer.hpp} | 0 src/chai/expt/UnifiedMemoryManager.hpp | 50 +-- 11 files changed, 1089 insertions(+), 95 deletions(-) create mode 100644 src/chai/expt/Allocator.hpp create mode 100644 src/chai/expt/Array.hpp rename src/chai/expt/{PinnedArrayContainer.hpp => DiscreteMemoryArray.hpp} (60%) create mode 100644 src/chai/expt/MemoryManager.hpp create mode 100644 src/chai/expt/PinnedArrayView.hpp create mode 100644 src/chai/expt/PinnedMemoryManager.hpp create mode 100644 src/chai/expt/UnifiedArrayManager.hpp rename src/chai/expt/{UnifiedMemoryPointer.hpp => UnifiedMemoryArrayPointer.hpp} (100%) diff --git a/src/chai/expt/Allocator.hpp b/src/chai/expt/Allocator.hpp new file mode 100644 index 00000000..e180cd47 --- /dev/null +++ b/src/chai/expt/Allocator.hpp @@ -0,0 +1,77 @@ +#ifndef CHAI_ALLOCATOR_HPP +#define CHAI_ALLOCATOR_HPP + +namespace chai::expt { + class Allocator { + private: + class AllocatorConcept + { + public: + virtual ~AllocatorConcept() = default; + virtual void* do_allocate(std::size_t bytes) = 0; + virtual void do_deallocate(void* ptr) = 0; + virtual std::unique_ptr clone() const = 0; + }; // class AllocatorConcept + + template + class AllocatorModel : public AllocatorConcept + { + public: + AllocatorModel(AllocatorType allocator) + : m_allocator{std::move(allocator)} + { + } + + virtual void* allocate(std::size_t bytes) override + { + return allocate(m_allocator, bytes); + } + + virtual void do_deallocate(void* ptr) override + { + deallocate(m_allocator, ptr); + } + + virtual std::unique_ptr clone() const override + { + return std::make_unique(*this); + } + + private: + AllocatorType m_allocator; + }; // class AllocatorModel + + friend void* allocate(const Allocator& allocator, std::size_t bytes) + { + return allocator.m_pimpl->do_allocate(bytes); + } + + friend void deallocate(const Allocator& allocator, void* ptr) + { + allocator.m_pimple->do_deallocate(ptr); + } + + std::unique_ptr m_pimpl; + + public: + template + Allocator(AllocatorType allocator) + : m_pimpl{std::make_unique>(std::move(allocator))} + { + } + + Allocator(const Allocator& other) + : m_pimple{other.m_pimpl->clone()} + { + } + + Allocator& operator=(const Allocator& other) + { + Allocator temp(other); + std::swap(m_pimpl, temp.m_pimpl); + return *this; + } + }; // class Allocator +} + +#endif // CHAI_ALLOCATOR_HPP \ No newline at end of file diff --git a/src/chai/expt/Array.hpp b/src/chai/expt/Array.hpp new file mode 100644 index 00000000..db452a37 --- /dev/null +++ b/src/chai/expt/Array.hpp @@ -0,0 +1,275 @@ +#ifndef CHAI_MANAGED_ARRAY_HPP +#define CHAI_MANAGED_ARRAY_HPP + +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ExecutionContextManager.hpp" +#include + +namespace chai { +namespace expt { + /*! + * \class ManagedArray + * + * \brief An array class that manages coherency across the CPU and GPU. + * How the coherence is obtained is controlled by the array manager. + * + * \tparam ElementType The type of element in the array. + */ + template + class ManagedArray { + public: + /*! + * \brief Constructs an empty array without an array manager. + */ + ManagedArray() = default; + + /*! + * \brief Constructs an array from a manager. + * + * \param manager The array manager controls the coherence of the array. + * + * \note The array takes ownership of the manager. + */ + explicit ManagedArray(ArrayManager* manager) + : m_manager{manager} + { + if (m_manager) + { + m_size = m_manager->size(); + } + } + + /*! + * \brief Constructs a shallow copy of an array from another and makes + * the data coherent in the current execution space. + * + * \param other The other array. + * + * \note This is a shallow copy. + */ + CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) : + m_data{other.m_data}, + m_size{other.m_size}, + m_manager{other.m_manager} + { +#if !defined(CHAI_DEVICE_COMPILE) + if (m_manager) { + m_data = m_manager->data(!std::is_const::value)); + } +#endif + } + + /*! + * \brief Constructs a ManagedArray from a ManagedArray. + * + * \param other The non-const array to convert from. + * + * \note This is a converting constructor that enables implicit conversion + * from ManagedArray to ManagedArray. + */ + template * = nullptr> + CHAI_HOST_DEVICE ManagedArray(const ManagedArray& other) + : m_data{other.m_data}, + m_size{other.m_size}, + m_manager{other.m_manager} + { + } + + /*! + * \brief Sets the array manager for this ManagedArray. + * + * \param manager The new array manager to be set. + * + * \post The ManagedArray takes ownership of the new manager objet. + */ + void setManager(ArrayManager* manager) + { + delete m_manager; + m_manager = manager; + } + + /*! + * \brief Get the array manager associated with this ManagedArray. + * + * \return A pointer to the array manager. + */ + ArrayManager* getManager() const { + return m_manager; + } + + /*! + * \brief Resizes the array to the specified new size. + * + * \param newSize The new size to resize the array to. + * + * \note This method updates the size of the array and triggers a resize operation in the array manager if it exists. + * If no array manager is associated, an exception is thrown. + */ + void resize(std::size_t newSize) { + if (m_manager) { + m_size = newSize; + m_manager->resize(newSize); + } + else { + throw std::runtime_exception("Unable to resize"); + } + } + + /*! + * \brief Frees the resources associated with this array. + * + * \note Once free has been called, it is invalid to use any other copies + * of this array (since copies are shallow). + */ + void free() { + m_data = nullptr; + m_size = 0; + delete m_manager; + m_manager = nullptr; + } + + /*! + * \brief Get the number of elements in the array. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE std::size_t size() const { + return m_size; + } + + /*! + * \brief Get a pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the element data. + * + * \return A pointer to the element data in the specified context. + */ + ElementType* data(ExecutionContext context) const { + if (m_manager) { + m_data = m_manager->data(context, !std::is_const::value); + } + + return m_data; + } + + /*! + * \brief Get a const pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the const element data. + * + * \return A const pointer to the element data in the specified context. + */ + const ElementType* cdata(ExecutionContext context) const { + if (m_manager) { + m_data = m_manager->data(context, false); + } + + return m_data; + } + + /*! + * \brief Get a pointer to the element data in the current execution space. + * + * \return A pointer to the element data in the current execution space. + */ + CHAI_HOST_DEVICE ElementType* data() const { +#if !defined(CHAI_DEVICE_COMPILE) + return data(HOST); +#endif + return m_data; + } + + /*! + * \brief Get a const pointer to the element data in the current execution space. + * + * \return A const pointer to the element data in the current execution space. + */ + CHAI_HOST_DEVICE const ElementType* cdata() const { +#if !defined(CHAI_DEVICE_COMPILE) + return cdata(HOST); +#endif + return m_data; + } + + /*! + * \brief Get the ith element in the array. + * + * \param i The index of the element to retrieve. + * + * \pre The copy constructor has been called with the execution space + * set to CPU or GPU (e.g. by the RAJA plugin). + */ + CHAI_HOST_DEVICE ElementType& operator[](std::size_t i) const { + return m_data[i]; + } + + /*! + * \brief Get the value of the element at the specified index. + * + * \param i The index of the element to retrieve. + * + * \return The value of the element at the specified index. + * + * \throw std::runtime_exception if unable to retrieve the element. + */ + ElementType get(std::size_t i) const { + if (m_manager) { + return m_manager->get(i); + } + else { + throw std::runtime_exception("Unable to get element"); + } + } + + /*! + * \brief Set a value at a specified index in the array. + * + * \param i The index where the value is to be set. + * \param value The value to set at the specified index. + * + * \throw std::runtime_exception if the array manager is not associated with the ManagedArray. + */ + void set(std::size_t i, const ElementType& value) { + if (m_manager) { + m_manager->set(i, value); + } + else { + throw std::runtime_exception("Unable to set element"); + } + } + + private: + /*! + * The array that is coherent in the current execution space. + */ + ElementType* m_data = nullptr; + + /*! + * The number of elements in the array. + */ + std::size_t m_size = 0; + + /*! + * The array manager controls the coherence of the array. + */ + ArrayManager* m_manager = nullptr; + }; // class ManagedArray + + /*! + * \brief Constructs an array by creating a new manager object. + * + * \tparam ArrayManager The type of array manager. + * \tparam Args The type of the arguments used to construct the array manager. + * + * \param args The arguments to construct an array manager. + */ + template , typename... Args> + ManagedArray makeArray(Args&&... args) { + return ManagedArray(new ArrayManager(std::forward(args)...)); + } +} // namespace expt +} // namespace chai + +#endif // CHAI_MANAGED_ARRAY_HPP diff --git a/src/chai/expt/ArrayManager.hpp b/src/chai/expt/ArrayManager.hpp index 6a237a00..e98e92bd 100644 --- a/src/chai/expt/ArrayManager.hpp +++ b/src/chai/expt/ArrayManager.hpp @@ -18,7 +18,6 @@ namespace expt { * * \brief Controls the coherence of an array. */ - template class ArrayManager { public: /*! @@ -52,7 +51,7 @@ namespace expt { * * \param data [out] A coherent array in the current execution context. */ - virtual T* data(Context context, bool touch) = 0; + virtual void* data(bool touch) = 0; /*! * \brief Returns the value at index i. @@ -62,7 +61,7 @@ namespace expt { * \param i The index of the element to get. * \return The value at index i. */ - virtual T get(std::size_t i) const = 0; + virtual void* get(std::size_t offset, std::size_t size) const = 0; /*! * \brief Sets the value at index i to the specified value. @@ -72,7 +71,7 @@ namespace expt { * \param i The index of the element to set. * \param value The value to set at index i. */ - virtual void set(std::size_t i, const T& value) = 0; + virtual void set(std::size_t offset, std::size_t size, const void* value) = 0; }; // class ArrayManager } // namespace expt } // namespace chai diff --git a/src/chai/expt/PinnedArrayContainer.hpp b/src/chai/expt/DiscreteMemoryArray.hpp similarity index 60% rename from src/chai/expt/PinnedArrayContainer.hpp rename to src/chai/expt/DiscreteMemoryArray.hpp index 1b842c6c..4d76a436 100644 --- a/src/chai/expt/PinnedArrayContainer.hpp +++ b/src/chai/expt/DiscreteMemoryArray.hpp @@ -1,5 +1,5 @@ -#ifndef CHAI_PINNED_ARRAY_CONTAINER_HPP -#define CHAI_PINNED_ARRAY_CONTAINER_HPP +#ifndef CHAI_UNIFIED_MEMORY_ARRAY_HPP +#define CHAI_UNIFIED_MEMORY_ARRAY_HPP #include "chai/expt/ExecutionContext.hpp" #include "umpire/ResourceManager.hpp" @@ -7,42 +7,83 @@ namespace chai { namespace expt { /*! - * \class PinnedArrayContainer + * \class UnifiedMemoryArray * - * \brief Controls the coherence of an array on the host and device. + * \brief A container for managing the lifetime and coherence of a + * unified memory array, meaning an array with a single address + * that is accessible from all processors/devices in a system. + * + * This container should be used in tandem with the ExecutionContextManager. + * Together, they provide a programming model where work (e.g. a kernel) + * is generally performed asynchronously, with synchronization occurring + * only as needed for coherence of the array. For example, if the array is + * written to in an asynchronize kernel on a GPU, then the GPU will be + * synchronized if the array needs to be accessed on the CPU. + * + * This model works well for APUs where the CPU and GPU have the same + * physical memory. It also works for pinned (i.e. page-locked) memory + * and in some cases for pageable memory, though no pre-fetching is + * performed. + * + * Example: + * + * \code + * // Create a UnifiedMemoryArray with size 100 and default allocator + * int size = 10000; + * UnifiedMemoryArray array(size); + * + * // Access elements on the device + * std::span device_view(array.data(ExecutionContext::DEVICE, array.size()); + * + * // Launch a kernel that modifies device_view. + * // Note that this example relies on c++20 and the ability to use constexpr + * // host code on the device. + * + * // Access elements on the host. This will synchronize the device. + * std::span host_view(array.data(ExecutionContext::HOST), array.size()); + * + * for (int i = 0; i < size; ++i) { + * std::cout << host_view[i] << "\n"; + * } + * + * // Access and modify individual elements in the container. + * // This should be used sparingly or it will tank performance. + * // Getting the last element after performing a scan is one use case. + * array.get(ExecutionContext::HOST, size - 1) = 10; + * \endcode */ template - class PinnedArrayContainer + class UnifiedMemoryArray public: - PinnedArrayContainer() = default; + UnifiedMemoryArray() = default; - explicit PinnedArrayContainer(const umpire::Allocator& allocator) : + explicit UnifiedMemoryArray(const umpire::Allocator& allocator) : m_allocator{allocator} { } - PinnedArrayContainer(std::size_t size, const umpire::Allocator& allocator) : + UnifiedMemoryArray(std::size_t size, const umpire::Allocator& allocator) : m_size{size}, m_allocator{allocator} { m_data = m_allocator.allocate(m_size * sizeof(T)); - // TODO: Initialization + // TODO: Investigate if/when to do initialization } - explicit PinnedArrayContainer(int allocatorID) : + explicit UnifiedMemoryArray(int allocatorID) : m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} { } - PinnedArrayContainer(std::size_t size, int allocatorID) : + UnifiedMemoryArray(std::size_t size, int allocatorID) : m_size{size}, m_allocator{umpire::ResourceManager::getInstance().getAllocator(allocatorID)} { m_data = m_allocator.allocate(m_size * sizeof(T)); - // TODO: Initialization + // TODO: Investigate if/when to do initialization } - PinnedArrayContainer(const PinnedArrayContainer& other) : + UnifiedMemoryArray(const UnifiedMemoryArray& other) : m_size{other.m_size}, m_allocator{other.m_allocator} { @@ -53,7 +94,7 @@ namespace expt { m_last_execution_context = ExecutionContext::DEVICE; } - PinnedArrayContainer(PinnedArrayContainer&& other) : + UnifiedMemoryArray(UnifiedMemoryArray&& other) : m_data{other.m_data}, m_size{other.m_size}, m_last_execution_context{other.m_last_execution_context}, @@ -65,7 +106,7 @@ namespace expt { other.m_allocator = umpire::Allocator(); } - PinnedArrayContainer& operator=(const PinnedArrayContainer& other) { + UnifiedMemoryArray& operator=(const UnifiedMemoryArray& other) { if (&other != this) { // Prevent self-assignment m_allocator.deallocate(m_data); @@ -81,7 +122,7 @@ namespace expt { return *this; } - PinnedArrayContainer& operator=(PinnedArrayContainer&& other) { + UnifiedMemoryArray& operator=(UnifiedMemoryArray&& other) { if (&other != this) { m_allocator.deallocate(m_data); @@ -102,7 +143,7 @@ namespace expt { /*! * \brief Destructor. */ - ~PinnedArrayContainer() { + ~UnifiedMemoryArray() { m_allocator.deallocate(m_data); } @@ -154,8 +195,8 @@ namespace expt { size_t m_size{0}; ExecutionContext m_last_execution_context{ExecutionContext::NONE}; umpire::Allocator m_allocator{}; - }; // class PinnedArrayContainer + }; // class UnifiedMemoryArray } // namespace expt } // namespace chai -#endif // CHAI_PINNED_ARRAY_CONTAINER_HPP +#endif // CHAI_UNIFIED_MEMORY_ARRAY_HPP diff --git a/src/chai/expt/ManagedArray.hpp b/src/chai/expt/ManagedArray.hpp index db452a37..2df313cb 100644 --- a/src/chai/expt/ManagedArray.hpp +++ b/src/chai/expt/ManagedArray.hpp @@ -2,7 +2,6 @@ #define CHAI_MANAGED_ARRAY_HPP #include "chai/expt/ArrayManager.hpp" -#include "chai/expt/ExecutionContextManager.hpp" #include namespace chai { @@ -30,14 +29,9 @@ namespace expt { * * \note The array takes ownership of the manager. */ - explicit ManagedArray(ArrayManager* manager) + explicit ManagedArray(ArrayManager* manager) : m_manager{manager} - { - if (m_manager) - { - m_size = m_manager->size(); - } - } + {} /*! * \brief Constructs a shallow copy of an array from another and makes @@ -52,11 +46,7 @@ namespace expt { m_size{other.m_size}, m_manager{other.m_manager} { -#if !defined(CHAI_DEVICE_COMPILE) - if (m_manager) { - m_data = m_manager->data(!std::is_const::value)); - } -#endif + update(); } /*! @@ -83,7 +73,7 @@ namespace expt { * * \post The ManagedArray takes ownership of the new manager objet. */ - void setManager(ArrayManager* manager) + void setManager(ArrayManager* manager) { delete m_manager; m_manager = manager; @@ -94,7 +84,7 @@ namespace expt { * * \return A pointer to the array manager. */ - ArrayManager* getManager() const { + ArrayManager* getManager() const { return m_manager; } @@ -139,57 +129,43 @@ namespace expt { return m_size; } - /*! - * \brief Get a pointer to the element data in the specified context. - * - * \param context The context in which to retrieve the element data. - * - * \return A pointer to the element data in the specified context. - */ - ElementType* data(ExecutionContext context) const { + CHAI_HOST_DEVICE void update() const { +#if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_data = m_manager->data(context, !std::is_const::value); + m_data = m_manager->data(!std::is_const_v); } - - return m_data; +#endif } - /*! - * \brief Get a const pointer to the element data in the specified context. - * - * \param context The context in which to retrieve the const element data. - * - * \return A const pointer to the element data in the specified context. - */ - const ElementType* cdata(ExecutionContext context) const { + CHAI_HOST_DEVICE void cupdate() const { +#if !defined(CHAI_DEVICE_COMPILE) if (m_manager) { - m_data = m_manager->data(context, false); + m_data = m_manager->data(false); } - - return m_data; +#endif } /*! - * \brief Get a pointer to the element data in the current execution space. + * \brief Get a pointer to the element data in the specified context. + * + * \param context The context in which to retrieve the element data. * - * \return A pointer to the element data in the current execution space. + * \return A pointer to the element data in the specified context. */ CHAI_HOST_DEVICE ElementType* data() const { -#if !defined(CHAI_DEVICE_COMPILE) - return data(HOST); -#endif + update(); return m_data; } /*! - * \brief Get a const pointer to the element data in the current execution space. + * \brief Get a const pointer to the element data in the specified context. * - * \return A const pointer to the element data in the current execution space. + * \param context The context in which to retrieve the const element data. + * + * \return A const pointer to the element data in the specified context. */ CHAI_HOST_DEVICE const ElementType* cdata() const { -#if !defined(CHAI_DEVICE_COMPILE) - return cdata(HOST); -#endif + cupdate(); return m_data; } @@ -216,7 +192,7 @@ namespace expt { */ ElementType get(std::size_t i) const { if (m_manager) { - return m_manager->get(i); + return *(static_cast(m_manager->get(i*sizeof(ElementType), sizeof(ElementType)))); } else { throw std::runtime_exception("Unable to get element"); @@ -233,7 +209,7 @@ namespace expt { */ void set(std::size_t i, const ElementType& value) { if (m_manager) { - m_manager->set(i, value); + m_manager->set(i*sizeof(ElementType), sizeof(ElementType), static_cast(std::addressof(value))); } else { throw std::runtime_exception("Unable to set element"); @@ -254,7 +230,7 @@ namespace expt { /*! * The array manager controls the coherence of the array. */ - ArrayManager* m_manager = nullptr; + ArrayManager* m_manager = nullptr; }; // class ManagedArray /*! @@ -265,9 +241,9 @@ namespace expt { * * \param args The arguments to construct an array manager. */ - template , typename... Args> + template ManagedArray makeArray(Args&&... args) { - return ManagedArray(new ArrayManager(std::forward(args)...)); + return ManagedArray(new ArrayManager(std::forward(args)...)); } } // namespace expt } // namespace chai diff --git a/src/chai/expt/MemoryManager.hpp b/src/chai/expt/MemoryManager.hpp new file mode 100644 index 00000000..b82e4f24 --- /dev/null +++ b/src/chai/expt/MemoryManager.hpp @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_ARRAY_MANAGER_HPP +#define CHAI_ARRAY_MANAGER_HPP + +#include "chai/expt/Context.hpp" +#include + +namespace chai { +namespace expt { + /*! + * \class ArrayManager + * + * \brief Controls the coherence of an array. + */ + class ArrayManager { + public: + /*! + * \brief Virtual destructor. + */ + virtual ~ArrayManager() = default; + + /*! + * \brief Creates a clone of this ArrayManager. + * + * \return A new ArrayManager object that is a clone of this instance. + */ + virtual ArrayManager* clone() const = 0; + + /*! + * \brief Resizes the array to the specified new size. + * + * \param newSize The new size to resize the array to. + */ + virtual void resize(std::size_t newSize) = 0; + + /*! + * \brief Returns the size of the contained array. + * + * \return The size of the contained array. + */ + virtual std::size_t size() const = 0; + + /*! + * \brief Updates the data to be coherent in the current execution context. + * + * \param data [out] A coherent array in the current execution context. + */ + virtual T* data(Context context, bool touch) = 0; + + /*! + * \brief Returns the value at index i. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to get. + * \return The value at index i. + */ + virtual T get(std::size_t i) const = 0; + + /*! + * \brief Sets the value at index i to the specified value. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to set. + * \param value The value to set at index i. + */ + virtual void set(std::size_t i, const T& value) = 0; + }; // class ArrayManager +} // namespace expt +} // namespace chai + +#endif // CHAI_ARRAY_MANAGER_HPP diff --git a/src/chai/expt/PinnedArrayView.hpp b/src/chai/expt/PinnedArrayView.hpp new file mode 100644 index 00000000..8a942787 --- /dev/null +++ b/src/chai/expt/PinnedArrayView.hpp @@ -0,0 +1,73 @@ +#ifndef CHAI_PINNED_ARRAY_VIEW_HPP +#define CHAI_PINNED_ARRAY_VIEW_HPP + +#include "chai/expt/PinnedArrayContainer.hpp" + +namespace chai { +namespace expt { + /*! + * \class PinnedArrayView + * + * \brief Provides a non-owning view into a PinnedArrayContainer. + */ + template + class PinnedArrayView { + public: + /*! + * \brief Default constructor. + */ + PinnedArrayView() = default; + + /*! + * \brief Construct a view from a PinnedArrayContainer. + * + * \param container The container to view. + */ + explicit PinnedArrayView(PinnedArrayContainer& container) : + m_container(&container) + { + } + + /*! + * \brief Get the number of elements. + */ + size_t size() const { + return m_container ? m_container->size() : 0; + } + + /*! + * \brief Get pointer to the data for the given execution context. + */ + T* data(ExecutionContext executionContext) { + return m_container ? m_container->data(executionContext) : nullptr; + } + + /*! + * \brief Get const pointer to the data for the given execution context. + */ + const T* data(ExecutionContext executionContext) const { + return m_container ? m_container->data(executionContext) : nullptr; + } + + /*! + * \brief Get element at index i for the given execution context. + */ + T& get(ExecutionContext executionContext, size_t i) { + return m_container->get(executionContext, i); + } + + /*! + * \brief Get const element at index i for the given execution context. + */ + const T& get(ExecutionContext executionContext, size_t i) const { + return m_container->get(executionContext, i); + } + + private: + PinnedArrayContainer* m_container{nullptr}; + }; // class PinnedArrayView + +} // namespace expt +} // namespace chai + +#endif // CHAI_PINNED_ARRAY_VIEW_HPP \ No newline at end of file diff --git a/src/chai/expt/PinnedMemoryManager.hpp b/src/chai/expt/PinnedMemoryManager.hpp new file mode 100644 index 00000000..d14aa85f --- /dev/null +++ b/src/chai/expt/PinnedMemoryManager.hpp @@ -0,0 +1,295 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-25, Lawrence Livermore National Security, LLC and CHAI +// project contributors. See the CHAI LICENSE file for details. +// +// SPDX-License-Identifier: BSD-3-Clause +////////////////////////////////////////////////////////////////////////////// + +#ifndef CHAI_PINNED_MEMORY_MANAGER_HPP +#define CHAI_PINNED_MEMORY_MANAGER_HPP + +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ContextManager.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + /*! + * \class PinnedMemoryManager + * + * \brief Controls the coherence of an array on the host and device. + */ + template + class PinnedMemoryManager : public ArrayManager { + public: + /*! + * Constructs a PinnedMemoryManager with default allocators from Umpire + * for the "HOST" and "DEVICE" resources. + */ + PinnedMemoryManager() = default; + + /*! + * Constructs a PinnedMemoryManager with the given Umpire allocators. + */ + PinnedMemoryManager(const umpire::Allocator& allocator) + : ArrayManager{}, + m_allocator{allocator} + { + } + + /*! + * Constructs a PinnedMemoryManager with the given Umpire allocator IDs. + */ + PinnedMemoryManager(int allocatorID) + : ArrayManager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_allocator{m_resource_manager.getAllocator(allocatorID)} + { + } + + /*! + * Constructs a PinnedMemoryManager with the given size using default allocators + * from Umpire for the "HOST" and "DEVICE" resources. + */ + PinnedMemoryManager(std::size_t size) + : ArrayManager{}, + m_size{size} + { + // TODO: Exception handling + m_data = static_cast(m_allocator.allocate(size*sizeof(ElementT)); + } + + /*! + * Constructs a PinnedMemoryManager with the given size using the given Umpire + * allocators. + */ + PinnedMemoryManager(std::size_t size, + const umpire::Allocator& allocator) + : ArrayManager{}, + m_allocator{allocator}, + m_size{size} + { + // TODO: Exception handling + m_data = static_cast(m_allocator.allocate(size*sizeof(ElementT)); + } + + /*! + * Constructs a PinnedMemoryManager with the given size using the given Umpire + * allocator IDs. + */ + PinnedMemoryManager(std::size_t size, + int allocatorID) + : ArrayManager{}, + m_resource_manager{umpire::ResourceManager::getInstance()}, + m_allocator{m_resource_manager.getAllocator(allocatorID)}, + m_size{size} + { + // TODO: Exception handling + static_cast(m_allocator.allocate(size*sizeof(ElementT)); + } + + /*! + * Constructs a deep copy of the given PinnedMemoryManager. + */ + PinnedMemoryManager(const PinnedMemoryManager& other) + : ArrayManager{}, + m_allocator{other.m_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch} + { + if (other.m_data) + { + m_data = m_allocator.allocate(m_size); + m_resource_manager.copy(m_data, other.m_data, m_size*sizeof(ElementT)); + // TODO: The copy could potentially change in which space the last touch occurs + } + } + + /*! + * Constructs a PinnedMemoryManager that takes ownership of the + * resources from the given PinnedMemoryManager. + */ + PinnedMemoryManager(PinnedMemoryManager&& other) noexcept + : ArrayManager{}, + m_allocator{other.m_allocator}, + m_size{other.m_size}, + m_touch{other.m_touch}, + m_data{other.m_data} + { + other.m_size = 0; + other.m_data = nullptr; + other.m_touch = NONE; + } + + /*! + * \brief Virtual destructor. + */ + virtual ~PinnedMemoryManager() + { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Copy assignment operator. + */ + PinnedMemoryManager& operator=(const PinnedMemoryManager& other) + { + if (this != &other) + { + // Copy-assign or copy members + m_allocator = other.m_allocator; + m_touch = other.m_touch; + + // Allocate new resources before releasing old ones for strong exception safety + void* new_data = nullptr; + + if (other.m_data) + { + new_data = static_cast(m_allocator.allocate(other.m_size*sizeof(ElementT))); + m_resource_manager.copy(new_data, other.m_data, other.m_size*sizeof(ElementT)); + // TODO: The copy operation could change m_touch + } + + // Clean up old resources + if (m_data) + { + m_allocator.deallocate(m_data); + } + + // Assign new resources and size + m_data = new_data; + m_size = other.m_size; + } + + return *this; + } + + /*! + * \brief Move assignment operator. + */ + PinnedMemoryManager& operator=(PinnedMemoryManager&& other) noexcept + { + if (this != &other) + { + // Release any resources currently held + if (m_data) + { + m_allocator.deallocate(m_data); + } + + // Move-assign or copy members + m_allocator = other.m_allocator; + m_size = other.m_size; + m_data = other.m_data; + m_touch = other.m_touch; + + // Null out other's pointers and reset size + other.m_data = nullptr; + other.m_size = 0; + other.m_touch = NONE; + } + return *this; + } + + /*! + * \brief Resize the underlying arrays. + */ + virtual void resize(std::size_t newSize) override + { + if (newSize != m_size) + { + // TODO: Is any synchronization needed? + m_resource_manager.reallocate(m_data, newSize); + m_size = newSize; + } + } + + /*! + * \brief Get the size of the underlying arrays. + */ + virtual std::size_t size() const override + { + return m_size; + } + + /*! + * \brief Updates the data to be coherent in the current execution space. + */ + virtual ElementT* data(Context context, bool touch) override + { + ElementT* result{nullptr}; + + if (context == HOST) + { + if (m_touch == DEVICE) + { + m_context_manager.synchronize(DEVICE); + m_touch = NONE; + } + + if (touch) + { + m_touch = HOST; + } + + result = m_data; + } + else if (context == DEVICE) + { + if (m_touch == HOST) + { + // TODO: Should we call m_context_manager.synchronize(HOST)? Would support host openmp. + m_touch = NONE; + } + + if (touch) + { + m_touch = DEVICE; + } + + result = m_data; + } + + return result; + } + + /*! + * \brief Returns the value at index i. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to get. + * \return The value at index i. + */ + virtual ElementT get(std::size_t i) const override { + m_context_manager.synchronize(m_touch); + m_touch = NONE; + return m_data[i]; + } + + /*! + * \brief Sets the value at index i to the specified value. + * + * Note: Use this function sparingly as it may be slow. + * + * \param i The index of the element to set. + * \param value The value to set at index i. + */ + virtual void set(std::size_t i, const ElementT& value) override + { + m_context_manager.synchronize(m_touch); + m_touch = HOST; + m_data[i] = value; + } + + private: + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_allocator{m_resource_manager.getAllocator("DEVICE")}; + std::size_t m_size{0}; + ElementT* m_data{nullptr}; + ExecutionContext m_touch{NONE}; + }; // class PinnedMemoryManager +} // namespace expt +} // namespace chai + +#endif // CHAI_PINNED_MEMORY_MANAGER_HPP diff --git a/src/chai/expt/UnifiedArrayManager.hpp b/src/chai/expt/UnifiedArrayManager.hpp new file mode 100644 index 00000000..93e168df --- /dev/null +++ b/src/chai/expt/UnifiedArrayManager.hpp @@ -0,0 +1,169 @@ +#ifndef CHAI_UNIFIED_ARRAY_MANAGER_HPP +#define CHAI_UNIFIED_ARRAY_MANAGER_HPP + +#include "chai/expt/ArrayManager.hpp" +#include "chai/expt/ExecutionContext.hpp" +#include "umpire/ResourceManager.hpp" + +namespace chai { +namespace expt { + class UnifiedArrayManager : public ArrayManager { + public: + UnifiedArrayManager() = default; + + explicit UnifiedArrayManager(const umpire::Allocator& allocator) : + m_allocator{allocator} + { + } + + UnifiedArrayManager(std::size_t size, const umpire::Allocator& allocator) : + m_allocator{allocator}, + m_size{size}, + m_data{m_allocator.allocate(m_size)} + { + } + + explicit UnifiedArrayManager(int allocatorID) : + m_allocator{m_resource_manager.getAllocator(allocatorID)} + { + } + + UnifiedArrayManager(std::size_t size, int allocatorID) : + m_allocator{m_resource_manager.getAllocator(allocatorID)}, + m_size{size}, + m_data{m_allocator.allocate(m_size)} + { + m_data = m_allocator.allocate(size); + } + + UnifiedArrayManager(const UnifiedArrayManager& other) : + m_size{other.m_size}, + m_allocator{other.m_allocator} + { + m_data = m_allocator.allocate(m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::DEVICE); + m_resource_manager.copy(other.m_data, m_data, m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::NONE); + m_modified = ExecutionContext::DEVICE; + } + + UnifiedArrayManager(UnifiedArrayManager&& other) : + m_data{other.m_data}, + m_size{other.m_size}, + m_modified{other.m_modified}, + m_allocator{other.m_allocator} + { + other.m_data = nullptr; + other.m_size = 0; + other.m_modified = NONE; + other.m_allocator = umpire::Allocator(); + } + + UnifiedArrayManager& operator=(const UnifiedArrayManager& other) { + if (&other != this) { // Prevent self-assignment + m_allocator.deallocate(m_data); + + m_size = other.m_size; + m_allocator = other.m_allocator; + m_data = m_allocator.allocate(m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::DEVICE); + m_resource_manager.copy(other.m_data, m_data, m_size * sizeof(T)); + m_execution_context_manager.setExecutionContext(ExecutionContext::NONE); + m_modified = ExecutionContext::DEVICE; + } + + return *this; + } + + UnifiedArrayManager& operator=(UnifiedArrayManager&& other) { + if (&other != this) { + m_allocator.deallocate(m_data); + + m_data = other.m_data; + m_size = other.m_size; + m_modified = other.m_modified; + m_allocator = other.m_allocator; + + other.m_data = nullptr; + other.m_size = 0; + other.m_modified = ExecutionContext::NONE; + other.m_allocator = umpire::Allocator(); + } + + return *this; + } + + /*! + * \brief Destructor. + */ + ~UnifiedArrayManager() { + m_allocator.deallocate(m_data); + } + + /*! + * \brief Get the number of elements. + */ + size_t size() const { + return m_size; + } + + void* data() { + ExecutionContext executionContext = m_execution_context_manager.getExecutionContext(); + + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = executionContext; + } + + return m_data; + } + + const T* data(ExecutionContext executionContext) const { + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = ExecutionContext::NONE; + } + + return m_data; + } + + T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = executionContext; + } + + return m_data[i]; + } + + const T& get(ExecutionContext executionContext, size_t i) { + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = ExecutionContext::NONE; + } + + return m_data[i]; + } + + ExecutionContext getModified() { + return m_modified; + } + + umpire::Allocator getAllocator() { + return m_allocator; + } + + private: + umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; + umpire::Allocator m_allocator{}; + T* m_data{nullptr}; + size_t m_size{0}; + ExecutionContext m_modified{ExecutionContext::NONE}; + + ExecutionContextManager& m_execution_context_manager{ExecutionContextManager::getInstance()}; + + }; // class UnifiedArrayManager +} // namespace expt +} // namespace chai + +#endif // CHAI_UNIFIED_ARRAY_MANAGER_HPP diff --git a/src/chai/expt/UnifiedMemoryPointer.hpp b/src/chai/expt/UnifiedMemoryArrayPointer.hpp similarity index 100% rename from src/chai/expt/UnifiedMemoryPointer.hpp rename to src/chai/expt/UnifiedMemoryArrayPointer.hpp diff --git a/src/chai/expt/UnifiedMemoryManager.hpp b/src/chai/expt/UnifiedMemoryManager.hpp index 385de46b..ef376194 100644 --- a/src/chai/expt/UnifiedMemoryManager.hpp +++ b/src/chai/expt/UnifiedMemoryManager.hpp @@ -43,18 +43,18 @@ namespace expt { m_execution_context_manager.setExecutionContext(ExecutionContext::DEVICE); m_resource_manager.copy(other.m_data, m_data, m_size * sizeof(T)); m_execution_context_manager.setExecutionContext(ExecutionContext::NONE); - m_last_execution_context = ExecutionContext::DEVICE; + m_modified = ExecutionContext::DEVICE; } UnifiedMemoryManager(UnifiedMemoryManager&& other) : m_data{other.m_data}, m_size{other.m_size}, - m_last_execution_context{other.m_last_execution_context}, + m_modified{other.m_modified}, m_allocator{other.m_allocator} { other.m_data = nullptr; other.m_size = 0; - other.m_last_execution_context = NONE; + other.m_modified = NONE; other.m_allocator = umpire::Allocator(); } @@ -68,7 +68,7 @@ namespace expt { m_execution_context_manager.setExecutionContext(ExecutionContext::DEVICE); m_resource_manager.copy(other.m_data, m_data, m_size * sizeof(T)); m_execution_context_manager.setExecutionContext(ExecutionContext::NONE); - m_last_execution_context = ExecutionContext::DEVICE; + m_modified = ExecutionContext::DEVICE; } return *this; @@ -80,12 +80,12 @@ namespace expt { m_data = other.m_data; m_size = other.m_size; - m_last_execution_context = other.m_last_execution_context; + m_modified = other.m_modified; m_allocator = other.m_allocator; other.m_data = nullptr; other.m_size = 0; - other.m_last_execution_context = ExecutionContext::NONE; + other.m_modified = ExecutionContext::NONE; other.m_allocator = umpire::Allocator(); } @@ -106,48 +106,58 @@ namespace expt { return m_size; } - T* data(ExecutionContext executionContext) { - if (executionContext != m_last_execution_context) { - m_execution_context_manager.synchronize(m_last_execution_context); - m_last_execution_context = executionContext; + void* data() { + ExecutionContext executionContext = m_execution_context_manager.getExecutionContext(); + + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = executionContext; } return m_data; } const T* data(ExecutionContext executionContext) const { - if (executionContext != m_last_execution_context) { - m_execution_context_manager.synchronize(m_last_execution_context); - m_last_execution_context = ExecutionContext::NONE; + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = ExecutionContext::NONE; } return m_data; } T& get(ExecutionContext executionContext, size_t i) { - if (executionContext != m_last_execution_context) { - m_execution_context_manager.synchronize(m_last_execution_context); - m_last_execution_context = executionContext; + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = executionContext; } return m_data[i]; } const T& get(ExecutionContext executionContext, size_t i) { - if (executionContext != m_last_execution_context) { - m_execution_context_manager.synchronize(m_last_execution_context); - m_last_execution_context = ExecutionContext::NONE; + if (executionContext != m_modified) { + m_execution_context_manager.synchronize(m_modified); + m_modified = ExecutionContext::NONE; } return m_data[i]; } + ExecutionContext getModified() { + return m_modified; + } + + umpire::Allocator getAllocator() { + return m_allocator; + } + private: umpire::ResourceManager& m_resource_manager{umpire::ResourceManager::getInstance()}; umpire::Allocator m_allocator{}; T* m_data{nullptr}; size_t m_size{0}; - ExecutionContext m_last_execution_context{ExecutionContext::NONE}; + ExecutionContext m_modified{ExecutionContext::NONE}; ExecutionContextManager& m_execution_context_manager{ExecutionContextManager::getInstance()};