Updates for CUTLASS 3.5.0 (#1468)
This commit is contained in:
@@ -71,85 +71,103 @@ cooperative_copy(uint32_t const& tid,
|
||||
|
||||
// Precondition on tid in DEBUG
|
||||
assert(tid < NumThreads);
|
||||
// Precondition on pointer alignment in DEBUG
|
||||
assert(is_byte_aligned<max(MaxVecBits/8, 1u)>(raw_pointer_cast(src.data())));
|
||||
assert(is_byte_aligned<max(MaxVecBits/8, 1u)>(raw_pointer_cast(dst.data())));
|
||||
//
|
||||
// Determine val+thr vectorization based on src/dst size and number of threads
|
||||
// NOTE: This heuristic promotes parallelization over vectorization
|
||||
//
|
||||
|
||||
constexpr int elem_bits = sizeof_bits_v<typename SrcEngine::value_type>;
|
||||
// Fallback - slow path, naive copy, vectorization disabled
|
||||
if constexpr(size(SrcLayout{}) % NumThreads != 0) {
|
||||
int index = static_cast<int>(tid);
|
||||
CUTE_UNROLL
|
||||
for(int i = 0; i < ceil_div(size(SrcLayout{}), NumThreads); i++) {
|
||||
if(index < size(SrcLayout{})) {
|
||||
dst[index] = src[index];
|
||||
}
|
||||
index += NumThreads;
|
||||
}
|
||||
} else {
|
||||
// Fast path with vectorization
|
||||
|
||||
// The number of elements that can be vectorized in values
|
||||
constexpr int common_elem = decltype(max_common_vector(src, dst))::value;
|
||||
constexpr int common_bits = common_elem * elem_bits;
|
||||
constexpr int total_elem = decltype(size(src))::value;
|
||||
constexpr int total_bits = total_elem * elem_bits;
|
||||
static_assert(total_bits % NumThreads == 0);
|
||||
constexpr int total_bits_per_thr = total_bits / NumThreads;
|
||||
// If there are too many threads to allow a full elem copy, trunc the thrs and use elem_bits
|
||||
constexpr int max_vec_bits_by_thr = cute::max(elem_bits, total_bits_per_thr);
|
||||
// Precondition on pointer alignment in DEBUG
|
||||
assert(is_byte_aligned<max(MaxVecBits/8, 1u)>(raw_pointer_cast(src.data())));
|
||||
assert(is_byte_aligned<max(MaxVecBits/8, 1u)>(raw_pointer_cast(dst.data())));
|
||||
constexpr int elem_bits = sizeof_bits_v<typename SrcEngine::value_type>;
|
||||
|
||||
// Cap the vectorization to the common bits, the max_vec_bits_by_thr, and the MaxVecBits
|
||||
constexpr int vec_bits = cute::min(common_bits, max_vec_bits_by_thr, static_cast<int>(MaxVecBits));
|
||||
// Convert back to number of elements, safe_div
|
||||
static_assert((vec_bits % elem_bits) == 0);
|
||||
constexpr int vec_elem = vec_bits / elem_bits;
|
||||
//
|
||||
// Determine val+thr vectorization based on src/dst size and number of threads
|
||||
// NOTE: This heuristic promotes parallelization over vectorization
|
||||
//
|
||||
|
||||
// Use only part of threads if there's not enough work for all threads
|
||||
constexpr int vec_thrs = (total_elem % (vec_elem * NumThreads) == 0)
|
||||
? NumThreads
|
||||
: (total_elem / vec_elem);
|
||||
// The number of elements that can be vectorized in values
|
||||
constexpr int common_elem = decltype(max_common_vector(src, dst))::value;
|
||||
constexpr int common_bits = common_elem * elem_bits;
|
||||
constexpr int total_elem = decltype(size(src))::value;
|
||||
constexpr int total_bits = total_elem * elem_bits;
|
||||
static_assert(total_bits % NumThreads == 0);
|
||||
constexpr int total_bits_per_thr = total_bits / NumThreads;
|
||||
// If there are too many threads to allow a full elem copy, trunc the thrs and use elem_bits
|
||||
constexpr int max_vec_bits_by_thr = cute::max(elem_bits, total_bits_per_thr);
|
||||
|
||||
// The common layout of the two tensors that can be vectorized over threads
|
||||
// vidx -> coord
|
||||
auto common_layout = max_common_layout(get_nonswizzle_portion(src.layout()),
|
||||
get_nonswizzle_portion(dst.layout()));
|
||||
// Cap the vectorization to the common bits, the max_vec_bits_by_thr, and the MaxVecBits
|
||||
constexpr int vec_bits = cute::min(common_bits, max_vec_bits_by_thr, static_cast<int>(MaxVecBits));
|
||||
// Convert back to number of elements, safe_div
|
||||
static_assert((vec_bits % elem_bits) == 0);
|
||||
constexpr int vec_elem = vec_bits / elem_bits;
|
||||
|
||||
// Scale up the common_layout to cover the entire tensors
|
||||
// vidx -> coord
|
||||
auto full_perm = tile_to_shape(make_layout(common_layout), size(src));
|
||||
// Use only part of threads if there's not enough work for all threads
|
||||
constexpr int vec_thrs = (total_elem % (vec_elem * NumThreads) == 0)
|
||||
? NumThreads
|
||||
: (total_elem / vec_elem);
|
||||
static_assert(vec_thrs <= NumThreads);
|
||||
|
||||
// Create the Tiler
|
||||
// ((vid,tid),iter)
|
||||
auto layout_vt = logical_divide(full_perm, Layout<Shape<Int<vec_elem>, Int<vec_thrs>>>{});
|
||||
// The common layout of the two tensors that can be vectorized over threads
|
||||
// vidx -> coord
|
||||
auto common_layout = max_common_layout(get_nonswizzle_portion(src.layout()),
|
||||
get_nonswizzle_portion(dst.layout()));
|
||||
|
||||
// Apply and slice
|
||||
Tensor src_v = src.compose(layout_vt)(make_coord(_,tid),_);
|
||||
Tensor dst_v = dst.compose(layout_vt)(make_coord(_,tid),_);
|
||||
// Scale up the common_layout to cover the entire tensors
|
||||
// vidx -> coord
|
||||
auto full_perm = tile_to_shape(make_layout(common_layout), size(src));
|
||||
|
||||
// Should account for vec_bits < 8 and/or vec_elem <= 1
|
||||
// And also account for subbyte types, which could cause race conditions
|
||||
// Want to ENFORCE sufficient vectorization in those cases
|
||||
static_assert((vec_bits >= 8), "No support for subbyte copying");
|
||||
using VecType = uint_bit_t<vec_bits>;
|
||||
// Create the Tiler
|
||||
// ((vid,tid),iter)
|
||||
auto layout_vt = logical_divide(full_perm, Layout<Shape<Int<vec_elem>, Int<vec_thrs>>>{});
|
||||
|
||||
// Apply and slice
|
||||
Tensor src_v = src.compose(layout_vt)(make_coord(_,tid),_);
|
||||
Tensor dst_v = dst.compose(layout_vt)(make_coord(_,tid),_);
|
||||
|
||||
// Should account for vec_bits < 8 and/or vec_elem <= 1
|
||||
// And also account for subbyte types, which could cause race conditions
|
||||
// Want to ENFORCE sufficient vectorization in those cases
|
||||
static_assert((vec_bits >= 8), "No support for subbyte copying");
|
||||
using VecType = uint_bit_t<vec_bits>;
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print(" "); print("NumThreads: "); print(NumThreads); print("\n");
|
||||
print(" "); print("src: "); print(src); print("\n");
|
||||
print(" "); print("dst: "); print(dst); print("\n");
|
||||
print(" "); print("common_layout: "); print(common_layout); print("\n");
|
||||
print(" "); print("full_perm: "); print(full_perm); print("\n");
|
||||
print(" "); print("Used vector: "); print(vec_elem); print("\n");
|
||||
print(" "); print("Used threads: "); print(vec_thrs); print("\n");
|
||||
print(" "); print("layout_vt: "); print(layout_vt); print("\n");
|
||||
print(" "); print("src.compose(layout_vt): "); print(src.compose(layout_vt)); print("\n");
|
||||
print(" "); print("dst.compose(layout_vt): "); print(dst.compose(layout_vt)); print("\n");
|
||||
print(" "); print("src_v: "); print(src_v); print("\n");
|
||||
print(" "); print("dst_v: "); print(dst_v); print("\n");
|
||||
print(" "); print("recast<VecType const>(src_v): "); print(recast<VecType const>(src_v)); print("\n");
|
||||
print(" "); print("recast<VecType const>(dst_v): "); print(recast<VecType const>(dst_v)); print("\n");
|
||||
}
|
||||
if (thread0()) {
|
||||
print(" "); print("cooperative_copy -- vec\n");
|
||||
print(" "); print("NumThreads: "); print(NumThreads); print("\n");
|
||||
print(" "); print("MaxVecBits: "); print(MaxVecBits); print("\n");
|
||||
print(" "); print("src: "); print(src); print("\n");
|
||||
print(" "); print("dst: "); print(dst); print("\n");
|
||||
print(" "); print("common_layout: "); print(common_layout); print("\n");
|
||||
print(" "); print("full_perm: "); print(full_perm); print("\n");
|
||||
print(" "); print("Used vector: "); print(vec_elem); print("\n");
|
||||
print(" "); print("Used threads: "); print(vec_thrs); print("\n");
|
||||
print(" "); print("layout_vt: "); print(layout_vt); print("\n");
|
||||
print(" "); print("src.compose(layout_vt): "); print(src.compose(layout_vt)); print("\n");
|
||||
print(" "); print("dst.compose(layout_vt): "); print(dst.compose(layout_vt)); print("\n");
|
||||
print(" "); print("src_v: "); print(src_v); print("\n");
|
||||
print(" "); print("dst_v: "); print(dst_v); print("\n");
|
||||
print(" "); print("recast<VecType const>(src_v): "); print(recast<VecType const>(src_v)); print("\n");
|
||||
print(" "); print("recast<VecType const>(dst_v): "); print(recast<VecType const>(dst_v)); print("\n");
|
||||
}
|
||||
#ifdef __CUDA_ARCH__
|
||||
__syncthreads();
|
||||
__syncthreads();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// If we're using all threads (static) or the tid is in in-range (dynamic)
|
||||
if (vec_thrs >= NumThreads or tid < vec_thrs) {
|
||||
return copy_if(TrivialPredTensor{}, recast<VecType const>(src_v), recast<VecType>(dst_v));
|
||||
// If we're using all threads (static) or the tid is in in-range (dynamic)
|
||||
if (vec_thrs >= NumThreads or tid < vec_thrs) {
|
||||
return copy_if(TrivialPredTensor{}, recast<VecType const>(src_v), recast<VecType>(dst_v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <cute/atom/mma_atom.hpp>
|
||||
|
||||
#include <cute/algorithm/axpby.hpp>
|
||||
#include <cute/algorithm/functional.hpp>
|
||||
#include <cute/algorithm/gemm.hpp>
|
||||
|
||||
@@ -44,40 +45,37 @@ namespace cute
|
||||
{
|
||||
|
||||
//
|
||||
// Collective Shared-Memory GEMMs
|
||||
// Cooperative Shared-Memory GEMMs
|
||||
//
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Predicated Cooperative GEMM
|
||||
template <class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
class ALoadTransformOp, class BLoadTransformOp,
|
||||
class CLoadTransformOp, class CStoreTransformOp,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op /* transforms A values before used in GEMM */,
|
||||
BLoadTransformOp const& sB_load_op /* transforms B values before used in GEMM */)
|
||||
cooperative_gemm_predication(ThrMMA<Args...> const& thr_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op, // transforms A values before use in GEMM
|
||||
BLoadTransformOp const& sB_load_op, // transforms B values before use in GEMM
|
||||
CLoadTransformOp const& sC_load_op, // transforms C values before use in GEMM
|
||||
CStoreTransformOp const& sC_store_op) // transforms results before they are stored to C
|
||||
{
|
||||
CUTE_STATIC_ASSERT_V(size<0>(sA) == size<0>(sC)); // AM == CM
|
||||
CUTE_STATIC_ASSERT_V(size<0>(sB) == size<1>(sC)); // BN == CN
|
||||
CUTE_STATIC_ASSERT_V(size<1>(sA) == size<1>(sB)); // AK == BK
|
||||
|
||||
using TypeA = typename TA::value_type;
|
||||
using TypeB = typename TB::value_type;
|
||||
using TypeC = typename TC::value_type;
|
||||
|
||||
static_assert(is_same_v<decay_t<invoke_result_t<ALoadTransformOp, TypeA>>, TypeA>,
|
||||
"ALoadTransformOp functor must accept and return value of type TA::value_type");
|
||||
static_assert(is_same_v<decay_t<invoke_result_t<BLoadTransformOp, TypeB>>, TypeB>,
|
||||
"BLoadTransformOp functor must accept and return value of type TB::value_type");
|
||||
|
||||
// Original, static size of the problem
|
||||
auto M = size<0>(sC);
|
||||
auto N = size<1>(sC);
|
||||
@@ -88,39 +86,14 @@ cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
auto BLK_N = tile_size<1>(thr_mma);
|
||||
auto BLK_K = tile_size<2>(thr_mma);
|
||||
|
||||
// Compute the "residues"
|
||||
auto m_residue = M - BLK_M * (ceil_div(M, BLK_M) - Int<1>{}); // (0,BLK_M]
|
||||
auto n_residue = N - BLK_N * (ceil_div(N, BLK_N) - Int<1>{}); // (0,BLK_N]
|
||||
auto k_residue = K - BLK_K * (ceil_div(K, BLK_K) ); // (-BLK_K,0]
|
||||
|
||||
// Shift the origin so k_residue is zeroth tile
|
||||
sA.data() = &sA(0,k_residue);
|
||||
sB.data() = &sB(0,k_residue);
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
printf("%d in BLK_M (%d)\n", int(m_residue), int(BLK_M));
|
||||
printf("%d in BLK_N (%d)\n", int(n_residue), int(BLK_N));
|
||||
printf("%d in BLK_K (%d)\n", int(k_residue), int(BLK_K));
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
// MMA Partitioning
|
||||
//
|
||||
|
||||
// Round the layout extents up to BLK_X
|
||||
Tensor rounded_sA = sA.compose(make_shape(ceil_div(M, BLK_M) * BLK_M, ceil_div(K, BLK_K) * BLK_K));
|
||||
Tensor rounded_sB = sB.compose(make_shape(ceil_div(N, BLK_N) * BLK_N, ceil_div(K, BLK_K) * BLK_K));
|
||||
Tensor rounded_sC = sC.compose(make_shape(ceil_div(M, BLK_M) * BLK_M, ceil_div(N, BLK_N) * BLK_N));
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print("rounded_sA: "); print(rounded_sA); print("\n");
|
||||
print("rounded_sB: "); print(rounded_sB); print("\n");
|
||||
print("rounded_sC: "); print(rounded_sC); print("\n");
|
||||
}
|
||||
#endif
|
||||
// Round the layout extents up to BLK_X to satisfy MMA partitioning safety
|
||||
Tensor rounded_sA = sA.compose(make_shape(round_up(M, BLK_M), round_up(K, BLK_K)));
|
||||
Tensor rounded_sB = sB.compose(make_shape(round_up(N, BLK_N), round_up(K, BLK_K)));
|
||||
Tensor rounded_sC = sC.compose(make_shape(round_up(M, BLK_M), round_up(N, BLK_N)));
|
||||
|
||||
// Partition the sA and sB tiles across the threads for the MMA
|
||||
Tensor tCsA = thr_mma.partition_A(rounded_sA); // (MMA,MMA_M,MMA_K)
|
||||
@@ -133,6 +106,13 @@ cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print(" sA: "); print( sA); print("\n");
|
||||
print(" sB: "); print( sB); print("\n");
|
||||
print(" sC: "); print( sC); print("\n");
|
||||
print("r_sA: "); print(rounded_sA); print("\n");
|
||||
print("r_sB: "); print(rounded_sB); print("\n");
|
||||
print("r_sC: "); print(rounded_sC); print("\n");
|
||||
print(thr_mma);
|
||||
print("tCsA: "); print(tCsA); print("\n");
|
||||
print("tCsB: "); print(tCsB); print("\n");
|
||||
print("tCsC: "); print(tCsC); print("\n");
|
||||
@@ -146,58 +126,232 @@ cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
// PREDICATION
|
||||
//
|
||||
|
||||
// Allocate the preds for only the MMA-mode of tCsA and tCsB
|
||||
Tensor tCpA = make_tensor<bool>(size<0>(tCsA));
|
||||
Tensor tCpB = make_tensor<bool>(size<0>(tCsB));
|
||||
|
||||
// Create coordinate tensors on a single compute block for predication
|
||||
Tensor cA = make_identity_tensor(make_shape(BLK_M, BLK_K)); // (BLK_M,BLK_K) -> (blk_m,blk_k)
|
||||
Tensor cB = make_identity_tensor(make_shape(BLK_N, BLK_K)); // (BLK_M,BLK_K) -> (blk_n,blk_k)
|
||||
// Create coordinate tensors for the problem
|
||||
Tensor cA = make_identity_tensor(shape(rounded_sA)); // (M,K) -> (m,k)
|
||||
Tensor cB = make_identity_tensor(shape(rounded_sB)); // (N,K) -> (n,k)
|
||||
|
||||
// Repeat partitioning with thr_mma
|
||||
Tensor tCcA = thr_mma.partition_A(cA); // (MMA,1,1) -> (blk_m,blk_k)
|
||||
Tensor tCcB = thr_mma.partition_B(cB); // (MMA,1,1) -> (blk_n,blk_k)
|
||||
Tensor tCcA = thr_mma.partition_A(cA); // (MMA,MMA_M,MMA_K) -> (m,k)
|
||||
Tensor tCcB = thr_mma.partition_B(cB); // (MMA,MMA_N,MMA_K) -> (n,k)
|
||||
|
||||
// Populate the m and n predicates
|
||||
// Allocate the preds for MMA- and MMA_MN-modes
|
||||
Tensor tCpA = make_tensor<bool>(make_shape(size<0>(tCsA), size<1>(tCsA)));
|
||||
Tensor tCpB = make_tensor<bool>(make_shape(size<0>(tCsB), size<1>(tCsB)));
|
||||
|
||||
// Populate the predicates on M and N
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size(tCpA); ++i) {
|
||||
tCpA(i) = elem_less(get<0>(tCcA(i)), m_residue);
|
||||
tCpA(i) = elem_less(get<0>(tCcA(_,_,Int<0>{})(i)), shape<0>(sA));
|
||||
}
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size(tCpB); ++i) {
|
||||
tCpB(i) = elem_less(get<0>(tCcB(i)), n_residue);
|
||||
tCpB(i) = elem_less(get<0>(tCcB(_,_,Int<0>{})(i)), shape<0>(sB));
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf("Thr %d: A(%d,%d):%d B(%d,%d):%d\n",
|
||||
threadIdx.x,
|
||||
int(get<0>(tCcA(0))), int(get<1>(tCcA(0))), int(tCpA(0)),
|
||||
int(get<0>(tCcB(0))), int(get<1>(tCcB(0))), int(tCpB(0)));
|
||||
if (thread0()) {
|
||||
print(" cA: "); print( cA); print("\n");
|
||||
print(" cB: "); print( cB); print("\n");
|
||||
print("tCcA: "); print(tCcA); print("\n");
|
||||
print("tCcB: "); print(tCcB); print("\n");
|
||||
print_tensor(tCpA);
|
||||
print_tensor(tCpB);
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
// PREFETCH k_block = 0 (with k-predication)
|
||||
// PREFETCH k_block = 0
|
||||
// Condition the k-predication on (static) k_block == K_BLOCK_MAX-1, the last k_block
|
||||
// Assumes the MMA-tiling in K is trivial
|
||||
//
|
||||
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCsA); ++i) { // Copy MMA_I
|
||||
if (k_residue == 0 || get<1>(tCcA(i)) >= -k_residue) { // k_block = 0, predicated on k
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < size<1>(tCsA); ++m) { // Copy MMA_M, predicated on m
|
||||
tCrA(i,m,0) = (m_residue == BLK_M || m < size<1>(tCsA)-1 || tCpA(i)) ? sA_load_op(tCsA(i,m,0)) : TypeA{};
|
||||
}
|
||||
}
|
||||
}
|
||||
constexpr int K_BLOCK_MAX = size<2>(tCrA);
|
||||
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCsB); ++i) { // Copy MMA_I
|
||||
if (k_residue == 0 || get<1>(tCcB(i)) >= -k_residue) { // k_block = 0, predicated on k
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < size<1>(tCsB); ++n) { // Copy MMA_N, predicated on n
|
||||
tCrB(i,n,0) = (n_residue == BLK_N || n < size<1>(tCsB)-1 || tCpB(i)) ? sB_load_op(tCsB(i,n,0)) : TypeB{};
|
||||
}
|
||||
for (int m = 0; m < size<1>(tCrA); ++m) { // Copy MMA_M
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCrA); ++i) { // Copy MMA_I
|
||||
tCrA(i,m,0) = (tCpA(i,m) && (0 < K_BLOCK_MAX-1 || elem_less(get<1>(tCcA(i,m,0)), shape<1>(sA)))) ? sA_load_op(tCsA(i,m,0)) : TypeA{};
|
||||
}
|
||||
}
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < size<1>(tCrB); ++n) { // Copy MMA_N
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCrB); ++i) { // Copy MMA_I
|
||||
tCrB(i,n,0) = (tCpB(i,n) && (0 < K_BLOCK_MAX-1 || elem_less(get<1>(tCcB(i,n,0)), shape<1>(sB)))) ? sB_load_op(tCsB(i,n,0)) : TypeB{};
|
||||
}
|
||||
}
|
||||
//
|
||||
// MAINLOOP
|
||||
//
|
||||
|
||||
// Clear accumulators
|
||||
clear(tCrC);
|
||||
|
||||
CUTE_UNROLL
|
||||
for (int k_block = 0; k_block < K_BLOCK_MAX; ++k_block)
|
||||
{
|
||||
if (k_block < K_BLOCK_MAX-1) // static-if not the last k_block
|
||||
{
|
||||
int k_next = k_block + 1; // Load k_next block
|
||||
|
||||
// Condition the k-predication on (static) k_block == K_BLOCK_MAX-1, the last k_block
|
||||
// Assumes the MMA-tiling in K is trivial
|
||||
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < size<1>(tCrA); ++m) { // Copy MMA_M
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCrA); ++i) { // Copy MMA_I
|
||||
tCrA(i,m,k_next) = (tCpA(i,m) && (k_next < K_BLOCK_MAX-1 || elem_less(get<1>(tCcA(i,m,k_next)), shape<1>(sA)))) ? sA_load_op(tCsA(i,m,k_next)) : TypeA{};
|
||||
}
|
||||
}
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < size<1>(tCrB); ++n) { // Copy MMA_N
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCrB); ++i) { // Copy MMA_I
|
||||
tCrB(i,n,k_next) = (tCpB(i,n) && (k_next < K_BLOCK_MAX-1 || elem_less(get<1>(tCcB(i,n,k_next)), shape<1>(sB)))) ? sB_load_op(tCsB(i,n,k_next)) : TypeB{};
|
||||
}
|
||||
}
|
||||
}
|
||||
// GEMM on k_block in registers
|
||||
gemm(thr_mma, tCrA(_,_,k_block), tCrB(_,_,k_block), tCrC);
|
||||
}
|
||||
|
||||
//
|
||||
// Epilogue
|
||||
//
|
||||
|
||||
// Create coordinate tensors for the problem
|
||||
Tensor cC = make_identity_tensor(shape(rounded_sC)); // (M,N) -> (m,n)
|
||||
// Repeat partitioning with thr_mma
|
||||
Tensor tCcC = thr_mma.partition_C(cC); // (MMA,MMA_M,MMA_N) -> (m,n)
|
||||
|
||||
const bool isBetaZero = (beta == Beta{});
|
||||
|
||||
// Custom axpby_if for now
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size(tCrC); ++i)
|
||||
{
|
||||
if (elem_less(tCcC(i), shape(sC)))
|
||||
{
|
||||
tCsC(i) = sC_store_op(isBetaZero ? alpha * static_cast<TypeC>(tCrC(i))
|
||||
: alpha * static_cast<TypeC>(tCrC(i)) +
|
||||
beta * static_cast<TypeC>(sC_load_op(tCsC(i))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slow fallback path
|
||||
template <class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
class ALoadTransformOp, class BLoadTransformOp,
|
||||
class CLoadTransformOp, class CStoreTransformOp,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
cooperative_gemm_predication(uint32_t thread_idx,
|
||||
TiledMMA<Args...> const& tiled_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op, // transforms A values before use in GEMM
|
||||
BLoadTransformOp const& sB_load_op, // transforms B values before use in GEMM
|
||||
CLoadTransformOp const& sC_load_op, // transforms C values before use in GEMM
|
||||
CStoreTransformOp const& sC_store_op) // transforms results before they are stored to C
|
||||
{
|
||||
// ThrMMA
|
||||
auto thr_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
cooperative_gemm_predication(thr_mma, alpha, sA, sB, beta, sC, sA_load_op, sB_load_op, sC_load_op, sC_store_op);
|
||||
}
|
||||
|
||||
// Unpredicated Cooperative GEMM
|
||||
template <class SmemCopyOpA, class SmemCopyOpB, class SmemCopyOpC,
|
||||
class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
class ALoadTransformOp, class BLoadTransformOp,
|
||||
class CLoadTransformOp, class CStoreTransformOp,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
cooperative_gemm_no_predication(uint32_t thread_idx,
|
||||
TiledMMA<Args...> const& tiled_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op, // transforms A values before use in GEMM
|
||||
BLoadTransformOp const& sB_load_op, // transforms B values before use in GEMM
|
||||
CLoadTransformOp const& sC_load_op, // transforms C values before use in GEMM
|
||||
CStoreTransformOp const& sC_store_op) // transforms results before they are stored to C
|
||||
{
|
||||
using TypeA = typename TA::value_type;
|
||||
using TypeB = typename TB::value_type;
|
||||
using TypeC = typename TC::value_type;
|
||||
|
||||
// ThrMMA
|
||||
auto thr_mma = tiled_mma.get_thread_slice(thread_idx);
|
||||
|
||||
//
|
||||
// MMA Partitioning
|
||||
//
|
||||
|
||||
Tensor tCsC = thr_mma.partition_C(sC);
|
||||
// Create register tensors for the MMA to operate on
|
||||
Tensor tCrA = thr_mma.partition_fragment_A(sA); // (MMA,MMA_M,MMA_K)
|
||||
Tensor tCrB = thr_mma.partition_fragment_B(sB); // (MMA,MMA_N,MMA_K)
|
||||
Tensor tCrC = thr_mma.make_fragment_C(tCsC); // (MMA,MMA_M,MMA_N)
|
||||
|
||||
using CopyOpAType = SmemCopyOpA;
|
||||
using CopyOpBType = SmemCopyOpB;
|
||||
|
||||
auto smem_tiled_copy_A = make_tiled_copy_A(Copy_Atom<CopyOpAType, TypeA>{}, thr_mma);
|
||||
auto smem_thr_copy_A = smem_tiled_copy_A.get_thread_slice(thread_idx);
|
||||
Tensor tCsA = smem_thr_copy_A.partition_S(sA);
|
||||
Tensor tCrA_copy_view = smem_thr_copy_A.retile_D(tCrA);
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsA) == size<1>(tCrA_copy_view)); // CPY_M
|
||||
CUTE_STATIC_ASSERT_V(size<2>(tCsA) == size<2>(tCrA_copy_view)); // CPY_K
|
||||
|
||||
auto smem_tiled_copy_B = make_tiled_copy_B(Copy_Atom<CopyOpBType, TypeB>{}, thr_mma);
|
||||
auto smem_thr_copy_B = smem_tiled_copy_B.get_thread_slice(thread_idx);
|
||||
Tensor tCsB = smem_thr_copy_B.partition_S(sB);
|
||||
Tensor tCrB_copy_view = smem_thr_copy_B.retile_D(tCrB);
|
||||
CUTE_STATIC_ASSERT_V(size<1>(tCsB) == size<1>(tCrB_copy_view)); // CPY_N
|
||||
CUTE_STATIC_ASSERT_V(size<2>(tCsB) == size<2>(tCrB_copy_view)); // CPY_K
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print(" sA: "); print(sA); print("\n");
|
||||
print(" sB: "); print(sB); print("\n");
|
||||
print(" sC: "); print(sC); print("\n");
|
||||
print(thr_mma); print("\n");
|
||||
print("tCsC: "); print(tCsC); print("\n");
|
||||
print("tCrA: "); print(tCrA); print("\n");
|
||||
print("tCrB: "); print(tCrB); print("\n");
|
||||
print("tCrC: "); print(tCrC); print("\n");
|
||||
print(smem_thr_copy_A); print("\n");
|
||||
print("tCsA: "); print(tCsA); print("\n");
|
||||
print("tCrA_copy_view: "); print(tCrA_copy_view); print("\n");
|
||||
print(smem_thr_copy_B); print("\n");
|
||||
print("tCsB: "); print(tCsB); print("\n");
|
||||
print("tCrB_copy_view: "); print(tCrB_copy_view); print("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
// PREFETCH
|
||||
//
|
||||
|
||||
copy(smem_tiled_copy_A, tCsA(_,_,Int<0>{}), tCrA_copy_view(_,_,Int<0>{}));
|
||||
copy(smem_tiled_copy_B, tCsB(_,_,Int<0>{}), tCrB_copy_view(_,_,Int<0>{}));
|
||||
//
|
||||
// MAINLOOP
|
||||
//
|
||||
@@ -214,25 +368,15 @@ cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
if (k_block < K_BLOCK_MAX-1)
|
||||
{
|
||||
// Load the next k_block
|
||||
int k_next = k_block + 1;
|
||||
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < size<1>(tCsA); ++m) { // Copy MMA_M
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCsA); ++i) { // Copy_if MMA_I predicated on m
|
||||
tCrA(i,m,k_next) = (m_residue == BLK_M || m < size<1>(tCsA)-1 || tCpA(i)) ? sA_load_op(tCsA(i,m,k_next)) : TypeA{};
|
||||
}
|
||||
}
|
||||
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < size<1>(tCsB); ++n) { // Copy MMA_N
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCsB); ++i) { // Copy MMA_I predicated on n
|
||||
tCrB(i,n,k_next) = (n_residue == BLK_N || n < size<1>(tCsB)-1 || tCpB(i)) ? sB_load_op(tCsB(i,n,k_next)) : TypeB{};
|
||||
}
|
||||
}
|
||||
int k_next = k_block + 1; // statically unrolled
|
||||
copy(smem_tiled_copy_A, tCsA(_,_,k_next), tCrA_copy_view(_,_,k_next));
|
||||
copy(smem_tiled_copy_B, tCsB(_,_,k_next), tCrB_copy_view(_,_,k_next));
|
||||
}
|
||||
|
||||
// Transform A and B, relying on the compiler to remove in case of identity ops
|
||||
cute::transform(tCrA(_,_,k_block), sA_load_op);
|
||||
cute::transform(tCrB(_,_,k_block), sB_load_op);
|
||||
|
||||
// GEMM on k_block in registers
|
||||
gemm(thr_mma, tCrA(_,_,k_block), tCrB(_,_,k_block), tCrC);
|
||||
}
|
||||
@@ -241,53 +385,124 @@ cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
// Epilogue
|
||||
//
|
||||
|
||||
Tensor cC = make_identity_tensor(make_shape(BLK_M, BLK_N)); // (BLK_M,BLK_N) -> (blk_m,blk_n)
|
||||
Tensor tCcC = thr_mma.partition_C(cC); // (MMA, 1, 1) -> (blk_m,blk_n)
|
||||
|
||||
const bool isBetaZero = (beta == Beta{});
|
||||
|
||||
// Custom axpby_if for now
|
||||
CUTE_UNROLL
|
||||
for (int m = 0; m < size<1>(tCsC); ++m)
|
||||
{
|
||||
CUTE_UNROLL
|
||||
for (int n = 0; n < size<2>(tCsC); ++n)
|
||||
{
|
||||
CUTE_UNROLL
|
||||
for (int i = 0; i < size<0>(tCsC); ++i)
|
||||
{
|
||||
if ((m_residue == BLK_M || m < size<1>(tCrC)-1 || get<0>(tCcC(i)) < m_residue) &&
|
||||
(n_residue == BLK_N || n < size<2>(tCrC)-1 || get<1>(tCcC(i)) < n_residue))
|
||||
{
|
||||
tCsC(i,m,n) = isBetaZero ? alpha * static_cast<TypeC>(tCrC(i,m,n)) : alpha * static_cast<TypeC>(tCrC(i,m,n)) + beta * static_cast<TypeC>(tCsC(i,m,n));
|
||||
}
|
||||
}
|
||||
auto isBetaZero = [&] () {
|
||||
if constexpr (is_complex<Beta>::value) {
|
||||
return beta.real() == Int<0>{} && beta.imag() == Int<0>{};
|
||||
}
|
||||
else {
|
||||
return beta == Int<0>{};
|
||||
}
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
} ();
|
||||
|
||||
using CopyOpCType = SmemCopyOpC;
|
||||
Tensor tCrD = thr_mma.make_fragment_C(tCsC);
|
||||
if(!isBetaZero) {
|
||||
copy(CopyOpCType{}, tCsC, tCrD);
|
||||
// Transform C on/after load
|
||||
cute::transform(tCrD, sC_load_op);
|
||||
}
|
||||
// C = alpha * (A * B) + beta * C
|
||||
axpby(alpha, tCrC, beta, tCrD);
|
||||
// Transform C before/on store
|
||||
cute::transform(tCrD, sC_store_op);
|
||||
copy(CopyOpCType{}, tCrD, tCsC);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class SmemCopyOpA, class SmemCopyOpB, class SmemCopyOpC,
|
||||
class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
class ALoadTransformOp = cute::identity, class BLoadTransformOp = cute::identity,
|
||||
class CLoadTransformOp = cute::identity, class CStoreTransformOp = cute::identity,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
cooperative_gemm(uint32_t thread_idx,
|
||||
TiledMMA<Args...> const& tiled_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op = {}, // transforms A values before use in GEMM
|
||||
BLoadTransformOp const& sB_load_op = {}, // transforms B values before use in GEMM
|
||||
CLoadTransformOp const& sC_load_op = {}, // transforms C values before use in GEMM
|
||||
CStoreTransformOp const& sC_store_op = {}) // transforms results before they are stored to C
|
||||
{
|
||||
CUTE_STATIC_ASSERT_V(size<0>(sA) == size<0>(sC)); // AM == CM
|
||||
CUTE_STATIC_ASSERT_V(size<0>(sB) == size<1>(sC)); // BN == CN
|
||||
CUTE_STATIC_ASSERT_V(size<1>(sA) == size<1>(sB)); // AK == BK
|
||||
|
||||
using TypeA = typename TA::value_type;
|
||||
using TypeB = typename TB::value_type;
|
||||
using TypeC = typename TC::value_type;
|
||||
|
||||
static_assert(is_convertible_v<decay_t<invoke_result_t<ALoadTransformOp, TypeA>>, TypeA>,
|
||||
"ALoadTransformOp functor must accept value of type TA::value_type and return value convertible to type TA::value_type");
|
||||
static_assert(is_convertible_v<decay_t<invoke_result_t<BLoadTransformOp, TypeB>>, TypeB>,
|
||||
"BLoadTransformOp functor must accept value of type TB::value_type and return value convertible to type TB::value_type");
|
||||
static_assert(is_convertible_v<decay_t<invoke_result_t<CLoadTransformOp, TypeC>>, TypeC>,
|
||||
"CLoadTransformOp functor must accept value of type TC::value_type and return value convertible to type TC::value_type");
|
||||
static_assert(is_convertible_v<decay_t<invoke_result_t<CStoreTransformOp, TypeC>>, TypeC>,
|
||||
"CStoreTransformOp functor must accept value of type TC::value_type and return value convertible to type TC::value_type");
|
||||
|
||||
static constexpr bool compat = weakly_compatible(tile_shape(TiledMMA<Args...>{}),
|
||||
make_shape(size<0>(sA), size<0>(sB), size<1>(sA)));
|
||||
if constexpr (compat) {
|
||||
detail::cooperative_gemm_no_predication<SmemCopyOpA, SmemCopyOpB, SmemCopyOpC>(
|
||||
thread_idx, tiled_mma, alpha, sA, sB, beta, sC,
|
||||
sA_load_op, sB_load_op, sC_load_op, sC_store_op
|
||||
);
|
||||
} else {
|
||||
detail::cooperative_gemm_predication(
|
||||
thread_idx, tiled_mma, alpha, sA, sB, beta, sC,
|
||||
sA_load_op, sB_load_op, sC_load_op, sC_store_op
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
class ALoadTransformOp = cute::identity, class BLoadTransformOp = cute::identity,
|
||||
class CLoadTransformOp = cute::identity, class CStoreTransformOp = cute::identity,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
cooperative_gemm(ThrMMA<Args...> const& thr_mma,
|
||||
cooperative_gemm(uint32_t thread_idx,
|
||||
TiledMMA<Args...> const& tiled_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC)
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op = {}, // transforms A values before use in GEMM
|
||||
BLoadTransformOp const& sB_load_op = {}, // transforms B values before use in GEMM
|
||||
CLoadTransformOp const& sC_load_op = {}, // transforms C values before use in GEMM
|
||||
CStoreTransformOp const& sC_store_op = {}) // transforms results before they are stored to C
|
||||
{
|
||||
cooperative_gemm(thr_mma, alpha, sA, sB, beta, sC, identity() /* sA_load_op */, identity() /* sB_load_op */);
|
||||
using CopyOpA = AutoVectorizingCopyWithAssumedAlignment<sizeof_bits_v<typename TA::value_type>>;
|
||||
using CopyOpB = AutoVectorizingCopyWithAssumedAlignment<sizeof_bits_v<typename TB::value_type>>;
|
||||
using CopyOpC = AutoVectorizingCopyWithAssumedAlignment<sizeof_bits_v<typename TC::value_type>>;
|
||||
cooperative_gemm<CopyOpA, CopyOpB, CopyOpC>(
|
||||
thread_idx, tiled_mma, alpha, sA, sB, beta, sC,
|
||||
sA_load_op, sB_load_op, sC_load_op, sC_store_op
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy overload of cute::gemm for backwards-compatibility
|
||||
template <class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
class ALoadTransformOp, class BLoadTransformOp,
|
||||
class ALoadTransformOp = cute::identity, class BLoadTransformOp = cute::identity,
|
||||
class CLoadTransformOp = cute::identity, class CStoreTransformOp = cute::identity,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
@@ -299,28 +514,16 @@ gemm(ThrMMA<Args...> const& thr_mma,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC,
|
||||
ALoadTransformOp const& sA_load_op /* transforms A values before used in GEMM */,
|
||||
BLoadTransformOp const& sB_load_op /* transforms B values before used in GEMM */)
|
||||
ALoadTransformOp const& sA_load_op = {}, // transforms A values before use in GEMM
|
||||
BLoadTransformOp const& sB_load_op = {}, // transforms B values before use in GEMM
|
||||
CLoadTransformOp const& sC_load_op = {}, // transforms C values before use in GEMM
|
||||
CStoreTransformOp const& sC_store_op = {}) // transforms results before they are stored to C
|
||||
{
|
||||
cooperative_gemm(thr_mma, alpha, sA, sB, beta, sC, sA_load_op, sB_load_op);
|
||||
}
|
||||
|
||||
template <class... Args,
|
||||
class Alpha, class TA, class ALayout, class TB, class BLayout,
|
||||
class Beta, class TC, class CLayout,
|
||||
__CUTE_REQUIRES(ALayout::rank == 2 && is_smem<TA>::value &&
|
||||
BLayout::rank == 2 && is_smem<TB>::value &&
|
||||
CLayout::rank == 2 && is_smem<TC>::value)>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
gemm(ThrMMA<Args...> const& thr_mma,
|
||||
Alpha const& alpha,
|
||||
Tensor<TA, ALayout> sA,
|
||||
Tensor<TB, BLayout> sB,
|
||||
Beta const& beta,
|
||||
Tensor<TC, CLayout> sC)
|
||||
{
|
||||
cooperative_gemm(thr_mma, alpha, sA, sB, beta, sC, identity() /* sA_load_op */, identity() /* sB_load_op */);
|
||||
// Goes directly to the slow path to avoid getting thread_idx from thr_mma
|
||||
detail::cooperative_gemm_predication(
|
||||
thr_mma, alpha, sA, sB, beta, sC,
|
||||
sA_load_op, sB_load_op, sC_load_op, sC_store_op
|
||||
);
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2024 - 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cute/config.hpp>
|
||||
|
||||
#include <cute/arch/copy.hpp>
|
||||
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 500
|
||||
#define CUTE_ARCH_WARP_SHUFFLE_ENABLED 1
|
||||
#endif
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
struct SM50_Shuffle_U32_2x2Trans
|
||||
{
|
||||
using SRegisters = uint32_t[2];
|
||||
using DRegisters = uint32_t[2];
|
||||
|
||||
CUTE_HOST_DEVICE static void
|
||||
copy(uint32_t const& src0, uint32_t const& src1, uint32_t& dst0, uint32_t& dst1)
|
||||
{
|
||||
#if defined(CUTE_ARCH_WARP_SHUFFLE_ENABLED)
|
||||
uint32_t x0 = src0;
|
||||
uint32_t y0 = __shfl_xor_sync(0xffffffff, x0, 1);
|
||||
|
||||
uint32_t x1 = src1;
|
||||
uint32_t y1 = __shfl_xor_sync(0xffffffff, x1, 1);
|
||||
|
||||
if (threadIdx.x % 2 == 0) {
|
||||
dst1 = y0;
|
||||
}
|
||||
else {
|
||||
dst0 = y1;
|
||||
}
|
||||
#else
|
||||
CUTE_INVALID_CONTROL_PATH("Trying to use __shfl_xor_sync without CUTE_ARCH_WARP_SHUFFLE_ENABLED.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // end namespace cute
|
||||
+102
-59
@@ -117,7 +117,7 @@ cast_smem_ptr_to_uint(void const* const ptr)
|
||||
uint32_t smem_ptr;
|
||||
|
||||
asm(
|
||||
"{ .reg .u64 smem_ptr; cvta.to.shared.u64 smem_ptr, %1; cvt.u32.u64 %0, smem_ptr; }\n"
|
||||
"{ .reg .u64 smem_ptr; cvta.to.shared.u64 smem_ptr, %1; cvt.u32.u64 %0, smem_ptr; }\n"
|
||||
: "=r"(smem_ptr) : "l"(ptr));
|
||||
|
||||
return smem_ptr;
|
||||
@@ -132,11 +132,47 @@ cast_smem_ptr_to_uint(void const* const ptr)
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
//
|
||||
// Utility for pointer interfaces
|
||||
// Wrapper for MMAOp::fma
|
||||
//
|
||||
|
||||
namespace detail {
|
||||
template <class MmaOp>
|
||||
struct CallFMA {
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE constexpr void
|
||||
operator()(Args&&... args) const {
|
||||
return MmaOp::fma(static_cast<Args&&>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Wrapper for CopyOp::copy
|
||||
//
|
||||
|
||||
template <class CopyOp>
|
||||
struct CallCOPY {
|
||||
template <class... Args>
|
||||
CUTE_HOST_DEVICE constexpr void
|
||||
operator()(Args&&... args) const {
|
||||
return CopyOp::copy(static_cast<Args&&>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Utility for exploding pointers/arrays/tensors into functions
|
||||
//
|
||||
|
||||
template <class Fn,
|
||||
class PtrA, int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode(Fn fn,
|
||||
PtrA&& a, int_sequence<I...>)
|
||||
{
|
||||
return fn(a[I]...);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class PtrS, int... Is,
|
||||
@@ -180,76 +216,83 @@ explode(Fn fn,
|
||||
return fn(d[Id]..., a[Ia]..., b[Ib]..., c[Ic]...);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class PtrA, int... Ia,
|
||||
class PtrB, int... Ib,
|
||||
class PtrC, int... Ic,
|
||||
class ParamType>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_with_d_scaling(Fn fn,
|
||||
PtrA&& a, int_sequence<Ia...>,
|
||||
PtrB&& b, int_sequence<Ib...>,
|
||||
PtrC&& c, int_sequence<Ic...>,
|
||||
ParamType&& p0)
|
||||
{
|
||||
return fn(a[Ia]..., b[Ib]..., c[Ic]..., p0);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class PtrD, int... Id,
|
||||
class PtrA, int... Ia,
|
||||
class PtrB, int... Ib,
|
||||
class PtrC, int... Ic,
|
||||
class ParamType>
|
||||
class PtrE, int... Ie>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_with_d_scaling(Fn fn,
|
||||
explode(Fn fn,
|
||||
PtrD&& d, int_sequence<Id...>,
|
||||
PtrA&& a, int_sequence<Ia...>,
|
||||
PtrB&& b, int_sequence<Ib...>,
|
||||
PtrC&& c, int_sequence<Ic...>,
|
||||
ParamType&& p0)
|
||||
PtrE&& e, int_sequence<Ie...>)
|
||||
{
|
||||
return fn(d[Id]..., a[Ia]..., b[Ib]..., c[Ic]..., p0);
|
||||
return fn(d[Id]..., a[Ia]..., b[Ib]..., c[Ic]..., e[Ie]...);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class PtrD, int... Id,
|
||||
class PtrA, int... Ia,
|
||||
class PtrB, int... Ib,
|
||||
class PtrC, int... Ic,
|
||||
class PtrSFA, int... Isfa,
|
||||
class PtrSFB, int... Isfb>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode(Fn fn,
|
||||
PtrD&& d, int_sequence<Id...>,
|
||||
PtrA&& a, int_sequence<Ia...>,
|
||||
PtrB&& b, int_sequence<Ib...>,
|
||||
PtrC&& c, int_sequence<Ic...>,
|
||||
PtrSFA&& sfa, int_sequence<Isfa...>,
|
||||
PtrSFB&& sfb, int_sequence<Isfb...>)
|
||||
{
|
||||
return fn(d[Id]..., a[Ia]..., b[Ib]..., c[Ic]..., sfa[Isfa]..., sfb[Isfb]...);
|
||||
}
|
||||
//
|
||||
// Utility for exploding tuples into functions
|
||||
//
|
||||
|
||||
template <class Fn,
|
||||
class TupleA, int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_tuple(Fn fn,
|
||||
TupleA&& a, int_sequence<I...>)
|
||||
{
|
||||
return fn(get<I>(a)...);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class TupleA, int... Ia,
|
||||
class TupleB, int... Ib>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_tuple(Fn fn,
|
||||
TupleA&& a, int_sequence<Ia...>,
|
||||
TupleB&& b, int_sequence<Ib...>)
|
||||
{
|
||||
return fn(get<Ia>(a)..., get<Ib>(b)...);
|
||||
}
|
||||
|
||||
template <class Fn,
|
||||
class TupleA, int... Ia,
|
||||
class TupleB, int... Ib,
|
||||
class TupleC, int... Ic>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode_tuple(Fn fn,
|
||||
TupleA&& a, int_sequence<Ia...>,
|
||||
TupleB&& b, int_sequence<Ib...>,
|
||||
TupleC&& c, int_sequence<Ic...>)
|
||||
{
|
||||
return fn(get<Ia>(a)..., get<Ib>(b)..., get<Ic>(c)...);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <int SRegCount, int DRegCount,
|
||||
class Fn, class PtrS, class PtrD>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode(Fn fn, PtrS&& s, PtrD&& d)
|
||||
{
|
||||
return detail::explode(fn,
|
||||
s, make_int_sequence<SRegCount>{},
|
||||
d, make_int_sequence<DRegCount>{});
|
||||
}
|
||||
|
||||
template <int ARegCount, int BRegCount, int CRegCount,
|
||||
class Fn, class PtrA, class PtrB, class PtrC>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode(Fn fn, PtrA&& a, PtrB&& b, PtrC&& c)
|
||||
{
|
||||
return detail::explode(fn,
|
||||
a, make_int_sequence<ARegCount>{},
|
||||
b, make_int_sequence<BRegCount>{},
|
||||
c, make_int_sequence<CRegCount>{});
|
||||
}
|
||||
|
||||
template <int DRegCount, int ARegCount, int BRegCount, int CRegCount,
|
||||
class Fn, class PtrD, class PtrA, class PtrB, class PtrC>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
explode(Fn fn, PtrD&& d, PtrA&& a, PtrB&& b, PtrC&& c)
|
||||
{
|
||||
return detail::explode(fn,
|
||||
d, make_int_sequence<DRegCount>{},
|
||||
a, make_int_sequence<ARegCount>{},
|
||||
b, make_int_sequence<BRegCount>{},
|
||||
c, make_int_sequence<CRegCount>{});
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -756,6 +756,7 @@ print_latex_copy(LayoutS const& S, ThrIDS const& TS, // (m,n) -> (tid,vid) and
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cute/atom/copy_traits_sm50.hpp>
|
||||
#include <cute/atom/copy_traits_sm75.hpp>
|
||||
#include <cute/atom/copy_traits_sm80.hpp>
|
||||
#include <cute/atom/copy_traits_sm90.hpp>
|
||||
|
||||
@@ -92,59 +92,6 @@ struct Copy_Traits<AutoVectorizingCopyWithAssumedAlignment<MaxVecBits>>
|
||||
using RefLayout = SrcLayout;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Utility for exploding pointers, arrays, or tensors into Operation::copy
|
||||
template <class Operation,
|
||||
class PtrSrc, int... Is,
|
||||
class PtrDst, int... Id>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_explode_index(PtrSrc&& s, int_sequence<Is...>,
|
||||
PtrDst&& d, int_sequence<Id...>)
|
||||
{
|
||||
return Operation::copy(s[Is]..., d[Id]...);
|
||||
}
|
||||
|
||||
// Utility for exploding tuples into ::copy
|
||||
template <class Operation,
|
||||
class TupleArg, int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_explode(TupleArg&& t, int_sequence<I...>)
|
||||
{
|
||||
return Operation::copy(get<I>(static_cast<TupleArg&&>(t))...);
|
||||
}
|
||||
|
||||
template <class Operation,
|
||||
class TupleSrc, int... Is,
|
||||
class TupleDst, int... Id>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_explode(TupleSrc&& s, int_sequence<Is...>,
|
||||
TupleDst&& d, int_sequence<Id...>)
|
||||
{
|
||||
return Operation::copy(get<Is>(static_cast<TupleSrc&&>(s))...,
|
||||
get<Id>(static_cast<TupleDst&&>(d))...);
|
||||
}
|
||||
|
||||
template <class Operation,
|
||||
class TupleAux, int... Ia,
|
||||
class TupleSrc, int... Is,
|
||||
class TupleDst, int... Id>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
copy_explode(TupleAux&& a, int_sequence<Ia...>,
|
||||
TupleSrc&& s, int_sequence<Is...>,
|
||||
TupleDst&& d, int_sequence<Id...>)
|
||||
{
|
||||
return Operation::copy(get<Ia>(static_cast<TupleAux&&>(a))...,
|
||||
get<Is>(static_cast<TupleSrc&&>(s))...,
|
||||
get<Id>(static_cast<TupleDst&&>(d))...);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
//
|
||||
// Generic copy_unpack for common argument-based Copy_Traits
|
||||
//
|
||||
@@ -177,8 +124,9 @@ copy_unpack(Copy_Traits<CopyOp,Args...> const&,
|
||||
CUTE_STATIC_ASSERT_V(size(rD) == Int<RegNumDst>{},
|
||||
"Copy_Traits: dst failed to vectorize into registers. Layout is incompatible with this CopyOp.");
|
||||
|
||||
detail::copy_explode_index<CopyOp>(rS, make_int_sequence<RegNumSrc>{},
|
||||
rD, make_int_sequence<RegNumDst>{});
|
||||
detail::explode(detail::CallCOPY<CopyOp>{},
|
||||
rS, make_int_sequence<RegNumSrc>{},
|
||||
rD, make_int_sequence<RegNumDst>{});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/***************************************************************************************************
|
||||
* Copyright (c) 2024 - 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
**************************************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include <cute/arch/copy_sm50.hpp>
|
||||
#include <cute/atom/copy_traits.hpp>
|
||||
|
||||
#include <cute/layout.hpp>
|
||||
|
||||
namespace cute
|
||||
{
|
||||
|
||||
template <>
|
||||
struct Copy_Traits<SM50_Shuffle_U32_2x2Trans>
|
||||
{
|
||||
// Logical thread id to thread idx (one-thread)
|
||||
using ThrID = Layout<_32>;
|
||||
|
||||
// Map from (src-thr,src-val) to bit
|
||||
using SrcLayout = Layout<Shape <_32,_64>,
|
||||
Stride<_64, _1>>;
|
||||
// Map from (dst-thr,dst-val) to bit
|
||||
using DstLayout = Layout<Shape <Shape < _2, _16>,Shape <_32, _2>>,
|
||||
Stride<Stride<_32, _128>,Stride< _1, _64>>>;
|
||||
|
||||
// Reference map from (thr,val) to bit
|
||||
using RefLayout = SrcLayout;
|
||||
};
|
||||
|
||||
} // end namespace cute
|
||||
@@ -73,14 +73,16 @@ struct TMA_LOAD_IM2COL_Unpack
|
||||
CUTE_STATIC_ASSERT_V(rank<1>(src_coord_offset) == rank<3>(src_coord_offset));
|
||||
|
||||
if constexpr (detail::is_prefetch<CopyOp>) {
|
||||
return detail::copy_explode<CopyOp>(traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
src_coord_cwhdn_offset_srt, tuple_seq<decltype(src_coord_cwhdn_offset_srt)>{});
|
||||
return detail::explode_tuple(detail::CallCOPY<CopyOp>{},
|
||||
traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
src_coord_cwhdn_offset_srt, tuple_seq<decltype(src_coord_cwhdn_offset_srt)>{});
|
||||
} else {
|
||||
static_assert(is_smem<TD>::value, "SM90_TMA_LOAD_IM2COL requires the destination be shared memory.");
|
||||
void* dst_ptr = cute::raw_pointer_cast(dst.data());
|
||||
return detail::copy_explode<CopyOp>(traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
make_tuple(dst_ptr), seq<0>{},
|
||||
src_coord_cwhdn_offset_srt, tuple_seq<decltype(src_coord_cwhdn_offset_srt)>{});
|
||||
return detail::explode_tuple(detail::CallCOPY<CopyOp>{},
|
||||
traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
make_tuple(dst_ptr), seq<0>{},
|
||||
src_coord_cwhdn_offset_srt, tuple_seq<decltype(src_coord_cwhdn_offset_srt)>{});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -349,8 +351,9 @@ struct Copy_Traits<SM90_TMA_STORE_IM2COL, NumBitsPerTMA, TMATensor>
|
||||
void const* const src_ptr = cute::raw_pointer_cast(src.data());
|
||||
auto dst_coord = flatten(take<0,3>(dst(Int<0>{})));
|
||||
|
||||
return detail::copy_explode<SM90_TMA_STORE_IM2COL>(make_tuple(desc_ptr, src_ptr), seq<0,1>{},
|
||||
dst_coord, tuple_seq<decltype(dst_coord)>{});
|
||||
return detail::explode_tuple(detail::CallCOPY<SM90_TMA_STORE_IM2COL>{},
|
||||
make_tuple(desc_ptr, src_ptr), seq<0,1>{},
|
||||
dst_coord, tuple_seq<decltype(dst_coord)>{});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -537,6 +540,133 @@ make_im2col_tma_copy_desc(
|
||||
return cute::make_tuple(tma_desc, tma_tensor);
|
||||
}
|
||||
|
||||
template <class CopyOp,
|
||||
class GEngine, class GLayout,
|
||||
class SLayout,
|
||||
class VShape, class VStride,
|
||||
class LowerCornerStride,
|
||||
class UpperCornerStride,
|
||||
class LowerPaddingStride,
|
||||
class UpperPaddingStride,
|
||||
class TraversalStride,
|
||||
class LowerSRTStride,
|
||||
class DilationStride>
|
||||
CUTE_HOST_RTC
|
||||
auto
|
||||
make_tma_atom_im2col(CopyOp,
|
||||
Tensor<GEngine,GLayout> const& gtensor, // Full GMEM Tensor: ((w, h, d, n), c)
|
||||
SLayout const& slayout, // CTA Tile of SMEM, potentially swizzled
|
||||
int32_t const& num_multicast, // The number of CTAs involved in multicasting
|
||||
Layout<VShape,VStride> const& cta_v_map, // V: CTA val idx -> gmem mode
|
||||
LowerCornerStride const& lower_corner_whd,
|
||||
UpperCornerStride const& upper_corner_whd,
|
||||
LowerPaddingStride const& lower_padding_whd,
|
||||
UpperPaddingStride const& upper_padding_whd,
|
||||
TraversalStride const& stride_whd, // traversal stride
|
||||
LowerSRTStride const& lower_srt,
|
||||
DilationStride const& stride_srt) // dilation
|
||||
{
|
||||
//
|
||||
// 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.");
|
||||
|
||||
//
|
||||
// TMA slayout manipulation
|
||||
//
|
||||
|
||||
// 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));
|
||||
|
||||
#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");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
auto glayout_basis = make_identity_layout(product_each(shape(gtensor)));
|
||||
|
||||
// Tile the modes of gtensor with the truncated cta_v_map o inv_smem_layout_trunc
|
||||
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 >= 2, "IM2COL expects at least 2 modes of the smem to vectorize with gmem.");
|
||||
// IM2COL uses a maximum of 2 modes
|
||||
constexpr int smem_tma_rank = cute::min(int(smem_rank), 2);
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto tma_layout_trunc = take<0,smem_tma_rank>(tma_layout_full);
|
||||
|
||||
// Split according to the portion each multicast CTA will be responsible for
|
||||
auto tma_layout_vt = logical_divide(tma_layout_trunc, shape_div(size(tma_layout_trunc), num_multicast));
|
||||
|
||||
#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_vt : "); print(tma_layout_vt); print("\n");
|
||||
#endif
|
||||
|
||||
auto range_c = size<0,0>(tma_layout_vt);
|
||||
auto range_whdn = size<0,1>(tma_layout_vt);
|
||||
|
||||
Tensor gtensor_cwhdn = make_tensor(gtensor.data(),
|
||||
flatten(make_layout(basis_get(stride<0,0>(tma_layout_vt), gtensor.layout()),
|
||||
basis_get(stride<0,1>(tma_layout_vt), gtensor.layout()))));
|
||||
|
||||
auto [tma_desc, tma_tensor] = make_im2col_tma_copy_desc(
|
||||
gtensor_cwhdn,
|
||||
range_c,
|
||||
range_whdn,
|
||||
detail::get_swizzle_portion(slayout),
|
||||
tma_layout_vt,
|
||||
lower_corner_whd,
|
||||
upper_corner_whd,
|
||||
lower_padding_whd,
|
||||
upper_padding_whd,
|
||||
stride_whd,
|
||||
lower_srt,
|
||||
stride_srt);
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
constexpr int num_bits_per_tma = decltype(size(tma_layout_trunc))::value * sizeof(T) * 8;
|
||||
|
||||
using Traits = Copy_Traits<CopyOp, cute::C<num_bits_per_tma>, decltype(tma_tensor)>;
|
||||
using Atom = Copy_Atom<Traits, typename GEngine::value_type>;
|
||||
|
||||
#if 0
|
||||
print("num_bits : "); print(num_bits_per_tma); print("\n");
|
||||
#endif
|
||||
|
||||
Traits tma_traits{tma_desc, tma_tensor};
|
||||
|
||||
// Return the Copy_Atom
|
||||
return Atom{tma_traits};
|
||||
}
|
||||
|
||||
/// Make a TiledCopy for im2col TMA load.
|
||||
///
|
||||
/// @param copy_op The copy implementation: either
|
||||
@@ -584,99 +714,12 @@ make_tma_copy_im2col(CopyOp const& copy_op,
|
||||
// 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
|
||||
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));
|
||||
|
||||
#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");
|
||||
#endif
|
||||
|
||||
//
|
||||
// TMA gtensor manipulation
|
||||
//
|
||||
|
||||
// Generate a TupleBasis for the gtensor
|
||||
auto glayout_basis = make_identity_layout(product_each(shape(gtensor)));
|
||||
|
||||
// Tile the modes of gtensor with the truncated cta_v_map o inv_smem_layout_trunc
|
||||
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 >= 2, "IM2COL expects at least 2 modes of the smem to vectorize with gmem.");
|
||||
// IM2COL uses a maximum of 2 modes
|
||||
constexpr int smem_tma_rank = cute::min(int(smem_rank), 2);
|
||||
|
||||
// Keep only the static-1 basis modes into gmem
|
||||
auto tma_layout_trunc = take<0,smem_tma_rank>(tma_layout_full);
|
||||
|
||||
// Split according to the portion each multicast CTA will be responsible for
|
||||
auto tma_layout_vt = logical_divide(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_vt : "); print(tma_layout_vt); print("\n");
|
||||
#endif
|
||||
|
||||
auto range_c = size<0,0>(tma_layout_vt);
|
||||
auto range_whdn = size<0,1>(tma_layout_vt);
|
||||
|
||||
Tensor gtensor_cwhdn = make_tensor(gtensor.data(),
|
||||
flatten(make_layout(basis_get(stride<0,0>(tma_layout_vt), gtensor.layout()),
|
||||
basis_get(stride<0,1>(tma_layout_vt), gtensor.layout()))));
|
||||
|
||||
auto [tma_desc, tma_tensor] = make_im2col_tma_copy_desc(
|
||||
gtensor_cwhdn,
|
||||
range_c,
|
||||
range_whdn,
|
||||
detail::get_swizzle_portion(slayout),
|
||||
tma_layout_vt,
|
||||
lower_corner_whd,
|
||||
upper_corner_whd,
|
||||
lower_padding_whd,
|
||||
upper_padding_whd,
|
||||
stride_whd,
|
||||
lower_srt,
|
||||
stride_srt);
|
||||
|
||||
//
|
||||
// Construct the Copy_Traits
|
||||
//
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
constexpr int num_bits_per_tma = decltype(size<0>(tma_layout_vt))::value * sizeof(T) * 8;
|
||||
|
||||
using Traits = Copy_Traits<CopyOp, cute::C<num_bits_per_tma>, decltype(tma_tensor)>;
|
||||
|
||||
#if 0
|
||||
print("num_bits : "); print(NumBitsPerTMA{}); print("\n");
|
||||
#endif
|
||||
|
||||
Traits tma_traits{tma_desc, tma_tensor};
|
||||
Copy_Atom atom = make_tma_atom_im2col(copy_op, gtensor, slayout, cosize(cta_t_map), cta_v_map,
|
||||
lower_corner_whd, upper_corner_whd, lower_padding_whd,
|
||||
upper_padding_whd, stride_whd, lower_srt, stride_srt);
|
||||
|
||||
//
|
||||
// Construct the TiledCopy
|
||||
@@ -684,25 +727,31 @@ make_tma_copy_im2col(CopyOp const& copy_op,
|
||||
|
||||
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(tma_layout_vt)));
|
||||
auto num_elems_per_tma = size<1>(typename decltype(atom)::RefLayout{}) / static_value<sizeof_bits<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, num_elems_per_tma);
|
||||
// Scale that up to cover all of the smem_coords
|
||||
//
|
||||
// The smem vector might not cover all of the tile,
|
||||
// so multiply it up to cover the entire tile.
|
||||
// "T" here (the parallel index) is a CTA index.
|
||||
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));
|
||||
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(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
|
||||
[[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
|
||||
|
||||
using T = typename GEngine::value_type;
|
||||
return TiledCopy<Copy_Atom<Traits,T>, decltype(layout_TV), decltype(cta_tiler)>{tma_traits};
|
||||
return TiledCopy<decltype(atom), decltype(layout_TV), decltype(cta_tiler)>{atom};
|
||||
}
|
||||
|
||||
/// Make a TiledCopy for im2col TMA with no offsets.
|
||||
|
||||
@@ -69,8 +69,9 @@ struct TMA_LOAD_Unpack
|
||||
{
|
||||
auto src_coord = src.data().coord_;
|
||||
if constexpr (detail::is_prefetch<CopyOp>) {
|
||||
return detail::copy_explode<CopyOp>(traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
src_coord, tuple_seq<decltype(src_coord)>{});
|
||||
return detail::explode_tuple(detail::CallCOPY<CopyOp>{},
|
||||
traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
src_coord, tuple_seq<decltype(src_coord)>{});
|
||||
} else {
|
||||
static_assert(is_smem<TD>::value, "SM90_TMA_LOAD requires the destination be shared memory.");
|
||||
void* dst_ptr = cute::raw_pointer_cast(dst.data());
|
||||
@@ -81,9 +82,10 @@ struct TMA_LOAD_Unpack
|
||||
blockIdx.x, blockIdx.y, blockIdx.z,
|
||||
int32_t(c0), int32_t(c1), int32_t(c2), int32_t(c3), int32_t(c4), dst_ptr);
|
||||
#endif
|
||||
return detail::copy_explode<CopyOp>(traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
make_tuple(dst_ptr), seq<0>{},
|
||||
src_coord, tuple_seq<decltype(src_coord)>{});
|
||||
return detail::explode_tuple(detail::CallCOPY<CopyOp>{},
|
||||
traits.opargs_, tuple_seq<decltype(traits.opargs_)>{},
|
||||
make_tuple(dst_ptr), seq<0>{},
|
||||
src_coord, tuple_seq<decltype(src_coord)>{});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -337,8 +339,9 @@ struct Copy_Traits<SM90_TMA_STORE, NumBitsPerTMA, AuxParams_>
|
||||
blockIdx.x, blockIdx.y, blockIdx.z,
|
||||
int32_t(c0), int32_t(c1), int32_t(c2), int32_t(c3), int32_t(c4), src_ptr);
|
||||
#endif
|
||||
return detail::copy_explode<SM90_TMA_STORE>(make_tuple(desc_ptr, src_ptr), seq<0,1>{},
|
||||
dst_coord, tuple_seq<decltype(dst_coord)>{});
|
||||
return detail::explode_tuple(detail::CallCOPY<SM90_TMA_STORE>{},
|
||||
make_tuple(desc_ptr, src_ptr), seq<0,1>{},
|
||||
dst_coord, tuple_seq<decltype(dst_coord)>{});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1278,7 +1281,7 @@ tma_partition(Copy_Atom<Args...> const& copy_atom,
|
||||
// Factor out the single-instrucion portion
|
||||
Layout tma_layout_v = make_layout(Int<Copy_Atom<Args...>::NumValSrc>{});
|
||||
auto layout_V = make_tile(logical_divide(layout_v, tma_layout_v));
|
||||
|
||||
|
||||
// Append with _ until we cover all Rest... modes
|
||||
auto glayout_V = append<rank_v<decltype(gtensor)>>(layout_V, _);
|
||||
auto slayout_V = append<rank_v<decltype(stensor)>>(layout_V, _);
|
||||
@@ -1288,39 +1291,45 @@ tma_partition(Copy_Atom<Args...> const& copy_atom,
|
||||
|
||||
#if 0
|
||||
if (thread0()) {
|
||||
print("gtensor : "); print(gtensor); print("\n");
|
||||
print("stensor : "); print(stensor); print("\n");
|
||||
print("cta_coord : "); print(cta_coord); print("\n");
|
||||
print("cta_layout : "); print(cta_layout); print("\n");
|
||||
print("gtensor : "); print(gtensor); print("\n");
|
||||
print("stensor : "); print(stensor); print("\n");
|
||||
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);
|
||||
auto tma_layout_tv = make_tile(make_tile(make_layout(tma_layout_t, tma_layout_v), _));
|
||||
// Offset inside the TMA-mode for the multicast
|
||||
auto multicast_offset = cta_layout(cta_coord) * (size(tma_layout_v) / cosize(cta_layout));
|
||||
auto multicast_coord = make_coord(make_coord(multicast_offset, Int<0>{}));
|
||||
auto scoord = append<SLayout::rank>(multicast_coord, Int<0>{});
|
||||
auto gcoord = append<GLayout::rank>(multicast_coord, Int<0>{});
|
||||
|
||||
// Append with _ until we cover all Rest... modes
|
||||
auto gtma_layout_tv = append<rank_v<decltype(gtensor)>>(tma_layout_tv, _);
|
||||
auto stma_layout_tv = append<rank_v<decltype(stensor)>>(tma_layout_tv, _);
|
||||
Tensor gresult = domain_offset(gcoord, gtensor_v);
|
||||
Tensor sresult = domain_offset(scoord, stensor_v);
|
||||
|
||||
// Transform TMA mode
|
||||
Tensor gtensor_tv = gtensor_v.compose(gtma_layout_tv); // (((Thr,Frg),TMA_Iter), Rest...)
|
||||
Tensor stensor_tv = stensor_v.compose(stma_layout_tv); // (((Thr,Frg),TMA_Iter), Rest...)
|
||||
return cute::make_tuple(gresult, sresult);
|
||||
}
|
||||
|
||||
#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");
|
||||
// TMA Multicast Masks Calculation
|
||||
template <int Mode, class CtaLayout, class CtaCoord>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
create_tma_multicast_mask(CtaLayout const& cta_layout_vmnk,
|
||||
CtaCoord const& cta_coord_vmnk)
|
||||
{
|
||||
auto cta_coord_slicer = replace<Mode>(cta_coord_vmnk, _);
|
||||
auto [cta_layout, elected_cta] = slice_and_offset(cta_coord_slicer, cta_layout_vmnk);
|
||||
// Get the instruction code
|
||||
uint16_t mcast_mask = 0;
|
||||
for (int i = 0; i < size(cta_layout); ++i) {
|
||||
mcast_mask |= uint16_t(1) << cta_layout(i);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto c = make_coord(make_coord(make_coord(cta_coord, _), _));
|
||||
auto c_s = append<rank_v<decltype(stensor_tv)>>(c, _);
|
||||
auto c_g = append<rank_v<decltype(gtensor_tv)>>(c, _);
|
||||
|
||||
return cute::make_tuple(group_modes<0,2>(gtensor_tv(c_g)), group_modes<0,2>(stensor_tv(c_s)));
|
||||
// Shift by the instruction's elected block rank (dynamic)
|
||||
mcast_mask <<= elected_cta;
|
||||
return mcast_mask;
|
||||
}
|
||||
|
||||
} // end namespace cute
|
||||
|
||||
@@ -715,6 +715,7 @@ 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(" Shape_MNK: "); print(typename Atom::Shape_MNK{}); 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");
|
||||
|
||||
@@ -149,17 +149,17 @@ mma_unpack(MMA_Traits<MMA_Op, MMA_Args...> const& traits,
|
||||
//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_);
|
||||
detail::explode(MMA_Op::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{},
|
||||
&(traits.accumulate_), seq<0>{});
|
||||
}
|
||||
else {
|
||||
detail::explode(MMA_Op::fma,
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -169,19 +169,19 @@ mma_unpack(MMA_Traits<MMA_Op, MMA_Args...> const& traits,
|
||||
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,
|
||||
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>{},
|
||||
traits.accumulate_);
|
||||
&(traits.accumulate_), seq<0>{});
|
||||
}
|
||||
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>{});
|
||||
rD, make_int_sequence<RegNumD>{},
|
||||
rA, make_int_sequence<RegNumA>{},
|
||||
rB, make_int_sequence<RegNumB>{},
|
||||
rC, make_int_sequence<RegNumC>{});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ template <class MMA_Op, class... MMA_Args,
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
void
|
||||
mma_unpack(MMA_Traits<MMA_Op, MMA_Args...> const& traits,
|
||||
Tensor<TD, DLayout> && D,
|
||||
Tensor<TD, DLayout> && D,
|
||||
Tensor<TA, ALayout> const& A,
|
||||
Tensor<TB, BLayout> const& B,
|
||||
Tensor<TC, CLayout> const& C)
|
||||
|
||||
@@ -208,7 +208,7 @@ make_gmma_desc(Tensor<TEngine,TLayout> const& tensor)
|
||||
|
||||
// Start address (4LSB not included)
|
||||
uint32_t start_address = cast_smem_ptr_to_uint(raw_pointer_cast(u128_tensor.data()));
|
||||
desc.bitfield.start_address_ = start_address >> 4;
|
||||
desc.bitfield.start_address_ = static_cast<uint16_t>(start_address >> 4);
|
||||
|
||||
constexpr uint8_t base_offset = 0;
|
||||
desc.bitfield.base_offset_ = base_offset;
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
// It's harmless to use the macro for other GCC versions or other
|
||||
// compilers, but it has no effect.
|
||||
#if ! defined(CUTE_GCC_UNREACHABLE)
|
||||
# if defined(__clang__) || defined(__GNUC__)
|
||||
# if defined(__GNUC__)
|
||||
# define CUTE_GCC_UNREACHABLE __builtin_unreachable()
|
||||
# else
|
||||
# define CUTE_GCC_UNREACHABLE
|
||||
|
||||
@@ -325,10 +325,21 @@ CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
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 implicitly 1
|
||||
return transform(a, append<R>(b,Int<1>{}), [](auto const& x, auto const& y) { return ceil_div(x,y); });
|
||||
if constexpr (is_tuple<IntTupleA>::value) {
|
||||
if constexpr (is_tuple<IntTupleB>::value) { // tuple tuple
|
||||
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 ceil_div(x,y); });
|
||||
} else { // tuple int
|
||||
auto const [result, rest] = fold(a, cute::make_tuple(cute::make_tuple(), b),
|
||||
[] (auto const& init, auto const& ai) {
|
||||
return cute::make_tuple(append(get<0>(init), ceil_div(ai, get<1>(init))), ceil_div(get<1>(init), ai));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
} else
|
||||
if constexpr (is_tuple<IntTupleB>::value) { // int tuple
|
||||
return ceil_div(a, product(b));
|
||||
} else {
|
||||
return (a + b - Int<1>{}) / b;
|
||||
}
|
||||
|
||||
+131
-73
@@ -418,8 +418,8 @@ make_layout_like(Layout<Shape,Stride> const& layout)
|
||||
// Make a compact layout with the same shape as @a layout
|
||||
// and strides following the order induced by @a layout.stride(),
|
||||
// except mode-0 is always stride-1 and generated column-major.
|
||||
// The 0th mode is commonly used for MMA_Atoms or Copy_Atoms
|
||||
// so this generates the 0th mode with LayoutLeft regardless of the reference layout.
|
||||
// The 0th mode is commonly used for MMA_Atoms or Copy_Atoms so this
|
||||
// generates the 0th mode with LayoutLeft (preserving stride-0s) regardless of the reference layout
|
||||
template <class Shape, class Stride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -427,7 +427,8 @@ make_fragment_like(Layout<Shape,Stride> const& layout)
|
||||
{
|
||||
constexpr int R = Layout<Shape,Stride>::rank;
|
||||
if constexpr (R > 1 && is_static<Shape>::value) {
|
||||
return tiled_product(make_layout(shape<0>(layout)),
|
||||
return tiled_product(make_layout(get<0>(layout.shape()),
|
||||
compact_col_major(filter_zeros(get<0>(layout.stride()), get<0>(layout.shape())))),
|
||||
make_ordered_layout(take<1,R>(layout.shape()), take<1,R>(layout.stride())));
|
||||
} else {
|
||||
return make_layout(layout.shape());
|
||||
@@ -757,7 +758,8 @@ bw_coalesce(OldShape const& old_shape, OldStride const& old_stride,
|
||||
} else if constexpr (is_constant<1, NewShape>::value) {
|
||||
// Replace our shape-1 with anything (Can only happen on input new_shape/new_stride)
|
||||
return bw_coalesce<I-1>(old_shape, old_stride, get<I>(old_shape), get<I>(old_stride));
|
||||
} else if constexpr (is_constant<true, decltype(get<I>(old_shape) * get<I>(old_stride) == get<0>(new_stride))>::value) {
|
||||
} else if constexpr (is_static<decltype(get<0>(new_shape))>::value &&
|
||||
is_constant<true, decltype(get<I>(old_shape) * get<I>(old_stride) == get<0>(new_stride))>::value) {
|
||||
// Merge modes because the shapes and strides match
|
||||
return bw_coalesce<I-1>(old_shape, old_stride,
|
||||
replace_front(new_shape, get<I>(old_shape) * get<0>(new_shape)),
|
||||
@@ -772,6 +774,45 @@ bw_coalesce(OldShape const& old_shape, OldStride const& old_stride,
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
// cute::coalesce promises to not change the Layout as a function from integers to codomain.
|
||||
// It accomplishes this inside of the Layout's domain, but not always outside of the domain.
|
||||
// Example: (_4,_1):(_1,_0) coalesces to _4:_1.
|
||||
// detail::coalesce_x preserves the Layout function inside its domain and outside.
|
||||
//
|
||||
// @post depth(@a result) <= 1
|
||||
// @post for all i, 0 <= i, @a layout(i) == @a result(i)
|
||||
template <class Shape, class Stride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
coalesce_x(Layout<Shape,Stride> const& layout)
|
||||
{
|
||||
auto flat_shape = flatten(layout.shape());
|
||||
auto flat_stride = flatten(layout.stride());
|
||||
|
||||
constexpr int R = decltype(rank(flat_shape))::value;
|
||||
if constexpr (is_constant<1, decltype(get<R-1>(flat_shape))>::value) {
|
||||
return detail::bw_coalesce<R-2>(flat_shape, flat_stride, Int<2>{}, get<R-1>(flat_stride));
|
||||
} else {
|
||||
return detail::bw_coalesce<R-2>(flat_shape, flat_stride, get<R-1>(flat_shape), get<R-1>(flat_stride));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply coalesce_x at the terminals of trg_profile
|
||||
template <class Shape, class Stride, class IntTuple>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
coalesce_x(Layout<Shape,Stride> const& layout, IntTuple const& trg_profile)
|
||||
{
|
||||
if constexpr (is_tuple<IntTuple>::value) {
|
||||
static_assert(tuple_size<IntTuple>::value <= Layout<Shape,Stride>::rank);
|
||||
return cute::transform_layout(layout, trg_profile, [](auto const& l, auto const& t) { return coalesce_x(l,t); });
|
||||
} else {
|
||||
return coalesce_x(layout);
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
// "Simplify" the layout by combining modes that are possible to combine
|
||||
@@ -807,6 +848,25 @@ coalesce(Layout<Shape,Stride> const& layout, IntTuple const& trg_profile)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
// Combine static and dynamic modes of a shape.
|
||||
// @post size(@a result) == size(@a shape)
|
||||
// @post depth(@a result) <= 1
|
||||
template <class Shape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
coalesce(Shape const& shape)
|
||||
{
|
||||
static_assert(is_integral<Shape>::value || is_tuple<Shape>::value);
|
||||
|
||||
return cute::fold_first(flatten(shape), [](auto const& init, auto const& a) {
|
||||
if constexpr (is_static<decltype(back(init))>::value == is_static<decltype(a)>::value) {
|
||||
return replace_back(init, back(init) * a); // Both static or both dynamic, coalesce and replace
|
||||
} else {
|
||||
return append(init, a); // Can't coalesce, so append
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Replace the modes in layout that have a 0-stride with a 1-size
|
||||
template <class Shape, class Stride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -918,70 +978,64 @@ template <class LShape, class LStride,
|
||||
class RShape, class RStride>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
composition_impl(Layout<LShape,LStride> const& lhs,
|
||||
composition_impl(LShape const& lhs_shape, LStride const& lhs_stride,
|
||||
RShape const& rhs_shape, RStride const& rhs_stride)
|
||||
{
|
||||
if constexpr (is_tuple<RShape>::value) {
|
||||
// Apply the right-distributivity of Layout composition
|
||||
return transform_layout(rhs_shape, rhs_stride, [&](auto const& s, auto const& d) { return composition_impl(lhs, s, d); });
|
||||
return transform_layout(rhs_shape, rhs_stride, [&](auto const& s, auto const& d) {
|
||||
return composition_impl(lhs_shape, lhs_stride, s, d);
|
||||
});
|
||||
} else
|
||||
if constexpr (is_scaled_basis<RStride>::value) {
|
||||
// Special case for a ScaledBasis stride
|
||||
return composition_impl(get<RStride::mode()>(lhs), rhs_shape, rhs_stride.value());
|
||||
return composition_impl(basis_get(rhs_stride, lhs_shape), basis_get(rhs_stride, lhs_stride),
|
||||
rhs_shape, basis_value(rhs_stride));
|
||||
} else
|
||||
if constexpr (is_integral<RStride>::value) {
|
||||
// Integral Rstride (and RShape)
|
||||
if constexpr (is_constant<0, RStride>::value) {
|
||||
// Special case shortcut for any static stride-0
|
||||
return Layout<RShape, RStride>{rhs_shape, rhs_stride};
|
||||
} else
|
||||
if constexpr (is_integral<decltype(lhs_shape)>::value) {
|
||||
// Special case shortcut for any integral LShape
|
||||
return Layout{rhs_shape, rhs_stride * lhs_stride};
|
||||
} else
|
||||
if constexpr (is_constant<1, RStride>::value) {
|
||||
// Special case shortcut for any static stride-1
|
||||
constexpr int R = rank_v<LShape>;
|
||||
auto result_shape_0 = take<0,R-1>(lhs_shape);
|
||||
|
||||
// NOTE: Should only flatten once for efficiency
|
||||
auto flat_shape = flatten(lhs.shape());
|
||||
[[maybe_unused]] auto flat_stride = flatten(lhs.stride());
|
||||
[[maybe_unused]] constexpr int R = rank(flat_shape);
|
||||
// Mod out the rhs_shape from the lhs_shape
|
||||
auto const [result_shape_1, rest_shape] = fold(result_shape_0, cute::make_tuple(cute::make_tuple(), rhs_shape),
|
||||
[] (auto const& init, auto const& si) {
|
||||
return cute::make_tuple(append(get<0>(init), shape_min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
});
|
||||
|
||||
if constexpr (is_constant<0, RStride>::value) {
|
||||
// Special case shortcut for any static stride-0
|
||||
return Layout<RShape, RStride>{rhs_shape, rhs_stride};
|
||||
} else
|
||||
if constexpr (is_integral<decltype(flat_shape)>::value) {
|
||||
// Special case shortcut for any integral LShape
|
||||
auto result_stride = rhs_stride * flat_stride;
|
||||
return Layout<RShape, decltype(result_stride)>{rhs_shape, result_stride};
|
||||
} else
|
||||
if constexpr (is_constant<1, RStride>::value) {
|
||||
// Special case shortcut for any static stride-1
|
||||
auto result_shape_0 = take<0,R-1>(flat_shape);
|
||||
// Jump into coalesce and append (rest_shape, get<R-1>(lhs_stride))
|
||||
return detail::bw_coalesce<R-2>(result_shape_1, lhs_stride, rest_shape, get<R-1>(lhs_stride));
|
||||
} else {
|
||||
// General case: integral RShape and RStride, tuple LShape and LStride
|
||||
constexpr int R = rank_v<LShape>;
|
||||
auto result_shape_0 = take<0,R-1>(lhs_shape);
|
||||
auto result_stride_0 = take<0,R-1>(lhs_stride);
|
||||
|
||||
// Mod out the rhs_shape from the lhs.shape()
|
||||
auto const [result_shape_1, rest_shape] = fold(result_shape_0, cute::make_tuple(cute::make_tuple(), rhs_shape),
|
||||
[] (auto const& init, auto const& si) {
|
||||
return cute::make_tuple(append(get<0>(init), shape_min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
});
|
||||
// Divide out the rhs_stride from the lhs_shape
|
||||
auto const [result_shape_1, rest_stride] = fold(result_shape_0, cute::make_tuple(cute::make_tuple(), rhs_stride),
|
||||
[] (auto const& init, auto const& di) {
|
||||
return cute::make_tuple(append(get<0>(init), shape_div(di, get<1>(init))), shape_div(get<1>(init), di));
|
||||
});
|
||||
|
||||
// Jump into coalesce and append (rest_shape, get<R-1>(lhs.stride())
|
||||
return detail::bw_coalesce<R-2>(result_shape_1, flat_stride, rest_shape, get<R-1>(flat_stride));
|
||||
} else
|
||||
{
|
||||
// General case
|
||||
auto result_shape_0 = take<0,R-1>(flat_shape);
|
||||
auto result_stride_0 = take<0,R-1>(flat_stride);
|
||||
// Apply any lhs_shape changes to the stride
|
||||
auto result_stride_1 = elem_scale(result_stride_0, shape_div(result_shape_0, result_shape_1));
|
||||
|
||||
// Divide out the rhs_stride from the lhs.shape()
|
||||
auto const [result_shape_1, rest_stride] = fold(result_shape_0, cute::make_tuple(cute::make_tuple(), rhs_stride),
|
||||
[] (auto const& init, auto const& di) {
|
||||
return cute::make_tuple(append(get<0>(init), shape_div(di, get<1>(init))), shape_div(get<1>(init), di));
|
||||
});
|
||||
// Mod out the rhs_shape from the lhs_shape
|
||||
auto const [result_shape_2, rest_shape] = fold(result_shape_1, cute::make_tuple(cute::make_tuple(), rhs_shape),
|
||||
[] (auto const& init, auto const& si) {
|
||||
return cute::make_tuple(append(get<0>(init), shape_min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
});
|
||||
|
||||
// Apply any lhs.shape() changes to the stride
|
||||
auto result_stride_1 = elem_scale(result_stride_0, shape_div(result_shape_0, result_shape_1));
|
||||
|
||||
// Mod out the rhs_shape from the lhs.shape()
|
||||
auto const [result_shape_2, rest_shape] = fold(result_shape_1, cute::make_tuple(cute::make_tuple(), rhs_shape),
|
||||
[] (auto const& init, auto const& si) {
|
||||
return cute::make_tuple(append(get<0>(init), shape_min(abs(si), get<1>(init))), shape_div(get<1>(init), abs(si)));
|
||||
});
|
||||
|
||||
// Jump into coalesce and append (rest_shape, rest_stride * get<R-1>(lhs.stride())
|
||||
return detail::bw_coalesce<R-2>(result_shape_2, result_stride_1, rest_shape, rest_stride * get<R-1>(flat_stride));
|
||||
}
|
||||
// Jump into coalesce and append (rest_shape, rest_stride * get<R-1>(lhs_stride))
|
||||
return detail::bw_coalesce<R-2>(result_shape_2, result_stride_1, rest_shape, rest_stride * get<R-1>(lhs_stride));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -996,7 +1050,9 @@ auto
|
||||
composition(Layout<LShape,LStride> const& lhs,
|
||||
Layout<RShape,RStride> const& rhs)
|
||||
{
|
||||
return detail::composition_impl(lhs, rhs.shape(), rhs.stride());
|
||||
auto coprofile = repeat_like(decltype(coshape(rhs)){}, Int<0>{});
|
||||
auto flat_lhs = detail::coalesce_x(lhs, coprofile);
|
||||
return detail::composition_impl(flat_lhs.shape(), flat_lhs.stride(), rhs.shape(), rhs.stride());
|
||||
}
|
||||
|
||||
template <class LShape, class LStride, class Tiler>
|
||||
@@ -1012,7 +1068,8 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
} else if constexpr (is_underscore<Tiler>::value) {
|
||||
return lhs;
|
||||
} else if constexpr (is_integral<Tiler>::value) {
|
||||
return detail::composition_impl(lhs, rhs, Int<1>{});
|
||||
auto flat_lhs = detail::coalesce_x(lhs);
|
||||
return detail::composition_impl(flat_lhs.shape(), flat_lhs.stride(), rhs, Int<1>{});
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -1032,14 +1089,14 @@ composition(Layout<LShape,LStride> const& lhs,
|
||||
namespace detail {
|
||||
|
||||
// @pre @a layout has been filtered (flattened and no stride-0 or size-1 modes).
|
||||
template <class Shape, class Stride, class CoSizeHi>
|
||||
template <class Shape, class Stride, class CoTarget>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
complement(Shape const& shape, Stride const& stride, CoSizeHi const& cosize_hi)
|
||||
complement(Shape const& shape, Stride const& stride, CoTarget const& cotarget)
|
||||
{
|
||||
if constexpr (is_constant<0, Stride>::value) {
|
||||
// Special case for irreducible rank-1 stride-0 layout
|
||||
return make_layout(cosize_hi);
|
||||
return make_layout(coalesce(cotarget));
|
||||
} else {
|
||||
// General case
|
||||
constexpr int R = rank_v<Shape>;
|
||||
@@ -1055,28 +1112,30 @@ complement(Shape const& shape, Stride const& stride, CoSizeHi const& cosize_hi)
|
||||
{
|
||||
auto [shape, stride, result_shape, result_stride] = init;
|
||||
auto min_stride = cute::min(stride);
|
||||
auto min_idx = find(stride, min_stride);
|
||||
auto min_idx = cute::find(stride, min_stride);
|
||||
auto new_shape = min_stride / get<i>(result_stride);
|
||||
auto new_stride = get<min_idx>(shape) * min_stride;
|
||||
auto new_stride = min_stride * get<min_idx>(shape);
|
||||
static_assert(not is_constant<0, decltype(new_shape)>::value, "Non-injective Layout detected in complement.");
|
||||
|
||||
return cute::make_tuple(remove<min_idx>(shape), // Remove the min_idx from shape
|
||||
remove<min_idx>(stride), // Remove the min_idx from stride
|
||||
append(result_shape , new_shape ), // new shape = min_stride / last_stride
|
||||
append(result_stride, new_stride)); // new stride = curr_shape * min_stride
|
||||
append(result_stride, new_stride)); // new stride = min_stride * curr_shape
|
||||
});
|
||||
|
||||
// Append the last shape mode
|
||||
auto new_shape = get<0>(stride_) / get<R-1>(result_stride);
|
||||
auto new_shape = get<0>(stride_) / get<R-1>(result_stride); // new shape = min_stride / last_stride
|
||||
static_assert(not is_constant<0, decltype(new_shape)>::value, "Non-injective Layout detected in complement.");
|
||||
auto result_shape = append(result_shape_, new_shape); // new shape = min_stride / last_stride
|
||||
auto result_shape = append(result_shape_, new_shape);
|
||||
|
||||
// Compute the rest_shape and rest_stride
|
||||
auto rest_stride = get<0>(shape_) * get<0>(stride_);
|
||||
auto rest_shape = ceil_div(cosize_hi, rest_stride);
|
||||
auto new_stride = get<0>(stride_) * get<0>(shape_); // new stride = min_stride * curr_shape
|
||||
auto rest_shape = coalesce(ceil_div(cotarget, new_stride));
|
||||
auto rest_stride = compact_col_major(rest_shape, new_stride);
|
||||
|
||||
// Jump into coalesce and append (rest_shape, rest_stride)
|
||||
return detail::bw_coalesce<R-1>(result_shape, result_stride, rest_shape, rest_stride);
|
||||
// Coalesce and append (rest_shape, rest_stride)
|
||||
return coalesce(make_layout(make_shape (result_shape , rest_shape ),
|
||||
make_stride(result_stride, rest_stride)));
|
||||
}
|
||||
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
@@ -1084,14 +1143,13 @@ complement(Shape const& shape, Stride const& stride, CoSizeHi const& cosize_hi)
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template <class Shape, class Stride, class CoSizeHi>
|
||||
template <class Shape, class Stride, class CoTarget>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
complement(Layout<Shape,Stride> const& layout, CoSizeHi const& cosize_hi)
|
||||
complement(Layout<Shape,Stride> const& layout, CoTarget const& cotarget)
|
||||
{
|
||||
static_assert(cute::is_integral<CoSizeHi>::value, "Expected integral codomain size in complement.");
|
||||
auto filter_layout = filter(layout);
|
||||
return detail::complement(filter_layout.shape(), filter_layout.stride(), cosize_hi);
|
||||
return detail::complement(filter_layout.shape(), filter_layout.stride(), shape(cotarget));
|
||||
}
|
||||
|
||||
template <class Shape, class Stride>
|
||||
@@ -1365,7 +1423,7 @@ auto
|
||||
logical_divide(Layout<LShape,LStride> const& layout,
|
||||
Layout<TShape,TStride> const& tiler)
|
||||
{
|
||||
return composition(layout, make_layout(tiler, complement(tiler, size(layout))));
|
||||
return composition(layout, make_layout(tiler, complement(tiler, shape(layout))));
|
||||
}
|
||||
|
||||
template <class LShape, class LStride, class Tiler>
|
||||
|
||||
@@ -392,12 +392,12 @@ composition(Layout<ShapeA,StrideA> const& a,
|
||||
// complement
|
||||
//
|
||||
|
||||
template <class A, class O, class B, class CoSizeHi>
|
||||
template <class A, class O, class B, class CoTarget>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
complement(ComposedLayout<A,O,B> const& layout, CoSizeHi const& cosize_hi)
|
||||
complement(ComposedLayout<A,O,B> const& layout, CoTarget const& cotarget)
|
||||
{
|
||||
return complement(layout.layout_b(), cosize_hi);
|
||||
return complement(layout.layout_b(), cotarget);
|
||||
}
|
||||
|
||||
template <class A, class O, class B>
|
||||
@@ -610,7 +610,7 @@ recast_layout(ComposedLayout<A,O,B> const& layout)
|
||||
else if constexpr (scale::num == 1) {
|
||||
return downcast<scale::den>(layout);
|
||||
}
|
||||
else if constexpr (scale::den == 1) {
|
||||
else if constexpr (scale::den == 1) {
|
||||
return upcast<scale::num>(layout);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -73,25 +73,17 @@ make_arithmetic_tuple(T const&... t) {
|
||||
return ArithmeticTuple<T...>(t...);
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
template <class T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(tuple<T...> const& t) {
|
||||
return ArithmeticTuple<T...>(t);
|
||||
}
|
||||
|
||||
template <class T, __CUTE_REQUIRES(is_integral<T>::value)>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
T const&
|
||||
as_arithmetic_tuple(T const& t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(ArithmeticTuple<T...> const& t) {
|
||||
return t;
|
||||
if constexpr (is_tuple<T>::value) {
|
||||
return detail::tapply(t, [](auto const& x){ return as_arithmetic_tuple(x); },
|
||||
[](auto const&... a){ return make_arithmetic_tuple(a...); },
|
||||
tuple_seq<T>{});
|
||||
} else {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -289,6 +281,26 @@ basis_get(SB const& e, Tuple const& t)
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T, int... I>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
to_atuple_i(T const& t, seq<I...>) {
|
||||
return make_arithmetic_tuple((void(I),Int<0>{})..., t);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
// Turn a ScaledBases<T,N> into a rank-N+1 ArithmeticTuple
|
||||
// with N prefix 0s: (_0,_0,...N...,_0,T)
|
||||
template <class T, int N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(ScaledBasis<T,N> const& t) {
|
||||
return detail::to_atuple_i(as_arithmetic_tuple(t.value()), make_seq<N>{});
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <int... Ns>
|
||||
struct Basis;
|
||||
|
||||
@@ -315,71 +327,6 @@ struct Basis<N,Ns...> {
|
||||
template <int... N>
|
||||
using E = typename detail::Basis<N...>::type;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T, int... I, int... J>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(T const& t, seq<I...>, seq<J...>) {
|
||||
return make_arithmetic_tuple((void(I),Int<0>{})..., t, (void(J),Int<0>{})...);
|
||||
}
|
||||
|
||||
template <class... T, int... I, int... J>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(ArithmeticTuple<T...> const& t, seq<I...>, seq<J...>) {
|
||||
return make_arithmetic_tuple(get<I>(t)..., (void(J),Int<0>{})...);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
// Turn a ScaledBases<T,N> into a rank-M ArithmeticTuple
|
||||
// with N prefix 0s: (_0,_0,...N...,_0,T,_0,...,_0,_0)
|
||||
template <int M, class T, int N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(ScaledBasis<T,N> const& t) {
|
||||
static_assert(M > N, "Mismatched ranks");
|
||||
return detail::as_arithmetic_tuple(t.value(), make_seq<N>{}, make_seq<M-N-1>{});
|
||||
}
|
||||
|
||||
// Turn a ScaledBases<T,N> into a rank-N ArithmeticTuple
|
||||
// with N prefix 0s: (_0,_0,...N...,_0,T)
|
||||
template <class T, int N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(ScaledBasis<T,N> const& t) {
|
||||
return as_arithmetic_tuple<N+1>(t);
|
||||
}
|
||||
|
||||
// Turn an ArithmeticTuple into a rank-M ArithmeticTuple
|
||||
// with postfix 0s: (t0,t1,t2,...,_0,...,_0,_0)
|
||||
template <int M, class... T>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
as_arithmetic_tuple(ArithmeticTuple<T...> const& t) {
|
||||
static_assert(M >= sizeof...(T), "Mismatched ranks");
|
||||
return detail::as_arithmetic_tuple(t, make_seq<int(sizeof...(T))>{}, make_seq<M-int(sizeof...(T))>{});
|
||||
}
|
||||
|
||||
template <class T, int M, class U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
safe_div(ScaledBasis<T,M> const& b, U const& u)
|
||||
{
|
||||
auto t = safe_div(b.value(), u);
|
||||
return ScaledBasis<decltype(t),M>{t};
|
||||
}
|
||||
|
||||
template <class T, int M, class U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
shape_div(ScaledBasis<T,M> const& b, U const& u)
|
||||
{
|
||||
auto t = shape_div(b.value(), u);
|
||||
return ScaledBasis<decltype(t),M>{t};
|
||||
}
|
||||
|
||||
template <class Shape>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
@@ -387,8 +334,7 @@ make_basis_like(Shape const& shape)
|
||||
{
|
||||
if constexpr (is_integral<Shape>::value) {
|
||||
return Int<1>{};
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Generate bases for each rank of shape
|
||||
return transform(tuple_seq<Shape>{}, shape, [](auto I, auto si) {
|
||||
// Generate bases for each rank of si and add an i on front
|
||||
@@ -408,6 +354,28 @@ make_basis_like(Shape const& shape)
|
||||
CUTE_GCC_UNREACHABLE;
|
||||
}
|
||||
|
||||
//
|
||||
// Arithmetic
|
||||
//
|
||||
|
||||
template <class T, int M, class U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
safe_div(ScaledBasis<T,M> const& b, U const& u)
|
||||
{
|
||||
auto t = safe_div(b.value(), u);
|
||||
return ScaledBasis<decltype(t),M>{t};
|
||||
}
|
||||
|
||||
template <class T, int M, class U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
shape_div(ScaledBasis<T,M> const& b, U const& u)
|
||||
{
|
||||
auto t = shape_div(b.value(), u);
|
||||
return ScaledBasis<decltype(t),M>{t};
|
||||
}
|
||||
|
||||
// Equality
|
||||
template <class T, int N, class U, int M>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
@@ -432,7 +400,7 @@ operator==(T const&, ScaledBasis<U,M> const&) {
|
||||
}
|
||||
|
||||
// Abs
|
||||
template <int N, class T>
|
||||
template <class T, int N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
abs(ScaledBasis<T,N> const& e) {
|
||||
@@ -440,7 +408,7 @@ abs(ScaledBasis<T,N> const& e) {
|
||||
}
|
||||
|
||||
// Multiplication
|
||||
template <class A, int N, class T>
|
||||
template <class A, class T, int N>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator*(A const& a, ScaledBasis<T,N> const& e) {
|
||||
@@ -448,7 +416,7 @@ operator*(A const& a, ScaledBasis<T,N> const& e) {
|
||||
return ScaledBasis<decltype(r),N>{r};
|
||||
}
|
||||
|
||||
template <int N, class T, class B>
|
||||
template <class T, int N, class B>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator*(ScaledBasis<T,N> const& e, B const& b) {
|
||||
@@ -457,44 +425,25 @@ operator*(ScaledBasis<T,N> const& e, B const& b) {
|
||||
}
|
||||
|
||||
// Addition
|
||||
template <int N, class T, class... U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(ScaledBasis<T,N> const& t, ArithmeticTuple<U...> const& u) {
|
||||
constexpr int R = cute::max(N+1, int(sizeof...(U)));
|
||||
return as_arithmetic_tuple<R>(t) + as_arithmetic_tuple<R>(u);
|
||||
}
|
||||
|
||||
template <class... T, int M, class U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(ArithmeticTuple<T...> const& t, ScaledBasis<U,M> const& u) {
|
||||
constexpr int R = cute::max(int(sizeof...(T)), M+1);
|
||||
return as_arithmetic_tuple<R>(t) + as_arithmetic_tuple<R>(u);
|
||||
}
|
||||
|
||||
template <int N, class T, class... U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(ScaledBasis<T,N> const& t, tuple<U...> const& u) {
|
||||
constexpr int R = cute::max(N+1, int(sizeof...(U)));
|
||||
return as_arithmetic_tuple<R>(t) + as_arithmetic_tuple(u);
|
||||
}
|
||||
|
||||
template <class... T, int M, class U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(tuple<T...> const& t, ScaledBasis<U,M> const& u) {
|
||||
constexpr int R = cute::max(int(sizeof...(T)), M+1);
|
||||
return as_arithmetic_tuple(t) + as_arithmetic_tuple<R>(u);
|
||||
}
|
||||
|
||||
template <int N, class T, int M, class U>
|
||||
template <class T, int N, class U, int M>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(ScaledBasis<T,N> const& t, ScaledBasis<U,M> const& u) {
|
||||
constexpr int R = cute::max(N+1,M+1);
|
||||
return as_arithmetic_tuple<R>(t) + as_arithmetic_tuple<R>(u);
|
||||
return as_arithmetic_tuple(t) + as_arithmetic_tuple(u);
|
||||
}
|
||||
|
||||
template <class T, int N, class... U>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(ScaledBasis<T,N> const& t, ArithmeticTuple<U...> const& u) {
|
||||
return as_arithmetic_tuple(t) + u;
|
||||
}
|
||||
|
||||
template <class... T, class U, int M>
|
||||
CUTE_HOST_DEVICE constexpr
|
||||
auto
|
||||
operator+(ArithmeticTuple<T...> const& t, ScaledBasis<U,M> const& u) {
|
||||
return t + as_arithmetic_tuple(u);
|
||||
}
|
||||
|
||||
template <auto t, class U, int M>
|
||||
|
||||
@@ -56,10 +56,10 @@ fma(complex<T> & d,
|
||||
complex<T> const& b,
|
||||
complex<T> const& c)
|
||||
{
|
||||
d.real(fma( a.real(), b.real(), c.real()));
|
||||
d.imag(fma( a.real(), b.imag(), c.imag()));
|
||||
d.real(fma(-a.imag(), b.imag(), d.real()));
|
||||
d.imag(fma( a.imag(), b.real(), d.imag()));
|
||||
fma(d.real(), a.real(), b.real(), c.real());
|
||||
fma(d.imag(), a.real(), b.imag(), c.imag());
|
||||
fma(d.real(), -a.imag(), b.imag(), d.real());
|
||||
fma(d.imag(), a.imag(), b.real(), d.imag());
|
||||
}
|
||||
|
||||
/// Fused multiply-add for triplets
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
|
||||
#include <cute/pointer_base.hpp>
|
||||
#include <cute/pointer_swizzle.hpp>
|
||||
#include <cute/layout.hpp>
|
||||
namespace cute
|
||||
{
|
||||
|
||||
@@ -102,6 +101,8 @@ template <class P> // Found the gmem
|
||||
struct is_gmem<gmem_ptr<P>> : true_type {};
|
||||
template <class P> // Recurse on ::iterator, if possible
|
||||
struct is_gmem<P, void_t<typename P::iterator>> : is_gmem<typename P::iterator> {};
|
||||
template <class P>
|
||||
constexpr bool is_gmem_v = is_gmem<P>::value;
|
||||
|
||||
// Idempotent gmem tag on an iterator
|
||||
template <class Iterator>
|
||||
@@ -163,6 +164,8 @@ template <class P> // Found the smem
|
||||
struct is_smem<smem_ptr<P>> : true_type {};
|
||||
template <class P> // Recurse on ::iterator, if possible
|
||||
struct is_smem<P, void_t<typename P::iterator>> : is_smem<typename P::iterator> {};
|
||||
template <class P>
|
||||
constexpr bool is_smem_v = is_smem<P>::value;
|
||||
|
||||
// Idempotent smem tag on an iterator
|
||||
template <class Iterator>
|
||||
@@ -224,6 +227,8 @@ template <class T, class = void>
|
||||
struct is_rmem : bool_constant<not (is_gmem<T>::value || is_smem<T>::value)> {};
|
||||
template <class P>
|
||||
struct is_rmem<rmem_ptr<P>> : true_type {};
|
||||
template <class P>
|
||||
constexpr bool is_rmem_v = is_rmem<P>::value;
|
||||
|
||||
// Idempotent rmem tag on an iterator
|
||||
template <class Iterator>
|
||||
|
||||
@@ -89,7 +89,7 @@ downcast(ComposedLayout<SwizzleFn,smem_ptr_flag_bits<B>,Layout> const& layout)
|
||||
// Conversion with swizzle_layout
|
||||
//
|
||||
|
||||
template <class T, class SwizzleFn, int B, class Layout>
|
||||
template <class SwizzleFn, int B, class Layout>
|
||||
CUTE_HOST_DEVICE
|
||||
auto
|
||||
as_position_independent_swizzle_layout(ComposedLayout<SwizzleFn,smem_ptr_flag_bits<B>,Layout> const& layout)
|
||||
@@ -129,6 +129,14 @@ as_position_independent_swizzle_tensor(Tensor&& tensor)
|
||||
//
|
||||
|
||||
// Capture and cast smem_ptr_flag Layouts to offset-0 layouts
|
||||
template <class SwizzleFn, int B, class Layout>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
print_layout(ComposedLayout<SwizzleFn,smem_ptr_flag_bits<B>,Layout> const& layout)
|
||||
{
|
||||
print_layout(as_position_independent_swizzle_layout(layout));
|
||||
}
|
||||
|
||||
template <class SwizzleFn, int B, class Layout>
|
||||
CUTE_HOST_DEVICE
|
||||
void
|
||||
|
||||
@@ -316,6 +316,8 @@ template <class T>
|
||||
struct is_tensor : false_type {};
|
||||
template <class Engine, class Layout>
|
||||
struct is_tensor<Tensor<Engine,Layout>> : true_type {};
|
||||
template <class T>
|
||||
constexpr bool is_tensor_v = is_tensor<T>::value;
|
||||
|
||||
// Customization point for creation of owning and non-owning Tensors
|
||||
template <class T>
|
||||
@@ -1082,7 +1084,6 @@ CUTE_HOST std::ostream& operator<<(std::ostream& os, Tensor<Engine,Layout> const
|
||||
|
||||
#include <cute/pointer_swizzle.hpp>
|
||||
#include <cute/pointer_flagged.hpp>
|
||||
|
||||
//
|
||||
// Tensor Algorithms
|
||||
//
|
||||
|
||||
@@ -101,6 +101,9 @@ using CUTE_STL_NAMESPACE::is_lvalue_reference_v;
|
||||
using CUTE_STL_NAMESPACE::is_reference;
|
||||
using CUTE_STL_NAMESPACE::is_trivially_copyable;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_convertible;
|
||||
using CUTE_STL_NAMESPACE::is_convertible_v;
|
||||
|
||||
using CUTE_STL_NAMESPACE::is_same;
|
||||
using CUTE_STL_NAMESPACE::is_same_v;
|
||||
|
||||
@@ -247,4 +250,15 @@ is_valid(F&&, Args&&...) {
|
||||
return detail::is_valid_impl<F&&, Args&&...>(int{});
|
||||
}
|
||||
|
||||
template <bool B, template<class...> class True, template<class...> class False>
|
||||
struct conditional_template {
|
||||
template <class... U>
|
||||
using type = True<U...>;
|
||||
};
|
||||
|
||||
template <template<class...> class True, template<class...> class False>
|
||||
struct conditional_template<false, True, False> {
|
||||
template <class... U>
|
||||
using type = False<U...>;
|
||||
};
|
||||
} // end namespace cute
|
||||
|
||||
Reference in New Issue
Block a user