From be97c803df9ad0bc6a13947ea7a62d0c56d48e32 Mon Sep 17 00:00:00 2001 From: Mario Gonzalez Date: Thu, 5 Mar 2026 14:01:22 +0100 Subject: [PATCH 1/5] Extend the HeterogeneousCore/TrivialSerialisation mechanism to device products Co-authored-by: Andrea Bocci --- .../interface/PortableDeviceCollection.h | 35 ++ .../Portable/interface/PortableDeviceObject.h | 25 ++ .../interface/PortableHostCollection.h | 11 +- .../Portable/interface/PortableHostObject.h | 14 +- .../PortableTestObjects/plugins/BuildFile.xml | 7 + .../plugins/alpaka/TrivialSerialisation.cc | 20 + .../interface/MemoryCopyTraits.h | 22 +- .../TrivialSerialisation/test/BuildFile.xml | 13 + ...est_catch2_MemoryCopyTraitsPortable.dev.cc | 363 ++++++++++++++++++ .../TrivialSerialisation/BuildFile.xml | 4 + .../TrivialSerialisation/interface/Common.h | 51 +++ .../TrivialSerialisation/interface/Reader.h | 25 +- .../interface/ReaderBase.h | 7 +- .../interface/Serialiser.h | 7 +- .../interface/SerialiserBase.h | 8 +- .../interface/SerialiserFactory.h | 25 +- .../TrivialSerialisation/interface/Writer.h | 32 +- .../interface/WriterBase.h | 7 +- .../interface/alpaka/Reader.h | 41 ++ .../interface/alpaka/ReaderBase.h | 26 ++ .../interface/alpaka/Serialiser.h | 41 ++ .../interface/alpaka/SerialiserBase.h | 26 ++ .../alpaka/SerialiserFactoryDevice.h | 30 ++ .../interface/alpaka/Writer.h | 84 ++++ .../interface/alpaka/WriterBase.h | 32 ++ .../src/alpaka/SerialiserFactoryDevice.cc | 4 + .../TrivialSerialisation/test/BuildFile.xml | 15 + .../test/alpaka/test_catch2_main.cc | 2 + ...eCollectionsSerialiserPluginFactory.dev.cc | 342 +++++++++++++++++ 29 files changed, 1246 insertions(+), 73 deletions(-) create mode 100644 DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/TrivialSerialisation/test/alpaka/test_catch2_MemoryCopyTraitsPortable.dev.cc create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/Common.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/Reader.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/Serialiser.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserBase.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/Writer.h create mode 100644 HeterogeneousCore/TrivialSerialisation/interface/alpaka/WriterBase.h create mode 100644 HeterogeneousCore/TrivialSerialisation/src/alpaka/SerialiserFactoryDevice.cc create mode 100644 HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_main.cc create mode 100644 HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_portableCollectionsSerialiserPluginFactory.dev.cc diff --git a/DataFormats/Portable/interface/PortableDeviceCollection.h b/DataFormats/Portable/interface/PortableDeviceCollection.h index ec357e8ecdcc0..76c1c556bb11b 100644 --- a/DataFormats/Portable/interface/PortableDeviceCollection.h +++ b/DataFormats/Portable/interface/PortableDeviceCollection.h @@ -12,6 +12,7 @@ #include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Portable/interface/PortableCollectionCommon.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" #include "HeterogeneousCore/AlpakaInterface/interface/memory.h" @@ -146,4 +147,38 @@ class PortableDeviceCollection { View view_; //! }; +namespace ngt { + + // Specialize MemoryCopyTraits for PortableDeviceCollection + template + struct MemoryCopyTraits> { + using value_type = PortableDeviceCollection; + + // Properties are the collection size: T::size_type, or std::array for SoABlocks. + using Properties = decltype(std::declval()->metadata().size()); + + static Properties properties(value_type const& object) { return object->metadata().size(); } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& size) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in global device memory + object = value_type(queue, size); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; +} // namespace ngt + #endif // DataFormats_Portable_interface_PortableDeviceCollection_h diff --git a/DataFormats/Portable/interface/PortableDeviceObject.h b/DataFormats/Portable/interface/PortableDeviceObject.h index 5cbd139a59752..4618c2d26eb95 100644 --- a/DataFormats/Portable/interface/PortableDeviceObject.h +++ b/DataFormats/Portable/interface/PortableDeviceObject.h @@ -8,6 +8,7 @@ #include #include "DataFormats/Common/interface/Uninitialized.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" #include "HeterogeneousCore/AlpakaInterface/interface/memory.h" @@ -80,4 +81,28 @@ class PortableDeviceObject { std::optional buffer_; }; +namespace ngt { + + // Specialize MemoryCopyTraits for PortableDeviceObject + template + struct MemoryCopyTraits> { + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, PortableDeviceObject& object) { + // Replace the default-constructed empty object with one where the + // buffer has been allocated in global device memory + object = PortableDeviceObject(queue); + } + + static std::vector> regions(PortableDeviceObject& object) { + return {{reinterpret_cast(object.data()), sizeof(T)}}; + } + + static std::vector> regions(PortableDeviceObject const& object) { + return {{reinterpret_cast(object.data()), sizeof(T)}}; + } + }; + +} // namespace ngt + #endif // DataFormats_Portable_interface_PortableDeviceObject_h diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index c793fbf6d87fb..52c7c1720b660 100644 --- a/DataFormats/Portable/interface/PortableHostCollection.h +++ b/DataFormats/Portable/interface/PortableHostCollection.h @@ -200,8 +200,17 @@ namespace ngt { // The properties needed to initialize a new PrortableHostCollection are just its size. static Properties properties(value_type const& object) { return object->metadata().size(); } - // Replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory. + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& size) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pinned host memory. + object = value_type(queue, size); + } + static void initialize(value_type& object, Properties const& size) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pageable host memory. object = value_type(cms::alpakatools::host(), size); } diff --git a/DataFormats/Portable/interface/PortableHostObject.h b/DataFormats/Portable/interface/PortableHostObject.h index 1c39278be414b..48cba17f51662 100644 --- a/DataFormats/Portable/interface/PortableHostObject.h +++ b/DataFormats/Portable/interface/PortableHostObject.h @@ -121,11 +121,21 @@ namespace ngt { template struct MemoryCopyTraits> { - // This specialisation requires a initialize() method, but does not need to pass any parameters to it. + // This specialisation requires an initialize() method, but does not need to + // pass any parameters to it. using Properties = void; + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, PortableHostObject& object) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pinned host memory + object = PortableHostObject(queue); + } + static void initialize(PortableHostObject& object) { - // Replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory. + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pageable host memory. object = PortableHostObject(cms::alpakatools::host()); } diff --git a/DataFormats/PortableTestObjects/plugins/BuildFile.xml b/DataFormats/PortableTestObjects/plugins/BuildFile.xml index bac81f07c93fc..67e28a8fa6daf 100644 --- a/DataFormats/PortableTestObjects/plugins/BuildFile.xml +++ b/DataFormats/PortableTestObjects/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..9ebb83cb1c748 --- /dev/null +++ b/DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,20 @@ +#include "DataFormats/PortableTestObjects/interface/alpaka/ImageDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/LogitsDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/MaskDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/MultiHeadNetDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/ParticleDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/SimpleNetDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceObject.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::ImageDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::LogitsDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::MaskDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::MultiHeadNetDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::ParticleDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::SimpleNetDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::TestDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::TestDeviceCollection2); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::TestDeviceCollection3); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(portabletest::TestDeviceObject); diff --git a/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h b/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h index 57f2511b72d7a..e56b6a0e9fdb9 100644 --- a/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h +++ b/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h @@ -54,36 +54,38 @@ namespace ngt { // Checks if the properties method is defined template - concept HasTrivialCopyProperties = requires(T const& object) { MemoryCopyTraits::properties(object); }; + concept HasTrivialCopyProperties = requires(T const& object) { ngt::MemoryCopyTraits::properties(object); }; // Get the return type of properties(...), if it exists. template - requires HasTrivialCopyProperties - using TrivialCopyProperties = decltype(MemoryCopyTraits::properties(std::declval())); + requires ngt::HasTrivialCopyProperties + using TrivialCopyProperties = decltype(ngt::MemoryCopyTraits::properties(std::declval())); // Checks if the declaration of initialize(...) is consistent with the presence or absence of properties. template concept HasValidInitialize = // does not have properties(...) and initialize(object) takes a single argument, or - (not HasTrivialCopyProperties and requires(T& object) { MemoryCopyTraits::initialize(object); }) or + (not ngt::HasTrivialCopyProperties and + requires(T& object) { ngt::MemoryCopyTraits::initialize(object); }) or // does have properties(...) and initialize(object, props) takes two arguments - (HasTrivialCopyProperties and - requires(T& object, TrivialCopyProperties props) { MemoryCopyTraits::initialize(object, props); }); + (ngt::HasTrivialCopyProperties and requires(T& object, ngt::TrivialCopyProperties props) { + ngt::MemoryCopyTraits::initialize(object, props); + }); // Checks for const and non const memory regions template concept HasRegions = requires(T& object, T const& const_object) { - MemoryCopyTraits::regions(object); - MemoryCopyTraits::regions(const_object); + ngt::MemoryCopyTraits::regions(object); + ngt::MemoryCopyTraits::regions(const_object); }; // Checks if there is a valid specialisation of MemoryCopyTraits for a type T template concept HasMemoryCopyTraits = // Has memory regions declared and - (HasRegions) and + (ngt::HasRegions) and // has either no initialize(...) or a valid one - (not requires { &MemoryCopyTraits::initialize; } or HasValidInitialize); + (not requires { &ngt::MemoryCopyTraits::initialize; } or ngt::HasValidInitialize); // Checks if finalize(...) is defined template diff --git a/DataFormats/TrivialSerialisation/test/BuildFile.xml b/DataFormats/TrivialSerialisation/test/BuildFile.xml index 5da9ef00be32e..1ffe7d7e317ff 100644 --- a/DataFormats/TrivialSerialisation/test/BuildFile.xml +++ b/DataFormats/TrivialSerialisation/test/BuildFile.xml @@ -2,3 +2,16 @@ + + + + + + + + + + + + + diff --git a/DataFormats/TrivialSerialisation/test/alpaka/test_catch2_MemoryCopyTraitsPortable.dev.cc b/DataFormats/TrivialSerialisation/test/alpaka/test_catch2_MemoryCopyTraitsPortable.dev.cc new file mode 100644 index 0000000000000..0ba52aee959e9 --- /dev/null +++ b/DataFormats/TrivialSerialisation/test/alpaka/test_catch2_MemoryCopyTraitsPortable.dev.cc @@ -0,0 +1,363 @@ +#include +#include + +#include + +#include + +#include + +#include "DataFormats/Portable/interface/PortableCollection.h" +#include "DataFormats/Portable/interface/PortableHostCollection.h" +#include "DataFormats/Portable/interface/PortableHostObject.h" +#include "DataFormats/PortableTestObjects/interface/TestSoA.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceObject.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" + +using namespace ALPAKA_ACCELERATOR_NAMESPACE; +namespace alpaka_portable_test = ALPAKA_ACCELERATOR_NAMESPACE::portabletest; + +/* + * Catch2 tests for MemoryCopyTraits specializations of portable types. + * + * This test verifies that: + * - PortableHostCollection has valid MemoryCopyTraits with Properties and initialize() + * - PortableHostObject has valid MemoryCopyTraits with initialize() but no Properties + * - PortableDeviceCollection has valid MemoryCopyTraits with Properties and initialize() + * - PortableDeviceObject has valid MemoryCopyTraits with initialize() but no Properties + * - Multi-block collections (SoABlocks2, SoABlocks3) have valid MemoryCopyTraits + * + * It also tests that data can be correctly copied using the MemoryCopyTraits interface + * for both host and device portable types. + */ + +TEST_CASE("Test MemoryCopyTraits with portable types", "[MemoryCopyTraits][Portable]") { + auto const& devices = cms::alpakatools::devices(); + if (devices.empty()) { + FAIL("No devices available for the " EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) " backend"); + } + + SECTION("PortableHostCollection checks") { + using HostCollectionType = PortableHostCollection>; + + // PortableHostCollection should have MemoryCopyTraits + static_assert(::ngt::HasMemoryCopyTraits); + // It should have Properties (the size) + static_assert(::ngt::HasTrivialCopyProperties); + // It should have a valid initialize() + static_assert(::ngt::HasValidInitialize); + // It should have regions + static_assert(::ngt::HasRegions); + } + + SECTION("PortableHostObject checks") { + using HostObjectType = PortableHostObject; + + // PortableHostObject should have MemoryCopyTraits + static_assert(::ngt::HasMemoryCopyTraits); + // It should not have Properties + static_assert(not ::ngt::HasTrivialCopyProperties); + // It should have regions + static_assert(::ngt::HasRegions); + } + + SECTION("PortableDeviceCollection checks") { + using DeviceCollectionType = alpaka_portable_test::TestDeviceCollection; + + // PortableDeviceCollection should have MemoryCopyTraits + static_assert(::ngt::HasMemoryCopyTraits); + // It should have Properties (the size) + static_assert(::ngt::HasTrivialCopyProperties); + // It should have regions + static_assert(::ngt::HasRegions); + } + + SECTION("PortableDeviceObject checks") { + using DeviceObjectType = alpaka_portable_test::TestDeviceObject; + + // PortableDeviceObject should have MemoryCopyTraits + static_assert(::ngt::HasMemoryCopyTraits); + // It should not have Properties + static_assert(not ::ngt::HasTrivialCopyProperties); + // It should have regions + static_assert(::ngt::HasRegions); + } + + SECTION("Multi-block PortableHostCollection checks") { + // SoABlocks2 + using HostCollection2Type = PortableHostCollection; + static_assert(::ngt::HasMemoryCopyTraits); + static_assert(::ngt::HasTrivialCopyProperties); + static_assert(::ngt::HasRegions); + + // SoABlocks3 + using HostCollection3Type = PortableHostCollection; + static_assert(::ngt::HasMemoryCopyTraits); + static_assert(::ngt::HasTrivialCopyProperties); + static_assert(::ngt::HasRegions); + } + + SECTION("Multi-block PortableDeviceCollection checks") { + using DeviceCollection2Type = alpaka_portable_test::TestDeviceCollection2; + static_assert(::ngt::HasMemoryCopyTraits); + static_assert(::ngt::HasTrivialCopyProperties); + static_assert(::ngt::HasRegions); + + using DeviceCollection3Type = alpaka_portable_test::TestDeviceCollection3; + static_assert(::ngt::HasMemoryCopyTraits); + static_assert(::ngt::HasTrivialCopyProperties); + static_assert(::ngt::HasRegions); + } + + SECTION("PortableHostCollection copy via MemoryCopyTraits") { + using HostCollectionType = PortableHostCollection>; + const int size = 10; + + for (auto const& device : devices) { + Queue queue(device); + + // Create a source collection and fill it with data + HostCollectionType source(queue, size); + source.view().r() = 3.14; + for (int i = 0; i < size; i++) { + source.view()[i].x() = i * 10.0 + 1; + source.view()[i].y() = i * 10.0 + 2; + source.view()[i].z() = i * 10.0 + 3; + source.view()[i].id() = i; + source.view()[i].flags() = std::array{ + {static_cast(i), static_cast(i + 1), static_cast(i + 2), static_cast(i + 3)}}; + source.view()[i].m = Eigen::Matrix::Identity() * i; + } + + // Get properties from source + auto props = ::ngt::MemoryCopyTraits::properties(source); + REQUIRE(props == size); + + // Create a clone and initialize it + HostCollectionType clone(edm::kUninitialized); + ::ngt::MemoryCopyTraits::initialize(clone, props); + + // Get regions and copy + auto sources_regions = ::ngt::MemoryCopyTraits::regions(std::as_const(source)); + auto targets_regions = ::ngt::MemoryCopyTraits::regions(clone); + + REQUIRE(sources_regions.size() == targets_regions.size()); + REQUIRE(sources_regions.size() == 1); + REQUIRE(sources_regions[0].size_bytes() == targets_regions[0].size_bytes()); + + std::memcpy(targets_regions[0].data(), sources_regions[0].data(), sources_regions[0].size_bytes()); + + // Verify clone + REQUIRE(clone.const_view().r() == source.const_view().r()); + for (int i = 0; i < size; i++) { + REQUIRE(clone.const_view()[i].x() == source.const_view()[i].x()); + REQUIRE(clone.const_view()[i].y() == source.const_view()[i].y()); + REQUIRE(clone.const_view()[i].z() == source.const_view()[i].z()); + REQUIRE(clone.const_view()[i].id() == source.const_view()[i].id()); + REQUIRE(clone.const_view()[i].flags() == source.const_view()[i].flags()); + REQUIRE(clone.const_view()[i].m() == source.const_view()[i].m()); + } + } + } + + SECTION("PortableHostObject copy via MemoryCopyTraits") { + using HostObjectType = PortableHostObject; + + for (auto const& device : devices) { + Queue queue(device); + + // Create source object + HostObjectType source(cms::alpakatools::host()); + source->x = 5.0; + source->y = 12.0; + source->z = 13.0; + source->id = 42; + + // Create an uninitialized clone + HostObjectType clone(edm::kUninitialized); + ::ngt::MemoryCopyTraits::initialize(clone); + + // Get regions and copy + auto sources_regions = ::ngt::MemoryCopyTraits::regions(std::as_const(source)); + auto targets_regions = ::ngt::MemoryCopyTraits::regions(clone); + + REQUIRE(sources_regions.size() == targets_regions.size()); + REQUIRE(sources_regions.size() == 1); + REQUIRE(sources_regions[0].size_bytes() == sizeof(alpaka_portable_test::TestStruct)); + REQUIRE(targets_regions[0].size_bytes() == sizeof(alpaka_portable_test::TestStruct)); + + std::memcpy(targets_regions[0].data(), sources_regions[0].data(), sources_regions[0].size_bytes()); + + // Verify clone + REQUIRE(clone->x == 5.0); + REQUIRE(clone->y == 12.0); + REQUIRE(clone->z == 13.0); + REQUIRE(clone->id == 42); + } + } + + SECTION("PortableDeviceCollection copy via MemoryCopyTraits") { + using DeviceCollectionType = alpaka_portable_test::TestDeviceCollection; + using HostCollectionType = PortableHostCollection>; + const int size = 10; + + for (auto const& device : devices) { + Queue queue(device); + + // Create a reference host collection with known data + HostCollectionType refHost(queue, size); + refHost.view().r() = 2.71; + for (int i = 0; i < size; i++) { + refHost.view()[i].x() = i * 100.0; + refHost.view()[i].y() = i * 200.0; + refHost.view()[i].z() = i * 300.0; + refHost.view()[i].id() = i + 100; + refHost.view()[i].flags() = std::array{ + {static_cast(i), static_cast(i + 1), static_cast(i + 2), static_cast(i + 3)}}; + refHost.view()[i].m = Eigen::Matrix::Identity() * i; + } + + // Create a device collection and copy the reference data to it + DeviceCollectionType source(queue, size); + alpaka::memcpy(queue, source.buffer(), refHost.buffer()); + alpaka::wait(queue); + + // Get properties + auto props = ::ngt::MemoryCopyTraits::properties(source); + REQUIRE(props == size); + + // Create a clone on device + DeviceCollectionType clone(edm::kUninitialized); + ::ngt::MemoryCopyTraits::initialize(queue, clone, props); + + // Get regions and copy (device-to-device via alpaka) + auto sources_regions = ::ngt::MemoryCopyTraits::regions(std::as_const(source)); + auto targets_regions = ::ngt::MemoryCopyTraits::regions(clone); + + REQUIRE(sources_regions.size() == targets_regions.size()); + for (size_t i = 0; i < sources_regions.size(); ++i) { + REQUIRE(sources_regions[i].size_bytes() == targets_regions[i].size_bytes()); + auto src_view = alpaka::createView(device, sources_regions[i].data(), sources_regions[i].size_bytes()); + auto trg_view = alpaka::createView(device, targets_regions[i].data(), targets_regions[i].size_bytes()); + alpaka::memcpy(queue, trg_view, src_view); + } + alpaka::wait(queue); + + // Copy the clone back to host to verify + HostCollectionType verifyHost(queue, size); + alpaka::memcpy(queue, verifyHost.buffer(), clone.buffer()); + alpaka::wait(queue); + + REQUIRE(verifyHost.const_view().r() == refHost.const_view().r()); + for (int i = 0; i < size; i++) { + REQUIRE(verifyHost.const_view()[i].x() == refHost.const_view()[i].x()); + REQUIRE(verifyHost.const_view()[i].y() == refHost.const_view()[i].y()); + REQUIRE(verifyHost.const_view()[i].z() == refHost.const_view()[i].z()); + REQUIRE(verifyHost.const_view()[i].id() == refHost.const_view()[i].id()); + REQUIRE(verifyHost.const_view()[i].flags() == refHost.const_view()[i].flags()); + } + } + } + + SECTION("PortableDeviceObject copy via MemoryCopyTraits") { + using DeviceObjectType = alpaka_portable_test::TestDeviceObject; + + for (auto const& device : devices) { + Queue queue(device); + + // Create source object on host, copy to device + PortableHostObject hostSource(queue); + hostSource->x = 1.0; + hostSource->y = 2.0; + hostSource->z = 3.0; + hostSource->id = 99; + + DeviceObjectType source(queue); + alpaka::memcpy(queue, source.buffer(), hostSource.buffer()); + alpaka::wait(queue); + + // Create an uninitialized clone + DeviceObjectType clone(edm::kUninitialized); + ::ngt::MemoryCopyTraits::initialize(queue, clone); + + // Get regions and copy + auto sources_regions = ::ngt::MemoryCopyTraits::regions(std::as_const(source)); + auto targets_regions = ::ngt::MemoryCopyTraits::regions(clone); + + REQUIRE(sources_regions.size() == 1); + REQUIRE(targets_regions.size() == 1); + REQUIRE(sources_regions[0].size_bytes() == sizeof(alpaka_portable_test::TestStruct)); + + auto src_view = alpaka::createView(device, sources_regions[0].data(), sources_regions[0].size_bytes()); + auto trg_view = alpaka::createView(device, targets_regions[0].data(), targets_regions[0].size_bytes()); + alpaka::memcpy(queue, trg_view, src_view); + alpaka::wait(queue); + + // Copy clone back to host and verify + PortableHostObject hostClone(queue); + alpaka::memcpy(queue, hostClone.buffer(), clone.buffer()); + alpaka::wait(queue); + + REQUIRE(hostClone->x == 1.0); + REQUIRE(hostClone->y == 2.0); + REQUIRE(hostClone->z == 3.0); + REQUIRE(hostClone->id == 99); + } + } + + SECTION("Multi-block PortableHostCollection copy via MemoryCopyTraits") { + using HostCollection2Type = PortableHostCollection; + const int size1 = 8; + const int size2 = 12; + + for (auto const& device : devices) { + Queue queue(device); + + // Create source with two blocks + HostCollection2Type source(queue, size1, size2); + source.view().first().r() = 1.23; + source.view().second().r2() = 4.56; + for (int i = 0; i < size1; i++) { + source.view().first()[i].x() = i * 1.0; + source.view().first()[i].id() = i; + } + for (int i = 0; i < size2; i++) { + source.view().second()[i].x2() = i * 2.0; + source.view().second()[i].id2() = i + 100; + } + + // Get properties + auto props = ::ngt::MemoryCopyTraits::properties(source); + REQUIRE(props[0] == size1); + REQUIRE(props[1] == size2); + + // Create clone and initialize + HostCollection2Type clone(edm::kUninitialized); + ::ngt::MemoryCopyTraits::initialize(clone, props); + + // Copy regions + auto sources_regions = ::ngt::MemoryCopyTraits::regions(std::as_const(source)); + auto targets_regions = ::ngt::MemoryCopyTraits::regions(clone); + + REQUIRE(sources_regions.size() == targets_regions.size()); + for (size_t i = 0; i < sources_regions.size(); ++i) { + REQUIRE(sources_regions[i].size_bytes() == targets_regions[i].size_bytes()); + std::memcpy(targets_regions[i].data(), sources_regions[i].data(), sources_regions[i].size_bytes()); + } + + // Verify + REQUIRE(clone.const_view().first().r() == 1.23); + REQUIRE(clone.const_view().second().r2() == 4.56); + for (int i = 0; i < size1; i++) { + REQUIRE(clone.const_view().first()[i].x() == i * 1.0); + REQUIRE(clone.const_view().first()[i].id() == i); + } + for (int i = 0; i < size2; i++) { + REQUIRE(clone.const_view().second()[i].x2() == i * 2.0); + REQUIRE(clone.const_view().second()[i].id2() == i + 100); + } + } + } +} diff --git a/HeterogeneousCore/TrivialSerialisation/BuildFile.xml b/HeterogeneousCore/TrivialSerialisation/BuildFile.xml index 6eeacf9dd884c..d7bfd7e7d5267 100644 --- a/HeterogeneousCore/TrivialSerialisation/BuildFile.xml +++ b/HeterogeneousCore/TrivialSerialisation/BuildFile.xml @@ -1,7 +1,11 @@ + + + + diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Common.h b/HeterogeneousCore/TrivialSerialisation/interface/Common.h new file mode 100644 index 0000000000000..7a04a21926958 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/Common.h @@ -0,0 +1,51 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_Common_h +#define HeterogeneousCore_TrivialSerialisation_interface_Common_h + +#include +#include +#include + +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" + +namespace ngt { + + // Helper functions used by both the alpaka and non-alpaka variants of the + // Reader and Writer. + + // Return the TrivialCopyProperties for an object, wrapped in an AnyBuffer. + // Returns an empty AnyBuffer if MemoryCopyTraits has no properties(). + template + ngt::AnyBuffer get_properties(T const& obj) { + if constexpr (not ngt::HasTrivialCopyProperties) { + return {}; + } else { + return ngt::AnyBuffer(ngt::MemoryCopyTraits::properties(obj)); + } + } + + // Return the memory regions of an object (mutable) + template + std::vector> get_regions(T& obj) { + static_assert(ngt::HasRegions); + return ngt::MemoryCopyTraits::regions(obj); + } + + // Return the memory regions of an object (const) + template + std::vector> get_regions(T const& obj) { + static_assert(ngt::HasRegions); + return ngt::MemoryCopyTraits::regions(obj); + } + + // Call finalize() on an object, if MemoryCopyTraits provides it + template + void do_finalize(T& obj) { + if constexpr (ngt::HasTrivialCopyFinalize) { + ngt::MemoryCopyTraits::finalize(obj); + } + } + +} // namespace ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_Common_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Reader.h b/HeterogeneousCore/TrivialSerialisation/interface/Reader.h index d358dd9f560bf..c8d26e49ab333 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/Reader.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/Reader.h @@ -1,5 +1,5 @@ -#ifndef TrivialSerialisation_Common_interface_Reader_h -#define TrivialSerialisation_Common_interface_Reader_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_Reader_h +#define HeterogeneousCore_TrivialSerialisation_interface_Reader_h #include #include @@ -9,10 +9,12 @@ #include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "FWCore/Utilities/interface/EDMException.h" #include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Common.h" #include "HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h" namespace ngt { + // Reader for host products: extracts properties and memory regions from a Wrapper. template class Reader : public ReaderBase { static_assert(ngt::HasMemoryCopyTraits, "No specialization of MemoryCopyTraits found for type T"); @@ -20,22 +22,11 @@ namespace ngt { public: using WrapperType = edm::Wrapper; - Reader(WrapperType const& wrapper) : ReaderBase(&wrapper) {} + explicit Reader(WrapperType const& wrapper) : ReaderBase(&wrapper) {} - ngt::AnyBuffer parameters() const override { - if constexpr (not ngt::HasTrivialCopyProperties) { - // if ngt::MemoryCopyTraits::properties(...) is not declared, do not call it. - return {}; - } else { - // if ngt::MemoryCopyTraits::properties(...) is declared, call it and wrap the result in an ngt::AnyBuffer - return ngt::AnyBuffer(ngt::MemoryCopyTraits::properties(object())); - } - } + ngt::AnyBuffer parameters() const override { return ngt::get_properties(object()); } - std::vector> regions() const override { - static_assert(ngt::HasRegions); - return ngt::MemoryCopyTraits::regions(object()); - } + std::vector> regions() const override { return ngt::get_regions(object()); } private: const T& object() const { @@ -49,4 +40,4 @@ namespace ngt { } // namespace ngt -#endif // TrivialSerialisation_Common_interface_Reader_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_Reader_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h b/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h index f3c871bdbd969..51f6d4dbf384f 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h @@ -1,5 +1,5 @@ -#ifndef TrivialSerialisation_Common_interface_ReaderBase_h -#define TrivialSerialisation_Common_interface_ReaderBase_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_ReaderBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_ReaderBase_h #include #include @@ -10,6 +10,7 @@ namespace ngt { + // Base class for reading the memory regions and properties of a serialised product. class ReaderBase { public: ReaderBase(const edm::WrapperBase* ptr) : ptr_(ptr) {} @@ -25,4 +26,4 @@ namespace ngt { } // namespace ngt -#endif // TrivialSerialisation_Common_interface_ReaderBase_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_ReaderBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h b/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h index 06049244ec292..ba30ed0e79c34 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h @@ -1,5 +1,5 @@ -#ifndef TrivialSerialisation_Common_interface_Serialiser_h -#define TrivialSerialisation_Common_interface_Serialiser_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_Serialiser_h +#define HeterogeneousCore_TrivialSerialisation_interface_Serialiser_h #include @@ -13,6 +13,7 @@ namespace ngt { + // Concrete Serialiser for host products. T is the bare product type (not wrapped). template class Serialiser : public SerialiserBase { public: @@ -26,4 +27,4 @@ namespace ngt { } // namespace ngt -#endif // TrivialSerialisation_Common_interface_Serialiser_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_Serialiser_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h index 35fb99273e8a5..90db52e6a3293 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h @@ -1,5 +1,5 @@ -#ifndef TrivialSerialisation_Common_interface_SerialiserBase_h -#define TrivialSerialisation_Common_interface_SerialiserBase_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_SerialiserBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_SerialiserBase_h #include @@ -8,6 +8,8 @@ #include "HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h" namespace ngt { + + // Abstract factory interface for creating Readers and Writers for host products. class SerialiserBase { public: virtual std::unique_ptr writer() = 0; @@ -17,4 +19,4 @@ namespace ngt { }; } // namespace ngt -#endif // TrivialSerialisation_Common_interface_SerialiserBase_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_SerialiserBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h index e927bfe643c94..f675c5dbcb173 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h @@ -1,16 +1,23 @@ -#ifndef TrivialSerialisation_src_SerialiserFactory_h -#define TrivialSerialisation_src_SerialiserFactory_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_SerialiserFactory_h +#define HeterogeneousCore_TrivialSerialisation_interface_SerialiserFactory_h -#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h" -#include "HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h" #include "FWCore/PluginManager/interface/PluginFactory.h" +#include "FWCore/Utilities/interface/stringize.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h" namespace ngt { + // Plugin factory for host-product Serialisers, keyed by typeid name. using SerialiserFactory = edmplugin::PluginFactory; -} +} // namespace ngt -// Helper macro to define Serialiser plugins -#define DEFINE_TRIVIAL_SERIALISER_PLUGIN(TYPE) \ - DEFINE_EDM_PLUGIN(ngt::SerialiserFactory, ngt::Serialiser, typeid(TYPE).name()) +// Register a Serialiser plugin for a host product type. +// +// The plugin is registered under both the mangled typeid name and +// EDM_STRINGIZE(TYPE). EDM_STRINGIZE(TYPE) is more human-readable, and thus +// more suitable for Python configuration files. +#define DEFINE_TRIVIAL_SERIALISER_PLUGIN(TYPE) \ + DEFINE_EDM_PLUGIN(ngt::SerialiserFactory, ngt::Serialiser, typeid(TYPE).name()); \ + DEFINE_EDM_PLUGIN2(ngt::SerialiserFactory, ngt::Serialiser, EDM_STRINGIZE(TYPE)) -#endif // TrivialSerialisation_src_SerialiserFactory_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_SerialiserFactory_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Writer.h b/HeterogeneousCore/TrivialSerialisation/interface/Writer.h index d4bfc4b7cad22..505db40bf6cdd 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/Writer.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/Writer.h @@ -1,5 +1,5 @@ -#ifndef TrivialSerialisation_Common_interface_Writer_h -#define TrivialSerialisation_Common_interface_Writer_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_Writer_h +#define HeterogeneousCore_TrivialSerialisation_interface_Writer_h #include #include @@ -8,10 +8,12 @@ #include "DataFormats/Common/interface/Wrapper.h" #include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Common.h" #include "HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h" namespace ngt { + // Writer for host products: creates a Wrapper and exposes its memory regions for writing. template class Writer : public WriterBase { static_assert(ngt::HasMemoryCopyTraits, "No specialization of MemoryCopyTraits found for type T"); @@ -28,19 +30,12 @@ namespace ngt { } } - ngt::AnyBuffer uninitialized_parameters() const override { - if constexpr (not ngt::HasTrivialCopyProperties) { - // if ngt::MemoryCopyTraits::properties(...) is not declared, do not call it. - return {}; - } else { - // if ngt::MemoryCopyTraits::properties(...) is declared, call it and wrap the result in an ngt::AnyBuffer - return ngt::AnyBuffer(ngt::MemoryCopyTraits::properties(object())); - } - } + ngt::AnyBuffer uninitialized_parameters() const override { return ngt::get_properties(object()); } void initialize(ngt::AnyBuffer const& args) override { if constexpr (not ngt::HasValidInitialize) { - // If there is no valid initialize(), this shouldn't be present. + // If MemoryCopyTraits has no valid initialize() then the object must be default-constructible. + // Check that Properties are not present, as they are not needed and cannot be used. static_assert(not ngt::HasTrivialCopyProperties); } else if constexpr (not ngt::HasTrivialCopyProperties) { // If T has no TrivialCopyProperties, call initialize() without any additional arguments. @@ -51,16 +46,9 @@ namespace ngt { } } - std::vector> regions() override { - static_assert(ngt::HasRegions); - return ngt::MemoryCopyTraits::regions(object()); - } + std::vector> regions() override { return ngt::get_regions(object()); } - void finalize() override { - if constexpr (ngt::HasTrivialCopyFinalize) { - ngt::MemoryCopyTraits::finalize(object()); - } - } + void finalize() override { ngt::do_finalize(object()); } private: const T& object() const { return static_cast(ptr_.get())->bareProduct(); } @@ -70,4 +58,4 @@ namespace ngt { } // namespace ngt -#endif // TrivialSerialisation_Common_interface_Writer_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_Writer_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h b/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h index 6fc1f5f12c865..44ec6f2bc9045 100644 --- a/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h +++ b/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h @@ -1,5 +1,5 @@ -#ifndef TrivialSerialisation_Common_interface_WriterBase_h -#define TrivialSerialisation_Common_interface_WriterBase_h +#ifndef HeterogeneousCore_TrivialSerialisation_interface_WriterBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_WriterBase_h #include #include @@ -10,6 +10,7 @@ namespace ngt { + // Base class for creating a new product and exposing its memory regions for writing. class WriterBase { public: WriterBase() = default; @@ -28,4 +29,4 @@ namespace ngt { } // namespace ngt -#endif // TrivialSerialisation_Common_interface_WriterBase_h +#endif // HeterogeneousCore_TrivialSerialisation_interface_WriterBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Reader.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Reader.h new file mode 100644 index 0000000000000..6c5cf5e34a22e --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Reader.h @@ -0,0 +1,41 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_Reader_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_Reader_h + +#include +#include +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Common.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Reader for device products. + template + class Reader : public ReaderBase { + static_assert(::ngt::HasMemoryCopyTraits, "No specialization of MemoryCopyTraits found for type T"); + + public: + using WrapperType = edm::Wrapper; + + // Constructor from Wrapper + explicit Reader(WrapperType const& wrapper) : productPtr_(&wrapper.bareProduct()) {} + + // Constructor from T const& directly + explicit Reader(T const& product) : productPtr_(&product) {} + + ::ngt::AnyBuffer parameters() const override { return ::ngt::get_properties(*productPtr_); } + + std::vector> regions() const override { return ::ngt::get_regions(*productPtr_); } + + private: + T const* productPtr_; + }; + +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_Reader_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h new file mode 100644 index 0000000000000..ed4da80c1c637 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h @@ -0,0 +1,26 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_ReaderBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_ReaderBase_h + +#include +#include +#include + +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Base class for reading the memory regions and properties of a serialised device product. + class ReaderBase { + public: + ReaderBase() = default; + + virtual ::ngt::AnyBuffer parameters() const = 0; + virtual std::vector> regions() const = 0; + + virtual ~ReaderBase() = default; + }; + +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_ReaderBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Serialiser.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Serialiser.h new file mode 100644 index 0000000000000..78d7829fde6a3 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Serialiser.h @@ -0,0 +1,41 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_Serialiser_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_Serialiser_h + +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/AlpakaCommon/interface/alpaka/DeviceProductType.h" +#include "DataFormats/AlpakaCommon/interface/alpaka/EDMetadata.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/Reader.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/Writer.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Concrete Serialiser for device products. + // T is the inner product type (e.g. PortableDeviceCollection<...>). + template + class Serialiser : public SerialiserBase { + public: + using WrapperType = edm::Wrapper>; + + std::unique_ptr writer() override { return std::make_unique>(); } + + std::unique_ptr reader(edm::WrapperBase const& wrapper, + EDMetadata& metadata, + bool tryReuseQueue) override { + WrapperType const& w = dynamic_cast(wrapper); + if constexpr (detail::useProductDirectly) { + return std::make_unique>(w.bareProduct()); + } else { + return std::make_unique>( + w.bareProduct().template getSynchronized(metadata, tryReuseQueue)); + } + } + }; + +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_Serialiser_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserBase.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserBase.h new file mode 100644 index 0000000000000..3afd28e9e350d --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserBase.h @@ -0,0 +1,26 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_SerialiserBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_SerialiserBase_h + +#include + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/AlpakaCommon/interface/alpaka/EDMetadata.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/WriterBase.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Abstract interface for creating Readers and Writers for device products. + class SerialiserBase { + public: + virtual std::unique_ptr writer() = 0; + virtual std::unique_ptr reader(const edm::WrapperBase& wrapper, + EDMetadata& metadata, + bool tryReuseQueue) = 0; + + virtual ~SerialiserBase() = default; + }; +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_SerialiserBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h new file mode 100644 index 0000000000000..7bb5c21d91bfd --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h @@ -0,0 +1,30 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_SerialiserFactoryDevice_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_SerialiserFactoryDevice_h + +#include "DataFormats/Common/interface/DeviceProduct.h" +#include "FWCore/PluginManager/interface/PluginFactory.h" +#include "FWCore/Utilities/interface/stringize.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/Serialiser.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserBase.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + using SerialiserFactoryDevice = edmplugin::PluginFactory; +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +// Helper macro to define Serialiser plugins. +// +// TYPE is the inner product type (e.g. PortableDeviceCollection<...>), not +// wrapped in DeviceProduct, and without ALPAKA_ACCELERATOR_NAMESPACE:: attached +// to it (it is attached here). The plugin is registered under both the mangled +// typeid name and EDM_STRINGIZE(TYPE). EDM_STRINGIZE(TYPE) is more +// human-readable, and thus more suitable for Python configuration files. +#define DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(TYPE) \ + DEFINE_EDM_PLUGIN(ALPAKA_ACCELERATOR_NAMESPACE::ngt::SerialiserFactoryDevice, \ + ALPAKA_ACCELERATOR_NAMESPACE::ngt::Serialiser, \ + typeid(edm::DeviceProduct).name()); \ + DEFINE_EDM_PLUGIN2(ALPAKA_ACCELERATOR_NAMESPACE::ngt::SerialiserFactoryDevice, \ + ALPAKA_ACCELERATOR_NAMESPACE::ngt::Serialiser, \ + EDM_STRINGIZE(TYPE)) + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_SerialiserFactoryDevice_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Writer.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Writer.h new file mode 100644 index 0000000000000..7358ebea1f8dd --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/Writer.h @@ -0,0 +1,84 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_Writer_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_Writer_h + +#include +#include +#include +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "DataFormats/AlpakaCommon/interface/alpaka/DeviceProductType.h" +#include "DataFormats/AlpakaCommon/interface/alpaka/EDMetadata.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Common.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/WriterBase.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Writer for device products. + // T is the inner product type (e.g. PortableDeviceCollection<...>) + template + class Writer : public WriterBase { + static_assert(::ngt::HasMemoryCopyTraits, "No specialization of MemoryCopyTraits found for type T"); + + public: + using WrapperType = edm::Wrapper>; + + Writer() : WriterBase(), data_{construct_()} {} + + ::ngt::AnyBuffer uninitialized_parameters() const override { return ::ngt::get_properties(object()); } + + void initialize(Queue& queue, ::ngt::AnyBuffer const& args) override { + using Traits = ::ngt::MemoryCopyTraits; + if constexpr (::ngt::HasTrivialCopyProperties) { + // properties are required to initialize an object of type T + using Props = ::ngt::TrivialCopyProperties; + static_assert( + requires(Queue& q, T& o, Props p) { Traits::initialize(q, o, p); }, + "MemoryCopyTraits has properties but no initialize(Queue&, T&, Properties const&)"); + Traits::initialize(queue, object(), args.cast_to()); + } else { + // No properties are required to initialize an object of type T + static_assert( + requires(Queue& q, T& o) { Traits::initialize(q, o); }, + "MemoryCopyTraits has no properties but no initialize(Queue&, T&)"); + Traits::initialize(queue, object()); + } + } + + std::vector> regions() override { return ::ngt::get_regions(object()); } + + void finalize() override { ::ngt::do_finalize(object()); } + + std::unique_ptr get(std::shared_ptr metadata) override { + if constexpr (detail::useProductDirectly) { + return std::make_unique(edm::WrapperBase::Emplace{}, std::move(data_)); + } else { + // metadata is needed to construct edm::DeviceProduct + return std::make_unique(edm::WrapperBase::Emplace{}, std::move(metadata), std::move(data_)); + } + } + + // Extract the product T directly by move. + T takeProduct() { return std::move(data_); } + + private: + static T construct_() { + if constexpr (requires { T(); }) { + return T{}; + } else { + return T{edm::kUninitialized}; + } + } + + T const& object() const { return data_; } + T& object() { return data_; } + + T data_; + }; + +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_Writer_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/alpaka/WriterBase.h b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/WriterBase.h new file mode 100644 index 0000000000000..ef516a2e38258 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/WriterBase.h @@ -0,0 +1,32 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_WriterBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_WriterBase_h + +#include +#include +#include +#include + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/AlpakaCommon/interface/alpaka/EDMetadata.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Base class for creating a new device product and exposing its memory regions for writing + class WriterBase { + public: + WriterBase() = default; + virtual ~WriterBase() = default; + + virtual void initialize(Queue& queue, ::ngt::AnyBuffer const& args) = 0; + virtual ::ngt::AnyBuffer uninitialized_parameters() const = 0; + virtual std::vector> regions() = 0; + virtual void finalize() = 0; + + virtual std::unique_ptr get(std::shared_ptr metadata) = 0; + }; + +} // namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_alpaka_WriterBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/src/alpaka/SerialiserFactoryDevice.cc b/HeterogeneousCore/TrivialSerialisation/src/alpaka/SerialiserFactoryDevice.cc new file mode 100644 index 0000000000000..9862bc16991c9 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/src/alpaka/SerialiserFactoryDevice.cc @@ -0,0 +1,4 @@ +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +EDM_REGISTER_PLUGINFACTORY(ALPAKA_ACCELERATOR_NAMESPACE::ngt::SerialiserFactoryDevice, + "SerialiserFactoryDevice " EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE)); diff --git a/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml b/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml index fb89e209b4425..ca193d8113369 100644 --- a/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml +++ b/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml @@ -8,3 +8,18 @@ + + + + + + + + + + + + + + + diff --git a/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_main.cc b/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_main.cc new file mode 100644 index 0000000000000..2c344d5b8aab1 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_portableCollectionsSerialiserPluginFactory.dev.cc b/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_portableCollectionsSerialiserPluginFactory.dev.cc new file mode 100644 index 0000000000000..76cc05370b41d --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_portableCollectionsSerialiserPluginFactory.dev.cc @@ -0,0 +1,342 @@ +#include + +#include + +#include + +#include "DataFormats/AlpakaCommon/interface/alpaka/EDMetadata.h" +#include "DataFormats/Common/interface/DeviceProduct.h" +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/Portable/interface/PortableCollection.h" +#include "DataFormats/Portable/interface/PortableHostCollection.h" +#include "DataFormats/Portable/interface/PortableHostObject.h" +#include "DataFormats/PortableTestObjects/interface/TestSoA.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceObject.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "FWCore/PluginManager/interface/PluginManager.h" +#include "FWCore/PluginManager/interface/standard.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +using namespace ALPAKA_ACCELERATOR_NAMESPACE; +namespace alpaka_portabletest = ALPAKA_ACCELERATOR_NAMESPACE::portabletest; +namespace alpaka_ngt = ALPAKA_ACCELERATOR_NAMESPACE::ngt; + +/* + * This test demonstrates a way to use the TrivialSerialisation mechanism to copy + * portable types (PortableCollections, PortableObjects, and multi-block + * collections) of which only the runtime type information is known. + * + * For each type, the test performs the following steps: + * - Create a portable type and initialize it with some data, wrapped in + * DeviceProductType. + * - Wrap it in edm::Wrapper and cast to WrapperBase* to hide the concrete type. + * - Use the SerialiserFactory plugin to obtain a type-erased reader and writer. + * - Copy the memory regions from the reader into the writer. + * - Retrieve the cloned product and verify its contents match the original. + * + * Tested types: single-block PortableCollection, PortableObject, 2-block and + * 3-block PortableCollections. + */ + +namespace test { + + // helper to create a dummy EDMetadata object for the purpose of these tests. + std::shared_ptr makeMetadata(Device const& device) { + if constexpr (detail::useProductDirectly) { + return std::make_shared(std::make_shared(device)); + } else { + return std::make_shared(std::make_shared(device), std::make_shared(device)); + } + } + + // helper to wrap a product in edm::Wrapper>. + template + edm::Wrapper> wrapProduct(T&& product, std::shared_ptr metadata) { + if constexpr (detail::useProductDirectly) { + return edm::Wrapper>(edm::WrapperBase::Emplace{}, std::move(product)); + } else { + // asynchronous backends, edm::DeviceProduct's constructor takes a + // metadata object + return edm::Wrapper>( + edm::WrapperBase::Emplace{}, std::move(metadata), std::move(product)); + } + } + + // helper to unwrap a product of type "T" from an + // edm::Wrapper> + template + auto const& extractProductFromEdmWrapper(WrapperType const& wrapper, EDMetadata& metadata) { + if constexpr (detail::useProductDirectly) { + // synchronous backends, WrapperType is edm::Wrapper + return wrapper.bareProduct(); + } else { + // asynchronous backends, WrapperType is edm::Wrapper> + return wrapper.bareProduct().template getSynchronized(metadata, true); + } + } +} // namespace test + +TEST_CASE("Test MemoryCopyTraits", "[MemoryCopyTraits]") { + if (not edmplugin::PluginManager::isAvailable()) + edmplugin::PluginManager::configure(edmplugin::standard::config()); + + auto const& devices = cms::alpakatools::devices(); + if (devices.empty()) { + FAIL("No devices available for the " EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) " backend"); + } + + SECTION("PortableCollection>") { + using PortableCollectionType = ::PortableCollection>; + using DeviceProductType = detail::DeviceProductType; + using PortableHostCollectionType = PortableHostCollection>; + const int size = 10; + + for (auto const& device : devices) { + std::cout << "Running on " << alpaka::getName(device) << std::endl; + Queue queue(device); + + // Create a PortableHostCollection, to be used as a reference for the checks + PortableHostCollectionType refHostCollection(queue, size); + + // Initialize it with some data + refHostCollection.view().r() = 3.14; + for (int i = 0; i < size; i++) { + refHostCollection.view()[i].x() = i * size + 1; + refHostCollection.view()[i].y() = i * size + 2; + refHostCollection.view()[i].z() = i * size + 3; + refHostCollection.view()[i].id() = i; + refHostCollection.view()[i].flags() = std::array{ + {static_cast(i), static_cast(i + 1), static_cast(i + 2), static_cast(i + 3)}}; + refHostCollection.view()[i].m = Eigen::Matrix::Identity() * i; + } + + // Function to validate a PortableCollection whose contents should be + // identical to the reference Host collection above + auto checkPortableCollection = [&queue, &refHostCollection](PortableCollectionType const& col) { + // Since PortableCollection might be a device collection, copy first its + // data into an auxiliary PortableHostCollection + PortableHostCollectionType auxPortableHostCollection(queue, size); + alpaka::memcpy(queue, auxPortableHostCollection.buffer(), col.buffer()); + alpaka::wait(queue); + + REQUIRE(auxPortableHostCollection.const_view().r() == refHostCollection.const_view().r()); + for (int i = 0; i < size; i++) { + REQUIRE(auxPortableHostCollection.const_view()[i].x() == refHostCollection.const_view()[i].x()); + REQUIRE(auxPortableHostCollection.const_view()[i].y() == refHostCollection.const_view()[i].y()); + REQUIRE(auxPortableHostCollection.const_view()[i].z() == refHostCollection.const_view()[i].z()); + REQUIRE(auxPortableHostCollection.const_view()[i].id() == refHostCollection.const_view()[i].id()); + REQUIRE(auxPortableHostCollection.const_view()[i].flags() == refHostCollection.const_view()[i].flags()); + REQUIRE(auxPortableHostCollection.const_view()[i].m() == refHostCollection.const_view()[i].m()); + } + }; + + // Create a PortableCollection + PortableCollectionType sourceCollection(queue, size); + + // Copy the reference host collection into this newly created one + alpaka::memcpy(queue, sourceCollection.buffer(), refHostCollection.buffer()); + + // Check that the collection has been successfully initialized. + checkPortableCollection(sourceCollection); + + // Wrap it in edm::Wrapper + auto metadata = test::makeMetadata(device); + auto wrapper_original = test::wrapProduct(std::move(sourceCollection), metadata); + + // Now cast the wrapper to edm::WrapperBase, hiding the underlying collection type + edm::WrapperBase const* wb_original = static_cast(&wrapper_original); + + // Check that a specialization of MemoryCopyTraits exists for this type + static_assert(::ngt::HasMemoryCopyTraits); + + // Get the Serialiser plugin for this type + std::string typeName = typeid(edm::DeviceProduct).name(); + std::unique_ptr serialiserSource{ + alpaka_ngt::SerialiserFactoryDevice::get()->create(typeName)}; + + // Create a "reader" and a "writer", then clone via memory regions + auto reader = serialiserSource->reader(*wb_original, *metadata, true); + auto writer = serialiserSource->writer(); + + writer->initialize(queue, reader->parameters()); + + // Get memory regions + auto targets = writer->regions(); + auto sources = reader->regions(); + + // Check that reader and writer have the same number of memory regions. In + // the case of a PortableCollection this should be equal to one. + REQUIRE(sources.size() == targets.size()); + + // copy each region from the source to the clone + for (size_t i = 0; i < sources.size(); ++i) { + REQUIRE(sources[i].data() != nullptr); + REQUIRE(targets[i].data() != nullptr); + REQUIRE(targets[i].size_bytes() == sources[i].size_bytes()); + + auto src_view = alpaka::createView(device, sources[i].data(), sources[i].size_bytes()); + auto trg_view = alpaka::createView(device, targets[i].data(), targets[i].size_bytes()); + + alpaka::memcpy(queue, trg_view, src_view); + } + + // Retrieve the cloned product back from its type-erased form, and verify its contents + std::unique_ptr clonebase = writer->get(metadata); + edm::Wrapper* cloneptr = dynamic_cast*>(clonebase.get()); + checkPortableCollection(test::extractProductFromEdmWrapper(*cloneptr, *metadata)); + } + } + + SECTION("DeviceProduct") { + using DeviceObjectType = alpaka_portabletest::TestDeviceObject; + using DeviceProductType = detail::DeviceProductType; + const alpaka_portabletest::TestStruct testData{5.0, 12.0, 13.0, 42}; + + for (auto const& device : devices) { + std::cout << "Running DeviceObject test on " << alpaka::getName(device) << std::endl; + Queue queue(device); + + // Create a reference host object with known data + PortableHostObject hostSource(queue); + hostSource->x = testData.x; + hostSource->y = testData.y; + hostSource->z = testData.z; + hostSource->id = testData.id; + + // Create a DeviceObject, copy the reference host object into it, + DeviceObjectType sourceObject(queue); + alpaka::memcpy(queue, sourceObject.buffer(), hostSource.buffer()); + + // Wrap it in the WrapperBase (type-erased) + auto metadata = test::makeMetadata(device); + auto wrapper = test::wrapProduct(std::move(sourceObject), metadata); + edm::WrapperBase const* wb = static_cast(&wrapper); + + // Get the serialiser plugin + std::string typeName = typeid(edm::DeviceProduct).name(); + std::unique_ptr serialiser{ + alpaka_ngt::SerialiserFactoryDevice::get()->create(typeName)}; + REQUIRE(serialiser); + + // Read and write + auto reader = serialiser->reader(*wb, *metadata, true); + auto writer = serialiser->writer(); + + writer->initialize(queue, reader->parameters()); + + auto targets = writer->regions(); + auto sources = reader->regions(); + REQUIRE(sources.size() == targets.size()); + + for (size_t i = 0; i < sources.size(); ++i) { + REQUIRE(sources[i].size_bytes() == targets[i].size_bytes()); + auto src_view = alpaka::createView(device, sources[i].data(), sources[i].size_bytes()); + auto trg_view = alpaka::createView(device, targets[i].data(), targets[i].size_bytes()); + alpaka::memcpy(queue, trg_view, src_view); + } + + // Get the collection back from its wrapped form + std::unique_ptr clonebase = writer->get(metadata); + auto* cloneptr = dynamic_cast*>(clonebase.get()); + REQUIRE(cloneptr); + + // Copy back to host to check + PortableHostObject hostClone(queue); + alpaka::memcpy(queue, hostClone.buffer(), test::extractProductFromEdmWrapper(*cloneptr, *metadata).buffer()); + alpaka::wait(queue); + + REQUIRE(hostClone->x == testData.x); + REQUIRE(hostClone->y == testData.y); + REQUIRE(hostClone->z == testData.z); + REQUIRE(hostClone->id == testData.id); + } + } + + SECTION("DeviceProduct") { + using DeviceCollection2Type = alpaka_portabletest::TestDeviceCollection2; + using DeviceProductType = detail::DeviceProductType; + using HostCollection2Type = PortableHostCollection; + const int size1 = 7; + const int size2 = 11; + + for (auto const& device : devices) { + std::cout << "Running DeviceCollection2 test on " << alpaka::getName(device) << std::endl; + Queue queue(device); + + // Create reference data on host + HostCollection2Type refHost(queue, size1, size2); + refHost.view().first().r() = 1.11; + refHost.view().second().r2() = 2.22; + for (int i = 0; i < size1; i++) { + refHost.view().first()[i].x() = i * 10.0; + refHost.view().first()[i].y() = 0.0; + refHost.view().first()[i].z() = 0.0; + refHost.view().first()[i].id() = i; + refHost.view().first()[i].flags() = std::array{{0, 0, 0, 0}}; + refHost.view().first()[i].m = Eigen::Matrix::Zero(); + } + for (int i = 0; i < size2; i++) { + refHost.view().second()[i].x2() = i * 20.0; + refHost.view().second()[i].y2() = 0.0; + refHost.view().second()[i].z2() = 0.0; + refHost.view().second()[i].id2() = i + 100; + refHost.view().second()[i].m2 = Eigen::Matrix::Zero(); + } + + // Create DeviceCollection2, fill it, and wrap in DeviceProduct + DeviceCollection2Type sourceCollection(queue, size1, size2); + alpaka::memcpy(queue, sourceCollection.buffer(), refHost.buffer()); + + // Wrap it and cast to WrapperBase + auto metadata = test::makeMetadata(device); + auto wrapper = test::wrapProduct(std::move(sourceCollection), metadata); + edm::WrapperBase const* wb = static_cast(&wrapper); + + // Get the serialiser plugin + std::string typeName = typeid(edm::DeviceProduct).name(); + std::unique_ptr serialiser{ + alpaka_ngt::SerialiserFactoryDevice::get()->create(typeName)}; + REQUIRE(serialiser); + + // Read and write + auto reader = serialiser->reader(*wb, *metadata, true); + auto writer = serialiser->writer(); + + writer->initialize(queue, reader->parameters()); + + auto targets = writer->regions(); + auto sources_r = reader->regions(); + REQUIRE(sources_r.size() == targets.size()); + + for (size_t i = 0; i < sources_r.size(); ++i) { + REQUIRE(sources_r[i].size_bytes() == targets[i].size_bytes()); + auto src_view = alpaka::createView(device, sources_r[i].data(), sources_r[i].size_bytes()); + auto trg_view = alpaka::createView(device, targets[i].data(), targets[i].size_bytes()); + alpaka::memcpy(queue, trg_view, src_view); + } + + // Get the clone + std::unique_ptr clonebase = writer->get(metadata); + auto* cloneptr = dynamic_cast*>(clonebase.get()); + REQUIRE(cloneptr); + + // Copy back to host and check + HostCollection2Type verifyHost(queue, size1, size2); + alpaka::memcpy(queue, verifyHost.buffer(), test::extractProductFromEdmWrapper(*cloneptr, *metadata).buffer()); + alpaka::wait(queue); + + REQUIRE(verifyHost.const_view().first().r() == 1.11); + REQUIRE(verifyHost.const_view().second().r2() == 2.22); + for (int i = 0; i < size1; i++) { + REQUIRE(verifyHost.const_view().first()[i].x() == i * 10.0); + REQUIRE(verifyHost.const_view().first()[i].id() == i); + } + for (int i = 0; i < size2; i++) { + REQUIRE(verifyHost.const_view().second()[i].x2() == i * 20.0); + REQUIRE(verifyHost.const_view().second()[i].id2() == i + 100); + } + } + } +} From 01954919cdec1a946fd333ee6375098d1681f8d3 Mon Sep 17 00:00:00 2001 From: Andrea Bocci Date: Mon, 30 Mar 2026 18:59:22 +0200 Subject: [PATCH 2/5] Implement serialisation plugins for device products Co-authored-by: Mario Gonzalez --- DataFormats/BeamSpot/plugins/BuildFile.xml | 7 +++++++ .../BeamSpot/plugins/alpaka/TrivialSerialisation.cc | 4 ++++ DataFormats/EcalDigi/plugins/BuildFile.xml | 7 +++++++ .../EcalDigi/plugins/alpaka/TrivialSerialisation.cc | 6 ++++++ DataFormats/EcalRecHit/plugins/BuildFile.xml | 6 ++++++ .../plugins/alpaka/TrivialSerialisation.cc | 6 ++++++ DataFormats/HGCalDigi/plugins/BuildFile.xml | 7 +++++++ .../plugins/alpaka/TrivialSerialisation.cc | 8 ++++++++ DataFormats/HGCalReco/plugins/BuildFile.xml | 7 +++++++ .../plugins/alpaka/TrivialSerialisation.cc | 8 ++++++++ DataFormats/HcalDigi/plugins/BuildFile.xml | 7 +++++++ .../HcalDigi/plugins/alpaka/TrivialSerialisation.cc | 5 +++++ DataFormats/HcalRecHit/plugins/BuildFile.xml | 7 +++++++ .../plugins/alpaka/TrivialSerialisation.cc | 4 ++++ DataFormats/ParticleFlowReco/plugins/BuildFile.xml | 7 +++++++ .../plugins/alpaka/TrivialSerialisation.cc | 13 +++++++++++++ DataFormats/TrackSoA/plugins/BuildFile.xml | 6 ++++++ .../TrackSoA/plugins/alpaka/TrivialSerialisation.cc | 4 ++++ DataFormats/VertexSoA/plugins/BuildFile.xml | 7 +++++++ .../plugins/alpaka/TrivialSerialisation.cc | 4 ++++ 20 files changed, 130 insertions(+) create mode 100644 DataFormats/BeamSpot/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/HGCalDigi/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/HGCalReco/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/HcalDigi/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc create mode 100644 DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc diff --git a/DataFormats/BeamSpot/plugins/BuildFile.xml b/DataFormats/BeamSpot/plugins/BuildFile.xml index fb0d04138c776..e44b8170bd266 100644 --- a/DataFormats/BeamSpot/plugins/BuildFile.xml +++ b/DataFormats/BeamSpot/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/BeamSpot/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/BeamSpot/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..c72d53adc73ed --- /dev/null +++ b/DataFormats/BeamSpot/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/BeamSpot/interface/alpaka/BeamSpotDevice.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(BeamSpotDevice); diff --git a/DataFormats/EcalDigi/plugins/BuildFile.xml b/DataFormats/EcalDigi/plugins/BuildFile.xml index 500770828ac90..5435a558786a9 100644 --- a/DataFormats/EcalDigi/plugins/BuildFile.xml +++ b/DataFormats/EcalDigi/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..198b397cb647a --- /dev/null +++ b/DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,6 @@ +#include "DataFormats/EcalDigi/interface/alpaka/EcalDigiDeviceCollection.h" +#include "DataFormats/EcalDigi/interface/alpaka/EcalDigiPhase2DeviceCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(EcalDigiDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(EcalDigiPhase2DeviceCollection); diff --git a/DataFormats/EcalRecHit/plugins/BuildFile.xml b/DataFormats/EcalRecHit/plugins/BuildFile.xml index ee7c656ef4f71..c4e18d6203b84 100644 --- a/DataFormats/EcalRecHit/plugins/BuildFile.xml +++ b/DataFormats/EcalRecHit/plugins/BuildFile.xml @@ -3,3 +3,9 @@ + + + + + + diff --git a/DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..b716a3b99a2ea --- /dev/null +++ b/DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,6 @@ +#include "DataFormats/EcalRecHit/interface/alpaka/EcalRecHitDeviceCollection.h" +#include "DataFormats/EcalRecHit/interface/alpaka/EcalUncalibratedRecHitDeviceCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(EcalRecHitDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(EcalUncalibratedRecHitDeviceCollection); diff --git a/DataFormats/HGCalDigi/plugins/BuildFile.xml b/DataFormats/HGCalDigi/plugins/BuildFile.xml index f22493a26579e..e78b43afc1749 100644 --- a/DataFormats/HGCalDigi/plugins/BuildFile.xml +++ b/DataFormats/HGCalDigi/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/HGCalDigi/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/HGCalDigi/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..fbc2e7d8d8c1c --- /dev/null +++ b/DataFormats/HGCalDigi/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,8 @@ +#include "DataFormats/HGCalDigi/interface/alpaka/HGCalDigiDevice.h" +#include "DataFormats/HGCalDigi/interface/alpaka/HGCalECONDPacketInfoDevice.h" +#include "DataFormats/HGCalDigi/interface/alpaka/HGCalFEDPacketInfoDevice.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(hgcaldigi::HGCalDigiDevice); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(hgcaldigi::HGCalECONDPacketInfoDevice); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(hgcaldigi::HGCalFEDPacketInfoDevice); diff --git a/DataFormats/HGCalReco/plugins/BuildFile.xml b/DataFormats/HGCalReco/plugins/BuildFile.xml index 1171aee28adb6..2e4bb422d2949 100644 --- a/DataFormats/HGCalReco/plugins/BuildFile.xml +++ b/DataFormats/HGCalReco/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/HGCalReco/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/HGCalReco/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..bb0b22d0498d2 --- /dev/null +++ b/DataFormats/HGCalReco/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,8 @@ +#include "DataFormats/HGCalReco/interface/alpaka/HGCalSoAClustersDeviceCollection.h" +#include "DataFormats/HGCalReco/interface/alpaka/HGCalSoARecHitsExtraDeviceCollection.h" +#include "DataFormats/HGCalReco/interface/alpaka/HGCalSoARecHitsDeviceCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(HGCalSoAClustersDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(HGCalSoARecHitsExtraDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(HGCalSoARecHitsDeviceCollection); diff --git a/DataFormats/HcalDigi/plugins/BuildFile.xml b/DataFormats/HcalDigi/plugins/BuildFile.xml index a627d6e7e1a0a..9fe9065002388 100644 --- a/DataFormats/HcalDigi/plugins/BuildFile.xml +++ b/DataFormats/HcalDigi/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/HcalDigi/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/HcalDigi/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..cea62bac1fe9d --- /dev/null +++ b/DataFormats/HcalDigi/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/HcalDigi/interface/alpaka/HcalDigiDeviceCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(hcal::Phase0DigiDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(hcal::Phase1DigiDeviceCollection); diff --git a/DataFormats/HcalRecHit/plugins/BuildFile.xml b/DataFormats/HcalRecHit/plugins/BuildFile.xml index 07725848fdb30..85446ad991e7f 100644 --- a/DataFormats/HcalRecHit/plugins/BuildFile.xml +++ b/DataFormats/HcalRecHit/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..76d4f0a4b69ed --- /dev/null +++ b/DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/HcalRecHit/interface/alpaka/HcalRecHitDeviceCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(hcal::RecHitDeviceCollection); diff --git a/DataFormats/ParticleFlowReco/plugins/BuildFile.xml b/DataFormats/ParticleFlowReco/plugins/BuildFile.xml index e20859b77da70..0d7521b2511fa 100644 --- a/DataFormats/ParticleFlowReco/plugins/BuildFile.xml +++ b/DataFormats/ParticleFlowReco/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..13b2aa9d7aaa8 --- /dev/null +++ b/DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,13 @@ +// Include the Eigen core library before including the SoA definitions +#include + +#include "DataFormats/ParticleFlowReco/interface/alpaka/CaloRecHitDeviceCollection.h" +#include "DataFormats/ParticleFlowReco/interface/alpaka/PFClusterDeviceCollection.h" +#include "DataFormats/ParticleFlowReco/interface/alpaka/PFRecHitFractionDeviceCollection.h" +#include "DataFormats/ParticleFlowReco/interface/alpaka/PFRecHitDeviceCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::CaloRecHitDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::PFClusterDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::PFRecHitFractionDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::PFRecHitDeviceCollection); diff --git a/DataFormats/TrackSoA/plugins/BuildFile.xml b/DataFormats/TrackSoA/plugins/BuildFile.xml index e2d5a430d6ae4..72968939bb9ef 100644 --- a/DataFormats/TrackSoA/plugins/BuildFile.xml +++ b/DataFormats/TrackSoA/plugins/BuildFile.xml @@ -3,3 +3,9 @@ + + + + + + diff --git a/DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..35ee64a006aaf --- /dev/null +++ b/DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/TrackSoA/interface/alpaka/TracksSoACollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::TracksSoACollection); diff --git a/DataFormats/VertexSoA/plugins/BuildFile.xml b/DataFormats/VertexSoA/plugins/BuildFile.xml index 9967a9fbb7ed1..53ccfa799df99 100644 --- a/DataFormats/VertexSoA/plugins/BuildFile.xml +++ b/DataFormats/VertexSoA/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..d81108e1f7410 --- /dev/null +++ b/DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/VertexSoA/interface/alpaka/ZVertexSoACollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::ZVertexSoACollection); From 4c975a1ae288e81f1691e169ea0155c447ba2468 Mon Sep 17 00:00:00 2001 From: Andrea Bocci Date: Tue, 31 Mar 2026 10:29:51 +0200 Subject: [PATCH 3/5] Update SiPixelDigiSoA products Update the constructors of the host and device products, and implement MemoryCopyTraits and the serialisation plugin for the device products. --- .../interface/SiPixelDigiErrorsDevice.h | 41 +++++++++++++++-- .../interface/SiPixelDigiErrorsHost.h | 34 ++++++++++++-- .../interface/SiPixelDigisDevice.h | 46 +++++++++++++++++-- .../interface/SiPixelDigisHost.h | 31 +++++++++++-- .../alpaka/SiPixelDigiErrorsSoACollection.h | 10 ++-- .../alpaka/SiPixelDigisSoACollection.h | 2 +- .../SiPixelDigiSoA/plugins/BuildFile.xml | 7 +++ .../plugins/alpaka/TrivialSerialisation.cc | 6 +++ .../test/alpaka/DigiErrors_test.cc | 6 +-- .../test/alpaka/DigiErrors_test.dev.cc | 2 +- .../test/alpaka/DigiErrors_test.h | 2 +- .../SiPixelDigiSoA/test/alpaka/Digis_test.cc | 6 +-- .../test/alpaka/Digis_test.dev.cc | 2 +- .../SiPixelDigiSoA/test/alpaka/Digis_test.h | 2 +- .../alpaka/SiPixelPhase2DigiToCluster.cc | 4 +- .../plugins/alpaka/SiPixelRawToCluster.cc | 4 +- .../alpaka/SiPixelRawToClusterKernel.dev.cc | 8 ++-- 17 files changed, 171 insertions(+), 42 deletions(-) create mode 100644 DataFormats/SiPixelDigiSoA/plugins/alpaka/TrivialSerialisation.cc diff --git a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsDevice.h b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsDevice.h index ba08151cec922..5c2a9a5387d2b 100644 --- a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsDevice.h +++ b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsDevice.h @@ -19,11 +19,11 @@ class SiPixelDigiErrorsDevice : public PortableDeviceCollection{edm::kUninitialized} {} template - explicit SiPixelDigiErrorsDevice(size_t maxFedWords, TQueue queue) + requires(alpaka::isQueue) + explicit SiPixelDigiErrorsDevice(TQueue queue, size_t maxFedWords) : PortableDeviceCollection(queue, maxFedWords), maxFedWords_(maxFedWords) {} - // Constructor which specifies the SoA size - explicit SiPixelDigiErrorsDevice(size_t maxFedWords, TDev const& device) + explicit SiPixelDigiErrorsDevice(TDev const& device, size_t maxFedWords) : PortableDeviceCollection(device, maxFedWords) {} auto maxFedWords() const { return maxFedWords_; } @@ -32,4 +32,39 @@ class SiPixelDigiErrorsDevice : public PortableDeviceCollection + struct MemoryCopyTraits> { + using value_type = SiPixelDigiErrorsDevice; + struct Properties { + int maxFedWords; + }; + + static Properties properties(value_type const& object) { return {object.maxFedWords()}; } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& props) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in device global memory. + object = value_type(queue, props.maxFedWords); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_SiPixelDigiSoA_interface_SiPixelDigiErrorsDevice_h diff --git a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h index 228161219f875..718ef99e5e7d7 100644 --- a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h +++ b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h @@ -15,10 +15,25 @@ class SiPixelDigiErrorsHost : public PortableHostCollection { public: - SiPixelDigiErrorsHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} + explicit SiPixelDigiErrorsHost(edm::Uninitialized) + : PortableHostCollection{edm::kUninitialized} {} + // Constructor for code that does not use alpaka explicitly, using the global + // "host" object returned by cms::alpakatools::host(). + // Construct the object in pageable host memory. + explicit SiPixelDigiErrorsHost(size_t maxFedWords) + : PortableHostCollection(cms::alpakatools::host(), maxFedWords), + maxFedWords_(maxFedWords) {} + + // Construct the object in pageable host memory. + explicit SiPixelDigiErrorsHost(alpaka_common::DevHost const& host, size_t maxFedWords) + : PortableHostCollection(host, maxFedWords), maxFedWords_(maxFedWords) {} + + // Construct the object in pinned host memory associated to the given work + // queue, accessible by the queue's device. template - explicit SiPixelDigiErrorsHost(int maxFedWords, TQueue queue) + requires(alpaka::isQueue) + explicit SiPixelDigiErrorsHost(TQueue queue, int maxFedWords) : PortableHostCollection(queue, maxFedWords), maxFedWords_(maxFedWords) {} int maxFedWords() const { return maxFedWords_; } @@ -27,7 +42,7 @@ class SiPixelDigiErrorsHost : public PortableHostCollection @@ -40,8 +55,17 @@ namespace ngt { static Properties properties(value_type const& object) { return {object.maxFedWords()}; } static void initialize(value_type& object, Properties const& props) { - // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory - object = value_type(props.maxFedWords, cms::alpakatools::host()); + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pageable host memory. + object = value_type(cms::alpakatools::host(), props.maxFedWords); + } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& props) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pinned host memory. + object = value_type(queue, props.maxFedWords); } static std::vector> regions(value_type& object) { diff --git a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisDevice.h b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisDevice.h index dde2dd2d20c80..2ae9037c5c06c 100644 --- a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisDevice.h +++ b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisDevice.h @@ -13,14 +13,15 @@ template class SiPixelDigisDevice : public PortableDeviceCollection { public: - SiPixelDigisDevice(edm::Uninitialized) : PortableDeviceCollection{edm::kUninitialized} {} + explicit SiPixelDigisDevice(edm::Uninitialized) + : PortableDeviceCollection{edm::kUninitialized} {} template - explicit SiPixelDigisDevice(size_t maxFedWords, TQueue queue) + requires(alpaka::isQueue) + explicit SiPixelDigisDevice(TQueue queue, size_t maxFedWords) : PortableDeviceCollection(queue, maxFedWords + 1) {} - // Constructor which specifies the SoA size - explicit SiPixelDigisDevice(size_t maxFedWords, TDev const &device) + explicit SiPixelDigisDevice(TDev const& device, size_t maxFedWords) : PortableDeviceCollection(device, maxFedWords + 1) {} void setNModules(uint32_t nModules) { nModules_h = nModules; } @@ -32,4 +33,41 @@ class SiPixelDigisDevice : public PortableDeviceCollection + struct MemoryCopyTraits> { + using value_type = SiPixelDigisDevice; + struct Properties { + uint32_t nDigis; + uint32_t nModules; + }; + + static Properties properties(value_type const& object) { return {object.nDigis(), object.nModules()}; } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& props) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in global device memory. + object = value_type(queue, props.nDigis); + object.setNModules(props.nModules); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_SiPixelDigiSoA_interface_SiPixelDigisDevice_h diff --git a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h index 167ce5be60402..4949634fd4b09 100644 --- a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h +++ b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h @@ -11,10 +11,23 @@ // See: https://github.com/cms-sw/cmssw/pull/40465#discussion_r1067364306 class SiPixelDigisHost : public PortableHostCollection { public: - SiPixelDigisHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} + explicit SiPixelDigisHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} + // Constructor for code that does not use alpaka explicitly, using the global + // "host" object returned by cms::alpakatools::host(). + // Construct the object in pageable host memory. + explicit SiPixelDigisHost(size_t maxFedWords) + : PortableHostCollection(cms::alpakatools::host(), maxFedWords + 1) {} + + // Construct the object in pageable host memory. + explicit SiPixelDigisHost(alpaka_common::DevHost const& host, size_t maxFedWords) + : PortableHostCollection(host, maxFedWords + 1) {} + + // Construct the object in pinned host memory associated to the given work + // queue, accessible by the queue's device. template - explicit SiPixelDigisHost(size_t maxFedWords, TQueue queue) + requires(alpaka::isQueue) + explicit SiPixelDigisHost(TQueue queue, size_t maxFedWords) : PortableHostCollection(queue, maxFedWords + 1) {} void setNModules(uint32_t nModules) { nModules_h = nModules; } @@ -40,8 +53,18 @@ namespace ngt { static Properties properties(value_type const& object) { return {object.nDigis(), object.nModules()}; } static void initialize(value_type& object, Properties const& props) { - // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory - object = value_type(props.nDigis, cms::alpakatools::host()); + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pageable host memory. + object = value_type(cms::alpakatools::host(), props.nDigis); + object.setNModules(props.nModules); + } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& props) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pinned host memory. + object = value_type(queue, props.nDigis); object.setNModules(props.nModules); } diff --git a/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigiErrorsSoACollection.h b/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigiErrorsSoACollection.h index 673a22bd23a1e..626fbba001fe2 100644 --- a/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigiErrorsSoACollection.h +++ b/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigiErrorsSoACollection.h @@ -6,11 +6,10 @@ #include #include "DataFormats/Portable/interface/alpaka/PortableCollection.h" -#include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h" #include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsDevice.h" -#include "HeterogeneousCore/AlpakaInterface/interface/config.h" -#include "HeterogeneousCore/AlpakaInterface/interface/memory.h" +#include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h" #include "HeterogeneousCore/AlpakaInterface/interface/CopyToHost.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" namespace ALPAKA_ACCELERATOR_NAMESPACE { @@ -24,11 +23,8 @@ namespace cms::alpakatools { struct CopyToHost> { template static auto copyAsync(TQueue& queue, SiPixelDigiErrorsDevice const& srcData) { - SiPixelDigiErrorsHost dstData(srcData.maxFedWords(), queue); + SiPixelDigiErrorsHost dstData(queue, srcData.maxFedWords()); alpaka::memcpy(queue, dstData.buffer(), srcData.buffer()); -#ifdef GPU_DEBUG - printf("SiPixelDigiErrorsSoACollection: I'm copying to host.\n"); -#endif return dstData; } }; diff --git a/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigisSoACollection.h b/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigisSoACollection.h index 6bc853a7970df..19d7c3dc9a853 100644 --- a/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigisSoACollection.h +++ b/DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigisSoACollection.h @@ -23,7 +23,7 @@ namespace cms::alpakatools { struct CopyToHost> { template static auto copyAsync(TQueue &queue, SiPixelDigisDevice const &srcData) { - SiPixelDigisHost dstData(srcData.view().metadata().size() - 1, queue); + SiPixelDigisHost dstData(queue, srcData.view().metadata().size() - 1); alpaka::memcpy(queue, dstData.buffer(), srcData.buffer()); dstData.setNModules(srcData.nModules()); return dstData; diff --git a/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml b/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml index 30b51a75552e9..24566f93d9519 100644 --- a/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml +++ b/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/SiPixelDigiSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/SiPixelDigiSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..32fa6373548f4 --- /dev/null +++ b/DataFormats/SiPixelDigiSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,6 @@ +#include "DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigiErrorsSoACollection.h" +#include "DataFormats/SiPixelDigiSoA/interface/alpaka/SiPixelDigisSoACollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(SiPixelDigiErrorsSoACollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(SiPixelDigisSoACollection); diff --git a/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.cc b/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.cc index 7ce777b619aca..50ffeabe38e28 100644 --- a/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.cc +++ b/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.cc @@ -34,12 +34,12 @@ int main() { { // Instantiate tracks on device. PortableDeviceCollection allocates // SoA on device automatically. - SiPixelDigiErrorsSoACollection digiErrors_d(1000, queue); - testDigisSoA::runKernels(digiErrors_d.view(), queue); + SiPixelDigiErrorsSoACollection digiErrors_d(queue, 1000); + testDigisSoA::runKernels(queue, digiErrors_d.view()); // Instantate tracks on host. This is where the data will be // copied to from device. - SiPixelDigiErrorsHost digiErrors_h(digiErrors_d.view().metadata().size(), queue); + SiPixelDigiErrorsHost digiErrors_h(queue, digiErrors_d.view().metadata().size()); alpaka::memcpy(queue, digiErrors_h.buffer(), digiErrors_d.const_buffer()); std::cout << "digiErrors_h.view().metadata().size(): " << digiErrors_h.view().metadata().size() << std::endl; std::cout << "digiErrors_h.view()[100].pixelErrors().rawId: " << digiErrors_h.view()[100].pixelErrors().rawId diff --git a/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.dev.cc b/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.dev.cc index 4ebedcb322558..7efcec13c3787 100644 --- a/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.dev.cc +++ b/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.dev.cc @@ -37,7 +37,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE::testDigisSoA { } }; - void runKernels(SiPixelDigiErrorsSoAView digiErrors_view, Queue& queue) { + void runKernels(Queue& queue, SiPixelDigiErrorsSoAView digiErrors_view) { uint32_t items = 64; uint32_t groups = cms::alpakatools::divide_up_by(digiErrors_view.metadata().size(), items); auto workDiv = cms::alpakatools::make_workdiv(groups, items); diff --git a/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.h b/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.h index 679ee9636b320..0b98ffbdea032 100644 --- a/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.h +++ b/DataFormats/SiPixelDigiSoA/test/alpaka/DigiErrors_test.h @@ -6,7 +6,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE::testDigisSoA { - void runKernels(SiPixelDigiErrorsSoAView digiErrors_view, Queue& queue); + void runKernels(Queue& queue, SiPixelDigiErrorsSoAView digiErrors_view); } // namespace ALPAKA_ACCELERATOR_NAMESPACE::testDigisSoA diff --git a/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.cc b/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.cc index f0914cab8277c..a97b66201f12a 100644 --- a/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.cc +++ b/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.cc @@ -34,12 +34,12 @@ int main() { { // Instantiate tracks on device. PortableDeviceCollection allocates // SoA on device automatically. - SiPixelDigisSoACollection digis_d(1000, queue); - testDigisSoA::runKernels(digis_d.view(), queue); + SiPixelDigisSoACollection digis_d(queue, 1000); + testDigisSoA::runKernels(queue, digis_d.view()); // Instantate tracks on host. This is where the data will be // copied to from device. - SiPixelDigisHost digis_h(digis_d.view().metadata().size(), queue); + SiPixelDigisHost digis_h(queue, digis_d.view().metadata().size()); std::cout << digis_h.view().metadata().size() << std::endl; alpaka::memcpy(queue, digis_h.buffer(), digis_d.const_buffer()); diff --git a/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.dev.cc b/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.dev.cc index 9d783655a3f33..de9b3cfac48a4 100644 --- a/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.dev.cc +++ b/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.dev.cc @@ -34,7 +34,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE::testDigisSoA { } }; - void runKernels(SiPixelDigisSoAView digi_view, Queue& queue) { + void runKernels(Queue& queue, SiPixelDigisSoAView digi_view) { uint32_t items = 64; uint32_t groups = cms::alpakatools::divide_up_by(digi_view.metadata().size(), items); auto workDiv = cms::alpakatools::make_workdiv(groups, items); diff --git a/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.h b/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.h index fbe2ffecc0f79..026a5692bbddb 100644 --- a/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.h +++ b/DataFormats/SiPixelDigiSoA/test/alpaka/Digis_test.h @@ -6,7 +6,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE::testDigisSoA { - void runKernels(SiPixelDigisSoAView digis_view, Queue& queue); + void runKernels(Queue& queue, SiPixelDigisSoAView digis_view); } // namespace ALPAKA_ACCELERATOR_NAMESPACE::testDigisSoA diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc index 84d5e8ecffd53..2dff97f7c7580 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc @@ -139,12 +139,12 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { for (const auto& det : input) { nDigis_ += det.size(); } - digis_d_ = SiPixelDigisSoACollection(nDigis_, iEvent.queue()); + digis_d_ = SiPixelDigisSoACollection(iEvent.queue(), nDigis_); if (nDigis_ == 0) return; - SiPixelDigisHost digis_h(nDigis_, iEvent.queue()); + SiPixelDigisHost digis_h(iEvent.queue(), nDigis_); uint32_t nDigis = 0; for (const auto& det : input) { diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc index 25a53e95f98fb..6afc982c3215a 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc @@ -420,10 +420,10 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { // are no valid pointers to clusters' Collection columns, instantiation // of TrackingRecHits fail. Example: workflow 11604.0 - iEvent.emplace(digiPutToken_, 0, iEvent.queue()); iEvent.emplace(clusterPutToken_, pixelTopology::Phase1::numberOfModules, iEvent.queue()); + iEvent.emplace(digiPutToken_, iEvent.queue(), 0); if (includeErrors_) { - iEvent.emplace(digiErrorPutToken_, 0, iEvent.queue()); + iEvent.emplace(digiErrorPutToken_, iEvent.queue(), 0); iEvent.emplace(fmtErrorToken_); } return; diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc index 26861f34aae31..26b955b514df3 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc @@ -465,9 +465,9 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { std::cout << "decoding " << wordCounter << " digis." << std::endl; #endif constexpr int numberOfModules = TrackerTraits::numberOfModules; - digis_d = SiPixelDigisSoACollection(wordCounter, queue); + digis_d = SiPixelDigisSoACollection(queue, wordCounter); if (includeErrors) { - digiErrors_d = SiPixelDigiErrorsSoACollection(wordCounter, queue); + digiErrors_d = SiPixelDigiErrorsSoACollection(queue, wordCounter); } clusters_d = SiPixelClustersSoACollection(numberOfModules, queue); // protect in case of empty event.... @@ -579,7 +579,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { const auto workDivFindClus = cms::alpakatools::make_workdiv(blocks, elementsPerBlockFindClus); // allocate a transient collection for the fake pixels recovered by the digi morphing algorithm - auto fakes_d = SiPixelDigisSoACollection(blocks * digiMorphingConfig.maxFakesInModule, queue); + auto fakes_d = SiPixelDigisSoACollection(queue, blocks * digiMorphingConfig.maxFakesInModule); #ifdef GPU_DEBUG alpaka::wait(queue); std::cout << "FindClus kernel launch with " << blocks << " blocks of " << elementsPerBlockFindClus @@ -697,7 +697,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { std::cout << "FindClus kernel launch with " << numberOfModules << " blocks of " << elementsPerBlockFindClus << " threadsPerBlockOrElementsPerThread\n"; #endif - auto unused = SiPixelDigisSoACollection(0, queue); + auto unused = SiPixelDigisSoACollection(queue, 0); alpaka::exec(queue, workDivMaxNumModules, From c006ea69bdc70e444977a928cd1eaa83873efd00 Mon Sep 17 00:00:00 2001 From: Andrea Bocci Date: Mon, 30 Mar 2026 21:49:41 +0200 Subject: [PATCH 4/5] Update SiPixelClusterSoA products Update the constructors of the host and device products, and implement MemoryCopyTraits and the serialisation plugin for the device products. --- .../interface/SiPixelClustersDevice.h | 46 ++++++++++++++++++- .../interface/SiPixelClustersHost.h | 29 ++++++++++-- .../alpaka/SiPixelClustersSoACollection.h | 2 +- .../SiPixelClusterSoA/plugins/BuildFile.xml | 7 +++ .../plugins/alpaka/TrivialSerialisation.cc | 4 ++ .../test/alpaka/Clusters_test.cc | 4 +- .../alpaka/SiPixelPhase2DigiToCluster.cc | 2 +- .../plugins/alpaka/SiPixelRawToCluster.cc | 2 +- .../alpaka/SiPixelRawToClusterKernel.dev.cc | 4 +- 9 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 DataFormats/SiPixelClusterSoA/plugins/alpaka/TrivialSerialisation.cc diff --git a/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersDevice.h b/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersDevice.h index 5e3755d3d78a8..c0cb96fcb6998 100644 --- a/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersDevice.h +++ b/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersDevice.h @@ -9,6 +9,7 @@ #include "DataFormats/Portable/interface/PortableDeviceCollection.h" #include "DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h" #include "DataFormats/SiPixelClusterSoA/interface/SiPixelClustersSoA.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/CopyToHost.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" @@ -18,11 +19,11 @@ class SiPixelClustersDevice : public PortableDeviceCollection{edm::kUninitialized} {} template - explicit SiPixelClustersDevice(size_t maxModules, TQueue queue) + explicit SiPixelClustersDevice(TQueue queue, size_t maxModules) : PortableDeviceCollection(queue, maxModules + 1) {} // Constructor which specifies the SoA size - explicit SiPixelClustersDevice(size_t maxModules, TDev const &device) + explicit SiPixelClustersDevice(TDev const& device, size_t maxModules) : PortableDeviceCollection(device, maxModules + 1) {} void setNClusters(uint32_t nClusters, int32_t offsetBPIX2) { @@ -38,4 +39,45 @@ class SiPixelClustersDevice : public PortableDeviceCollection + struct MemoryCopyTraits> { + using value_type = SiPixelClustersDevice; + + struct Properties { + int size; + uint32_t nClusters; + int32_t offsetBPIX2; + }; + + static Properties properties(value_type const& object) { + return {object->metadata().size() - 1, object.nClusters(), object.offsetBPIX2()}; + } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& prop) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in device global memory. + object = value_type(queue, prop.size); + object.setNClusters(prop.nClusters, prop.offsetBPIX2); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_SiPixelClusterSoA_interface_SiPixelClustersDevice_h diff --git a/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h b/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h index b35413e0fc33c..91044d31c210c 100644 --- a/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h +++ b/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h @@ -14,11 +14,20 @@ // See: https://github.com/cms-sw/cmssw/pull/40465#discussion_r1067364306 class SiPixelClustersHost : public PortableHostCollection { public: - SiPixelClustersHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} + explicit SiPixelClustersHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} - // FIXME add an explicit overload for the host case + // Constructor for code that does not use alpaka explicitly, using the global "host" object returned by cms::alpakatools::host(), construct the object in pageable system memory. + explicit SiPixelClustersHost(size_t maxModules) + : PortableHostCollection(cms::alpakatools::host(), maxModules + 1) {} + + // Construct the object in pageable system memory. + explicit SiPixelClustersHost(alpaka_common::DevHost const& host, size_t maxModules) + : PortableHostCollection(host, maxModules + 1) {} + + // Construct the object in pinned host memory associated to the given work queue, accessible by the queue's device. template - explicit SiPixelClustersHost(size_t maxModules, TQueue queue) + requires(alpaka::isQueue) + explicit SiPixelClustersHost(TQueue queue, size_t maxModules) : PortableHostCollection(queue, maxModules + 1) {} void setNClusters(uint32_t nClusters, int32_t offsetBPIX2) { @@ -51,8 +60,18 @@ namespace ngt { } static void initialize(value_type& object, Properties const& prop) { - // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory - object = value_type(prop.size, cms::alpakatools::host()); + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pageable host memory. + object = value_type(cms::alpakatools::host(), prop.size); + object.setNClusters(prop.nClusters, prop.offsetBPIX2); + } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& prop) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pinned host memory. + object = value_type(queue, prop.size); object.setNClusters(prop.nClusters, prop.offsetBPIX2); } diff --git a/DataFormats/SiPixelClusterSoA/interface/alpaka/SiPixelClustersSoACollection.h b/DataFormats/SiPixelClusterSoA/interface/alpaka/SiPixelClustersSoACollection.h index 2563f08810eca..d2af62faf77c2 100644 --- a/DataFormats/SiPixelClusterSoA/interface/alpaka/SiPixelClustersSoACollection.h +++ b/DataFormats/SiPixelClusterSoA/interface/alpaka/SiPixelClustersSoACollection.h @@ -21,7 +21,7 @@ namespace cms::alpakatools { template static auto copyAsync(TQueue &queue, SiPixelClustersDevice const &srcData) { // SiPixelClustersHost and SiPixelClustersDevice have a capacity larger than the ctor argument by one - SiPixelClustersHost dstData(srcData->metadata().size() - 1, queue); + SiPixelClustersHost dstData(queue, srcData->metadata().size() - 1); alpaka::memcpy(queue, dstData.buffer(), srcData.buffer()); dstData.setNClusters(srcData.nClusters(), srcData.offsetBPIX2()); #ifdef GPU_DEBUG //keeping this untiil copies are in the Tracer diff --git a/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml b/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml index 373ad21b5af1b..516ca76281cff 100644 --- a/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml +++ b/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/SiPixelClusterSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/SiPixelClusterSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..c0493fa10a264 --- /dev/null +++ b/DataFormats/SiPixelClusterSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/SiPixelClusterSoA/interface/alpaka/SiPixelClustersSoACollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(SiPixelClustersSoACollection); diff --git a/DataFormats/SiPixelClusterSoA/test/alpaka/Clusters_test.cc b/DataFormats/SiPixelClusterSoA/test/alpaka/Clusters_test.cc index 26d0c656016a3..10bcb78b620e6 100644 --- a/DataFormats/SiPixelClusterSoA/test/alpaka/Clusters_test.cc +++ b/DataFormats/SiPixelClusterSoA/test/alpaka/Clusters_test.cc @@ -33,12 +33,12 @@ int main() { { // Instantiate tracks on device. PortableDeviceCollection allocates // SoA on device automatically. - SiPixelClustersSoACollection clusters_d(100, queue); + SiPixelClustersSoACollection clusters_d(queue, 100); testClusterSoA::runKernels(clusters_d.view(), queue); // Instantate tracks on host. This is where the data will be // copied to from device. - SiPixelClustersHost clusters_h(clusters_d.view().metadata().size(), queue); + SiPixelClustersHost clusters_h(queue, clusters_d.view().metadata().size()); std::cout << clusters_h.view().metadata().size() << std::endl; alpaka::memcpy(queue, clusters_h.buffer(), clusters_d.const_buffer()); diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc index 2dff97f7c7580..ce337a848d952 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelPhase2DigiToCluster.cc @@ -172,7 +172,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { void SiPixelPhase2DigiToCluster::produce(device::Event& iEvent, device::EventSetup const& iSetup) { if (nDigis_ == 0) { iEvent.emplace(digiPutToken_, std::move(*digis_d_)); - iEvent.emplace(clusterPutToken_, pixelTopology::Phase2::numberOfModules, iEvent.queue()); + iEvent.emplace(clusterPutToken_, iEvent.queue(), pixelTopology::Phase2::numberOfModules); } else { digis_d_->setNModules(algo_.nModules()); iEvent.emplace(digiPutToken_, std::move(*digis_d_)); diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc index 6afc982c3215a..f1fa327ef6745 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToCluster.cc @@ -420,8 +420,8 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { // are no valid pointers to clusters' Collection columns, instantiation // of TrackingRecHits fail. Example: workflow 11604.0 - iEvent.emplace(clusterPutToken_, pixelTopology::Phase1::numberOfModules, iEvent.queue()); iEvent.emplace(digiPutToken_, iEvent.queue(), 0); + iEvent.emplace(clusterPutToken_, iEvent.queue(), pixelTopology::Phase1::numberOfModules); if (includeErrors_) { iEvent.emplace(digiErrorPutToken_, iEvent.queue(), 0); iEvent.emplace(fmtErrorToken_); diff --git a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc index 26b955b514df3..d622614c533b1 100644 --- a/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc +++ b/RecoLocalTracker/SiPixelClusterizer/plugins/alpaka/SiPixelRawToClusterKernel.dev.cc @@ -469,7 +469,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { if (includeErrors) { digiErrors_d = SiPixelDigiErrorsSoACollection(queue, wordCounter); } - clusters_d = SiPixelClustersSoACollection(numberOfModules, queue); + clusters_d = SiPixelClustersSoACollection(queue, numberOfModules); // protect in case of empty event.... if (wordCounter) { const int threadsPerBlockOrElementsPerThread = @@ -669,7 +669,7 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE { using pixelTopology::Phase2; nDigis = numDigis; constexpr int numberOfModules = pixelTopology::Phase2::numberOfModules; - clusters_d = SiPixelClustersSoACollection(numberOfModules, queue); + clusters_d = SiPixelClustersSoACollection(queue, numberOfModules); const auto threadsPerBlockOrElementsPerThread = 512; const auto blocks = cms::alpakatools::divide_up_by(std::max(numDigis, numberOfModules), threadsPerBlockOrElementsPerThread); From 1c886f7341dcc63288dd6eadac6dfe8002f81c2a Mon Sep 17 00:00:00 2001 From: Andrea Bocci Date: Mon, 30 Mar 2026 22:26:51 +0200 Subject: [PATCH 5/5] Update TrackingRecHitSoA products Update the constructors of the host and device products, and implement MemoryCopyTraits and the serialisation plugin for the device products. --- .../interface/TrackingRecHitsDevice.h | 40 ++++++++++++++++++- .../interface/TrackingRecHitsHost.h | 29 ++++++++++++-- .../TrackingRecHitSoA/plugins/BuildFile.xml | 7 ++++ .../plugins/alpaka/TrivialSerialisation.cc | 4 ++ .../test/alpaka/Hits_test.cc | 2 +- 5 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 DataFormats/TrackingRecHitSoA/plugins/alpaka/TrivialSerialisation.cc diff --git a/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsDevice.h b/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsDevice.h index 6f78f83df95d2..15c41d6aa5de8 100644 --- a/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsDevice.h +++ b/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsDevice.h @@ -41,7 +41,7 @@ namespace reco { // Constructor from clusters template - explicit TrackingRecHitDevice(TQueue queue, SiPixelClustersDevice const &clusters) + explicit TrackingRecHitDevice(TQueue queue, SiPixelClustersDevice const& clusters) : HitPortableCollectionDevice(queue, clusters.nClusters(), clusters.view().metadata().size()), offsetBPIX2_{clusters.offsetBPIX2()} { auto hitsView = this->view().trackingHits(); @@ -78,4 +78,40 @@ namespace reco { }; } // namespace reco -#endif // DataFormats_RecHits_interface_TrackingRecHitSoADevice_h +namespace ngt { + + template + struct MemoryCopyTraits> { + using value_type = reco::TrackingRecHitDevice; + + struct Properties { + uint32_t nHits; + uint32_t nModules; + }; + + static Properties properties(value_type const& object) { return {object.nHits(), object.nModules()}; } + + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& prop) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in device global memory. + object = value_type(queue, prop.nHits, prop.nModules); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + +#endif // DataFormats_TrackingRecHitSoA_interface_TrackingRecHitSoADevice_h diff --git a/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h b/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h index ca13dd0d8ff80..c0f388b2ce228 100644 --- a/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h +++ b/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h @@ -22,17 +22,29 @@ namespace reco { class TrackingRecHitHost : public HitPortableCollectionHost { public: - TrackingRecHitHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} + explicit TrackingRecHitHost(edm::Uninitialized) + : PortableHostCollection{edm::kUninitialized} {} + + // Constructor which specifies only the SoA size, to be used when copying + // the results from the device to the host. + // Construct the object in pageable system memory. + explicit TrackingRecHitHost(alpaka_common::DevHost const& host, uint32_t nHits, uint32_t nModules) + : HitPortableCollectionHost(host, nHits, nModules + 1) {} + // Why this +1? See TrackingRecHitDevice.h constructor for an explanation - // Constructor which specifies only the SoA size, to be used when copying the results from the device to the host - // FIXME add an explicit overload for the host case + // Constructor which specifies only the SoA size, to be used when copying + // the results from the device to the host. + // Construct the object in pinned host memory associated to the given work + // queue, accessible by the queue's device. template + requires(alpaka::isQueue) explicit TrackingRecHitHost(TQueue queue, uint32_t nHits, uint32_t nModules) : HitPortableCollectionHost(queue, nHits, nModules + 1) {} // Why this +1? See TrackingRecHitDevice.h constructor for an explanation // Constructor from clusters template + requires(alpaka::isQueue) explicit TrackingRecHitHost(TQueue queue, SiPixelClustersHost const& clusters) : HitPortableCollectionHost(queue, clusters.nClusters(), clusters.view().metadata().size()) { auto hitsView = view().trackingHits(); @@ -74,10 +86,19 @@ namespace ngt { static Properties properties(value_type const& object) { return {object.nHits(), object.nModules()}; } static void initialize(value_type& object, Properties const& prop) { - // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pageable host memory. object = value_type(cms::alpakatools::host(), prop.nHits, prop.nModules); } + template + requires(alpaka::isQueue) + static void initialize(TQueue& queue, value_type& object, Properties const& prop) { + // Replace the default-constructed empty object with one where the buffer + // has been allocated in pinned host memory. + object = value_type(queue, prop.nHits, prop.nModules); + } + static std::vector> regions(value_type& object) { std::byte* address = reinterpret_cast(object.buffer().data()); size_t size = alpaka::getExtentProduct(object.buffer()); diff --git a/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml b/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml index d1a16495ee94e..d0ee409c3c83c 100644 --- a/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml +++ b/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml @@ -3,3 +3,10 @@ + + + + + + + diff --git a/DataFormats/TrackingRecHitSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/TrackingRecHitSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..ff080ccab5de4 --- /dev/null +++ b/DataFormats/TrackingRecHitSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/TrackingRecHitSoA/interface/alpaka/TrackingRecHitsSoACollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::TrackingRecHitsSoACollection); diff --git a/DataFormats/TrackingRecHitSoA/test/alpaka/Hits_test.cc b/DataFormats/TrackingRecHitSoA/test/alpaka/Hits_test.cc index 2c70c326ba5b0..46d471a59b1d3 100644 --- a/DataFormats/TrackingRecHitSoA/test/alpaka/Hits_test.cc +++ b/DataFormats/TrackingRecHitSoA/test/alpaka/Hits_test.cc @@ -38,7 +38,7 @@ int main() { int32_t offset = 100; uint32_t nModules = 200; - SiPixelClustersSoACollection clusters(nModules, queue); + SiPixelClustersSoACollection clusters(queue, nModules); clusters.setNClusters(nHits, offset); auto moduleStartH = cms::alpakatools::make_host_buffer(queue, nModules + 1);