diff --git a/DataFormats/EcalDigi/plugins/BuildFile.xml b/DataFormats/EcalDigi/plugins/BuildFile.xml index 500770828ac90..c9beb08d7248a 100644 --- a/DataFormats/EcalDigi/plugins/BuildFile.xml +++ b/DataFormats/EcalDigi/plugins/BuildFile.xml @@ -3,3 +3,11 @@ + + + + + + + + diff --git a/DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..af964aceaac7c --- /dev/null +++ b/DataFormats/EcalDigi/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/EcalDigi/interface/alpaka/EcalDigiDeviceCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(EcalDigiDeviceCollection); diff --git a/DataFormats/EcalRecHit/plugins/BuildFile.xml b/DataFormats/EcalRecHit/plugins/BuildFile.xml index ee7c656ef4f71..071bf88e6de04 100644 --- a/DataFormats/EcalRecHit/plugins/BuildFile.xml +++ b/DataFormats/EcalRecHit/plugins/BuildFile.xml @@ -3,3 +3,11 @@ + + + + + + + + diff --git a/DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..f5efded924487 --- /dev/null +++ b/DataFormats/EcalRecHit/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/EcalRecHit/interface/alpaka/EcalUncalibratedRecHitDeviceCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(EcalUncalibratedRecHitDeviceCollection); diff --git a/DataFormats/HcalRecHit/plugins/BuildFile.xml b/DataFormats/HcalRecHit/plugins/BuildFile.xml index 07725848fdb30..c50aaad71fc44 100644 --- a/DataFormats/HcalRecHit/plugins/BuildFile.xml +++ b/DataFormats/HcalRecHit/plugins/BuildFile.xml @@ -3,3 +3,13 @@ + + + + + + + + + + diff --git a/DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..8a2ba97f1751b --- /dev/null +++ b/DataFormats/HcalRecHit/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/HcalRecHit/interface/alpaka/HcalRecHitDeviceCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.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..cd269d52304ed 100644 --- a/DataFormats/ParticleFlowReco/plugins/BuildFile.xml +++ b/DataFormats/ParticleFlowReco/plugins/BuildFile.xml @@ -3,3 +3,12 @@ + + + + + + + + + diff --git a/DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..895f83585b52a --- /dev/null +++ b/DataFormats/ParticleFlowReco/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,11 @@ +#include + +#include "DataFormats/ParticleFlowReco/interface/alpaka/PFClusterDeviceCollection.h" +#include "DataFormats/ParticleFlowReco/interface/alpaka/PFRecHitDeviceCollection.h" +#include "DataFormats/ParticleFlowReco/interface/alpaka/PFRecHitFractionDeviceCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::PFRecHitDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::PFClusterDeviceCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::PFRecHitFractionDeviceCollection); diff --git a/DataFormats/Portable/interface/PortableDeviceCollection.h b/DataFormats/Portable/interface/PortableDeviceCollection.h index ec357e8ecdcc0..ead1773e0068c 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,39 @@ 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 + static void initialize(TQueue& queue, value_type& object, Properties const& size) + requires(alpaka::isQueue) + { + // 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..f7eb9c2ad95d7 100644 --- a/DataFormats/PortableTestObjects/plugins/BuildFile.xml +++ b/DataFormats/PortableTestObjects/plugins/BuildFile.xml @@ -3,3 +3,12 @@ + + + + + + + + + diff --git a/DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..18e1114b1f4b8 --- /dev/null +++ b/DataFormats/PortableTestObjects/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,9 @@ +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceCollection.h" +#include "DataFormats/PortableTestObjects/interface/alpaka/TestDeviceObject.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +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/TrackSoA/plugins/BuildFile.xml b/DataFormats/TrackSoA/plugins/BuildFile.xml index e2d5a430d6ae4..b7d65dcb63c60 100644 --- a/DataFormats/TrackSoA/plugins/BuildFile.xml +++ b/DataFormats/TrackSoA/plugins/BuildFile.xml @@ -3,3 +3,11 @@ + + + + + + + + diff --git a/DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..a96727d3e3486 --- /dev/null +++ b/DataFormats/TrackSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/TrackSoA/interface/alpaka/TracksSoACollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::TracksSoACollection); 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/DataFormats/VertexSoA/plugins/BuildFile.xml b/DataFormats/VertexSoA/plugins/BuildFile.xml index 9967a9fbb7ed1..f42c0ddcc28e9 100644 --- a/DataFormats/VertexSoA/plugins/BuildFile.xml +++ b/DataFormats/VertexSoA/plugins/BuildFile.xml @@ -3,3 +3,12 @@ + + + + + + + + + diff --git a/DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc b/DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc new file mode 100644 index 0000000000000..51637655ce1a3 --- /dev/null +++ b/DataFormats/VertexSoA/plugins/alpaka/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/VertexSoA/interface/alpaka/ZVertexSoACollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN_DEVICE(reco::ZVertexSoACollection); 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..784ff212f7eb4 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,39 +30,24 @@ 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, and thus there is nothing to initialize. Just check + // that properties are not present, as they are not needed. static_assert(not ngt::HasTrivialCopyProperties); } else if constexpr (not ngt::HasTrivialCopyProperties) { - // If T has no TrivialCopyProperties, call initialize() without any additional arguments. ngt::MemoryCopyTraits::initialize(object()); } else { - // If T has TrivialCopyProperties, cast args to Properties and pass it as an additional argument to initialize(). ngt::MemoryCopyTraits::initialize(object(), args.cast_to>()); } } - 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 +57,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..ecb3f1df69628 --- /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/ReaderBase.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // Reader for device products. + template + class Reader : public ::ngt::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) : ::ngt::ReaderBase(&wrapper), productPtr_(&wrapper.bareProduct()) {} + + // Constructor from T const& directly. + explicit Reader(T const& product) : ::ngt::ReaderBase(nullptr), 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..01bf5c2c0cb63 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/alpaka/ReaderBase.h @@ -0,0 +1,14 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_alpaka_ReaderBase_h +#define HeterogeneousCore_TrivialSerialisation_interface_alpaka_ReaderBase_h + +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h" + +namespace ALPAKA_ACCELERATOR_NAMESPACE::ngt { + + // The alpaka ReaderBase is the same as the host one. + using ReaderBase = ::ngt::ReaderBase; + +} // 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..ff3c889a2b88f --- /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(T& o, Queue& q, 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(T& o, Queue& q) { 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..d834efc9e3de5 100644 --- a/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml +++ b/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml @@ -8,3 +8,19 @@ + + + + + + + + + + + + + + + + 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..be1bad1d73ccf --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/alpaka/test_catch2_portableCollectionsSerialiserPluginFactory.dev.cc @@ -0,0 +1,340 @@ +#include +#include +#include + +#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 "DataFormats/AlpakaCommon/interface/alpaka/EDMetadata.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/alpaka/SerialiserFactoryDevice.h" + +using namespace ALPAKA_ACCELERATOR_NAMESPACE; +namespace alpaka_portable_test = 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_portable_test::TestDeviceObject; + using DeviceProductType = detail::DeviceProductType; + const alpaka_portable_test::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_portable_test::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); + } + } + } +}