Skip to content

Commit

Permalink
Merge pull request #43295 from PixelTracksAlpaka/alpaka_port_13_1_por…
Browse files Browse the repository at this point in the history
…table

Pixel Alpaka Migration: Portable Product [II]
  • Loading branch information
cmsbuild authored Nov 24, 2023
2 parents c4e98e9 + 1066b18 commit 4fd19dd
Show file tree
Hide file tree
Showing 34 changed files with 699 additions and 44 deletions.
9 changes: 6 additions & 3 deletions DataFormats/BeamSpot/BuildFile.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<use name="clhep"/>
<use name="rootcore"/>
<use name="rootsmatrix"/>
<use name="DataFormats/Common"/>
<use name="DataFormats/CLHEP"/>
<use name="DataFormats/GeometrySurface"/>
<use name="DataFormats/Math"/>
<use name="rootcore"/>
<use name="rootsmatrix"/>
<use name="clhep"/>
<use name="DataFormats/Portable"/>
<use name="HeterogeneousCore/AlpakaInterface"/>
<flags ALPAKA_BACKENDS="!serial"/>
<export>
<lib name="1"/>
</export>
10 changes: 10 additions & 0 deletions DataFormats/BeamSpot/interface/BeamSpotHost.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef DataFormats_BeamSpot_interface_BeamSpotHost_h
#define DataFormats_BeamSpot_interface_BeamSpotHost_h

#include "DataFormats/BeamSpot/interface/BeamSpotPOD.h"
#include "DataFormats/Portable/interface/PortableHostObject.h"

// simplified representation of the beamspot data, in host memory
using BeamSpotHost = PortableHostObject<BeamSpotPOD>;

#endif // DataFormats_BeamSpot_interface_BeamSpotHost_h
17 changes: 11 additions & 6 deletions DataFormats/BeamSpot/interface/BeamSpotPOD.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
#ifndef DataFormats_BeamSpot_interface_BeamSpotPOD_h
#define DataFormats_BeamSpot_interface_BeamSpotPOD_h

// This struct is a transient-only, simplified representation of the beamspot
// data used as the underlying type for data transfers and operations in
// This struct is a simplified representation of the beamspot data
// used as the underlying type for data transfers and operations in
// heterogeneous code (e.g. in CUDA code).

// The covariance matrix is not used in that code, so is left out here.

// align to the CUDA L1 cache line size
struct alignas(128) BeamSpotPOD {
float x, y, z; // position
float x; // position
float y;
float z;
float sigmaZ;
float beamWidthX, beamWidthY;
float dxdz, dydz;
float emittanceX, emittanceY;
float beamWidthX;
float beamWidthY;
float dxdz;
float dydz;
float emittanceX;
float emittanceY;
float betaStar;
};

Expand Down
19 changes: 19 additions & 0 deletions DataFormats/BeamSpot/interface/alpaka/BeamSpotDevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef DataFormats_BeamSpot_interface_alpaka_BeamSpotDevice_h
#define DataFormats_BeamSpot_interface_alpaka_BeamSpotDevice_h

#include "DataFormats/BeamSpot/interface/BeamSpotHost.h"
#include "DataFormats/BeamSpot/interface/BeamSpotPOD.h"
#include "DataFormats/Portable/interface/alpaka/PortableObject.h"
#include "HeterogeneousCore/AlpakaInterface/interface/config.h"

namespace ALPAKA_ACCELERATOR_NAMESPACE {

// simplified representation of the beamspot data, in device global memory
using BeamSpotDevice = PortableObject<BeamSpotPOD>;

} // namespace ALPAKA_ACCELERATOR_NAMESPACE

// check that the portable device collection for the host device is the same as the portable host collection
ASSERT_DEVICE_MATCHES_HOST_COLLECTION(BeamSpotDevice, BeamSpotHost);

#endif // DataFormats_BeamSpot_interface_alpaka_BeamSpotDevice_h
4 changes: 4 additions & 0 deletions DataFormats/BeamSpot/src/alpaka/classes_cuda.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "DataFormats/BeamSpot/interface/BeamSpotPOD.h"
#include "DataFormats/BeamSpot/interface/alpaka/BeamSpotDevice.h"
#include "DataFormats/Common/interface/DeviceProduct.h"
#include "DataFormats/Common/interface/Wrapper.h"
5 changes: 5 additions & 0 deletions DataFormats/BeamSpot/src/alpaka/classes_cuda_def.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<lcgdict>
<class name="alpaka_cuda_async::BeamSpotDevice" persistent="false"/>
<class name="edm::DeviceProduct<alpaka_cuda_async::BeamSpotDevice>" persistent="false"/>
<class name="edm::Wrapper<edm::DeviceProduct<alpaka_cuda_async::BeamSpotDevice>>" persistent="false"/>
</lcgdict>
4 changes: 4 additions & 0 deletions DataFormats/BeamSpot/src/alpaka/classes_rocm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "DataFormats/BeamSpot/interface/BeamSpotPOD.h"
#include "DataFormats/BeamSpot/interface/alpaka/BeamSpotDevice.h"
#include "DataFormats/Common/interface/DeviceProduct.h"
#include "DataFormats/Common/interface/Wrapper.h"
5 changes: 5 additions & 0 deletions DataFormats/BeamSpot/src/alpaka/classes_rocm_def.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<lcgdict>
<class name="alpaka_rocm_async::BeamSpotDevice" persistent="false"/>
<class name="edm::DeviceProduct<alpaka_rocm_async::BeamSpotDevice>" persistent="false"/>
<class name="edm::Wrapper<edm::DeviceProduct<alpaka_rocm_async::BeamSpotDevice>>" persistent="false"/>
</lcgdict>
4 changes: 4 additions & 0 deletions DataFormats/BeamSpot/src/classes.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "DataFormats/BeamSpot/interface/BeamSpotHost.h"
#include "DataFormats/Portable/interface/PortableHostObjectReadRules.h"

SET_PORTABLEHOSTOBJECT_READ_RULES(BeamSpotHost);
1 change: 1 addition & 0 deletions DataFormats/BeamSpot/src/classes.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "DataFormats/BeamSpot/interface/BeamSpot.h"
#include "DataFormats/BeamSpot/interface/BeamSpotHost.h"
#include "DataFormats/BeamSpot/interface/BeamSpotPOD.h"
#include "DataFormats/Common/interface/Wrapper.h"
9 changes: 7 additions & 2 deletions DataFormats/BeamSpot/src/classes_def.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
</class>
<class name="edm::Wrapper<reco::BeamSpot>"/>

<class name="BeamSpotPOD" persistent="false"/>
<class name="edm::Wrapper<BeamSpotPOD>" persistent="false"/>
<class name="BeamSpotPOD" ClassVersion="3">
<version ClassVersion="3" checksum="280341519"/>
</class>
<class name="edm::Wrapper<BeamSpotPOD>"/>

<class name="BeamSpotHost"/>
<class name="edm::Wrapper<BeamSpotHost>"/>
</lcgdict>
73 changes: 67 additions & 6 deletions DataFormats/Portable/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,71 @@
## Define portable data formats that wrap a simple C-style `struct` and can be persisted to ROOT files

### `PortableHostObject<T>`

`PortableHostObject<T>` is a class template that wraps a `struct` type `T` and an alpaka host buffer, that owns the
memory where the `struct` is allocated. The content of the `struct` is persistent, while the buffer itself is transient.
Specialisations of this template can be persisted, but requre special ROOT read rules to read the data into an alpaka
memory buffer.

The traditional way to declare these rules would be to add them to the `classes_def.xml` file. This method is described
here for reference, but is deprecated. See below for a simpler method.
For example, to declare the read rules for `portabletest::TestHostObject` based on the `portabletest::TestStruct` `struct`,
one would add to the same `classes_def.xml` file where `portabletest::TestHostObject` is declared:
```xml
<read
sourceClass="portabletest::TestHostObject"
targetClass="portabletest::TestHostObject"
version="[1-]"
source="portabletest::TestStruct* product_;"
target="buffer_,product_"
embed="false">
<![CDATA[
portabletest::TestHostObject::ROOTReadStreamer(newObj, *onfile.product_);
]]>
</read>
```

The recommended way to declare these rules is to create a `classes.cc` file along with `classes.h`, and use the
`SET_PORTABLEHOSTOBJECT_READ_RULES` macro. For example, to declare the rules for `portabletest::TestHostObject` one
would create the file `classes.cc` with the content:
```c++
#include "DataFormats/Portable/interface/PortableHostObjectReadRules.h"
#include "DataFormats/PortableTestObjects/interface/TestHostObject.h"

SET_PORTABLEHOSTOBJECT_READ_RULES(portabletest::TestHostObject);
```
`PortableHostObject<T>` objects can also be read back in "bare ROOT" mode, without any dictionaries.
They have no implicit or explicit references to alpaka (neither as part of the class signature nor as part of its name).
This could make it possible to read them back with different portability solutions in the future.
### `PortableDeviceObject<T, TDev>`
`PortableDeviceObject<T, TDev>` is a class template that wraps a `struct` type `T` and an alpaka device buffer, that
owns the memory where the `struct` is allocated.
To avoid confusion and ODR-violations, the `PortableDeviceObject<T, TDev>` template cannot be used with the `Host`
device type.
Specialisations of this template are transient and cannot be persisted.
### `ALPAKA_ACCELERATOR_NAMESPACE::PortableObject<T>`
`ALPAKA_ACCELERATOR_NAMESPACE::PortableObject<T>` is a template alias that resolves to either
`PortableHostObject<T>` or `PortableDeviceObject<T, ALPAKA_ACCELERATOR_NAMESPACE::Device>`, depending on the
backend.
### `PortableObject<T, TDev>`
`PortableObject<T, TDev>` is an alias template that resolves to `ALPAKA_ACCELERATOR_NAMESPACE::PortableObject<T>`
for the matching device.
## Define portable data formats that wrap SoA data structures and can be persisted to ROOT files
### `PortableHostCollection<T>`
`PortableHostCollection<T>` is a class template that wraps a SoA type `T` and an alpaka host buffer, which owns the
memory where the SoA is allocated. The content of the SoA is persistent, while the buffer itself is transient.
Specialisations of this template can be persisted, but requre specil ROOT read rules to read the data into a single
Specialisations of this template can be persisted, but requre special ROOT read rules to read the data into a single
memory buffer.
The original way to declare these rules, now deprecated, is to add them to the `classes_def.xml` file. For example,
Expand All @@ -24,17 +85,16 @@ would add to the same `classes_def.xml` file where `portabletest::TestHostCollec
```

The new, recommended way to declare these rules is to create a `classes.cc` file along with `classes.h`, and use the
`SET_PORTABLEHOSTCOLLECTION_READ_RULES`. For example, to declare the rules for `portabletest::TestHostCollection` one
would create the file `classes.cc` with the content:
`SET_PORTABLEHOSTCOLLECTION_READ_RULES` macro. For example, to declare the rules for `portabletest::TestHostCollection`
one would create the file `classes.cc` with the content:
```c++
#include "DataFormats/Portable/interface/PortableHostCollectionReadRules.h"
#include "DataFormats/PortableTestObjects/interface/TestHostCollection.h"

SET_PORTABLEHOSTCOLLECTION_READ_RULES(portabletest::TestHostCollection);
```
`PortableHostCollection<T>` can also be read back in "bare ROOT" mode, without any dictionaries.
`PortableHostCollection<T>` collections can also be read back in "bare ROOT" mode, without any dictionaries.
They have no implicit or explicit references to alpaka (neither as part of the class signature nor as part of its name).
This could make it possible to read them back with different portability solutions in the future.
Expand All @@ -61,7 +121,8 @@ for the matching device.
## Notes
Modules that are supposed to work with only host types (_e.g._ dealing with de/serialisation, data transfers, _etc._)
should explicitly use the `PortableHostCollection<T>` types.
should explicitly use the `PortableHostObject<T>` and `PortableHostCollection<T>` types.
Modules that implement portable interfaces (_e.g._ producers) should use the generic types based on
`ALPAKA_ACCELERATOR_NAMESPACE::PortableObject<T>` or `PortableObject<T, TDev>`, and
`ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection<T>` or `PortableCollection<T, TDev>`.
72 changes: 72 additions & 0 deletions DataFormats/Portable/interface/PortableDeviceObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef DataFormats_Portable_interface_PortableDeviceObject_h
#define DataFormats_Portable_interface_PortableDeviceObject_h

#include <cassert>
#include <optional>
#include <type_traits>

#include <alpaka/alpaka.hpp>

#include "HeterogeneousCore/AlpakaInterface/interface/config.h"
#include "HeterogeneousCore/AlpakaInterface/interface/memory.h"

// generic object in device memory
template <typename T, typename TDev, typename = std::enable_if_t<alpaka::isDevice<TDev>>>
class PortableDeviceObject {
static_assert(not std::is_same_v<TDev, alpaka_common::DevHost>,
"Use PortableHostObject<T> instead of PortableDeviceObject<T, DevHost>");

public:
using Product = T;
using Buffer = cms::alpakatools::device_buffer<TDev, Product>;
using ConstBuffer = cms::alpakatools::const_device_buffer<TDev, Product>;

PortableDeviceObject() = default;

PortableDeviceObject(TDev const& device)
// allocate global device memory
: buffer_{cms::alpakatools::make_device_buffer<Product>(device)} {
assert(reinterpret_cast<uintptr_t>(buffer_->data()) % alignof(Product) == 0);
}

template <typename TQueue, typename = std::enable_if_t<alpaka::isQueue<TQueue>>>
PortableDeviceObject(TQueue const& queue)
// allocate global device memory with queue-ordered semantic
: buffer_{cms::alpakatools::make_device_buffer<Product>(queue)} {
assert(reinterpret_cast<uintptr_t>(buffer_->data()) % alignof(Product) == 0);
}

// non-copyable
PortableDeviceObject(PortableDeviceObject const&) = delete;
PortableDeviceObject& operator=(PortableDeviceObject const&) = delete;

// movable
PortableDeviceObject(PortableDeviceObject&&) = default;
PortableDeviceObject& operator=(PortableDeviceObject&&) = default;

// default destructor
~PortableDeviceObject() = default;

// access the product
Product& value() { return *buffer_->data(); }
Product const& value() const { return *buffer_->data(); }

Product* data() { return buffer_->data(); }
Product const* data() const { return buffer_->data(); }

Product& operator*() { return *buffer_->data(); }
Product const& operator*() const { return *buffer_->data(); }

Product* operator->() { return buffer_->data(); }
Product const* operator->() const { return buffer_->data(); }

// access the buffer
Buffer buffer() { return *buffer_; }
ConstBuffer buffer() const { return *buffer_; }
ConstBuffer const_buffer() const { return *buffer_; }

private:
std::optional<Buffer> buffer_;
};

#endif // DataFormats_Portable_interface_PortableDeviceObject_h
81 changes: 81 additions & 0 deletions DataFormats/Portable/interface/PortableHostObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#ifndef DataFormats_Portable_interface_PortableHostObject_h
#define DataFormats_Portable_interface_PortableHostObject_h

#include <cassert>
#include <optional>
#include <type_traits>

#include <alpaka/alpaka.hpp>

#include "HeterogeneousCore/AlpakaInterface/interface/config.h"
#include "HeterogeneousCore/AlpakaInterface/interface/host.h"
#include "HeterogeneousCore/AlpakaInterface/interface/memory.h"

// generic object in host memory
template <typename T>
class PortableHostObject {
public:
using Product = T;
using Buffer = cms::alpakatools::host_buffer<Product>;
using ConstBuffer = cms::alpakatools::const_host_buffer<Product>;

PortableHostObject() = default;

PortableHostObject(alpaka_common::DevHost const& host)
// allocate pageable host memory
: buffer_{cms::alpakatools::make_host_buffer<Product>()}, product_{buffer_->data()} {
assert(reinterpret_cast<uintptr_t>(product_) % alignof(Product) == 0);
}

template <typename TQueue, typename = std::enable_if_t<alpaka::isQueue<TQueue>>>
PortableHostObject(TQueue const& queue)
// allocate pinned host memory associated to the given work queue, accessible by the queue's device
: buffer_{cms::alpakatools::make_host_buffer<Product>(queue)}, product_{buffer_->data()} {
assert(reinterpret_cast<uintptr_t>(product_) % alignof(Product) == 0);
}

// non-copyable
PortableHostObject(PortableHostObject const&) = delete;
PortableHostObject& operator=(PortableHostObject const&) = delete;

// movable
PortableHostObject(PortableHostObject&&) = default;
PortableHostObject& operator=(PortableHostObject&&) = default;

// default destructor
~PortableHostObject() = default;

// access the product
Product& value() { return *product_; }
Product const& value() const { return *product_; }

Product* data() { return product_; }
Product const* data() const { return product_; }

Product& operator*() { return *product_; }
Product const& operator*() const { return *product_; }

Product* operator->() { return product_; }
Product const* operator->() const { return product_; }

// access the buffer
Buffer buffer() { return *buffer_; }
ConstBuffer buffer() const { return *buffer_; }
ConstBuffer const_buffer() const { return *buffer_; }

// part of the ROOT read streamer
static void ROOTReadStreamer(PortableHostObject* newObj, Product& product) {
// destroy the default-constructed object
newObj->~PortableHostObject();
// use the global "host" object returned by cms::alpakatools::host()
new (newObj) PortableHostObject(cms::alpakatools::host());
// copy the data from the on-file object to the new one
std::memcpy(newObj->product_, &product, sizeof(Product));
}

private:
std::optional<Buffer> buffer_; //!
Product* product_;
};

#endif // DataFormats_Portable_interface_PortableHostObject_h
Loading

0 comments on commit 4fd19dd

Please sign in to comment.