Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions DataFormats/Portable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ from a `View` or `ConstView` (pointing to data in host/device memory and potenti
a `ConstDescriptor` object, into the `PortableHostCollection` contigous buffer. This method should be used for device to
host data transfer.

`PortableHostCollection<T>` can also wraps an AoS type, in the same way as for SoA. The member function
`void transpose<TAcc>(SourceCollection const& sourceCollection, TQueue& queue)` performs the transposition from SoA to
AoS and viceversa. Any attempt to perform transpositions other than between compatible structures will fail.

### `PortableDeviceCollection<T, TDev>`

`PortableDeviceCollection<T, TDev>` is a class template that wraps a SoA type `T` and an alpaka device buffer, which
Expand All @@ -121,6 +125,10 @@ from a `View` or `ConstView` (pointing to data in host/device memory and potenti
a `ConstDescriptor` object into the `PortableDeviceCollection` contigous buffer. This method should be used for
host to device or device to device data transfer.

`PortableDeviceCollection<T, TDev>` can also wraps an AoS type, in the same way as for SoA. The member function
`void transpose<TAcc>(SourceCollection const& sourceCollection, TQueue& queue)` performs the transposition from SoA to
AoS and viceversa. Any attempt to perform transpositions other than between compatible structures will fail.

### `ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection<T>`

`ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection<T>` is a template alias that resolves to either
Expand All @@ -141,6 +149,9 @@ Modules that implement portable interfaces (_e.g._ producers) should use the gen
`ALPAKA_ACCELERATOR_NAMESPACE::PortableObject<T>` or `PortableObject<T, TDev>`, and
`ALPAKA_ACCELERATOR_NAMESPACE::PortableCollection<T>` or `PortableCollection<T, TDev>`.

`portablecollection::kernels` contains the only kernel in `Portable`, used to perform heterogeneous transposition from
AoS/SoA to SoA/AoS.

## Multi layout collections

Some use cases require multiple sets of columns of different sizes. This is can be achieved in a single
Expand Down
16 changes: 16 additions & 0 deletions DataFormats/Portable/interface/PortableCollectionCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <cstddef>
#include <type_traits>
#include <array>
#include "HeterogeneousCore/AlpakaInterface/interface/config.h"
#include "HeterogeneousCore/AlpakaInterface/interface/memory.h"
#include "HeterogeneousCore/AlpakaInterface/interface/workdivision.h"

namespace portablecollection {

Expand Down Expand Up @@ -98,6 +101,19 @@ namespace portablecollection {
template <typename T, typename... Args>
inline constexpr std::size_t typeIndex = TypeIndex<T, Args...>::value;

namespace kernels {
// Kernel for filling the AoS
struct Transpose {
template <typename TAcc, typename DestView, typename SourceView>
ALPAKA_FN_ACC ALPAKA_FN_INLINE void operator()(TAcc const& acc,
DestView destView,
SourceView const& sourceView) const {
for (auto local_idx : cms::alpakatools::uniform_elements(acc, sourceView.metadata().size())) {
destView.transpose(sourceView, local_idx);
}
}
};
} // namespace kernels
} // namespace portablecollection

#endif // DataFormats_Portable_interface_PortableCollectionCommon_h
22 changes: 22 additions & 0 deletions DataFormats/Portable/interface/PortableDeviceCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,34 @@ class PortableDeviceCollection {

// Copy column by column heterogeneously for device to host/device data transfer.
template <typename TQueue>
requires(Layout::isSoA)
void deepCopy(ConstView const& view, TQueue& queue) {
ConstDescriptor desc{view};
Descriptor desc_{view_};
_deepCopy<0>(desc_, desc, queue);
}

// Transpose the data from a SoA view into an AoS collection.
// Requires that one of the two collections is SoA and the other is AoS,
// and that the AoS type is the AoSWrapper of the SoA type.
template <typename TAcc, typename SourceCollection, typename TQueue>
ALPAKA_FN_HOST ALPAKA_FN_INLINE void transpose(SourceCollection const& sourceCollection, TQueue& queue)
requires((!SourceCollection::Layout::isSoA && Layout::isSoA) ||
(SourceCollection::Layout::isSoA && !Layout::isSoA)) &&
((std::is_same_v<typename SourceCollection::Layout, typename Layout::AoSWrapper>) ||
(std::is_same_v<typename SourceCollection::Layout::AoSWrapper, Layout>))
{
if (view_.metadata().size() != sourceCollection.const_view().metadata().size()) {
throw std::runtime_error("PortableDeviceCollection::transpose: size mismatch between SoA and AoS");
}
alpaka::exec<TAcc>(queue,
cms::alpakatools::make_workdiv<TAcc>(
cms::alpakatools::divide_up_by(sourceCollection.const_view().metadata().size(), 256), 256),
portablecollection::kernels::Transpose{},
view_,
sourceCollection.const_view());
}

private:
// Helper function implementing the recursive deep copy
template <int I, typename TQueue>
Expand Down
22 changes: 22 additions & 0 deletions DataFormats/Portable/interface/PortableHostCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,34 @@ class PortableHostCollection {

// Copy column by column heterogeneously for device to host data transfer.
template <typename TQueue>
requires(Layout::isSoA)
void deepCopy(ConstView const& view, TQueue& queue) {
ConstDescriptor desc{view};
Descriptor desc_{view_};
_deepCopy<0>(desc_, desc, queue);
}

// Transpose the data from a SoA view into an AoS collection.
// Requires that one of the two collections is SoA and the other is AoS,
// and that the AoS type is the AoSWrapper of the SoA type.
template <typename TAcc, typename SourceCollection, typename TQueue>
ALPAKA_FN_HOST ALPAKA_FN_INLINE void transpose(SourceCollection const& sourceCollection, TQueue& queue)
requires((!SourceCollection::Layout::isSoA && Layout::isSoA) ||
(SourceCollection::Layout::isSoA && !Layout::isSoA)) &&
((std::is_same_v<typename SourceCollection::Layout, typename Layout::AoSWrapper>) ||
(std::is_same_v<typename SourceCollection::Layout::AoSWrapper, Layout>))
{
if (view_.metadata().size() != sourceCollection.const_view().metadata().size()) {
throw std::runtime_error("PortableHostCollection::transpose: size mismatch between SoA and AoS");
}
alpaka::exec<TAcc>(queue,
cms::alpakatools::make_workdiv<TAcc>(
cms::alpakatools::divide_up_by(sourceCollection.const_view().metadata().size(), 256), 256),
portablecollection::kernels::Transpose{},
view_,
sourceCollection.const_view());
}

private:
// Helper function implementing the recursive deep copy
template <int I, typename TQueue>
Expand Down
9 changes: 9 additions & 0 deletions DataFormats/Portable/test/BuildFile.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@
<use name="HeterogeneousCore/AlpakaInterface"/>
<flags ALPAKA_BACKENDS="1"/>
</bin>

<bin name="TestDataFormatsPortableAoS" file="alpaka/test_catch2_portableAoS.dev.cc">
<use name="DataFormats/Portable"/>
<use name="DataFormats/SoATemplate"/>
<use name="catch2"/>
<use name="eigen"/>
<use name="HeterogeneousCore/AlpakaInterface"/>
<flags ALPAKA_BACKENDS="1"/>
</bin>
149 changes: 149 additions & 0 deletions DataFormats/Portable/test/alpaka/test_catch2_portableAoS.dev.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include <Eigen/Core>
#include <Eigen/Dense>

#include <alpaka/alpaka.hpp>

#define CATCH_CONFIG_MAIN
#include <catch2/catch_all.hpp>

#include "DataFormats/SoATemplate/interface/SoALayout.h"
#include "DataFormats/Portable/interface/PortableCollection.h"
#include "HeterogeneousCore/AlpakaInterface/interface/config.h"
#include "HeterogeneousCore/AlpakaInterface/interface/memory.h"
#include "HeterogeneousCore/AlpakaInterface/interface/workdivision.h"

using namespace ALPAKA_ACCELERATOR_NAMESPACE;

GENERATE_SOA_LAYOUT(SoATemplate,
SOA_SCALAR(int, s1),
SOA_COLUMN(float, x),
SOA_COLUMN(float, y),
SOA_COLUMN(float, z),
SOA_SCALAR(float, s2),
SOA_EIGEN_COLUMN(Eigen::Vector3f, exampleVector),
SOA_SCALAR(float, s3))

using SoA = SoATemplate<>;
using AoS = SoA::AoSWrapper;
using SoAView = SoA::View;
using AoSView = AoS::View;
using SoAConstView = SoA::ConstView;
using AoSConstView = AoS::ConstView;

struct FillSoA {
template <typename TAcc, typename SoAView>
ALPAKA_FN_ACC void operator()(TAcc const& acc, SoAView view) const {
if (cms::alpakatools::once_per_grid(acc)) {
view.s1() = 1;
view.s2() = 2.0f;
view.s3() = 3.0f;
}

const float n = static_cast<float>(view.metadata().size());

for (auto local_idx : cms::alpakatools::uniform_elements(acc, view.metadata().size())) {
view[local_idx].x() = static_cast<float>(local_idx) + 0.0f * n;
view[local_idx].y() = static_cast<float>(local_idx) + 1.0f * n;
view[local_idx].z() = static_cast<float>(local_idx) + 2.0f * n;

view[local_idx].exampleVector()(0) = static_cast<float>(local_idx) + 3.0f * n;
view[local_idx].exampleVector()(1) = static_cast<float>(local_idx) + 4.0f * n;
view[local_idx].exampleVector()(2) = static_cast<float>(local_idx) + 5.0f * n;
}
}
};

TEST_CASE("AoS testcase for PortableCollection", "[PortableCollectionAOS]") {
auto const& devices = cms::alpakatools::devices<Platform>();
if (devices.empty()) {
FAIL("No devices available for the " EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) " backend, "
"the test will be skipped.");
}

auto devHost = alpaka::getDevByIdx(alpaka::PlatformCpu{}, 0u);

for (auto const& device : cms::alpakatools::devices<Platform>()) {
std::cout << "Running on " << alpaka::getName(device) << std::endl;

Queue queue(device);

// number of elements for this test case
const std::size_t elems = 10;

// Portable Collections using SoA layout
PortableCollection<SoA, Device> soaCollection(elems, queue);
SoAView& soaCollectionView = soaCollection.view();

PortableCollection<AoS, Device> aosCollection(elems, queue);

auto blockSize = 64;
auto numberOfBlocks = cms::alpakatools::divide_up_by(elems, blockSize);
const auto workDiv = cms::alpakatools::make_workdiv<Acc1D>(numberOfBlocks, blockSize);

alpaka::exec<Acc1D>(queue, workDiv, FillSoA{}, soaCollectionView);
alpaka::wait(queue);

// Transpose the data from SoA to AoS
aosCollection.transpose<Acc1D>(soaCollection, queue);
alpaka::wait(queue);

// Check the results on host
PortableHostCollection<AoS> aosCollectionHost(elems, queue);
const AoSConstView& aosCollectionHostView = aosCollectionHost.const_view();

alpaka::memcpy(queue, aosCollectionHost.buffer(), aosCollection.buffer());
alpaka::wait(queue);

for (size_t i = 0; i < elems; i++) {
REQUIRE_THAT(aosCollectionHostView[i].x(),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 0.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(aosCollectionHostView[i].y(),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 1.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(aosCollectionHostView[i].z(),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 2.0f * static_cast<float>(elems), 1.e-6));

REQUIRE_THAT(aosCollectionHostView[i].exampleVector()(0),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 3.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(aosCollectionHostView[i].exampleVector()(1),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 4.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(aosCollectionHostView[i].exampleVector()(2),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 5.0f * static_cast<float>(elems), 1.e-6));
}
REQUIRE(aosCollectionHostView.s1() == 1);
REQUIRE_THAT(aosCollectionHostView.s2(), Catch::Matchers::WithinAbs(2.0f, 1.e-6));
REQUIRE_THAT(aosCollectionHostView.s3(), Catch::Matchers::WithinAbs(3.0f, 1.e-6));

// Transpose the data back from AoS to SoA
PortableCollection<SoA, Device> soaCollection2(elems, queue);

soaCollection2.transpose<Acc1D>(aosCollection, queue);
alpaka::wait(queue);

// Check the results on host
PortableHostCollection<SoA> soaCollectionHost2(elems, queue);
const SoAConstView& soaCollectionHost2View = soaCollectionHost2.const_view();

alpaka::memcpy(queue, soaCollectionHost2.buffer(), soaCollection2.buffer());
alpaka::wait(queue);

for (size_t i = 0; i < elems; i++) {
REQUIRE_THAT(soaCollectionHost2View[i].x(),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 0.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(soaCollectionHost2View[i].y(),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 1.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(soaCollectionHost2View[i].z(),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 2.0f * static_cast<float>(elems), 1.e-6));

REQUIRE_THAT(soaCollectionHost2View[i].exampleVector()(0),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 3.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(soaCollectionHost2View[i].exampleVector()(1),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 4.0f * static_cast<float>(elems), 1.e-6));
REQUIRE_THAT(soaCollectionHost2View[i].exampleVector()(2),
Catch::Matchers::WithinAbs(static_cast<float>(i) + 5.0f * static_cast<float>(elems), 1.e-6));
}
REQUIRE(soaCollectionHost2View.s1() == 1);
REQUIRE_THAT(soaCollectionHost2View.s2(), Catch::Matchers::WithinAbs(2.0f, 1.e-6));
REQUIRE_THAT(soaCollectionHost2View.s3(), Catch::Matchers::WithinAbs(3.0f, 1.e-6));
REQUIRE_THAT(soaCollectionHost2View.s3(), Catch::Matchers::WithinAbs(3.0f, 1.e-6));
}
}
10 changes: 10 additions & 0 deletions DataFormats/SoATemplate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ are supported. Scalar members are members of layout with one element, irrespecti
Static utility functions automatically compute the byte size of a layout, taking into account all its columns and
alignment.

## AoSWrapper
`AoSWrapper` is a minimal Array-of-Structures companion to a SoA layout. It mirrors the SoA’s constructors and data
accessors so existing code can be written the same way, while keeping distinct types
(e.g. `SoA::ConstView` ≠ `SoA::AoSWrapper::ConstView`). It exists to enable easy interoperability with
`Portable{Host,Device}Collection`.

A two-way transposition between AoS and SoA is provided by `void transpose(...)` for both `SoA::View` and
`SoA::AoSWrapper::View`. This function is invoked by the `PortableCollection`s to move data back and forth reliably,
preserving a one-to-one mapping between elements and scalars.

## View

Layout classes also define a `View` and `ConstView` subclass that provide access to each column and
Expand Down
72 changes: 72 additions & 0 deletions DataFormats/SoATemplate/interface/SoACommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,60 @@ namespace cms::soa {
static constexpr byte_size_type defaultSize = NvidiaGPU;
};

// Proxy structs for SoA-like accessors with AoS layout
template <auto Member, class ValueElement>
struct ColumnProxy {
using value_element = ValueElement;

SOA_HOST_DEVICE SOA_INLINE ColumnProxy(value_element* val, size_type len) : proxy_(val, len) {}

// Reference/proxy to the field of the i-th element
SOA_HOST_DEVICE SOA_INLINE auto operator[](size_type i) -> decltype(auto) { return (proxy_[i].*Member); }

// const version
SOA_HOST_DEVICE SOA_INLINE auto operator[](size_type i) const -> decltype(auto) { return (proxy_[i].*Member); }

SOA_HOST_DEVICE SOA_INLINE size_type size() const { return proxy_.size(); }

private:
// value_element* base = nullptr;
// size_type length = 0;
std::span<value_element> proxy_;
};

// Proxy structs for const SoA-like accessors with AoS layout
template <auto Member, class ValueElement>
struct ConstColumnProxy {
using value_element = ValueElement;

SOA_HOST_DEVICE SOA_INLINE ConstColumnProxy(const value_element* val, size_type len) : proxy_(val, len) {}

SOA_HOST_DEVICE SOA_INLINE auto operator[](size_type i) const -> decltype(auto) { return (proxy_[i].*Member); }

SOA_HOST_DEVICE SOA_INLINE size_type size() const { return proxy_.size(); }

private:
// const value_element* proxy_ = nullptr;
// size_type length = 0;
std::span<const value_element> proxy_;
};

template <auto Member>
struct AoSMember {
template <typename ValueElement>
struct AoSElement {
using Column = ColumnProxy<Member, ValueElement>;
};
};

template <auto Member>
struct AoSConstMember {
template <typename ValueElement>
struct AoSElement {
using ConstColumn = ConstColumnProxy<Member, ValueElement>;
};
};

} // namespace cms::soa

// Small wrapper for stream insertion of SoA printing
Expand Down Expand Up @@ -896,6 +950,24 @@ namespace cms::soa::detail {
}
};

template <typename ColumnType>
struct AccumulateAoSByteSizes;

template <typename T>
struct AccumulateAoSByteSizes<cms::soa::SoAParametersImpl<cms::soa::SoAColumnType::scalar, T>> {
cms::soa::byte_size_type operator()() const { return sizeof(T); }
};

template <typename T>
struct AccumulateAoSByteSizes<cms::soa::SoAParametersImpl<cms::soa::SoAColumnType::column, T>> {
cms::soa::byte_size_type operator()() const { return 0; }
};

template <typename T>
struct AccumulateAoSByteSizes<cms::soa::SoAParametersImpl<cms::soa::SoAColumnType::eigen, T>> {
cms::soa::byte_size_type operator()() const { return 0; }
};

// Helper functions for computing the pitch of each column
template <typename T>
SOA_HOST_DEVICE constexpr cms::soa::byte_size_type computePitch(
Expand Down
Loading