cuda::experimental::coop::group_view#
namespace cuda::experimental::coop {
template <typename Unit, typename Group>
class group_view : public /*group-interface-for*/<group_view>
{
public:
using unit_type = Unit;
using level_type = typename Group::level_type;
using hierarchy_type = typename Group::hierarchy_type;
group_view() = delete;
__device__ group_view(const Group& group) noexcept
requires cuda::std::same_as<Unit, typename Group::unit_type>;
__device__ explicit group_view(const Unit& unit, const Group& group) noexcept;
template <typename OtherUnit>
requires /*unit-same-as-or-below*/<Unit, OtherUnit>
__device__ explicit group_view(const Unit& unit, const group_view<OtherUnit, Group>& other) noexcept;
group_view(const group_view&) = default;
group_view(group_view&&) = default;
group_view& operator=(const group_view&) = default;
group_view& operator=(group_view&&) = default;
};
template <group Group>
group_view(const Group&) -> group_view<typename Group::unit_type, Group>;
template <typename Unit, class Group>
group_view(const group_view<Unit, Group>&) -> group_view<Unit, Group>;
template </*hierarchy-level-type*/ Unit, group Group>
group_view(const Unit&, const Group&) -> group_view<Unit, Group>;
template </*hierarchy-level-type*/ Unit, class OtherUnit, class Group>
requires /*unit-same-as-or-below*/<Unit, OtherUnit>
group_view(const Unit&, const group_view<OtherUnit, Group>&) -> group_view<Unit, Group>;
} // namespace cuda::experimental::coop
Overview#
cuda::experimental::coop::group_view provides a non-owning view over another group that also allows you to view a group as if it had its unit_type changed. These properties can be useful when you need to pass a group by value or need more fine-grained rank addressing.
cuda::experimental::coop::group_view has the same mapping-related properties as the viewed group, unless the unit type was changed. In that case, the (static) unit count and unit rank are recalculated.
cuda::experimental::coop::group_view uses the viewed group’s synchronization mechanism.
Queries#
cuda::experimental::coop::group_view provides the exact same queries as the viewed group, unless the unit type was changed. In that case, the sub-unit queries are limited to the group view’s unit type.
Examples#
#include <cuda/devices>
#include <cuda/stream>
#include <cuda/hierarchy>
#include <cuda/launch>
#include <cuda/std/cassert>
#include <cuda/std/cstdint>
#include <cuda/experimental/coop/group>
namespace cudax = cuda::experimental;
// The shuffle algorithm exchanges values among units within a given group.
// An overload for "this" groups (the unit type is the same as the level type) is basically useless,
// because there is just 1 unit within the group.
template <typename Group>
requires cuda::std::same_as<typename Group::unit_type, typename Group::level_type>
__device__ int shuffle(const Group& group, int value, cuda::std::uint32_t src_rank)
{
assert(src_rank == 0);
return value;
}
// What we need is an overload that takes a group whose unit and level types are not the same.
// This overload implements shuffling values among threads within a warp.
template <typename Group>
requires cuda::std::same_as<typename Group::unit_type, cuda::thread_level>
&& cuda::std::same_as<typename Group::level_type, cuda::warp_level>
__device__ int shuffle(const Group& group, int value, cuda::std::uint32_t src_rank)
{
// Because this overload takes the group as const&, we can't use it in a constant expression
// before C++23. This is when group_view can be handy. We can create a view over the group
// that can be used in a constant expression.
const cudax::coop::group_view view{group};
constexpr auto unit_count = cuda::gpu_thread.static_count(view);
// Allocate shared memory for data exchange.
__shared__ int smem[unit_count];
// Write the unit's data to the shared memory.
smem[cuda::gpu_thread.rank(view)] = value;
// Wait until the write is done.
group.sync_aligned();
// Return the result.
return smem[src_rank];
}
__device__ void demo(auto config)
{
// Create a group that holds a warp.
cudax::coop::this_warp g{config};
// Using g directly in the shuffle algorithm doesn't give us what we need. Only rank 0 can
// be passed as the source rank.
const auto result1 = shuffle(g, /*value*/ 0xabcdef, /*src_rank*/ 0);
assert(result1 == 0xabcdef);
// What we need is to treat g as a group of threads. That's where group_view can save us. We
// needn't create another group that could require new synchronization resources; we can
// just change the unit type.
const auto result2 = shuffle(cudax::coop::group_view{cuda::gpu_thread, g},
/*value*/ cuda::gpu_thread.rank(g) * 200,
/*src_rank*/ (cuda::gpu_thread.rank(g) + 10) % cuda::gpu_thread.count(g));
assert(result2 == ((cuda::gpu_thread.rank(g) + 10) % cuda::gpu_thread.count(g)) * 200);
}
struct Kernel
{
__device__ void operator()(auto config) const
{
demo(config);
}
};
int main()
{
// Select a device and create a stream for it.
const auto device = cuda::devices[0];
cuda::stream stream{device};
// Launch the test kernel on the stream.
const auto config = cuda::make_config(cuda::grid_dims(1), cuda::block_dims<32>());
cuda::launch(stream, config, Kernel{});
// Wait until the kernel finishes.
stream.sync();
}