co-authored by
Aniket Shivam
parent
9b8166e3f0
commit
d572cc1aab
+194
-159
@@ -30,51 +30,17 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/copy.hpp>
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute {
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
// Generic copy_unpack for any Copy_Traits
|
||||
template <class Operation, class... Args,
|
||||
class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits<Operation, Args...> const&,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
// Specializations can generalize on these checks
|
||||
//static_assert(is_smem<TS>::value, "Expected smem for this Copy_Traits<Operation>");
|
||||
//static_assert(is_rmem<TD>::value, "Expected rmem for this Copy_Traits<Operation>");
|
||||
|
||||
using RegistersSrc = typename Operation::SRegisters;
|
||||
using RegistersDst = typename Operation::DRegisters;
|
||||
using RegTypeSrc = typename std::remove_extent<RegistersSrc>::type;
|
||||
using RegTypeDst = typename std::remove_extent<RegistersDst>::type;
|
||||
constexpr int RegNumSrc = std::extent<RegistersSrc>::value;
|
||||
constexpr int RegNumDst = std::extent<RegistersDst>::value;
|
||||
|
||||
Tensor rS = recast<RegTypeSrc>(src);
|
||||
Tensor rD = recast<RegTypeDst>(dst);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rS) == Int<RegNumSrc>{},
|
||||
"In CopyAtom, src layout doesn't vectorize into registers. This src layout is incompatible with this tiled copy.");
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumDst>{},
|
||||
"In CopyAtom, dst layout doesn't vectorize into registers. This dst layout is incompatible with this tiled copy.");
|
||||
|
||||
detail::explode(Operation::copy,
|
||||
rS, make_int_sequence<RegNumSrc>{},
|
||||
rD, make_int_sequence<RegNumDst>{});
|
||||
}
|
||||
|
||||
|
||||
template <class... Args>
|
||||
struct Copy_Atom;
|
||||
@@ -110,33 +76,18 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
|
||||
// Additional Trait parameters/transformations
|
||||
template <class... TraitsArgs>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
with(TraitsArgs&&... args) const {
|
||||
auto traits = Traits::with(std::forward<TraitsArgs>(args)...);
|
||||
return Copy_Atom<decltype(traits), T>{traits};
|
||||
}
|
||||
|
||||
// Print thread and data layouts for debugging
|
||||
CUTE_HOST_DEVICE static
|
||||
void
|
||||
print_all()
|
||||
{
|
||||
print("ThrID: "); print(ThrID{}); print("\n");
|
||||
print("BitLayoutSrc: "); print(BitLayoutSrc{}); print("\n");
|
||||
print("BitLayoutDst: "); print(BitLayoutDst{}); print("\n");
|
||||
print("BitLayoutRef: "); print(BitLayoutRef{}); print("\n");
|
||||
print("ValLayoutSrc: "); print(ValLayoutSrc{}); print("\n");
|
||||
print("ValLayoutDst: "); print(ValLayoutDst{}); print("\n");
|
||||
print("ValLayoutRef: "); print(ValLayoutRef{}); print("\n");
|
||||
print("ValueType: %db", sizeof_bits<ValType>::value); print("\n");
|
||||
}
|
||||
|
||||
//
|
||||
// Tensor call interfaces
|
||||
//
|
||||
|
||||
// Cast, check, and call
|
||||
// Check and call instruction, or recurse
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE
|
||||
@@ -147,12 +98,19 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
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) {
|
||||
if constexpr (is_constant<NumValSrc, decltype(size(src))>::value ||
|
||||
is_constant<NumValDst, decltype(size(dst))>::value) {
|
||||
// Dispatch to unpack for instruction
|
||||
return copy_unpack(*this, src, dst);
|
||||
} else {
|
||||
// Recurse if needed by peeling the tensor mode
|
||||
} else
|
||||
if constexpr (is_tuple<decltype(shape(src))>::value &&
|
||||
is_tuple<decltype(shape(dst))>::value) {
|
||||
// If the size of the src/dst doesn't match the instruction,
|
||||
// recurse this rank-1 layout by peeling off the mode
|
||||
// ((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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +130,9 @@ struct Copy_Atom<Copy_Traits<Args...>, T>
|
||||
// A tiling of copy atoms
|
||||
//
|
||||
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
struct ThrCopy;
|
||||
|
||||
template <class Copy_Atom,
|
||||
class LayoutCopy_TV, // (tid,vid) -> coord [Need not be 2D...]
|
||||
class ShapeTile_MN> // coord space
|
||||
@@ -211,7 +172,13 @@ struct TiledCopy : Copy_Atom
|
||||
auto
|
||||
tidfrg_S(STensor&& stensor)
|
||||
{
|
||||
return thrfrg(stensor, right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}));
|
||||
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>{});
|
||||
|
||||
return tile2thrfrg(zipped_divide(stensor,Tiler_MN{}), right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}));
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
@@ -229,20 +196,24 @@ struct TiledCopy : Copy_Atom
|
||||
auto
|
||||
tidfrg_D(DTensor&& dtensor)
|
||||
{
|
||||
return thrfrg(dtensor, right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}));
|
||||
}
|
||||
|
||||
template <class Tensor, class Ref2TrgLayout>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
thrfrg(Tensor&& tensor, Ref2TrgLayout const& ref2trg)
|
||||
{
|
||||
constexpr int R = remove_cvref_t<Tensor>::rank;
|
||||
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>{});
|
||||
|
||||
return tile2thrfrg(zipped_divide(dtensor,Tiler_MN{}), right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}));
|
||||
}
|
||||
|
||||
// Tile a tensor or a layout from shape
|
||||
// (Tile,(RestM,RestN,...))
|
||||
// to shape
|
||||
// ((ThrV,ThrX),FrgV,(RestM,RestN,...))
|
||||
template <class Tensor, class Ref2TrgLayout>
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
tile2thrfrg(Tensor&& tensor, Ref2TrgLayout const& ref2trg)
|
||||
{
|
||||
// Take the thrs/vals that the atom is interested in
|
||||
// NOTE: Assumes the AtomNumThr are contiguous and identity within TiledThrID
|
||||
auto atom_layout_TV = zipped_divide(TiledLayout_TV{}, make_shape(AtomNumThr{}, AtomNumVal{}));
|
||||
@@ -259,12 +230,8 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
/// ==================
|
||||
|
||||
// Tile the tensor for TiledLayout
|
||||
auto t_tensor = zipped_divide(tensor, Tiler_MN{});
|
||||
// ((TileM,TileN,...),(RestM,RestN,...))
|
||||
|
||||
// Transform the tile mode
|
||||
auto tv_tensor = t_tensor.compose(thrval2mn, _);
|
||||
auto tv_tensor = tensor.compose(thrval2mn, _);
|
||||
// ((thrid,val),(RM,RN,...))
|
||||
|
||||
// Unfold and return
|
||||
@@ -308,14 +275,22 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutS_MN()
|
||||
get_layoutS_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_S = make_layout(TiledShape_MN{});
|
||||
auto ref_S = make_layout(make_shape(TiledShape_MN{}, Int<1>{}));
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutS_TV = tidfrg_S(ref_S);
|
||||
return tile2thrfrg(ref_S, right_inverse(AtomLayoutRef{}).compose(AtomLayoutSrc{}))(_,_,Int<0>{});
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutS_MN()
|
||||
{
|
||||
// (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(shape(ref_S));
|
||||
auto layoutS_MK = right_inverse(layoutS_TV).with_shape(TiledShape_MN{});
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_S = make_layout(size<0>(TiledLayout_TV{}));
|
||||
@@ -325,24 +300,22 @@ struct TiledCopy : Copy_Atom
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutS_TV()
|
||||
get_layoutD_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_S = make_layout(TiledShape_MN{});
|
||||
auto ref_D = make_layout(make_shape(TiledShape_MN{}, Int<1>{}));
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
return tidfrg_S(ref_S)(_,_,Int<0>{});
|
||||
return tile2thrfrg(ref_D, right_inverse(AtomLayoutRef{}).compose(AtomLayoutDst{}))(_,_,Int<0>{});
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutD_MN()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_D = make_layout(TiledShape_MN{});
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
auto layoutD_TV = tidfrg_D(ref_D);
|
||||
auto layoutD_TV = get_layoutD_TV();
|
||||
// (M,K) -> (thr_idx,val_idx)
|
||||
auto layoutD_MK = right_inverse(layoutD_TV).with_shape(shape(ref_D));
|
||||
auto layoutD_MK = right_inverse(layoutD_TV).with_shape(TiledShape_MN{});
|
||||
|
||||
// athrid = (v,m,k) -> thr_idx
|
||||
auto thrID_D = make_layout(size<0>(TiledLayout_TV{}));
|
||||
@@ -350,70 +323,13 @@ struct TiledCopy : Copy_Atom
|
||||
return cute::make_tuple(layoutD_MK, thrID_D);
|
||||
}
|
||||
|
||||
CUTE_HOST_DEVICE constexpr static
|
||||
auto
|
||||
get_layoutD_TV()
|
||||
{
|
||||
// (M,N) -> (M,N)
|
||||
auto ref_D = make_layout(TiledShape_MN{});
|
||||
// (thr_idx,val_idx) -> (M,N)
|
||||
return tidfrg_D(ref_D)(_,_,Int<0>{});
|
||||
}
|
||||
|
||||
template <class ThrIdx>
|
||||
struct ThrCopy : Copy_Atom
|
||||
{
|
||||
ThrIdx thr_idx_;
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
ThrCopy(ThrIdx const& thr_idx) : thr_idx_(thr_idx) {}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_S(STensor&& stensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
// "Expected ValType for tiling SrcTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<STensor>(stensor).data(), tidfrg_S(stensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<STensor>>(_));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_D(DTensor&& dtensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
// "Expected ValType for tiling DstTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<DTensor>(dtensor).data(), tidfrg_D(dtensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<DTensor>>(_));
|
||||
}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_S(STensor&& stensor) {
|
||||
static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
"Expected ValType for tiling SrcTensor.");
|
||||
return make_tensor(std::forward<STensor>(stensor).data(), TiledCopy::retile(stensor.layout()));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_D(DTensor&& dtensor) {
|
||||
static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename Copy_Atom::ValType),
|
||||
"Expected ValType for tiling DstTensor.");
|
||||
return make_tensor(std::forward<DTensor>(dtensor).data(), TiledCopy::retile(dtensor.layout()));
|
||||
}
|
||||
};
|
||||
|
||||
template <class ThrIdx,
|
||||
__CUTE_REQUIRES(is_integral<ThrIdx>::value)>
|
||||
CUTE_HOST_DEVICE static
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
get_slice(ThrIdx const& thr_idx)
|
||||
{
|
||||
return ThrCopy<ThrIdx>(thr_idx);
|
||||
return ThrCopy<TiledCopy, ThrIdx>(thr_idx);
|
||||
}
|
||||
|
||||
template <class ThrIdx,
|
||||
@@ -426,17 +342,64 @@ struct TiledCopy : Copy_Atom
|
||||
}
|
||||
};
|
||||
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
struct ThrCopy
|
||||
{
|
||||
ThrIdx thr_idx_;
|
||||
|
||||
CUTE_HOST_DEVICE
|
||||
ThrCopy(ThrIdx const& thr_idx) : thr_idx_(thr_idx) {}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_S(STensor&& stensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling SrcTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<STensor>(stensor).data(), TiledCopy::tidfrg_S(stensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<STensor>>(_));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
partition_D(DTensor&& dtensor) {
|
||||
//static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling DstTensor.");
|
||||
auto thr_tensor = make_tensor(std::forward<DTensor>(dtensor).data(), TiledCopy::tidfrg_D(dtensor.layout()));
|
||||
return thr_tensor(thr_idx_, _, repeat<rank_v<DTensor>>(_));
|
||||
}
|
||||
|
||||
template <class STensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_S(STensor&& stensor) {
|
||||
// static_assert(sizeof(typename remove_cvref_t<STensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling SrcTensor.");
|
||||
return make_tensor(std::forward<STensor>(stensor).data(), TiledCopy::retile(stensor.layout()));
|
||||
}
|
||||
|
||||
template <class DTensor>
|
||||
CUTE_HOST_DEVICE static
|
||||
auto
|
||||
retile_D(DTensor&& dtensor) {
|
||||
// static_assert(sizeof(typename remove_cvref_t<DTensor>::value_type) == sizeof(typename TiledCopy::ValType),
|
||||
// "Expected ValType for tiling DstTensor.");
|
||||
return make_tensor(std::forward<DTensor>(dtensor).data(), TiledCopy::retile(dtensor.layout()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class... Args,
|
||||
class LayoutCopy_TV,
|
||||
class... TLayout>
|
||||
CUTE_HOST_DEVICE
|
||||
class Tiler>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_impl(Copy_Atom<Args...> const& atom,
|
||||
LayoutCopy_TV const&,
|
||||
Tile<TLayout...> const&)
|
||||
Tiler const&)
|
||||
{
|
||||
return TiledCopy<Copy_Atom<Args...>, LayoutCopy_TV, Tile<TLayout...>>{atom};
|
||||
return TiledCopy<Copy_Atom<Args...>, LayoutCopy_TV, Tiler>{atom};
|
||||
}
|
||||
|
||||
//
|
||||
@@ -445,7 +408,7 @@ make_tiled_copy_impl(Copy_Atom<Args...> const& atom,
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_A(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
@@ -456,7 +419,7 @@ make_tiled_copy_A(Copy_Atom<Args...> const& copy_atom,
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_B(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
@@ -467,7 +430,7 @@ make_tiled_copy_B(Copy_Atom<Args...> const& copy_atom,
|
||||
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_C(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_mma)
|
||||
@@ -476,10 +439,50 @@ make_tiled_copy_C(Copy_Atom<Args...> const& copy_atom,
|
||||
return make_tiled_copy_impl(copy_atom, tiled_mma.get_layoutC_TV(), make_shape(size<0>(MNK{}),size<1>(MNK{})));
|
||||
}
|
||||
|
||||
// returns the smallest tiled copy that can retile LayoutC_TV
|
||||
// for use with pipelined epilogues with subtiled stores
|
||||
template <class... Args,
|
||||
class TiledMMA>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_C_atom(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledMMA const& tiled_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>{};
|
||||
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)));
|
||||
|
||||
// Recompute tiler and restride the TV layout for the new tiler
|
||||
|
||||
// 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_zeros = repeat_like(mma_tiler, Int<0>{});
|
||||
|
||||
auto tiler = transform(make_seq<rank(mma_tiler)>{}, [&](auto i) {
|
||||
return filter(composition(make_layout(mma_tiler, replace<i>(mma_zeros, Int<1>{})), layout_TV));
|
||||
});
|
||||
|
||||
// Layout_TV -- Find the (tid,vid) -> tile coord transformation
|
||||
// Apply the tiler to a reference and transform the codomain
|
||||
// tile_coord -> mma_coord
|
||||
auto tile2mma = composition(make_layout(mma_tiler), tiler);
|
||||
|
||||
// (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);
|
||||
}
|
||||
|
||||
template <class... Args,
|
||||
class ThrLayout,
|
||||
class ValLayout = Layout<_1>>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy(Copy_Atom<Args...> const& copy_atom,
|
||||
ThrLayout const& thr_layout = {}, // (m,n) -> thr_idx
|
||||
@@ -493,11 +496,10 @@ make_tiled_copy(Copy_Atom<Args...> const& copy_atom,
|
||||
// Take the raked_products to compute the Layout_MN
|
||||
auto layout_mn = raked_product(thr_layout_mn, val_layout_mn);
|
||||
auto layout_tv = right_inverse(layout_mn).with_shape(make_shape(size(thr_layout), size(val_layout)));
|
||||
|
||||
//print("thr_layout: "); print(thr_layout_mn); print("\n");
|
||||
//print("val_layout: "); print(val_layout_mn); print("\n");
|
||||
//print("layout_mn : "); print(layout_mn); print("\n");
|
||||
//print("layout_tv : "); print(layout_tv); print("\n");
|
||||
// print("thr_layout: "); print(thr_layout_mn); print("\n");
|
||||
// print("val_layout: "); print(val_layout_mn); print("\n");
|
||||
// print("layout_mn : "); print(layout_mn); print("\n");
|
||||
// print("layout_tv : "); print(layout_tv); print("\n");
|
||||
|
||||
return make_tiled_copy_impl(copy_atom, layout_tv, product_each(shape(layout_mn)));
|
||||
}
|
||||
@@ -505,7 +507,7 @@ make_tiled_copy(Copy_Atom<Args...> const& copy_atom,
|
||||
// Make a TiledCopy out of the copy_atom that matches the Src-Layout of tiled_copy
|
||||
template <class... Args,
|
||||
class TiledCopy>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_S(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledCopy const& tiled_copy)
|
||||
@@ -516,7 +518,7 @@ make_tiled_copy_S(Copy_Atom<Args...> const& copy_atom,
|
||||
// Make a TiledCopy out of the copy_atom that matches the Dst-Layout of tiled_copy
|
||||
template <class... Args,
|
||||
class TiledCopy>
|
||||
CUTE_HOST_DEVICE
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
make_tiled_copy_D(Copy_Atom<Args...> const& copy_atom,
|
||||
TiledCopy const& tiled_copy)
|
||||
@@ -550,6 +552,40 @@ size(TiledCopy<Args...> const&)
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
template <class... Args, class T>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(Copy_Atom<Copy_Traits<Args...>, T> const&)
|
||||
{
|
||||
using Atom = Copy_Atom<Copy_Traits<Args...>, T>;
|
||||
print("Copy_Atom\n");
|
||||
print(" ThrID: "); print(typename Atom::ThrID{}); print("\n");
|
||||
print(" ValLayoutSrc: "); print(typename Atom::ValLayoutSrc{}); print("\n");
|
||||
print(" ValLayoutDst: "); print(typename Atom::ValLayoutDst{}); print("\n");
|
||||
print(" ValLayoutRef: "); print(typename Atom::ValLayoutRef{}); print("\n");
|
||||
print(" ValueType: %db\n", int(sizeof_bits<typename Atom::ValType>::value));
|
||||
}
|
||||
|
||||
template <class Atom, class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(TiledCopy<Atom, Args...> const& copy, char const* pad = "")
|
||||
{
|
||||
using Copy = TiledCopy<Atom, Args...>;
|
||||
print("TiledCopy\n");
|
||||
print(" Tiler_MN: "); print(typename Copy::Tiler_MN{}); print("\n");
|
||||
print(" TiledLayout_TV: "); print(typename Copy::TiledLayout_TV{}); print("\n");
|
||||
print(static_cast<Atom const&>(copy));
|
||||
}
|
||||
|
||||
template <class TiledCopy, class ThrIdx>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(ThrCopy<TiledCopy, ThrIdx> const&)
|
||||
{
|
||||
print(TiledCopy{});
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
@@ -655,7 +691,6 @@ print_latex_copy(LayoutS const& S, ThrIDS const& TS, // (m,n) -> (tid,vid) and
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
#include <cute/atom/copy_traits_sm75.hpp>
|
||||
#include <cute/atom/copy_traits_sm80.hpp>
|
||||
#include <cute/atom/copy_traits_sm90.hpp>
|
||||
|
||||
@@ -32,11 +32,30 @@
|
||||
|
||||
#include <cute/arch/copy.hpp>
|
||||
|
||||
#include <cute/layout.hpp>
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
/**
|
||||
* concept Copy_Traits
|
||||
* {
|
||||
* using ThrID = // Logical thread id (tid) -> tidx
|
||||
*
|
||||
* using SrcLayout = // (Logical src thread id (tid), Logical src value id (vid)) -> bit
|
||||
* using DstLayout = // (Logical dst thread id (tid), Logical dst value id (vid)) -> bit
|
||||
* using RefLayout = // (Logical ref thread id (tid), Logical ref value id (vid)) -> bit
|
||||
* };
|
||||
*
|
||||
* The abstract bit ordering of the Copy_Traits (the codomain of SrcLayout, DstLayout, and RefLayout)
|
||||
* is arbitrary and only used to construct maps
|
||||
* (ref-tid,ref-vid) -> (src-tid,src-vid)
|
||||
* (ref-tid,ref-vid) -> (dst-tid,dst-vid)
|
||||
* in TiledCopy. The Layout_TV in TiledCopy is in accordance with the RefLayout of a Traits, then mapped to
|
||||
* the Src or Dst (tid,vid) representation on demand.
|
||||
*
|
||||
*/
|
||||
|
||||
template <class CopyOperation, class... CopyOpArgs>
|
||||
struct Copy_Traits
|
||||
{
|
||||
@@ -73,4 +92,40 @@ struct Copy_Traits<DefaultCopy>
|
||||
using RefLayout = SrcLayout;
|
||||
};
|
||||
|
||||
//
|
||||
// Generic copy_unpack for any Copy_Traits
|
||||
//
|
||||
template <class Operation, class... Args,
|
||||
class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits<Operation, Args...> const&,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
{
|
||||
// Specializations can generalize on these checks
|
||||
//static_assert(is_smem<TS>::value, "Expected smem for this Copy_Traits<Operation>");
|
||||
//static_assert(is_rmem<TD>::value, "Expected rmem for this Copy_Traits<Operation>");
|
||||
|
||||
using RegistersSrc = typename Operation::SRegisters;
|
||||
using RegistersDst = typename Operation::DRegisters;
|
||||
using RegTypeSrc = typename remove_extent<RegistersSrc>::type;
|
||||
using RegTypeDst = typename remove_extent<RegistersDst>::type;
|
||||
constexpr int RegNumSrc = extent<RegistersSrc>::value;
|
||||
constexpr int RegNumDst = extent<RegistersDst>::value;
|
||||
|
||||
Tensor rS = recast<RegTypeSrc>(src);
|
||||
Tensor rD = recast<RegTypeDst>(dst);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rS) == Int<RegNumSrc>{},
|
||||
"In CopyAtom, src layout doesn't vectorize into registers. This src layout is incompatible with this tiled copy.");
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumDst>{},
|
||||
"In CopyAtom, dst layout doesn't vectorize into registers. This dst layout is incompatible with this tiled copy.");
|
||||
|
||||
detail::explode(Operation::copy,
|
||||
rS, make_int_sequence<RegNumSrc>{},
|
||||
rD, make_int_sequence<RegNumDst>{});
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -30,14 +30,18 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
#include <cuda.h>
|
||||
#endif
|
||||
|
||||
#include <cute/arch/copy_sm90_desc.hpp>
|
||||
#include <cute/arch/copy_sm90_tma.hpp>
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
#include <cute/atom/copy_atom.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
@@ -142,7 +146,7 @@ struct Copy_Traits<SM90_TMA_LOAD, NumBits, GmemStrides>
|
||||
return {tma_desc_, tma_mbar};
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -257,7 +261,7 @@ struct Copy_Traits<SM90_TMA_LOAD_MULTICAST, NumBits, GmemStrides>
|
||||
return {tma_desc_, tma_load_mbar, multicast_mask};
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -300,6 +304,13 @@ struct Copy_Traits<SM90_TMA_STORE, NumBits, GmemStrides>
|
||||
TmaDescriptor tma_desc_;
|
||||
GmemStrides g_stride_;
|
||||
|
||||
// Return TmaDescriptor/TensorMap
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
TmaDescriptor const*
|
||||
get_tma_descriptor() const {
|
||||
return &tma_desc_;
|
||||
}
|
||||
|
||||
// Generate the TMA coord tensor
|
||||
template <class GShape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -352,47 +363,491 @@ struct Copy_Traits<SM90_TMA_STORE, NumBits, GmemStrides>
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////// BULK COPY //////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <class NumBits, class... OpArgs>
|
||||
struct Copy_Traits<SM90_BULK_COPY_G2S, NumBits, OpArgs...>
|
||||
{
|
||||
static_assert(int32_t(NumBits::value / 8) % 16 == 0,
|
||||
"Bulk Copy requires copy vector size align to 16B.");
|
||||
|
||||
using ThrID = Layout<_1>;
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
|
||||
// SM90_BULK_COPY_G2S arguments
|
||||
// 0: uint64_t* bulk_load_memory_barrier
|
||||
cute::tuple<OpArgs...> bulk_load_mbar_;
|
||||
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE friend constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits const& traits,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
{
|
||||
static_assert(is_same<cute::tuple<OpArgs...>, cute::tuple<uint64_t*>>::value,
|
||||
"Extra arguments not set. Set .with() before use.");
|
||||
static_assert(is_gmem<TS>::value, "Expected gmem src for SM90_BULK_COPY_G2S");
|
||||
static_assert(is_smem<TD>::value, "Expected smem dst for SM90_BULK_COPY_G2S");
|
||||
SM90_BULK_COPY_G2S::copy(src.data().get(), *get<0>(traits.bulk_load_mbar_),
|
||||
dst.data().get(), int32_t(NumBits::value / 8));
|
||||
}
|
||||
|
||||
// Record the memory barrier for the instruction
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_BULK_COPY_G2S, NumBits, uint64_t*>
|
||||
with(uint64_t& bulk_mbar) const {
|
||||
return {{&bulk_mbar}};
|
||||
}
|
||||
};
|
||||
|
||||
template <class NumBits>
|
||||
struct Copy_Traits<SM90_BULK_COPY_S2G, NumBits>
|
||||
{
|
||||
static_assert(int32_t(NumBits::value / 8) % 16 == 0,
|
||||
"Bulk Copy requires copy vector size align to 16B.");
|
||||
|
||||
using ThrID = Layout<_1>;
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape<_1,NumBits>>;
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
|
||||
template <class TS, class SLayout,
|
||||
class TD, class DLayout>
|
||||
CUTE_HOST_DEVICE friend constexpr
|
||||
void
|
||||
copy_unpack(Copy_Traits const& traits,
|
||||
Tensor<TS,SLayout> const& src,
|
||||
Tensor<TD,DLayout> & dst)
|
||||
{
|
||||
static_assert(is_smem<TS>::value, "Expected smem src for SM90_BULK_COPY_S2G");
|
||||
static_assert(is_gmem<TD>::value, "Expected gmem dst for SM90_BULK_COPY_S2G");
|
||||
SM90_BULK_COPY_S2G::copy(src.data().get(), dst.data().get(), int32_t(NumBits::value / 8));
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Placeholder for the bulk copy algorithm's default, auto-vectorizing behavior
|
||||
//
|
||||
|
||||
template <class... OpArgs>
|
||||
struct Copy_Traits<SM90_BULK_COPY_AUTO, OpArgs...>
|
||||
{
|
||||
// Logical thread id to thread idx (one-thread)
|
||||
using ThrID = Layout<_1>;
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape<_1,_1>, Stride<_0,_0>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape<_1,_1>, Stride<_0,_0>>;
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
|
||||
// SM90_UBULK_COPY arguments
|
||||
// 0: uint64_t* bulk_load_memory_barrier [if this is a BULK_LOAD_G2S]
|
||||
cute::tuple<OpArgs...> opargs_;
|
||||
|
||||
// Record the memory barrier for the instruction
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
Copy_Traits<SM90_BULK_COPY_AUTO, uint64_t*>
|
||||
with(uint64_t& bulk_mbar) const {
|
||||
return {{&bulk_mbar}};
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// MAKE_TMA_COPY and related
|
||||
//
|
||||
|
||||
template <int B, int M, int S, class Offset, class SLayout>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout>)
|
||||
namespace detail
|
||||
{
|
||||
static_assert(M == 4, "Expected 128b=16B=(2^4)B base swizzle.");
|
||||
static_assert(S == 3, "Unsupported layout swizzle");
|
||||
|
||||
switch (B) {
|
||||
default: static_assert(0 <= B && B <= 3, "Expected B = 0,1,2, or 3. Unsupported layout swizzle.");
|
||||
case 3: return TMA::SmemSwizzleBits::B128;
|
||||
case 2: return TMA::SmemSwizzleBits::B64;
|
||||
case 1: return TMA::SmemSwizzleBits::B32;
|
||||
case 0: return TMA::SmemSwizzleBits::DISABLE;
|
||||
}
|
||||
template <int B, int M, int S, class Offset, class SLayout>
|
||||
auto
|
||||
get_swizzle_portion(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout>)
|
||||
{
|
||||
return Swizzle<B,M,S>{};
|
||||
}
|
||||
|
||||
template <class Shape, class Stride>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(Layout<Shape,Stride>)
|
||||
auto
|
||||
get_swizzle_portion(Layout<Shape,Stride>)
|
||||
{
|
||||
return TMA::SmemSwizzleBits::DISABLE;
|
||||
return Swizzle<0,4,3>{};
|
||||
}
|
||||
|
||||
template <int B, int M, int S, class Offset, class SLayout>
|
||||
auto
|
||||
get_nonswizzle_layout(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout> const& slayout)
|
||||
get_nonswizzle_portion(ComposedLayout<Swizzle<B,M,S>,Offset,SLayout> const& slayout)
|
||||
{
|
||||
return slayout.layout_fn();
|
||||
}
|
||||
|
||||
template <class Shape, class Stride>
|
||||
auto
|
||||
get_nonswizzle_layout(Layout<Shape,Stride> const& slayout)
|
||||
get_nonswizzle_portion(Layout<Shape,Stride> const& slayout)
|
||||
{
|
||||
return slayout;
|
||||
}
|
||||
|
||||
template <int B, int M, int S>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(Swizzle<B,M,S>)
|
||||
{
|
||||
if constexpr (M == 4) {
|
||||
switch (B) {
|
||||
default: static_assert(0 <= B && B <= 3, "Expected B = 0,1,2, or 3 when M == 4. Unsupported layout swizzle.");
|
||||
case 3: return TMA::SmemSwizzleBits::B128;
|
||||
case 2: return TMA::SmemSwizzleBits::B64;
|
||||
case 1: return TMA::SmemSwizzleBits::B32;
|
||||
case 0: return TMA::SmemSwizzleBits::DISABLE;
|
||||
}
|
||||
} else
|
||||
{
|
||||
static_assert(M < 0, "Unsupported layout swizzle.");
|
||||
}
|
||||
}
|
||||
|
||||
template <class Layout>
|
||||
TMA::SmemSwizzleBits
|
||||
get_tma_swizzle_bits(Layout const& layout)
|
||||
{
|
||||
return get_tma_swizzle_bits(get_swizzle_portion(layout));
|
||||
}
|
||||
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
// Use a smem2gmode map to read through the GMEM tensor
|
||||
// and construct a TMA Descriptor for the resulting instruction
|
||||
template <class GEngine, class GLayout,
|
||||
class SShape, class SStride,
|
||||
int B, int M, int S>
|
||||
CUTE_HOST
|
||||
auto
|
||||
make_tma_copy_desc(Tensor<GEngine,GLayout> const& gtensor, // The original GMEM Tensor
|
||||
Layout<SShape,SStride> const& smem_inv, // smem_idx to flat gmode
|
||||
Swizzle<B,M,S> const& swizzle) // Swizzle fn on smem_idx
|
||||
{
|
||||
using T = typename GEngine::value_type;
|
||||
|
||||
auto flat_glayout = flatten(gtensor.layout());
|
||||
CUTE_STATIC_ASSERT_V(rank(flat_glayout) == rank(smem_inv));
|
||||
constexpr int rank_smem_inv = decltype(rank(smem_inv))::value;
|
||||
|
||||
auto tma_multimode = rank(flat_glayout) > Int<5>{};
|
||||
constexpr uint32_t tma_dim = cute::min(rank(flat_glayout), 5);;
|
||||
|
||||
//
|
||||
// TMA gmem desc info
|
||||
//
|
||||
|
||||
void* gmem_address = (void*) gtensor.data();
|
||||
|
||||
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};
|
||||
for_each(make_seq<rank_smem_inv>{}, [&](auto i) {
|
||||
auto e = stride<i>(smem_inv); // For g++-7.5, let it deduce e rather than fuse with below
|
||||
constexpr int j = decltype(e.mode())::value;
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
|
||||
// Problem stride
|
||||
uint64_t stride_j = stride<j>(flat_glayout) * sizeof(T);
|
||||
uint64_t old_stride = gmem_prob_stride[tma_i];
|
||||
gmem_prob_stride[tma_i] = gcd(gmem_prob_stride[tma_i], stride_j);
|
||||
|
||||
// Problem shape
|
||||
uint64_t shape_j = shape<j>(flat_glayout);
|
||||
if (gmem_prob_stride[tma_i] != 0) {
|
||||
// We're "resetting" this TMA mode and using it as a "multimode"
|
||||
// Recurrence: g_shape = (s_i - 1) * (d_i / gcd_j d_j) + 1
|
||||
gmem_prob_shape[tma_i] = (gmem_prob_shape[tma_i]-1) * (old_stride / gmem_prob_stride[tma_i])
|
||||
+ (shape_j-1) * (stride_j / gmem_prob_stride[tma_i])
|
||||
+ 1;
|
||||
} else {
|
||||
gmem_prob_shape[tma_i] = shape_j;
|
||||
}
|
||||
});
|
||||
|
||||
assert((reinterpret_cast<uint64_t>(gmem_address) & 0b1111) == 0); // Address must be 16B-aligned
|
||||
|
||||
assert(gmem_prob_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[0] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[1] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[1] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[2] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[2] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[3] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[3] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[4] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[4] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
|
||||
assert((gmem_prob_stride[0]) == sizeof(T)); // First stride is implicitly 1
|
||||
assert((gmem_prob_stride[1]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[1] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[2]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[2] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[3]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[3] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[4]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[4] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
|
||||
//
|
||||
// TMA smem desc info
|
||||
//
|
||||
|
||||
cute::array<uint32_t, 5> smem_box_shape = {1,1,1,1,1};
|
||||
cute::array<uint32_t, 5> smem_box_stride = {1,1,1,1,1};
|
||||
for_each(make_seq<rank_smem_inv>{}, [&](auto i) {
|
||||
uint32_t shape_i = shape<i>(smem_inv);
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
if (tma_multimode && tma_i == 4) {
|
||||
// We're "reusing" this TMA mode and using it as a "multimode"
|
||||
smem_box_shape[tma_i] = 1;
|
||||
} else {
|
||||
smem_box_shape[tma_i] = shape_i;
|
||||
}
|
||||
});
|
||||
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
|
||||
assert(smem_box_stride[0] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[0] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[1] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[1] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[2] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[2] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[3] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[3] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[4] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[4] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
|
||||
//
|
||||
// Construct the descriptor
|
||||
//
|
||||
|
||||
TmaDescriptor tma_desc = {0};
|
||||
|
||||
//
|
||||
// TMA general info
|
||||
//
|
||||
|
||||
#if (__CUDACC_VER_MAJOR__ >= 12)
|
||||
|
||||
CUtensorMapDataType tma_format = TMA::to_CUtensorMapDataType<T>();
|
||||
CUtensorMapInterleave tma_interleave = CU_TENSOR_MAP_INTERLEAVE_NONE;
|
||||
CUtensorMapL2promotion tma_l2Promotion = CU_TENSOR_MAP_L2_PROMOTION_NONE;
|
||||
CUtensorMapFloatOOBfill tma_oobFill = CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE;
|
||||
|
||||
// TMA smem swizzle type
|
||||
CUtensorMapSwizzle smem_swizzle = TMA::to_CUtensorMapSwizzle(get_tma_swizzle_bits(swizzle));
|
||||
CUresult result = cuTensorMapEncodeTiled(
|
||||
&tma_desc,
|
||||
tma_format,
|
||||
tma_dim,
|
||||
gmem_address,
|
||||
gmem_prob_shape.data(),
|
||||
gmem_prob_stride.data() + 1, // gmem_prob_stride[0] implicitly 1
|
||||
smem_box_shape.data(),
|
||||
smem_box_stride.data(),
|
||||
tma_interleave,
|
||||
smem_swizzle,
|
||||
tma_l2Promotion,
|
||||
tma_oobFill);
|
||||
|
||||
if (result != CUDA_SUCCESS) {
|
||||
std::cerr << "TMA Desc Addr: " << &tma_desc
|
||||
<< "\nformat " << tma_format
|
||||
<< "\ndim " << tma_dim
|
||||
<< "\ngmem_address " << gmem_address
|
||||
<< "\nglobalDim " << gmem_prob_shape
|
||||
<< "\nglobalStrides " << gmem_prob_stride
|
||||
<< "\nboxDim " << smem_box_shape
|
||||
<< "\nelementStrides " << smem_box_stride
|
||||
<< "\ninterleave " << tma_interleave
|
||||
<< "\nswizzle " << smem_swizzle
|
||||
<< "\nl2Promotion " << tma_l2Promotion
|
||||
<< "\noobFill " << tma_oobFill << std::endl;
|
||||
std::cerr << "Error: Failed to initialize the TMA descriptor " << result << std::endl;
|
||||
assert(false);
|
||||
}
|
||||
|
||||
#endif // (__CUDACC_VER_MAJOR__ >= 12)
|
||||
// Finally, get the inverse permutation of the E<i> bases for the mocked gmem stride
|
||||
auto gmem_stride_bases_flat = transform(make_seq<rank_smem_inv>{}, [&](auto i) {
|
||||
auto k = find(stride(smem_inv), E<i>{});
|
||||
// For gcc 7.5 -- avoid 'if constexpr'
|
||||
int32_t tma_coord_stride = int32_t(stride<i>(flat_glayout) * sizeof(T) / (gmem_prob_stride[4] != 0 ? gmem_prob_stride[4] : 16));
|
||||
return conditional_return(tma_multimode && (k >= Int<4>{}),
|
||||
E<4>{} * tma_coord_stride, // The 4th TMA mode is the multimode, use int32_t coord stride
|
||||
E<k>{});
|
||||
});
|
||||
|
||||
// Give that the profile of gtensor and fold it
|
||||
// NOTE: This is the only reason we want the original gtensor shape rather than the more intuitive flattened shape
|
||||
auto gmem_stride_bases = stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), gmem_stride_bases_flat),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{}))));
|
||||
|
||||
return make_tuple(tma_desc, gmem_stride_bases);
|
||||
}
|
||||
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
class TShape, class TStride,
|
||||
class VShape, class VStride>
|
||||
CUTE_HOST
|
||||
auto
|
||||
make_tma_copy_tiled(CopyOp,
|
||||
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 coord
|
||||
{
|
||||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
auto flat_glayout = flatten(gtensor.layout());
|
||||
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_portion(slayout));
|
||||
// trunc_smem_idx -> trunc_smem_coord
|
||||
|
||||
// Map from smem idx to a gmem mode
|
||||
auto sidx_to_gmode = coalesce(composition(cta_v_map, inv_smem_layout));
|
||||
|
||||
// Truncate any incompatibilities
|
||||
auto smem_rank = find_if(stride(sidx_to_gmode), [](auto e) {
|
||||
auto v = basis_value(e);
|
||||
return not is_constant<1,decltype(v)>{};
|
||||
});
|
||||
static_assert(smem_rank > 0, "Could not find a common smem-gmem vectorization for TMA. Do they have a common majorness?");
|
||||
// TMA uses a maximum of 5 modes
|
||||
// If the gtensor has more than 5 modes, we need to reserve the last TMA-mode as a "multimode"
|
||||
constexpr int smem_tma_rank = cute::min(int(smem_rank), (rank(flat_glayout) > Int<5>{} ? 4 : 5));
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto sidx_to_gmode_trunc = take<0,smem_tma_rank>(sidx_to_gmode);
|
||||
|
||||
// Split according to the portion each multicast CTA will be responsible for
|
||||
auto sidx_to_gmode_vt = logical_divide(sidx_to_gmode_trunc, shape_div(size(sidx_to_gmode_trunc), cosize(cta_t_map)));
|
||||
|
||||
#if 0
|
||||
print("g_layout : "); print(gtensor.layout()); 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_smem : "); print(inv_smem_layout); print("\n");
|
||||
print("sidx_to_gmode : "); print(sidx_to_gmode); print("\n");
|
||||
|
||||
print("sidx_to_gmode_trunc : "); print(sidx_to_gmode_trunc); print("\n");
|
||||
print("sidx_to_gmode_vt : "); print(sidx_to_gmode_vt); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
auto flat_gbasis = make_basis_like(shape(flat_glayout));
|
||||
|
||||
// Fold the flat_gbasis into the glayout
|
||||
auto glayout_basis = make_layout(shape(gtensor),
|
||||
stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), flat_gbasis),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{})))));
|
||||
|
||||
// Tile the modes of gtensor with the truncated cta_v_map o inv_smem_layout_trunc
|
||||
auto tma_layout_v_trunc = flatten(composition(glayout_basis, layout<0>(sidx_to_gmode_vt)));
|
||||
|
||||
// Append any missing basis on the end as size-1 modes b/c they got truncated
|
||||
// NOTE This is essentially ArithmeticTuple complement...
|
||||
auto missing_basis = fold(stride(tma_layout_v_trunc), flat_gbasis, [](auto init, auto e) {
|
||||
auto k = find(init, e);
|
||||
return remove<k>(init);
|
||||
});
|
||||
|
||||
// The appended map from truncated smem codomain to gmem mode: trunc_smem_idx -> gmem_mode
|
||||
auto tma_layout_v = make_layout(flatten(cute::make_tuple(tma_layout_v_trunc.shape(), repeat<rank(missing_basis)>(Int<1>{}))),
|
||||
flatten(cute::make_tuple(tma_layout_v_trunc.stride(), missing_basis)));
|
||||
|
||||
#if 0
|
||||
print("flat_gbasis : "); print(flat_gbasis); print("\n");
|
||||
print("missing_b : "); print(missing_basis); print("\n");
|
||||
print("tma_layout_v : "); print(tma_layout_v); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// Construct the TMA Desc and GMEM mode ordering
|
||||
//
|
||||
|
||||
auto [tma_desc, gmem_stride_bases] = detail::make_tma_copy_desc(gtensor, tma_layout_v, get_swizzle_portion(slayout));
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
constexpr int num_bits = decltype(size<0>(sidx_to_gmode_vt))::value * sizeof(T) * 8;
|
||||
using Traits = Copy_Traits<CopyOp, Int<num_bits>, decltype(gmem_stride_bases)>;
|
||||
|
||||
#if 0
|
||||
print("num_bits : "); print(num_bits); print("\n");
|
||||
print("g_stride_bases: "); print(gmem_stride_bases); print("\n");
|
||||
#endif
|
||||
|
||||
Traits tma_traits{tma_desc, gmem_stride_bases};
|
||||
|
||||
//
|
||||
// Construct the TiledCopy
|
||||
//
|
||||
|
||||
auto cta_tiler = product_each(shape(cta_v_map));
|
||||
|
||||
// (CTA V, CTA T) -> smem_coord
|
||||
auto layout_vt = composition(inv_smem_layout, make_layout(shape(sidx_to_gmode_vt)));
|
||||
// Scale that up to cover all of the smem_coords
|
||||
auto layout_VT = tile_to_shape(layout_vt, make_shape(size(cta_v_map)/size<1>(layout_vt), size<1>(layout_vt)));
|
||||
// Flip it and change the domain of the T from logical thr to thr_idx
|
||||
auto layout_TV = make_layout(composition(layout<1>(layout_VT), cta_t_map), layout<0>(layout_VT));
|
||||
|
||||
#if 0
|
||||
print("cta_tiler : "); print(cta_tiler); print("\n");
|
||||
print("layout_VT : "); print(layout_VT); print("\n");
|
||||
print("layout_TV : "); print(layout_TV); print("\n");
|
||||
#endif
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
return TiledCopy<Copy_Atom<Traits,T>, decltype(layout_TV), decltype(cta_tiler)>{tma_traits};
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
/** Make a CuTe CTA-collective TiledCopy for a TMA operation.
|
||||
*
|
||||
* @param CopyOp The target copy operation: SM90_TMA_LOAD, SM90_TMA_LOAD_MULTICAST, SM90_TMA_STORE
|
||||
@@ -465,6 +920,7 @@ get_nonswizzle_layout(Layout<Shape,Stride> const& slayout)
|
||||
|
||||
copy(tma.with(barrier, mcast_mask), tAgA, tAsA); // copy with supporting TMA params
|
||||
*/
|
||||
#if !defined(__CUDACC_RTC__)
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
@@ -472,307 +928,18 @@ template <class CopyOp,
|
||||
class Cluster_Size>
|
||||
CUTE_HOST
|
||||
auto
|
||||
make_tma_copy(CopyOp,
|
||||
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)
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
static_assert((std::is_same<CopyOp, SM90_TMA_LOAD>::value && is_constant<1, Cluster_Size>::value) ||
|
||||
(std::is_same<CopyOp, SM90_TMA_LOAD_MULTICAST>::value) ||
|
||||
(std::is_same<CopyOp, SM90_TMA_STORE>::value && is_constant<1, Cluster_Size>::value));
|
||||
|
||||
using T = typename Tensor<GEngine,GLayout>::value_type;
|
||||
|
||||
//
|
||||
// TMA parameter checking
|
||||
//
|
||||
|
||||
auto flat_glayout = flatten(gtensor.layout());
|
||||
|
||||
CUTE_STATIC_ASSERT_V(rank(flatten(cta_tile)) <= Int<5>{},
|
||||
"CTA_Tile cannot have more than five modes, TMA arch restriction.");
|
||||
CUTE_STATIC_ASSERT_V(rank(flat_glayout) <= Int<5>{} || rank(flatten(cta_tile)) <= Int<4>{},
|
||||
"If GTensor has more than five modes, then CTA_Tile cannot have more than four modes. TMA multimode.");
|
||||
CUTE_STATIC_ASSERT_V(compatible(product_each(shape(slayout)), shape(cta_tile)),
|
||||
"CTA_Tile must be compatible with SLayout.");
|
||||
CUTE_STATIC_ASSERT_V(is_integral<Cluster_Size>{} && has_single_bit(cluster_size) && cluster_size <= Int<16>{},
|
||||
"Expecting a pow2 integral Cluster_Size leq 16.");
|
||||
CUTE_STATIC_ASSERT_V(size(slayout) % cluster_size == Int<0>{},
|
||||
"ClusterShape must divide domain size of slayout.");
|
||||
|
||||
//
|
||||
// TMA slayout manipulation
|
||||
//
|
||||
|
||||
auto tma_multimode = rank(flat_glayout) > Int<5>{};
|
||||
|
||||
// Invert the smem to get the largest contiguous vector in the smem layout
|
||||
auto inv_smem_layout = right_inverse(get_nonswizzle_layout(slayout));
|
||||
// trunc_smem_idx -> trunc_smem_coord
|
||||
|
||||
// Map from smem idx to a gmem mode
|
||||
auto sidx_to_gmode = flatten(composition(make_identity_layout(cta_tile), inv_smem_layout));
|
||||
|
||||
// Truncate any incompatibilities
|
||||
auto smem_rank = find_if(stride(sidx_to_gmode), [](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 smem-gmem vectorization for TMA.");
|
||||
constexpr int smem_tma_rank = cute::min(int(smem_rank), (tma_multimode ? 4 : 5));
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto sidx_to_gmode_cluster_trunc = take<0,smem_tma_rank>(sidx_to_gmode);
|
||||
// Keep only the portion each multicast CTA will be responsible for
|
||||
auto sidx_to_gmode_cta_trunc = composition(sidx_to_gmode_cluster_trunc, shape_div(size(sidx_to_gmode_cluster_trunc), cluster_size));
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
auto flat_gbasis = make_basis_like(shape(flat_glayout));
|
||||
|
||||
// Fold the flat_gbasis into the glayout
|
||||
auto glayout_basis = make_layout(shape(gtensor),
|
||||
stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), flat_gbasis),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{})))));
|
||||
|
||||
// Tile the modes of gtensor with cta_tile
|
||||
auto cta_glayout_basis = composition(glayout_basis, cta_tile);
|
||||
|
||||
// Check that the cta_tile selects modes from gtensor properly
|
||||
for_each(flatten(stride(cta_glayout_basis)), [](auto d) {
|
||||
static_assert(is_constant<1, decltype(d.value())>::value,
|
||||
"CTA_Tile does not faithfully partition the GMEM, it should select the number of elements from each mode of glayout.");
|
||||
});
|
||||
|
||||
// Tile the modes of gtensor again with the truncated cta_tile o inv_smem_layout
|
||||
auto tma_layout_cta_trunc = flatten(composition(glayout_basis, sidx_to_gmode_cta_trunc));
|
||||
|
||||
// Append any missing basis on the end as size-1 modes b/c they got truncated
|
||||
auto missing_basis = fold(stride(tma_layout_cta_trunc), flat_gbasis, [](auto init, auto e){
|
||||
auto k = find(init, e);
|
||||
return remove<k>(init);
|
||||
});
|
||||
|
||||
// The appended map from truncated smem codomain to gmem mode: trunc_smem_idx -> gmem_mode
|
||||
auto tma_layout_cta = flatten(make_layout(tma_layout_cta_trunc,
|
||||
make_layout(repeat<rank(missing_basis)>(Int<1>{}), missing_basis)));
|
||||
|
||||
#if 0
|
||||
print("g_layout : "); print(gtensor.layout()); print("\n");
|
||||
print("s_layout : "); print(slayout); print("\n");
|
||||
print("cta_tile : "); print(cta_tile); print("\n");
|
||||
print("cluster_size : "); print(cluster_size); print("\n");
|
||||
print("flat_gbasis : "); print(flat_gbasis); print("\n");
|
||||
print("cta_glayout : "); print(cta_glayout_basis); print("\n");
|
||||
print("inv_smem : "); print(inv_smem_layout); print("\n");
|
||||
print("sidx_to_gmode : "); print(sidx_to_gmode); print("\n");
|
||||
print("missing_b : "); print(missing_basis); print("\n");
|
||||
print("tma_layout_cta: "); print(tma_layout_cta); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gmem desc info
|
||||
//
|
||||
|
||||
constexpr int TmaRANK = cute::min(rank(flat_glayout), 5);
|
||||
void* gmem_address = (void*) gtensor.data();
|
||||
|
||||
cute::array<cuuint64_t, 5> gmem_prob_shape = {1,1,1,1,1};
|
||||
cute::array<cuuint64_t, 5> gmem_prob_stride = {0,0,0,0,0};
|
||||
for_each(make_seq<rank(tma_layout_cta)>{}, [&](auto i) {
|
||||
// NOTE : WAR g++-7.3.5, let it deduce e rather than fuse with below
|
||||
auto e = stride<i>(tma_layout_cta);
|
||||
constexpr int j = decltype(e.mode())::value;
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
|
||||
// Problem stride
|
||||
uint64_t stride_j = stride<j>(flat_glayout) * sizeof(T);
|
||||
uint64_t old_stride = gmem_prob_stride[tma_i];
|
||||
gmem_prob_stride[tma_i] = gcd(gmem_prob_stride[tma_i], stride_j);
|
||||
|
||||
// Problem shape
|
||||
uint64_t shape_j = shape<j>(flat_glayout);
|
||||
if (gmem_prob_stride[tma_i] != 0) {
|
||||
// We're "resetting" this TMA mode and using it as a "multimode"
|
||||
// Recurrence: g_shape = (s_i - 1) * (d_i / gcd_j d_j) + 1
|
||||
gmem_prob_shape[tma_i] = (gmem_prob_shape[tma_i]-1) * (old_stride / gmem_prob_stride[tma_i])
|
||||
+ (shape_j-1) * (stride_j / gmem_prob_stride[tma_i])
|
||||
+ 1;
|
||||
} else {
|
||||
gmem_prob_shape[tma_i] = shape_j;
|
||||
}
|
||||
});
|
||||
|
||||
assert((reinterpret_cast<uint64_t>(gmem_address) & 0b1111) == 0); // Address must be 16B-aligned
|
||||
|
||||
assert(gmem_prob_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[0] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[1] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[1] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[2] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[2] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[3] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[3] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
assert(gmem_prob_shape[4] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(gmem_prob_shape[4] <= (uint64_t(1) << 32)); // Size must be max 2^32
|
||||
|
||||
assert((gmem_prob_stride[0]) == sizeof(T)); // First stride is implicitly 1
|
||||
assert((gmem_prob_stride[1]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[1] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[2]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[2] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[3]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[3] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
assert((gmem_prob_stride[4]) < (uint64_t(1) << 40)); // Stride must be max 2^40
|
||||
assert((gmem_prob_stride[4] & 0b1111) == 0); // Stride must be multiple of 16B (128b)
|
||||
|
||||
//
|
||||
// TMA smem desc info
|
||||
//
|
||||
|
||||
// TMA smem box size
|
||||
cute::array<cuuint32_t, 5> smem_box_shape = {1,1,1,1,1};
|
||||
for_each(make_seq<rank(tma_layout_cta)>{}, [&](auto i) {
|
||||
uint32_t shape_i = shape<i>(tma_layout_cta);
|
||||
constexpr int tma_i = i < 5 ? i : 4;
|
||||
if (tma_multimode && tma_i == 4) {
|
||||
// We're "reusing" this TMA mode and using it as a "multimode"
|
||||
smem_box_shape[tma_i] = 1;
|
||||
} else {
|
||||
smem_box_shape[tma_i] = shape_i;
|
||||
}
|
||||
});
|
||||
|
||||
// TMA smem mode strides
|
||||
[[maybe_unused]] cute::array<cuuint32_t, 5> smem_box_stride = {1,1,1,1,1};
|
||||
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
assert(smem_box_shape[0] >= (uint64_t(1))); // Size must be min 1
|
||||
assert(smem_box_shape[0] <= (uint64_t(1) << 8)); // Size must be max 2^8
|
||||
|
||||
assert(smem_box_stride[0] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[0] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[1] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[1] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[2] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[2] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[3] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[3] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
assert(smem_box_stride[4] >= (uint32_t(1))); // Stride must be min 1
|
||||
assert(smem_box_stride[4] <= (uint32_t(8))); // Stride must be max 2^3
|
||||
|
||||
//
|
||||
// Construct the descriptor
|
||||
//
|
||||
|
||||
TmaDescriptor tma_desc = {0};
|
||||
|
||||
#if (__CUDACC_VER_MAJOR__ >= 12)
|
||||
|
||||
//
|
||||
// TMA general info
|
||||
//
|
||||
|
||||
cuuint32_t tma_dim = TmaRANK;
|
||||
CUtensorMapDataType tma_format = TMA::to_CUtensorMapDataType<T>();
|
||||
CUtensorMapInterleave tma_interleave = CU_TENSOR_MAP_INTERLEAVE_NONE;
|
||||
CUtensorMapL2promotion tma_l2Promotion = CU_TENSOR_MAP_L2_PROMOTION_NONE;
|
||||
CUtensorMapFloatOOBfill tma_oobFill = CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE;
|
||||
|
||||
// TMA smem swizzle type
|
||||
CUtensorMapSwizzle smem_swizzle = TMA::to_CUtensorMapSwizzle(get_tma_swizzle_bits(slayout));
|
||||
|
||||
CUresult result = cuTensorMapEncodeTiled(
|
||||
&tma_desc,
|
||||
tma_format,
|
||||
tma_dim,
|
||||
gmem_address,
|
||||
gmem_prob_shape.data(),
|
||||
gmem_prob_stride.data() + 1, // gmem_prob_stride[0] implicitly 1
|
||||
smem_box_shape.data(),
|
||||
smem_box_stride.data(),
|
||||
tma_interleave,
|
||||
smem_swizzle,
|
||||
tma_l2Promotion,
|
||||
tma_oobFill);
|
||||
|
||||
if (result != CUDA_SUCCESS) {
|
||||
std::cerr << "TMA Desc Addr: " << &tma_desc
|
||||
<< "\nformat " << tma_format
|
||||
<< "\ndim " << tma_dim
|
||||
<< "\ngmem_address " << gmem_address
|
||||
<< "\nglobalDim " << gmem_prob_shape
|
||||
<< "\nglobalStrides " << gmem_prob_stride
|
||||
<< "\nboxDim " << smem_box_shape
|
||||
<< "\nelementStrides " << smem_box_stride
|
||||
<< "\ninterleave " << tma_interleave
|
||||
<< "\nswizzle " << smem_swizzle
|
||||
<< "\nl2Promotion " << tma_l2Promotion
|
||||
<< "\noobFill " << tma_oobFill << std::endl;
|
||||
std::cerr << "Error: Failed to initialize the TMA descriptor " << result << std::endl;
|
||||
assert(false);
|
||||
}
|
||||
#endif // (__CUDACC_VER_MAJOR__ >= 12)
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
// Finally, get the inverse permutation of the E<i> bases for the mocked gmem stride
|
||||
auto gmem_stride_bases_flat = transform(make_seq<rank(tma_layout_cta)>{}, [&](auto i) {
|
||||
auto k = find(stride(tma_layout_cta), E<i>{});
|
||||
// NOTE: gcc 7.3.5 WAR -- avoid if constexpr
|
||||
int32_t tma_coord_stride = int32_t(stride<i>(flat_glayout) * sizeof(T) / (gmem_prob_stride[4] != 0 ? gmem_prob_stride[4] : 16));
|
||||
return conditional_return(tma_multimode && (k >= Int<4>{}),
|
||||
E<4>{} * tma_coord_stride, // The 4th TMA mode is the multimode, use int32_t coord stride
|
||||
E<k>{});
|
||||
});
|
||||
|
||||
// Give that the profile of gtensor and fold it
|
||||
auto gmem_stride_bases = stride(composition(make_layout(repeat_like(shape(flat_glayout), Int<2>{}), gmem_stride_bases_flat),
|
||||
make_layout(repeat_like(shape(gtensor), Int<2>{}))));
|
||||
|
||||
constexpr int num_bits = size(sidx_to_gmode_cta_trunc) * sizeof(T) * 8;
|
||||
using Traits = Copy_Traits<CopyOp, Int<num_bits>, decltype(gmem_stride_bases)>;
|
||||
|
||||
#if 0
|
||||
print("num_bits : "); print(num_bits); print("\n");
|
||||
print("g_stride_bases: "); print(gmem_stride_bases); print("\n");
|
||||
#endif
|
||||
|
||||
//
|
||||
// Construct the TiledCopy
|
||||
//
|
||||
|
||||
// The ThrVal layout for 1 TMA instruction within cta_tile
|
||||
auto layout_tv_1 = composition(inv_smem_layout, make_layout(make_shape(cluster_size, size(sidx_to_gmode_cta_trunc)), GenRowMajor{}));
|
||||
// The ThrVal layout for N TMA instructions within cta_tile
|
||||
auto layout_tv = tile_to_shape(layout_tv_1, make_shape(cluster_size, size(cta_tile)/cluster_size));
|
||||
|
||||
#if 0
|
||||
print("layout_tv : "); print(layout_tv); print("\n");
|
||||
#endif
|
||||
|
||||
// If CTA_Tile and SLayout are incompatible, product_each makes sure
|
||||
// that the TiledCopy generates consistent accesses.
|
||||
auto cta_tile_tiled = [&]() {
|
||||
if constexpr (compatible(shape(CTA_Tile{}), shape(SLayout{}))) {
|
||||
return cta_tile;
|
||||
} else {
|
||||
return product_each(cta_tile);
|
||||
}
|
||||
}();
|
||||
|
||||
return TiledCopy<Copy_Atom<Traits,T>, decltype(layout_tv), decltype(cta_tile_tiled)>{tma_desc, gmem_stride_bases};
|
||||
return detail::make_tma_copy_tiled(copy_op,
|
||||
gtensor,
|
||||
slayout,
|
||||
make_layout(cluster_size),
|
||||
make_identity_layout(cta_tile));
|
||||
}
|
||||
|
||||
// Explicit defaulting
|
||||
@@ -797,9 +964,10 @@ auto
|
||||
make_tma_copy(CopyOp const& copy_op,
|
||||
Tensor<GEngine,GLayout> const& gtensor,
|
||||
SLayout const& slayout,
|
||||
Cluster_Size const& cluster_size)
|
||||
Cluster_Size const& cluster_size)
|
||||
{
|
||||
return make_tma_copy(copy_op, gtensor, slayout, product_each(shape(slayout)), cluster_size);
|
||||
}
|
||||
#endif // !defined(__CUDACC_RTC__)
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
+113
-129
@@ -30,102 +30,17 @@
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/mma.hpp>
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
#include <cute/atom/mma_traits_sm90.hpp>
|
||||
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
|
||||
#include <cute/util/type_traits.hpp>
|
||||
|
||||
namespace cute {
|
||||
|
||||
// Generic mma_unpack for any MMA_Traits
|
||||
template <class Operation,
|
||||
class TD, class DLayout,
|
||||
class TA, class ALayout,
|
||||
class TB, class BLayout,
|
||||
class TC, class CLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
mma_unpack(MMA_Traits<Operation> const&,
|
||||
Tensor<TD, DLayout> & D,
|
||||
Tensor<TA, ALayout> const& A,
|
||||
Tensor<TB, BLayout> const& B,
|
||||
Tensor<TC, CLayout> const& C)
|
||||
{
|
||||
static_assert(is_rmem<TD>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TA>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TB>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TC>::value, "Expected registers in MMA_Atom::call");
|
||||
|
||||
// Register value types from the MMA_Operation register arrays
|
||||
using RegTypeD = typename std::remove_extent<typename Operation::DRegisters>::type;
|
||||
using RegTypeA = typename std::remove_extent<typename Operation::ARegisters>::type;
|
||||
using RegTypeB = typename std::remove_extent<typename Operation::BRegisters>::type;
|
||||
using RegTypeC = typename std::remove_extent<typename Operation::CRegisters>::type;
|
||||
constexpr int RegNumD = std::extent<typename Operation::DRegisters>::value;
|
||||
constexpr int RegNumA = std::extent<typename Operation::ARegisters>::value;
|
||||
constexpr int RegNumB = std::extent<typename Operation::BRegisters>::value;
|
||||
constexpr int RegNumC = std::extent<typename Operation::CRegisters>::value;
|
||||
|
||||
Tensor rA = recast<RegTypeA>(A);
|
||||
Tensor rB = recast<RegTypeB>(B);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rA) == Int<RegNumA>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rB) == Int<RegNumB>{});
|
||||
|
||||
if constexpr (std::is_same<RegTypeD, void>::value)
|
||||
{
|
||||
static_assert(std::is_same<typename TD::value_type, typename TC::value_type>::value, "GMMA C and D value_type must match.");
|
||||
static_assert(std::is_same<DLayout, CLayout>::value, "GMMA C and D layouts must match.");
|
||||
// assert((void*)&C == (void*)&D);
|
||||
|
||||
Tensor rC = recast<RegTypeC>(D); // NOTE: D and C are same, so use mutable D
|
||||
|
||||
//CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
|
||||
detail::explode(Operation::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
} else
|
||||
{
|
||||
Tensor rD = recast<RegTypeD>(D);
|
||||
Tensor rC = recast<RegTypeC>(C);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumD>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
|
||||
detail::explode(Operation::fma,
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeA_or_Default { using type = typename X::ElementAVal; };
|
||||
template <class X>
|
||||
struct FrgTypeA_or_Default<X,void_t<typename X::ElementAFrg>> { using type = typename X::ElementAFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeB_or_Default { using type = typename X::ElementBVal; };
|
||||
template <class X>
|
||||
struct FrgTypeB_or_Default<X,void_t<typename X::ElementBFrg>> { using type = typename X::ElementBFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeC_or_Default { using type = typename X::ElementCVal; };
|
||||
template <class X>
|
||||
struct FrgTypeC_or_Default<X,void_t<typename X::ElementCFrg>> { using type = typename X::ElementCFrg; };
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class... Args>
|
||||
struct MMA_Atom;
|
||||
|
||||
@@ -167,17 +82,6 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
return MMA_Atom<decltype(traits)>{traits};
|
||||
}
|
||||
|
||||
// Print thread and data layouts for debugging
|
||||
CUTE_HOST_DEVICE static
|
||||
void
|
||||
print_all()
|
||||
{
|
||||
print("ThrID: "); print(ThrID{}); print("\n");
|
||||
print("LayoutA_TV: "); print(LayoutA_TV{}); print("\n");
|
||||
print("LayoutB_TV: "); print(LayoutB_TV{}); print("\n");
|
||||
print("LayoutC_TV: "); print(LayoutC_TV{}); print("\n");
|
||||
}
|
||||
|
||||
//
|
||||
// Tensor call interfaces
|
||||
//
|
||||
@@ -232,7 +136,6 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
// Check that this tensor is likely already partitioned
|
||||
CUTE_STATIC_ASSERT_V(rank(ctensor) >= Int<3>{}); // VMN
|
||||
CUTE_STATIC_ASSERT_V(size<0>(ctensor) == size<1>(LayoutC_TV{}));
|
||||
|
||||
// C is a bit special because we are after accumulators here
|
||||
// The input/output type doesn't have to match the accumulator type
|
||||
//static_assert(std::is_same<ValTypeC, typename remove_cvref_t<CTensor>::value_type>::value, "Expecting ValTypeC type");
|
||||
@@ -249,12 +152,14 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
// Check that this tensor is likely already partitioned
|
||||
CUTE_STATIC_ASSERT_V(rank(atensor) >= Int<3>{}); // VMK
|
||||
CUTE_STATIC_ASSERT_V(size<0>(atensor) == size<1>(LayoutA_TV{}));
|
||||
static_assert(std::is_same<ValTypeA, typename remove_cvref_t<ATensor>::value_type>::value, "Expecting ValTypeA type");
|
||||
|
||||
if constexpr (has_dereference<FrgTypeA>::value) {
|
||||
return recast<FrgTypeA>(std::forward<ATensor>(atensor));
|
||||
// If the intended FrgTypeA is a view (of the current tensor), forward the whole
|
||||
static_assert(is_same<ValTypeA, typename remove_cvref_t<ATensor>::value_type>::value, "Expecting ValTypeA type");
|
||||
return make_tensor<FrgTypeA>(std::forward<ATensor>(atensor));
|
||||
} else {
|
||||
return make_tensor<FrgTypeA>(make_fragment_like(atensor.layout()));
|
||||
// Else, the intended FrgTypeA is a value type, construct a new tensor with a fragment layout
|
||||
return make_fragment_like<FrgTypeA>(atensor);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -268,12 +173,14 @@ struct MMA_Atom<MMA_Traits<Args...>>
|
||||
// Check that this tensor is likely already partitioned
|
||||
CUTE_STATIC_ASSERT_V(rank(btensor) >= Int<3>{}); // VNK
|
||||
CUTE_STATIC_ASSERT_V(size<0>(btensor) == size<1>(LayoutB_TV{}));
|
||||
static_assert(std::is_same<ValTypeB, typename remove_cvref_t<BTensor>::value_type>::value, "Expecting ValTypeB type");
|
||||
|
||||
if constexpr (has_dereference<FrgTypeB>::value) {
|
||||
return recast<FrgTypeB>(std::forward<BTensor>(btensor));
|
||||
// If the intended FrgTypeB is a view (of the current tensor), forward the whole
|
||||
static_assert(is_same<ValTypeB, typename remove_cvref_t<BTensor>::value_type>::value, "Expecting ValTypeB type");
|
||||
return make_tensor<FrgTypeB>(std::forward<BTensor>(btensor));
|
||||
} else {
|
||||
return make_tensor<FrgTypeB>(make_fragment_like(btensor.layout()));
|
||||
// Else, the intended FrgTypeB is a value type, construct a new tensor with a fragment layout
|
||||
return make_fragment_like<FrgTypeB>(btensor);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -607,7 +514,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_C(CTensor&& ctensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<CTensor>(ctensor).data(), thrfrg_C(ctensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<CTensor>(ctensor).data(), TiledMMA::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)>(_)));
|
||||
@@ -618,7 +525,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_A(ATensor&& atensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<ATensor>(atensor).data(), thrfrg_A(atensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<ATensor>(atensor).data(), TiledMMA::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)>(_)));
|
||||
@@ -629,7 +536,7 @@ struct ThrMMA : TiledMMA
|
||||
auto
|
||||
partition_B(BTensor&& btensor) const
|
||||
{
|
||||
auto thr_tensor = make_tensor(std::forward<BTensor>(btensor).data(), thrfrg_B(btensor.layout()));
|
||||
auto thr_tensor = make_tensor(std::forward<BTensor>(btensor).data(), TiledMMA::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)>(_)));
|
||||
@@ -675,8 +582,8 @@ make_tiled_mma(MMA_Atom<MMA_Op> const&,
|
||||
MMAValLayout const& val_layout = {},
|
||||
Permutations const& permutations = {})
|
||||
{
|
||||
auto thr_layout_mnk = append<3>(thr_layout, Layout<_1>{});
|
||||
auto val_layout_mnk = append<3>(val_layout, Layout<_1>{});
|
||||
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>,
|
||||
@@ -707,19 +614,24 @@ make_tiled_mma(MMA_Op const&,
|
||||
template <class... Args, class Shape_MN>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_fragment_C(TiledMMA<Args...>, Shape_MN shapeMN)
|
||||
partition_shape_C(TiledMMA<Args...> const& mma, Shape_MN const& shape_MN)
|
||||
{
|
||||
constexpr int R = rank_v<Shape_MN>;
|
||||
static_assert(R >= 2, "Must have at least rank-2");
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto V = shape<1>(typename TiledMMA<Args...>::AtomLayoutC_TV{});
|
||||
auto M = shape_div(size<0>(shape_MN), size<0>(atomMNK) * size<1>(thrVMNK));
|
||||
auto N = shape_div(size<1>(shape_MN), size<1>(atomMNK) * size<2>(thrVMNK));
|
||||
return tuple_cat(make_shape(V,M,N), take<2,R>(shape_MN));
|
||||
}
|
||||
|
||||
auto V = size<1>(typename TiledMMA<Args...>::AtomLayoutC_TV{});
|
||||
auto M = shape_div(size<0>(shapeMN), size<0>(atomMNK) * size<1>(thrVMNK));
|
||||
auto N = shape_div(size<1>(shapeMN), size<1>(atomMNK) * size<2>(thrVMNK));
|
||||
auto frg_shape = tuple_cat(make_shape(V,M,N), take<2,R>(shapeMN));
|
||||
|
||||
return make_tensor<typename TiledMMA<Args...>::FrgTypeC>(frg_shape);
|
||||
template <class... Args, class Shape_MN>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_fragment_C(TiledMMA<Args...> const& mma, Shape_MN const& shapeMN)
|
||||
{
|
||||
return make_tensor<typename TiledMMA<Args...>::FrgTypeC>(partition_shape_C(mma, shapeMN));
|
||||
}
|
||||
|
||||
// partition_fragment_A and partition_fragment_B often depend on the
|
||||
@@ -727,6 +639,36 @@ partition_fragment_C(TiledMMA<Args...>, Shape_MN shapeMN)
|
||||
// For these reasons, they should not be used in a static context.
|
||||
// See TiledMMA::get_slice(thr_idx).partition_fragment_A(tensorA) instead.
|
||||
|
||||
template <class... Args, class Shape_MK>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_shape_A(TiledMMA<Args...> const& mma, Shape_MK const& shape_MK)
|
||||
{
|
||||
constexpr int R = rank_v<Shape_MK>;
|
||||
static_assert(R >= 2, "Must have at least rank-2");
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto V = shape<1>(typename TiledMMA<Args...>::AtomLayoutA_TV{});
|
||||
auto M = shape_div(size<0>(shape_MK), size<0>(atomMNK) * size<1>(thrVMNK));
|
||||
auto K = shape_div(size<1>(shape_MK), size<2>(atomMNK) * size<3>(thrVMNK));
|
||||
return tuple_cat(make_shape(V,M,K), take<2,R>(shape_MK));
|
||||
}
|
||||
|
||||
template <class... Args, class Shape_NK>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
partition_shape_B(TiledMMA<Args...> const& mma, Shape_NK const& shape_NK)
|
||||
{
|
||||
constexpr int R = rank_v<Shape_NK>;
|
||||
static_assert(R >= 2, "Must have at least rank-2");
|
||||
auto atomMNK = typename TiledMMA<Args...>::AtomShape_MNK{};
|
||||
auto thrVMNK = typename TiledMMA<Args...>::ThrLayoutVMNK{};
|
||||
auto V = shape<1>(typename TiledMMA<Args...>::AtomLayoutB_TV{});
|
||||
auto N = shape_div(size<0>(shape_NK), size<1>(atomMNK) * size<2>(thrVMNK));
|
||||
auto K = shape_div(size<1>(shape_NK), size<2>(atomMNK) * size<3>(thrVMNK));
|
||||
return tuple_cat(make_shape(V,N,K), take<2,R>(shape_NK));
|
||||
}
|
||||
|
||||
//
|
||||
// Size
|
||||
//
|
||||
@@ -739,18 +681,62 @@ tile_size(TiledMMA<Args...> const& mma)
|
||||
return size<I...>(typename TiledMMA<Args...>::TiledShape_MNK{});
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
template <int... I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
tile_shape(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return shape<I...>(typename TiledMMA<Args...>::TiledShape_MNK{});
|
||||
}
|
||||
|
||||
template <int... I, class... Args>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
size(TiledMMA<Args...> const& mma)
|
||||
{
|
||||
return size(typename TiledMMA<Args...>::ThrLayoutVMNK{});
|
||||
return size<I...>(typename TiledMMA<Args...>::ThrLayoutVMNK{});
|
||||
}
|
||||
|
||||
//
|
||||
// Display utilities
|
||||
//
|
||||
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
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");
|
||||
}
|
||||
|
||||
template <class Atom, class TiledThr, class TiledVal, class TiledPerm>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(TiledMMA<Atom, TiledThr, TiledVal, 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(static_cast<Atom const&>(mma));
|
||||
}
|
||||
|
||||
template <class TiledMMA, class ThrVMNK>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print(ThrMMA<TiledMMA, ThrVMNK> const&)
|
||||
{
|
||||
print(TiledMMA{});
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
@@ -992,9 +978,9 @@ print_latex_mma(Shape_MNK const& shape_mnk,
|
||||
|
||||
printf(latex_header);
|
||||
|
||||
int M = size<0>(shape_mnk);
|
||||
int N = size<1>(shape_mnk);
|
||||
int K = size<2>(shape_mnk);
|
||||
constexpr int M = size<0>(shape_mnk);
|
||||
constexpr int N = size<1>(shape_mnk);
|
||||
constexpr int K = size<2>(shape_mnk);
|
||||
|
||||
// C starting at 0,0
|
||||
bool c_filled[M][N] = {};
|
||||
@@ -1070,12 +1056,10 @@ print_latex_mma(Shape_MNK const& shape_mnk,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cute/atom/mma_traits.hpp>
|
||||
#include <cute/atom/mma_traits_sm61.hpp>
|
||||
#include <cute/atom/mma_traits_sm70.hpp>
|
||||
#include <cute/atom/mma_traits_sm75.hpp>
|
||||
#include <cute/atom/mma_traits_sm80.hpp>
|
||||
#include <cute/atom/mma_traits_sm90.hpp>
|
||||
#include <cute/atom/mma_traits_sm90_gmma.hpp>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -32,11 +32,43 @@
|
||||
|
||||
#include <cute/arch/mma.hpp>
|
||||
|
||||
#include <cute/layout.hpp>
|
||||
#include <cute/tensor.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class X, class = void>
|
||||
struct supports_output_scaling { static constexpr bool value = false; };
|
||||
|
||||
template <class X>
|
||||
struct supports_output_scaling<X, void_t<decltype(declval<X>().accumulate_)>> { static constexpr bool value = true; };
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
/**
|
||||
* concept MMA_Traits
|
||||
* {
|
||||
* using ElementDVal = // Logical A-value type
|
||||
* using ElementAVal = // Logical B-value type
|
||||
* using ElementBVal = // Logical C-value type
|
||||
* using ElementCVal = // Logical D-value type (NOTE: Not used? Assumed == ElementDVal)
|
||||
*
|
||||
* using ElementAFrg = // A-type consumed by MMA (if ommitted, same as ElementAVal)
|
||||
* using ElementBFrg = // B_type consumed by MMA (if ommitted, same as ElementBVal)
|
||||
* using ElementCFrg = // C_type consumed by MMA (if ommitted, same as ElementCVal)
|
||||
*
|
||||
* using Shape_MNK = // Logical MxNxK shape of the MMA
|
||||
*
|
||||
* using ThrID = // Logical thread id (tid) -> tidx
|
||||
*
|
||||
* using ALayout = // (Logical thread id (tid), Logical value id (vid)) -> Flat MK-coord
|
||||
* using BLayout = // (Logical thread id (tid), Logical value id (vid)) -> Flat NK-coord
|
||||
* using CLayout = // (Logical thread id (tid), Logical value id (vid)) -> Flat MN-coord
|
||||
* };
|
||||
*/
|
||||
|
||||
template <class MMAOperation, class... MMAOpArgs>
|
||||
struct MMA_Traits
|
||||
{
|
||||
@@ -67,4 +99,110 @@ struct MMA_Traits<UniversalFMA<D,A,B,C>>
|
||||
using CLayout = Layout<Shape<_1,_1>>;
|
||||
};
|
||||
|
||||
//
|
||||
// Generic mma_unpack for any MMA_Traits
|
||||
//
|
||||
template <class MMA_Op, class... MMA_Args,
|
||||
class TD, class DLayout,
|
||||
class TA, class ALayout,
|
||||
class TB, class BLayout,
|
||||
class TC, class CLayout>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
mma_unpack(MMA_Traits<MMA_Op, MMA_Args...> const& traits,
|
||||
Tensor<TD, DLayout> & D,
|
||||
Tensor<TA, ALayout> const& A,
|
||||
Tensor<TB, BLayout> const& B,
|
||||
Tensor<TC, CLayout> const& C)
|
||||
{
|
||||
static_assert(is_rmem<TD>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TA>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TB>::value, "Expected registers in MMA_Atom::call");
|
||||
static_assert(is_rmem<TC>::value, "Expected registers in MMA_Atom::call");
|
||||
|
||||
// Register value types from the MMA_Operation register arrays
|
||||
using RegTypeD = typename remove_extent<typename MMA_Op::DRegisters>::type;
|
||||
using RegTypeA = typename remove_extent<typename MMA_Op::ARegisters>::type;
|
||||
using RegTypeB = typename remove_extent<typename MMA_Op::BRegisters>::type;
|
||||
using RegTypeC = typename remove_extent<typename MMA_Op::CRegisters>::type;
|
||||
using MMATraits = MMA_Traits<MMA_Op, MMA_Args...>;
|
||||
|
||||
constexpr int RegNumD = extent<typename MMA_Op::DRegisters>::value;
|
||||
constexpr int RegNumA = extent<typename MMA_Op::ARegisters>::value;
|
||||
constexpr int RegNumB = extent<typename MMA_Op::BRegisters>::value;
|
||||
constexpr int RegNumC = extent<typename MMA_Op::CRegisters>::value;
|
||||
|
||||
Tensor rA = recast<RegTypeA>(A);
|
||||
Tensor rB = recast<RegTypeB>(B);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rA) == Int<RegNumA>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rB) == Int<RegNumB>{});
|
||||
|
||||
if constexpr (is_same<RegTypeD, void>::value)
|
||||
{
|
||||
static_assert(is_same<typename TD::value_type, typename TC::value_type>::value, "GMMA C and D value_type must match.");
|
||||
static_assert(is_same<DLayout, CLayout>::value, "GMMA C and D layouts must match.");
|
||||
// assert((void*)&C == (void*)&D);
|
||||
|
||||
Tensor rC = recast<RegTypeC>(D); // NOTE: D and C are same, so use mutable D
|
||||
|
||||
//CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
|
||||
if constexpr (detail::supports_output_scaling<MMATraits>::value) {
|
||||
detail::explode_with_d_scaling(MMA_Op::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{},
|
||||
traits.accumulate_);
|
||||
}
|
||||
else {
|
||||
detail::explode(MMA_Op::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Tensor rD = recast<RegTypeD>(D);
|
||||
Tensor rC = recast<RegTypeC>(C);
|
||||
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumD>{});
|
||||
CUTE_STATIC_ASSERT_V(size(rC) == Int<RegNumC>{});
|
||||
if constexpr (detail::supports_output_scaling<MMATraits>::value) {
|
||||
detail::explode_with_d_scaling(MMA_Op::fma,
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{},
|
||||
traits.accumulate_);
|
||||
}
|
||||
else {
|
||||
detail::explode(MMA_Op::fma,
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeA_or_Default { using type = typename X::ElementAVal; };
|
||||
template <class X>
|
||||
struct FrgTypeA_or_Default<X,void_t<typename X::ElementAFrg>> { using type = typename X::ElementAFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeB_or_Default { using type = typename X::ElementBVal; };
|
||||
template <class X>
|
||||
struct FrgTypeB_or_Default<X,void_t<typename X::ElementBFrg>> { using type = typename X::ElementBFrg; };
|
||||
|
||||
template <class X, class = void>
|
||||
struct FrgTypeC_or_Default { using type = typename X::ElementCVal; };
|
||||
template <class X>
|
||||
struct FrgTypeC_or_Default<X,void_t<typename X::ElementCFrg>> { using type = typename X::ElementCFrg; };
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
} // namespace cute
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user