CUTLASS 3.4.0 (#1286)
* CUTLASS 3.4.0 * Update CHANGELOG.md --------- Co-authored-by: Pradeep Ramani <prramani@nvidia.com>
This commit is contained in:
co-authored by
Pradeep Ramani
parent
b7508e3379
commit
8236f30675
@@ -95,6 +95,7 @@ CUTE_DEVICE dim3 cluster_grid_dims()
|
||||
return gridDim;
|
||||
#elif defined(_MSC_VER)
|
||||
CUTE_RUNTIME_ASSERT("cluster_grid_dims() can only be called on device");
|
||||
return {0, 0, 0};
|
||||
#else
|
||||
return {0, 0, 0};
|
||||
#endif
|
||||
@@ -114,6 +115,7 @@ CUTE_DEVICE dim3 cluster_id_in_grid()
|
||||
return blockIdx;
|
||||
#elif defined(_MSC_VER)
|
||||
CUTE_RUNTIME_ASSERT("cluster_id_in_grid() can only be called on device");
|
||||
return {0, 0, 0};
|
||||
#else
|
||||
return {0, 0, 0};
|
||||
#endif
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
# define CUTE_ARCH_TMA_SM90_ENABLED
|
||||
#endif
|
||||
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED) && \
|
||||
((__CUDACC_VER_MAJOR__ > 12) || ((__CUDACC_VER_MAJOR__ == 12) && (__CUDACC_VER_MINOR__ >= 3)))
|
||||
# define CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED
|
||||
#endif
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include <cute/container/alignment.hpp>
|
||||
#include <cute/container/bit_field.hpp>
|
||||
#include <cute/container/array.hpp>
|
||||
#include <cute/numeric/int.hpp> // to_Format<[u]intX>
|
||||
#include <cute/numeric/half.hpp> // to_Format<half_t>
|
||||
|
||||
@@ -200,6 +201,141 @@ prefetch_tma_descriptor(TmaDescriptor const* desc_ptr)
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Perform a TensorMap modification (by each field)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Replace tensor pointer directly in GMEM
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
tma_descriptor_replace_addr_in_global_mem(TmaDescriptor const* desc_ptr,
|
||||
void const* const new_tensor_ptr)
|
||||
{
|
||||
#if defined(CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint64_t const new_desc_addr = reinterpret_cast<uint64_t>(new_tensor_ptr);
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_address.global.b1024.b64 [%0], %1;"
|
||||
:: "l"(gmem_int_desc), "l"(new_desc_addr));
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Using TMA Descriptor modification without CUTE_ARCH_TMA_SM90_ENABLED and CUDA 12.3");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Replace tensor pointer by bringing the tensormap from GMEM into the shared memory
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
tma_descriptor_replace_addr_in_shared_mem(TmaDescriptor& smem_desc,
|
||||
void const* const new_tensor_ptr)
|
||||
{
|
||||
#if defined(CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED)
|
||||
uint32_t smem_int_desc = cast_smem_ptr_to_uint(&smem_desc);
|
||||
uint64_t const new_desc_addr = reinterpret_cast<uint64_t>(new_tensor_ptr);
|
||||
uint64_t const smem_int64_desc = 0;
|
||||
asm volatile (
|
||||
"cvt.u64.u32 %0, %1;"
|
||||
:: "l"(smem_int64_desc), "r"(smem_int_desc));
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_address.shared::cta.b1024.b64 [%0], %1;"
|
||||
:: "l"(smem_int64_desc), "l"(new_desc_addr));
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Using TMA Descriptor modification without CUTE_ARCH_TMA_SM90_ENABLED and CUDA 12.3");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Replace tensor dims and strides for GEMMs by bringing the tensormap from GMEM into the shared memory
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
tma_descriptor_replace_dims_strides_in_shared_mem(TmaDescriptor & smem_desc,
|
||||
cute::array<uint32_t, 3> const& prob_shape,
|
||||
cute::array<uint64_t, 3> const& prob_stride)
|
||||
{
|
||||
#if defined(CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED)
|
||||
uint32_t smem_int_desc = cast_smem_ptr_to_uint(&smem_desc);
|
||||
uint64_t const smem_int64_desc = 0;
|
||||
asm volatile (
|
||||
"cvt.u64.u32 %0, %1;"
|
||||
:: "l"(smem_int64_desc), "r"(smem_int_desc));
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_dim.shared::cta.b1024.b32 [%0], 0, %1;"
|
||||
:: "l"(smem_int64_desc), "r"(prob_shape[0]));
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_dim.shared::cta.b1024.b32 [%0], 1, %1;"
|
||||
:: "l"(smem_int64_desc), "r"(prob_shape[1]));
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_dim.shared::cta.b1024.b32 [%0], 2, %1;"
|
||||
:: "l"(smem_int64_desc), "r"(prob_shape[2]));
|
||||
// Strides must be a multiple of 16. Also, stride for the intermost dimension is implicitly 1
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_stride.shared::cta.b1024.b64 [%0], 0, %1;"
|
||||
:: "l"(smem_int64_desc), "l"(prob_stride[1] >> 4));
|
||||
asm volatile (
|
||||
"tensormap.replace.tile.global_stride.shared::cta.b1024.b64 [%0], 1, %1;"
|
||||
:: "l"(smem_int64_desc), "l"(prob_stride[2] >> 4));
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Using TMA Descriptor modification without CUTE_ARCH_TMA_SM90_ENABLED and CUDA 12.3");
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Perform a fused copy and fence operation (needed when modifying tensormap in shared memory)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
tma_descriptor_cp_fence_release(TmaDescriptor const* gmem_desc_ptr, TmaDescriptor& smem_desc)
|
||||
{
|
||||
#if defined(CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(gmem_desc_ptr);
|
||||
uint32_t smem_int_desc = cast_smem_ptr_to_uint(&smem_desc);
|
||||
asm volatile (
|
||||
"tensormap.cp_fenceproxy.global.shared::cta.tensormap::generic.release.gpu.sync.aligned [%0], [%1], 128;"
|
||||
:: "l"(gmem_int_desc), "r"(smem_int_desc));
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Using TMA Descriptor modification without CUTE_ARCH_TMA_SM90_ENABLED and CUDA 12.3");
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Perform a release fence operation (needed when modifying tensormap directly in GMEM)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
tma_descriptor_fence_release()
|
||||
{
|
||||
#if defined(CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED)
|
||||
asm volatile ("fence.proxy.tensormap::generic.release.gpu;");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Using TMA Descriptor modification without CUTE_ARCH_TMA_SM90_ENABLED and CUDA 12.3");
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Perform a acquire fence operation
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
tma_descriptor_fence_acquire(TmaDescriptor const* desc_ptr)
|
||||
{
|
||||
#if defined(CUTE_ARCH_DEVICE_MODIFIABLE_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
asm volatile (
|
||||
"fence.proxy.tensormap::generic.acquire.gpu [%0], 128;"
|
||||
:
|
||||
: "l"(gmem_int_desc)
|
||||
: "memory");
|
||||
asm volatile (
|
||||
"cvta.global.u64 %0, %0;"
|
||||
:
|
||||
: "l"(gmem_int_desc), "l"(gmem_int_desc)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Using TMA Descriptor modification without CUTE_ARCH_TMA_SM90_ENABLED and CUDA 12.3");
|
||||
#endif
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -775,6 +775,104 @@ struct SM90_TMA_STORE
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// TMA_STORE im2col: Initiates a TMA copy, in im2col mode, from shared memory to global memory
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct SM90_TMA_STORE_IM2COL_3D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_n)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.3d.global.shared::cta.im2col_no_offs.bulk_group"
|
||||
" [%0, {%2, %3, %4}], [%1];"
|
||||
:
|
||||
: "l"(gmem_int_desc), "r"(smem_int_ptr),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_n)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_STORE_IM2COL_4D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_n)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.4d.global.shared::cta.im2col_no_offs.bulk_group"
|
||||
" [%0, {%2, %3, %4, %5}], [%1];"
|
||||
:
|
||||
: "l"(gmem_int_desc), "r"(smem_int_ptr),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_h), "r"(coord_n)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_STORE_IM2COL_5D
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_d, int32_t const& coord_n)
|
||||
{
|
||||
#if defined(CUTE_ARCH_TMA_SM90_ENABLED)
|
||||
uint64_t gmem_int_desc = reinterpret_cast<uint64_t>(desc_ptr);
|
||||
uint32_t smem_int_ptr = cast_smem_ptr_to_uint(smem_ptr);
|
||||
asm volatile (
|
||||
"cp.async.bulk.tensor.5d.global.shared::cta.im2col_no_offs.bulk_group"
|
||||
" [%0, {%2, %3, %4, %5, %6}], [%1];"
|
||||
:
|
||||
: "l"(gmem_int_desc), "r"(smem_int_ptr),
|
||||
"r"(coord_c), "r"(coord_w), "r"(coord_h), "r"(coord_d), "r"(coord_n)
|
||||
: "memory");
|
||||
#else
|
||||
CUTE_RUNTIME_ASSERT("Trying to use tma without CUTE_ARCH_TMA_SM90_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
struct SM90_TMA_STORE_IM2COL
|
||||
{
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_n)
|
||||
{
|
||||
return SM90_TMA_STORE_IM2COL_3D::copy(desc_ptr, smem_ptr, coord_c, coord_w, coord_n);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_n)
|
||||
{
|
||||
return SM90_TMA_STORE_IM2COL_4D::copy(desc_ptr, smem_ptr, coord_c, coord_w, coord_h, coord_n);
|
||||
}
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(void const* const desc_ptr,
|
||||
void const* const smem_ptr,
|
||||
int32_t const& coord_c, int32_t const& coord_w, int32_t const& coord_h, int32_t const& coord_d, int32_t const& coord_n)
|
||||
{
|
||||
return SM90_TMA_STORE_IM2COL_5D::copy(desc_ptr, smem_ptr, coord_c, coord_w, coord_h, coord_d, coord_n);
|
||||
}
|
||||
};
|
||||
|
||||
// Indicate arrival of warp issuing TMA_STORE
|
||||
CUTE_HOST_DEVICE static void
|
||||
tma_store_arrive() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**************************************************************************************************
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2023 - 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
|
||||
@@ -31,26 +31,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/copy.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
#include <cute/atom/mma_atom.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <class... Args>
|
||||
struct Copy_Atom;
|
||||
|
||||
template <class CopyOperation, class T>
|
||||
struct Copy_Atom<CopyOperation, T> : Copy_Atom<Copy_Traits<CopyOperation>, T>
|
||||
template <class CopyOperation, class CopyInternalType>
|
||||
struct Copy_Atom<CopyOperation, CopyInternalType> : Copy_Atom<Copy_Traits<CopyOperation>, CopyInternalType>
|
||||
{};
|
||||
|
||||
template <class... Args, class T>
|
||||
struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
template <class... Args, class CopyInternalType>
|
||||
struct Copy_Atom<Copy_Traits<Args...>, CopyInternalType>
|
||||
: Copy_Traits<Args...>
|
||||
{
|
||||
using Traits = Copy_Traits<Args...>;
|
||||
@@ -61,7 +63,7 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
using BitLayoutDst = typename Traits::DstLayout;
|
||||
using BitLayoutRef = typename Traits::RefLayout;
|
||||
|
||||
using ValType = T;
|
||||
using ValType = CopyInternalType;
|
||||
|
||||
using ValLayoutSrc = decltype(upcast<sizeof_bits<ValType>::value>(BitLayoutSrc{}));
|
||||
using ValLayoutDst = decltype(upcast<sizeof_bits<ValType>::value>(BitLayoutDst{}));
|
||||
@@ -80,7 +82,7 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
auto
|
||||
with(TraitsArgs&&... args) const {
|
||||
auto traits = Traits::with(std::forward<TraitsArgs>(args)...);
|
||||
return Copy_Atom<decltype(traits), T>{traits};
|
||||
return Copy_Atom<decltype(traits), CopyInternalType>{traits};
|
||||
}
|
||||
|
||||
//
|
||||
@@ -88,19 +90,19 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
//
|
||||
|
||||
// Check and call instruction, or recurse
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
template <class SEngine, class SLayout,
|
||||
class DEngine, class DLayout>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
call(Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst) const
|
||||
call(Tensor<SEngine,SLayout> const& src,
|
||||
Tensor<DEngine,DLayout> & dst) const
|
||||
{
|
||||
static_assert(SLayout::rank == 1, "Expected rank-1 src tensor");
|
||||
static_assert(DLayout::rank == 1, "Expected rank-1 dst tensor");
|
||||
|
||||
if constexpr (is_constant<NumValSrc, decltype(size(src))>::value ||
|
||||
is_constant<NumValDst, decltype(size(dst))>::value) {
|
||||
// Dispatch to unpack for instruction
|
||||
// Dispatch to unpack to execute instruction
|
||||
return copy_unpack(*this, src, dst);
|
||||
} else
|
||||
if constexpr (is_tuple<decltype(shape(src))>::value &&
|
||||
@@ -110,7 +112,7 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
// ((A,B,C,...)) -> (A,B,C,...)
|
||||
return copy(*this, tensor<0>(src), tensor<0>(dst));
|
||||
} else {
|
||||
static_assert(sizeof(TS) < 0, "No instruction match and no recursion possible.");
|
||||
static_assert(dependent_false<SEngine>, "No instruction match and no recursion possible.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +137,7 @@ struct ThrCopy;
|
||||
|
||||
template <class Copy_Atom,
|
||||
class LayoutCopy_TV, // (tid,vid) -> coord [Need not be 2D...]
|
||||
class ShapeTile_MN> // coord space
|
||||
class ShapeTiler_MN> // coord space
|
||||
struct TiledCopy : Copy_Atom
|
||||
{
|
||||
// Layout information from the CopyAtom
|
||||
@@ -148,8 +150,7 @@ struct TiledCopy : Copy_Atom
|
||||
using AtomNumVal = decltype(size<1>(AtomLayoutRef{}));
|
||||
|
||||
// Layout information for the TiledCopy
|
||||
using Tiler_MN = ShapeTile_MN;
|
||||
using TiledShape_MN = decltype(shape(ShapeTile_MN{}));
|
||||
using Tiler_MN = ShapeTiler_MN;
|
||||
using TiledLayout_TV = LayoutCopy_TV;
|
||||
using TiledNumThr = decltype(size<0>(TiledLayout_TV{}));
|
||||
using TiledNumVal = decltype(size<1>(TiledLayout_TV{}));
|
||||
@@ -172,12 +173,9 @@ struct TiledCopy : Copy_Atom
|
||||
auto
|
||||
tidfrg_S(STensor&& stensor)
|
||||
{
|
||||
constexpr int R = remove_cvref_t<STensor>::rank;
|
||||
static_assert(R >= rank_v<TiledShape_MN>, "Rank of tensor to be partitioned too small.");
|
||||
// Generalize the dimension checks for arbitrary rank
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(stensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(stensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
CUTE_STATIC_ASSERT_V(rank(stensor) >= rank(Tiler_MN{}), "Rank of tensor to be partitioned too small.");
|
||||
|
||||
// Tile the stensor and compute the (src-thr, src-val) -> (ref-thr, ref-val) layout
|
||||
return tile2thrfrg(zipped_divide(stensor,Tiler_MN{}), right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}));
|
||||
}
|
||||
|
||||
@@ -196,17 +194,14 @@ struct TiledCopy : Copy_Atom
|
||||
auto
|
||||
tidfrg_D(DTensor&& dtensor)
|
||||
{
|
||||
constexpr int R = remove_cvref_t<DTensor>::rank;
|
||||
static_assert(R >= rank_v<TiledShape_MN>, "Rank of tensor to be partitioned too small.");
|
||||
// Generalize the dimension checks for arbitrary rank
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(stensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(stensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
CUTE_STATIC_ASSERT_V(rank(dtensor) >= rank(Tiler_MN{}), "Rank of tensor to be partitioned too small.");
|
||||
|
||||
// Tile the dtensor and compute the (dst-thr, dst-val) -> (ref-thr, ref-val) layout
|
||||
return tile2thrfrg(zipped_divide(dtensor,Tiler_MN{}), right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}));
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
// (Tile,(RestM,RestN,...))
|
||||
// ((TileM,TileN,...), (RestM,RestN,...))
|
||||
// to shape
|
||||
// ((ThrV,ThrX),FrgV,(RestM,RestN,...))
|
||||
template <class Tensor, class Ref2TrgLayout>
|
||||
@@ -232,7 +227,7 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
// Transform the tile mode
|
||||
auto tv_tensor = tensor.compose(thrval2mn, _);
|
||||
// ((thrid,val),(RM,RN,...))
|
||||
// ((thrid,val),(RestM,RestN,...))
|
||||
|
||||
// Unfold and return
|
||||
return tv_tensor(make_coord(_,_), _);
|
||||
@@ -253,7 +248,7 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
auto V = size<0>(tensor);
|
||||
|
||||
auto frg_layout_mn = upcast<TiledNumThr{} * V>(right_inverse(TiledLayout_TV{}).with_shape(TiledShape_MN{}));
|
||||
auto frg_layout_mn = upcast<TiledNumThr{} * V>(right_inverse(TiledLayout_TV{}).with_shape(shape(Tiler_MN{})));
|
||||
// (m,n) -> v_idx -- The shape and order of the V inside of TiledLayout_TV
|
||||
|
||||
auto frg_layout_v = zipped_divide(logical_product(make_layout(V), right_inverse(frg_layout_mn)), make_layout(AtomNumVal{}));
|
||||
@@ -278,7 +273,7 @@ struct TiledCopy : Copy_Atom
|
||||
get_layoutS_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_S = make_layout(make_shape(TiledShape_MN{}, Int<1>{}));
|
||||
auto ref_S = make_layout(make_shape(shape(Tiler_MN{}), Int<1>{}));
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
return tile2thrfrg(ref_S, right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}))(_,_,Int<0>{});
|
||||
}
|
||||
@@ -290,7 +285,7 @@ struct TiledCopy : Copy_Atom
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutS_TV = get_layoutS_TV();
|
||||
// (M,K) -> (thr_idx,val_idx)
|
||||
auto layoutS_MK = right_inverse(layoutS_TV).with_shape(TiledShape_MN{});
|
||||
auto layoutS_MK = right_inverse(layoutS_TV).with_shape(shape(Tiler_MN{}));
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_S = make_layout(size<0>(TiledLayout_TV{}));
|
||||
@@ -303,7 +298,7 @@ struct TiledCopy : Copy_Atom
|
||||
get_layoutD_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_D = make_layout(make_shape(TiledShape_MN{}, Int<1>{}));
|
||||
auto ref_D = make_layout(make_shape(shape(Tiler_MN{}), Int<1>{}));
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
return tile2thrfrg(ref_D, right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}))(_,_,Int<0>{});
|
||||
}
|
||||
@@ -315,7 +310,7 @@ struct TiledCopy : Copy_Atom
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutD_TV = get_layoutD_TV();
|
||||
// (M,K) -> (thr_idx,val_idx)
|
||||
auto layoutD_MK = right_inverse(layoutD_TV).with_shape(TiledShape_MN{});
|
||||
auto layoutD_MK = right_inverse(layoutD_TV).with_shape(shape(Tiler_MN{}));
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_D = make_layout(size<0>(TiledLayout_TV{}));
|
||||
@@ -406,51 +401,44 @@ make_tiled_copy_impl(Copy_Atom<Args...> const& atom,
|
||||
// These tile the Copy_Atom as a whole
|
||||
//
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
template <class... CArgs, class... MArgs>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_A(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
make_tiled_copy_A(Copy_Atom<CArgs...> const& copy_atom,
|
||||
TiledMMA<MArgs...> const& mma)
|
||||
{
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
return make_tiled_copy_impl(copy_atom, tiled_mma.get_layoutA_TV(), make_shape(size<0>(MNK{}),size<2>(MNK{})));
|
||||
return make_tiled_copy_impl(copy_atom, mma.get_layoutA_TV(), make_shape(tile_size<0>(mma),tile_size<2>(mma)));
|
||||
}
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
template <class... CArgs, class... MArgs>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_B(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
make_tiled_copy_B(Copy_Atom<CArgs...> const& copy_atom,
|
||||
TiledMMA<MArgs...> const& mma)
|
||||
{
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
return make_tiled_copy_impl(copy_atom, tiled_mma.get_layoutB_TV(), make_shape(size<1>(MNK{}),size<2>(MNK{})));
|
||||
return make_tiled_copy_impl(copy_atom, mma.get_layoutB_TV(), make_shape(tile_size<1>(mma),tile_size<2>(mma)));
|
||||
}
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
template <class... CArgs, class... MArgs>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_C(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
make_tiled_copy_C(Copy_Atom<CArgs...> const& copy_atom,
|
||||
TiledMMA<MArgs...> const& mma)
|
||||
{
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
return make_tiled_copy_impl(copy_atom, tiled_mma.get_layoutC_TV(), make_shape(size<0>(MNK{}),size<1>(MNK{})));
|
||||
return make_tiled_copy_impl(copy_atom, mma.get_layoutC_TV(), make_shape(tile_size<0>(mma),tile_size<1>(mma)));
|
||||
}
|
||||
|
||||
// returns the smallest tiled copy that can retile LayoutC_TV
|
||||
// for use with pipelined epilogues with subtiled stores
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
template <class... CArgs, class... MArgs>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_C_atom(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
make_tiled_copy_C_atom(Copy_Atom<CArgs...> const& copy_atom,
|
||||
TiledMMA<MArgs...> const& mma)
|
||||
{
|
||||
// Truncate the V-layout to just the Copy_Atom, keep the V-order
|
||||
auto layoutC_TV = tiled_mma.get_layoutC_TV();
|
||||
auto copy_V = Int<Copy_Atom<Args...>::NumValSrc>{};
|
||||
auto layoutC_TV = mma.get_layoutC_TV();
|
||||
auto copy_V = Int<Copy_Atom<CArgs...>::NumValSrc>{};
|
||||
CUTE_STATIC_ASSERT_V(copy_V <= size<1>(layoutC_TV));
|
||||
auto layout_TV = composition(layoutC_TV, make_layout(make_shape(size<0>(layoutC_TV), copy_V)));
|
||||
|
||||
@@ -458,8 +446,7 @@ make_tiled_copy_C_atom(Copy_Atom<Args...> const& copy_atom,
|
||||
|
||||
// Tiler -- Find the active elements in the MMA tensor and generate a tiler to extract them
|
||||
// Convert to the awkward by-mode tiler to preserve the modes of the tiled MMA
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
auto mma_tiler = make_shape(size<0>(MNK{}),size<1>(MNK{}));
|
||||
auto mma_tiler = make_shape(tile_size<0>(mma),tile_size<1>(mma));
|
||||
auto mma_zeros = repeat_like(mma_tiler, Int<0>{});
|
||||
|
||||
auto tiler = transform(make_seq<rank(mma_tiler)>{}, [&](auto i) {
|
||||
@@ -474,8 +461,6 @@ make_tiled_copy_C_atom(Copy_Atom<Args...> const& copy_atom,
|
||||
// (tid,vid) -> tile_coord
|
||||
auto layout_tv = composition(left_inverse(tile2mma), layout_TV);
|
||||
|
||||
|
||||
using MNK = typename TiledMMA::TiledShape_MNK;
|
||||
return make_tiled_copy_impl(copy_atom, layout_tv, tiler);
|
||||
}
|
||||
|
||||
@@ -655,8 +640,10 @@ print(TiledCopy<Atom, Args...> const& copy, char const* pad = "")
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(ThrCopy<TiledCopy, ThrIdx> const&)
|
||||
print(ThrCopy<TiledCopy, ThrIdx> const& thr_copy)
|
||||
{
|
||||
print("ThrCopy\n");
|
||||
print(" ThrIdx: "); print(thr_copy.thr_idx_); print("\n");
|
||||
print(TiledCopy{});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,14 @@
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <class GmemStrides_, class TmaGBasis_, class TmaSwizzle_>
|
||||
template <class GmemTmaBasisStrides_, class TmaGmemBasis_, class TmaSwizzle_>
|
||||
struct AuxTmaParams {
|
||||
using GmemStrides = GmemStrides_;
|
||||
using GmemStrides = GmemTmaBasisStrides_; // Strides for Gmem mode -> Tma coord mode, may be dynamic
|
||||
GmemStrides g_stride_;
|
||||
using TmaGmemBasis = TmaGmemBasis_; // Layout for Tma box shape -> Gmem mode(s), always static
|
||||
static_assert(is_static<TmaGmemBasis>::value);
|
||||
using TmaSwizzle = TmaSwizzle_; // Tma swizzle, always Swizzle<B,M,S>
|
||||
static_assert(is_static<TmaSwizzle>::value);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -138,13 +142,19 @@ struct Copy_Traits<SM90_TMA_LOAD, NumBitsPerTMA, AuxParams_>
|
||||
// Construct an executable SM90_TMA_LOAD with tma_mbar
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_TMA_LOAD_OP, NumBitsPerTMA>
|
||||
with(uint64_t& tma_mbar, uint16_t const& multicast_mask = 0) const {
|
||||
with(uint64_t& tma_mbar, [[maybe_unused]] uint16_t const& multicast_mask = 0) const {
|
||||
// We accept multicast_mask here to keep the API for both atoms consistent
|
||||
// assert(multicast_mask == 0);
|
||||
(void) multicast_mask;
|
||||
return {tma_desc_, tma_mbar};
|
||||
}
|
||||
|
||||
// Construct an executable SM90_TMA_LOAD with tma_mbar (temp. overloaded for grouped gemm/ptr array gemm)
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_TMA_LOAD_OP, NumBitsPerTMA>
|
||||
with(TmaDescriptor const* new_tma_desc, uint64_t& tma_mbar, [[maybe_unused]] uint16_t const& multicast_mask = 0) const {
|
||||
// We accept multicast_mask here to keep the API for both atoms consistent
|
||||
return {*new_tma_desc, tma_mbar};
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -251,6 +261,13 @@ struct Copy_Traits<SM90_TMA_LOAD_MULTICAST, NumBitsPerTMA, AuxParams_>
|
||||
return {tma_desc_, tma_load_mbar, multicast_mask};
|
||||
}
|
||||
|
||||
// Construct an executable SM90_TMA_LOAD_MULTICAST_OP with tma_mbar (temp. overloaded for grouped gemm/ptr array gemm)
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_TMA_LOAD_MULTICAST_OP, NumBitsPerTMA>
|
||||
with(TmaDescriptor const* new_tma_desc, uint64_t& tma_load_mbar, uint16_t const& multicast_mask) const {
|
||||
return {*new_tma_desc, tma_load_mbar, multicast_mask};
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -508,51 +525,91 @@ coalesce_256(Layout<Shape,Stride> const& layout)
|
||||
return coalesce_256_impl<1>(flat_shape, flat_stride, get<0>(flat_shape), get<0>(flat_stride));
|
||||
}
|
||||
|
||||
template <class Engine, class Layout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
coalesce_256(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
return make_tensor(tensor.data(), coalesce_256(tensor.layout()));
|
||||
}
|
||||
|
||||
|
||||
// Use a smem_inv_h to read through the GMEM tensor
|
||||
// and construct a TMA Descriptor for the resulting instruction
|
||||
// At the same time, construct the Tma Tensor's Stride to generate
|
||||
// the TMA coordinates that the instruction consumes.
|
||||
//
|
||||
template <class TmaInternalType,
|
||||
class GEngine, class GLayout,
|
||||
class SShape, class SStride,
|
||||
int B, int M, int S>
|
||||
CUTE_HOST_RTC
|
||||
class VShape, class VStride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GMEM Tensor
|
||||
Layout<SShape,SStride> const& smem_inv_h, // smem_idx to hier gmode
|
||||
Swizzle<B,M,S> const& swizzle) // Swizzle fn on smem_idx
|
||||
construct_tma_gbasis(Tensor<GEngine,GLayout> const& gtensor, // The original GMEM Tensor
|
||||
Layout<SShape,SStride> const& slayout, // The layout of SMEM
|
||||
Layout<VShape,VStride> const& cta_v_map) // smem_idx to hier gmode
|
||||
{
|
||||
//
|
||||
// TMA parameter checking
|
||||
//
|
||||
|
||||
CUTE_STATIC_ASSERT_V(product_each(shape(slayout)) == product_each(shape(cta_v_map)),
|
||||
"TMA requires CTA_Tile and SLayout top-level shape equivalence.");
|
||||
|
||||
#if 0
|
||||
print("gtensor : "); print(gtensor); print("\n");
|
||||
print("slayout : "); print(slayout); print("\n");
|
||||
print("cta_v_map : "); print(cta_v_map); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA slayout manipulation
|
||||
//
|
||||
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
// smem idx -> smem coord
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_portion(slayout));
|
||||
|
||||
// Compose with the V-Map to convert smem coord (CTA val idx) to gmem mode
|
||||
// smem idx -> gmem mode
|
||||
auto sidx2gmode_full = coalesce(composition(cta_v_map, inv_smem_layout));
|
||||
|
||||
#if 0
|
||||
print("inv_smem_layout : "); print(inv_smem_layout); print("\n");
|
||||
print("sidx2gmode_full : "); print(sidx2gmode_full); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor truncation
|
||||
//
|
||||
|
||||
// Truncate any incompatibilities -- no starting in the middle of gmodes
|
||||
auto smem_rank = find_if(stride(sidx2gmode_full), [](auto e) {
|
||||
[[maybe_unused]] auto v = basis_value(e);
|
||||
return not is_constant<1,decltype(v)>{};
|
||||
});
|
||||
static_assert(smem_rank > 0, "Could not find a common tile-gmem vectorization. Does the Tile select out major GMEM modes?");
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto sidx2gmode = take<0,smem_rank>(sidx2gmode_full);
|
||||
|
||||
#if 0
|
||||
print("smem_rank : "); print(smem_rank); print("\n");
|
||||
print("sidx2gmode : "); print(sidx2gmode); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// The smem vector is the same units as gtensor, so compose first and then recast
|
||||
// tma_val_idx:gmem_strides
|
||||
Tensor tile_gstride = recast<TmaInternalType>(gtensor.compose(smem_inv_h));
|
||||
auto tile_gstride = recast<TmaInternalType>(gtensor.compose(sidx2gmode)).layout();
|
||||
// Coalesce modes up to size-256 (the maximum TMA box extent in units of TmaInternalType)
|
||||
// tma_box_shape:gmem_strides
|
||||
Tensor tma_gstride = coalesce_256(tile_gstride);
|
||||
auto tma_gstride = coalesce_256(tile_gstride);
|
||||
|
||||
// Perform the tiling to the gmem vector again, but with indirections to the gtensor modes
|
||||
// Perform the tiling, recast, and coalesce to the gmem vector again, but with indirections to the gtensor modes
|
||||
auto gbasis = make_identity_layout(shape(gtensor));
|
||||
auto tile_gbasis_tmp = gbasis.compose(smem_inv_h);
|
||||
auto tile_gbasis_tmp = gbasis.compose(sidx2gmode);
|
||||
|
||||
// Instead of the recast (gbasis doesn't have type info), replace the shape with the already-recasted shape
|
||||
// tma_box_shape:gmem_mode
|
||||
auto tile_gbasis = make_layout(shape(tile_gstride), stride(tile_gbasis_tmp));
|
||||
|
||||
// Recast the original tensor for shape inspections
|
||||
auto gtensor_T = recast<TmaInternalType>(gtensor);
|
||||
// "Coalesce" the tile basis into a compatible shape with the tma_gstride
|
||||
auto tma_gbasis_tile = tile_gbasis.compose(make_layout(wrap(shape(tma_gstride))));
|
||||
|
||||
// Recast the original tensor for shape/stride inspections
|
||||
Tensor gtensor_T = recast<TmaInternalType>(gtensor);
|
||||
|
||||
// Find missing bases that don't appear in tile_gbasis
|
||||
// NOTE This is essentially ArithmeticTuple complement...
|
||||
// NOTE in pursuit of implementing an ArithmeticTuple logical_divide for smem_inv_h
|
||||
auto tile_gbasis_remaining_stride = filter_tuple(flatten(shape (gtensor_T)), flatten(stride(gtensor_T)),
|
||||
flatten(stride(gbasis)),
|
||||
[&](auto s, auto d, auto e)
|
||||
@@ -561,7 +618,7 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
return cute::tuple<>{}; // If size-1 or stride-0, then don't append
|
||||
} else {
|
||||
using E = decltype(e);
|
||||
auto has_e = any_of(stride(tile_gbasis), [] (auto tb) { return tb == E{}; });
|
||||
auto has_e = any_of(flatten(stride(tma_gbasis_tile)), [] (auto tb) { return tb == E{}; });
|
||||
if constexpr (decltype(has_e)::value) {
|
||||
return cute::tuple<>{}; // If d was found, then don't append
|
||||
} else {
|
||||
@@ -569,13 +626,10 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
}
|
||||
}
|
||||
});
|
||||
auto tile_gbasis_remaining_rank = rank(tile_gbasis_remaining_stride);
|
||||
|
||||
// "Coalesce" the tile basis into a compatible shape with the tma
|
||||
auto tma_gbasis_tile = tile_gbasis.compose(make_layout(wrap(shape(tma_gstride))));
|
||||
|
||||
// Append the remaining basis modes that contribute to the TMA with size-1
|
||||
auto tma_gbasis_full = make_layout(tuple_cat(wrap( shape(tma_gbasis_tile)), wrap(repeat<tile_gbasis_remaining_rank>(Int<1>{}))),
|
||||
auto tile_gbasis_remaining_shape = repeat<rank(tile_gbasis_remaining_stride)>(Int<1>{});
|
||||
auto tma_gbasis_full = make_layout(tuple_cat(wrap( shape(tma_gbasis_tile)), wrap(tile_gbasis_remaining_shape )),
|
||||
tuple_cat(wrap(stride(tma_gbasis_tile)), wrap(tile_gbasis_remaining_stride)));
|
||||
|
||||
// Group the trailing modes to make this max rank-5 -- TMA rank limitation
|
||||
@@ -583,15 +637,98 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
auto tma_gbasis = group<cute::min(rank(tma_gbasis_full),4),-1>(tma_gbasis_full);
|
||||
|
||||
#if 0
|
||||
print("smem_inv_h : "); print(smem_inv_h); print("\n");
|
||||
print("gtensor : "); print(gtensor); print("\n");
|
||||
print("tile_gstride : "); print(tile_gstride); print("\n");
|
||||
print("tma_gstride : "); print(tma_gstride); print("\n");
|
||||
print("gbasis : "); print(gbasis); print("\n");
|
||||
print("tile_gbasis : "); print(tile_gbasis); print("\n");
|
||||
print("tile_gbasis : "); print(tma_gbasis_tile); print("\n");
|
||||
print("tma_gbasis : "); print(tma_gbasis); print("\n");
|
||||
#endif
|
||||
|
||||
return tma_gbasis;
|
||||
}
|
||||
|
||||
template <class GEngine, class GLayout,
|
||||
class TmaGmemBasisStride,
|
||||
class ShapeT, size_t TmaRank>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
fill_tma_gmem_shape_stride(Tensor<GEngine,GLayout> const& gtensor, // Gmem Shapes and Strides, in units of TmaInternalType
|
||||
TmaGmemBasisStride const& tma_gbasis_stride, // Map Tma mode idx -> Gmem mode(s)
|
||||
cute::array<ShapeT, TmaRank> & gmem_prob_shape, // Tma Shapes, uint32_t or uin64_t
|
||||
cute::array<uint64_t, TmaRank> & gmem_prob_stride) // Tma Strides
|
||||
{
|
||||
static_assert(is_tuple<TmaGmemBasisStride>::value);
|
||||
static_assert(is_same<uint32_t, ShapeT>::value || is_same<uint64_t, ShapeT>::value);
|
||||
|
||||
using TmaInternalType = typename GEngine::value_type;
|
||||
constexpr int tma_rank = decltype(rank(tma_gbasis_stride))::value;
|
||||
static_assert(TmaRank >= tma_rank);
|
||||
|
||||
auto gmem_shape = shape(gtensor);
|
||||
auto gmem_stride = stride(gtensor);
|
||||
// Use the indirections in tma_gbasis_stride into gtensor to construct the tma gmem shapes/strides
|
||||
for_each(make_seq<tma_rank>{}, [&](auto i) {
|
||||
constexpr int tma_i_rank = decltype(rank<i>(tma_gbasis_stride))::value;
|
||||
if constexpr (tma_i_rank == 1) {
|
||||
// Trivial contribution of this gmem mode to this tma mode
|
||||
auto ej = unwrap(get<i>(tma_gbasis_stride));
|
||||
gmem_prob_shape[i] = basis_get(ej, gmem_shape);
|
||||
gmem_prob_stride[i] = basis_get(ej, gmem_stride) * sizeof_bits_v<TmaInternalType> / 8;
|
||||
} else {
|
||||
// Apply a recurrence to each gmem mode that contributes to this tma mode
|
||||
for_each(get<i>(tma_gbasis_stride), [&](auto ej) {
|
||||
// Problem shape
|
||||
uint64_t shape_j = basis_get(ej, gmem_shape);
|
||||
// Problem stride (in bytes)
|
||||
uint64_t stride_j = basis_get(ej, gmem_stride) * sizeof_bits_v<TmaInternalType> / 8;
|
||||
|
||||
uint64_t old_stride = gmem_prob_stride[i];
|
||||
gmem_prob_stride[i] = gcd(gmem_prob_stride[i], stride_j);
|
||||
|
||||
if (gmem_prob_stride[i] != 0) {
|
||||
// Recurrence: g_shape = (s_i - 1) * (d_i / gcd_j d_j) + 1
|
||||
gmem_prob_shape[i] = (gmem_prob_shape[i]-1) * (old_stride / gmem_prob_stride[i])
|
||||
+ (shape_j-1) * (stride_j / gmem_prob_stride[i])
|
||||
+ 1;
|
||||
} else {
|
||||
gmem_prob_shape[i] = shape_j;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Overload for an existing Copy_Traits
|
||||
template <class GEngine, class GLayout,
|
||||
class Op, class Bits, class Aux,
|
||||
class ShapeT, size_t TmaRank>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
fill_tma_gmem_shape_stride(Copy_Traits<Op,Bits,Aux> const& tma_traits,
|
||||
Tensor<GEngine,GLayout> const& gtensor, // Gmem Shapes and Strides, value_type = TmaInternalType
|
||||
cute::array<ShapeT, TmaRank> & gmem_prob_shape, // Tma Shapes, uint32_t or uin64_t
|
||||
cute::array<uint64_t, TmaRank> & gmem_prob_stride) // Tma Strides
|
||||
{
|
||||
return fill_tma_gmem_shape_stride(gtensor, stride(typename Aux::TmaGmemBasis{}),
|
||||
gmem_prob_shape, gmem_prob_stride);
|
||||
}
|
||||
|
||||
// Use a sidx2gmode to read through the GMEM tensor
|
||||
// and construct a TMA Descriptor for the resulting instruction
|
||||
// At the same time, construct the Tma Tensor's Stride to generate
|
||||
// the TMA coordinates that the instruction consumes.
|
||||
//
|
||||
template <class TmaInternalType,
|
||||
class GEngine, class GLayout,
|
||||
class TShape, class TStride,
|
||||
int B, int M, int S>
|
||||
CUTE_HOST_RTC
|
||||
auto
|
||||
make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GMEM Tensor
|
||||
Layout<TShape,TStride> const& tma_gbasis, // TMA mode -> GMEM mode mapping
|
||||
Swizzle<B,M,S> const& swizzle, // Swizzle fn on smem_idx
|
||||
uint32_t num_multicast) // The number of CTAs in multicasting
|
||||
{
|
||||
//
|
||||
// TMA desc creation
|
||||
//
|
||||
@@ -602,31 +739,16 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
// TMA gmem desc info
|
||||
//
|
||||
|
||||
// Recast the original tensor for shape/stride inspections
|
||||
Tensor gtensor_T = recast<TmaInternalType>(gtensor);
|
||||
|
||||
void* gmem_address = (void*) raw_pointer_cast(gtensor_T.data());
|
||||
auto gmem_layout = gtensor_T.layout();
|
||||
|
||||
cute::array<uint64_t, 5> gmem_prob_shape = {1,1,1,1,1};
|
||||
cute::array<uint64_t, 5> gmem_prob_stride = {0,0,0,0,0};
|
||||
// Use the indirections in tma_gbasis in the values of flat_glayout to construct the gmem shapes/strides
|
||||
for_each(make_seq<tma_dim>{}, [&](auto i) {
|
||||
for_each(stride<i>(tma_gbasis), [&](auto ej) {
|
||||
// Problem stride
|
||||
uint64_t stride_j = ceil_div(basis_get(ej, stride(gmem_layout)) * sizeof_bits_v<TmaInternalType>, 8);
|
||||
uint64_t old_stride = gmem_prob_stride[i];
|
||||
gmem_prob_stride[i] = gcd(gmem_prob_stride[i], stride_j);
|
||||
|
||||
// Problem shape
|
||||
uint64_t shape_j = basis_get(ej, shape(gmem_layout));
|
||||
if (gmem_prob_stride[i] != 0) {
|
||||
// Recurrence: g_shape = (s_i - 1) * (d_i / gcd_j d_j) + 1
|
||||
gmem_prob_shape[i] = (gmem_prob_shape[i]-1) * (old_stride / gmem_prob_stride[i])
|
||||
+ (shape_j-1) * (stride_j / gmem_prob_stride[i])
|
||||
+ 1;
|
||||
} else {
|
||||
gmem_prob_shape[i] = shape_j;
|
||||
}
|
||||
});
|
||||
});
|
||||
fill_tma_gmem_shape_stride(gtensor_T, stride(tma_gbasis), gmem_prob_shape, gmem_prob_stride);
|
||||
|
||||
assert((reinterpret_cast<uint64_t>(gmem_address) & 0b1111) == 0); // Address must be 16B-aligned
|
||||
|
||||
@@ -663,6 +785,13 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
for_each(make_seq<tma_dim>{}, [&](auto i) {
|
||||
smem_box_shape[i] *= size<i>(tma_gbasis);
|
||||
});
|
||||
// Finally, truncate the tma box by the num_multicast
|
||||
for (uint32_t i = tma_dim-1, multicast = num_multicast; multicast > 1; --i) {
|
||||
assert(smem_box_shape[i] % multicast == 0 || multicast % smem_box_shape[i] == 0);
|
||||
uint32_t new_mult = ceil_div(multicast, smem_box_shape[i]);
|
||||
smem_box_shape[i] = ceil_div(smem_box_shape[i], multicast);
|
||||
multicast = new_mult;
|
||||
}
|
||||
|
||||
assert(smem_box_shape[0] >= (uint32_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint32_t(1) << 8)); // Size must be max 2^8 = 256
|
||||
@@ -740,26 +869,27 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
auto recast_ratio = cute::ratio(Int<sizeof_bits<typename GEngine::value_type>::value>{},
|
||||
Int<sizeof_bits< TmaInternalType>::value>{});
|
||||
|
||||
auto gbasis = make_basis_like(shape(gtensor));
|
||||
|
||||
// Finally, get the inverse permutation of the E<i> bases for the mocked gmem stride
|
||||
// NOTE This is essentially ArithmeticTuple inverse...
|
||||
auto gmem_stride_bases = transform_leaf(stride(gbasis), [&](auto ei) {
|
||||
auto gmem_tma_basis_stride = transform_leaf(gbasis, [&](auto ei) {
|
||||
auto si = basis_get(ei, shape(gmem_layout));
|
||||
auto di = basis_get(ei, stride(gmem_layout));
|
||||
if constexpr (is_constant<1, decltype(si)>::value || is_constant<0, decltype(di)>::value) {
|
||||
return Int<0>{}; // If size-1 or stride-0, return arithmetic identity -- no contribution to the TMA
|
||||
} else {
|
||||
auto tma_gbasis_stride = stride(tma_gbasis);
|
||||
auto tma_gmem_basis_stride = stride(tma_gbasis);
|
||||
// Find j such that E<i> is in stride<j>(tma_gbasis)
|
||||
using EI = decltype(ei);
|
||||
[[maybe_unused]] auto j = find_if(tma_gbasis_stride, [&](auto tma_stride_j) { return any_of(tma_stride_j, [&](auto dj) { return dj == EI{}; }); });
|
||||
if constexpr (decltype(j == rank(tma_gbasis_stride))::value) {
|
||||
[[maybe_unused]] auto j = find_if(tma_gmem_basis_stride, [&](auto tma_stride_j) { return any_of(tma_stride_j, [&](auto dj) { return dj == EI{}; }); });
|
||||
if constexpr (decltype(j == rank(tma_gmem_basis_stride))::value) {
|
||||
return Int<0>{}; // If not-found, return arithmetic identity -- no contribution to the TMA
|
||||
} else
|
||||
if constexpr (decltype(j == Int<0>{})::value) {
|
||||
auto scale = recast_ratio * basis_get(ei, stride(gtensor));
|
||||
return E<j>{} * scale; // Return TMA Coord basis -- with a recast scale factor
|
||||
} else
|
||||
if constexpr (decltype(rank<j>(tma_gbasis_stride) == Int<1>{})::value) {
|
||||
if constexpr (decltype(rank<j>(tma_gmem_basis_stride) == Int<1>{})::value) {
|
||||
return E<j>{}; // Return TMA Coord basis -- known scale of Int<1>{}
|
||||
} else {
|
||||
int32_t scale = ceil_div(int32_t(di * sizeof_bits_v<TmaInternalType> / cute::max(gmem_prob_stride[j], 16)), 8);
|
||||
@@ -768,14 +898,64 @@ make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GM
|
||||
}
|
||||
});
|
||||
|
||||
#if 0
|
||||
print("tma_gbasis : "); print(gmem_stride_bases); print("\n");
|
||||
#endif
|
||||
#if 0
|
||||
print("gmem_tma_basis_stride : "); print(gmem_tma_basis_stride); print("\n");
|
||||
#endif
|
||||
|
||||
using AuxParams = AuxTmaParams<decltype(gmem_stride_bases),
|
||||
using AuxParams = AuxTmaParams<decltype(gmem_tma_basis_stride),
|
||||
decltype(tma_gbasis),
|
||||
decltype(swizzle)>;
|
||||
return cute::make_tuple(tma_desc, AuxParams{gmem_stride_bases});
|
||||
return cute::make_tuple(tma_desc, AuxParams{gmem_tma_basis_stride});
|
||||
}
|
||||
|
||||
template <class TmaInternalType,
|
||||
class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
class VShape, class VStride>
|
||||
CUTE_HOST_RTC
|
||||
auto
|
||||
make_tma_copy_atom(CopyOp,
|
||||
Tensor<GEngine,GLayout> const& gtensor, // Full GMEM Tensor
|
||||
SLayout const& slayout, // CTA Tile of SMEM, potentially swizzled
|
||||
uint32_t const& num_multicast, // The number of CTAs involved in multicasting
|
||||
Layout<VShape,VStride> const& cta_v_map) // V: CTA val idx -> gmem mode
|
||||
{
|
||||
//
|
||||
// TMA truncated layout
|
||||
//
|
||||
|
||||
auto smem_swizzle = get_swizzle_portion(slayout);
|
||||
auto smem_layout = get_nonswizzle_portion(slayout);
|
||||
|
||||
auto tma_gbasis = detail::construct_tma_gbasis<TmaInternalType>(gtensor, smem_layout, cta_v_map);
|
||||
|
||||
//
|
||||
// Construct the TMA Desc and the strides of the TMA Tensor
|
||||
//
|
||||
|
||||
auto [tma_desc, aux_params] = detail::make_tma_copy_desc<TmaInternalType>(gtensor,
|
||||
tma_gbasis,
|
||||
smem_swizzle,
|
||||
num_multicast);
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
constexpr int num_bits_per_tma = decltype(size(tma_gbasis))::value * sizeof_bits_v<TmaInternalType>;
|
||||
using Traits = Copy_Traits<CopyOp, cute::C<num_bits_per_tma>, decltype(aux_params)>;
|
||||
using Atom = Copy_Atom<Traits, typename GEngine::value_type>;
|
||||
|
||||
Traits tma_traits{tma_desc, aux_params};
|
||||
|
||||
#if 0
|
||||
print("num_bits_per_tma : "); print(num_bits_per_tma); print("\n");
|
||||
print("g_stride_bases : "); print(tma_traits.aux_params_.g_stride_); print("\n");
|
||||
#endif
|
||||
|
||||
// Return the Copy_Atom
|
||||
return Atom{tma_traits};
|
||||
}
|
||||
|
||||
// The "logical TMA tid" is a map from the CTA rank to its logical id
|
||||
@@ -790,122 +970,46 @@ template <class TmaInternalType,
|
||||
class VShape, class VStride>
|
||||
CUTE_HOST_RTC
|
||||
auto
|
||||
make_tma_copy_tiled(CopyOp,
|
||||
make_tma_copy_tiled(CopyOp const& copy_op,
|
||||
Tensor<GEngine,GLayout> const& gtensor, // Full GMEM Tensor
|
||||
SLayout const& slayout, // CTA Tile of SMEM
|
||||
Layout<TShape,TStride> const& cta_t_map, // T: CTA thr idx -> logical TMA tid
|
||||
Layout<VShape,VStride> const& cta_v_map) // V: CTA val idx -> gmem mode
|
||||
{
|
||||
//
|
||||
// TMA parameter checking
|
||||
//
|
||||
|
||||
CUTE_STATIC_ASSERT_V(product_each(shape(slayout)) == product_each(shape(cta_v_map)),
|
||||
"TMA requires CTA_Tile and SLayout top-level shape equivalence.");
|
||||
CUTE_STATIC_ASSERT_V(size(slayout) % cosize(cta_t_map) == Int<0>{},
|
||||
"Number of active CTAs in TMA must divide domain size of slayout.");
|
||||
|
||||
//
|
||||
// TMA slayout manipulation
|
||||
//
|
||||
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
// smem idx -> smem coord
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_portion(slayout));
|
||||
|
||||
// Compose with the V-Map to convert smem coord (CTA val idx) to gmem mode
|
||||
// smem idx -> gmem mode
|
||||
auto sidx_to_gmode = coalesce(composition(cta_v_map, inv_smem_layout));
|
||||
|
||||
#if 0
|
||||
print("g_tensor : "); print(gtensor); print("\n");
|
||||
print("s_layout : "); print(slayout); print("\n");
|
||||
print("cta_t_map : "); print(cta_t_map); print("\n");
|
||||
print("cta_v_map : "); print(cta_v_map); print("\n");
|
||||
print("inv_s_layout : "); print(inv_smem_layout); print("\n");
|
||||
print("sidx_to_gmode : "); print(sidx_to_gmode); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
// gmem coord -> gmem coord
|
||||
auto glayout_basis = make_identity_layout(shape(gtensor));
|
||||
|
||||
// Tile the modes of gtensor with the truncated cta_v_map o inv_smem_layout_trunc
|
||||
// smem idx -> gmem coord
|
||||
auto tma_layout_full = flatten(composition(glayout_basis, sidx_to_gmode));
|
||||
|
||||
// Truncate any incompatibilities -- no starting in the middle of gmodes
|
||||
auto smem_rank = find_if(stride(tma_layout_full), [](auto e) {
|
||||
[[maybe_unused]] auto v = basis_value(e);
|
||||
return not is_constant<1,decltype(v)>{};
|
||||
});
|
||||
static_assert(smem_rank > 0, "Could not find a common tile-gmem vectorization. Does the Tile select out major GMEM modes?");
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto tma_layout_trunc = take<0,smem_rank>(tma_layout_full);
|
||||
|
||||
// Keep only the portion each multicast CTA will be responsible for
|
||||
auto tma_layout_v = composition(tma_layout_trunc, shape_div(size(tma_layout_trunc), cosize(cta_t_map)));
|
||||
|
||||
#if 0
|
||||
print("glayout_basis : "); print(glayout_basis); print("\n");
|
||||
print("tma_layout_full : "); print(tma_layout_full); print("\n");
|
||||
|
||||
print("tma_layout_trunc: "); print(tma_layout_trunc); print("\n");
|
||||
print("tma_layout_v : "); print(tma_layout_v); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// Construct the TMA Desc and the strides of the TMA Tensor
|
||||
//
|
||||
|
||||
auto [tma_desc, aux_params] = detail::make_tma_copy_desc<TmaInternalType>(gtensor,
|
||||
tma_layout_v,
|
||||
get_swizzle_portion(slayout));
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
constexpr int num_bits_per_tma = decltype(size(tma_layout_trunc))::value * sizeof_bits_v<T>;
|
||||
using Traits = Copy_Traits<CopyOp, cute::C<num_bits_per_tma>, decltype(aux_params)>;
|
||||
using Atom = Copy_Atom<Traits, T>;
|
||||
|
||||
Traits tma_traits{tma_desc, aux_params};
|
||||
|
||||
#if 0
|
||||
print("num_bits_per_tma : "); print(num_bits_per_tma); print("\n");
|
||||
print("g_stride_bases : "); print(tma_traits.aux_params_.g_stride_); print("\n");
|
||||
#endif
|
||||
Copy_Atom atom = make_tma_copy_atom<TmaInternalType>(copy_op, gtensor, slayout,
|
||||
cosize(cta_t_map), cta_v_map);
|
||||
|
||||
//
|
||||
// Construct the TiledCopy
|
||||
//
|
||||
|
||||
auto cta_tiler = product_each(shape(cta_v_map));
|
||||
[[maybe_unused]] auto cta_tiler = product_each(shape(cta_v_map));
|
||||
|
||||
auto num_elems_per_tma = size<1>(typename decltype(atom)::RefLayout{}) / Int<sizeof_bits_v<typename GEngine::value_type>>{};
|
||||
|
||||
// smem idx -> smem coord
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_portion(slayout));
|
||||
// CTA V -> smem_coord
|
||||
auto layout_v = composition(inv_smem_layout, size(tma_layout_trunc));
|
||||
auto layout_v = composition(inv_smem_layout, num_elems_per_tma);
|
||||
// Scale that up to cover all of the smem_coords
|
||||
auto layout_V = tile_to_shape(make_layout(layout_v), size(cta_v_map));
|
||||
// CTA T -> smem idx
|
||||
auto layout_t = make_layout(cosize(cta_t_map), shape_div(size(tma_layout_trunc), cosize(cta_t_map)));
|
||||
auto layout_t = make_layout(cosize(cta_t_map), shape_div(num_elems_per_tma, cosize(cta_t_map)));
|
||||
// CTA TID -> smem coord
|
||||
auto layout_T = composition(inv_smem_layout, composition(layout_t, cta_t_map));
|
||||
// Combine with the T mapping
|
||||
auto layout_TV = make_layout(layout_T, layout_V);
|
||||
[[maybe_unused]] auto layout_TV = make_layout(layout_T, layout_V);
|
||||
|
||||
#if 0
|
||||
print("cta_tiler : "); print(cta_tiler); print("\n");
|
||||
print("layout_VT : "); print(layout_VT); print("\n");
|
||||
print("layout_v : "); print(layout_v); print("\n");
|
||||
print("layout_V : "); print(layout_V); print("\n");
|
||||
print("layout_t : "); print(layout_t); print("\n");
|
||||
print("layout_T : "); print(layout_T); print("\n");
|
||||
print("layout_TV : "); print(layout_TV); print("\n");
|
||||
#endif
|
||||
|
||||
return TiledCopy<Atom, decltype(layout_TV), decltype(cta_tiler)>{tma_traits};
|
||||
return TiledCopy<decltype(atom), decltype(layout_TV), decltype(cta_tiler)>{atom};
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
@@ -982,7 +1086,7 @@ make_tma_copy_tiled(CopyOp,
|
||||
|
||||
copy(tma.with(barrier, mcast_mask), tAgA, tAsA); // copy with supporting TMA params
|
||||
*/
|
||||
template <class TmaInternalType,
|
||||
template <class TmaInternalType = void,
|
||||
class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
@@ -996,37 +1100,16 @@ make_tma_copy(CopyOp const& copy_op,
|
||||
CTA_Tiler const& cta_tiler,
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
auto cta_v_tile = make_identity_layout(shape(gtensor)).compose(cta_tiler);
|
||||
auto cta_t_tile = make_layout(cluster_size);
|
||||
return detail::make_tma_copy_tiled<TmaInternalType>(copy_op,
|
||||
gtensor,
|
||||
slayout,
|
||||
cta_t_tile,
|
||||
cta_v_tile);
|
||||
auto cta_v_tile = make_identity_layout(shape(gtensor)).compose(cta_tiler);
|
||||
auto cta_t_tile = make_layout(cluster_size);
|
||||
// Prefer TmaInternalType if specified. Fallback to GEngine::value_type
|
||||
using TmaType = conditional_t<is_same<void, TmaInternalType>::value, typename GEngine::value_type, TmaInternalType>;
|
||||
return detail::make_tma_copy_tiled<TmaType>(copy_op,
|
||||
gtensor, slayout,
|
||||
cta_t_tile, cta_v_tile);
|
||||
}
|
||||
|
||||
// Explicit defaulting
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
class CTA_Tile,
|
||||
class Cluster_Size>
|
||||
CUTE_HOST_RTC
|
||||
auto
|
||||
make_tma_copy(CopyOp const& copy_op,
|
||||
Tensor<GEngine,GLayout> const& gtensor,
|
||||
SLayout const& slayout,
|
||||
CTA_Tile const& cta_tile,
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
using TmaInternalType = typename GEngine::value_type;
|
||||
return make_tma_copy<TmaInternalType>(copy_op,
|
||||
gtensor,
|
||||
slayout,
|
||||
cta_tile,
|
||||
cluster_size);
|
||||
}
|
||||
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout>
|
||||
@@ -1039,6 +1122,7 @@ make_tma_copy(CopyOp const& copy_op,
|
||||
return make_tma_copy(copy_op, gtensor, slayout, product_each(shape(slayout)), Int<1>{});
|
||||
}
|
||||
|
||||
// Explicit defaulting
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
@@ -1053,4 +1137,86 @@ make_tma_copy(CopyOp const& copy_op,
|
||||
return make_tma_copy(copy_op, gtensor, slayout, product_each(shape(slayout)), cluster_size);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// Experimental Make TMA Atom and Partitioner
|
||||
///////////////////////////////////
|
||||
|
||||
template <class TmaInternalType = void,
|
||||
class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
class CTA_Tiler,
|
||||
class Cluster_Size>
|
||||
CUTE_HOST_RTC
|
||||
auto
|
||||
make_tma_atom(CopyOp const& copy_op,
|
||||
Tensor<GEngine,GLayout> const& gtensor,
|
||||
SLayout const& slayout,
|
||||
CTA_Tiler const& cta_tiler,
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
auto cta_v_tile = make_identity_layout(shape(gtensor)).compose(cta_tiler);
|
||||
// Prefer TmaInternalType if specified. Fallback to GEngine::value_type
|
||||
using TmaType = conditional_t<is_same<void, TmaInternalType>::value, typename GEngine::value_type, TmaInternalType>;
|
||||
return detail::make_tma_copy_atom<TmaType>(copy_op,
|
||||
gtensor, slayout,
|
||||
size(cluster_size), cta_v_tile);
|
||||
}
|
||||
|
||||
// The "VectorCopy Partitioner" for TMA
|
||||
template <class... Args,
|
||||
class CtaCoord,
|
||||
class TShape, class TStride,
|
||||
class SEngine, class SLayout,
|
||||
class GEngine, class GLayout>
|
||||
CUTE_DEVICE
|
||||
auto
|
||||
tma_partition(Copy_Atom<Args...> const& copy_atom,
|
||||
CtaCoord const& cta_coord,
|
||||
Layout<TShape,TStride> const& cta_layout, // T: CTA coord -> logical multicast id
|
||||
Tensor<SEngine,SLayout> const& stensor, // SMEM Tensor (TMATile, Iter)
|
||||
Tensor<GEngine,GLayout> const& gtensor) // GMEM Tensor (TMATile, Iter)
|
||||
{
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
Layout inv_smem_layout = right_inverse(get_nonswizzle_portion(layout<0>(stensor)));
|
||||
// Scale that up to cover all of the smem_coords
|
||||
Layout layout_v = tile_to_shape(make_layout(inv_smem_layout), size<0>(stensor));
|
||||
|
||||
// Factor out the single-instrucion portion
|
||||
Layout tma_layout_v = make_layout(Int<Copy_Atom<Args...>::NumValSrc>{});
|
||||
Layout layout_V = logical_divide(layout_v, tma_layout_v);
|
||||
|
||||
// Transform tile mode and coalesce
|
||||
Tensor gtensor_v = coalesce(gtensor.compose(layout_V, _), Shape<Shape<_1,_1>,_1>{}); // ((TMA,TMA_Iter),Iter)
|
||||
Tensor stensor_v = coalesce(stensor.compose(layout_V, _), Shape<Shape<_1,_1>,_1>{}); // ((TMA,TMA_Iter),Iter)
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print("layout_V : "); print(layout_V); print("\n");
|
||||
print("gtensor_v : "); print(gtensor_v); print("\n");
|
||||
print("stensor_v : "); print(stensor_v); print("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Restride the cta-into-tma-instr layout
|
||||
Layout tma_layout_t = composition(make_layout(Int<1>{}, shape_div(size(tma_layout_v), cosize(cta_layout))), cta_layout);
|
||||
Layout tma_layout_tv = make_layout(tma_layout_t, tma_layout_v);
|
||||
|
||||
// Transform TMA mode
|
||||
Tensor gtensor_tv = gtensor_v.compose(make_tile(tma_layout_tv, _), _); // (((Thr,Frg),TMA_Iter),Iter)
|
||||
Tensor stensor_tv = stensor_v.compose(make_tile(tma_layout_tv, _), _); // (((Thr,Frg),TMA_Iter),Iter)
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print("tma_layout_tv : "); print(tma_layout_tv); print("\n");
|
||||
print("gtensor_tv : "); print(gtensor_tv); print("\n");
|
||||
print("stensor_tv : "); print(stensor_tv); print("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Slice and group Frg,TMA_Iter and return
|
||||
auto c = make_coord(make_coord(make_coord(cta_coord, _), _), _);
|
||||
return cute::make_tuple(group_modes<0,2>(gtensor_tv(c)), group_modes<0,2>(stensor_tv(c)));
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
+150
-282
@@ -31,12 +31,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cute/config.hpp>
|
||||
#include <cute/arch/mma.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
#include <cute/arch/mma.hpp>
|
||||
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute {
|
||||
@@ -196,39 +196,37 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
template <class TiledMMA, class ThrCoord>
|
||||
struct ThrMMA;
|
||||
|
||||
// @tparam MMA_Atom The MMA_Atom to use in the TiledMMA
|
||||
// @tparam AtomLayoutMNK The MNK-tiling of the Atom to be performed.
|
||||
// @tparam PermuationsMNK Permutations to apply to each MNK-mode before tiling for the Atom.
|
||||
template <class MMA_Atom,
|
||||
class AtomLayoutMNK = Layout<Shape<_1,_1,_1>>,
|
||||
class ValLayoutMNK = Layout<Shape<_1,_1,_1>>,
|
||||
class PermutationsMNK = Tile<Underscore,Underscore,Underscore>>
|
||||
class AtomLayoutMNK,
|
||||
class PermutationMNK = Tile<Underscore,Underscore,Underscore>>
|
||||
struct TiledMMA : MMA_Atom
|
||||
{
|
||||
static_assert(rank_v<AtomLayoutMNK> == 3, "TiledMMA requires rank-3 AtomLayoutMNK");
|
||||
static_assert(rank_v<ValLayoutMNK> == 3, "TiledMMA requires rank-3 ValLayoutMNK");
|
||||
static_assert(rank_v<PermutationsMNK> == 3, "TiledMMA requires rank-3 PermutationsMNK");
|
||||
|
||||
using Atom = MMA_Atom;
|
||||
using AtomShape_MNK = typename MMA_Atom::Shape_MNK;
|
||||
|
||||
using AtomThrID = typename MMA_Atom::ThrID;
|
||||
using AtomLayoutC_TV = typename MMA_Atom::LayoutC_TV;
|
||||
using AtomLayoutA_TV = typename MMA_Atom::LayoutA_TV;
|
||||
using AtomLayoutB_TV = typename MMA_Atom::LayoutB_TV;
|
||||
|
||||
// ThrV -> thread_idx
|
||||
using AtomThrID = typename MMA_Atom::ThrID;
|
||||
static_assert( rank_v<AtomLayoutMNK> == 3, "TiledMMA requires rank-3 AtomLayoutMNK");
|
||||
static_assert( rank_v<PermutationMNK> == 3, "TiledMMA requires rank-3 PermutationMNK");
|
||||
static_assert( is_tile<PermutationMNK>::value, "TiledMMA requires independent permutations of MNK.");
|
||||
static_assert(is_static<PermutationMNK>::value, "TiledMMA requires static permutations of MNK.");
|
||||
|
||||
// (M,N,K)
|
||||
using TiledShape_MNK = decltype(make_shape(size<0>(AtomShape_MNK{})*size<0>(AtomLayoutMNK{})*size<0>(ValLayoutMNK{}),
|
||||
size<1>(AtomShape_MNK{})*size<1>(AtomLayoutMNK{})*size<1>(ValLayoutMNK{}),
|
||||
size<2>(AtomShape_MNK{})*size<2>(AtomLayoutMNK{})*size<2>(ValLayoutMNK{})));
|
||||
|
||||
// thrid = (ThrV,ThrM,ThrN,ThrK) -> thr_idx
|
||||
using ThrLayoutVMNK = decltype(tiled_product(AtomThrID{}, AtomLayoutMNK{}));
|
||||
ThrLayoutVMNK thr_layout_vmnk_;
|
||||
|
||||
// thr_idx -> (ThrV,ThrM,ThrN,ThrK)
|
||||
using TidLayout = decltype(right_inverse(ThrLayoutVMNK{}));
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
TiledMMA(MMA_Atom const& mma_atom = {}, AtomLayoutMNK const& thr_layout_mnk = {})
|
||||
: MMA_Atom(mma_atom),
|
||||
thr_layout_vmnk_(tiled_product(AtomThrID{}, thr_layout_mnk)) {}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr auto
|
||||
get_thr_layout_vmnk() const {
|
||||
return ThrLayoutVMNK{};
|
||||
return thr_layout_vmnk_;
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
@@ -243,17 +241,17 @@ struct TiledMMA : MMA_Atom
|
||||
// RestM: The values tiled in M.
|
||||
// RestN: The values tiled in N.
|
||||
template <class CTensor>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
thrfrg_C(CTensor&& ctensor)
|
||||
thrfrg_C(CTensor&& ctensor) const
|
||||
{
|
||||
CUTE_STATIC_ASSERT_V(rank(ctensor) >= Int<2>{});
|
||||
CUTE_STATIC_ASSERT_V(size<0>(ctensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
CUTE_STATIC_ASSERT_V(size<1>(ctensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(ctensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(ctensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
|
||||
// Reorder the tensor for the TiledAtom
|
||||
auto t_tile = make_tile(left_inverse(get<0>(PermutationsMNK{})),
|
||||
left_inverse(get<1>(PermutationsMNK{})));
|
||||
auto t_tile = make_tile(get<0>(PermutationMNK{}),
|
||||
get<1>(PermutationMNK{}));
|
||||
auto t_tensor = logical_divide(ctensor, t_tile); // (PermM,PermN)
|
||||
|
||||
// Tile the tensor for the Atom
|
||||
@@ -266,25 +264,13 @@ struct TiledMMA : MMA_Atom
|
||||
|
||||
// Tile the tensor for the C-threads
|
||||
auto thr_tile = make_tile(_,
|
||||
make_tile(make_layout(size<1>(ThrLayoutVMNK{})),
|
||||
make_layout(size<2>(ThrLayoutVMNK{}))));
|
||||
make_tile(make_layout(size<1>(thr_layout_vmnk_)),
|
||||
make_layout(size<2>(thr_layout_vmnk_))));
|
||||
auto thr_tensor = zipped_divide(tv_tensor, thr_tile); // ((ThrV,(ThrM,ThrN)),(FrgV,(RestM,RestN)))
|
||||
|
||||
return thr_tensor;
|
||||
}
|
||||
|
||||
// Tile from (M,N,...)
|
||||
// to (thr_idx,(FrgV,(RestM,RestN,...)))
|
||||
template <class CTensor>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
tidfrg_C(CTensor&& ctensor)
|
||||
{
|
||||
// Don't need a ctile composition because ThrK is last mode in TidLayout
|
||||
|
||||
return thrfrg_C(ctensor).compose(TidLayout{}, _);
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
// (M,K,...)
|
||||
// to shape
|
||||
@@ -297,17 +283,17 @@ struct TiledMMA : MMA_Atom
|
||||
// RestM: The values tiled in M.
|
||||
// RestK: The values tiled in K.
|
||||
template <class ATensor>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
thrfrg_A(ATensor&& atensor)
|
||||
thrfrg_A(ATensor&& atensor) const
|
||||
{
|
||||
CUTE_STATIC_ASSERT_V(rank(atensor) >= Int<2>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(atensor) % size<0>(TiledShape_MNK{}) == Int<0>{});
|
||||
//UTE_STATIC_ASSERT_V(size<1>(atensor) % size<2>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(atensor) % size<2>(TiledShape_MNK{}) == Int<0>{});
|
||||
|
||||
// Reorder the tensor for the TiledAtom
|
||||
auto t_tile = make_tile(left_inverse(get<0>(PermutationsMNK{})),
|
||||
left_inverse(get<2>(PermutationsMNK{})));
|
||||
auto t_tile = make_tile(get<0>(PermutationMNK{}),
|
||||
get<2>(PermutationMNK{}));
|
||||
auto t_tensor = logical_divide(atensor, t_tile); // (PermM,PermK)
|
||||
|
||||
// Tile the tensor for the Atom
|
||||
@@ -320,29 +306,13 @@ struct TiledMMA : MMA_Atom
|
||||
|
||||
// Tile the tensor for the Thread
|
||||
auto thr_tile = make_tile(_,
|
||||
make_tile(make_layout(size<1>(ThrLayoutVMNK{})),
|
||||
make_layout(size<3>(ThrLayoutVMNK{}))));
|
||||
make_tile(make_layout(size<1>(thr_layout_vmnk_)),
|
||||
make_layout(size<3>(thr_layout_vmnk_))));
|
||||
auto thr_tensor = zipped_divide(tv_tensor, thr_tile); // ((ThrV,(ThrM,ThrK)),(FrgV,(RestM,RestK)))
|
||||
|
||||
return thr_tensor;
|
||||
}
|
||||
|
||||
// Tile from (M,K,...)
|
||||
// to (thr_idx,(FrgV,(RestM,RestK,...)))
|
||||
template <class ATensor>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
tidfrg_A(ATensor&& atensor)
|
||||
{
|
||||
auto atile = make_tile(_,
|
||||
make_tile(make_layout(make_shape (size<1>(ThrLayoutVMNK{}), size<2>(ThrLayoutVMNK{})),
|
||||
make_stride( Int<1>{} , Int<0>{} )),
|
||||
_));
|
||||
// (ThrV,(ThrM,ThrK)) -> (ThrV,(ThrM,ThrN,ThrK))
|
||||
|
||||
return thrfrg_A(atensor).compose(atile, _).compose(TidLayout{}, _);
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
// (N,K,...)
|
||||
// to shape
|
||||
@@ -355,17 +325,17 @@ struct TiledMMA : MMA_Atom
|
||||
// RestN: The values tiled in N.
|
||||
// RestK: The values tiled in K.
|
||||
template <class BTensor>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
thrfrg_B(BTensor&& btensor)
|
||||
thrfrg_B(BTensor&& btensor) const
|
||||
{
|
||||
CUTE_STATIC_ASSERT_V(rank(btensor) >= Int<2>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<0>(btensor) % size<1>(TiledShape_MNK{}) == Int<0>{});
|
||||
//CUTE_STATIC_ASSERT_V(size<1>(btensor) % size<2>(TiledShape_MNK{}) == Int<0>{});
|
||||
|
||||
// Reorder the tensor for the TiledAtom
|
||||
auto t_tile = make_tile(left_inverse(get<1>(PermutationsMNK{})),
|
||||
left_inverse(get<2>(PermutationsMNK{})));
|
||||
auto t_tile = make_tile(get<1>(PermutationMNK{}),
|
||||
get<2>(PermutationMNK{}));
|
||||
auto t_tensor = logical_divide(btensor, t_tile); // (PermN,PermK)
|
||||
|
||||
// Tile the tensor for the Atom
|
||||
@@ -378,44 +348,28 @@ struct TiledMMA : MMA_Atom
|
||||
|
||||
// Tile the tensor for the Thread
|
||||
auto thr_tile = make_tile(_,
|
||||
make_tile(make_layout(size<2>(ThrLayoutVMNK{})),
|
||||
make_layout(size<3>(ThrLayoutVMNK{}))));
|
||||
make_tile(make_layout(size<2>(thr_layout_vmnk_)),
|
||||
make_layout(size<3>(thr_layout_vmnk_))));
|
||||
auto thr_tensor = zipped_divide(tv_tensor, thr_tile); // ((ThrV,(ThrN,ThrK)),(FrgV,(RestN,RestK)))
|
||||
|
||||
return thr_tensor;
|
||||
}
|
||||
|
||||
// Tile from (N,K,...)
|
||||
// to (thr_idx,(FrgV,(RestN,RestK,...)))
|
||||
template <class BTensor>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
template <class ThrIdx,
|
||||
__CUTE_REQUIRES(is_integral<ThrIdx>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tidfrg_B(BTensor&& btensor)
|
||||
get_slice(ThrIdx const& thr_idx) const
|
||||
{
|
||||
auto btile = make_tile(_,
|
||||
make_tile(make_layout(make_shape (size<1>(ThrLayoutVMNK{}), size<2>(ThrLayoutVMNK{})),
|
||||
make_stride( Int<0>{} , Int<1>{} )),
|
||||
_));
|
||||
// (ThrV,(ThrN,ThrK)) -> (ThrV,(ThrM,ThrN,ThrK))
|
||||
|
||||
return thrfrg_B(btensor).compose(btile, _).compose(TidLayout{}, _);
|
||||
auto thr_vmnk = thr_layout_vmnk_.get_flat_coord(thr_idx);
|
||||
return ThrMMA<TiledMMA, decltype(thr_vmnk)>{*this, thr_vmnk};
|
||||
}
|
||||
|
||||
template <class ThrIdx,
|
||||
__CUTE_REQUIRES(is_integral<ThrIdx>::value)>
|
||||
CUTE_HOST_DEVICE static constexpr
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_slice(ThrIdx const& thr_idx)
|
||||
{
|
||||
auto thr_vmnk = ThrLayoutVMNK{}.get_flat_coord(thr_idx);
|
||||
return ThrMMA<TiledMMA, decltype(thr_vmnk)>(thr_vmnk);
|
||||
}
|
||||
|
||||
template <class ThrIdx,
|
||||
__CUTE_REQUIRES(is_integral<ThrIdx>::value)>
|
||||
CUTE_HOST_DEVICE static constexpr
|
||||
auto
|
||||
get_thread_slice(ThrIdx const& thr_idx)
|
||||
get_thread_slice(ThrIdx const& thr_idx) const
|
||||
{
|
||||
return get_slice(thr_idx);
|
||||
}
|
||||
@@ -424,104 +378,144 @@ struct TiledMMA : MMA_Atom
|
||||
// Utility for printing and visualization
|
||||
//
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
// The size of the MNK-mode
|
||||
template <int I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutC_MN()
|
||||
tile_size_mnk() const {
|
||||
static_assert(0 <= I && I < 3);
|
||||
auto core_size = size<I>(AtomShape_MNK{}) * size<I+1>(get_thr_layout_vmnk());
|
||||
[[maybe_unused]] auto perm_size = size<I>(PermutationMNK{});
|
||||
if constexpr (is_underscore<decltype(perm_size)>::value) {
|
||||
return core_size;
|
||||
} else {
|
||||
return cute::max(core_size, perm_size);
|
||||
}
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutC_MN() const
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_C = make_layout(make_shape(size<0>(TiledShape_MNK{}), size<1>(TiledShape_MNK{})));
|
||||
auto ref_C = make_layout(make_shape(tile_size_mnk<0>(), tile_size_mnk<1>()));
|
||||
// (cthrid,val) -> (M,N)
|
||||
auto layoutC_TV = thrfrg_C(ref_C);
|
||||
// (M,N) -> (cthrid,frg)
|
||||
auto layoutC_MN = right_inverse(layoutC_TV).with_shape(shape(ref_C));
|
||||
|
||||
// cthrid = (v,m,n) -> thr_idx
|
||||
auto thrID_C = ThrLayoutVMNK{}(_,_,_,Int<0>{});
|
||||
auto thrID_C = thr_layout_vmnk_(_,_,_,Int<0>{});
|
||||
|
||||
return cute::make_tuple(layoutC_MN, thrID_C);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutC_TV()
|
||||
get_layoutC_TV() const
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_C = make_layout(make_shape(size<0>(TiledShape_MNK{}), size<1>(TiledShape_MNK{})));
|
||||
auto ref_C = make_layout(make_shape(tile_size_mnk<0>(), tile_size_mnk<1>()));
|
||||
// (cthrid,val) -> (M,N)
|
||||
auto layoutC_TV = thrfrg_C(ref_C);
|
||||
|
||||
return tidfrg_C(ref_C);
|
||||
// thr_idx -> (ThrV,ThrM,ThrN,ThrK)
|
||||
auto thridx_2_thrid = right_inverse(thr_layout_vmnk_);
|
||||
|
||||
// (thr_idx,val) -> (M,N)
|
||||
return layoutC_TV.compose(thridx_2_thrid, _);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutA_MK()
|
||||
get_layoutA_MK() const
|
||||
{
|
||||
// (M,K) -> (M,K)
|
||||
auto ref_A = make_layout(make_shape(size<0>(TiledShape_MNK{}), size<2>(TiledShape_MNK{})));
|
||||
auto ref_A = make_layout(make_shape(tile_size_mnk<0>(), tile_size_mnk<2>()));
|
||||
// (athrid,val) -> (M,K)
|
||||
auto layoutA_TV = thrfrg_A(ref_A);
|
||||
// (M,K) -> (athrid,frg)
|
||||
auto layoutA_MK = right_inverse(layoutA_TV).with_shape(shape(ref_A));
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_A = ThrLayoutVMNK{}(_,_,Int<0>{},_);
|
||||
auto thrID_A = thr_layout_vmnk_(_,_,Int<0>{},_);
|
||||
|
||||
return cute::make_tuple(layoutA_MK, thrID_A);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutA_TV()
|
||||
get_layoutA_TV() const
|
||||
{
|
||||
// (M,K) -> (M,K)
|
||||
auto ref_A = make_layout(make_shape(size<0>(TiledShape_MNK{}), size<2>(TiledShape_MNK{})));
|
||||
auto ref_A = make_layout(make_shape(tile_size_mnk<0>(), tile_size_mnk<2>()));
|
||||
// (athrid,val) -> (M,K)
|
||||
auto layoutA_TV = thrfrg_A(ref_A);
|
||||
|
||||
return tidfrg_A(ref_A);
|
||||
// (ThrV,(ThrM,ThrK)) -> (ThrV,(ThrM,ThrN,ThrK))
|
||||
auto atile = make_tile(_,
|
||||
make_tile(make_layout(make_shape (size<1>(thr_layout_vmnk_), size<2>(thr_layout_vmnk_)),
|
||||
make_stride( Int<1>{} , Int<0>{} )),
|
||||
_));
|
||||
|
||||
// thr_idx -> (ThrV,ThrM,ThrN,ThrK)
|
||||
auto thridx_2_thrid = right_inverse(thr_layout_vmnk_);
|
||||
|
||||
// (thr_idx,val) -> (M,K)
|
||||
return thrfrg_A(ref_A).compose(atile, _).compose(thridx_2_thrid, _);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutB_NK()
|
||||
get_layoutB_NK() const
|
||||
{
|
||||
// (N,K) -> (N,K)
|
||||
auto ref_B = make_layout(make_shape(size<1>(TiledShape_MNK{}), size<2>(TiledShape_MNK{})));
|
||||
auto ref_B = make_layout(make_shape(tile_size_mnk<1>(), tile_size_mnk<2>()));
|
||||
// (bthrid,val) -> (N,K)
|
||||
auto layoutB_TV = thrfrg_B(ref_B);
|
||||
// (N,K) -> (bthrid,frg)
|
||||
auto layoutB_NK = right_inverse(layoutB_TV).with_shape(shape(ref_B));
|
||||
|
||||
// bthrid = (v,n,k) -> thr_idx
|
||||
auto thrID_B = ThrLayoutVMNK{}(_,Int<0>{},_,_);
|
||||
auto thrID_B = thr_layout_vmnk_(_,Int<0>{},_,_);
|
||||
|
||||
return cute::make_tuple(layoutB_NK, thrID_B);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
get_layoutB_TV()
|
||||
get_layoutB_TV() const
|
||||
{
|
||||
// (N,K) -> (N,K)
|
||||
auto ref_B = make_layout(make_shape(size<1>(TiledShape_MNK{}), size<2>(TiledShape_MNK{})));
|
||||
auto ref_B = make_layout(make_shape(tile_size_mnk<1>(), tile_size_mnk<2>()));
|
||||
// (bthrid,val) -> (N,K)
|
||||
auto layoutB_TV = thrfrg_B(ref_B);
|
||||
|
||||
return tidfrg_B(ref_B);
|
||||
// (ThrV,(ThrM,ThrK)) -> (ThrV,(ThrM,ThrN,ThrK))
|
||||
auto btile = make_tile(_,
|
||||
make_tile(make_layout(make_shape (size<1>(thr_layout_vmnk_), size<2>(thr_layout_vmnk_)),
|
||||
make_stride( Int<0>{} , Int<1>{} )),
|
||||
_));
|
||||
|
||||
// thr_idx -> (ThrV,ThrM,ThrN,ThrK)
|
||||
auto thridx_2_thrid = right_inverse(thr_layout_vmnk_);
|
||||
|
||||
// (thr_idx,val) -> (N,K)
|
||||
return thrfrg_B(ref_B).compose(btile, _).compose(thridx_2_thrid, _);
|
||||
}
|
||||
};
|
||||
|
||||
template <class TiledMMA, class ThrVMNK>
|
||||
struct ThrMMA : TiledMMA
|
||||
{
|
||||
// Use ThrVMNK and thrfrg rather than thr_idx and tidfrg
|
||||
// to support swizzled threads partitioning dynamic layouts
|
||||
ThrVMNK thr_vmnk_;
|
||||
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
ThrMMA(ThrVMNK const& thr_vmnk) : thr_vmnk_(thr_vmnk) {}
|
||||
|
||||
template <class CTensor>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_C(CTensor&& ctensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<CTensor>(ctensor).data(), TiledMMA::thrfrg_C(ctensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<CTensor>(ctensor).data(), this->thrfrg_C(ctensor.layout()));
|
||||
|
||||
auto thr_vmn = make_coord(get<0>(thr_vmnk_), make_coord(get<1>(thr_vmnk_), get<2>(thr_vmnk_)));
|
||||
return thr_tensor(thr_vmn, make_coord(_, repeat<rank<1,1>(thr_tensor)>(_)));
|
||||
@@ -532,7 +526,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_A(ATensor&& atensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<ATensor>(atensor).data(), TiledMMA::thrfrg_A(atensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<ATensor>(atensor).data(), this->thrfrg_A(atensor.layout()));
|
||||
|
||||
auto thr_vmk = make_coord(get<0>(thr_vmnk_), make_coord(get<1>(thr_vmnk_), get<3>(thr_vmnk_)));
|
||||
return thr_tensor(thr_vmk, make_coord(_, repeat<rank<1,1>(thr_tensor)>(_)));
|
||||
@@ -543,7 +537,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_B(BTensor&& btensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<BTensor>(btensor).data(), TiledMMA::thrfrg_B(btensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<BTensor>(btensor).data(), this->thrfrg_B(btensor.layout()));
|
||||
|
||||
auto thr_vnk = make_coord(get<0>(thr_vmnk_), make_coord(get<2>(thr_vmnk_), get<3>(thr_vmnk_)));
|
||||
return thr_tensor(thr_vnk, make_coord(_, repeat<rank<1,1>(thr_tensor)>(_)));
|
||||
@@ -580,38 +574,32 @@ struct ThrMMA : TiledMMA
|
||||
|
||||
template <class MMA_Op,
|
||||
class MMAThrLayout = Layout<Shape<_1,_1,_1>>,
|
||||
class MMAValLayout = Layout<Shape<_1,_1,_1>>,
|
||||
class Permutations = Tile<Underscore,Underscore,Underscore>>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tiled_mma(MMA_Atom<MMA_Op> const&,
|
||||
make_tiled_mma(MMA_Atom<MMA_Op> const& mma_atom,
|
||||
MMAThrLayout const& thr_layout = {},
|
||||
MMAValLayout const& val_layout = {},
|
||||
Permutations const& permutations = {})
|
||||
{
|
||||
auto thr_layout_mnk = append<3>(thr_layout, Layout<_1,_0>{});
|
||||
auto val_layout_mnk = append<3>(val_layout, Layout<_1,_0>{});
|
||||
auto permutation_mnk = append<3>(permutations, _);
|
||||
|
||||
return TiledMMA<MMA_Atom<MMA_Op>,
|
||||
decltype(thr_layout_mnk),
|
||||
decltype(val_layout_mnk),
|
||||
decltype(permutation_mnk)>{};
|
||||
decltype(permutation_mnk)>{mma_atom, thr_layout_mnk};
|
||||
}
|
||||
|
||||
template <class MMA_Op,
|
||||
class MMAThrLayout = Layout<Shape<_1,_1,_1>>,
|
||||
class MMAValLayout = Layout<Shape<_1,_1,_1>>,
|
||||
class Permutations = Tile<Underscore,Underscore,Underscore>>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_tiled_mma(MMA_Op const&,
|
||||
MMAThrLayout const& thr_layout = {},
|
||||
MMAValLayout const& val_layout = {},
|
||||
Permutations const& permutations = {})
|
||||
{
|
||||
// Attempt to wrap in an MMA_Atom<> and forward
|
||||
return make_tiled_mma(MMA_Atom<MMA_Op>{}, thr_layout, val_layout, permutations);
|
||||
return make_tiled_mma(MMA_Atom<MMA_Op>{}, thr_layout, permutations);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -680,28 +668,38 @@ partition_shape_B(TiledMMA<Args...> const& mma, Shape_NK const& shape_NK)
|
||||
// Size
|
||||
//
|
||||
|
||||
template <int... I, class... Args>
|
||||
template <int I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tile_size(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return size<I...>(typename TiledMMA<Args...>::TiledShape_MNK{});
|
||||
return mma.template tile_size_mnk<I>();
|
||||
}
|
||||
|
||||
template <int... I, class... Args>
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tile_shape(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return shape<I...>(typename TiledMMA<Args...>::TiledShape_MNK{});
|
||||
return make_shape(tile_size<0>(mma), tile_size<1>(mma), tile_size<2>(mma));
|
||||
}
|
||||
|
||||
// Deprecate?
|
||||
template <int... I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
size(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return size<I...>(typename TiledMMA<Args...>::ThrLayoutVMNK{});
|
||||
return size<I...>(mma.get_thr_layout_vmnk());
|
||||
}
|
||||
|
||||
// Alias
|
||||
template <int... I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
thr_size(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return size<I...>(mma.get_thr_layout_vmnk());
|
||||
}
|
||||
|
||||
//
|
||||
@@ -715,33 +713,31 @@ print(MMA_Atom<MMA_Traits<Args...>> const&)
|
||||
{
|
||||
using Atom = MMA_Atom<MMA_Traits<Args...>>;
|
||||
print("MMA_Atom\n");
|
||||
print(" ThrID: "); print(typename Atom::ThrID{}); print("\n");
|
||||
print(" LayoutA_TV: "); print(typename Atom::LayoutA_TV{}); print("\n");
|
||||
print(" LayoutB_TV: "); print(typename Atom::LayoutB_TV{}); print("\n");
|
||||
print(" LayoutC_TV: "); print(typename Atom::LayoutC_TV{}); print("\n");
|
||||
print(" ThrID: "); print(typename Atom::ThrID{}); print("\n");
|
||||
print(" LayoutA_TV: "); print(typename Atom::LayoutA_TV{}); print("\n");
|
||||
print(" LayoutB_TV: "); print(typename Atom::LayoutB_TV{}); print("\n");
|
||||
print(" LayoutC_TV: "); print(typename Atom::LayoutC_TV{}); print("\n");
|
||||
}
|
||||
|
||||
template <class Atom, class TiledThr, class TiledVal, class TiledPerm>
|
||||
template <class Atom, class TiledThr, class TiledPerm>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(TiledMMA<Atom, TiledThr, TiledVal, TiledPerm> const& mma)
|
||||
print(TiledMMA<Atom, TiledThr, TiledPerm> const& mma)
|
||||
{
|
||||
using MMA = TiledMMA<Atom, TiledThr, TiledVal, TiledPerm>;
|
||||
print("TiledMMA\n");
|
||||
print(" TiledThr: "); print(TiledThr{}); print("\n");
|
||||
print(" TiledVal: "); print(TiledVal{}); print("\n");
|
||||
print(" TiledPerm: "); print(TiledPerm{}); print("\n");
|
||||
print(" TiledShape_MNK: "); print(typename MMA::TiledShape_MNK{}); print("\n");
|
||||
print(" ThrLayoutVMNK: "); print(typename MMA::ThrLayoutVMNK{}); print("\n");
|
||||
print(" ThrLayoutVMNK: "); print(mma.get_thr_layout_vmnk()); print("\n");
|
||||
print(" PermutationMNK: "); print(TiledPerm{}); print("\n");
|
||||
print(static_cast<Atom const&>(mma));
|
||||
}
|
||||
|
||||
template <class TiledMMA, class ThrVMNK>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(ThrMMA<TiledMMA, ThrVMNK> const&)
|
||||
print(ThrMMA<TiledMMA, ThrVMNK> const& thr_mma)
|
||||
{
|
||||
print(TiledMMA{});
|
||||
print("ThrMMA\n");
|
||||
print(" Thr VMNK: "); print(thr_mma.thr_vmnk_); print("\n");
|
||||
print(static_cast<TiledMMA>(thr_mma));
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
@@ -766,18 +762,6 @@ print_latex(TiledMMA<Args...> const& mma)
|
||||
layoutB_NK, thrID_B);
|
||||
}
|
||||
|
||||
// EXPERIMENTAL -- Doesn't work with Swizzled Thr TileMMAs...
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
print_latex_2(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
print_latex_mma(typename TiledMMA<Args...>::TiledShape_MNK{},
|
||||
mma.get_layoutC_TV(),
|
||||
mma.get_layoutA_TV(),
|
||||
mma.get_layoutB_TV());
|
||||
}
|
||||
|
||||
// MNK MMA Layout to console printer -- 8-value color coded by thread
|
||||
template <class LayoutC, class ThrIDC,
|
||||
class LayoutA, class ThrIDA,
|
||||
@@ -943,122 +927,6 @@ print_latex_mma(LayoutC const& C, ThrIDC const& TC, // (m,n) -> (tid,vid) and
|
||||
printf(latex_footer);
|
||||
}
|
||||
|
||||
// ThrVal MMA Layout to Latex TIKZ -- 8-value color coded by thread
|
||||
template <class Shape_MNK,
|
||||
class LayoutC, class LayoutA, class LayoutB>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print_latex_mma(Shape_MNK const& shape_mnk,
|
||||
LayoutC const& C, // (thr_idx,vid) -> (m,n)
|
||||
LayoutA const& A, // (thr_idx,vid) -> (m,k)
|
||||
LayoutB const& B) // (thr_idx,vid) -> (n,k)
|
||||
{
|
||||
CUTE_STATIC_ASSERT_V(rank(C) == Int<2>{});
|
||||
CUTE_STATIC_ASSERT_V(rank(A) == Int<2>{});
|
||||
CUTE_STATIC_ASSERT_V(rank(B) == Int<2>{});
|
||||
|
||||
char const* latex_header =
|
||||
"\\documentclass{standalone}\n"
|
||||
"\\usepackage{tikz}\n"
|
||||
"\\usetikzlibrary{external}\n"
|
||||
"\\tikzexternalize\n"
|
||||
"\\begin{document}\n"
|
||||
"\\begin{tikzpicture}[x={(0cm,-1cm)},y={(1cm,0cm)},box/.style={rectangle,draw=black,thick,minimum size=1cm,anchor=center}]\n\n";
|
||||
char const* latex_footer =
|
||||
"\\end{tikzpicture}\n"
|
||||
"\\end{document}\n";
|
||||
|
||||
char const* color_map[8] = {"{rgb,255:red,175;green,175;blue,255}",
|
||||
"{rgb,255:red,175;green,255;blue,175}",
|
||||
"{rgb,255:red,255;green,255;blue,175}",
|
||||
"{rgb,255:red,255;green,175;blue,175}",
|
||||
"{rgb,255:red,210;green,210;blue,255}",
|
||||
"{rgb,255:red,210;green,255;blue,210}",
|
||||
"{rgb,255:red,255;green,255;blue,210}",
|
||||
"{rgb,255:red,255;green,210;blue,210}"};
|
||||
|
||||
// Header
|
||||
printf("%% Shape_MNK: "); print(shape_mnk); printf("\n");
|
||||
printf("%% LayoutC : "); print(C); printf("\n");
|
||||
printf("%% LayoutA : "); print(A); printf("\n");
|
||||
printf("%% LayoutB : "); print(B); printf("\n\n");
|
||||
|
||||
printf(latex_header);
|
||||
|
||||
auto M = size<0>(shape_mnk);
|
||||
auto N = size<1>(shape_mnk);
|
||||
auto K = size<2>(shape_mnk);
|
||||
|
||||
// C starting at 0,0
|
||||
bool c_filled[M][N] = {};
|
||||
for (int t = 0; t < size<0>(C); ++t) {
|
||||
for (int v = 0; v < size<1>(C); ++v) {
|
||||
int m = C(t,v) % M;
|
||||
int n = C(t,v) / M;
|
||||
|
||||
if (not c_filled[m][n]) {
|
||||
printf("\\node[box,fill=%s] at (%d,%d) {\\shortstack{T%d \\\\ V%d}};\n",
|
||||
color_map[t % 8],
|
||||
m, n,
|
||||
t, v);
|
||||
c_filled[m][n] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A starting at 0,-size<1>(A)-1
|
||||
bool a_filled[M][K] = {};
|
||||
for (int t = 0; t < size<0>(A); ++t) {
|
||||
for (int v = 0; v < size<1>(A); ++v) {
|
||||
int m = A(t,v) % M;
|
||||
int k = A(t,v) / M;
|
||||
|
||||
if (not a_filled[m][k]) {
|
||||
printf("\\node[box,fill=%s] at (%d,%d) {\\shortstack{T%d \\\\ V%d}};\n",
|
||||
color_map[t % 8],
|
||||
m, k - 1 - K,
|
||||
t, v);
|
||||
a_filled[m][k] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B starting at -size<1>(B)-1,0
|
||||
bool b_filled[N][K] = {};
|
||||
for (int t = 0; t < size<0>(B); ++t) {
|
||||
for (int v = 0; v < size<1>(B); ++v) {
|
||||
int n = B(t,v) % N;
|
||||
int k = B(t,v) / N;
|
||||
|
||||
if (not b_filled[n][k]) {
|
||||
printf("\\node[box,fill=%s] at (%d,%d) {\\shortstack{T%d \\\\ V%d}};\n",
|
||||
color_map[t % 8],
|
||||
k - 1 - K, n,
|
||||
t, v);
|
||||
b_filled[n][k] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A labels
|
||||
for (int m = 0, k = -1; m < M; ++m) {
|
||||
printf("\\node at (%d,%d) {\\Large{\\texttt{%d}}};\n", m, k - 1 - K, m);
|
||||
}
|
||||
for (int k = 0, m = -1; k < K; ++k) {
|
||||
printf("\\node at (%d,%d) {\\Large{\\texttt{%d}}};\n", m, k - 1 - K, k);
|
||||
}
|
||||
// B labels
|
||||
for (int n = 0, k = -1; n < N; ++n) {
|
||||
printf("\\node at (%d,%d) {\\Large{\\texttt{%d}}};\n", k - 1 - K, n, n);
|
||||
}
|
||||
for (int k = 0, n = -1; k < K; ++k) {
|
||||
printf("\\node at (%d,%d) {\\Large{\\texttt{%d}}};\n", k - 1 - K, n, k);
|
||||
}
|
||||
|
||||
// Footer
|
||||
printf(latex_footer);
|
||||
}
|
||||
|
||||
} // namespace cute
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
#endif
|
||||
|
||||
#if !defined(__CUDACC_RTC__) && !defined(__clang__) && \
|
||||
(defined(__CUDA_ARCH__) || defined(_NVHPC_CUDA))
|
||||
(defined(__CUDA_ARCH__) || defined(_NVHPC_CUDA))
|
||||
# define CUTE_UNROLL #pragma unroll
|
||||
# define CUTE_NO_UNROLL #pragma unroll 1
|
||||
#elif defined(__CUDACC_RTC__) || defined(__clang__)
|
||||
@@ -120,6 +120,8 @@
|
||||
#include <cassert>
|
||||
#endif
|
||||
|
||||
#define CUTE_STATIC_V(x) decltype(x)::value
|
||||
|
||||
#define CUTE_STATIC_ASSERT static_assert
|
||||
#define CUTE_STATIC_ASSERT_V(x,...) static_assert(decltype(x)::value, ##__VA_ARGS__)
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ private:
|
||||
// Flag for fast branching on straddled elements
|
||||
static constexpr bool is_storage_unaligned = ((sizeof_bits_v<storage_type> % sizeof_bits_v<element_type>) != 0);
|
||||
|
||||
friend class subbyte_iterator<T>;
|
||||
friend struct subbyte_iterator<T>;
|
||||
|
||||
// Pointer to storage element
|
||||
storage_type* ptr_ = nullptr;
|
||||
@@ -208,7 +208,7 @@ struct subbyte_iterator
|
||||
|
||||
private:
|
||||
|
||||
template <class, class> friend class swizzle_ptr;
|
||||
template <class, class> friend struct swizzle_ptr;
|
||||
|
||||
// Pointer to storage element
|
||||
storage_type* ptr_ = nullptr;
|
||||
|
||||
@@ -327,7 +327,7 @@ ceil_div(IntTupleA const& a, IntTupleB const& b)
|
||||
{
|
||||
if constexpr (is_tuple<IntTupleA>::value && is_tuple<IntTupleB>::value) {
|
||||
static_assert(tuple_size<IntTupleA>::value >= tuple_size<IntTupleB>::value, "Mismatched ranks");
|
||||
constexpr int R = tuple_size<IntTupleA>::value; // Missing ranks in TupleB are implictly 1
|
||||
constexpr int R = tuple_size<IntTupleA>::value; // Missing ranks in TupleB are implicitly 1
|
||||
return transform(a, append<R>(b,Int<1>{}), [](auto const& x, auto const& y) { return ceil_div(x,y); });
|
||||
} else {
|
||||
return (a + b - Int<1>{}) / b;
|
||||
@@ -336,6 +336,28 @@ ceil_div(IntTupleA const& a, IntTupleB const& b)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
//
|
||||
// round_up
|
||||
// Round @a a up to the nearest multiple of @a b.
|
||||
// For negative numbers, rounds away from zero.
|
||||
//
|
||||
|
||||
template <class IntTupleA, class IntTupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
round_up(IntTupleA const& a, IntTupleB const& b)
|
||||
{
|
||||
if constexpr (is_tuple<IntTupleA>::value && is_tuple<IntTupleB>::value) {
|
||||
static_assert(tuple_size<IntTupleA>::value >= tuple_size<IntTupleB>::value, "Mismatched ranks");
|
||||
constexpr int R = tuple_size<IntTupleA>::value; // Missing ranks in TupleB are implicitly 1
|
||||
return transform(a, append<R>(b,Int<1>{}), [](auto const& x, auto const& y) { return round_up(x,y); });
|
||||
} else {
|
||||
return ((a + b - Int<1>{}) / b) * b;
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
/** Division for Shapes
|
||||
* Case Tuple Tuple:
|
||||
* Perform shape_div element-wise
|
||||
@@ -429,6 +451,7 @@ template <class A, class B>
|
||||
using is_congruent = decltype(congruent(declval<A>(), declval<B>()));
|
||||
|
||||
/** Test if two IntTuple have the similar profiles up to Shape A (hierarchical rank division)
|
||||
* weakly_congruent is a partial order on A and B: A <= B
|
||||
*/
|
||||
template <class IntTupleA, class IntTupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -458,7 +481,7 @@ using is_weakly_congruent = decltype(weakly_congruent(declval<A>(), declval<B>()
|
||||
|
||||
/** Test if Shape B is compatible with Shape A:
|
||||
* Any coordinate into A can also be used as a coordinate into B
|
||||
* A <= B is a partially ordered set of factored shapes
|
||||
* compatible is a partial order on A and B: A <= B
|
||||
*/
|
||||
template <class IntTupleA, class IntTupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -487,7 +510,8 @@ template <class A, class B>
|
||||
using is_compatible = decltype(compatible(declval<A>(), declval<B>()));
|
||||
|
||||
/** Test if Shape B is weakly compatible with Shape A:
|
||||
* Shape B divides Shape A at some level of refinement
|
||||
* Shape B is a multiple of a shape that is compatible with Shape A
|
||||
* weakly_compatible is a partial order on A and B: A <= B
|
||||
*/
|
||||
template <class IntTupleA, class IntTupleB>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -502,7 +526,7 @@ weakly_compatible(IntTupleA const& a, IntTupleB const& b)
|
||||
[](auto const&... z) { return (true_type{} && ... && z); });
|
||||
}
|
||||
} else if constexpr (is_integral<IntTupleA>::value) {
|
||||
return a % size(b) == Int<0>{};
|
||||
return size(b) % a == Int<0>{};
|
||||
} else if constexpr (is_integral<IntTupleB>::value) {
|
||||
return false_type{};
|
||||
} else {
|
||||
|
||||
+51
-57
@@ -981,7 +981,6 @@ auto
|
||||
composition(Layout<LShape,LStride> const& lhs,
|
||||
Layout<RShape,RStride> const& rhs)
|
||||
{
|
||||
//return detail::composition_impl(flatten(lhs), rhs.shape(), rhs.stride());
|
||||
return detail::composition_impl(lhs, rhs.shape(), rhs.stride());
|
||||
}
|
||||
|
||||
@@ -997,8 +996,8 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
return detail::transform_layout(lhs, rhs, [](auto const& l, auto const& r) { return composition(l,r); }, make_seq<tuple_size<IntTuple>::value>{}, seq<>{}, seq<>{});
|
||||
} else if constexpr (is_underscore<IntTuple>::value) {
|
||||
return lhs;
|
||||
} else {
|
||||
return composition(lhs, make_layout(rhs));
|
||||
} else if constexpr (is_integral<IntTuple>::value) {
|
||||
return detail::composition_impl(lhs, rhs, Int<1>{});
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -1097,15 +1096,18 @@ inverse_seq(Shape const& shape, Stride const& stride, seq<Is...>)
|
||||
auto next_I = cute::find_if(stride, [](auto a) { return is_constant<NextStride, decltype(a)>{}; });
|
||||
|
||||
if constexpr (next_I == decltype(rank(stride))::value) {
|
||||
// If not found, return current seq
|
||||
return seq<Is...>{};
|
||||
} else {
|
||||
// auto next_stride = get<next_I>(shape) * get<next_I>(stride);
|
||||
// NOTE: Needed for g++-7
|
||||
using next_stride = decltype(get<next_I>(shape) * get<next_I>(stride));
|
||||
|
||||
if constexpr (is_static<next_stride>::value) {
|
||||
if constexpr (is_static<next_stride>::value && !is_constant<NextStride, next_stride>::value) {
|
||||
// If next_stride is static and unique, then continue
|
||||
return inverse_seq<next_stride::value>(shape, stride, seq<Is..., next_I>{});
|
||||
} else {
|
||||
// Else return current seq + next_I
|
||||
return seq<Is..., next_I>{};
|
||||
}
|
||||
}
|
||||
@@ -1340,28 +1342,24 @@ template <class LShape, class LStride,
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
logical_divide(Layout<LShape,LStride> const& layout,
|
||||
Layout<TShape,TStride> const& tile)
|
||||
Layout<TShape,TStride> const& tiler)
|
||||
{
|
||||
//CUTE_STATIC_ASSERT_V(size(layout) % size(tile) == Int<0>{},
|
||||
// "Tiling does not evenly divide the block");
|
||||
// NOTE: With tiles that have stride-0, this doesn't have to be true
|
||||
|
||||
return composition(layout, make_layout(tile, complement(tile, size(layout))));
|
||||
return composition(layout, make_layout(tiler, complement(tiler, size(layout))));
|
||||
}
|
||||
|
||||
template <class LShape, class LStride, class IntTuple>
|
||||
template <class LShape, class LStride, class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
logical_divide(Layout<LShape,LStride> const& layout,
|
||||
IntTuple const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
if constexpr (is_tuple<IntTuple>::value) {
|
||||
static_assert(tuple_size<IntTuple>::value <= Layout<LShape,LStride>::rank, "logical_divide: Too many modes in tile.");
|
||||
return transform_layout(layout, tile, [](auto const& l, auto const& t) { return logical_divide(l,t); });
|
||||
} else if constexpr (is_underscore<IntTuple>::value) {
|
||||
if constexpr (is_tuple<Tiler>::value) {
|
||||
static_assert(tuple_size<Tiler>::value <= Layout<LShape,LStride>::rank, "logical_divide: Too many modes in tiler.");
|
||||
return transform_layout(layout, tiler, [](auto const& l, auto const& t) { return logical_divide(l,t); });
|
||||
} else if constexpr (is_underscore<Tiler>::value) {
|
||||
return layout;
|
||||
} else if constexpr (is_integral<IntTuple>::value) {
|
||||
return logical_divide(layout, make_layout(tile));
|
||||
} else if constexpr (is_integral<Tiler>::value) {
|
||||
return logical_divide(layout, make_layout(tiler));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -1374,24 +1372,24 @@ logical_divide(Layout<LShape,LStride> const& layout,
|
||||
//
|
||||
|
||||
template <class LShape, class LStride,
|
||||
class Tile>
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
zipped_divide(Layout<LShape,LStride> const& layout,
|
||||
Tile const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
return tile_unzip(logical_divide(layout, tile), tile);
|
||||
return tile_unzip(logical_divide(layout, tiler), tiler);
|
||||
}
|
||||
|
||||
// Same as zipped_divide, but unpacks the second mode: ((BLK_A,BLK_B,...),a,b,...,x,y)
|
||||
template <class LShape, class LStride,
|
||||
class Tile>
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tiled_divide(Layout<LShape,LStride> const& layout,
|
||||
Tile const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
auto div = zipped_divide(layout, tile);
|
||||
auto div = zipped_divide(layout, tiler);
|
||||
|
||||
auto R = rank<1>(div);
|
||||
return div(_, repeat<R>(_));
|
||||
@@ -1399,13 +1397,13 @@ tiled_divide(Layout<LShape,LStride> const& layout,
|
||||
|
||||
// Same as zipped_divide, but unpacks both modes: (BLK_A,BLK_B,...,a,b,...,x,y)
|
||||
template <class LShape, class LStride,
|
||||
class Tile>
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
flat_divide(Layout<LShape,LStride> const& layout,
|
||||
Tile const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
auto div = zipped_divide(layout, tile);
|
||||
auto div = zipped_divide(layout, tiler);
|
||||
|
||||
auto R0 = rank<0>(div);
|
||||
auto R1 = rank<1>(div);
|
||||
@@ -1421,24 +1419,24 @@ template <class LShape, class LStride,
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
logical_product(Layout<LShape,LStride> const& layout,
|
||||
Layout<TShape,TStride> const& tile)
|
||||
Layout<TShape,TStride> const& tiler)
|
||||
{
|
||||
return make_layout(layout, composition(complement(layout, size(layout)*cosize(tile)), tile));
|
||||
return make_layout(layout, composition(complement(layout, size(layout)*cosize(tiler)), tiler));
|
||||
}
|
||||
|
||||
template <class LShape, class LStride, class IntTuple>
|
||||
template <class LShape, class LStride, class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
logical_product(Layout<LShape,LStride> const& layout,
|
||||
IntTuple const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
if constexpr (is_tuple<IntTuple>::value) {
|
||||
static_assert(tuple_size<IntTuple>::value <= Layout<LShape,LStride>::rank);
|
||||
return transform_layout(layout, tile, [](auto const& l, auto const& t) { return logical_product(l,t); });
|
||||
} else if constexpr (is_underscore<IntTuple>::value) {
|
||||
if constexpr (is_tuple<Tiler>::value) {
|
||||
static_assert(tuple_size<Tiler>::value <= Layout<LShape,LStride>::rank, "logical_product: Too many modes in tiler.");
|
||||
return transform_layout(layout, tiler, [](auto const& l, auto const& t) { return logical_product(l,t); });
|
||||
} else if constexpr (is_underscore<Tiler>::value) {
|
||||
return layout;
|
||||
} else if constexpr (is_integral<IntTuple>::value) {
|
||||
return logical_product(layout, make_layout(tile));
|
||||
} else if constexpr (is_integral<Tiler>::value) {
|
||||
return logical_product(layout, make_layout(tiler));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -1451,45 +1449,43 @@ logical_product(Layout<LShape,LStride> const& layout,
|
||||
//
|
||||
|
||||
template <class LShape, class LStride,
|
||||
class Tile>
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
zipped_product(Layout<LShape,LStride> const& layout,
|
||||
Tile const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
return tile_unzip(logical_product(layout, tile), tile);
|
||||
return tile_unzip(logical_product(layout, tiler), tiler);
|
||||
}
|
||||
|
||||
// Same as zipped_product, but unpacks the second mode: ((BLK_A,BLK_B,...),a,b,...,x,y)
|
||||
template <class LShape, class LStride,
|
||||
class Tile>
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tiled_product(Layout<LShape,LStride> const& layout,
|
||||
Tile const& tile)
|
||||
Tiler const& tiler)
|
||||
{
|
||||
auto div = zipped_product(layout, tile);
|
||||
auto div = zipped_product(layout, tiler);
|
||||
|
||||
auto R = rank(tile);
|
||||
auto R = rank<1>(div);
|
||||
return div(_, repeat<R>(_));
|
||||
}
|
||||
|
||||
// Attempts to reproduce layout "block" over layout "layout"
|
||||
// That is, think of every element of "layout" as a "block"
|
||||
// Attempts to reproduce a layout over a tiler
|
||||
// That is, think of every element of "tiler" as a "layout"
|
||||
// and return the layout of the resulting structure
|
||||
template <class TShape, class TStride,
|
||||
class UShape, class UStride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
blocked_product(Layout<TShape,TStride> const& block,
|
||||
Layout<UShape,UStride> const& layout)
|
||||
blocked_product(Layout<TShape,TStride> const& layout,
|
||||
Layout<UShape,UStride> const& tiler)
|
||||
{
|
||||
constexpr int R = cute::max(rank_v<TShape>, rank_v<UShape>);
|
||||
auto padded_block = append<R>(block);
|
||||
auto padded_layout = append<R>(layout);
|
||||
|
||||
auto result = logical_product(padded_block, padded_layout);
|
||||
|
||||
auto result = logical_product(append<R>(layout), append<R>(tiler));
|
||||
|
||||
return coalesce(zip(get<0>(result), get<1>(result)), repeat<R>(Int<1>{}));
|
||||
}
|
||||
|
||||
@@ -1497,14 +1493,12 @@ template <class TShape, class TStride,
|
||||
class UShape, class UStride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
raked_product(Layout<TShape,TStride> const& block,
|
||||
Layout<UShape,UStride> const& layout)
|
||||
raked_product(Layout<TShape,TStride> const& layout,
|
||||
Layout<UShape,UStride> const& tiler)
|
||||
{
|
||||
constexpr int R = cute::max(rank_v<TShape>, rank_v<UShape>);
|
||||
auto padded_block = append<R>(block);
|
||||
auto padded_layout = append<R>(layout);
|
||||
|
||||
auto result = logical_product(padded_block, padded_layout);
|
||||
auto result = logical_product(append<R>(layout), append<R>(tiler));
|
||||
|
||||
return coalesce(zip(get<1>(result), get<0>(result)), repeat<R>(Int<1>{}));
|
||||
}
|
||||
|
||||
@@ -473,6 +473,16 @@ zipped_divide(ComposedLayout<A,O,B> const& a,
|
||||
return composition(a.layout_a(), a.offset(), zipped_divide(a.layout_b(), b));
|
||||
}
|
||||
|
||||
template <class A, class O, class B,
|
||||
class Tile>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
flat_divide(ComposedLayout<A,O,B> const& a,
|
||||
Tile const& b)
|
||||
{
|
||||
return composition(a.layout_a(), a.offset(), flat_divide(a.layout_b(), b));
|
||||
}
|
||||
|
||||
template <class A, class O, class B,
|
||||
class Tile>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
|
||||
@@ -181,7 +181,7 @@ template <class T0, class T1, class... Ts>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
make_inttuple_iter(T0 const& t0, T1 const& t1, Ts const&... ts) {
|
||||
return make_tuple_iter(cute::make_tuple(t0, t1, ts...));
|
||||
return make_inttuple_iter(cute::make_tuple(t0, t1, ts...));
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -148,7 +148,9 @@ using _96 = Int<96>;
|
||||
using _128 = Int<128>;
|
||||
using _192 = Int<192>;
|
||||
using _256 = Int<256>;
|
||||
using _384 = Int<384>;
|
||||
using _512 = Int<512>;
|
||||
using _768 = Int<768>;
|
||||
using _1024 = Int<1024>;
|
||||
using _2048 = Int<2048>;
|
||||
using _4096 = Int<4096>;
|
||||
|
||||
@@ -97,7 +97,7 @@ template <class T, class U,
|
||||
__CUTE_REQUIRES(is_std_integral<T>::value &&
|
||||
is_std_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
cute::common_type_t<T, U>
|
||||
gcd(T t, U u) {
|
||||
while (true) {
|
||||
if (t == 0) { return u; }
|
||||
@@ -112,7 +112,7 @@ template <class T, class U,
|
||||
__CUTE_REQUIRES(is_std_integral<T>::value &&
|
||||
is_std_integral<U>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
cute::common_type_t<T, U>
|
||||
lcm(T const& t, U const& u) {
|
||||
return (t / gcd(t,u)) * u;
|
||||
}
|
||||
|
||||
@@ -233,14 +233,14 @@ CUTE_HOST_DEVICE void print(T const* const ptr)
|
||||
template <class T>
|
||||
CUTE_HOST_DEVICE void print(counting_iterator<T> ptr)
|
||||
{
|
||||
printf("counting_iter_"); print(ptr.n_);
|
||||
printf("counting_iter("); print(ptr.n_); printf(")");
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class T>
|
||||
CUTE_HOST std::ostream& operator<<(std::ostream& os, counting_iterator<T> ptr)
|
||||
{
|
||||
return os << "counting_iter_" << ptr.n_;
|
||||
return os << "counting_iter(" << ptr.n_ << ")";
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
|
||||
@@ -990,13 +990,10 @@ CUTE_HOST_DEVICE void print_tensor(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
print(tensor); print(":\n");
|
||||
|
||||
auto format = get_format(tensor(0));
|
||||
using type = typename decltype(format)::type;
|
||||
|
||||
if constexpr (Layout::rank == 1)
|
||||
{
|
||||
for (int m = 0; m < size(tensor); ++m) {
|
||||
printf(format.format, format.digits, type(tensor(m)));
|
||||
pretty_print(tensor(m));
|
||||
printf("\n");
|
||||
}
|
||||
} else
|
||||
@@ -1004,7 +1001,7 @@ CUTE_HOST_DEVICE void print_tensor(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
for (int m = 0; m < size<0>(tensor); ++m) {
|
||||
for (int n = 0; n < size<1>(tensor); ++n) {
|
||||
printf(format.format, format.digits, type(tensor(m,n)));
|
||||
pretty_print(tensor(m,n));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
@@ -1013,7 +1010,7 @@ CUTE_HOST_DEVICE void print_tensor(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
print_tensor(tensor(_,_,0));
|
||||
for (int k = 1; k < size<2>(tensor); ++k) {
|
||||
for (int i = 0; i < format.digits*size<1>(tensor); ++i) { print("-"); } print("\n");
|
||||
for (int i = 0; i < 5*size<1>(tensor); ++i) { print("-"); } print("\n");
|
||||
print_tensor(tensor(_,_,k));
|
||||
}
|
||||
} else
|
||||
@@ -1021,7 +1018,7 @@ CUTE_HOST_DEVICE void print_tensor(Tensor<Engine,Layout> const& tensor)
|
||||
{
|
||||
print_tensor(tensor(_,_,_,0));
|
||||
for (int p = 1; p < size<3>(tensor); ++p) {
|
||||
for (int i = 0; i < format.digits*size<1>(tensor); ++i) { print("="); } print("\n");
|
||||
for (int i = 0; i < 5*size<1>(tensor); ++i) { print("="); } print("\n");
|
||||
print_tensor(tensor(_,_,_,p));
|
||||
}
|
||||
}
|
||||
|
||||
+50
-55
@@ -57,61 +57,6 @@ num_digits(int x)
|
||||
10)))))))));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct format_and_size {
|
||||
using type = T;
|
||||
char const* format;
|
||||
int digits;
|
||||
};
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<int>
|
||||
get_format(bool) {
|
||||
return {"%*d", 3};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<int32_t>
|
||||
get_format(int32_t) {
|
||||
return {"%*d", 5};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<uint32_t>
|
||||
get_format(uint32_t) {
|
||||
return {"%*d", 5};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<int64_t>
|
||||
get_format(int64_t) {
|
||||
return {"%*d", 5};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<uint64_t>
|
||||
get_format(uint64_t) {
|
||||
return {"%*d", 5};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<float>
|
||||
get_format(half_t) {
|
||||
return {"%*.2f", 8};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<float>
|
||||
get_format(float) {
|
||||
return {"%*.2e", 10};
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
format_and_size<double>
|
||||
get_format(double) {
|
||||
return {"%*.3e", 11};
|
||||
}
|
||||
|
||||
//
|
||||
// print dispatcher
|
||||
//
|
||||
@@ -195,4 +140,54 @@ print(char const* format) {
|
||||
printf("%s", format);
|
||||
}
|
||||
|
||||
//
|
||||
// pretty printing
|
||||
//
|
||||
|
||||
template <class T>
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(T const& v) {
|
||||
printf(" "); print(v);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(bool const& v) {
|
||||
printf("%*d", 3, int(v));
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(int32_t const& v) {
|
||||
printf("%*d", 5, v);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(uint32_t const& v) {
|
||||
printf("%*d", 5, v);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(int64_t const& v) {
|
||||
printf("%*lld", 5, static_cast<long long>(v));
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(uint64_t const& v) {
|
||||
printf("%*llu", 5, static_cast<unsigned long long>(v));
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(half_t const& v) {
|
||||
printf("%*.2f", 8, float(v));
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(float const& v) {
|
||||
printf("%*.2e", 10, v);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE void
|
||||
pretty_print(double const& v) {
|
||||
printf("%*.3e", 11, v);
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -122,6 +122,9 @@ using CUTE_STL_NAMESPACE::is_empty_v;
|
||||
|
||||
using CUTE_STL_NAMESPACE::invoke_result_t;
|
||||
|
||||
using CUTE_STL_NAMESPACE::common_type;
|
||||
using CUTE_STL_NAMESPACE::common_type_t;
|
||||
|
||||
// <utility>
|
||||
using CUTE_STL_NAMESPACE::declval;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user